diff --git a/CHANGES.md b/CHANGES.md index a7459a205b..1e3caf8d7d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -99,6 +99,7 @@ - Fix paste without selection sends the new element in the back [Taiga #12382](https://tree.taiga.io/project/penpot/issue/12382) - Fix options button does not work for comments created in the lower part of the screen [Taiga #12422](https://tree.taiga.io/project/penpot/issue/12422) - Fix problem when checking usage with removed teams [Taiga #12442](https://tree.taiga.io/project/penpot/issue/12442) +- Fix focus mode persisting across page/file navigation [Taiga #12469](https://tree.taiga.io/project/penpot/issue/12469) ## 2.10.1 diff --git a/backend/deps.edn b/backend/deps.edn index a6f6fcfecd..31b5e48096 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -28,8 +28,8 @@ com.google.guava/guava {:mvn/version "33.4.8-jre"} funcool/yetti - {:git/tag "v11.6" - :git/sha "94dc017" + {:git/tag "v11.8" + :git/sha "1d1b33f" :git/url "https://github.com/funcool/yetti.git" :exclusions [org.slf4j/slf4j-api]} diff --git a/backend/src/app/binfile/common.clj b/backend/src/app/binfile/common.clj index 1f6ab99cf2..cf8508caa0 100644 --- a/backend/src/app/binfile/common.clj +++ b/backend/src/app/binfile/common.clj @@ -550,7 +550,7 @@ [cfg data file-id] (let [library-ids (get-libraries cfg [file-id])] (reduce (fn [data library-id] - (if-let [library (get-file cfg library-id)] + (if-let [library (get-file cfg library-id :include-deleted? true)] (ctf/absorb-assets data (:data library)) data)) data diff --git a/backend/src/app/binfile/v3.clj b/backend/src/app/binfile/v3.clj index 682ddc74de..cf88f71551 100644 --- a/backend/src/app/binfile/v3.clj +++ b/backend/src/app/binfile/v3.clj @@ -228,6 +228,7 @@ (db/tx-run! cfg (fn [cfg] (cond-> (bfc/get-file cfg file-id {:realize? true + :include-deleted? true :lock-for-update? true}) detach? (-> (ctf/detach-external-references file-id) @@ -285,14 +286,12 @@ (let [file (cond-> (select-keys file bfc/file-attrs) (:options data) - (assoc :options (:options data)) + (assoc :options (:options data))) - :always - (dissoc :data)) - - file (cond-> file - :always - (encode-file)) + file (-> file + (dissoc :data) + (dissoc :deleted-at) + (encode-file)) path (str "files/" file-id ".json")] (write-entry! output path file)) diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index e5339a25a4..18bec5e053 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -319,5 +319,9 @@ ([key default] (c/get config key default))) +(defn logging-context + [] + {:version/backend (:full version)}) + ;; Set value for all new threads bindings. (alter-var-root #'*assert* (constantly (contains? flags :backend-asserts))) diff --git a/backend/src/app/http/errors.clj b/backend/src/app/http/errors.clj index 0ad134c927..52d4196f18 100644 --- a/backend/src/app/http/errors.clj +++ b/backend/src/app/http/errors.clj @@ -25,15 +25,14 @@ (let [claims (-> {} (into (::session/token-claims request)) (into (::actoken/token-claims request)))] - {:request/path (:path request) - :request/method (:method request) - :request/params (:params request) - :request/user-agent (yreq/get-header request "user-agent") - :request/ip-addr (inet/parse-request request) - :request/profile-id (:uid claims) - :version/frontend (or (yreq/get-header request "x-frontend-version") "unknown") - :version/backend (:full cf/version)})) - + (-> (cf/logging-context) + (assoc :request/path (:path request)) + (assoc :request/method (:method request)) + (assoc :request/params (:params request)) + (assoc :request/user-agent (yreq/get-header request "user-agent")) + (assoc :request/ip-addr (inet/parse-request request)) + (assoc :request/profile-id (:uid claims)) + (assoc :version/frontend (or (yreq/get-header request "x-frontend-version") "unknown"))))) (defmulti handle-error (fn [cause _ _] diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 63d778a89c..02e290bade 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -64,9 +64,13 @@ (let [mdata (meta result) response (if (fn? result) (result request) - (let [result (rph/unwrap result)] - {::yres/status (::http/status mdata 200) - ::yres/headers (::http/headers mdata {}) + (let [result (rph/unwrap result) + status (::http/status mdata 200) + headers (cond-> (::http/headers mdata {}) + (yres/stream-body? result) + (assoc "content-type" "application/octet-stream"))] + {::yres/status status + ::yres/headers headers ::yres/body result}))] (-> response (handle-response-transformation request mdata) diff --git a/backend/src/app/rpc/commands/binfile.clj b/backend/src/app/rpc/commands/binfile.clj index f0fc211e46..98b2c04193 100644 --- a/backend/src/app/rpc/commands/binfile.clj +++ b/backend/src/app/rpc/commands/binfile.clj @@ -25,10 +25,10 @@ [app.rpc.commands.projects :as projects] [app.rpc.commands.teams :as teams] [app.rpc.doc :as-alias doc] + [app.rpc.helpers :as rph] [app.tasks.file-gc] [app.util.services :as sv] - [app.worker :as-alias wrk] - [yetti.response :as yres])) + [app.worker :as-alias wrk])) (set! *warn-on-reflection* true) @@ -44,7 +44,7 @@ (defn stream-export-v1 [cfg {:keys [file-id include-libraries embed-assets] :as params}] - (yres/stream-body + (rph/stream (fn [_ output-stream] (try (-> cfg @@ -59,7 +59,7 @@ (defn stream-export-v3 [cfg {:keys [file-id include-libraries embed-assets] :as params}] - (yres/stream-body + (rph/stream (fn [_ output-stream] (try (-> cfg @@ -79,16 +79,11 @@ ::sm/params schema:export-binfile} [{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id version file-id] :as params}] (files/check-read-permissions! pool profile-id file-id) - (fn [_] - (let [version (or version 1) - body (case (int version) - 1 (stream-export-v1 cfg params) - 2 (throw (ex-info "not-implemented" {})) - 3 (stream-export-v3 cfg params))] - - {::yres/status 200 - ::yres/headers {"content-type" "application/octet-stream"} - ::yres/body body}))) + (let [version (or version 1)] + (case (int version) + 1 (stream-export-v1 cfg params) + 2 (throw (ex-info "not-implemented" {})) + 3 (stream-export-v3 cfg params)))) ;; --- Command: import-binfile diff --git a/backend/src/app/rpc/helpers.clj b/backend/src/app/rpc/helpers.clj index a424b1f8f6..5b117b82fd 100644 --- a/backend/src/app/rpc/helpers.clj +++ b/backend/src/app/rpc/helpers.clj @@ -11,7 +11,7 @@ [app.common.data.macros :as dm] [app.http :as-alias http] [app.rpc :as-alias rpc] - [yetti.response :as-alias yres])) + [yetti.response :as yres])) ;; A utilty wrapper object for wrap service responses that does not ;; implements the IObj interface that make possible attach metadata to @@ -78,3 +78,8 @@ (let [exp (if (integer? max-age) max-age (inst-ms max-age)) val (dm/fmt "max-age=%" (int (/ exp 1000.0)))] (update response ::yres/headers assoc "cache-control" val))))) + +(defn stream + "A convenience allias for yetti.response/stream-body" + [f] + (yres/stream-body f)) diff --git a/backend/src/app/worker/runner.clj b/backend/src/app/worker/runner.clj index ea8cd08cf2..f3f44bfd30 100644 --- a/backend/src/app/worker/runner.clj +++ b/backend/src/app/worker/runner.clj @@ -13,6 +13,7 @@ [app.common.schema :as sm] [app.common.time :as ct] [app.common.transit :as t] + [app.config :as cf] [app.db :as db] [app.metrics :as mtx] [app.redis :as rds] @@ -60,7 +61,8 @@ (defn get-error-context [_ item] - {:params item}) + (-> (cf/logging-context) + (assoc :params item))) (defn- get-task [{:keys [::db/pool]} task-id] @@ -131,6 +133,11 @@ [{:keys [::id ::timeout] :as cfg} task-id scheduled-at] (loop [task (get-task cfg task-id)] (cond + (nil? task) + (l/wrn :hint "no task found on the database" + :runner-id id + :task-id task-id) + (ex/exception? task) (if (or (db/connection-error? task) (db/serialization-error? task)) @@ -153,11 +160,6 @@ :task-id task-id :runner-id id) - (nil? task) - (l/wrn :hint "no task found on the database" - :runner-id id - :task-id task-id) - :else (let [result (run-task cfg task)] (with-meta result @@ -213,6 +215,7 @@ :payload payload))) (catch Throwable cause (l/err :hint "unable to decode payload" + ::l/context (cf/logging-context) :payload payload :length (alength ^String/1 payload) :cause cause)))) @@ -224,11 +227,11 @@ "failed" (handle-task-failure result) "completed" (handle-task-completion result) (throw (IllegalArgumentException. - (str "invalid status received: " status)))))) + (str "invalid status received: '" status "'")))))) (run-task-loop [[task-id scheduled-at]] (loop [result (run-task! cfg task-id scheduled-at)] - (when-let [cause (process-result result)] + (when-let [cause (some-> result process-result)] (if (or (db/connection-error? cause) (db/serialization-error? cause)) (do @@ -236,9 +239,9 @@ :cause cause) (px/sleep timeout) (recur result)) - (do - (l/err :hint "unhandled exception on processing task result" - :cause cause))))))] + (l/err :hint "unhandled exception on processing task result" + ::l/context (cf/logging-context) + :cause cause)))))] (try (let [key (str/ffmt "penpot.worker.queue:%" queue) @@ -254,11 +257,14 @@ (if (rds/timeout-exception? cause) (do (l/err :hint "redis pop operation timeout, consider increasing redis timeout (will retry in some instants)" + ::l/context (cf/logging-context) :timeout timeout :cause cause) (px/sleep timeout)) - (l/err :hint "unhandled exception" :cause cause)))))) + (l/err :hint "unhandled exception" + ::l/context (cf/logging-context) + :cause cause)))))) (defn- start-thread! [{:keys [::id ::queue ::wrk/tenant] :as cfg}] @@ -284,6 +290,7 @@ :queue queue)) (catch Throwable cause (l/err :hint "unexpected exception" + ::l/context (cf/logging-context) :id id :queue queue :cause cause)) diff --git a/common/src/app/common/files/validate.cljc b/common/src/app/common/files/validate.cljc index 93753f5cea..5b0e1d74d4 100644 --- a/common/src/app/common/files/validate.cljc +++ b/common/src/app/common/files/validate.cljc @@ -466,19 +466,20 @@ children (map #(ctst/get-shape page %) shapes) prop-names (cfv/extract-properties-names (first children) (:data file))] (doseq [child children] - (if (not (ctk/is-variant? child)) - (report-error :not-a-variant - (str/ffmt "Shape % should be a variant" (:id child)) - child file page) - (do - (when (not= (:variant-id child) shape-id) - (report-error :invalid-variant-id - (str/ffmt "Variant % has invalid variant-id %" (:id child) (:variant-id child)) - child file page)) - (when (not= prop-names (cfv/extract-properties-names child (:data file))) - (report-error :invalid-variant-properties - (str/ffmt "Variant % has invalid properties %" (:id child) (vec prop-names)) - child file page))))))) + (when child + (if (not (ctk/is-variant? child)) + (report-error :not-a-variant + (str/ffmt "Shape % should be a variant" (:id child)) + child file page) + (do + (when (not= (:variant-id child) shape-id) + (report-error :invalid-variant-id + (str/ffmt "Variant % has invalid variant-id %" (:id child) (:variant-id child)) + child file page)) + (when (not= prop-names (cfv/extract-properties-names child (:data file))) + (report-error :invalid-variant-properties + (str/ffmt "Variant % has invalid properties %" (:id child) (vec prop-names)) + child file page)))))))) (defn- check-variant "Shape is a variant, so @@ -627,7 +628,8 @@ main-component (if (:deleted component) (dm/get-in component [:objects (:main-instance-id component)]) (ctst/get-shape component-page (:main-instance-id component)))] - (when-not (ctk/is-variant? main-component) + (when (and main-component + (not (ctk/is-variant? main-component))) (report-error :not-a-variant (str/ffmt "Shape % should be a variant" (:id main-component)) main-component file component-page)))) diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc index 4b99b0e1c4..41e8f7b252 100644 --- a/common/src/app/common/types/tokens_lib.cljc +++ b/common/src/app/common/types/tokens_lib.cljc @@ -2053,13 +2053,19 @@ Will return a value that matches this schema: #?(:clj (defn- migrate-to-v1-4 - "Migrate the TokensLib data structure internals to v1.2 version; it + "Migrate the TokensLib data structure internals to v1.4 version; it expects input from v1.3 version" [params] (let [migrate-set-node (fn recurse [node] - (if (token-set-legacy? node) + (cond + (token-set-legacy? node) (make-token-set node) + + (token-set? node) + node + + :else (d/update-vals node recurse)))] (update params :sets d/update-vals migrate-set-node)))) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index f2f9e79795..2fb6a96650 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -1280,7 +1280,7 @@ (watch [_ _ stream] (let [stopper-s (->> stream - (rx/filter #(or (= ::dw/finalize-page (ptk/type %)) + (rx/filter #(or (= ::dwpg/finalize-page (ptk/type %)) (= ::watch-component-changes (ptk/type %))))) workspace-data-s diff --git a/frontend/src/app/main/data/workspace/path/drawing.cljs b/frontend/src/app/main/data/workspace/path/drawing.cljs index 85ae357533..d7a5409f1b 100644 --- a/frontend/src/app/main/data/workspace/path/drawing.cljs +++ b/frontend/src/app/main/data/workspace/path/drawing.cljs @@ -20,6 +20,7 @@ [app.main.data.helpers :as dsh] [app.main.data.workspace.drawing.common :as dwdc] [app.main.data.workspace.edition :as dwe] + [app.main.data.workspace.pages :as-alias dwpg] [app.main.data.workspace.path.changes :as changes] [app.main.data.workspace.path.common :as common] [app.main.data.workspace.path.helpers :as helpers] @@ -43,7 +44,7 @@ (= type :app.main.data.workspace.path.shortcuts/esc-pressed) (= type :app.main.data.workspace.common/clear-edition-mode) (= type :app.main.data.workspace.edition/clear-edition-mode) - (= type :app.main.data.workspace/finalize-page) + (= type ::dwpg/finalize-page) (= event :interrupt) ;; ESC (and ^boolean (mse/mouse-event? event) ^boolean (mse/mouse-double-click-event? event))))) diff --git a/frontend/src/app/main/data/workspace/path/undo.cljs b/frontend/src/app/main/data/workspace/path/undo.cljs index 54b2f3eeaf..bd0a6efa9f 100644 --- a/frontend/src/app/main/data/workspace/path/undo.cljs +++ b/frontend/src/app/main/data/workspace/path/undo.cljs @@ -10,6 +10,8 @@ [app.common.data.undo-stack :as u] [app.common.uuid :as uuid] [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.edition :as-alias dwe] + [app.main.data.workspace.pages :as-alias dwpg] [app.main.data.workspace.path.changes :as changes] [app.main.data.workspace.path.common :as common] [app.main.data.workspace.path.state :as st] @@ -133,8 +135,8 @@ (defn- stop-undo? [event] (let [type (ptk/type event)] - (or (= :app.main.data.workspace.edition/clear-edition-mode type) - (= :app.main.data.workspace/finalize-page type)))) + (or (= ::dwe/clear-edition-mode type) + (= ::dwpg/finalize-page type)))) (def path-content-ref (letfn [(selector [state] diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 16459d7408..5424a280d9 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -24,6 +24,7 @@ [app.main.data.modal :as md] [app.main.data.workspace.collapse :as dwc] [app.main.data.workspace.edition :as dwe] + [app.main.data.workspace.pages :as-alias dwpg] [app.main.data.workspace.specialized-panel :as-alias dwsp] [app.main.data.workspace.undo :as dwu] [app.main.data.workspace.zoom :as dwz] @@ -596,21 +597,21 @@ ptk/WatchEvent (watch [_ state stream] (let [stopper (rx/filter #(or (= ::toggle-focus-mode (ptk/type %)) - (= :app.main.data.workspace/finalize-page (ptk/type %))) stream)] + (= ::dwpg/finalize-page (ptk/type %))) stream)] (when (d/not-empty? (:workspace-focus-selected state)) - (rx/merge - (rx/of dwz/zoom-to-selected-shape - (deselect-all)) - (->> (rx/from-atom refs/workspace-page-objects {:emit-current-value? true}) - (rx/take-until stopper) - (rx/map (comp set keys)) - (rx/buffer 2 1) - (rx/merge-map - ;; While focus is active, update it with any new and deleted shapes - (fn [[old-keys new-keys]] - (let [removed (set/difference old-keys new-keys) - added (set/difference new-keys old-keys)] + (->> (rx/merge + (rx/of dwz/zoom-to-selected-shape + (deselect-all)) + (->> (rx/from-atom refs/workspace-page-objects {:emit-current-value? true}) + (rx/map (comp set keys)) + (rx/buffer 2 1) + (rx/merge-map + ;; While focus is active, update it with any new and deleted shapes + (fn [[old-keys new-keys]] + (let [removed (set/difference old-keys new-keys) + added (set/difference new-keys old-keys)] - (if (or (d/not-empty? added) (d/not-empty? removed)) - (rx/of (update-focus-shapes added removed)) - (rx/empty)))))))))))) + (if (or (d/not-empty? added) (d/not-empty? removed)) + (rx/of (update-focus-shapes added removed)) + (rx/empty))))))) + (rx/take-until stopper))))))) diff --git a/frontend/src/app/main/data/workspace/thumbnails.cljs b/frontend/src/app/main/data/workspace/thumbnails.cljs index 3e330baa0a..a8ff8fedf9 100644 --- a/frontend/src/app/main/data/workspace/thumbnails.cljs +++ b/frontend/src/app/main/data/workspace/thumbnails.cljs @@ -17,6 +17,7 @@ [app.main.data.helpers :as dsh] [app.main.data.persistence :as-alias dps] [app.main.data.workspace.notifications :as-alias wnt] + [app.main.data.workspace.pages :as-alias dwpg] [app.main.rasterizer :as thr] [app.main.refs :as refs] [app.main.render :as render] @@ -254,7 +255,7 @@ (let [stopper-s (rx/filter (fn [event] (as-> (ptk/type event) type - (or (= :app.main.data.workspace/finalize-page type) + (or (= ::dwpg/finalize-page type) (= ::watch-state-changes type)))) stream)