mirror of
https://github.com/penpot/penpot.git
synced 2025-06-08 01:21:37 +02:00
Merge remote-tracking branch 'origin/staging'
This commit is contained in:
commit
3ea3923751
18 changed files with 155 additions and 36 deletions
|
@ -28,6 +28,8 @@
|
||||||
### :boom: Breaking changes & Deprecations
|
### :boom: Breaking changes & Deprecations
|
||||||
|
|
||||||
- New strokes default to inside border [Taiga #6847](https://tree.taiga.io/project/penpot/issue/6847)
|
- 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!)
|
### :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)
|
- 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)
|
||||||
|
|
|
@ -349,6 +349,8 @@
|
||||||
:audit-log-archive (ig/ref :app.loggers.audit.archive-task/handler)
|
:audit-log-archive (ig/ref :app.loggers.audit.archive-task/handler)
|
||||||
:audit-log-gc (ig/ref :app.loggers.audit.gc-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
|
:process-webhook-event
|
||||||
(ig/ref ::webhooks/process-event-handler)
|
(ig/ref ::webhooks/process-event-handler)
|
||||||
:run-webhook
|
:run-webhook
|
||||||
|
@ -378,6 +380,9 @@
|
||||||
:app.tasks.orphan-teams-gc/handler
|
: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
|
:app.tasks.file-gc/handler
|
||||||
{::db/pool (ig/ref ::db/pool)
|
{::db/pool (ig/ref ::db/pool)
|
||||||
::sto/storage (ig/ref ::sto/storage)}
|
::sto/storage (ig/ref ::sto/storage)}
|
||||||
|
|
|
@ -271,7 +271,7 @@
|
||||||
(when (and (some? th1)
|
(when (and (some? th1)
|
||||||
(not= (:media-id th1)
|
(not= (:media-id th1)
|
||||||
(:media-id th2)))
|
(:media-id th2)))
|
||||||
(sto/touch-object! storage (:media-id th1)))
|
(sto/touch-object! storage (:media-id th1) :async true))
|
||||||
|
|
||||||
th2))
|
th2))
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,10 @@
|
||||||
(let [email (str/lower email)
|
(let [email (str/lower email)
|
||||||
email (if (str/starts-with? email "mailto:")
|
email (if (str/starts-with? email "mailto:")
|
||||||
(subs email 7)
|
(subs email 7)
|
||||||
|
email)
|
||||||
|
email (if (or (str/starts-with? email "<")
|
||||||
|
(str/ends-with? email ">"))
|
||||||
|
(str/trim email "<>")
|
||||||
email)]
|
email)]
|
||||||
email))
|
email))
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,11 @@
|
||||||
team (-> (db/get conn :team {:id (:team-id project)})
|
team (-> (db/get conn :team {:id (:team-id project)})
|
||||||
(teams/decode-row))
|
(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/get-team-enabled-features cf/flags team)
|
||||||
(cfeat/check-client-features! (:features params))
|
(cfeat/check-client-features! (:features params))
|
||||||
(cfeat/check-file-features! (:features file)))
|
(cfeat/check-file-features! (:features file)))
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
[app.storage.impl :as impl]
|
[app.storage.impl :as impl]
|
||||||
[app.storage.s3 :as ss3]
|
[app.storage.s3 :as ss3]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
|
[app.worker :as wrk]
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
[datoteka.fs :as fs]
|
[datoteka.fs :as fs]
|
||||||
[integrant.core :as ig]
|
[integrant.core :as ig]
|
||||||
|
@ -170,15 +171,28 @@
|
||||||
(impl/put-object object content))
|
(impl/put-object object content))
|
||||||
object)))
|
object)))
|
||||||
|
|
||||||
|
(def ^:private default-touch-delay
|
||||||
|
"A default delay for the asynchronous touch operation"
|
||||||
|
(dt/duration "5m"))
|
||||||
|
|
||||||
(defn touch-object!
|
(defn touch-object!
|
||||||
"Mark object as touched."
|
"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)
|
(us/assert! ::storage storage)
|
||||||
(let [id (if (impl/object? object-or-id) (:id object-or-id) object-or-id)
|
(let [id (if (impl/object? object-or-id) (:id object-or-id) object-or-id)]
|
||||||
rs (db/update! pool-or-conn :storage-object
|
(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)}
|
{:touched-at (dt/now)}
|
||||||
{:id id})]
|
{:id id})
|
||||||
(pos? (db/get-update-count rs))))
|
(db/get-update-count)
|
||||||
|
(pos?)))))
|
||||||
|
|
||||||
(defn get-object-data
|
(defn get-object-data
|
||||||
"Return an input stream instance of the object content."
|
"Return an input stream instance of the object content."
|
||||||
|
|
32
backend/src/app/tasks/object_update.clj
Normal file
32
backend/src/app/tasks/object_update.clj
Normal file
|
@ -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)))
|
|
@ -119,11 +119,13 @@
|
||||||
:next.jdbc/update-count))]
|
:next.jdbc/update-count))]
|
||||||
(l/trc :hint "submit task"
|
(l/trc :hint "submit task"
|
||||||
:name task
|
:name task
|
||||||
|
:task-id (str id)
|
||||||
:queue queue
|
:queue queue
|
||||||
:label label
|
:label label
|
||||||
:dedupe (boolean dedupe)
|
:dedupe (boolean dedupe)
|
||||||
:deleted (or deleted 0)
|
:delay (dt/format-duration duration)
|
||||||
:in (dt/format-duration duration))
|
:replace (or deleted 0))
|
||||||
|
|
||||||
|
|
||||||
(db/exec-one! conn [sql:insert-new-task id task props queue
|
(db/exec-one! conn [sql:insert-new-task id task props queue
|
||||||
label priority max-retries interval])
|
label priority max-retries interval])
|
||||||
|
|
|
@ -92,7 +92,7 @@
|
||||||
(let [ts (ms-until-valid cron)
|
(let [ts (ms-until-valid cron)
|
||||||
ft (px/schedule! ts (partial execute-cron-task cfg task))]
|
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)
|
:ts (dt/format-duration ts)
|
||||||
:at (dt/format-instant (dt/in-future ts)))
|
:at (dt/format-instant (dt/in-future ts)))
|
||||||
|
|
||||||
|
|
|
@ -139,7 +139,7 @@
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(try
|
(try
|
||||||
(l/trc :hint "start task"
|
(l/dbg :hint "start"
|
||||||
:name (:name task)
|
:name (:name task)
|
||||||
:task-id (str task-id)
|
:task-id (str task-id)
|
||||||
:queue queue
|
:queue queue
|
||||||
|
@ -149,7 +149,7 @@
|
||||||
result (handle-task task)
|
result (handle-task task)
|
||||||
elapsed (dt/format-duration (tpoint))]
|
elapsed (dt/format-duration (tpoint))]
|
||||||
|
|
||||||
(l/trc :hint "end task"
|
(l/dbg :hint "end"
|
||||||
:name (:name task)
|
:name (:name task)
|
||||||
:task-id (str task-id)
|
:task-id (str task-id)
|
||||||
:queue queue
|
:queue queue
|
||||||
|
@ -228,7 +228,7 @@
|
||||||
(recur))))
|
(recur))))
|
||||||
|
|
||||||
(catch InterruptedException _
|
(catch InterruptedException _
|
||||||
(l/debug :hint "interrupted"
|
(l/dbg :hint "interrupted"
|
||||||
:id id
|
:id id
|
||||||
:queue queue))
|
:queue queue))
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
|
|
|
@ -277,8 +277,6 @@
|
||||||
(t/is (thrown? org.postgresql.util.PSQLException
|
(t/is (thrown? org.postgresql.util.PSQLException
|
||||||
(th/db-delete! :storage-object {:id (:media-id row1)}))))))
|
(th/db-delete! :storage-object {:id (:media-id row1)}))))))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(t/deftest get-file-object-thumbnail
|
(t/deftest get-file-object-thumbnail
|
||||||
(let [storage (::sto/storage th/*system*)
|
(let [storage (::sto/storage th/*system*)
|
||||||
profile (th/create-profile* 1)
|
profile (th/create-profile* 1)
|
||||||
|
@ -317,3 +315,44 @@
|
||||||
|
|
||||||
(let [result (:result out)]
|
(let [result (:result out)]
|
||||||
(t/is (contains? result "test-key-2"))))))
|
(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")))))))
|
||||||
|
|
|
@ -27,6 +27,14 @@
|
||||||
(t/use-fixtures :once th/state-init)
|
(t/use-fixtures :once th/state-init)
|
||||||
(t/use-fixtures :each th/database-reset)
|
(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:<foo@example.com>"))
|
||||||
|
(t/is "foo@example.com" (profile/clean-email "<foo@example.com>"))
|
||||||
|
(t/is "foo@example.com" (profile/clean-email "foo@example.com>"))
|
||||||
|
(t/is "foo@example.com" (profile/clean-email "<foo@example.com")))
|
||||||
|
|
||||||
;; Test with wrong credentials
|
;; Test with wrong credentials
|
||||||
(t/deftest profile-login-failed-1
|
(t/deftest profile-login-failed-1
|
||||||
(let [profile (th/create-profile* 1)
|
(let [profile (th/create-profile* 1)
|
||||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
|
@ -1,5 +1,5 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||||
|
|
|
@ -48,7 +48,8 @@
|
||||||
(dom/set-html-title (tr "title.default")))
|
(dom/set-html-title (tr "title.default")))
|
||||||
|
|
||||||
[:main {:class (stl/css :auth-section)}
|
[: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)}
|
[:div {:class (stl/css :login-illustration)}
|
||||||
i/login-illustration]
|
i/login-illustration]
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
.login-illustration {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -55,14 +65,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-btn {
|
.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 {
|
svg {
|
||||||
width: $s-120;
|
width: $s-120;
|
||||||
height: $s-40;
|
height: $s-40;
|
||||||
|
|
|
@ -54,9 +54,10 @@
|
||||||
(let [props (.-props tab)
|
(let [props (.-props tab)
|
||||||
id (.-id props)
|
id (.-id props)
|
||||||
title (.-title props)
|
title (.-title props)
|
||||||
sid (d/name id)]
|
sid (d/name id)
|
||||||
|
tooltip (if (string? title) title nil)]
|
||||||
[:div {:key (str/concat "tab-" sid)
|
[:div {:key (str/concat "tab-" sid)
|
||||||
:title title
|
:title tooltip
|
||||||
:data-id sid
|
:data-id sid
|
||||||
:on-click on-click
|
:on-click on-click
|
||||||
:class (stl/css-case
|
:class (stl/css-case
|
||||||
|
|
|
@ -180,7 +180,7 @@
|
||||||
:on-zoom-fit handle-zoom-fit
|
:on-zoom-fit handle-zoom-fit
|
||||||
:on-fullscreen toggle-fullscreen}]
|
:on-fullscreen toggle-fullscreen}]
|
||||||
|
|
||||||
(when (:can-edit permissions)
|
(when (:in-team permissions)
|
||||||
[:span {:on-click go-to-workspace
|
[:span {:on-click go-to-workspace
|
||||||
:class (stl/css :edit-btn)}
|
:class (stl/css :edit-btn)}
|
||||||
i/curve])
|
i/curve])
|
||||||
|
@ -191,7 +191,9 @@
|
||||||
:on-click toggle-fullscreen}
|
:on-click toggle-fullscreen}
|
||||||
i/expand]
|
i/expand]
|
||||||
|
|
||||||
(when (:is-admin permissions)
|
(when (and
|
||||||
|
(:in-team permissions)
|
||||||
|
(:is-admin permissions))
|
||||||
[:button {:on-click open-share-dialog
|
[:button {:on-click open-share-dialog
|
||||||
:class (stl/css :share-btn)}
|
:class (stl/css :share-btn)}
|
||||||
(tr "labels.share")])
|
(tr "labels.share")])
|
||||||
|
@ -301,8 +303,8 @@
|
||||||
;; If the user doesn't have permission we disable the link
|
;; If the user doesn't have permission we disable the link
|
||||||
[:a {:class (stl/css :home-link)
|
[:a {:class (stl/css :home-link)
|
||||||
:on-click go-to-dashboard
|
:on-click go-to-dashboard
|
||||||
:style {:cursor (when-not (:can-edit permissions) "auto")
|
:style {:cursor (when-not (:in-team permissions) "auto")
|
||||||
:pointer-events (when-not (:can-edit permissions) "none")}}
|
:pointer-events (when-not (:in-team permissions) "none")}}
|
||||||
[:span {:class (stl/css :logo-icon)}
|
[:span {:class (stl/css :logo-icon)}
|
||||||
i/logo-icon]]
|
i/logo-icon]]
|
||||||
|
|
||||||
|
@ -321,7 +323,7 @@
|
||||||
:title (tr "viewer.header.interactions-section" (sc/get-tooltip :open-interactions))}
|
:title (tr "viewer.header.interactions-section" (sc/get-tooltip :open-interactions))}
|
||||||
i/play]
|
i/play]
|
||||||
|
|
||||||
(when (or (:can-edit permissions)
|
(when (or (:in-team permissions)
|
||||||
(= (:who-comment permissions) "all"))
|
(= (:who-comment permissions) "all"))
|
||||||
[:button {:on-click navigate
|
[:button {:on-click navigate
|
||||||
:data-value "comments"
|
:data-value "comments"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue