diff --git a/CHANGES.md b/CHANGES.md index 01dfd5b7c..100296f4d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -28,6 +28,8 @@ ### :boom: Breaking changes & Deprecations - New strokes default to inside border [Taiga #6847](https://tree.taiga.io/project/penpot/issue/6847) +- Change default z ordering on layers in flex layout. The previous behavior was inconsistent with how HTML works and we changed it to be more consistent. Previous layers that overlapped could be hidden, the fastest way to fix this is changing the z-index property but a better way is to change the order of your layers. + ### :heart: Community contributions (Thank you!) - New Hausa, Yoruba and Igbo translations and update translation files (by All For Tech Empowerment Foundation) [Taiga #6950](https://tree.taiga.io/project/penpot/us/6950), [Taiga #6534](https://tree.taiga.io/project/penpot/us/6534) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 6d5fc3d5f..97171825c 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -349,6 +349,8 @@ :audit-log-archive (ig/ref :app.loggers.audit.archive-task/handler) :audit-log-gc (ig/ref :app.loggers.audit.gc-task/handler) + :object-update + (ig/ref :app.tasks.object-update/handler) :process-webhook-event (ig/ref ::webhooks/process-event-handler) :run-webhook @@ -376,7 +378,10 @@ ::sto/storage (ig/ref ::sto/storage)} :app.tasks.orphan-teams-gc/handler - {::db/pool (ig/ref ::db/pool)} + {::db/pool (ig/ref ::db/pool)} + + :app.tasks.object-update/handler + {::db/pool (ig/ref ::db/pool)} :app.tasks.file-gc/handler {::db/pool (ig/ref ::db/pool) diff --git a/backend/src/app/rpc/commands/files_thumbnails.clj b/backend/src/app/rpc/commands/files_thumbnails.clj index d766acd3c..bd982ce17 100644 --- a/backend/src/app/rpc/commands/files_thumbnails.clj +++ b/backend/src/app/rpc/commands/files_thumbnails.clj @@ -271,7 +271,7 @@ (when (and (some? th1) (not= (:media-id th1) (:media-id th2))) - (sto/touch-object! storage (:media-id th1))) + (sto/touch-object! storage (:media-id th1) :async true)) th2)) diff --git a/backend/src/app/rpc/commands/profile.clj b/backend/src/app/rpc/commands/profile.clj index 89a50b6ad..fe33da10d 100644 --- a/backend/src/app/rpc/commands/profile.clj +++ b/backend/src/app/rpc/commands/profile.clj @@ -46,6 +46,10 @@ (let [email (str/lower email) email (if (str/starts-with? email "mailto:") (subs email 7) + email) + email (if (or (str/starts-with? email "<") + (str/ends-with? email ">")) + (str/trim email "<>") email)] email)) diff --git a/backend/src/app/rpc/commands/viewer.clj b/backend/src/app/rpc/commands/viewer.clj index c2887ef96..70d5193c1 100644 --- a/backend/src/app/rpc/commands/viewer.clj +++ b/backend/src/app/rpc/commands/viewer.clj @@ -38,6 +38,11 @@ team (-> (db/get conn :team {:id (:team-id project)}) (teams/decode-row)) + members (into #{} (->> (teams/get-team-members conn (:team-id project)) + (map :id))) + + perms (assoc perms :in-team (contains? members profile-id)) + _ (-> (cfeat/get-team-enabled-features cf/flags team) (cfeat/check-client-features! (:features params)) (cfeat/check-file-features! (:features file))) diff --git a/backend/src/app/storage.clj b/backend/src/app/storage.clj index f6924aedb..070c53f3f 100644 --- a/backend/src/app/storage.clj +++ b/backend/src/app/storage.clj @@ -16,6 +16,7 @@ [app.storage.impl :as impl] [app.storage.s3 :as ss3] [app.util.time :as dt] + [app.worker :as wrk] [clojure.spec.alpha :as s] [datoteka.fs :as fs] [integrant.core :as ig] @@ -170,15 +171,28 @@ (impl/put-object object content)) object))) +(def ^:private default-touch-delay + "A default delay for the asynchronous touch operation" + (dt/duration "5m")) + (defn touch-object! "Mark object as touched." - [{:keys [::db/pool-or-conn] :as storage} object-or-id] + [{:keys [::db/pool-or-conn] :as storage} object-or-id & {:keys [async]}] (us/assert! ::storage storage) - (let [id (if (impl/object? object-or-id) (:id object-or-id) object-or-id) - rs (db/update! pool-or-conn :storage-object - {:touched-at (dt/now)} - {:id id})] - (pos? (db/get-update-count rs)))) + (let [id (if (impl/object? object-or-id) (:id object-or-id) object-or-id)] + (if async + (wrk/submit! ::wrk/conn pool-or-conn + ::wrk/task :object-update + ::wrk/delay default-touch-delay + :object :storage-object + :id id + :key :touched-at + :val (dt/now)) + (-> (db/update! pool-or-conn :storage-object + {:touched-at (dt/now)} + {:id id}) + (db/get-update-count) + (pos?))))) (defn get-object-data "Return an input stream instance of the object content." diff --git a/backend/src/app/tasks/object_update.clj b/backend/src/app/tasks/object_update.clj new file mode 100644 index 000000000..cfe5fda44 --- /dev/null +++ b/backend/src/app/tasks/object_update.clj @@ -0,0 +1,32 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.tasks.object-update + "A task used for perform simple object properties update + in an asynchronous flow." + (:require + [app.common.data :as d] + [app.common.logging :as l] + [app.db :as db] + [clojure.spec.alpha :as s] + [integrant.core :as ig])) + +(defn- update-object + [{:keys [::db/conn] :as cfg} {:keys [id object key val] :as props}] + (l/trc :hint "update object prop" + :id (str id) + :object (d/name object) + :key (d/name key) + :val val) + (db/update! conn object {key val} {:id id} {::db/return-keys false})) + +(defmethod ig/pre-init-spec ::handler [_] + (s/keys :req [::db/pool])) + +(defmethod ig/init-key ::handler + [_ cfg] + (fn [{:keys [props] :as params}] + (db/tx-run! cfg update-object props))) diff --git a/backend/src/app/worker.clj b/backend/src/app/worker.clj index a648080f3..1da2e8de0 100644 --- a/backend/src/app/worker.clj +++ b/backend/src/app/worker.clj @@ -119,11 +119,13 @@ :next.jdbc/update-count))] (l/trc :hint "submit task" :name task + :task-id (str id) :queue queue :label label :dedupe (boolean dedupe) - :deleted (or deleted 0) - :in (dt/format-duration duration)) + :delay (dt/format-duration duration) + :replace (or deleted 0)) + (db/exec-one! conn [sql:insert-new-task id task props queue label priority max-retries interval]) diff --git a/backend/src/app/worker/cron.clj b/backend/src/app/worker/cron.clj index 72a66a22b..cb5a69d88 100644 --- a/backend/src/app/worker/cron.clj +++ b/backend/src/app/worker/cron.clj @@ -92,7 +92,7 @@ (let [ts (ms-until-valid cron) ft (px/schedule! ts (partial execute-cron-task cfg task))] - (l/dbg :hint "schedule task" :id id + (l/dbg :hint "schedule" :id id :ts (dt/format-duration ts) :at (dt/format-instant (dt/in-future ts))) diff --git a/backend/src/app/worker/runner.clj b/backend/src/app/worker/runner.clj index 11ca3d972..be3663365 100644 --- a/backend/src/app/worker/runner.clj +++ b/backend/src/app/worker/runner.clj @@ -139,7 +139,7 @@ :else (try - (l/trc :hint "start task" + (l/dbg :hint "start" :name (:name task) :task-id (str task-id) :queue queue @@ -149,7 +149,7 @@ result (handle-task task) elapsed (dt/format-duration (tpoint))] - (l/trc :hint "end task" + (l/dbg :hint "end" :name (:name task) :task-id (str task-id) :queue queue @@ -228,9 +228,9 @@ (recur)))) (catch InterruptedException _ - (l/debug :hint "interrupted" - :id id - :queue queue)) + (l/dbg :hint "interrupted" + :id id + :queue queue)) (catch Throwable cause (l/err :hint "unexpected exception" :id id diff --git a/backend/test/backend_tests/rpc_file_thumbnails_test.clj b/backend/test/backend_tests/rpc_file_thumbnails_test.clj index f0cfc9637..11ed4f352 100644 --- a/backend/test/backend_tests/rpc_file_thumbnails_test.clj +++ b/backend/test/backend_tests/rpc_file_thumbnails_test.clj @@ -277,8 +277,6 @@ (t/is (thrown? org.postgresql.util.PSQLException (th/db-delete! :storage-object {:id (:media-id row1)})))))) - - (t/deftest get-file-object-thumbnail (let [storage (::sto/storage th/*system*) profile (th/create-profile* 1) @@ -317,3 +315,44 @@ (let [result (:result out)] (t/is (contains? result "test-key-2")))))) + +(t/deftest create-file-object-thumbnail + (th/db-delete! :task {:name "object-update"}) + (let [storage (::sto/storage th/*system*) + profile (th/create-profile* 1) + file (th/create-file* 1 {:profile-id (:id profile) + :project-id (:default-project-id profile) + :is-shared false}) + data {::th/type :create-file-object-thumbnail + ::rpc/profile-id (:id profile) + :file-id (:id file) + :object-id "test-key-2" + :media {:filename "sample.jpg" + :mtype "image/jpeg"}}] + + (let [data (update data :media + (fn [media] + (-> media + (assoc :path (th/tempfile "backend_tests/test_files/sample2.jpg")) + (assoc :size 7923)))) + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (map? (:result out)))) + + (let [data (update data :media + (fn [media] + (-> media + (assoc :path (th/tempfile "backend_tests/test_files/sample.jpg")) + (assoc :size 312043)))) + out (th/command! data)] + (t/is (nil? (:error out))) + (t/is (map? (:result out)))) + + (let [[row1 :as rows] + (->> (th/db-query :task {:name "object-update"}) + (map #(update % :props db/decode-transit-pgobject)))] + + ;; (app.common.pprint/pprint rows) + (t/is (= 1 (count rows))) + (t/is (> (inst-ms (dt/diff (:created-at row1) (:scheduled-at row1))) + (inst-ms (dt/duration "4m"))))))) diff --git a/backend/test/backend_tests/rpc_profile_test.clj b/backend/test/backend_tests/rpc_profile_test.clj index cbaff6038..00d2f2ac0 100644 --- a/backend/test/backend_tests/rpc_profile_test.clj +++ b/backend/test/backend_tests/rpc_profile_test.clj @@ -27,6 +27,14 @@ (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) + +(t/deftest clean-email + (t/is "foo@example.com" (profile/clean-email "mailto:foo@example.com")) + (t/is "foo@example.com" (profile/clean-email "mailto:")) + (t/is "foo@example.com" (profile/clean-email "")) + (t/is "foo@example.com" (profile/clean-email "foo@example.com>")) + (t/is "foo@example.com" (profile/clean-email " \ No newline at end of file + + + \ No newline at end of file diff --git a/frontend/resources/templates/index.mustache b/frontend/resources/templates/index.mustache index 9090976a3..239893399 100644 --- a/frontend/resources/templates/index.mustache +++ b/frontend/resources/templates/index.mustache @@ -1,5 +1,5 @@ - + diff --git a/frontend/src/app/main/ui/auth.cljs b/frontend/src/app/main/ui/auth.cljs index 218fc21ce..e5f28bd70 100644 --- a/frontend/src/app/main/ui/auth.cljs +++ b/frontend/src/app/main/ui/auth.cljs @@ -48,7 +48,8 @@ (dom/set-html-title (tr "title.default"))) [:main {:class (stl/css :auth-section)} - [:a {:href "#/" :class (stl/css :logo-btn)} i/logo] + [:h1 {:class (stl/css :logo-container)} + [:a {:href "#/" :title "Penpot" :class (stl/css :logo-btn)} i/logo]] [:div {:class (stl/css :login-illustration)} i/login-illustration] diff --git a/frontend/src/app/main/ui/auth.scss b/frontend/src/app/main/ui/auth.scss index 81e418e9c..f2d41c34d 100644 --- a/frontend/src/app/main/ui/auth.scss +++ b/frontend/src/app/main/ui/auth.scss @@ -24,6 +24,16 @@ } } +.logo-container { + position: absolute; + top: $s-20; + left: $s-20; + display: flex; + justify-content: flex-start; + width: $s-120; + margin-block-end: $s-52; +} + .login-illustration { display: flex; justify-content: center; @@ -55,14 +65,6 @@ } .logo-btn { - position: absolute; - top: $s-20; - left: $s-20; - display: flex; - justify-content: flex-start; - width: $s-120; - margin-block-end: $s-52; - svg { width: $s-120; height: $s-40; diff --git a/frontend/src/app/main/ui/components/tab_container.cljs b/frontend/src/app/main/ui/components/tab_container.cljs index bd3ff6727..20c79a417 100644 --- a/frontend/src/app/main/ui/components/tab_container.cljs +++ b/frontend/src/app/main/ui/components/tab_container.cljs @@ -54,9 +54,10 @@ (let [props (.-props tab) id (.-id props) title (.-title props) - sid (d/name id)] + sid (d/name id) + tooltip (if (string? title) title nil)] [:div {:key (str/concat "tab-" sid) - :title title + :title tooltip :data-id sid :on-click on-click :class (stl/css-case diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index 6e3051cf7..77460f0b6 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -180,7 +180,7 @@ :on-zoom-fit handle-zoom-fit :on-fullscreen toggle-fullscreen}] - (when (:can-edit permissions) + (when (:in-team permissions) [:span {:on-click go-to-workspace :class (stl/css :edit-btn)} i/curve]) @@ -191,7 +191,9 @@ :on-click toggle-fullscreen} i/expand] - (when (:is-admin permissions) + (when (and + (:in-team permissions) + (:is-admin permissions)) [:button {:on-click open-share-dialog :class (stl/css :share-btn)} (tr "labels.share")]) @@ -301,8 +303,8 @@ ;; If the user doesn't have permission we disable the link [:a {:class (stl/css :home-link) :on-click go-to-dashboard - :style {:cursor (when-not (:can-edit permissions) "auto") - :pointer-events (when-not (:can-edit permissions) "none")}} + :style {:cursor (when-not (:in-team permissions) "auto") + :pointer-events (when-not (:in-team permissions) "none")}} [:span {:class (stl/css :logo-icon)} i/logo-icon]] @@ -321,7 +323,7 @@ :title (tr "viewer.header.interactions-section" (sc/get-tooltip :open-interactions))} i/play] - (when (or (:can-edit permissions) + (when (or (:in-team permissions) (= (:who-comment permissions) "all")) [:button {:on-click navigate :data-value "comments"