Compare commits

..

60 Commits

Author SHA1 Message Date
Alejandro Alonso
24fa4f71ad 📎 Update version.txt file 2023-02-27 10:37:39 +01:00
Andrey Antukh
fa21dc4cf9 📎 Fix tests 2023-02-25 10:35:00 +01:00
Alejandro
2460f36bab Merge pull request #2983 from penpot/niwinz-invitations-fixes
Fix issues with invitation user flow
2023-02-24 15:50:40 +01:00
Andrey Antukh
4d627f8993 🐛 Fix incorrect invitation flow 2023-02-24 15:44:29 +01:00
Andrey Antukh
7771467aa0 🐛 Fix missing member-id field on invitation copy-link 2023-02-24 15:41:15 +01:00
Alejandro
0e97182ef0 Merge pull request #2977 from penpot/niwinz-invitations-1
 Add proper audit log for invitations
2023-02-24 10:57:13 +01:00
Andrey Antukh
f0c0e5e43a Add proper audit log for invitations 2023-02-24 10:28:07 +01:00
Alejandro
475b6ff6e0 Merge pull request #2969 from penpot/alotor-fix-redo-curve-tool
🐛 Fix problem with redo curve drawings
2023-02-22 11:25:40 +01:00
alonso.torres
a1f41c80a2 🐛 Fix problem with redo curve drawings 2023-02-22 10:43:51 +01:00
Eva Marco
4297b6fda8 Merge pull request #2968 from penpot/alotor-bug-width-fill
🐛 Fix problem with align center and width 100%
2023-02-21 16:25:08 +01:00
alonso.torres
28dce3cc8b 🐛 Fix problem with align center and width 100% 2023-02-21 15:44:13 +01:00
Andrey Antukh
3c650ae47e Merge branch 'main' into staging 2023-02-20 13:28:51 +01:00
Alejandro
1806200613 Merge pull request #2947 from penpot/alotor-performance-improvement
 Performance improvement
2023-02-16 09:38:05 +01:00
alonso.torres
ed22e2c6d1 Performance improvement 2023-02-15 15:17:50 +01:00
Alejandro
0487539b23 Merge pull request #2946 from penpot/alotor-bug-new-frame
🐛 Fix problem with new frame inside layout
2023-02-15 13:48:57 +01:00
alonso.torres
fd15ff940f 🐛 Fix problem with new frame inside layout 2023-02-15 13:38:03 +01:00
Alejandro
ece6193260 Merge pull request #2939 from penpot/palba-fix-undo-duplicate-with-alt
Fix duplicate with alt and undo only undo one step
2023-02-15 12:21:00 +01:00
Pablo Alba
813a188e24 🐛 Fix duplicate with alt and undo only undo one step 2023-02-15 12:20:47 +01:00
Alejandro
0f07def536 Merge pull request #2940 from penpot/alotor-layout-improvements
 Add space-evenly option
2023-02-15 12:08:36 +01:00
alonso.torres
490f5f19f1 Add space-evenly option 2023-02-15 12:08:22 +01:00
Alejandro
b3216000fd Merge pull request #2941 from penpot/alotor-fix-frame-opacity
🐛 Fix problem with opacity in frames
2023-02-15 11:57:10 +01:00
Alejandro
2ef3e4b325 Merge pull request #2944 from penpot/alotor-fix-unhandled-error
🐛 Fix crash when resizing frame
2023-02-15 11:49:17 +01:00
alonso.torres
70edd2c290 🐛 Fix crash when resizing frame 2023-02-15 09:59:28 +01:00
alonso.torres
02543b1a4f 🐛 Fix problem with opacity in frames 2023-02-14 17:54:51 +01:00
Alejandro
094556926e Merge pull request #2932 from penpot/eva-change-onboarding-images
💄 Update onboarding images with new style
2023-02-13 16:54:23 +01:00
Andrey Antukh
1ed3b3cf75 📎 Add missing restart policy to some containers
on default compose file
2023-02-10 14:07:12 +01:00
Eva
1637e82018 💄 Update onboarding images with new style 2023-02-10 13:52:53 +01:00
Andrey Antukh
c467d04d50 🐛 Fix permission issue on docker images 2023-02-10 13:37:33 +01:00
Andrey Antukh
8d19c067e8 🐛 Fix incorrect mountpoint on docker compose 2023-02-10 13:23:22 +01:00
Alejandro
a99fb7ada3 Merge pull request #2922 from penpot/palba-fix-middle-button-drags-guides
🐛 Fix middle button panning can drag guides
2023-02-09 14:27:28 +01:00
Alejandro
2f1d1a6c41 Merge pull request #2921 from penpot/eva-fix-invite-members-btn
🐛 Fix invite members text on modal button
2023-02-09 14:23:40 +01:00
Eva
7f963edf9e 🐛 Fix invite members text on modal button 2023-02-09 13:51:43 +01:00
Eva Marco
9c99d86e08 Merge pull request #2927 from penpot/alotor-fix-auto-size
Fix auto size
2023-02-09 13:51:03 +01:00
Eva
6a5bfdd7fb ❤️ Add thanks for ondrejkonec 2023-02-09 13:36:26 +01:00
Ondřej Konečný
a98ba72c12 added width property to avoid shrinking on icons 2023-02-09 13:33:06 +01:00
alonso.torres
ee42dd8b01 🐛 Fix layout on multiple selection 2023-02-09 11:18:37 +01:00
alonso.torres
da209b7507 🐛 Fix problem with auto sizes 2023-02-09 10:41:18 +01:00
Pablo Alba
d49e1f1641 🐛 Fix middle button panning can drag guides 2023-02-09 08:53:42 +01:00
Pablo Alba
8e35ad0f7f Merge pull request #2896 from penpot/eva-bugfixing-6
🐛 Fix paste a frame inside itself
2023-02-08 12:16:09 +01:00
Eva
be3a973d09 🐛 Fix paste a frame inside itself 2023-02-08 12:01:11 +01:00
Andrey Antukh
78aea0f24e 🐛 Fix incorrect props cleaning on auditlog 2023-02-08 10:35:57 +01:00
Andrey Antukh
6e1ce62aad Merge branch 'staging' 2023-02-07 17:06:42 +01:00
Alejandro
070ea135e5 Merge pull request #2919 from penpot/niwinz-docker-oidc-fixes
🐛 Docker & OIDC fixes
2023-02-07 16:56:22 +01:00
Andrey Antukh
5ae1fe5867 📎 Add nano editor to backend docker image 2023-02-07 16:50:58 +01:00
Andrey Antukh
eef2cba976 🐛 Fix incorrect registration flag handling on frontend
registration flag should not prevent include register on the
router because a registration process can be started from oidc
auth process
2023-02-07 16:50:52 +01:00
Andrey Antukh
1c4dcf1574 Add minor improvements to logging on docker images 2023-02-07 15:06:35 +01:00
Andrey Antukh
220b80799d Add more logging to OIDC providers 2023-02-07 14:49:12 +01:00
Andrey Antukh
f1085aadd1 🐛 Fix compatibility issues on docker upgrade path 2023-02-06 19:21:55 +01:00
Christian Clauss
b05ca4bb82 🐛 Fix undefined name RuntimeException on manage.py script
Python defines [`RuntimeError`](https://docs.python.org/3.7/library/exceptions.html#RuntimeError)
but it does not define `RuntimeException` so a `NameError` will be raised when any of these lines
are executed.

% `python3 -c "RuntimeException('This is a test...')"`
```
Traceback (most recent call last):
  File "<string>", line 1, in <module>
NameError: name 'RuntimeException' is not defined
```

% `flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics`
```
./backend/scripts/manage.py:22:15: F821 undefined name 'RuntimeException'
        raise RuntimeException(f"invalid PREPL_URI: {PREPL_URI}")
              ^
./backend/scripts/manage.py:25:15: F821 undefined name 'RuntimeException'
        raise RuntimeException(f"invalid PREPL_URI: {PREPL_URI}")
              ^
./backend/scripts/manage.py:49:23: F821 undefined name 'RuntimeException'
                raise RuntimeException("unexpected response from PREPL")
                      ^
3     F821 undefined name 'RuntimeException'
3
```
2023-02-05 11:19:41 +01:00
Mario Bašić
29c0190b7a 🐛 Add mailcatch to penpot network on docker compose
Without this the backend complains that it cannot connect to the smtp host (when using mailcatcher). The reason is because the mailcatcher is not on the same network as the backend application.
2023-02-05 11:16:34 +01:00
Alejandro Alonso
3cfc432c23 Merge remote-tracking branch 'origin/staging' 2023-02-02 18:07:20 +01:00
Andrey Antukh
43d034798c Merge branch 'staging' 2023-02-01 18:06:50 +01:00
Alejandro Alonso
707e6c2a33 Merge remote-tracking branch 'origin/staging' 2023-02-01 13:12:34 +01:00
Andrey Antukh
7ab91f68af Merge branch 'staging' 2023-01-31 23:02:22 +01:00
Alejandro Alonso
95cad24c18 Merge remote-tracking branch 'origin/staging' 2023-01-31 13:57:30 +01:00
Andrés Moya
77cd645e25 🔧 Update docker-compose without needing config file 2023-01-23 10:34:00 +01:00
Alejandro
04dc9f7881 Merge pull request #2736 from penpot/superalex-fix-text-sync-hotfix
🐛 Fix text content sync and touched detection in shape displacement
2023-01-09 11:35:59 +01:00
Andrés Moya
0863a96f93 🐛 Fix text content sync and touched detection in shape displacement 2023-01-05 13:26:33 +01:00
Alejandro
216a43cc43 Merge pull request #2731 from penpot/superalex-fix-enter-events-hotfix
🐛 Fix enter events
2023-01-05 07:02:34 +01:00
Alejandro Alonso
05431cc757 🐛 Fix enter events 2023-01-04 13:23:05 +01:00
54 changed files with 609 additions and 370 deletions

View File

@@ -1,4 +1,23 @@
# CHANGELOG
## 1.17.2
### :bug: Bugs fixed
- Fix invite members button text [Taiga #4794](https://tree.taiga.io/project/penpot/issue/4794)
- Fix problem with opacity in frames [Taiga #4795](https://tree.taiga.io/project/penpot/issue/4795)
- Fix correct behaviour for space-around and added space-evenly option
- Fix duplicate with alt and undo only undo one step [Taiga #4746](https://tree.taiga.io/project/penpot/issue/4746)
- Fix problem creating frames inside layout [Taiga #4844](https://tree.taiga.io/project/penpot/issue/4844)
## 1.17.2
### :bug: Bugs fixed
- Fix paste board inside itself [Taiga #4775](https://tree.taiga.io/project/penpot/issue/4775)
- Fix middle button panning can drag guides [Taiga #4266](https://tree.taiga.io/project/penpot/issue/4266)
### :heart: Community contributions by (Thank you!)
- To @ondrejkonec: for some code contributions on this release.
## 1.17.1

View File

@@ -12,6 +12,7 @@ cp ../CHANGES.md target/classes/changelog.md;
clojure -T:build jar;
mv target/penpot.jar target/dist/penpot.jar
cp resources/log4j2.xml target/dist/log4j2.xml
cp scripts/run.template.sh target/dist/run.sh;
cp scripts/manage.py target/dist/manage.py
chmod +x target/dist/run.sh;

View File

@@ -18,5 +18,7 @@ if [ -f ./environ ]; then
source ./environ
fi
export JVM_OPTS="-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -Dlog4j2.configurationFile=log4j2.xml -XX:-OmitStackTraceInFastThrow $JVM_OPTS"
set -x
exec $JAVA_CMD $JVM_OPTS "$@" -jar penpot.jar -m app.main

View File

@@ -64,10 +64,17 @@
nil)
(= 200 (:status response))
(let [data (json/decode (:body response))]
{:token-uri (get data :token_endpoint)
:auth-uri (get data :authorization_endpoint)
:user-uri (get data :userinfo_endpoint)})
(let [data (json/decode (:body response))
token-uri (get data :token_endpoint)
auth-uri (get data :authorization_endpoint)
user-uri (get data :userinfo_endpoint)]
(l/debug :hint "oidc uris discovered"
:token-uri token-uri
:auth-uri auth-uri
:user-uri user-uri)
{:token-uri token-uri
:auth-uri auth-uri
:user-uri user-uri})
:else
(do
@@ -110,7 +117,7 @@
(if-let [opts (prepare-oidc-opts cfg)]
(do
(l/info :hint "provider initialized"
:provider :oidc
:provider "oidc"
:method (if (:discover? opts) "discover" "manual")
:client-id (:client-id opts)
:client-secret (obfuscate-string (:client-secret opts))
@@ -122,7 +129,7 @@
:roles (:roles opts))
opts)
(do
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider :oidc)
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider "oidc")
nil))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -144,13 +151,13 @@
(string? (:client-secret opts)))
(do
(l/info :hint "provider initialized"
:provider :google
:provider "google"
:client-id (:client-id opts)
:client-secret (obfuscate-string (:client-secret opts)))
opts)
(do
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider :google)
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider "google")
nil)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -196,13 +203,13 @@
(string? (:client-secret opts)))
(do
(l/info :hint "provider initialized"
:provider :github
:provider "github"
:client-id (:client-id opts)
:client-secret (obfuscate-string (:client-secret opts)))
opts)
(do
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider :github)
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider "github")
nil)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -225,14 +232,14 @@
(string? (:client-secret opts)))
(do
(l/info :hint "provider initialized"
:provider :gitlab
:provider "gitlab"
:base-uri base
:client-id (:client-id opts)
:client-secret (obfuscate-string (:client-secret opts)))
opts)
(do
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider :gitlab)
(l/warn :hint "unable to initialize auth provider, missing configuration" :provider "gitlab")
nil)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -275,8 +282,19 @@
"accept" "application/json"}
:uri (:token-uri provider)
:body (u/map->query-string params)}]
(l/trace :hint "request access token"
:provider (:name provider)
:client-id (:client-id provider)
:client-secret (obfuscate-string (:client-secret provider))
:grant-type (:grant_type params)
:redirect-uri (:redirect_uri params))
(->> (http/req! cfg req)
(p/map (fn [{:keys [status body] :as res}]
(l/trace :hint "access token response"
:status status
:body body)
(if (= status 200)
(let [data (json/decode body)]
{:token (get data :access_token)
@@ -289,12 +307,19 @@
(defn- retrieve-user-info
[{:keys [provider] :as cfg} tdata]
(letfn [(retrieve []
(l/trace :hint "request user info"
:uri (:user-uri provider)
:token (obfuscate-string (:token tdata))
:token-type (:type tdata))
(http/req! cfg
{:uri (:user-uri provider)
:headers {"Authorization" (str (:type tdata) " " (:token tdata))}
:timeout 6000
:method :get}))
(validate-response [response]
(l/trace :hint "user info response"
:status (:status response)
:body (:body response))
(when-not (s/int-in-range? 200 300 (:status response))
(ex/raise :type :internal
:code :unable-to-retrieve-user-info
@@ -309,7 +334,7 @@
(if-let [get-email-fn (:get-email-fn provider)]
(get-email-fn tdata info)
(let [attr-kw (cf/get :oidc-email-attr :email)]
(get info attr-kw))))
(p/resolved (get info attr-kw)))))
(get-name [info]
(let [attr-kw (cf/get :oidc-name-attr :name)]
@@ -325,6 +350,7 @@
(qualify-props provider))}))
(validate-info [info]
(l/trace :hint "authentication info" :info info)
(when-not (s/valid? ::info info)
(l/warn :hint "received incomplete profile info object (please set correct scopes)"
:info (pr-str info))
@@ -334,10 +360,10 @@
:info info))
info)]
(-> (retrieve)
(p/then validate-response)
(p/then process-response)
(p/then validate-info))))
(->> (retrieve)
(p/fmap validate-response)
(p/mcat process-response)
(p/fmap validate-info))))
(s/def ::backend ::us/not-empty-string)
(s/def ::email ::us/not-empty-string)
@@ -434,12 +460,11 @@
(ex/raise :type :restriction
:code :profile-blocked))
(when-let [collector (::audit/collector cfg)]
(audit/submit! collector {:type "command"
:name "login"
:profile-id (:id profile)
:ip-addr (audit/parse-client-ip request)
:props (audit/profile->props profile)}))
(audit/submit! cfg {:type "command"
:name "login-with-password"
:profile-id (:id profile)
:ip-addr (audit/parse-client-ip request)
:props (audit/profile->props profile)})
(->> (redirect-response uri)
(sxf request)))

View File

@@ -167,7 +167,11 @@
(instance? javax.sql.DataSource v))
(s/def ::pool pool?)
(s/def ::conn some?)
;; DEPRECATED: to be removed in 1.18
(s/def ::conn-or-pool some?)
(s/def ::pool-or-conn some?)
(defn closed?
[pool]

View File

@@ -20,7 +20,6 @@
[app.loggers.audit.tasks :as-alias tasks]
[app.loggers.webhooks :as-alias webhooks]
[app.main :as-alias main]
[app.metrics :as mtx]
[app.rpc :as-alias rpc]
[app.tokens :as tokens]
[app.util.retry :as rtry]
@@ -30,7 +29,6 @@
[cuerdas.core :as str]
[integrant.core :as ig]
[lambdaisland.uri :as u]
[promesa.core :as p]
[promesa.exec :as px]
[yetti.request :as yrq]))
@@ -77,28 +75,20 @@
(merge (:props profile))
(d/without-nils)))
(defn clean-props
[{:keys [profile-id] :as event}]
(let [invalid-keys #{:session-id
:password
:old-password
:token}
xform (comp
(remove (fn [kv]
(qualified-keyword? (first kv))))
(remove (fn [kv]
(contains? invalid-keys (first kv))))
(remove (fn [[k v]]
(and (= k :profile-id)
(= v profile-id))))
(filter (fn [[_ v]]
(or (string? v)
(keyword? v)
(uuid? v)
(boolean? v)
(number? v)))))]
(def reserved-props
#{:session-id
:password
:old-password
:token})
(update event :props #(into {} xform %))))
(defn clean-props
[props]
(into {}
(comp
(d/without-nils)
(d/without-qualified)
(remove #(contains? reserved-props (key %))))
props))
;; --- SPECS
@@ -132,7 +122,7 @@
(s/keys :req [::wrk/executor ::db/pool]))
(defmethod ig/pre-init-spec ::collector [_]
(s/keys :req [::db/pool ::wrk/executor ::mtx/metrics]))
(s/keys :req [::db/pool ::wrk/executor]))
(defmethod ig/init-key ::collector
[_ {:keys [::db/pool] :as cfg}]
@@ -143,8 +133,8 @@
:else
cfg))
(defn- persist-event!
[pool event]
(defn- handle-event!
[conn-or-pool event]
(us/verify! ::event event)
(let [params {:id (uuid/next)
:name (:name event)
@@ -161,7 +151,7 @@
::rtry/max-retries 6
::rtry/label "persist-audit-log-event"}
(let [now (dt/now)]
(db/insert! pool :audit-log
(db/insert! conn-or-pool :audit-log
(-> params
(update :props db/tjson)
(update :ip-addr db/inet)
@@ -180,7 +170,7 @@
:else label)
dedupe? (boolean (and batch-key batch-timeout))]
(wrk/submit! ::wrk/conn pool
(wrk/submit! ::wrk/conn conn-or-pool
::wrk/task :process-webhook-event
::wrk/queue :webhooks
::wrk/max-retries 0
@@ -191,16 +181,19 @@
::webhooks/event
(-> params
(dissoc :ip-addr)
(dissoc :type)))))))
(dissoc :type)))))
params))
(defn submit!
"Submit audit event to the collector."
[{:keys [::wrk/executor ::db/pool] :as collector} params]
(us/assert! ::collector collector)
(->> (px/submit! executor (partial persist-event! pool (d/without-nils params)))
(p/merr (fn [cause]
(l/error :hint "audit: unexpected error processing event" :cause cause)
(p/resolved nil)))))
[{:keys [::wrk/executor] :as cfg} params]
(let [conn (or (::db/conn cfg) (::db/pool cfg))]
(us/assert! ::wrk/executor executor)
(us/assert! ::db/pool-or-conn conn)
(try
(handle-event! conn (d/without-nils params))
(catch Throwable cause
(l/error :hint "audit: unexpected error processing event" :cause cause)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; TASK: ARCHIVE

View File

@@ -14,7 +14,6 @@
[app.db :as-alias db]
[app.http.client :as-alias http.client]
[app.http.session :as-alias http.session]
[app.loggers.audit :as-alias audit]
[app.loggers.audit.tasks :as-alias audit.tasks]
[app.loggers.webhooks :as-alias webhooks]
[app.loggers.zmq :as-alias lzmq]
@@ -268,10 +267,8 @@
:github (ig/ref ::oidc.providers/github)
:gitlab (ig/ref ::oidc.providers/gitlab)
:oidc (ig/ref ::oidc.providers/generic)}
::audit/collector (ig/ref ::audit/collector)
::http.session/session (ig/ref :app.http.session/manager)}
;; TODO: revisit the dependencies of this service, looks they are too much unused of them
:app.http/router
{:assets (ig/ref :app.http.assets/handlers)
@@ -324,8 +321,7 @@
:scheduled-executor (ig/ref ::wrk/scheduled-executor)}
:app.rpc/methods
{::audit/collector (ig/ref ::audit/collector)
::http.client/client (ig/ref ::http.client/client)
{::http.client/client (ig/ref ::http.client/client)
::db/pool (ig/ref ::db/pool)
::wrk/executor (ig/ref ::wrk/executor)
::props (ig/ref :app.setup/props)
@@ -423,11 +419,6 @@
::lzmq/receiver
{}
::audit/collector
{::db/pool (ig/ref ::db/pool)
::wrk/executor (ig/ref ::wrk/executor)
::mtx/metrics (ig/ref ::mtx/metrics)}
::audit.tasks/archive
{::props (ig/ref :app.setup/props)
::db/pool (ig/ref ::db/pool)

View File

@@ -7,11 +7,11 @@
(ns app.rpc
(:require
[app.auth.ldap :as-alias ldap]
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.http :as-alias http]
[app.http.client :as-alias http.client]
@@ -164,7 +164,8 @@
(defn- wrap-audit
[cfg f mdata]
(if-let [collector (::audit/collector cfg)]
(if (or (contains? cf/flags :webhooks)
(contains? cf/flags :audit-log))
(letfn [(handle-audit [params result]
(let [resultm (meta result)
request (::http/request params)
@@ -181,8 +182,7 @@
(merge (::audit/props resultm))
(dissoc :profile-id)
(dissoc :type)))
(d/without-qualified)
(d/without-nils))
(audit/clean-props))
event {:type (or (::audit/type resultm)
(::type cfg))
@@ -210,13 +210,14 @@
(::webhooks/event? resultm)
false)}]
(audit/submit! collector event)))
(audit/submit! cfg event)))
(handle-request [cfg params]
(->> (f cfg params)
(p/mcat (fn [result]
(->> (handle-audit params result)
(p/map (constantly result)))))))]
(p/fnly (fn [result cause]
(when-not cause
(handle-audit params result))))))]
(if-not (::audit/skip mdata)
(with-meta handle-request mdata)
f))
@@ -316,8 +317,7 @@
(s/def ::sprops map?)
(defmethod ig/pre-init-spec ::methods [_]
(s/keys :req [::audit/collector
::http.client/client
(s/keys :req [::http.client/client
::db/pool
::ldap/provider
::wrk/executor]

View File

@@ -348,7 +348,7 @@
:extra-data ptoken})))
(defn register-profile
[{:keys [conn session] :as cfg} {:keys [token] :as params}]
[{:keys [::db/conn session] :as cfg} {:keys [token] :as params}]
(let [claims (tokens/verify (::main/props cfg) {:token token :iss :prepared-register})
params (merge params claims)
@@ -372,11 +372,10 @@
;; accordingly.
(when-let [id (:profile-id claims)]
(db/update! conn :profile {:modified-at (dt/now)} {:id id})
(when-let [collector (::audit/collector cfg)]
(audit/submit! collector
{:type "fact"
:name "register-profile-retry"
:profile-id id})))
(audit/submit! cfg
{:type "fact"
:name "register-profile-retry"
:profile-id id}))
(cond
;; If invitation token comes in params, this is because the
@@ -428,7 +427,7 @@
::doc/added "1.15"}
[{:keys [::db/pool] :as cfg} params]
(db/with-atomic [conn pool]
(-> (assoc cfg :conn conn)
(-> (assoc cfg ::db/conn conn)
(register-profile params))))
;; ---- COMMAND: Request Profile Recovery

View File

@@ -638,10 +638,11 @@
;; --- Mutation: Create Team Invitation
(def sql:upsert-team-invitation
"insert into team_invitation(team_id, email_to, role, valid_until)
values (?, ?, ?, ?)
"insert into team_invitation(id, team_id, email_to, role, valid_until)
values (?, ?, ?, ?, ?)
on conflict(team_id, email_to) do
update set role = ?, valid_until = ?, updated_at = now();")
update set role = ?, valid_until = ?, updated_at = now()
returning *")
(defn- create-invitation-token
[cfg {:keys [profile-id valid-until team-id member-id member-email role]}]
@@ -662,16 +663,8 @@
:exp (dt/in-future {:days 30})}))
(defn- create-invitation
[{:keys [::conn] :as cfg} {:keys [team profile role email] :as params}]
(let [member (profile/retrieve-profile-data-by-email conn email)
expire (dt/in-future "168h") ;; 7 days
itoken (create-invitation-token cfg {:profile-id (:id profile)
:valid-until expire
:team-id (:id team)
:member-email (or (:email member) email)
:member-id (:id member)
:role role})
ptoken (create-profile-identity-token cfg profile)]
[{:keys [::db/conn] :as cfg} {:keys [team profile role email] :as params}]
(let [member (profile/retrieve-profile-data-by-email conn email)]
(when (and member (not (eml/allow-send-emails? conn member)))
(ex/raise :type :validation
@@ -686,9 +679,6 @@
:email email
:hint "the email you invite has been repeatedly reported as spam or bounce"))
(when (contains? cf/flags :log-invitation-tokens)
(l/trace :hint "invitation token" :token itoken))
;; When we have email verification disabled and invitation user is
;; already present in the database, we proceed to add it to the
;; team as-is, without email roundtrip.
@@ -709,10 +699,38 @@
(when-not (:is-active member)
(db/update! conn :profile
{:is-active true}
{:id (:id member)})))
(do
(db/exec-one! conn [sql:upsert-team-invitation
(:id team) (str/lower email) (name role) expire (name role) expire])
{:id (:id member)}))
nil)
(let [id (uuid/next)
expire (dt/in-future "168h") ;; 7 days
invitation (db/exec-one! conn [sql:upsert-team-invitation id
(:id team) (str/lower email)
(name role) expire
(name role) expire])
updated? (not= id (:id invitation))
tprops {:profile-id (:id profile)
:invitation-id (:id invitation)
:valid-until expire
:team-id (:id team)
:member-email (:email-to invitation)
:member-id (:id member)
:role role}
itoken (create-invitation-token cfg tprops)
ptoken (create-profile-identity-token cfg profile)]
(when (contains? cf/flags :log-invitation-tokens)
(l/info :hint "invitation token" :token itoken))
(audit/submit! cfg
{:type "action"
:name (if updated?
"update-team-invitation"
"create-team-invitation")
:profile-id (:id profile)
:props (-> (dissoc tprops :profile-id)
(d/without-nils))})
(eml/send! {::eml/conn conn
::eml/factory eml/invite-to-team
:public-uri (cf/get :public-uri)
@@ -720,9 +738,9 @@
:invited-by (:fullname profile)
:team (:name team)
:token itoken
:extra-data ptoken})))
:extra-data ptoken})
itoken))
itoken))))
(s/def ::email ::us/email)
(s/def ::emails ::us/set-of-valid-emails)
@@ -763,14 +781,14 @@
:code :profile-is-muted
:hint "looks like the profile has reported repeatedly as spam or has permanent bounces"))
(let [cfg (assoc cfg ::conn conn)
(let [cfg (assoc cfg ::db/conn conn)
invitations (->> emails
(map (fn [email]
{:email (str/lower email)
:team team
:profile profile
:role role}))
(map (partial create-invitation cfg)))]
(keep (partial create-invitation cfg)))]
(with-meta (vec invitations)
{::audit/props {:invitations (count invitations)}})))))
@@ -789,7 +807,7 @@
(let [params (assoc params :profile-id profile-id)
team (create-team conn params)
profile (db/get-by-id conn :profile profile-id)
cfg (assoc cfg ::conn conn)]
cfg (assoc cfg ::db/conn conn)]
;; Create invitations for all provided emails.
(->> emails
@@ -812,18 +830,16 @@
::quotes/team-id (:id team)
::quotes/incr (count emails)}))
(-> team
(vary-meta assoc ::audit/props {:invitations (count emails)})
(rph/with-defer
#(when-let [collector (::audit/collector cfg)]
(audit/submit! collector
{:type "command"
:name "create-team-invitations"
:profile-id profile-id
:props {:emails emails
:role role
:profile-id profile-id
:invitations (count emails)}})))))))
(audit/submit! cfg
{:type "command"
:name "create-team-invitations"
:profile-id profile-id
:props {:emails emails
:role role
:profile-id profile-id
:invitations (count emails)}})
(vary-meta team assoc ::audit/props {:invitations (count emails)}))))
;; --- Query: get-team-invitation-token
@@ -839,7 +855,7 @@
{:team-id team-id
:email-to (str/lower email)})
(update :role keyword))
member (profile/retrieve-profile-data-by-email pool (:email invit))
member (profile/retrieve-profile-data-by-email pool (:email-to invit))
token (create-invitation-token cfg {:team-id (:team-id invit)
:profile-id profile-id
:valid-until (:valid-until invit)
@@ -885,6 +901,7 @@
(ex/raise :type :validation
:code :insufficient-permissions))
(db/delete! conn :team-invitation
{:team-id team-id :email-to (str/lower email)})
nil)))
(let [invitation (db/delete! conn :team-invitation
{:team-id team-id
:email-to (str/lower email)})]
(rph/wrap nil {::audit/props {:invitation-id (:id invitation)}})))))

View File

@@ -133,7 +133,7 @@
:opt-un [::spec.team-invitation/member-id]))
(defmethod process-token :team-invitation
[{:keys [conn session] :as cfg}
[{:keys [conn] :as cfg}
{:keys [::rpc/profile-id token]}
{:keys [member-id team-id member-email] :as claims}]
@@ -152,45 +152,30 @@
(if (some? profile)
(if (or (= member-id profile-id)
(= member-email (:email profile)))
;; if we have logged-in user and it matches the invitation we
;; proceed with accepting the invitation and joining the
;; current profile to the invited team.
;; if we have logged-in user and it matches the invitation we proceed
;; with accepting the invitation and joining the current profile to the
;; invited team.
(let [profile (accept-invitation cfg claims invitation profile)]
(-> (assoc claims :state :created)
(rph/with-meta {::audit/name "accept-team-invitation"
::audit/props (merge
(audit/profile->props profile)
{:team-id (:team-id claims)
:role (:role claims)})
::audit/profile-id profile-id})))
::audit/profile-id (:id profile)
::audit/props {:team-id (:team-id claims)
:role (:role claims)
:invitation-id (:id invitation)}})))
(ex/raise :type :validation
:code :invalid-token
:hint "logged-in user does not matches the invitation"))
;; If we have not logged-in user, we try find the invited
;; profile by member-id or member-email props of the invitation
;; token; If profile is found, we accept the invitation and
;; leave the user logged-in.
(if-let [member (db/get* conn :profile
(if member-id
{:id member-id}
{:email member-email})
{:columns [:id :email]})]
(let [profile (accept-invitation cfg claims invitation member)]
(-> (assoc claims :state :created)
(rph/with-transform (session/create-fn session (:id profile)))
(rph/with-meta {::audit/name "accept-team-invitation"
::audit/props (merge
(audit/profile->props profile)
{:team-id (:team-id claims)
:role (:role claims)})
::audit/profile-id member-id})))
;; If we have not logged-in user, and invitation comes with member-id we
;; redirect user to login, if no memeber-id is present in the invitation
;; token, we redirect user the the register page.
{:invitation-token token
:iss :team-invitation
:redirect-to :auth-register
:state :pending}))))
{:invitation-token token
:iss :team-invitation
:redirect-to (if member-id :auth-login :auth-register)
:state :pending})))
;; --- Default

View File

@@ -166,6 +166,7 @@
(t/deftest accept-invitation-tokens
(let [profile1 (th/create-profile* 1 {:is-active true})
profile2 (th/create-profile* 2 {:is-active true})
profile3 (th/create-profile* 3 {:is-active true})
team (th/create-team* 1 {:profile-id (:id profile1)})
@@ -181,25 +182,29 @@
:member-email (:email profile2)
:member-id (:id profile2)})]
;; --- Verify token as anonymous user
(t/testing "Verify token as anonymous user"
(db/insert! pool :team-invitation
{:team-id (:id team)
:email-to (:email profile2)
:role "editor"
:valid-until (dt/in-future "48h")})
(db/insert! pool :team-invitation
{:team-id (:id team)
:email-to (:email profile2)
:role "editor"
:valid-until (dt/in-future "48h")})
(let [data {::th/type :verify-token :token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (th/success? out))
(let [data {::th/type :verify-token :token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (th/success? out))
(let [result (:result out)]
(t/is (= :created (:state result)))
(t/is (= (:email profile2) (:member-email result)))
(t/is (= (:id profile2) (:member-id result))))
(let [result (:result out)]
(t/is (contains? result :invitation-token))
(t/is (contains? result :iss))
(t/is (contains? result :redirect-to))
(t/is (contains? result :state))
(let [rows (db/query pool :team-profile-rel {:team-id (:id team)})]
(t/is (= 2 (count rows)))))
(t/is (= :pending (:state result)))
(t/is (= :auth-login (:redirect-to result))))
(let [rows (db/query pool :team-profile-rel {:team-id (:id team)})]
(t/is (= 1 (count rows))))))
;; Clean members
(db/delete! pool :team-profile-rel
@@ -207,46 +212,37 @@
:profile-id (:id profile2)})
;; --- Verify token as logged-in user
(t/testing "Verify token as logged-in user"
(let [data {::th/type :verify-token
::rpc/profile-id (:id profile2)
:token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (th/success? out))
(let [result (:result out)]
(t/is (= :created (:state result)))
(t/is (= (:email profile2) (:member-email result)))
(t/is (= (:id profile2) (:member-id result))))
(db/insert! pool :team-invitation
{:team-id (:id team)
:email-to (:email profile2)
:role "editor"
:valid-until (dt/in-future "48h")})
(let [rows (db/query pool :team-profile-rel {:team-id (:id team)})]
(t/is (= 2 (count rows))))))
(let [data {::th/type :verify-token
::rpc/profile-id (:id profile2)
:token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (th/success? out))
(let [result (:result out)]
(t/is (= :created (:state result)))
(t/is (= (:email profile2) (:member-email result)))
(t/is (= (:id profile2) (:member-id result))))
(t/testing "Verify token as logged-in wrong user"
(db/insert! pool :team-invitation
{:team-id (:id team)
:email-to (:email profile3)
:role "editor"
:valid-until (dt/in-future "48h")})
(let [rows (db/query pool :team-profile-rel {:team-id (:id team)})]
(t/is (= 2 (count rows)))))
;; --- Verify token as logged-in wrong user
(db/insert! pool :team-invitation
{:team-id (:id team)
:email-to (:email profile2)
:role "editor"
:valid-until (dt/in-future "48h")})
(let [data {::th/type :verify-token
::rpc/profile-id (:id profile1)
:token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (not (th/success? out)))
(let [edata (-> out :error ex-data)]
(t/is (= :validation (:type edata)))
(t/is (= :invalid-token (:code edata)))))
(let [data {::th/type :verify-token
::rpc/profile-id (:id profile1)
:token token}
out (th/command! data)]
;; (th/print-result! out)
(t/is (not (th/success? out)))
(let [edata (-> out :error ex-data)]
(t/is (= :validation (:type edata)))
(t/is (= :invalid-token (:code edata))))))
)))

View File

@@ -203,19 +203,22 @@
([coll value]
(sequence (replace-by-id value) coll)))
(defn without-nils
"Given a map, return a map removing key-value
pairs when value is `nil`."
[data]
(into {} (remove (comp nil? second)) data))
(defn vec-without-nils
[coll]
(into [] (remove nil?) coll))
(defn without-nils
"Given a map, return a map removing key-value
pairs when value is `nil`."
([] (remove (comp nil? val)))
([data]
(into {} (without-nils) data)))
(defn without-qualified
[data]
(into {} (remove (comp qualified-keyword? first)) data))
([]
(remove (comp qualified-keyword? key)))
([data]
(into {} (without-qualified) data)))
(defn without-keys
"Return a map without the keys provided

View File

@@ -68,20 +68,28 @@
(gpt/add base-p (hv 0.01))
(gpt/add base-p (vv 0.01))]
col?
(conj (gpt/add base-p (vv min-height)))
row?
(conj (gpt/add base-p (hv min-width)))
(and col? h-start?)
(conj (gpt/add base-p (hv min-width)))
(and col? h-center?)
(conj (gpt/add base-p (hv (/ min-width 2))))
(conj (gpt/add base-p (hv (/ min-width 2)))
(gpt/subtract base-p (hv (/ min-width 2))))
(and col? h-center?)
(and col? h-end?)
(conj (gpt/subtract base-p (hv min-width)))
(and row? v-start?)
(conj (gpt/add base-p (vv min-height)))
(and row? v-center?)
(conj (gpt/add base-p (vv (/ min-height 2))))
(conj (gpt/add base-p (vv (/ min-height 2)))
(gpt/subtract base-p (vv (/ min-height 2))))
(and row? v-end?)
(conj (gpt/subtract base-p (vv min-height))))))
@@ -120,16 +128,19 @@
row? (ctl/row? parent)
col? (ctl/col? parent)
space-around? (ctl/space-around? parent)
content-around? (ctl/content-around? parent)
space-evenly? (ctl/space-evenly? parent)
content-evenly? (ctl/content-evenly? parent)
[layout-gap-row layout-gap-col] (ctl/gaps parent)
row-pad (if (or (and col? space-around?)
(and row? content-around?))
row-pad (if (or (and col? space-evenly?)
(and col? space-around?)
(and row? content-evenly?))
layout-gap-row
0)
col-pad (if (or(and row? space-around?)
(and col? content-around?))
col-pad (if (or(and row? space-evenly?)
(and row? space-around?)
(and col? content-evenly?))
layout-gap-col
0)

View File

@@ -24,9 +24,10 @@
"Calculates the lines basic data and accumulated values. The positions will be calculated in a different operation"
[shape children layout-bounds]
(let [col? (ctl/col? shape)
row? (ctl/row? shape)
(let [col? (ctl/col? shape)
row? (ctl/row? shape)
space-around? (ctl/space-around? shape)
space-evenly? (ctl/space-evenly? shape)
wrap? (and (ctl/wrap? shape)
(or col? (not (ctl/auto-width? shape)))
@@ -78,18 +79,28 @@
next-max-width (+ child-margin-width (if fill-width? child-max-width child-width))
next-max-height (+ child-margin-height (if fill-height? child-max-height child-height))
total-gap-col (if space-around?
total-gap-col (cond
space-evenly?
(* layout-gap-col (+ num-children 2))
space-around?
(* layout-gap-col (+ num-children 1))
:else
(* layout-gap-col num-children))
total-gap-row (if space-around?
total-gap-row (cond
space-evenly?
(* layout-gap-row (+ num-children 2))
space-around?
(* layout-gap-row (+ num-children 1))
:else
(* layout-gap-row num-children))
next-line-min-width (+ line-min-width next-min-width total-gap-col)
next-line-min-height (+ line-min-height next-min-height total-gap-row)
]
next-line-min-height (+ line-min-height next-min-height total-gap-row)]
(if (and (some? line-data)
(or (not wrap?)
@@ -150,10 +161,11 @@
(defn add-lines-positions
[parent layout-bounds layout-lines]
(let [row? (ctl/row? parent)
col? (ctl/col? parent)
auto-width? (ctl/auto-width? parent)
auto-height? (ctl/auto-height? parent)
(let [row? (ctl/row? parent)
col? (ctl/col? parent)
auto-width? (ctl/auto-width? parent)
auto-height? (ctl/auto-height? parent)
space-evenly? (ctl/space-evenly? parent)
space-around? (ctl/space-around? parent)
[layout-gap-row layout-gap-col] (ctl/gaps parent)
@@ -183,10 +195,26 @@
(->> layout-lines (reduce add-ranges [0 0 0 0]))
get-layout-width (fn [{:keys [num-children]}]
(let [num-gap (if space-around? (inc num-children) (dec num-children))]
(let [num-gap (cond
space-evenly?
(inc num-children)
space-around?
num-children
:else
(dec num-children))]
(- layout-width (* layout-gap-col num-gap))))
get-layout-height (fn [{:keys [num-children]}]
(let [num-gap (if space-around? (inc num-children) (dec num-children))]
(let [num-gap (cond
space-evenly?
(inc num-children)
space-around?
num-children
:else
(dec num-children))]
(- layout-height (* layout-gap-row num-gap))))
num-lines (count layout-lines)
@@ -280,34 +308,47 @@
auto-height? (ctl/auto-height? shape)
auto-width? (ctl/auto-width? shape)
space-between? (ctl/space-between? shape)
space-evenly? (ctl/space-evenly? shape)
space-around? (ctl/space-around? shape)
[layout-gap-row layout-gap-col] (ctl/gaps shape)
margin-x
(cond (and row? space-around? (not auto-width?))
(cond (and row? space-evenly? (not auto-width?))
(max layout-gap-col (/ (- width line-width) (inc num-children)))
(and row? space-around? auto-width?)
(and row? space-around? (not auto-width?))
(/ (max layout-gap-col (/ (- width line-width) num-children)) 2)
(and row? (or space-evenly? space-around?) auto-width?)
layout-gap-col
:else
0)
margin-y
(cond (and col? space-around? (not auto-height?))
(cond (and col? space-evenly? (not auto-height?))
(max layout-gap-row (/ (- height line-height) (inc num-children)))
(and col? space-around? auto-height?)
(and col? space-around? (not auto-height?))
(/ (max layout-gap-row (/ (- height line-height) num-children)) 2)
(and col? (or space-evenly? space-around?) auto-height?)
layout-gap-row
:else
0)
layout-gap-col
(cond (and row? space-around?)
(cond (and row? space-evenly?)
0
(and row? space-around? auto-width?)
0
(and row? space-around?)
(/ (max layout-gap-col (/ (- width line-width) num-children)) 2)
(and row? space-between? (not auto-width?))
(max layout-gap-col (/ (- width line-width) (dec num-children)))
@@ -315,9 +356,15 @@
layout-gap-col)
layout-gap-row
(cond (and col? space-around?)
(cond (and col? space-evenly?)
0
(and col? space-evenly? auto-height?)
0
(and col? space-around?)
(/ (max layout-gap-row (/ (- height line-height) num-children)) 2)
(and col? space-between? (not auto-height?))
(max layout-gap-row (/ (- height line-height) (dec num-children)))

View File

@@ -27,6 +27,7 @@
center? (or (and wrap? (ctl/content-center? parent))
(and (not wrap?) (ctl/align-items-center? parent)))
around? (and wrap? (ctl/content-around? parent))
evenly? (and wrap? (ctl/content-evenly? parent))
;; Adjust the totals so it takes into account the gaps
[layout-gap-row layout-gap-col] (ctl/gaps parent)
@@ -47,6 +48,9 @@
(gpt/add (vv free-height-gap))
around?
(gpt/add (vv (max lines-gap-row (/ free-height num-lines 2))))
evenly?
(gpt/add (vv (max lines-gap-row (/ free-height (inc num-lines))))))
col?
@@ -57,6 +61,9 @@
(gpt/add (hv free-width-gap))
around?
(gpt/add (hv (max lines-gap-col (/ free-width num-lines) 2)))
evenly?
(gpt/add (hv (max lines-gap-col (/ free-width (inc num-lines)))))))))
(defn get-next-line
@@ -78,6 +85,7 @@
stretch? (ctl/content-stretch? parent)
between? (ctl/content-between? parent)
around? (ctl/content-around? parent)
evenly? (ctl/content-evenly? parent)
free-width (- layout-width total-width)
free-height (- layout-height total-height)
@@ -94,6 +102,9 @@
(/ free-width (dec num-lines))
around?
(/ free-width num-lines)
evenly?
(/ free-width (inc num-lines))
:else
@@ -111,6 +122,9 @@
(/ free-height (dec num-lines))
around?
(/ free-height num-lines)
evenly?
(/ free-height (inc num-lines))
:else
@@ -134,6 +148,7 @@
col? (ctl/col? parent)
space-between? (ctl/space-between? parent)
space-around? (ctl/space-around? parent)
space-evenly? (ctl/space-evenly? parent)
h-center? (ctl/h-center? parent)
h-end? (ctl/h-end? parent)
v-center? (ctl/v-center? parent)
@@ -159,20 +174,20 @@
start-p
(cond-> base-p
;; X AXIS
(and row? h-center? (not space-around?) (not space-between?))
(and row? h-center? (not space-around?) (not space-evenly?) (not space-between?))
(-> (gpt/add (hv (/ layout-width 2)))
(gpt/subtract (hv (/ (+ line-width children-gap-width) 2))))
(and row? h-end? (not space-around?) (not space-between?))
(and row? h-end? (not space-around?) (not space-evenly?) (not space-between?))
(-> (gpt/add (hv layout-width))
(gpt/subtract (hv (+ line-width children-gap-width))))
;; Y AXIS
(and col? v-center? (not space-around?) (not space-between?))
(and col? v-center? (not space-around?) (not space-evenly?) (not space-between?))
(-> (gpt/add (vv (/ layout-height 2)))
(gpt/subtract (vv (/ (+ line-height children-gap-height) 2))))
(and col? v-end? (not space-around?) (not space-between?))
(and col? v-end? (not space-around?) (not space-evenly?) (not space-between?))
(-> (gpt/add (vv layout-height))
(gpt/subtract (vv (+ line-height children-gap-height)))))]

View File

@@ -115,13 +115,15 @@
(if (empty? children)
modif-tree
(let [child-id (first children)
child (get objects child-id)
child-bounds @(get bounds child-id)
child-modifiers (gct/calc-child-modifiers parent child modifiers ignore-constraints child-bounds parent-bounds transformed-parent-bounds)]
(recur (cond-> modif-tree
(not (ctm/empty? child-modifiers))
(update-in [child-id :modifiers] ctm/add-modifiers child-modifiers))
(rest children)))))))))
child (get objects child-id)]
(if (some? child)
(let [child-bounds @(get bounds child-id)
child-modifiers (gct/calc-child-modifiers parent child modifiers ignore-constraints child-bounds parent-bounds transformed-parent-bounds)]
(recur (cond-> modif-tree
(not (ctm/empty? child-modifiers))
(update-in [child-id :modifiers] ctm/add-modifiers child-modifiers))
(rest children)))
(recur modif-tree (rest children))))))))))
(defn get-group-bounds
[objects bounds modif-tree shape]

View File

@@ -15,8 +15,8 @@
;; :layout-gap-type ;; :simple, :multiple
;; :layout-gap ;; {:row-gap number , :column-gap number}
;; :layout-align-items ;; :start :end :center :stretch
;; :layout-justify-content ;; :start :center :end :space-between :space-around
;; :layout-align-content ;; :start :center :end :space-between :space-around :stretch (by default)
;; :layout-justify-content ;; :start :center :end :space-between :space-around :space-evenly
;; :layout-align-content ;; :start :center :end :space-between :space-around :space-evenly :stretch (by default)
;; :layout-wrap-type ;; :wrap, :nowrap
;; :layout-padding-type ;; :simple, :multiple
;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
@@ -36,8 +36,8 @@
(s/def ::layout-gap-type #{:simple :multiple})
(s/def ::layout-gap ::us/safe-number)
(s/def ::layout-align-items #{:start :end :center :stretch})
(s/def ::layout-align-content #{:start :end :center :space-between :space-around :stretch})
(s/def ::layout-justify-content #{:start :center :end :space-between :space-around})
(s/def ::layout-align-content #{:start :end :center :space-between :space-around :space-evenly :stretch})
(s/def ::layout-justify-content #{:start :center :end :space-between :space-around :space-evenly})
(s/def ::layout-wrap-type #{:wrap :nowrap :no-wrap}) ;;TODO remove no-wrap after script
(s/def ::layout-padding-type #{:simple :multiple})
@@ -286,6 +286,10 @@
[{:keys [layout-align-content]}]
(= :space-around layout-align-content))
(defn content-evenly?
[{:keys [layout-align-content]}]
(= :space-evenly layout-align-content))
(defn content-stretch?
[{:keys [layout-align-content]}]
(or (= :stretch layout-align-content)
@@ -320,6 +324,10 @@
[{:keys [layout-justify-content]}]
(= layout-justify-content :space-around))
(defn space-evenly?
[{:keys [layout-justify-content]}]
(= layout-justify-content :space-evenly))
(defn align-self-start? [{:keys [layout-item-align-self]}]
(= :start layout-item-align-self))
@@ -349,4 +357,3 @@
(some (partial fill-height? objects) children-ids))
(and (row? objects frame-id)
(every? (partial fill-height? objects) children-ids)))))

View File

@@ -205,9 +205,8 @@
(defn all-frames-by-position
[objects position]
(->> (get-frames-ids objects)
(sort-z-index objects)
(filterv #(and position (gsh/has-point? (get objects %) position)))))
(filter #(and position (gsh/has-point? (get objects %) position)))
(sort-z-index objects)))
(defn top-nested-frame
"Search for the top nested frame for positioning shapes when moving or creating.

View File

@@ -13,6 +13,7 @@ RUN set -ex; \
apt-get -qq update; \
apt-get -qq upgrade; \
apt-get -qqy --no-install-recommends install \
nano \
curl \
tzdata \
locales \
@@ -28,7 +29,7 @@ RUN set -ex; \
; \
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \
locale-gen; \
mkdir -p /opt/data; \
mkdir -p /opt/data/assets; \
mkdir -p /opt/penpot; \
chown -R penpot:penpot /opt/penpot; \
chown -R penpot:penpot /opt/data; \

View File

@@ -1,6 +1,11 @@
FROM nginx:1.23
LABEL maintainer="Andrey Antukh <niwi@niwi.nz>"
RUN set -ex; \
useradd -U -M -u 1001 -s /bin/false -d /opt/penpot penpot; \
mkdir -p /opt/data/assets; \
chown -R penpot:penpot /opt/data;
ADD ./bundle-frontend/ /var/www/app/
ADD ./files/config.js /var/www/app/js/config.js
ADD ./files/nginx.conf /etc/nginx/nginx.conf

View File

@@ -36,6 +36,7 @@ services:
penpot-frontend:
image: "penpotapp/frontend:latest"
restart: always
ports:
- 9001:80
@@ -96,8 +97,10 @@ services:
penpot-backend:
image: "penpotapp/backend:latest"
restart: always
volumes:
- penpot_assets:/opt/penpot/assets
- penpot_assets:/opt/data/assets
depends_on:
- penpot-postgres
@@ -214,6 +217,7 @@ services:
penpot-exporter:
image: "penpotapp/exporter:latest"
restart: always
networks:
- penpot
@@ -268,6 +272,7 @@ services:
# minio:
# image: "minio/minio:latest"
# command: minio server /mnt/data --console-address ":9001"
# restart: always
#
# volumes:
# - "penpot_minio:/mnt/data"

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 9.7 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 132.292 132.292">
<path d="M0 0v132.292h11.207V0Zm121.085 0v132.292h11.207V0ZM36.023 28.259c-6.487 0-11.745 5.258-11.745 11.744 0 6.487 5.258 11.745 11.745 11.745 6.486 0 11.744-5.26 11.744-11.745 0-6.486-5.258-11.744-11.744-11.744zm30.04 0c-6.486 0-11.744 5.258-11.744 11.744 0 6.487 5.258 11.745 11.744 11.745 6.487 0 11.745-5.26 11.745-11.745 0-6.486-5.259-11.744-11.745-11.744zm30.496 0c-6.487 0-11.745 5.258-11.745 11.744 0 6.487 5.258 11.745 11.745 11.745 6.486 0 11.744-5.26 11.744-11.745 0-6.486-5.258-11.744-11.744-11.744zM36.023 80.545c-6.487 0-11.745 5.26-11.745 11.745 0 6.486 5.258 11.745 11.745 11.745 6.486 0 11.744-5.259 11.744-11.745 0-6.486-5.258-11.744-11.744-11.744zm30.04 0c-6.486 0-11.744 5.26-11.744 11.745 0 6.486 5.258 11.745 11.744 11.745 6.487 0 11.745-5.259 11.745-11.745 0-6.486-5.259-11.744-11.745-11.744z"/>
</svg>

After

Width:  |  Height:  |  Size: 926 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 132.292 132.292">
<path d="M0 0v11.207h132.292V0H0zm36.567 29.029c-5.96.092-12.09 4.407-12.289 10.974 0 6.487 5.258 11.745 11.745 11.745 6.486 0 11.744-5.26 11.744-11.745-.81-7.848-5.94-11.055-11.2-10.974zm60.536 0c-5.96.092-12.09 4.407-12.289 10.974 0 6.487 5.258 11.745 11.745 11.745 6.486 0 11.744-5.26 11.744-11.745-.81-7.848-5.94-11.055-11.2-10.974zm-30.495 0c-5.96.092-12.09 4.407-12.29 10.974 0 6.487 5.259 11.745 11.745 11.745 6.487 0 11.745-5.26 11.745-11.745-.811-7.848-5.94-11.055-11.2-10.974zM36.023 80.545c-6.487 0-11.745 5.26-11.745 11.745 0 6.486 5.258 11.745 11.745 11.745 6.486 0 11.744-5.259 11.744-11.745 0-6.486-5.258-11.744-11.744-11.744zm30.04 0c-6.486 0-11.744 5.26-11.744 11.745 0 6.486 5.258 11.745 11.744 11.745 6.487 0 11.745-5.259 11.745-11.745 0-6.486-5.26-11.744-11.745-11.744zM0 121.085v11.207h132.292v-11.207H0z"/>
</svg>

After

Width:  |  Height:  |  Size: 934 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 132.292 132.292">
<path d="M0 0v11.207h132.292V0Zm18.947 28.264V57.99h94.4V28.264zm.098 47.096v29.726h94.302V75.36ZM0 121.086v11.206h132.292v-11.207z"/>
</svg>

After

Width:  |  Height:  |  Size: 240 B

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" viewBox="0 0 132.292 132.292">
<path d="M0 132.292h11.207V0H0Zm28.264-18.947H57.99v-94.4H28.264zm47.096-.098h29.726V18.945H75.36Zm45.726 19.045h11.206V0h-11.207z"/>
</svg>

After

Width:  |  Height:  |  Size: 239 B

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -50,7 +50,7 @@
}
img {
width: 274px;
margin-bottom: -41px;
margin-bottom: -19px;
@media (max-width: 1200px) {
display: none;
width: 0;

View File

@@ -1403,6 +1403,7 @@
align-items: center;
justify-content: center;
background: #31efb8;
min-width: 28px;
width: 28px;
height: 28px;
border-radius: 50%;
@@ -1494,10 +1495,6 @@
display: grid;
grid-template-columns: 1fr auto;
gap: 8px;
.btn-large {
overflow: hidden;
text-overflow: ellipsis;
}
.btn-primary {
max-width: 250px;
}
@@ -1586,7 +1583,19 @@
}
}
.buttons {
margin-top: 12px;
margin: 12px 0;
button {
height: auto;
}
input {
white-space: break-spaces;
word-break: break-word;
height: fit-content;
margin: 0;
padding: 5px 10px;
min-height: 40px;
max-height: 90px;
}
}
}
}

View File

@@ -1644,6 +1644,7 @@
font-family: "worksans", sans-serif;
&.justify-content,
&.align-content,
&.sizing {
align-items: start;
margin-top: 4px;
@@ -1658,7 +1659,8 @@
gap: 5px;
}
&.justify-content {
&.justify-content,
&.align-content {
display: flex;
flex-direction: column;
gap: 5px;

View File

@@ -1440,6 +1440,19 @@
(and (= 1 (count selected))
(= :frame (get-in objects [(first selected) :type])))))
(defn same-frame-from-selected? [state frame-id]
(let [selected (wsh/lookup-selected state)]
(contains? frame-id (first selected))))
(defn frame-same-size?
[paste-obj frame-obj]
(and
(= (:heigth (:selrect (first (vals paste-obj))))
(:heigth (:selrect frame-obj)))
(= (:width (:selrect (first (vals paste-obj))))
(:width (:selrect frame-obj)))))
(defn- paste-shape
[{selected :selected
paste-objects :objects ;; rename this because here comes only the clipboard shapes,
@@ -1478,55 +1491,67 @@
item))
(calculate-paste-position [state mouse-pos in-viewport?]
(let [page-objects (wsh/lookup-page-objects state)
selected-objs (map #(get paste-objects %) selected)
page-selected (wsh/lookup-selected state)
wrapper (gsh/selection-rect selected-objs)
orig-pos (gpt/point (:x1 wrapper) (:y1 wrapper))]
(let [page-objects (wsh/lookup-page-objects state)
selected-objs (map #(get paste-objects %) selected)
first-selected-obj (first selected-objs)
page-selected (wsh/lookup-selected state)
wrapper (gsh/selection-rect selected-objs)
orig-pos (gpt/point (:x1 wrapper) (:y1 wrapper))
frame-id (first page-selected)
frame-object (get page-objects frame-id)
base (cph/get-base-shape page-objects page-selected)
index (cph/get-position-on-parent page-objects (:id base))]
(cond
;; Pasting inside a frame
(selected-frame? state)
(let [frame-id (first page-selected)
frame-object (get page-objects frame-id)
origin-frame-id (:frame-id (first selected-objs))
origin-frame-object (get page-objects origin-frame-id)
(if (or (same-frame-from-selected? state (first (vals paste-objects)))
(frame-same-size? paste-objects frame-object))
;; Paste next to selected frame, if selected is itself or of the same size as the copied
(let [selected-frame-obj (get page-objects (first page-selected))
parent-id (:parent-id base)
paste-x (+ (:width selected-frame-obj) (:x selected-frame-obj) 50)
paste-y (:y selected-frame-obj)
delta (gpt/subtract (gpt/point paste-x paste-y) orig-pos)]
margin-x (-> (- (:width origin-frame-object) (+ (:x wrapper) (:width wrapper)))
(min (- (:width frame-object) (:width wrapper))))
[(:frame-id base) parent-id delta index])
margin-y (-> (- (:height origin-frame-object) (+ (:y wrapper) (:height wrapper)))
(min (- (:height frame-object) (:height wrapper))))
;; Paste inside selected frame otherwise
(let [origin-frame-id (:frame-id first-selected-obj)
origin-frame-object (get page-objects origin-frame-id)
margin-x (-> (- (:width origin-frame-object) (+ (:x wrapper) (:width wrapper)))
(min (- (:width frame-object) (:width wrapper))))
margin-y (-> (- (:height origin-frame-object) (+ (:y wrapper) (:height wrapper)))
(min (- (:height frame-object) (:height wrapper))))
;; Pasted objects mustn't exceed the selected frame x limit
paste-x (if (> (+ (:width wrapper) (:x1 wrapper)) (:width frame-object))
(+ (- (:x frame-object) (:x orig-pos)) (- (:width frame-object) (:width wrapper) margin-x))
(:x frame-object))
paste-x (if (> (+ (:width wrapper) (:x1 wrapper)) (:width frame-object))
(+ (- (:x frame-object) (:x orig-pos)) (- (:width frame-object) (:width wrapper) margin-x))
(:x frame-object))
;; Pasted objects mustn't exceed the selected frame y limit
paste-y (if (> (+ (:height wrapper) (:y1 wrapper)) (:height frame-object))
(+ (- (:y frame-object) (:y orig-pos)) (- (:height frame-object) (:height wrapper) margin-y))
(:y frame-object))
paste-y (if (> (+ (:height wrapper) (:y1 wrapper)) (:height frame-object))
(+ (- (:y frame-object) (:y orig-pos)) (- (:height frame-object) (:height wrapper) margin-y))
(:y frame-object))
delta (if (= origin-frame-id uuid/zero)
delta (if (= origin-frame-id uuid/zero)
;; When the origin isn't in a frame the result is pasted in the center.
(gpt/subtract (gsh/center-shape frame-object) (gsh/center-selrect wrapper))
(gpt/subtract (gsh/center-shape frame-object) (gsh/center-selrect wrapper))
;; When pasting from one frame to another frame the object position must be limited to container boundaries. If the pasted object doesn't fit we try to:
;; - Align it to the limits on the x and y axis
;; - Respect the distance of the object to the right and bottom in the original frame
(gpt/point paste-x paste-y))]
[frame-id frame-id delta])
(gpt/point paste-x paste-y))]
[frame-id frame-id delta]))
(empty? page-selected)
(let [frame-id (ctst/top-nested-frame page-objects mouse-pos)
delta (gpt/subtract mouse-pos orig-pos)]
[frame-id frame-id delta])
:else
(let [base (cph/get-base-shape page-objects page-selected)
index (cph/get-position-on-parent page-objects (:id base))
frame-id (:frame-id base)
(let [frame-id (:frame-id base)
parent-id (:parent-id base)
delta (if in-viewport?
(gpt/subtract mouse-pos orig-pos)

View File

@@ -34,6 +34,23 @@
(declare commit-changes)
(defn- add-group-id
[changes state]
(let [undo (:workspace-undo state)
items (:items undo)
index (or (:index undo) (dec (count items)))
prev-item (when-not (or (empty? items) (= index -1))
(get items index))
group-id (:group-id prev-item)
add-group-id? (and
(not (nil? group-id))
(= (get-in changes [:redo-changes 0 :type]) :mod-obj)
(= (get-in prev-item [:redo-changes 0 :type]) :add-obj)) ;; This is a copy-and-move with mouse+alt
]
(cond-> changes add-group-id? (assoc :group-id group-id))))
(def commit-changes? (ptk/type? ::commit-changes))
(defn update-shapes
@@ -64,7 +81,8 @@
(-> (pcb/empty-changes it page-id)
(pcb/set-save-undo? save-undo?)
(pcb/with-objects objects))
ids)]
ids)
changes (add-group-id changes state)]
(rx/concat
(if (seq (:redo-changes changes))
(let [changes (cond-> changes reg-objects? (pcb/resize-parents ids))]
@@ -147,7 +165,7 @@
(defn commit-changes
[{:keys [redo-changes undo-changes
origin save-undo? file-id]
origin save-undo? file-id group-id]
:or {save-undo? true}}]
(log/debug :msg "commit-changes"
:js/redo-changes redo-changes
@@ -164,7 +182,8 @@
:changes redo-changes
:page-id page-id
:frames frames
:save-undo? save-undo?})
:save-undo? save-undo?
:group-id group-id})
ptk/UpdateEvent
(update [_ state]
@@ -212,5 +231,6 @@
(when (and save-undo? (seq undo-changes))
(let [entry {:undo-changes undo-changes
:redo-changes redo-changes}]
:redo-changes redo-changes
:group-id group-id}]
(rx/of (dwu/append-undo entry)))))))))))

View File

@@ -27,6 +27,7 @@
(defn interrupt? [e] (= e :interrupt))
(declare undo-to-index)
(defn- assure-valid-current-page
[]
@@ -60,13 +61,25 @@
items (:items undo)
index (or (:index undo) (dec (count items)))]
(when-not (or (empty? items) (= index -1))
(let [changes (get-in items [index :undo-changes])]
(rx/of (dwu/materialize-undo changes (dec index))
(dch/commit-changes {:redo-changes changes
:undo-changes []
:save-undo? false
:origin it})
(assure-valid-current-page))))))))))
(let [item (get items index)
changes (:undo-changes item)
group-id (:group-id item)
find-first-group-idx (fn ffgidx[index]
(let [item (get items index)]
(if (= (:group-id item) group-id)
(ffgidx (dec index))
(inc index))))
undo-group-index (when group-id
(find-first-group-idx index))]
(if group-id
(rx/of (undo-to-index (dec undo-group-index)))
(rx/of (dwu/materialize-undo changes (dec index))
(dch/commit-changes {:redo-changes changes
:undo-changes []
:save-undo? false
:origin it})
(assure-valid-current-page)))))))))))
(def redo
(ptk/reify ::redo
@@ -74,17 +87,29 @@
(watch [it state _]
(let [edition (get-in state [:workspace-local :edition])
drawing (get state :workspace-drawing)]
(when-not (or (some? edition) (not-empty drawing))
(when (and (nil? edition) (or (empty drawing) (= :curve (:tool drawing))))
(let [undo (:workspace-undo state)
items (:items undo)
index (or (:index undo) (dec (count items)))]
(when-not (or (empty? items) (= index (dec (count items))))
(let [changes (get-in items [(inc index) :redo-changes])]
(rx/of (dwu/materialize-undo changes (inc index))
(dch/commit-changes {:redo-changes changes
:undo-changes []
:origin it
:save-undo? false}))))))))))
(let [item (get items (inc index))
changes (:redo-changes item)
group-id (:group-id item)
find-last-group-idx (fn flgidx [index]
(let [item (get items index)]
(if (= (:group-id item) group-id)
(flgidx (inc index))
(dec index))))
redo-group-index (when group-id
(find-last-group-idx (inc index)))]
(if group-id
(rx/of (undo-to-index redo-group-index))
(rx/of (dwu/materialize-undo changes (inc index))
(dch/commit-changes {:redo-changes changes
:undo-changes []
:origin it
:save-undo? false})))))))))))
(defn undo-to-index
"Repeat undoing or redoing until dest-index is reached."
@@ -99,7 +124,7 @@
items (:items undo)
index (or (:index undo) (dec (count items)))]
(when (and (some? items)
(<= 0 dest-index (dec (count items))))
(<= -1 dest-index (dec (count items))))
(let [changes (vec (apply concat
(cond
(< dest-index index)

View File

@@ -485,7 +485,10 @@
(gpt/subtract new-pos pt-obj)))))
(defn duplicate-selected [move-delta?]
(defn duplicate-selected
([move-delta?]
(duplicate-selected move-delta? false))
([move-delta? add-group-id?]
(ptk/reify ::duplicate-selected
ptk/WatchEvent
(watch [it state _]
@@ -502,6 +505,8 @@
changes (->> (prepare-duplicate-changes objects page selected delta it)
(duplicate-changes-update-indices objects selected))
changes (cond-> changes add-group-id? (assoc :group-id (uuid/random)))
id-original (first selected)
new-selected (->> changes
@@ -525,7 +530,7 @@
(select-shapes new-selected)
(ptk/data-event :layout/update frames)
(memorize-duplicated id-original id-duplicated)
(dwu/commit-undo-transaction undo-id)))))))))
(dwu/commit-undo-transaction undo-id))))))))))
(defn change-hover-state
[id value]

View File

@@ -77,7 +77,7 @@
([attrs]
(add-shape attrs {}))
([attrs {:keys [no-select?]}]
([attrs {:keys [no-select? no-update-layout?]}]
(us/verify ::shape-attrs attrs)
(ptk/reify ::add-shape
ptk/WatchEvent
@@ -108,7 +108,8 @@
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(ptk/data-event :layout/update [(:parent-id shape)])
(when-not no-update-layout?
(ptk/data-event :layout/update [(:parent-id shape)]))
(when-not no-select?
(dws/select-shapes (d/ordered-set id)))
(dwu/commit-undo-transaction undo-id))
@@ -387,8 +388,9 @@
undo-id (js/Symbol)]
(rx/of
(dwu/start-undo-transaction undo-id)
(add-shape shape)
(add-shape shape {:no-update-layout? true})
(move-shapes-into-frame (:id shape) selected)
(ptk/data-event :layout/update [(:id shape)])
(dwu/commit-undo-transaction undo-id)))))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -453,4 +455,3 @@
(map (fn [[page-id frame-ids]]
(dch/update-shapes frame-ids #(dissoc % :use-for-thumbnail?) {:page-id page-id})))))
(rx/of (dch/update-shapes selected #(update % :use-for-thumbnail? not))))))))

View File

@@ -382,7 +382,7 @@
(if alt?
;; When alt is down we start a duplicate+move
(rx/of (start-move-duplicate initial)
(dws/duplicate-selected false))
(dws/duplicate-selected false true))
;; Otherwise just plain old move
(rx/of (start-move initial selected))))))

View File

@@ -55,10 +55,11 @@
state))
(defn- accumulate-undo-entry
[state {:keys [undo-changes redo-changes]}]
[state {:keys [undo-changes redo-changes group-id]}]
(-> state
(update-in [:workspace-undo :transaction :undo-changes] #(into undo-changes %))
(update-in [:workspace-undo :transaction :redo-changes] #(into % redo-changes))))
(update-in [:workspace-undo :transaction :redo-changes] #(into % redo-changes))
(assoc-in [:workspace-undo :transaction :group-id] group-id)))
(defn append-undo
[entry]

View File

@@ -15,11 +15,13 @@
(def actions (icon-xref :actions))
(def align-bottom (icon-xref :align-bottom))
(def align-content-column-around (icon-xref :align-content-column-around))
(def align-content-column-evenly (icon-xref :align-content-column-evenly))
(def align-content-column-between (icon-xref :align-content-column-between))
(def align-content-column-center (icon-xref :align-content-column-center))
(def align-content-column-end (icon-xref :align-content-column-end))
(def align-content-column-start (icon-xref :align-content-column-start))
(def align-content-row-around (icon-xref :align-content-row-around))
(def align-content-row-evenly (icon-xref :align-content-row-evenly))
(def align-content-row-between (icon-xref :align-content-row-between))
(def align-content-row-center (icon-xref :align-content-row-center))
(def align-content-row-end (icon-xref :align-content-row-end))
@@ -126,11 +128,13 @@
(def infocard (icon-xref :infocard))
(def interaction (icon-xref :interaction))
(def justify-content-column-around (icon-xref :justify-content-column-around))
(def justify-content-column-evenly (icon-xref :justify-content-column-evenly))
(def justify-content-column-between (icon-xref :justify-content-column-between))
(def justify-content-column-center (icon-xref :justify-content-column-center))
(def justify-content-column-end (icon-xref :justify-content-column-end))
(def justify-content-column-start (icon-xref :justify-content-column-start))
(def justify-content-row-around (icon-xref :justify-content-row-around))
(def justify-content-row-evenly (icon-xref :justify-content-row-evenly))
(def justify-content-row-between (icon-xref :justify-content-row-between))
(def justify-content-row-center (icon-xref :justify-content-row-center))
(def justify-content-row-end (icon-xref :justify-content-row-end))

View File

@@ -36,7 +36,7 @@
(next))]
[:div.modal-container.onboarding.onboarding-v2
[:div.modal-left.welcome
[:img {:src "images/onboarding-welcome.jpg" :border "0" :alt (tr "onboarding.welcome.alt")}]]
[:img {:src "images/onboarding-welcome.png" :border "0" :alt (tr "onboarding.welcome.alt")}]]
[:div.modal-right
[:div.release-container [:span.release "Version " (:main @cf/version)]]
[:div.right-content
@@ -71,7 +71,7 @@
(next))]
[:div.modal-container.onboarding.onboarding-v2
[:div.modal-left.welcome
[:img {:src "images/onboarding-people.jpg" :border "0" :alt (tr "onboarding.welcome.alt")}]]
[:img {:src "images/onboarding-people.png" :border "0" :alt (tr "onboarding.welcome.alt")}]]
[:div.modal-right
[:div.release-container [:span.release "Version " (:main @cf/version)]]
[:div.right-content

View File

@@ -196,9 +196,8 @@
:name name
:step 2}))}
(tr "labels.back")]
[:div {:title (tr "onboarding.choice.team-up.invite-members-submit")}
[:& fm/submit-button
{:label (tr "onboarding.choice.team-up.invite-members-submit")}]]]
[:& fm/submit-button
{:label (tr "onboarding.choice.team-up.invite-members-submit")}]]
[:div.skip-action
{:on-click on-skip}
[:div.action (tr "onboarding.choice.team-up.invite-members-skip")]]]]

View File

@@ -8,7 +8,6 @@
(:require
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.users :as du]
[app.main.repo :as rp]
[app.main.store :as st]
@@ -35,12 +34,9 @@
(def routes
[["/auth"
["/login" :auth-login]
(when (contains? @cf/flags :registration)
["/register" :auth-register])
(when (contains? @cf/flags :registration)
["/register/validate" :auth-register-validate])
(when (contains? @cf/flags :registration)
["/register/success" :auth-register-success])
["/register" :auth-register]
["/register/validate" :auth-register-validate]
["/register/success" :auth-register-success]
["/recovery/request" :auth-recovery-request]
["/recovery" :auth-recovery]
["/verify-token" :auth-verify-token]]

View File

@@ -124,11 +124,10 @@
(mf/fnc frame-shape
{::mf/wrap-props false}
[props]
(let [childs (unchecked-get props "childs")]
(let [shape (unchecked-get props "shape")
childs (unchecked-get props "childs")]
[:> frame-container props
[:g.frame-children
[:g.frame-children {:opacity (:opacity shape)}
(for [item childs]
[:& shape-wrapper {:key (dm/str (:id item)) :shape item}]
)]])))
[:& shape-wrapper {:key (dm/str (:id item)) :shape item}])]])))

View File

@@ -21,8 +21,8 @@
:layout-gap-type ;; :simple, :multiple
:layout-gap ;; {:row-gap number , :column-gap number}
:layout-align-items ;; :start :end :center :stretch
:layout-justify-content ;; :start :center :end :space-between :space-around
:layout-align-content ;; :start :center :end :space-between :space-around :stretch (by default)
:layout-justify-content ;; :start :center :end :space-between :space-around :space-evenly
:layout-align-content ;; :start :center :end :space-between :space-around :space-evenly :stretch (by default)
:layout-wrap-type ;; :wrap, :nowrap
:layout-padding-type ;; :simple, :multiple
:layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
@@ -50,12 +50,14 @@
:end i/justify-content-column-end
:center i/justify-content-column-center
:space-around i/justify-content-column-around
:space-evenly i/justify-content-column-evenly
:space-between i/justify-content-column-between)
(case val
:start i/justify-content-row-start
:end i/justify-content-row-end
:center i/justify-content-row-center
:space-around i/justify-content-row-around
:space-evenly i/justify-content-row-evenly
:space-between i/justify-content-row-between))
:align-content (if is-col?
@@ -64,6 +66,7 @@
:end i/align-content-column-end
:center i/align-content-column-center
:space-around i/align-content-column-around
:space-evenly i/align-content-column-evenly
:space-between i/align-content-column-between
:stretch nil)
@@ -72,6 +75,7 @@
:end i/align-content-row-end
:center i/align-content-row-center
:space-around i/align-content-row-around
:space-evenly i/align-content-row-evenly
:space-between i/align-content-row-between
:stretch nil))
@@ -140,16 +144,27 @@
(mf/defc align-content-row
[{:keys [is-col? align-content set-align-content] :as props}]
[:div.align-content-style
(for [align [:start :center :end :space-around :space-between]]
[:button.align-content.tooltip
{:class (dom/classnames :active (= align-content align)
:tooltip-bottom-left (not= align :start)
:tooltip-bottom (= align :start))
:alt (dm/str "Align content " (d/name align))
:on-click #(set-align-content align)
:key (dm/str "align-content" (d/name align))}
(get-layout-flex-icon :align-content align is-col?)])])
[:*
[:div.align-content-style
(for [align [:start :center :end]]
[:button.align-content.tooltip
{:class (dom/classnames :active (= align-content align)
:tooltip-bottom-left (not= align :start)
:tooltip-bottom (= align :start))
:alt (dm/str "Align content " (d/name align))
:on-click #(set-align-content align)
:key (dm/str "align-content" (d/name align))}
(get-layout-flex-icon :align-content align is-col?)])]
[:div.align-content-style
(for [align [:space-between :space-around :space-evenly]]
[:button.align-content.tooltip
{:class (dom/classnames :active (= align-content align)
:tooltip-bottom-left (not= align :start)
:tooltip-bottom (= align :start))
:alt (dm/str "Align content " (d/name align))
:on-click #(set-align-content align)
:key (dm/str "align-content" (d/name align))}
(get-layout-flex-icon :align-content align is-col?)])]])
(mf/defc justify-content-row
[{:keys [is-col? justify-content set-justify] :as props}]
@@ -165,7 +180,7 @@
:key (dm/str "justify-content" (d/name justify))}
(get-layout-flex-icon :justify-content justify is-col?)])]
[:div.justify-content-style
(for [justify [:space-around :space-between]]
(for [justify [:space-between :space-around :space-evenly]]
[:button.justify.tooltip
{:class (dom/classnames :active (= justify-content justify)
:tooltip-bottom-left (not= justify :space-around)
@@ -399,7 +414,7 @@
(when (= :wrap wrap-type)
[:div.layout-row
[:div.align-content.row-title "Content"]
[:div.btn-wrapper
[:div.btn-wrapper.align-content
[:& align-content-row {:is-col? is-col?
:align-content align-content
:set-align-content set-align-content}]]])

View File

@@ -340,8 +340,7 @@
(when-not (empty? measure-ids)
[:& measures-menu {:type type :all-types all-types :ids measure-ids :values measure-values :shape shapes}])
(when has-layout-container?
[:& layout-container-menu {:type type :ids layout-container-ids :values layout-container-values :multiple true}])
[:& layout-container-menu {:type type :ids layout-container-ids :values layout-container-values :multiple true}]
(when (or is-layout-child? has-layout-container?)
[:& layout-item-menu

View File

@@ -439,8 +439,6 @@
[:& presence/active-cursors
{:page-id page-id}])
[:& widgets/viewport-actions]
[:& scroll-bars/viewport-scrollbars
{:objects base-objects
:zoom zoom

View File

@@ -69,10 +69,11 @@
on-pointer-down
(mf/use-callback
(fn [event]
(dom/capture-pointer event)
(mf/set-ref-val! dragging-ref true)
(mf/set-ref-val! start-ref (dom/get-client-position event))
(mf/set-ref-val! start-pos-ref (get @ms/mouse-position axis))))
(when (= 0 (.-button event))
(dom/capture-pointer event)
(mf/set-ref-val! dragging-ref true)
(mf/set-ref-val! start-ref (dom/get-client-position event))
(mf/set-ref-val! start-pos-ref (get @ms/mouse-position axis)))))
on-pointer-up
(mf/use-callback

View File

@@ -56,7 +56,8 @@
drawing-obj (:object drawing)
shape (or drawing-obj (-> selected first))]
(when (or (and (= (count selected) 1) (= (:id shape) edition) (not= :text (:type shape)))
(and (some? drawing-obj) (= :path (:type drawing-obj))))
(and (some? drawing-obj) (= :path (:type drawing-obj))
(not= :curve (:tool drawing))))
[:div.viewport-actions
[:& path-actions {:shape shape}]])))

View File

@@ -1 +1 @@
1.17.1
1.17.2