Compare commits

..

60 Commits

Author SHA1 Message Date
alonso.torres
4703f6d5c7 🐛 Fixes problem with multiple selection 2021-02-04 15:29:19 +01:00
alonso.torres
8d2797f8a1 🐛 Fixes problem with multiple selection 2021-02-04 15:08:47 +01:00
alonso.torres
6cdde84445 🐛 Color palette text-wrap and showing when open color palette 2021-02-04 14:54:40 +01:00
Andrey Antukh
afa35379b2 🐛 Fix onboarding after logging with token. 2021-02-04 14:47:14 +01:00
alonso.torres
1099e08b7d 🐛 Fixed small visual problem for images in handoff 2021-02-04 14:31:49 +01:00
alonso.torres
89cb20ada7 🐛 Fixes Ctrl+a for viewer 2021-02-04 14:31:49 +01:00
alonso.torres
32b0fd7b36 🐛 Fixes issue with multiple selection and shadows 2021-02-04 14:31:49 +01:00
Andrey Antukh
04670bb5f2 Reset some message timeout defaults. 2021-02-04 14:29:39 +01:00
Andrey Antukh
8566fe4ac1 Show close icon on messages by default. 2021-02-04 14:29:39 +01:00
Andrey Antukh
e607e8315c Auto login after email verify. 2021-02-04 14:29:39 +01:00
Andrés Moya
a9b7cf61a5 🐛 Fix display of custom shape strokes 2021-02-04 14:22:39 +01:00
elhombretecla
7c7bda669c Add better layout for register success page. 2021-02-04 13:36:47 +01:00
Andrey Antukh
0c82c6f2f5 🐛 Fix recursion error on not-found. 2021-02-04 13:34:38 +01:00
alonso.torres
b7cbe49cb2 🐛 Fixes image upload position when uploading from left sidebar 2021-02-04 12:43:50 +01:00
alonso.torres
7378089f4a 🐛 Fixes problems with multiple values in fill and stroke 2021-02-04 12:39:41 +01:00
Andrey Antukh
62b6b12066 Merge branch 'violoncelloCH-fix/js-var-prefix' into develop 2021-02-04 12:16:24 +01:00
Jonas Sulzer
39fdff9052 🐛 Fix js variable prefix app->penpot on config doc.
Signed-off-by: Jonas Sulzer <jonas@violoncello.ch>
2021-02-04 12:15:40 +01:00
alonso.torres
32c0913f00 🐛 Fixes problem with pixel-level movement 2021-02-04 11:54:45 +01:00
Andrey Antukh
7eb90d62b0 🐛 Fix typos on translation strings. 2021-02-04 11:48:47 +01:00
Andrey Antukh
ec2683417f 🐛 Fix image upload internal error. 2021-02-04 11:48:47 +01:00
Andrey Antukh
cb23c8b093 Increase default flash message timeout. 2021-02-04 11:48:47 +01:00
Andrey Antukh
687f7ddf64 Don't send emails on recovery password on not verified profile.
And show proper message to the user saying that the profile
need to be verfied before proceed.
2021-02-04 11:48:47 +01:00
Andrey Antukh
992a8e9aef Improve posible race condition handling on user registration. 2021-02-04 11:48:47 +01:00
Andrey Antukh
6e08c6bc35 📎 Fix linter issues. 2021-02-04 11:48:47 +01:00
Andrey Antukh
b71d05935a Expose user-agent and frontend-version on error report. 2021-02-04 11:48:47 +01:00
Andrey Antukh
c14dbc19f8 🎉 Add register confirmation page. 2021-02-04 11:48:47 +01:00
Andrey Antukh
1eff1c94c4 🔥 Remove goodbye page (useless). 2021-02-04 11:48:47 +01:00
Andrey Antukh
53be7feee1 🎉 Add 3rd party auth buttons to register page. 2021-02-04 11:48:47 +01:00
Andrey Antukh
e182cc4028 Add default headers to frontend http client. 2021-02-04 11:48:47 +01:00
Andrey Antukh
80309cbff3 Improve error reporting of tasks. 2021-02-04 11:48:47 +01:00
alonso.torres
816db29f9c Refactor of shortcuts and adaptations for macosx 2021-02-04 11:34:00 +01:00
Andrés Moya
526e0afc70 💄 Fix args and docstrings 2021-02-04 11:24:19 +01:00
Andrés Moya
77973af49f Remember assets libraries open in local session 2021-02-04 11:24:19 +01:00
Andrés Moya
dc5cff645a Remember color picker library in local session 2021-02-04 11:24:19 +01:00
alonso.torres
0ea8e9e750 🐛 Fixes issue with lock proportions 2021-02-04 11:18:59 +01:00
alonso.torres
69b4968578 Change to add when selected shape 2021-02-04 11:17:40 +01:00
Andrey Antukh
b7e266e350 Revert "🐛 Fixes problems with multiple values in fill and stroke"
This reverts commit 8fd8bc4537.
2021-02-03 17:27:08 +01:00
alonso.torres
b056cc35e4 🐛 Fixes problem when moving parent to children group 2021-02-03 15:36:28 +01:00
alonso.torres
d66452423f 🐛 Fixes recursion problems when creating component 2021-02-03 15:36:28 +01:00
Andrey Antukh
d85537fa7b Merge branch 'main' into develop 2021-02-03 15:18:35 +01:00
Andrey Antukh
fc11fb6e3d Reduce system resources for frontend build. 2021-02-03 15:18:16 +01:00
alonso.torres
cbdfb4349b 🐛 Fixed problem when editing paths 2021-02-03 13:30:59 +01:00
Abtin
19ed0b70c2 Update 00-Getting-Started.md
fix link to configuration guide
2021-02-03 13:29:55 +01:00
Andrey Antukh
3092747b5f Merge branch 'main' into develop 2021-02-03 13:27:03 +01:00
Andrey Antukh
0adfc2ddab Update manage.sh
Make the bundle use LZ4 compression by default.
2021-02-03 13:25:58 +01:00
alonso.torres
8fd8bc4537 🐛 Fixes problems with multiple values in fill and stroke 2021-02-03 12:30:58 +01:00
Andrey Antukh
e7d6a54907 🐛 Fix static file handling on docker images. 2021-02-03 11:30:10 +01:00
Hirunatan
e3c273c84b Merge pull request #532 from penpot/hotfix/texts
🐛 Fixes problems with paste empty text
2021-02-02 15:43:19 +01:00
alonso.torres
8aedbd1418 🐛 Fixes problems with paste empty text 2021-02-02 15:36:49 +01:00
Andrés Moya
e713c30785 🐛 Prevent browser dragging of images in some cases 2021-02-02 15:01:36 +01:00
Andrey Antukh
74a168d87e 🐛 Use proper config value. 2021-02-02 14:39:44 +01:00
Andrey Antukh
ca63ff621a 🐛 Fix email from handling. 2021-02-02 14:39:44 +01:00
Andrés Moya
d120af2c81 🐛 Fix workspace breadcrumb 2021-02-02 13:03:36 +01:00
alonso.torres
95ab5b57b7 🐛 Removes problems with texts 2021-02-02 13:03:21 +01:00
alonso.torres
2e7f90f3cc Adds commands to load data into user 2021-02-02 13:03:21 +01:00
Andrés Moya
8403352af8 🐛 Fix error in fixtures loading 2021-02-02 10:40:13 +01:00
Andrey Antukh
526b6e1f03 🐛 Unexpected exception on handling of invitation user registration. 2021-02-02 09:30:43 +01:00
Andrey Antukh
f2fd976934 📎 Replace develop with latest in default compose file. 2021-02-01 22:37:28 +01:00
Andrey Antukh
8b9371d7e1 🎉 Add the ability to disable mattermost webhook on runtime. 2021-02-01 22:37:28 +01:00
Andrés Moya
948a4038c6 Update social cards meta tags 2021-02-01 18:19:07 +01:00
89 changed files with 1698 additions and 962 deletions

View File

@@ -67,39 +67,61 @@
<div class="table-val">{{profile-id}}</div>
</div>
{% endif %}
{% if user-agent %}
<div class="table-row">
<div class="table-key">VERS: </div>
<div class="table-key">UAGENT: </div>
<div class="table-val">{{user-agent}}</div>
</div>
{% endif %}
{% if frontend-version %}
<div class="table-row">
<div class="table-key">FVERS: </div>
<div class="table-val">{{frontend-version}}</div>
</div>
{% endif %}
<div class="table-row">
<div class="table-key">BVERS: </div>
<div class="table-val">{{version}}</div>
</div>
<div class="table-row">
<div class="table-key">HOST: </div>
<div class="table-val">{{host}}</div>
</div>
{% if type %}
<div class="table-row">
<div class="table-key">TYPE: </div>
<div class="table-val">{{type}}</div>
</div>
{% endif %}
{% if code %}
<div class="table-row">
<div class="table-key">CODE: </div>
<div class="table-val">{{code}}</div>
</div>
{% endif %}
<div class="table-row">
<div class="table-key">CLASS: </div>
<div class="table-val">{{class}}</div>
</div>
<div class="table-row">
<div class="table-key">HINT: </div>
<div class="table-val">{{hint}}</div>
</div>
{% if method %}
<div class="table-row">
<div class="table-key">PATH: </div>
<div class="table-val">{{method|upper}} {{path}}</div>
</div>
{% endif %}
{% if params %}
<div class="table-row multiline">
@@ -128,7 +150,6 @@
</div>
{% endif %}
<div class="table-row multiline">
<div class="table-key">TRACE:</div>
<div class="table-val">

View File

@@ -75,7 +75,7 @@
(let [rng (java.util.Random. 1)]
(letfn [(create-profile [conn index]
(let [id (mk-uuid "profile" index)
_ (log/info "create profile" id)
_ (log/info "create profile" index id)
prof (register-profile conn
{:id id
@@ -98,10 +98,9 @@
(create-team [conn index]
(let [id (mk-uuid "team" index)
name (str "Team" index)]
(log/info "create team" id)
(log/info "create team" index id)
(db/insert! conn :team {:id id
:name name
:photo ""})
:name name})
id))
(create-teams [conn]
@@ -113,7 +112,7 @@
(let [id (mk-uuid "file" project-id index)
name (str "file" index)
data (cp/make-file-data id)]
(log/info "create file" id)
(log/info "create file" index id)
(db/insert! conn :file
{:id id
:data (blob/encode data)
@@ -135,7 +134,7 @@
(create-project [conn team-id owner-id index]
(let [id (mk-uuid "project" team-id index)
name (str "project " index)]
(log/info "create project" id)
(log/info "create project" index id)
(db/insert! conn :project
{:id id
:team-id team-id
@@ -188,7 +187,7 @@
project-id (:default-project-id owner)
data (cp/make-file-data id)]
(log/info "create draft file" id)
(log/info "create draft file" index id)
(db/insert! conn :file
{:id id
:data (blob/encode data)

View File

@@ -44,8 +44,8 @@
:rlimits-image 2
:smtp-enabled false
:smtp-default-reply-to "no-reply@example.com"
:smtp-default-from "no-reply@example.com"
:smtp-default-reply-to "Penpot <no-reply@example.com>"
:smtp-default-from "Penpot <no-reply@example.com>"
:allow-demo-users true
:registration-enabled true
@@ -92,8 +92,8 @@
(s/def ::error-report-webhook ::us/string)
(s/def ::smtp-enabled ::us/boolean)
(s/def ::smtp-default-reply-to ::us/email)
(s/def ::smtp-default-from ::us/email)
(s/def ::smtp-default-reply-to ::us/string)
(s/def ::smtp-default-from ::us/string)
(s/def ::smtp-host ::us/string)
(s/def ::smtp-port ::us/integer)
(s/def ::smtp-username (s/nilable ::us/string))

View File

@@ -76,38 +76,42 @@
(tr/decode-stream input))))
(defn create-profile-initial-data
[conn profile]
(when-let [initial-data-path (:initial-data-file cfg/config)]
(when-let [{:keys [file file-library-rel file-media-object]} (read-initial-data initial-data-path)]
(let [sample-project-name (:initial-data-project-name cfg/config "Penpot Onboarding")
proj (projects/create-project conn {:profile-id (:id profile)
:team-id (:default-team-id profile)
:name sample-project-name})
([conn profile]
(when-let [initial-data-path (:initial-data-file cfg/config)]
(create-profile-initial-data conn initial-data-path profile)))
map-ids {}
([conn file profile]
(when-let [{:keys [file file-library-rel file-media-object]} (read-initial-data file)]
(let [sample-project-name (:initial-data-project-name cfg/config "Penpot Onboarding")
;; Create new ID's and change the references
[map-ids file] (change-ids map-ids file #{:id})
[map-ids file-library-rel] (change-ids map-ids file-library-rel #{:file-id :library-file-id})
[_ file-media-object] (change-ids map-ids file-media-object #{:id :file-id :media-id :thumbnail-id})
proj (projects/create-project conn {:profile-id (:id profile)
:team-id (:default-team-id profile)
:name sample-project-name})
file (map #(assoc % :project-id (:id proj)) file)
file-profile-rel (map #(array-map :file-id (:id %)
:profile-id (:id profile)
:is-owner true
:is-admin true
:can-edit true)
file)]
map-ids {}
(projects/create-project-profile conn {:project-id (:id proj)
:profile-id (:id profile)})
;; Create new ID's and change the references
[map-ids file] (change-ids map-ids file #{:id})
[map-ids file-library-rel] (change-ids map-ids file-library-rel #{:file-id :library-file-id})
[_ file-media-object] (change-ids map-ids file-media-object #{:id :file-id :media-id :thumbnail-id})
(projects/create-team-project-profile conn {:team-id (:default-team-id profile)
:project-id (:id proj)
:profile-id (:id profile)})
file (map #(assoc % :project-id (:id proj)) file)
file-profile-rel (map #(array-map :file-id (:id %)
:profile-id (:id profile)
:is-owner true
:is-admin true
:can-edit true)
file)]
;; Re-insert into the database
(db/insert-multi! conn :file file)
(db/insert-multi! conn :file-profile-rel file-profile-rel)
(db/insert-multi! conn :file-library-rel file-library-rel)
(db/insert-multi! conn :file-media-object file-media-object)))))
(projects/create-project-profile conn {:project-id (:id proj)
:profile-id (:id profile)})
(projects/create-team-project-profile conn {:team-id (:default-team-id profile)
:project-id (:id proj)
:profile-id (:id profile)})
;; Re-insert into the database
(db/insert-multi! conn :file file)
(db/insert-multi! conn :file-profile-rel file-profile-rel)
(db/insert-multi! conn :file-library-rel file-library-rel)
(db/insert-multi! conn :file-media-object file-media-object)))))

View File

@@ -36,6 +36,7 @@
(declare handle-event)
(defonce enabled-mattermost (atom true))
(defonce queue (a/chan (a/sliding-buffer 64)))
(defonce queue-fn (fn [event] (a/>!! queue event)))
@@ -117,7 +118,7 @@
[cfg event]
(try
(let [cdata (get-context-data event)]
(when (:uri cfg)
(when (and (:uri cfg) @enabled-mattermost)
(send-mattermost-notification! cfg cdata))
(persist-on-database! cfg cdata))
(catch Exception e

View File

@@ -16,6 +16,7 @@
[app.http.errors :as errors]
[app.http.middleware :as middleware]
[app.metrics :as mtx]
[app.util.log4j :refer [update-thread-context!]]
[clojure.spec.alpha :as s]
[clojure.tools.logging :as log]
[integrant.core :as ig]
@@ -103,7 +104,7 @@
(catch Throwable e
(try
(let [cdata (errors/get-error-context request e)]
(errors/update-thread-context! cdata)
(update-thread-context! cdata)
(log/errorf e "Unhandled exception: %s (id: %s)" (ex-message e) (str (:id cdata)))
{:status 500
:body "internal server error"})

View File

@@ -12,25 +12,10 @@
(:require
[app.common.uuid :as uuid]
[app.config :as cfg]
[clojure.pprint :refer [pprint]]
[app.util.log4j :refer [update-thread-context!]]
[clojure.tools.logging :as log]
[cuerdas.core :as str]
[expound.alpha :as expound])
(:import
org.apache.logging.log4j.ThreadContext))
(defn update-thread-context!
[data]
(run! (fn [[key val]]
(ThreadContext/put
(name key)
(cond
(coll? val)
(binding [clojure.pprint/*print-right-margin* 120]
(with-out-str (pprint val)))
(instance? clojure.lang.Named val) (name val)
:else (str val))))
data))
[expound.alpha :as expound]))
(defn- explain-error
[error]
@@ -48,10 +33,12 @@
:version (:full cfg/version)
:host (:public-uri cfg/config)
:class (.getCanonicalName ^java.lang.Class (class error))
:hint (ex-message error)}
:hint (ex-message error)
:data edata}
(when (map? edata)
edata)
(let [headers (:headers request)]
{:user-agent (get headers "user-agent")
:frontend-version (get headers "x-frontend-version" "unknown")})
(when (and (map? edata) (:data edata))
{:explain (explain-error edata)}))))

View File

@@ -10,7 +10,7 @@
(ns app.http.session
(:require
[app.db :as db]
[app.http.errors :refer [update-thread-context!]]
[app.util.log4j :refer [update-thread-context!]]
[buddy.core.codecs :as bc]
[buddy.core.nonce :as bn]
[clojure.spec.alpha :as s]

View File

@@ -94,7 +94,7 @@
:app.http.assets/handlers
{:metrics (ig/ref :app.metrics/metrics)
:assets-path (:assets-path cfg/config)
:assets-path (:assets-path config)
:storage (ig/ref :app.storage/storage)
:cache-max-age (dt/duration {:hours 24})
:signature-max-age (dt/duration {:hours 24 :minutes 5})}
@@ -148,11 +148,11 @@
;; RLimit definition for password hashing
:app.rlimits/password
(:rlimits-password cfg/config)
(:rlimits-password config)
;; RLimit definition for image processing
:app.rlimits/image
(:rlimits-image cfg/config)
(:rlimits-image config)
;; A collection of rlimits as hash-map.
:app.rlimits/all
@@ -211,10 +211,10 @@
:cron #app/cron "0 0 0 */1 * ?" ;; daily
:fn (ig/ref :app.tasks.tasks-gc/handler)}
(when (:telemetry-enabled cfg/config)
(when (:telemetry-enabled config)
{:id "telemetry"
:cron #app/cron "0 0 */6 * * ?" ;; every 6h
:uri (:telemetry-uri cfg/config)
:uri (:telemetry-uri config)
:fn (ig/ref :app.tasks.telemetry/handler)})]}
:app.tasks/all
@@ -265,18 +265,18 @@
:app.tasks.telemetry/handler
{:pool (ig/ref :app.db/pool)
:version (:full cfg/version)
:uri (:telemetry-uri cfg/config)
:uri (:telemetry-uri config)
:sprops (ig/ref :app.sprops/props)}
:app.srepl/server
{:port (:srepl-port cfg/config)
:host (:srepl-host cfg/config)}
{:port (:srepl-port config)
:host (:srepl-host config)}
:app.sprops/props
{:pool (ig/ref :app.db/pool)}
:app.error-reporter/reporter
{:uri (:error-report-webhook cfg/config)
{:uri (:error-report-webhook config)
:pool (ig/ref :app.db/pool)
:executor (ig/ref :app.worker/executor)}
@@ -286,18 +286,18 @@
:app.storage/storage
{:pool (ig/ref :app.db/pool)
:executor (ig/ref :app.worker/executor)
:backend (:storage-backend cfg/config :fs)
:backend (:storage-backend config :fs)
:backends {:s3 (ig/ref [::main :app.storage.s3/backend])
:db (ig/ref [::main :app.storage.db/backend])
:fs (ig/ref [::main :app.storage.fs/backend])
:tmp (ig/ref [::tmp :app.storage.fs/backend])}}
[::main :app.storage.s3/backend]
{:region (:storage-s3-region cfg/config)
:bucket (:storage-s3-bucket cfg/config)}
{:region (:storage-s3-region config)
:bucket (:storage-s3-bucket config)}
[::main :app.storage.fs/backend]
{:directory (:storage-fs-directory cfg/config)}
{:directory (:storage-fs-directory config)}
[::tmp :app.storage.fs/backend]
{:directory "/tmp/penpot"}
@@ -305,7 +305,7 @@
[::main :app.storage.db/backend]
{:pool (ig/ref :app.db/pool)}}
(when (:telemetry-server-enabled cfg/config)
(when (:telemetry-server-enabled config)
{:app.telemetry/handler
{:pool (ig/ref :app.db/pool)
:executor (ig/ref :app.worker/executor)}

View File

@@ -78,8 +78,10 @@
;; profile data.
(let [claims (tokens :verify {:token token :iss :team-invitation})
claims (assoc claims :member-id (:id profile))
params (assoc params :profile-id (:id profile))]
(process-token conn params claims)
params (assoc params :profile-id (:id profile))
cfg (assoc cfg :conn conn)]
(process-token cfg params claims)
;; Automatically mark the created profile as active because
;; we already have the verification of email with the
@@ -168,15 +170,24 @@
active? (if demo? true is-active)
props (db/tjson (or props {}))
password (derive-password password)]
(-> (db/insert! conn :profile
{:id id
:fullname fullname
:email (str/lower email)
:password password
:props props
:is-active active?
:is-demo demo?})
(update :props db/decode-transit-pgobject))))
(try
(-> (db/insert! conn :profile
{:id id
:fullname fullname
:email (str/lower email)
:password password
:props props
:is-active active?
:is-demo demo?})
(update :props db/decode-transit-pgobject))
(catch org.postgresql.util.PSQLException e
(let [state (.getSQLState e)]
(if (not= state "23505")
(throw e)
(ex/raise :type :validation
:code :email-already-exists
:cause e)))))))
(defn- create-profile-relations
[conn profile]
@@ -392,11 +403,14 @@
:name (:fullname profile)}))]
(db/with-atomic [conn pool]
(some->> email
(profile/retrieve-profile-data-by-email conn)
(create-recovery-token)
(send-email-notification conn))
nil)))
(when-let [profile (profile/retrieve-profile-data-by-email conn email)]
(when-not (:is-active profile)
(ex/raise :type :validation
:code :profile-not-verified
:hint "the user need to validate profile before recover password"))
(->> profile
(create-recovery-token)
(send-email-notification conn))))))
;; --- Mutation: Recover Profile

View File

@@ -44,20 +44,29 @@
claims)
(defmethod process-token :verify-email
[{:keys [conn] :as cfg} _params {:keys [profile-id] :as claims}]
(let [profile (db/get-by-id conn :profile profile-id {:for-update true})]
(when (:is-active profile)
(ex/raise :type :validation
:code :email-already-validated))
(when (not= (:email profile)
(:email claims))
(ex/raise :type :validation
:code :invalid-token))
[{:keys [conn session] :as cfg} _params {:keys [profile-id] :as claims}]
(let [profile (profile/retrieve-profile conn profile-id)
claims (assoc claims :profile profile)]
(when-not (:is-active profile)
(when (not= (:email profile)
(:email claims))
(ex/raise :type :validation
:code :invalid-token))
(db/update! conn :profile
{:is-active true}
{:id (:id profile)}))
(with-meta claims
{:transform-response
(fn [request response]
(let [uagent (get-in request [:headers "user-agent"])
id (session/create! session {:profile-id profile-id
:user-agent uagent})]
(assoc response
:cookies (session/cookies session {:value id}))))})))
(db/update! conn :profile
{:is-active true}
{:id (:id profile)})
claims))
(defmethod process-token :auth
[{:keys [conn] :as cfg} _params {:keys [profile-id] :as claims}]

View File

@@ -4,9 +4,11 @@
(:require
[app.common.pages :as cp]
[app.common.pages.migrations :as pmg]
[app.config :as cfg]
[app.db :as db]
[app.db.profile-initial-data :as pid]
[app.main :refer [system]]
[app.rpc.queries.profile :as prof]
[app.srepl.dev :as dev]
[app.util.blob :as blob]
[clojure.pprint :refer [pprint]]))
@@ -58,3 +60,15 @@
([system project-id path]
(db/with-atomic [conn (:app.db/pool system)]
(pid/create-initial-data-dump conn project-id path))))
(defn load-data-into-user
([system user-email]
(if-let [file (:initial-data-file cfg/config)]
(load-data-into-user system file user-email)
(prn "Data file not found in configuration")))
([system file user-email]
(db/with-atomic [conn (:app.db/pool system)]
(let [profile (prof/retrieve-profile-data-by-email conn user-email)
profile (merge profile (prof/retrieve-additional-data conn (:id profile)))]
(pid/create-profile-initial-data conn file profile)))))

View File

@@ -29,24 +29,11 @@
;; Email Building
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn build-address
[v charset]
(try
(cond
(string? v)
(InternetAddress. v nil charset)
(defn- parse-address
[v]
(InternetAddress/parse ^String v))
(map? v)
(InternetAddress. (:addr v)
(:name v)
(:charset v charset))
:else
(throw (ex-info "Invalid address" {:data v})))
(catch Exception e
(throw (ex-info "Invalid address" {:data v} e)))))
(defn- resolve-recipient-type
(defn- ^Message$RecipientType resolve-recipient-type
[type]
(case type
:to Message$RecipientType/TO
@@ -54,33 +41,33 @@
:bcc Message$RecipientType/BCC))
(defn- assign-recipient
[^MimeMessage mmsg type address charset]
[^MimeMessage mmsg type address]
(if (sequential? address)
(reduce #(assign-recipient %1 type %2 charset) mmsg address)
(let [address (build-address address charset)
(reduce #(assign-recipient %1 type %2) mmsg address)
(let [address (parse-address address)
type (resolve-recipient-type type)]
(.addRecipient mmsg type address)
(.addRecipients mmsg type address)
mmsg)))
(defn- assign-recipients
[mmsg {:keys [to cc bcc charset] :or {charset "utf-8"} :as params}]
[mmsg {:keys [to cc bcc] :as params}]
(cond-> mmsg
(some? to) (assign-recipient :to to charset)
(some? cc) (assign-recipient :cc cc charset)
(some? bcc) (assign-recipient :bcc bcc charset)))
(some? to) (assign-recipient :to to)
(some? cc) (assign-recipient :cc cc)
(some? bcc) (assign-recipient :bcc bcc)))
(defn- assign-from
[mmsg {:keys [from charset] :or {charset "utf-8"}}]
(when from
(let [from (build-address from charset)]
(.setFrom ^MimeMessage mmsg ^InternetAddress from))))
[mmsg {:keys [default-from]} {:keys [from] :as props}]
(let [from (or from default-from)]
(when from
(let [from (parse-address from)]
(.addFrom ^MimeMessage mmsg from)))))
(defn- assign-reply-to
[mmsg {:keys [defaut-reply-to]} {:keys [reply-to charset] :or {charset "utf-8"}}]
(let [reply-to (or reply-to defaut-reply-to)]
[mmsg {:keys [default-reply-to] :as cfg} {:keys [reply-to] :as params}]
(let [reply-to (or reply-to default-reply-to)]
(when reply-to
(let [reply-to (build-address reply-to charset)
reply-to (into-array InternetAddress [reply-to])]
(let [reply-to (parse-address reply-to)]
(.setReplyTo ^MimeMessage mmsg reply-to)))))
(defn- assign-subject
@@ -136,7 +123,7 @@
[cfg session params]
(let [mmsg (MimeMessage. ^Session session)]
(assign-recipients mmsg params)
(assign-from mmsg params)
(assign-from mmsg cfg params)
(assign-reply-to mmsg cfg params)
(assign-subject mmsg params)
(assign-extra-headers mmsg params)
@@ -156,12 +143,12 @@
(Properties.)
{"mail.user" username
"mail.host" host
"mail.from" default-from
"mail.smtp.auth" (boolean username)
"mail.smtp.starttls.enable" tls
"mail.smtp.starttls.required" tls
"mail.smtp.host" host
"mail.smtp.port" port
"mail.smtp.from" default-from
"mail.smtp.user" username
"mail.smtp.timeout" timeout
"mail.smtp.connectiontimeout" timeout}))
@@ -183,7 +170,9 @@
(defn send!
[cfg message]
(let [^MimeMessage message (smtp-message cfg message)]
(Transport/send message (:username cfg) (:password cfg))
(Transport/send message
(:username cfg)
(:password cfg))
nil))
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@@ -0,0 +1,27 @@
;; 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/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2021 UXBOX Labs SL
(ns app.util.log4j
(:require
[clojure.pprint :refer [pprint]])
(:import
org.apache.logging.log4j.ThreadContext))
(defn update-thread-context!
[data]
(run! (fn [[key val]]
(ThreadContext/put
(name key)
(cond
(coll? val)
(binding [clojure.pprint/*print-right-margin* 120]
(with-out-str (pprint val)))
(instance? clojure.lang.Named val) (name val)
:else (str val))))
data))

View File

@@ -12,11 +12,12 @@
(:require
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.config :as cfg]
[app.db :as db]
[app.util.async :as aa]
[app.util.log4j :refer [update-thread-context!]]
[app.util.time :as dt]
[clojure.core.async :as a]
[clojure.pprint :refer [pprint]]
[clojure.spec.alpha :as s]
[clojure.tools.logging :as log]
[integrant.core :as ig]
@@ -205,6 +206,17 @@
(log/warn "no task handler found for" (pr-str name)))
{:status :completed :task item}))
(defn get-error-context
[error item]
(let [edata (ex-data error)]
{:id (uuid/next)
:version (:full cfg/version)
:host (:public-uri cfg/config)
:class (.getCanonicalName ^java.lang.Class (class error))
:hint (ex-message error)
:data edata
:params item}))
(defn- handle-exception
[error item]
(let [edata (ex-data error)]
@@ -218,14 +230,9 @@
(= ::noop (:strategy edata))
(assoc :inc-by 0))
(do
(log/errorf error
(str "Unhandled exception.\n"
"=> task: " (:name item) "\n"
"=> retry: " (:retry-num item) "\n"
"=> props: \n"
(with-out-str
(pprint (:props item)))))
(let [cdata (get-error-context error item)]
(update-thread-context! cdata)
(log/errorf error "Unhandled exception on task (id: %s)" (:id cdata))
(if (>= (:retry-num item) (:max-retries item))
{:status :failed :task item :error error}
{:status :retry :task item :error error})))))

View File

@@ -60,6 +60,7 @@
(let [new-val (get curr attr ::undefined)
value (cond
(= new-val ::undefined) value
(= new-val :multiple) :multiple
(= value ::undefined) (sel new-val)
(eqfn new-val value) value
:else :multiple)]

View File

@@ -328,6 +328,11 @@
nil
(apply f args))))
(defn nilv
"Returns a default value if the given value is nil"
[v default]
(if (some? v) v default))
(defn check-num
"Function that checks if a number is nil or nan. Will return 0 when not
valid and the number otherwise."

View File

@@ -61,6 +61,7 @@
(d/export helpers/set-touched-group)
(d/export helpers/touched-group?)
(d/export helpers/get-base-shape)
(d/export helpers/is-parent?)
;; Process changes
(d/export changes/process-changes)

View File

@@ -376,3 +376,25 @@
;; The first id will be the top-most
(get objects (first sorted-ids))))
(defn is-parent?
"Check if `parent-candidate` is parent of `shape-id`"
[objects shape-id parent-candidate]
(loop [current (get objects parent-candidate)
done #{}
pending (:shapes current)]
(cond
(contains? done (:id current))
(recur (get objects (first pending))
done
(rest pending))
(empty? pending) false
(and current (contains? (set (:shapes current)) shape-id)) true
:else
(recur (get objects (first pending))
(conj done (:id current))
(concat (rest pending) (:shapes current))))))

View File

@@ -10,7 +10,7 @@ volumes:
services:
penpot-frontend:
image: "penpotapp/frontend:develop"
image: "penpotapp/frontend:latest"
ports:
- 9001:80
@@ -24,7 +24,7 @@ services:
- penpot
penpot-backend:
image: "penpotapp/backend:develop"
image: "penpotapp/backend:latest"
volumes:
- penpot_assets_data:/opt/data
@@ -79,7 +79,7 @@ services:
- penpot
penpot-exporter:
image: "penpotapp/exporter:develop"
image: "penpotapp/exporter:latest"
environment:
# Don't touch it; this uses internal docker network to
# communicate with the frontend.

View File

@@ -107,7 +107,7 @@ http {
}
location /assets {
proxy_pass http://127.0.0.1:6060/assets;
proxy_pass http://penpot-backend:6060/assets;
recursive_error_pages on;
proxy_intercept_errors on;
error_page 301 302 307 = @handle_redirect;
@@ -115,7 +115,7 @@ http {
location /internal/assets {
internal;
alias /var/www/assets;
alias /opt/data/assets;
add_header x-internal-redirect "$upstream_http_x_accel_redirect";
}
}

View File

@@ -59,4 +59,4 @@ docker-compose -p penpot -f docker-compose.yaml up
The docker compose file contains the essential configuration for
getting the application running, and many essential configurations
already explained in comments. All other configuration options are
explained in [management guide](./05-Management-Guide.md).
explained in [configuration guide](./05-Configuration-Guide.md).

View File

@@ -200,10 +200,10 @@ If any of the following variables are defined, they will enable the
corresponding auth button in the login page
```js
var appGoogleClientID = "<google-client-id-here>";
var appGitlabClientID = "<gitlab-client-id-here>";
var appGithubClientID = "<github-client-id-here>";
var appLoginWithLDAP = <true|false>;
var penpotGoogleClientID = "<google-client-id-here>";
var penpotGitlabClientID = "<gitlab-client-id-here>";
var penpotGithubClientID = "<github-client-id-here>";
var penpotLoginWithLDAP = <true|false>;
```
**NOTE:** The configuration should match the backend configuration for
@@ -216,8 +216,8 @@ It is possible to display a warning message on a demo environment and
disable/enable demo users:
```js
var appDemoWarning = <true|false>;
var appAllowDemoUsers = <true|false>;
var penpotDemoWarning = <true|false>;
var penpotAllowDemoUsers = <true|false>;
```
**NOTE:** The configuration for demo users should match the backend

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="279.383" height="369.004" viewBox="0 0 261.922 345.941">
<path d="M68.319 0L31.94 51.236v28.962L.286 95.255 0 95.12v188.953l123.419 58.308 7.542 3.56 7.542-3.56 123.419-58.308V95.12l-.233.11-31.666-15.062V51.236l-1.106-1.558L193.645 0l-36.38 51.236v.052l-26.47-37.283L104.527 51l-.94-1.322L68.318 0zm6.436 29.762l14.07 19.815H47.81l13.908-19.583 13.036-.232zm125.325 0l14.071 19.815H173.14l13.904-19.583 13.037-.232zm-62.85 14.007l14.07 19.814h-41.008L124.195 44l13.035-.23zM43.923 59.564h19.452v65.497l-19.452-9.19V59.564zm29.438 0h19.355l-.002 79.356-19.355-9.142.002-70.214zm95.887 0h19.453l-.001 70.146-19.452 9.188V59.564zm29.438 0h19.353v56.285l-19.353 9.142V59.564zM106.4 73.57h19.451v81.004l-19.45-9.19V73.57zm29.44 0h19.35l-.001 71.971-19.353 9.145.004-81.116zm94.18 21.526l17.126 7.002-17.126 8.09V95.095zm-198.08.025v15.09l-17.12-8.09 17.12-7zm-16.857 23.81l108.337 51.178v155.588L15.082 274.52V118.93zm231.756 0v155.588l-108.335 51.178V170.11l108.335-51.179zm-19.521 44.302l-45.187 82.05-26.366-21.373-7.364 12.228 37.954 30.627 51.45-94.185-10.487-9.347z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

File diff suppressed because it is too large Load Diff

View File

@@ -61,6 +61,35 @@ textarea {
margin-bottom: 20px;
}
.notification-icon {
justify-content: center;
display: flex;
margin-bottom: 3rem;
svg {
fill: $color-gray-60;
height: 40%;
width: 40%;
}
}
.notification-text {
font-size: $fs18;
color: $color-gray-60;
margin-bottom: 20px;
}
.notification-text-email {
background: $color-gray-10;
border-radius: $br-small;
color: $color-gray-60;
font-size: $fs18;
font-weight: 500;
margin: 1.5rem 0 2.5rem 0;
padding: 1rem;
text-align: center;
}
h2 {
font-size: $fs24;
color: $color-gray-60;

View File

@@ -211,8 +211,10 @@
width: calc(100% - 1rem);
min-height: 5rem;
img {
max-height: 8rem;
max-width: 100%;
width: auto;
}
}

View File

@@ -99,8 +99,14 @@
position: fixed;
right: calc(#{$width-settings-bar} + 10px);
text-align: center;
width: 110px;
width: 125px;
white-space: nowrap;
padding-bottom: 2px;
transition: bottom 0.5s;
&.color-palette-open {
bottom: 5rem;
}
span {
color: $color-white;

View File

@@ -3,7 +3,18 @@
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>PENPOT - The Open-Source prototyping tool</title>
<title>Penpot - Design Freedom for Teams</title>
<meta name="description" content="The open-source solution for design and prototyping.">
<meta property="og:locale" content="en_US">
<meta property="og:title" content="Penpot | Design Freedom for Teams">
<meta property="og:description" content="The open-source solution for design and prototyping">
<meta property="og:image" content="https://penpot.app/images/workspace-ui.jpg">
<meta name="twitter:title" content="Penpot | Design Freedom for Teams">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:description" content="The open-source solution for design and prototyping">
<meta name="twitter:image" content="https://penpot.app/images/workspace-ui.jpg">
<meta name="twitter:site" content="@penpotapp">
<meta name="twitter:creator" content="@penpotapp">
<link id="theme" href="/css/main-{{& th}}.css?ts={{& ts}}"
rel="stylesheet" type="text/css" />

View File

@@ -1,7 +1,7 @@
{:deps {:aliases [:dev]}
:http {:port 3448}
:nrepl {:port 3447}
:jvm-opts ["-Xmx1g" "-Xms512m"]
:jvm-opts ["-Xmx700m" "-Xms100m" "-XX:+UseSerialGC"]
:builds
{:main

View File

@@ -9,12 +9,13 @@
(ns app.main
(:require
[app.config :as cfg]
[app.common.uuid :as uuid]
[app.common.spec :as us]
[app.main.repo :as rp]
[app.common.uuid :as uuid]
[app.config :as cfg]
[app.main.data.auth :refer [logout]]
[app.main.data.messages :as dm]
[app.main.data.users :as udu]
[app.main.repo :as rp]
[app.main.store :as st]
[app.main.ui :as ui]
[app.main.ui.confirm]
@@ -22,12 +23,12 @@
[app.main.worker]
[app.util.dom :as dom]
[app.util.i18n :as i18n]
[app.util.logging :as log]
[app.util.object :as obj]
[app.util.router :as rt]
[app.util.storage :refer [storage]]
[app.util.theme :as theme]
[app.util.timers :as ts]
[app.util.logging :as log]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[rumext.alpha :as mf]))
@@ -71,7 +72,7 @@
(st/emit! (rt/nav :auth-login))
(nil? match)
(st/emit! (rt/nav :not-found))
(st/emit! (dm/assign-exception {:type :not-found}))
:else
(st/emit! #(assoc % :route match)))))

View File

@@ -36,7 +36,7 @@
(let [team-id (:default-team-id profile)]
(rx/merge
(rx/of (du/profile-fetched profile)
(rt/nav :dashboard-projects {:team-id team-id}))
(rt/nav' :dashboard-projects {:team-id team-id}))
(when-not (get-in profile [:props :onboarding-viewed])
(->> (rx/of (modal/show {:type :onboarding}))
(rx/delay 1000))))))))
@@ -77,9 +77,7 @@
ptk/WatchEvent
(watch [this state s]
(let [team-id (:default-team-id profile)]
(rx/of (du/profile-fetched profile)
(rt/nav' :dashboard-projects {:team-id team-id}))))))
(rx/of (logged-in profile)))))
(defn login-with-ldap
[{:keys [email password] :as data}]
@@ -184,10 +182,7 @@
(->> (rp/mutation :request-profile-recovery data)
(rx/tap on-success)
(rx/catch (fn [err]
(on-error err)
(rx/empty))))))))
(rx/catch on-error))))))
;; --- Recovery (Password)

View File

@@ -50,7 +50,8 @@
(-> state
(update-in [:workspace-file :colors] #(d/replace-by-id % color))))))
(defn change-palette-size [size]
(defn change-palette-size
[size]
(s/assert #{:big :small} size)
(ptk/reify ::change-palette-size
ptk/UpdateEvent
@@ -58,14 +59,27 @@
(-> state
(assoc-in [:workspace-local :selected-palette-size] size)))))
(defn change-palette-selected [selected]
(defn change-palette-selected
"Change the library used by the general palette tool"
[selected]
(ptk/reify ::change-palette-selected
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-local :selected-palette] selected)))))
(defn show-palette [selected]
(defn change-palette-selected-colorpicker
"Change the library used by the color picker"
[selected]
(ptk/reify ::change-palette-selected-colorpicker
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-local :selected-palette-colorpicker] selected)))))
(defn show-palette
"Show the palette tool and change the library it uses"
[selected]
(ptk/reify ::change-palette-selected
ptk/UpdateEvent
(update [_ state]
@@ -73,14 +87,16 @@
(update :workspace-layout conj :colorpalette)
(assoc-in [:workspace-local :selected-palette] selected)))))
(defn start-picker []
(defn start-picker
[]
(ptk/reify ::start-picker
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-local :picking-color?] true)))))
(defn stop-picker []
(defn stop-picker
[]
(ptk/reify ::stop-picker
ptk/UpdateEvent
(update [_ state]
@@ -89,14 +105,16 @@
(update :workspace-local dissoc :picked-shift?)
(assoc-in [:workspace-local :picking-color?] false)))))
(defn pick-color [rgba]
(defn pick-color
[rgba]
(ptk/reify ::pick-color
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-local :picked-color] rgba)))))
(defn pick-color-select [value shift?]
(defn pick-color-select
[value shift?]
(ptk/reify ::pick-color-select
ptk/UpdateEvent
(update [_ state]
@@ -116,11 +134,21 @@
text-ids (filter is-text? ids)
shape-ids (filter (comp not is-text?) ids)
attrs {:fill-color (:color color)
:fill-color-ref-id (:id color)
:fill-color-ref-file (:file-id color)
:fill-color-gradient (:gradient color)
:fill-opacity (:opacity color)}
attrs (cond-> {}
(contains? color :color)
(assoc :fill-color (:color color))
(contains? color :id)
(assoc :fill-color-ref-id (:id color))
(contains? color :file-id)
(assoc :fill-color-ref-file (:file-id color))
(contains? color :gradient)
(assoc :fill-color-gradient (:gradient color))
(contains? color :opacity)
(assoc :fill-opacity (:opacity color)))
update-fn (fn [shape] (merge shape attrs))
editors (get-in state [:workspace-local :editors])
@@ -131,29 +159,42 @@
(map #(dwt/update-text-attrs {:id % :editor (get editors %) :attrs attrs}) text-ids)
(dwc/update-shapes shape-ids update-fn))))))))
(defn change-stroke [ids color]
(defn change-stroke
[ids color]
(ptk/reify ::change-stroke
ptk/WatchEvent
(watch [_ state s]
(let [pid (:current-page-id state)
objects (get-in state [:workspace-data :pages-index pid :objects])
not-frame (fn [shape-id] (not= (get-in objects [shape-id :type]) :frame))
update-fn (fn [s]
(cond-> s
true
(assoc :stroke-color (:color color)
:stroke-opacity (:opacity color)
:stroke-color-gradient (:gradient color)
:stroke-color-ref-id (:id color)
:stroke-color-ref-file (:file-id color))
(= (:stroke-style s) :none)
(assoc :stroke-style :solid
:stroke-width 1
:stroke-opacity 1)))]
color-attrs (cond-> {}
(contains? color :color)
(assoc :stroke-color (:color color))
(contains? color :id)
(assoc :stroke-color-ref-id (:id color))
(contains? color :file-id)
(assoc :stroke-color-ref-file (:file-id color))
(contains? color :gradient)
(assoc :stroke-color-gradient (:gradient color))
(contains? color :opacity)
(assoc :stroke-opacity (:opacity color)))
update-fn (fn [shape]
(-> shape
(merge color-attrs)
(cond-> (= (:stroke-style s) :none)
(assoc :stroke-style :solid
:stroke-width 1
:stroke-opacity 1))))]
(rx/of (dwc/update-shapes ids update-fn))))))
(defn picker-for-selected-shape []
(defn picker-for-selected-shape
[]
(let [sub (rx/subject)]
(ptk/reify ::picker-for-selected-shape
ptk/WatchEvent
@@ -189,7 +230,8 @@
:props {:on-change handle-change-color}
:allow-click-outside true})))))))
(defn start-gradient [gradient]
(defn start-gradient
[gradient]
(ptk/reify ::start-gradient
ptk/UpdateEvent
(update [_ state]
@@ -198,21 +240,24 @@
(assoc-in [:workspace-local :current-gradient] gradient)
(assoc-in [:workspace-local :current-gradient :shape-id] id))))))
(defn stop-gradient []
(defn stop-gradient
[]
(ptk/reify ::stop-gradient
ptk/UpdateEvent
(update [_ state]
(-> state
(update :workspace-local dissoc :current-gradient)))))
(defn update-gradient [changes]
(defn update-gradient
[changes]
(ptk/reify ::update-gradient
ptk/UpdateEvent
(update [_ state]
(-> state
(update-in [:workspace-local :current-gradient] merge changes)))))
(defn select-gradient-stop [spot]
(defn select-gradient-stop
[spot]
(ptk/reify ::select-gradient-stop
ptk/UpdateEvent
(update [_ state]

View File

@@ -13,6 +13,7 @@
[app.common.media :as cm]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.common.exceptions :as ex]
[app.main.data.messages :as dm]
[app.main.repo :as rp]
[app.main.store :as st]
@@ -50,9 +51,13 @@
;; Check that a file obtained with the file javascript API is valid.
[file]
(when (> (.-size file) cm/max-file-size)
(throw (ex-info (tr "errors.media-too-large") {})))
(ex/raise :type :validation
:code :media-too-large
:hint (str/fmt "media size is large than 5mb (size: %s)" (.-size file))))
(when-not (contains? cm/valid-media-types (.-type file))
(throw (ex-info (tr "errors.media-format-unsupported") {})))
(ex/raise :type :validation
:code :media-type-not-allowed
:hint (str/fmt "media type %s is not supported" (.-type file))))
file)
(defn notify-start-loading

View File

@@ -22,7 +22,7 @@
(declare show)
(def default-animation-timeout 600)
(def default-timeout 2000)
(def default-timeout 5000)
(s/def ::type #{:success :error :info :warning})
(s/def ::position #{:fixed :floating :inline})

View File

@@ -0,0 +1,75 @@
;; 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/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.data.shortcuts
(:require
[app.main.data.colors :as mdc]
[app.main.data.workspace.transforms :as dwt]
[app.main.store :as st]
[app.util.dom :as dom]
[potok.core :as ptk]
[beicon.core :as rx]
[app.config :as cfg])
(:refer-clojure :exclude [meta]))
(def mac-command "\u2318")
(def mac-option "\u2325")
(def mac-delete "\u232B")
(def mac-shift "\u21E7")
(def mac-control "\u2303")
(def mac-esc "\u238B")
(def left-arrow "\u2190")
(def up-arrow "\u2191")
(def right-arrow "\u2192")
(def down-arrow "\u2193")
(defn c-mod
"Adds the control/command modifier to a shortcuts depending on the
operating system for the user"
[shortcut]
(if (cfg/check-platform? :macos)
(str "command+" shortcut)
(str "ctrl+" shortcut)))
(defn bind-shortcuts [shortcuts bind-fn cb-fn]
(doseq [[key {:keys [command disabled fn]}] shortcuts]
(when-not disabled
(if (vector? command)
(doseq [cmd (seq command)]
(bind-fn cmd (cb-fn key fn)))
(bind-fn command (cb-fn key fn))))))
(defn meta [key]
(str
(if (cfg/check-platform? :macos)
mac-command
"Ctrl+")
key))
(defn shift [key]
(str
(if (cfg/check-platform? :macos)
mac-shift
"Shift+")
key))
(defn meta-shift [key]
(-> key meta shift))
(defn supr []
(if (cfg/check-platform? :macos)
mac-delete
"Supr"))
(defn esc []
(if (cfg/check-platform? :macos)
mac-esc
"Escape"))

View File

@@ -112,21 +112,22 @@
(defn bundle-fetched
[{:keys [project file page share-token token libraries users] :as bundle}]
(us/verify ::bundle bundle)
(ptk/reify ::file-fetched
(ptk/reify ::bundle-fetched
ptk/UpdateEvent
(update [_ state]
(let [objects (:objects page)
frames (extract-frames objects)]
(assoc state
:viewer-libraries (d/index-by :id libraries)
:viewer-data {:project project
:objects objects
:users (d/index-by :id users)
:file file
:page page
:frames frames
:token token
:share-token share-token})))))
(-> state
(assoc :viewer-libraries (d/index-by :id libraries))
(update :viewer-data assoc
:project project
:objects objects
:users (d/index-by :id users)
:file file
:page page
:frames frames
:token token
:share-token share-token))))))
(defn fetch-comment-threads
[{:keys [file-id page-id] :as params}]
@@ -346,7 +347,7 @@
(defn set-current-frame [frame-id]
(ptk/reify ::current-frame
(ptk/reify ::set-current-frame
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:viewer-data :current-frame-id] frame-id))))
@@ -415,15 +416,3 @@
(update [_ state]
(assoc-in state [:viewer-local :hover] (when hover? id)))))
;; --- Shortcuts
(def shortcuts
{"+" (st/emitf increase-zoom)
"-" (st/emitf decrease-zoom)
"ctrl+a" (st/emitf (select-all))
"shift+0" (st/emitf zoom-to-50)
"shift+1" (st/emitf reset-zoom)
"shift+2" (st/emitf zoom-to-200)
"left" (st/emitf select-prev-frame)
"right" (st/emitf select-next-frame)})

View File

@@ -0,0 +1,57 @@
;; 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/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.data.viewer.shortcuts
(:require
[app.config :as cfg]
[app.main.data.colors :as mdc]
[app.main.data.shortcuts :as ds]
[app.main.data.shortcuts :refer [c-mod]]
[app.main.data.viewer :as dv]
[app.main.store :as st]
[app.util.dom :as dom]
[beicon.core :as rx]
[potok.core :as ptk]))
(def shortcuts
{:increase-zoom {:tooltip "+"
:command "+"
:fn (st/emitf dv/increase-zoom)}
:decrease-zoom {:tooltip "-"
:command "-"
:fn (st/emitf dv/decrease-zoom)}
:select-all {:tooltip (ds/meta "A")
:command (ds/c-mod "a")
:fn (st/emitf (dv/select-all))}
:zoom-50 {:tooltip (ds/shift "0")
:command "shift+0"
:fn (st/emitf dv/zoom-to-50)}
:reset-zoom {:tooltip (ds/shift "1")
:command "shift+1"
:fn (st/emitf dv/reset-zoom)}
:zoom-200 {:tooltip (ds/shift "2")
:command "shift+2"
:fn (st/emitf dv/zoom-to-200)}
:next-frame {:tooltip ds/left-arrow
:command "left"
:fn (st/emitf dv/select-prev-frame)}
:prev-frame {:tooltip ds/right-arrow
:command "right"
:fn (st/emitf dv/select-next-frame)}})
(defn get-tooltip [shortcut]
(assert (contains? shortcuts shortcut) (str shortcut))
(get-in shortcuts [shortcut :tooltip]))

View File

@@ -9,14 +9,13 @@
(ns app.main.data.workspace
(:require
[goog.string.path :as path]
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.geom.align :as gal]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.geom.proportions :as gpr]
[app.common.geom.align :as gal]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
@@ -27,33 +26,33 @@
[app.main.data.colors :as mdc]
[app.main.data.messages :as dm]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.drawing :as dwd]
[app.main.data.workspace.drawing.path :as dwdp]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.notifications :as dwn]
[app.main.data.workspace.persistence :as dwp]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.texts :as dwtxt]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.drawing :as dwd]
[app.main.data.workspace.drawing.path :as dwdp]
[app.main.repo :as rp]
[app.main.store :as st]
[app.main.streams :as ms]
[app.main.worker :as uw]
[app.util.dom :as dom]
[app.util.http :as http]
[app.util.i18n :refer [tr] :as i18n]
[app.util.logging :as log]
[app.util.object :as obj]
[app.util.router :as rt]
[app.util.timers :as ts]
[app.util.transit :as t]
[app.util.webapi :as wapi]
[app.util.i18n :refer [tr] :as i18n]
[app.util.object :as obj]
[app.util.dom :as dom]
[app.util.http :as http]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[clojure.set :as set]
[cuerdas.core :as str]
;; [cljs.pprint :refer [pprint]]
[goog.string.path :as path]
[potok.core :as ptk]))
;; (log/set-level! :trace)
@@ -121,8 +120,10 @@
:left-sidebar? true
:right-sidebar? true
:color-for-rename nil
:selected-palette-colorpicker :recent
:selected-palette :recent
:selected-palette-size :big
:assets-files-open {}
:picking-color? false
:picked-color nil
:picked-color-select false})
@@ -822,6 +823,9 @@
;; Ignore any shape whose parent is also intented to be moved
ids (cp/clean-loops objects ids)
;; If we try to move a parent into a child we remove it
ids (filter #(not (cp/is-parent? objects parent-id %)) ids)
parents (loop [res #{parent-id}
ids (seq ids)]
(if (nil? ids)
@@ -1220,20 +1224,26 @@
(defn go-to-viewer
[{:keys [file-id page-id] :as params}]
(ptk/reify ::go-to-viewer
ptk/WatchEvent
(watch [_ state stream]
(rx/of ::dwp/force-persist
(rt/nav :viewer params {:index 0})))))
([] (go-to-viewer {}))
([{:keys [file-id page-id]}]
(ptk/reify ::go-to-viewer
ptk/WatchEvent
(watch [_ state stream]
(let [{:keys [current-file-id current-page-id]} state
params {:file-id (or file-id current-file-id)
:page-id (or page-id current-page-id)}]
(rx/of ::dwp/force-persist
(rt/nav :viewer params {:index 0})))))))
(defn go-to-dashboard
[{:keys [team-id] :as project}]
(ptk/reify ::go-to-viewer
ptk/WatchEvent
(watch [_ state stream]
(rx/of ::dwp/force-persist
(rt/nav :dashboard-projects {:team-id team-id})))))
([] (go-to-dashboard nil))
([{:keys [team-id]}]
(ptk/reify ::go-to-dashboard
ptk/WatchEvent
(watch [_ state stream]
(let [team-id (or team-id (get-in state [:workspace-project :team-id]))]
(rx/of ::dwp/force-persist
(rt/nav :dashboard-projects {:team-id team-id})))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Context Menu
@@ -1279,7 +1289,6 @@
;; Clipboard
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn copy-selected
[]
(letfn [;; Retrieve all ids of selected shapes with corresponding
@@ -1398,12 +1407,22 @@
(let [paste-data (wapi/read-from-paste-event event)
image-data (wapi/extract-images paste-data)
text-data (wapi/extract-text paste-data)
decoded-data (and (t/transit? text-data) (t/decode text-data))]
decoded-data (and (t/transit? text-data)
(t/decode text-data))]
(cond
(seq image-data) (rx/from (map paste-image image-data))
decoded-data (rx/of (paste-shape decoded-data in-viewport?))
(string? text-data) (rx/of (paste-text text-data))
:else (rx/empty)))
(seq image-data)
(rx/from (map paste-image image-data))
(coll? decoded-data)
(->> (rx/of decoded-data)
(rx/filter #(= :copied-shapes (:type %)))
(rx/map #(paste-shape % in-viewport?)))
(string? text-data)
(rx/of (paste-text text-data))
:else
(rx/empty)))
(catch :default err
(js/console.error "Clipboard error:" err))))))
@@ -1564,7 +1583,7 @@
(watch [_ state stream]
(let [id (uuid/next)
{:keys [x y]} @ms/mouse-position
width (min (* 7 (count text)) 700)
width (max 8 (min (* 7 (count text)) 700))
height 16
page-id (:current-page-id state)
frame-id (-> (dwc/lookup-page-objects state page-id)
@@ -1738,80 +1757,3 @@
(d/export dwg/unmask-group)
(d/export dwg/group-selected)
(d/export dwg/ungroup-selected)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts impl https://github.com/ccampbell/mousetrap
(defn esc-pressed []
(ptk/reify :esc-pressed
ptk/WatchEvent
(watch [_ state stream]
;; Not interrupt when we're editing a path
(let [edition-id (or (get-in state [:workspace-drawing :object :id])
(get-in state [:workspace-local :edition]))
path-edit-mode (get-in state [:workspace-local :edit-path edition-id :edit-mode])]
(if-not (= :draw path-edit-mode)
(rx/of :interrupt (deselect-all true))
(rx/empty))))))
(defn c-mod
"Adds the control/command modifier to a shortcuts depending on the
operating system for the user"
[shortcut]
(if (cfg/check-platform? :macos)
(str "command+" shortcut)
(str "ctrl+" shortcut)))
(def shortcuts
{(c-mod "i") #(st/emit! (toggle-layout-flags :assets))
(c-mod "l") #(st/emit! (toggle-layout-flags :sitemap :layers))
(c-mod "shift+r") #(st/emit! (toggle-layout-flags :rules))
(c-mod "a") #(st/emit! (select-all))
(c-mod "p") #(st/emit! (toggle-layout-flags :colorpalette))
(c-mod "'") #(st/emit! (toggle-layout-flags :display-grid))
(c-mod "shift+'") #(st/emit! (toggle-layout-flags :snap-grid))
"+" #(st/emit! (increase-zoom nil))
"-" #(st/emit! (decrease-zoom nil))
(c-mod "g") #(st/emit! group-selected)
"shift+g" #(st/emit! ungroup-selected)
(c-mod "m") #(st/emit! mask-group)
"shift+m" #(st/emit! unmask-group)
(c-mod "k") #(st/emit! dwl/add-component)
"shift+0" #(st/emit! reset-zoom)
"shift+1" #(st/emit! zoom-to-fit-all)
"shift+2" #(st/emit! zoom-to-selected-shape)
(c-mod "d") #(st/emit! duplicate-selected)
(c-mod "z") #(st/emit! dwc/undo)
(c-mod "shift+z") #(st/emit! dwc/redo)
(c-mod "y") #(st/emit! dwc/redo)
(c-mod "q") #(st/emit! dwc/reinitialize-undo)
"a" #(st/emit! (dwd/select-for-drawing :frame))
"r" #(st/emit! (dwd/select-for-drawing :rect))
"e" #(st/emit! (dwd/select-for-drawing :circle))
"t" #(st/emit! dwtxt/start-edit-if-selected
(dwd/select-for-drawing :text))
"p" #(st/emit! (dwd/select-for-drawing :path))
"k" (fn [event]
(let [image-upload (dom/get-element "image-upload")]
(dom/click image-upload)))
(c-mod "c") #(st/emit! (copy-selected))
(c-mod "x") #(st/emit! (copy-selected) delete-selected)
"escape" #(st/emit! (esc-pressed))
"del" #(st/emit! delete-selected)
"backspace" #(st/emit! delete-selected)
(c-mod "up") #(st/emit! (vertical-order-selected :up))
(c-mod "down") #(st/emit! (vertical-order-selected :down))
(c-mod "shift+up") #(st/emit! (vertical-order-selected :top))
(c-mod "shift+down") #(st/emit! (vertical-order-selected :bottom))
"shift+up" #(st/emit! (dwt/move-selected :up true))
"shift+down" #(st/emit! (dwt/move-selected :down true))
"shift+right" #(st/emit! (dwt/move-selected :right true))
"shift+left" #(st/emit! (dwt/move-selected :left true))
"up" #(st/emit! (dwt/move-selected :up false))
"down" #(st/emit! (dwt/move-selected :down false))
"right" #(st/emit! (dwt/move-selected :right false))
"left" #(st/emit! (dwt/move-selected :left false))
"i" #(st/emit! (mdc/picker-for-selected-shape ))})

View File

@@ -536,20 +536,26 @@
(defn get-shape-layer-position
[objects selected attrs]
(cond
(= :frame (:type attrs))
(if (= :frame (:type attrs))
;; Frames are alwasy positioned on the root frame
[uuid/zero uuid/zero nil]
(empty? selected)
;; Calculate the frame over which we're drawing
(let [position @ms/mouse-position
frame-id (:frame-id attrs (cp/frame-id-by-position objects position))]
[frame-id frame-id nil])
frame-id (:frame-id attrs (cp/frame-id-by-position objects position))
shape (when-not (empty? selected)
(cp/get-base-shape objects selected))]
:else
(let [shape (cp/get-base-shape objects selected)
index (cp/position-on-parent (:id shape) objects)
{:keys [frame-id parent-id]} shape]
[frame-id parent-id (inc index)])))
;; When no shapes has been selected or we're over a different frame
;; we add it as the latest shape of that frame
(if (or (not shape) (not= (:frame-id shape) frame-id))
[frame-id frame-id nil]
;; Otherwise, we add it to next to the selected shape
(let [index (cp/position-on-parent (:id shape) objects)
{:keys [frame-id parent-id]} shape]
[frame-id parent-id (inc index)])))))
(defn add-shape-changes
[page-id objects selected attrs]
@@ -562,6 +568,9 @@
shape (merge default-attrs shape)
not-frame? #(not (= :frame (get-in objects [% :type])))
selected (into #{} (filter not-frame?) selected)
[frame-id parent-id index] (get-shape-layer-position objects selected attrs)
redo-changes [{:type :add-obj

View File

@@ -21,12 +21,17 @@
[app.main.data.workspace.drawing.common :as common]
[app.common.math :as mth]))
(defn truncate-zero [num default]
(if (mth/almost-zero? num) default num))
(defn resize-shape [{:keys [x y width height transform transform-inverse] :as shape} point lock?]
(let [;; The new shape behaves like a resize on the bottom-right corner
initial (gpt/point (+ x width) (+ y height))
shapev (gpt/point width height)
deltav (gpt/to-vec initial point)
scalev (gpt/divide (gpt/add shapev deltav) shapev)
scalev (-> (gpt/divide (gpt/add shapev deltav) shapev)
(update :x truncate-zero 1)
(update :y truncate-zero 1))
scalev (if lock?
(let [v (max (:x scalev) (:y scalev))]
(gpt/point v v))

View File

@@ -692,8 +692,8 @@
point (-> content (get (if (= prefix :c1) (dec index) index)) (ugp/command->point))
handler (-> content (get index) (ugp/get-handler prefix))
current-distance (gpt/distance (ugp/opposite-handler point handler) opposite-handler)
match-opposite? (mth/almost-zero? current-distance)]
current-distance (when opposite-handler (gpt/distance (ugp/opposite-handler point handler) opposite-handler))
match-opposite? (and opposite-handler (mth/almost-zero? current-distance))]
(drag-stream
(rx/concat

View File

@@ -65,6 +65,13 @@
(declare sync-file)
(defn set-assets-box-open
[file-id box open?]
(ptk/reify ::set-assets-box-open
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :assets-files-open file-id box] open?))))
(defn default-color-name [color]
(or (:color color)
(case (get-in color [:gradient :type])
@@ -230,7 +237,8 @@
(let [file-id (:current-file-id state)
page-id (:current-page-id state)
objects (dwc/lookup-page-objects state page-id)
selected (get-in state [:workspace-local :selected])]
selected (get-in state [:workspace-local :selected])
selected (cp/clean-loops objects selected)]
(let [[group rchanges uchanges]
(dwlh/generate-add-component selected objects page-id file-id)]
(when-not (empty? rchanges)

View File

@@ -9,9 +9,8 @@
(ns app.main.data.workspace.persistence
(:require
[cuerdas.core :as str]
[app.util.http :as http]
[app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.geom.point :as gpt]
[app.common.media :as cm]
[app.common.pages :as cp]
@@ -21,21 +20,22 @@
[app.main.data.media :as di]
[app.main.data.messages :as dm]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.svg-upload :as svg]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.svg-upload :as svg]
[app.main.repo :as rp]
[app.main.store :as st]
[app.util.avatars :as avatars]
[app.util.http :as http]
[app.util.i18n :as i18n :refer [tr]]
[app.util.object :as obj]
[app.util.router :as rt]
[app.util.time :as dt]
[app.util.transit :as t]
[app.util.avatars :as avatars]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[potok.core :as ptk]
[app.main.store :as st]))
[cuerdas.core :as str]
[potok.core :as ptk]))
(declare persist-changes)
(declare persist-sychronous-changes)
@@ -417,24 +417,27 @@
(defn- handle-upload-error [on-error stream]
(->> stream
(rx/catch
(fn [error]
(cond
(= (:code error) :media-type-not-allowed)
(rx/of (dm/error (tr "errors.media-type-not-allowed")))
(fn on-error [error]
(if (ex/ex-info? error)
(on-error (ex-data error))
(cond
(= (:code error) :media-type-not-allowed)
(rx/of (dm/error (tr "errors.media-type-not-allowed")))
(= (:code error) :media-type-mismatch)
(rx/of (dm/error (tr "errors.media-type-mismatch")))
(= (:code error) :media-too-large)
(rx/of (dm/error (tr "errors.media-too-large")))
(= (:code error) :unable-to-optimize)
(rx/of (dm/error (:hint error)))
(= (:code error) :media-type-mismatch)
(rx/of (dm/error (tr "errors.media-type-mismatch")))
(fn? on-error)
(do
(= (:code error) :unable-to-optimize)
(rx/of (dm/error (:hint error)))
(fn? on-error)
(on-error error)
(rx/empty))
:else
(rx/throw error))))))
:else
(rx/throw error)))))))
(defn- upload-uris [file-id local? name uris mtype on-image on-svg]
(letfn [(svg-url? [url]
@@ -490,7 +493,7 @@
(rx/map #(assoc (first %) :name (.-name (second %))))
(rx/do on-svg)))))
(defn upload-media-objects
(defn- upload-media-objects
[{:keys [file-id local? data name uris mtype svg-as-images] :as params}]
(us/assert ::upload-media-objects params)
(ptk/reify ::upload-media-objects
@@ -499,7 +502,6 @@
(let [{:keys [on-image on-svg on-error]
:or {on-image identity
on-svg identity}} (meta params)]
(rx/concat
(rx/of (dm/show {:content (tr "media.loading")
:type :info
@@ -515,7 +517,8 @@
(handle-upload-error on-error)
(rx/finalize (st/emitf (dm/hide-tag :media-loading)))))))))
(defn upload-media-asset [params]
(defn upload-media-asset
[params]
(let [params (-> params
(assoc :svg-as-images true)
(assoc :local? false)
@@ -525,13 +528,12 @@
(defn upload-media-workspace
[params position]
(let [{:keys [x y]} position
params (-> params
(assoc :local? true)
(with-meta
{:on-image
#(st/emit! (dwc/image-uploaded % x y))
:on-svg
#(st/emit! (svg/svg-uploaded % x y))}))]
mdata {:on-image #(st/emit! (dwc/image-uploaded % x y))
:on-svg #(st/emit! (svg/svg-uploaded % x y))}
params (-> (assoc params :local? true)
(with-meta mdata))]
(upload-media-objects params)))

View File

@@ -105,6 +105,14 @@
objects (dwc/lookup-page-objects state page-id)]
(rx/of (dwc/expand-all-parents [id] objects)))))))
(defn deselect-shape
[id]
(us/verify ::us/uuid id)
(ptk/reify ::select-shape
ptk/UpdateEvent
(update [_ state]
(update-in state [:workspace-local :selected] disj id))))
(defn shift-select-shapes
([id]
(ptk/reify ::shift-select-shapes

View File

@@ -0,0 +1,254 @@
;; 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/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns app.main.data.workspace.shortcuts
(:require
[app.config :as cfg]
[app.main.data.colors :as mdc]
[app.main.data.shortcuts :as ds]
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.drawing :as dwd]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.texts :as dwtxt]
[app.main.data.workspace.transforms :as dwt]
[app.main.store :as st]
[app.util.dom :as dom]
[beicon.core :as rx]
[potok.core :as ptk]))
;; \u2318P
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts impl https://github.com/ccampbell/mousetrap
(defn esc-pressed []
(ptk/reify :esc-pressed
ptk/WatchEvent
(watch [_ state stream]
;; Not interrupt when we're editing a path
(let [edition-id (or (get-in state [:workspace-drawing :object :id])
(get-in state [:workspace-local :edition]))
path-edit-mode (get-in state [:workspace-local :edit-path edition-id :edit-mode])]
(if-not (= :draw path-edit-mode)
(rx/of :interrupt (dw/deselect-all true))
(rx/empty))))))
(def shortcuts
{:toggle-layers {:tooltip (ds/meta "L")
:command (ds/c-mod "l")
:fn #(st/emit! (dw/go-to-layout :layers))}
:toggle-assets {:tooltip (ds/meta "I")
:command (ds/c-mod "i")
:fn #(st/emit! (dw/go-to-layout :assets))}
:toggle-history {:tooltip (ds/meta "H")
:command (ds/c-mod "h")
:fn #(st/emit! (dw/go-to-layout :document-history))}
:toggle-palette {:tooltip (ds/meta "P")
:command (ds/c-mod "p")
:fn #(st/emit! (dw/toggle-layout-flags :colorpalette))}
:toggle-rules {:tooltip (ds/meta-shift "R")
:command (ds/c-mod "shift+r")
:fn #(st/emit! (dw/toggle-layout-flags :rules))}
:select-all {:tooltip (ds/meta "A")
:command (ds/c-mod "a")
:fn #(st/emit! (dw/select-all))}
:toggle-grid {:tooltip (ds/meta "'")
:command (ds/c-mod "'")
:fn #(st/emit! (dw/toggle-layout-flags :display-grid))}
:toggle-snap-grid {:tooltip (ds/meta-shift "'")
:command (ds/c-mod "shift+'")
:fn #(st/emit! (dw/toggle-layout-flags :snap-grid))}
:toggle-alignment {:tooltip (ds/meta "\\")
:command (ds/c-mod "\\")
:fn #(st/emit! (dw/toggle-layout-flags :dynamic-alignment))}
:increase-zoom {:tooltip "+"
:command "+"
:fn #(st/emit! (dw/increase-zoom nil))}
:decrease-zoom {:tooltip "-"
:command "-"
:fn #(st/emit! (dw/decrease-zoom nil))}
:group {:tooltip (ds/meta "G")
:command (ds/c-mod "g")
:fn #(st/emit! dw/group-selected)}
:ungroup {:tooltip (ds/shift "G")
:command "shift+g"
:fn #(st/emit! dw/ungroup-selected)}
:mask {:tooltip (ds/meta "M")
:command (ds/c-mod "m")
:fn #(st/emit! dw/mask-group)}
:unmask {:tooltip (ds/meta-shift "M")
:command (ds/c-mod "shift+m")
:fn #(st/emit! dw/unmask-group)}
:create-component {:tooltip (ds/meta "K")
:command (ds/c-mod "k")
:fn #(st/emit! dwl/add-component)}
:reset-zoom {:tooltip (ds/shift "0")
:command "shift+0"
:fn #(st/emit! dw/reset-zoom)}
:fit-all {:tooltip (ds/shift "1")
:command "shift+1"
:fn #(st/emit! dw/zoom-to-fit-all)}
:zoom-selected {:tooltip (ds/shift "2")
:command "shift+2"
:fn #(st/emit! dw/zoom-to-selected-shape)}
:duplicate {:tooltip (ds/meta "D")
:command (ds/c-mod "d")
:fn #(st/emit! dw/duplicate-selected)}
:undo {:tooltip (ds/meta "Z")
:command (ds/c-mod "z")
:fn #(st/emit! dwc/undo)}
:redo {:tooltip (ds/meta "Y")
:command [(ds/c-mod "shift+z") (ds/c-mod "y")]
:fn #(st/emit! dwc/redo)}
:clear-undo {:tooltip (ds/meta "Q")
:command (ds/c-mod "q")
:fn #(st/emit! dwc/reinitialize-undo)}
:draw-frame {:tooltip "A"
:command "a"
:fn #(st/emit! (dwd/select-for-drawing :frame))}
:draw-rect {:tooltip "R"
:command "r"
:fn #(st/emit! (dwd/select-for-drawing :rect))}
:draw-ellipse {:tooltip "E"
:command "e"
:fn #(st/emit! (dwd/select-for-drawing :circle))}
:draw-text {:tooltip "T"
:command "t"
:fn #(st/emit! dwtxt/start-edit-if-selected
(dwd/select-for-drawing :text))}
:draw-path {:tooltip "P"
:command "p"
:fn #(st/emit! (dwd/select-for-drawing :path))}
:draw-curve {:tooltip (ds/shift "C")
:command "shift+c"
:fn #(st/emit! (dwd/select-for-drawing :curve))}
:add-comment {:tooltip "C"
:command "c"
:fn #(st/emit! (dwd/select-for-drawing :comments))}
:insert-image {:tooltip "K"
:command "k"
:fn #(-> "image-upload" dom/get-element dom/click)}
:copy {:tooltip (ds/meta "C")
:command (ds/c-mod "c")
:fn #(st/emit! (dw/copy-selected))}
:cut {:tooltip (ds/meta "X")
:command (ds/c-mod "x")
:fn #(st/emit! (dw/copy-selected) dw/delete-selected)}
:paste {:tooltip (ds/meta "V")
:disabled true
:command (ds/c-mod "v")}
:delete {:tooltip (ds/supr)
:command ["del" "backspace"]
:fn #(st/emit! dw/delete-selected)}
:bring-forward {:tooltip (ds/meta ds/up-arrow)
:command (ds/c-mod "up")
:fn #(st/emit! (dw/vertical-order-selected :up))}
:bring-backward {:tooltip (ds/meta ds/down-arrow)
:command (ds/c-mod "down")
:fn #(st/emit! (dw/vertical-order-selected :down))}
:bring-front {:tooltip (ds/meta-shift ds/up-arrow)
:command (ds/c-mod "shift+up")
:fn #(st/emit! (dw/vertical-order-selected :top))}
:bring-back {:tooltip (ds/meta-shift ds/down-arrow)
:command (ds/c-mod "shift+down")
:fn #(st/emit! (dw/vertical-order-selected :bottom))}
:move-fast-up {:tooltip (ds/shift ds/up-arrow)
:command "shift+up"
:fn #(st/emit! (dwt/move-selected :up true))}
:move-fast-down {:tooltip (ds/shift ds/down-arrow)
:command "shift+down"
:fn #(st/emit! (dwt/move-selected :down true))}
:move-fast-right {:tooltip (ds/shift ds/right-arrow)
:command "shift+right"
:fn #(st/emit! (dwt/move-selected :right true))}
:move-fast-left {:tooltip (ds/shift ds/left-arrow)
:command "shift+left"
:fn #(st/emit! (dwt/move-selected :left true))}
:move-unit-up {:tooltip ds/up-arrow
:command "up"
:fn #(st/emit! (dwt/move-selected :up false))}
:move-unit-down {:tooltip ds/down-arrow
:command "down"
:fn #(st/emit! (dwt/move-selected :down false))}
:move-unit-left {:tooltip ds/right-arrow
:command "right"
:fn #(st/emit! (dwt/move-selected :right false))}
:move-unit-right {:tooltip ds/left-arrow
:command "left"
:fn #(st/emit! (dwt/move-selected :left false))}
:open-color-picker {:tooltip "I"
:command "i"
:fn #(st/emit! (mdc/picker-for-selected-shape ))}
:open-viewer {:tooltip "G V"
:command "g v"
:fn #(st/emit! (dw/go-to-viewer))}
:open-dashboard {:tooltip "G D"
:command "g d"
:fn #(st/emit! (dw/go-to-dashboard))}
:escape {:tooltip (ds/esc)
:command "escape"
:fn #(st/emit! (esc-pressed))}})
(defn get-tooltip [shortcut]
(assert (contains? shortcuts shortcut) (str shortcut))
(get-in shortcuts [shortcut :tooltip]))

View File

@@ -95,7 +95,14 @@
;; Resize vector
scalev (gpt/divide (gpt/add shapev deltav) shapev)
scalev (if lock? (let [v (max (:x scalev) (:y scalev))] (gpt/point v v)) scalev)
scalev (if lock?
(let [v (cond
(#{:right :left} handler) (:x scalev)
(#{:top :bottom} handler) (:y scalev)
:else (max (:x scalev) (:y scalev)))]
(gpt/point v v))
scalev)
shape-transform (:transform shape (gmt/matrix))
shape-transform-inverse (:transform-inverse shape (gmt/matrix))
@@ -294,18 +301,20 @@
snap-delta (->> position
(rx/switch-map #(snap/closest-snap-move page-id shapes objects layout zoom %)))]
(rx/concat
(->> snap-delta
(rx/with-latest vector position)
(rx/map (fn [[delta pos]] (-> (gpt/add pos delta) (gpt/round 0))))
(rx/map gmt/translate-matrix)
(rx/map #(fn [state] (assoc-in state [:workspace-local :modifiers] {:displacement %}))))
(if (empty? shapes)
(rx/empty)
(rx/concat
(->> snap-delta
(rx/with-latest vector position)
(rx/map (fn [[delta pos]] (-> (gpt/add pos delta) (gpt/round 0))))
(rx/map gmt/translate-matrix)
(rx/map #(fn [state] (assoc-in state [:workspace-local :modifiers] {:displacement %}))))
(rx/of (set-modifiers ids)
(apply-modifiers ids)
(calculate-frame-for-move ids)
(fn [state] (update state :workspace-local dissoc :modifiers))
finish-transform)))))))
(rx/of (set-modifiers ids)
(apply-modifiers ids)
(calculate-frame-for-move ids)
(fn [state] (update state :workspace-local dissoc :modifiers))
finish-transform))))))))
(defn- get-displacement-with-grid
"Retrieve the correct displacement delta point for the
@@ -369,15 +378,17 @@
(->> move-events
(rx/take-until stopper)
(rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0))
(rx/map #(set-modifiers selected {:displacement (gmt/translate-matrix %)})))
(rx/map gmt/translate-matrix)
(rx/map #(fn [state] (assoc-in state [:workspace-local :modifiers] {:displacement %}))))
(rx/of (move-selected direction shift?)))
(rx/of (apply-modifiers selected)
(rx/of (set-modifiers selected)
(apply-modifiers selected)
(calculate-frame-for-move selected)
(fn [state] (-> state
(update :workspace-local dissoc :current-move-selected))))
(->>
(rx/timer 100)
(rx/map (fn [] finish-transform)))))
(update :workspace-local dissoc :modifiers)
(update :workspace-local dissoc :current-move-selected)))
finish-transform)))
(rx/empty))))))

View File

@@ -12,6 +12,7 @@
(:require
[beicon.core :as rx]
[okulary.core :as l]
[app.common.data :as d]
[app.common.pages :as cp]
[app.common.uuid :as uuid]
[app.main.constants :as c]
@@ -109,7 +110,8 @@
(def workspace-file-colors
(l/derived (fn [state]
(when-let [file (:workspace-file state)]
(get-in file [:data :colors])))
(->> (get-in file [:data :colors])
(d/mapm #(assoc %2 :file-id (:id file))))))
st/state))
(def workspace-recent-colors

View File

@@ -61,10 +61,10 @@
[["/auth"
["/login" :auth-login]
["/register" :auth-register]
["/register/success" :auth-register-success]
["/recovery/request" :auth-recovery-request]
["/recovery" :auth-recovery]
["/verify-token" :auth-verify-token]
["/goodbye" :auth-goodbye]]
["/verify-token" :auth-verify-token]]
["/settings"
["/profile" :settings-profile]
@@ -111,7 +111,7 @@
(case (get-in route [:data :name])
(:auth-login
:auth-register
:auth-goodbye
:auth-register-success
:auth-recovery-request
:auth-recovery)
[:& auth {:route route}]
@@ -225,17 +225,17 @@
(ts/schedule
(st/emitf
(dm/show {:content "Unexpected validation error (server side)."
:type :error
:timeout 3000})))
:type :error})))
;; Print to the console some debug info.
(js/console.group "Server Error")
(js/console.info
(with-out-str
(pprint (dissoc error :explain))))
(when-let [explain (:explain error)]
(js/console.error explain))
(js/console.endGroup "Server Error"))
(js/console.group "Validation Error")
(ex/ignoring
(js/console.info
(with-out-str
(pprint (dissoc error :explain))))
(when-let [explain (:explain error)]
(js/console.error explain)))
(js/console.groupEnd "Validation Error"))
;; This is a pure frontend error that can be caused by an active
;; assertion (assertion that is preserved on production builds). From
@@ -244,8 +244,7 @@
[{:keys [data stack message context] :as error}]
(ts/schedule
(st/emitf (dm/show {:content "Internal error: assertion."
:type :error
:timeout 3000})))
:type :error})))
;; Print to the console some debugging info
(js/console.group message)
@@ -268,8 +267,8 @@
(ts/schedule
(st/emitf (dm/show
{:content "Something wrong has happened (on backend)."
:type :error
:timeout 3000})))
:type :error})))
(js/console.group "Internal Server Error:")
(js/console.error "hint:" (or (:hint data) (:message data)))
(js/console.info

View File

@@ -18,7 +18,7 @@
[app.main.ui.auth.login :refer [login-page]]
[app.main.ui.auth.recovery :refer [recovery-page]]
[app.main.ui.auth.recovery-request :refer [recovery-request-page]]
[app.main.ui.auth.register :refer [register-page]]
[app.main.ui.auth.register :refer [register-page register-success-page]]
[app.main.ui.icons :as i]
[app.util.forms :as fm]
[app.util.storage :refer [cache]]
@@ -29,11 +29,6 @@
[cljs.spec.alpha :as s]
[rumext.alpha :as mf]))
(mf/defc goodbye-page
[{:keys [locale] :as props}]
[:div.goodbay
[:h1 (t locale "auth.goodbye-title")]])
(mf/defc auth
[{:keys [route] :as props}]
(let [section (get-in route [:data :name])
@@ -47,9 +42,18 @@
[:section.auth-content
(case section
:auth-register [:& register-page {:locale locale :params params}]
:auth-login [:& login-page {:locale locale :params params}]
:auth-goodbye [:& goodbye-page {:locale locale}]
:auth-recovery-request [:& recovery-request-page {:locale locale}]
:auth-recovery [:& recovery-page {:locale locale
:params (:query-params route)}])]]))
:auth-register
[:& register-page {:locale locale :params params}]
:auth-register-success
[:& register-success-page {:params params}]
:auth-login
[:& login-page {:locale locale :params params}]
:auth-recovery-request
[:& recovery-request-page {:locale locale}]
:auth-recovery
[:& recovery-page {:locale locale
:params (:query-params route)}])]]))

View File

@@ -107,6 +107,7 @@
[:& fm/submit-button
{:label (tr "auth.login-submit")
:on-click on-submit}]
(when cfg/login-with-ldap
[:& fm/submit-button
{:label (tr "auth.login-with-ldap-submit")

View File

@@ -20,49 +20,69 @@
[app.util.router :as rt]
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
[beicon.core :as rx]
[rumext.alpha :as mf]))
(s/def ::email ::us/email)
(s/def ::recovery-request-form (s/keys :req-un [::email]))
(defn- on-success
[]
(st/emit! (dm/info (tr "auth.notifications.recovery-token-sent"))
(rt/nav :auth-login)))
(defn- on-submit
[form event]
(let [params (with-meta (:clean-data @form)
{:on-success on-success})]
(st/emit! (uda/request-profile-recovery params))))
(mf/defc recovery-form
[{:keys [locale] :as props}]
[]
(let [form (fm/use-form :spec ::recovery-request-form
:initial {})]
:initial {})
submitted (mf/use-state false)
on-error
(mf/use-callback
(fn [{:keys [code] :as error}]
(reset! submitted false)
(if (= code :profile-not-verified)
(rx/of (dm/error (tr "auth.notifications.profile-not-verified")
{:timeout nil}))
(rx/throw error))))
on-success
(mf/use-callback
(fn []
(reset! submitted false)
(st/emit! (dm/info (tr "auth.notifications.recovery-token-sent"))
(rt/nav :auth-login))))
on-submit
(mf/use-callback
(fn []
(reset! submitted true)
(->> (with-meta (:clean-data @form)
{:on-success on-success
:on-error on-error})
(uda/request-profile-recovery)
(st/emit!))))]
[:& fm/form {:on-submit on-submit
:form form}
[:div.fields-row
[:& fm/input {:name :email
:label (t locale "auth.email")
:label (tr "auth.email")
:help-icon i/at
:type "text"}]]
[:& fm/submit-button
{:label (t locale "auth.recovery-request-submit")}]]))
{:label (tr "auth.recovery-request-submit")}]]))
;; --- Recovery Request Page
(mf/defc recovery-request-page
[{:keys [locale] :as props}]
[]
[:section.generic-form
[:div.form-container
[:h1 (t locale "auth.recovery-request-title")]
[:div.subtitle (t locale "auth.recovery-request-subtitle")]
[:& recovery-form {:locale locale}]
[:h1 (tr "auth.recovery-request-title")]
[:div.subtitle (tr "auth.recovery-request-subtitle")]
[:& recovery-form]
[:div.links
[:div.link-entry
[:a {:on-click #(st/emit! (rt/nav :auth-login))}
(t locale "auth.go-back-to-login")]]]]])
(tr "auth.go-back-to-login")]]]]])

View File

@@ -18,6 +18,7 @@
[app.main.ui.components.forms :as fm]
[app.main.ui.icons :as i]
[app.main.ui.messages :as msgs]
[app.main.ui.auth.login :as login]
[app.util.dom :as dom]
[app.util.i18n :refer [tr t]]
[app.util.router :as rt]
@@ -32,42 +33,12 @@
{:type :warning
:content (tr "auth.demo-warning")}])
(defn- on-error
[form error]
(case (:code error)
:registration-disabled
(st/emit! (dm/error (tr "errors.registration-disabled")))
:email-already-exists
(swap! form assoc-in [:errors :email]
{:message "errors.email-already-exists"})
(st/emit! (dm/error (tr "errors.unexpected-error")))))
(defn- on-success
[form data]
(if (and (:is-active data) (:claims data))
(let [message (tr "auth.notifications.team-invitation-accepted")]
(st/emit! (rt/nav :dashboard-projects {:team-id (get-in data [:claims :team-id])})
du/fetch-profile
(dm/success message)))
(let [message (tr "notifications.validation-email-sent" (:email data))]
(st/emit! (rt/nav :auth-login)
(dm/success message)))))
(defn- validate
[data]
(let [password (:password data)]
(when (> 8 (count password))
{:password {:message "errors.password-too-short"}})))
(defn- on-submit
[form event]
(let [data (with-meta (:clean-data @form)
{:on-error (partial on-error form)
:on-success (partial on-success form)})]
(st/emit! (da/register data))))
(s/def ::fullname ::us/not-empty-string)
(s/def ::password ::us/not-empty-string)
(s/def ::email ::us/email)
@@ -80,59 +51,125 @@
:opt-un [::token]))
(mf/defc register-form
[{:keys [locale params] :as props}]
[{:keys [params] :as props}]
(let [initial (mf/use-memo (mf/deps params) (constantly params))
form (fm/use-form :spec ::register-form
:validators [validate]
:initial initial)]
:initial initial)
submitted? (mf/use-state false)
on-error
(mf/use-callback
(fn [form error]
(reset! submitted? false)
(case (:code error)
:registration-disabled
(st/emit! (dm/error (tr "errors.registration-disabled")))
:email-already-exists
(swap! form assoc-in [:errors :email]
{:message "errors.email-already-exists"})
(st/emit! (dm/error (tr "errors.unexpected-error"))))))
on-success
(mf/use-callback
(fn [form data]
(reset! submitted? false)
(if (and (:is-active data) (:claims data))
(let [message (tr "auth.notifications.team-invitation-accepted")]
(st/emit! (rt/nav :dashboard-projects {:team-id (get-in data [:claims :team-id])})
du/fetch-profile
(dm/success message)))
(st/emit! (rt/nav :auth-register-success {} {:email (:email data)})))))
on-submit
(mf/use-callback
(fn [form event]
(reset! submitted? true)
(let [data (with-meta (:clean-data @form)
{:on-error (partial on-error form)
:on-success (partial on-success form)})]
(st/emit! (da/register data)))))]
[:& fm/form {:on-submit on-submit
:form form}
[:div.fields-row
[:& fm/input {:name :fullname
:tab-index "1"
:label (t locale "auth.fullname")
:label (tr "auth.fullname")
:type "text"}]]
[:div.fields-row
[:& fm/input {:type "email"
:name :email
:tab-index "2"
:help-icon i/at
:label (t locale "auth.email")}]]
:label (tr "auth.email")}]]
[:div.fields-row
[:& fm/input {:name :password
:tab-index "3"
:hint (t locale "auth.password-length-hint")
:label (t locale "auth.password")
:hint (tr "auth.password-length-hint")
:label (tr "auth.password")
:type "password"}]]
[:& fm/submit-button
{:label (t locale "auth.register-submit")}]]))
{:label (tr "auth.register-submit")
:disabled @submitted?
}]]))
;; --- Register Page
(mf/defc register-page
[{:keys [locale params] :as props}]
(mf/defc register-success-page
[{:keys [params] :as props}]
[:div.form-container
[:h1 (t locale "auth.register-title")]
[:div.subtitle (t locale "auth.register-subtitle")]
[:div.notification-icon i/icon-verify]
[:div.notification-text (tr "auth.verification-email-sent")]
[:div.notification-text-email (:email params "")]
[:div.notification-text (tr "auth.check-your-email")]])
(mf/defc register-page
[{:keys [params] :as props}]
[:div.form-container
[:h1 (tr "auth.register-title")]
[:div.subtitle (tr "auth.register-subtitle")]
(when cfg/demo-warning
[:& demo-warning])
[:& register-form {:locale locale
:params params}]
[:& register-form {:params params}]
[:div.links
[:div.link-entry
[:span (t locale "auth.already-have-account") " "]
[:span (tr "auth.already-have-account") " "]
[:a {:on-click #(st/emit! (rt/nav :auth-login))
:tab-index "4"}
(t locale "auth.login-here")]]
(tr "auth.login-here")]]
(when cfg/allow-demo-users
[:div.link-entry
[:span (t locale "auth.create-demo-profile") " "]
[:span (tr "auth.create-demo-profile") " "]
[:a {:on-click #(st/emit! da/create-demo-profile)
:tab-index "5"}
(t locale "auth.create-demo-account")]])]])
(tr "auth.create-demo-account")]])]
(when cfg/google-client-id
[:a.btn-ocean.btn-large.btn-google-auth
{:on-click login/login-with-google}
"Login with Google"])
(when cfg/gitlab-client-id
[:a.btn-ocean.btn-large.btn-gitlab-auth
{:on-click login/login-with-gitlab}
[:img.logo
{:src "/images/icons/brand-gitlab.svg"}]
(tr "auth.login-with-gitlab-submit")])
(when cfg/github-client-id
[:a.btn-ocean.btn-large.btn-github-auth
{:on-click login/login-with-github}
[:img.logo
{:src "/images/icons/brand-github.svg"}]
(tr "auth.login-with-github-submit")])])

View File

@@ -5,7 +5,7 @@
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
;; Copyright (c) 2020-2021 UXBOX Labs SL
(ns app.main.ui.auth.verify-token
(:require
@@ -21,9 +21,9 @@
[app.main.ui.auth.register :refer [register-page]]
[app.main.ui.icons :as i]
[app.util.forms :as fm]
[app.util.storage :refer [cache]]
[app.util.i18n :as i18n :refer [tr t]]
[app.util.router :as rt]
[app.util.storage :refer [cache]]
[app.util.timers :as ts]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
@@ -35,7 +35,7 @@
[data]
(let [msg (tr "dashboard.notifications.email-verified-successfully")]
(ts/schedule 100 #(st/emit! (dm/success msg)))
(st/emit! (rt/nav :auth-login))))
(st/emit! (da/login-from-token data))))
(defmethod handle-token :change-email
[data]

View File

@@ -122,12 +122,12 @@
i/arrow-slide]]]))
(mf/defc submit-button
[{:keys [label form on-click] :as props}]
[{:keys [label form on-click disabled] :as props}]
(let [form (or form (mf/use-ctx form-ctx))]
[:input.btn-primary.btn-large
{:name "submit"
:class (when-not (:valid @form) "btn-disabled")
:disabled (not (:valid @form))
:disabled (or (not (:valid @form)) (true? disabled))
:on-click on-click
:value label
:type "submit"}]))

View File

@@ -11,6 +11,7 @@
(:require
[app.common.exceptions :as ex]
[app.main.data.viewer :as dv]
[app.main.data.viewer.shortcuts :as sc]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.fullscreen :as fs]
@@ -75,7 +76,7 @@
(let [on-mouse-wheel
(mf/use-callback
(fn [event]
(when (kbd/ctrl? event)
(when (or (kbd/ctrl? event) (kbd/meta? event))
(dom/prevent-default event)
(let [event (.getBrowserEvent ^js event)
delta (+ (.-deltaY ^js event)
@@ -94,7 +95,7 @@
(events/unlistenByKey key1))))]
(mf/use-effect on-mount)
(hooks/use-shortcuts dv/shortcuts)
(hooks/use-shortcuts sc/shortcuts)
[:& fs/fullscreen-wrapper {}
[:div.handoff-layout

View File

@@ -15,6 +15,7 @@
[app.main.store :as st]
[app.main.ui.icons :as i]
[app.main.ui.keyboard :as kbd]
[app.main.ui.keyboard :as kbd]
[app.main.ui.workspace.sidebar.layers :refer [element-icon layer-name frame-wrapper]]
[app.util.dom :as dom]
[okulary.core :as l]
@@ -52,10 +53,10 @@
(dom/prevent-default event)
(let [id (:id item)]
(cond
(.-ctrlKey event)
(or (kbd/ctrl? event) (kbd/meta? event))
(st/emit! (dv/toggle-selection id))
(.-shiftKey event)
(kbd/shift? event)
(st/emit! (dv/shift-select-to id))
:else

View File

@@ -10,19 +10,23 @@
(ns app.main.ui.hooks
"A collection of general purpose react hooks."
(:require
[cljs.spec.alpha :as s]
["mousetrap" :as mousetrap]
[app.common.spec :as us]
[beicon.core :as rx]
[goog.events :as events]
[rumext.alpha :as mf]
[app.util.transit :as t]
[app.main.data.shortcuts :refer [bind-shortcuts]]
[app.util.dom :as dom]
[app.util.dom.dnd :as dnd]
[app.util.webapi :as wapi]
[app.util.logging :as log]
[app.util.timers :as ts]
["mousetrap" :as mousetrap])
[app.util.transit :as t]
[app.util.webapi :as wapi]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[goog.events :as events]
[rumext.alpha :as mf])
(:import goog.events.EventType))
(log/set-level! :warn)
(defn use-rxsub
[ob]
(let [[state reset-state!] (mf/useState @ob)]
@@ -33,20 +37,18 @@
#js [ob])
state))
(s/def ::shortcuts
(s/map-of ::us/string fn?))
(defn use-shortcuts
[shortcuts]
(us/assert ::shortcuts shortcuts)
(mf/use-effect
(fn []
(->> (seq shortcuts)
(run! (fn [[key f]]
(mousetrap/bind key (fn [event]
(js/console.log "[debug]: shortcut:" key)
(.preventDefault event)
(f event))))))
(bind-shortcuts
shortcuts
mousetrap/bind
(fn [key cb]
(fn [event]
(log/debug :msg (str "Shortcut" key))
(.preventDefault event)
(cb event))))
(fn [] (mousetrap/reset))))
nil)

View File

@@ -51,6 +51,7 @@
(def icon-empty (icon-xref :icon-empty))
(def icon-list (icon-xref :icon-list))
(def icon-lock (icon-xref :icon-lock))
(def icon-verify (icon-xref :icon-verify))
(def icon-set (icon-xref :icon-set))
(def image (icon-xref :image))
(def infocard (icon-xref :infocard))

View File

@@ -13,6 +13,10 @@
[event]
(.-ctrlKey event))
(defn ^boolean meta?
[event]
(.-metaKey event))
(defn ^boolean shift?
[event]
(.-shiftKey event))

View File

@@ -58,9 +58,7 @@
:position (or (:position message) :fixed)
:controls (if (some? (:controls message))
(:controls message)
(if (some? (:timeout message))
:none
:close))
:close)
:on-close on-close)])))
(mf/defc inline-banner

View File

@@ -31,7 +31,7 @@
(defn on-success
[x]
(st/emit! (rt/nav :auth-goodbye)))
(st/emit! (rt/nav :auth-login)))
(mf/defc delete-account-modal
{::mf/register modal/components

View File

@@ -22,7 +22,7 @@
(let [shape (unchecked-get props "shape")
base-props (unchecked-get props "base-props")
elem-name (unchecked-get props "elem-name")
;; {:keys [x y width height]} (geom/shape->rect-shape shape)
base-style (obj/get base-props "style")
{:keys [x y width height]} (:selrect shape)
stroke-id (mf/use-var (uuid/next))
stroke-style (:stroke-style shape :none)
@@ -37,19 +37,25 @@
(= stroke-position :inner)
(let [clip-id (str "clip-" @stroke-id)
clip-props (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:stroke nil
clip-props (obj/merge
base-props
#js {:transform nil
:style (obj/merge
base-style
#js {:stroke nil
:strokeWidth nil
:strokeOpacity nil
:strokeDasharray nil
:fill "white"
:fillOpacity 1
:transform nil}))
:fillOpacity 1})})
stroke-width (obj/get base-props "strokeWidth")
shape-props (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:strokeWidth (* stroke-width 2)
:clipPath (str "url('#" clip-id "')")}))]
stroke-width (obj/get base-style "strokeWidth" 0)
shape-props (obj/merge
base-props
#js {:clipPath (str "url('#" clip-id "')")
:style (obj/merge
base-style
#js {:strokeWidth (* stroke-width 2)})})]
[:*
[:> "clipPath" #js {:id clip-id}
[:> elem-name clip-props]]
@@ -63,34 +69,46 @@
(= stroke-position :outer)
(let [stroke-mask-id (str "mask-" @stroke-id)
stroke-width (or (.-strokeWidth ^js base-props) 0)
mask-props1 (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:stroke "white"
stroke-width (obj/get base-style "strokeWidth" 0)
mask-props1 (obj/merge
base-props
#js {:transform nil
:style (obj/merge
base-style
#js {:stroke "white"
:strokeWidth (* stroke-width 2)
:strokeOpacity 1
:strokeDasharray nil
:fill "white"
:fillOpacity 1
:transform nil}))
mask-props2 (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:stroke nil
:fillOpacity 1})})
mask-props2 (obj/merge
base-props
#js {:transform nil
:style (obj/merge
base-style
#js {:stroke nil
:strokeWidth nil
:strokeOpacity nil
:strokeDasharray nil
:fill "black"
:fillOpacity 1
:transform nil}))
:fillOpacity 1})})
shape-props1 (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:stroke nil
shape-props1 (obj/merge
base-props
#js {:style (obj/merge
base-style
#js {:stroke nil
:strokeWidth nil
:strokeOpacity nil
:strokeDasharray nil}))
shape-props2 (-> (obj/merge! #js {} base-props)
(obj/merge! #js {:strokeWidth (* stroke-width 2)
:strokeDasharray nil})})
shape-props2 (obj/merge
base-props
#js {:mask (str "url('#" stroke-mask-id "')")
:style (obj/merge
base-style
#js {:strokeWidth (* stroke-width 2)
:fill "none"
:fillOpacity 0
:mask (str "url('#" stroke-mask-id "')")}))]
:fillOpacity 0})})]
[:*
[:mask {:id stroke-mask-id}
[:> elem-name mask-props1]

View File

@@ -13,6 +13,7 @@
[app.config :as cfg]
[app.common.geom.shapes :as geom]
[app.main.ui.shapes.attrs :as attrs]
[app.util.dom :as dom]
[app.util.object :as obj]
[app.main.ui.context :as muc]
[app.main.data.fetch :as df]
@@ -43,7 +44,11 @@
:transform transform
:width width
:height height
:preserveAspectRatio "none"}))]
:preserveAspectRatio "none"}))
on-drag-start (fn [event]
;; Prevent browser dragging of the image
(dom/prevent-default event))]
(if (nil? @data-uri)
[:> "rect" (obj/merge!
props
@@ -51,4 +56,5 @@
:stroke "#000000"})]
[:> "image" (obj/merge!
props
#js {:xlinkHref @data-uri})]))))
#js {:xlinkHref @data-uri
:onDragStart on-drag-start})]))))

View File

@@ -16,6 +16,7 @@
[app.common.geom.shapes :as geom]
[app.common.pages :as cp]
[app.main.data.viewer :as dv]
[app.main.data.viewer.shortcuts :as sc]
[app.main.data.comments :as dcm]
[app.main.refs :as refs]
[app.main.store :as st]
@@ -214,7 +215,7 @@
on-mouse-wheel
(fn [event]
(when (kbd/ctrl? event)
(when (or (kbd/ctrl? event) (kbd/meta? event))
(dom/prevent-default event)
(let [event (.getBrowserEvent ^js event)
delta (+ (.-deltaY ^js event) (.-deltaX ^js event))]
@@ -240,7 +241,7 @@
(events/unlistenByKey key3))))]
(mf/use-effect on-mount)
(hooks/use-shortcuts dv/shortcuts)
(hooks/use-shortcuts sc/shortcuts)
[:& fs/fullscreen-wrapper {}
[:div.viewer-layout

View File

@@ -11,9 +11,10 @@
(:require
[app.common.math :as mth]
[app.common.uuid :as uuid]
[app.main.data.comments :as dcm]
[app.main.data.messages :as dm]
[app.main.data.viewer :as dv]
[app.main.data.comments :as dcm]
[app.main.data.viewer.shortcuts :as sc]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
@@ -44,15 +45,15 @@
:on-close #(reset! show-dropdown? false)}
[:ul.dropdown.zoom-dropdown
[:li {:on-click on-increase}
"Zoom in" [:span "+"]]
"Zoom in" [:span (sc/get-tooltip :increase-zoom)]]
[:li {:on-click on-decrease}
"Zoom out" [:span "-"]]
"Zoom out" [:span (sc/get-tooltip :decrease-zoom)]]
[:li {:on-click on-zoom-to-50}
"Zoom to 50%" [:span "Shift + 0"]]
"Zoom to 50%" [:span (sc/get-tooltip :zoom-50)]]
[:li {:on-click on-zoom-to-100}
"Zoom to 100%" [:span "Shift + 1"]]
"Zoom to 100%" [:span (sc/get-tooltip :reset-zoom)]]
[:li {:on-click on-zoom-to-200}
"Zoom to 200%" [:span "Shift + 2"]]]]]))
"Zoom to 200%" [:span (sc/get-tooltip :zoom-200)]]]]]))
(mf/defc share-link
[{:keys [page token] :as props}]
@@ -72,7 +73,7 @@
(wapi/write-to-clipboard link)
(st/emit! (dm/show {:type :info
:content "Link copied successfuly!"
:timeout 2000})))]
:timeout 3000})))]
[:*
[:span.btn-primary.btn-small
{:alt (t locale "viewer.header.share.title")
@@ -272,7 +273,7 @@
:on-zoom-to-100 (st/emitf dv/reset-zoom)
:on-zoom-to-200 (st/emitf dv/zoom-to-200)}]
[:span.btn-icon-dark.btn-small.tooltip.tooltip-bottom
[:span.btn-icon-dark.btn-small.tooltip.tooltip-bottom-left
{:alt (t locale "viewer.header.fullscreen")
:on-click #(if @fullscreen (fullscreen false) (fullscreen true))}
(if @fullscreen

View File

@@ -12,15 +12,16 @@
[app.common.geom.point :as gpt]
[app.main.constants :as c]
[app.main.data.history :as udh]
[app.main.data.workspace :as dw]
[app.main.data.messages :as dm]
[app.main.data.workspace :as dw]
[app.main.data.workspace.shortcuts :as sc]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.streams :as ms]
[app.main.ui.context :as ctx]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i]
[app.main.ui.keyboard :as kbd]
[app.main.ui.context :as ctx]
[app.main.ui.workspace.colorpalette :refer [colorpalette]]
[app.main.ui.workspace.colorpicker]
[app.main.ui.workspace.context-menu :refer [context-menu]]
@@ -30,8 +31,8 @@
[app.main.ui.workspace.rules :refer [horizontal-rule vertical-rule]]
[app.main.ui.workspace.sidebar :refer [left-sidebar right-sidebar]]
[app.main.ui.workspace.viewport :refer [viewport viewport-actions coordinates]]
[app.util.object :as obj]
[app.util.dom :as dom]
[app.util.object :as obj]
[beicon.core :as rx]
[cuerdas.core :as str]
[okulary.core :as l]
@@ -45,7 +46,8 @@
[props]
(let [zoom (or (obj/get props "zoom") 1)
vbox (obj/get props "vbox")
vport (obj/get props "vport")]
vport (obj/get props "vport")
colorpalette? (obj/get props "colorpalette?")]
[:*
[:div.empty-rule-square]
@@ -55,7 +57,7 @@
[:& vertical-rule {:zoom zoom
:vbox vbox
:vport vport}]
[:& coordinates]]))
[:& coordinates {:colorpalette? colorpalette?}]]))
(mf/defc workspace-content
{::mf/wrap-props false}
@@ -75,7 +77,8 @@
(when (contains? layout :rules)
[:& workspace-rules {:zoom zoom
:vbox vbox
:vport vport}])
:vport vport
:colorpalette? (contains? layout :colorpalette)}])
[:& viewport-actions]
[:& viewport {:file file
@@ -128,7 +131,7 @@
;; Close any non-modal dialog that may be still open
(st/emitf dm/hide)))
(hooks/use-shortcuts dw/shortcuts)
(hooks/use-shortcuts sc/shortcuts)
(let [file (mf/deref refs/workspace-file)
project (mf/deref refs/workspace-project)

View File

@@ -19,7 +19,7 @@
[app.main.ui.context :as ctx]
[app.main.ui.icons :as i]
[app.main.ui.keyboard :as kbd]
[app.util.color :refer [hex->rgb]]
[app.util.color :as uc]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [t]]
[app.util.object :as obj]
@@ -55,8 +55,8 @@
(fn [event]
(let [ids (get-in @st/state [:workspace-local :selected])]
(if (kbd/shift? event)
(st/emit! (mdc/change-stroke ids color))
(st/emit! (mdc/change-fill ids color)))))]
(st/emit! (mdc/change-stroke ids (merge uc/empty-color color)))
(st/emit! (mdc/change-fill ids (merge uc/empty-color color))))))]
[:div.color-cell {:class (str "cell-"(name size))
:on-click select-color}

View File

@@ -32,9 +32,13 @@
[app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector]]
[app.main.ui.workspace.colorpicker.color-inputs :refer [color-inputs]]))
(def selected-palette-ref
(-> (l/in [:workspace-local :selected-palette-colorpicker])
(l/derived st/state)))
(mf/defc libraries [{:keys [current-color on-select-color on-add-library-color
disable-gradient disable-opacity]}]
(let [selected-library (mf/use-state "recent")
(let [selected-library (or (mf/deref selected-palette-ref) :recent)
current-library-colors (mf/use-state [])
shared-libs (mf/deref refs/workspace-libraries)
@@ -43,10 +47,10 @@
locale (mf/deref i18n/locale)
parse-selected
(fn [selected]
(if (#{"recent" "file"} selected)
(keyword selected)
(uuid selected)) )
(fn [selected-str]
(if (#{"recent" "file"} selected-str)
(keyword selected-str)
(uuid selected-str)))
check-valid-color? (fn [color]
(and (or (not disable-gradient) (not (:gradient color)))
@@ -54,37 +58,36 @@
;; Load library colors when the select is changed
(mf/use-effect
(mf/deps @selected-library)
(mf/deps selected-library)
(fn []
(let [mapped-colors
(cond
(= @selected-library "recent")
(= selected-library :recent)
;; The `map?` check is to keep backwards compatibility. We transform from string to map
(map #(if (map? %) % (hash-map :color %)) (reverse (or recent-colors [])))
(= @selected-library "file")
(= selected-library :file)
(vals file-colors)
:else ;; Library UUID
(->> (get-in shared-libs [(uuid @selected-library) :data :colors])
(->> (get-in shared-libs [selected-library :data :colors])
(vals)
(map #(merge % {:file-id (uuid @selected-library)}))))]
(map #(merge % {:file-id selected-library}))))]
(reset! current-library-colors (into [] (filter check-valid-color?) mapped-colors)))))
;; If the file colors change and the file option is selected updates the state
(mf/use-effect
(mf/deps file-colors)
(fn [] (when (= @selected-library "file")
(fn [] (when (= selected-library :file)
(let [colors (vals file-colors)]
(reset! current-library-colors (into [] (filter check-valid-color?) colors))))))
[:div.libraries
[:select {:on-change (fn [e]
(when-let [val (dom/get-target-val e)]
(reset! selected-library val)))
:value @selected-library}
(when-let [val (parse-selected (dom/get-target-val e))]
(st/emit! (dc/change-palette-selected-colorpicker val))))
:value (name selected-library)}
[:option {:value "recent"} (t locale "workspace.libraries.colors.recent-colors")]
[:option {:value "file"} (t locale "workspace.libraries.colors.file-library")]
@@ -93,13 +96,13 @@
:value id} name])]
[:div.selected-colors
(when (= "file" @selected-library)
(when (= selected-library :file)
[:div.color-bullet.button.plus-button {:style {:background-color "white"}
:on-click on-add-library-color}
i/plus])
[:div.color-bullet.button {:style {:background-color "white"}
:on-click #(st/emit! (dc/show-palette (parse-selected @selected-library)))}
:on-click #(st/emit! (dc/show-palette selected-library))}
i/palette]
(for [[idx color] (map-indexed vector @current-library-colors)]

View File

@@ -14,11 +14,12 @@
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.shortcuts :as sc]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.streams :as ms]
[app.main.ui.context :as ctx]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.context :as ctx]
[app.main.ui.hooks :refer [use-rxsub]]
[app.main.ui.icons :as i]
[app.util.dom :as dom]
@@ -99,52 +100,52 @@
(:component-file shape)))]
[:*
[:& menu-entry {:title (t locale "workspace.shape.menu.copy")
:shortcut "Ctrl + c"
:shortcut (sc/get-tooltip :copy)
:on-click do-copy}]
[:& menu-entry {:title (t locale "workspace.shape.menu.cut")
:shortcut "Ctrl + x"
:shortcut (sc/get-tooltip :cut)
:on-click do-cut}]
[:& menu-entry {:title (t locale "workspace.shape.menu.paste")
:shortcut "Ctrl + v"
:shortcut (sc/get-tooltip :paste)
:on-click do-paste}]
[:& menu-entry {:title (t locale "workspace.shape.menu.duplicate")
:shortcut "Ctrl + d"
:shortcut (sc/get-tooltip :duplicate)
:on-click do-duplicate}]
[:& menu-separator]
[:& menu-entry {:title (t locale "workspace.shape.menu.forward")
:shortcut "Ctrl + ↑"
:shortcut (sc/get-tooltip :bring-forward)
:on-click do-bring-forward}]
[:& menu-entry {:title (t locale "workspace.shape.menu.front")
:shortcut "Ctrl + Shift + ↑"
:shortcut (sc/get-tooltip :bring-front)
:on-click do-bring-to-front}]
[:& menu-entry {:title (t locale "workspace.shape.menu.backward")
:shortcut "Ctrl + ↓"
:shortcut (sc/get-tooltip :bring-backward)
:on-click do-send-backward}]
[:& menu-entry {:title (t locale "workspace.shape.menu.back")
:shortcut "Ctrl + Shift + ↓"
:shortcut (sc/get-tooltip :bring-back)
:on-click do-send-to-back}]
[:& menu-separator]
(when (> (count selected) 1)
[:*
[:& menu-entry {:title (t locale "workspace.shape.menu.group")
:shortcut "Ctrl + g"
:shortcut (sc/get-tooltip :group)
:on-click do-create-group}]
[:& menu-entry {:title (t locale "workspace.shape.menu.mask")
:shortcut "Ctrl + M"
:shortcut (sc/get-tooltip :mask)
:on-click do-mask-group}]])
(when (and (= (count selected) 1) (= (:type shape) :group))
[:*
[:& menu-entry {:title (t locale "workspace.shape.menu.ungroup")
:shortcut "Shift + g"
:shortcut (sc/get-tooltip :ungroup)
:on-click do-remove-group}]
(if (:masked-group? shape)
[:& menu-entry {:title (t locale "workspace.shape.menu.unmask")
:shortcut "Shift + M"
:shortcut (sc/get-tooltip :unmask)
:on-click do-unmask-group}]
[:& menu-entry {:title "Mask"
:shortcut "Ctrl + M"
[:& menu-entry {:title (t locale "workspace.shape.menu.mask")
:shortcut (sc/get-tooltip :group)
:on-click do-mask-group}])])
(if (:hidden shape)
@@ -165,7 +166,7 @@
[:*
[:& menu-separator]
[:& menu-entry {:title (t locale "workspace.shape.menu.create-component")
:shortcut "Ctrl + K"
:shortcut (sc/get-tooltip :create-component)
:on-click do-add-component}]])
(when (and (:component-id shape)
@@ -197,7 +198,7 @@
[:& menu-separator]
[:& menu-entry {:title (t locale "workspace.shape.menu.delete")
:shortcut "Supr"
:shortcut (sc/get-tooltip :delete)
:on-click do-delete}]]))
(mf/defc viewport-context-menu
@@ -206,7 +207,7 @@
do-paste (st/emitf dw/paste)]
[:*
[:& menu-entry {:title (t locale "workspace.shape.menu.paste")
:shortcut "Ctrl + v"
:shortcut (sc/get-tooltip :paste)
:on-click do-paste}]]))
(mf/defc context-menu

View File

@@ -52,7 +52,7 @@
drawing? @refs/selected-drawing-tool
button (.-which (.-nativeEvent event))
shift? (kbd/shift? event)
ctrl? (kbd/ctrl? event)
ctrl? (or (kbd/ctrl? event) (kbd/meta? event))
allow-click? (and (not blocked)
(not drawing?)

View File

@@ -13,6 +13,8 @@
[app.config :as cfg]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.shortcuts :as sc]
[app.main.data.workspace.shortcuts :as sc]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
@@ -73,15 +75,15 @@
:on-close #(reset! show-dropdown? false)}
[:ul.zoom-dropdown
[:li {:on-click on-increase}
"Zoom in" [:span "+"]]
"Zoom in" [:span (sc/get-tooltip :increase-zoom)]]
[:li {:on-click on-decrease}
"Zoom out" [:span "-"]]
"Zoom out" [:span (sc/get-tooltip :decrease-zoom)]]
[:li {:on-click on-zoom-reset}
"Zoom to 100%" [:span "Shift + 0"]]
"Zoom to 100%" [:span (sc/get-tooltip :reset-zoom)]]
[:li {:on-click on-zoom-fit}
"Zoom to fit all" [:span "Shift + 1"]]
"Zoom to fit all" [:span (sc/get-tooltip :fit-all)]]
[:li {:on-click on-zoom-selected}
"Zoom to selected" [:span "Shift + 2"]]]]]))
"Zoom to selected" [:span (sc/get-tooltip :zoom-selected)]]]]]))
;; --- Header Users
@@ -146,8 +148,8 @@
[:div.btn-icon-dark.btn-small {:on-click #(reset! show-menu? true)} i/actions]
[:div.project-tree {:alt (tr "workspace.sitemap")}
[:span.project-name
{:on-click #(st/emit! (rt/navigate :dashboard-project {:team-id team-id
:project-id (:project-id file)}))}
{:on-click #(st/emit! (rt/navigate :dashboard-files {:team-id team-id
:project-id (:project-id file)}))}
(:name project) " /"]
(if @editing?
[:input.file-name
@@ -171,52 +173,53 @@
(if (contains? layout :rules)
(tr "workspace.header.menu.hide-rules")
(tr "workspace.header.menu.show-rules"))]
[:span.shortcut "Ctrl+shift+R"]]
[:span.shortcut (sc/get-tooltip :toggle-rules)]]
[:li {:on-click #(st/emit! (dw/toggle-layout-flags :display-grid))}
[:span
(if (contains? layout :display-grid)
(tr "workspace.header.menu.hide-grid")
(tr "workspace.header.menu.show-grid"))]
[:span.shortcut "Ctrl+'"]]
[:span.shortcut (sc/get-tooltip :toggle-grid)]]
[:li {:on-click #(st/emit! (dw/toggle-layout-flags :snap-grid))}
[:span
(if (contains? layout :snap-grid)
(tr "workspace.header.menu.disable-snap-grid")
(tr "workspace.header.menu.enable-snap-grid"))]
[:span.shortcut "Ctrl+Shift+'"]]
[:span.shortcut (sc/get-tooltip :toggle-snap-grid)]]
[:li {:on-click #(st/emit! (dw/toggle-layout-flags :sitemap :layers))}
[:span
(if (or (contains? layout :sitemap) (contains? layout :layers))
(tr "workspace.header.menu.hide-layers")
(tr "workspace.header.menu.show-layers"))]
[:span.shortcut "Ctrl+l"]]
[:span.shortcut (sc/get-tooltip :toggle-layers)]]
[:li {:on-click #(st/emit! (dw/toggle-layout-flags :colorpalette))}
[:span
(if (contains? layout :colorpalette)
(tr "workspace.header.menu.hide-palette")
(tr "workspace.header.menu.show-palette"))]
[:span.shortcut "Ctrl+p"]]
[:span.shortcut (sc/get-tooltip :toggle-palette)]]
[:li {:on-click #(st/emit! (dw/toggle-layout-flags :assets))}
[:span
(if (contains? layout :assets)
(tr "workspace.header.menu.hide-assets")
(tr "workspace.header.menu.show-assets"))]
[:span.shortcut "Ctrl+i"]]
[:span.shortcut (sc/get-tooltip :toggle-assets)]]
[:li {:on-click #(st/emit! (dw/select-all))}
[:span (tr "workspace.header.menu.select-all")]
[:span.shortcut "Ctrl+a"]]
[:span.shortcut (sc/get-tooltip :select-all)]]
[:li {:on-click #(st/emit! (dw/toggle-layout-flags :dynamic-alignment))}
[:span
(if (contains? layout :dynamic-alignment)
(tr "workspace.header.menu.disable-dynamic-alignment")
(tr "workspace.header.menu.enable-dynamic-alignment"))]]
(tr "workspace.header.menu.enable-dynamic-alignment"))]
[:span.shortcut (sc/get-tooltip :toggle-alignment)]]
(if (:is-shared file)
[:li {:on-click on-remove-shared}
@@ -272,8 +275,8 @@
:on-zoom-fit #(st/emit! dw/zoom-to-fit-all)
:on-zoom-selected #(st/emit! dw/zoom-to-selected-shape)}]
[:a.btn-icon-dark.btn-small.tooltip.tooltip-bottom
{:alt (tr "workspace.header.viewer")
[:a.btn-icon-dark.btn-small.tooltip.tooltip-bottom-left
{:alt (tr "workspace.header.viewer" (sc/get-tooltip :open-viewer))
:href (str "#" view-url)
:on-click go-viewer}
i/play]]]))

View File

@@ -10,8 +10,10 @@
(ns app.main.ui.workspace.left-toolbar
(:require
[app.common.geom.point :as gpt]
[app.common.math :as mth]
[app.common.media :as cm]
[app.main.data.workspace :as dw]
[app.main.data.workspace.shortcuts :as sc]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.file-uploader :refer [file-uploader]]
@@ -35,8 +37,13 @@
(mf/deps file)
(fn [blobs]
(let [params {:file-id (:id file)
:data (seq blobs)}]
(st/emit! (dw/upload-media-workspace params (gpt/point 0 0))))))]
:data (seq blobs)}
;; We don't want to add a ref because that redraws the component
;; for everychange. Better direct access on the callback
vbox (get-in @st/state [:workspace-local :vbox])
x (mth/round (+ (:x vbox) (/ (:width vbox) 2)))
y (mth/round (+ (:y vbox) (/ (:height vbox) 2)))]
(st/emit! (dw/upload-media-workspace params (gpt/point x y))))))]
[:li.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.image")
@@ -67,22 +74,22 @@
:on-click (st/emitf :interrupt)}
i/pointer-inner]
[:li.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.frame")
{:alt (tr "workspace.toolbar.frame" (sc/get-tooltip :draw-frame))
:class (when (= selected-drawtool :frame) "selected")
:on-click (partial select-drawtool :frame)}
i/artboard]
[:li.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.rect")
{:alt (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect))
:class (when (= selected-drawtool :rect) "selected")
:on-click (partial select-drawtool :rect)}
i/box]
[:li.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.ellipse")
{:alt (tr "workspace.toolbar.ellipse" (sc/get-tooltip :draw-ellipse))
:class (when (= selected-drawtool :circle) "selected")
:on-click (partial select-drawtool :circle)}
i/circle]
[:li.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.text")
{:alt (tr "workspace.toolbar.text" (sc/get-tooltip :draw-text))
:class (when (= selected-drawtool :text) "selected")
:on-click (partial select-drawtool :text)}
i/text]
@@ -90,40 +97,40 @@
[:& image-upload]
[:li.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.curve")
{:alt (tr "workspace.toolbar.curve" (sc/get-tooltip :draw-curve))
:class (when (= selected-drawtool :curve) "selected")
:on-click (partial select-drawtool :curve)}
i/pencil]
[:li.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.path")
{:alt (tr "workspace.toolbar.path" (sc/get-tooltip :draw-path))
:class (when (= selected-drawtool :path) "selected")
:on-click (partial select-drawtool :path)}
i/pen]
[:li.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.comments")
{:alt (tr "workspace.toolbar.comments" (sc/get-tooltip :add-comment))
:class (when (= selected-drawtool :comments) "selected")
:on-click (partial select-drawtool :comments)}
i/chat]]
[:ul.left-toolbar-options.panels
[:li.tooltip.tooltip-right
{:alt "Layers"
{:alt (tr "workspace.sidebar.layers" (sc/get-tooltip :toggle-layers))
:class (when (contains? layout :layers) "selected")
:on-click (st/emitf (dw/go-to-layout :layers))}
i/layers]
[:li.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.assets")
{:alt (tr "workspace.toolbar.assets" (sc/get-tooltip :toggle-assets))
:class (when (contains? layout :assets) "selected")
:on-click (st/emitf (dw/go-to-layout :assets))}
i/library]
[:li.tooltip.tooltip-right
{:alt "History"
{:alt (tr "workspace.sidebar.history" (sc/get-tooltip :toggle-history))
:class (when (contains? layout :document-history) "selected")
:on-click (st/emitf (dw/go-to-layout :document-history))}
i/undo-history]
[:li.tooltip.tooltip-right
{:alt (tr "workspace.toolbar.color-palette")
{:alt (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-palette))
:class (when (contains? layout :colorpalette) "selected")
:on-click (st/emitf (dw/toggle-layout-flags :colorpalette))}
i/palette]]]]))

View File

@@ -176,7 +176,7 @@
:y cy'
:width resize-point-circle-radius
:height resize-point-circle-radius
:transform (str/fmt "rotate(%s, %s, %s)" rotation cx' cy')
:transform (when rotation (str/fmt "rotate(%s, %s, %s)" rotation cx' cy'))
:style {:fill (if (debug? :resize-handler) "red" "transparent")
:cursor cursor}
:on-mouse-down #(on-resize {:x cx' :y cy'} %)}])

View File

@@ -23,6 +23,7 @@
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.selection :as dws]
[app.main.ui.cursors :as cur]
[app.main.ui.shapes.text.styles :as sts])
(:import
@@ -163,7 +164,8 @@
(fn []
(st/emit! dw/clear-edition-mode)
(when (= 0 (content-size @content-var))
(st/emit! (dw/delete-shapes [id]))))
(st/emit! (dws/deselect-shape id)
(dw/delete-shapes [id]))))
on-click-outside
(fn [event]
@@ -202,7 +204,8 @@
on-mount
(fn []
(let [keys [(events/listen js/document EventType.CLICK on-click-outside)
(let [keys [(events/listen js/document EventType.MOUSEDOWN on-click-outside)
(events/listen js/document EventType.CLICK on-click-outside)
(events/listen js/document EventType.KEYUP on-key-up)]]
(st/emit! (dwt/assign-editor id editor)
(dwc/start-undo-transaction))

View File

@@ -45,7 +45,7 @@
[rumext.alpha :as mf]))
(mf/defc components-box
[{:keys [file-id local? components open? on-open on-close] :as props}]
[{:keys [file-id local? components open?] :as props}]
(let [state (mf/use-state {:menu-open false
:renaming nil
:top nil
@@ -106,7 +106,8 @@
[:div.asset-group
[:div.group-title {:class (when (not open?) "closed")}
[:span {:on-click #(if open? (on-close) (on-open))} i/arrow-slide (tr "workspace.assets.components")]
[:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :components (not open?)))}
i/arrow-slide (tr "workspace.assets.components")]
[:span (str "\u00A0(") (count components) ")"]] ;; Unicode 00A0 is non-breaking space
(when open?
[:div.group-grid.big
@@ -140,7 +141,7 @@
[(tr "workspace.assets.delete") on-delete]]}])]))
(mf/defc graphics-box
[{:keys [file-id local? objects open? on-open on-close] :as props}]
[{:keys [file-id local? objects open?] :as props}]
(let [input-ref (mf/use-ref nil)
state (mf/use-state {:menu-open false
:renaming nil
@@ -151,7 +152,7 @@
add-graphic
(mf/use-callback
(fn []
(on-open)
(st/emitf (dwl/set-assets-box-open file-id :graphics true))
(dom/click (mf/ref-val input-ref))))
on-media-uploaded
@@ -218,7 +219,8 @@
[:div.asset-group
[:div.group-title {:class (when (not open?) "closed")}
[:span {:on-click #(if open? (on-close) (on-open))} i/arrow-slide (tr "workspace.assets.graphics")]
[:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :graphics (not open?)))}
i/arrow-slide (tr "workspace.assets.graphics")]
[:span.num-assets (str "\u00A0(") (count objects) ")"] ;; Unicode 00A0 is non-breaking space
(when local?
[:div.group-button {:on-click add-graphic}
@@ -375,7 +377,7 @@
[(t locale "workspace.assets.delete") delete-color]]}])]))
(mf/defc colors-box
[{:keys [file-id local? colors locale open? on-open on-close] :as props}]
[{:keys [file-id local? colors locale open?] :as props}]
(let [add-color
(mf/use-callback
(mf/deps file-id)
@@ -386,7 +388,7 @@
(mf/use-callback
(mf/deps file-id)
(fn [event]
(on-open)
(st/emitf (dwl/set-assets-box-open file-id :colors true))
(modal/show! :colorpicker
{:x (.-clientX event)
:y (.-clientY event)
@@ -396,7 +398,8 @@
:position :right})))]
[:div.asset-group
[:div.group-title {:class (when (not open?) "closed")}
[:span {:on-click #(if open? (on-close) (on-open))} i/arrow-slide (t locale "workspace.assets.colors")]
[:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :colors (not open?)))}
i/arrow-slide (t locale "workspace.assets.colors")]
[:span.num-assets (str "\u00A0(") (count colors) ")"] ;; Unicode 00A0 is non-breaking space
(when local?
[:div.group-button {:on-click add-color-clicked} i/plus])]
@@ -414,7 +417,7 @@
:locale locale}]))])]))
(mf/defc typography-box
[{:keys [file file-id local? typographies locale open? on-open on-close] :as props}]
[{:keys [file file-id local? typographies locale open?] :as props}]
(let [state (mf/use-state {:detail-open? false
:menu-open? false
@@ -488,7 +491,8 @@
[:div.asset-group
[:div.group-title {:class (when (not open?) "closed")}
[:span {:on-click #(if open? (on-close) (on-open))} i/arrow-slide (t locale "workspace.assets.typography")]
[:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :typography (not open?)))}
i/arrow-slide (t locale "workspace.assets.typography")]
[:span.num-assets (str "\u00A0(") (count typographies) ")"] ;; Unicode 00A0 is non-breaking space
(when local?
[:div.group-button {:on-click add-typography} i/plus])]
@@ -553,6 +557,11 @@
(vals (get-in state [:workspace-libraries id :data :typographies])))))
st/state =))
(defn open-file-ref
[id]
(-> (l/in [:assets-files-open id])
(l/derived refs/workspace-local)))
(defn apply-filters
[coll filters]
(->> coll
@@ -562,16 +571,18 @@
(sort-by #(str/lower (:name %)))))
(mf/defc file-library
[{:keys [file local? open? filters locale] :as props}]
(let [open? (mf/use-state open?)
[{:keys [file local? default-open? filters locale] :as props}]
(let [open-file (mf/deref (open-file-ref (:id file)))
open? (-> open-file
:library
(d/nilv default-open?))
open-box? (fn [box]
(-> open-file
box
(d/nilv true)))
shared? (:is-shared file)
router (mf/deref refs/router)
toggle-open #(swap! open? not)
toggles (mf/use-state #{:components
:graphics
:colors
:typographies})
toggle-open (st/emitf (dwl/set-assets-box-open (:id file) :library (not open?)))
url (rt/resolve router :workspace
{:project-id (:project-id file)
@@ -594,7 +605,7 @@
[:div.tool-window-bar.library-bar
{:on-click toggle-open}
[:div.collapse-library
{:class (dom/classnames :open @open?)}
{:class (dom/classnames :open open?)}
i/arrow-slide]
(if local?
@@ -610,7 +621,7 @@
:on-click dom/stop-propagation}
i/chain]]])]
(when @open?
(when open?
(let [show-components? (and (or (= (:box filters) :all)
(= (:box filters) :components))
(or (> (count components) 0)
@@ -632,24 +643,18 @@
[:& components-box {:file-id (:id file)
:local? local?
:components components
:open? (contains? @toggles :components)
:on-open #(swap! toggles conj :components)
:on-close #(swap! toggles disj :components)}])
:open? (open-box? :components)}])
(when show-graphics?
[:& graphics-box {:file-id (:id file)
:local? local?
:objects media
:open? (contains? @toggles :graphics)
:on-open #(swap! toggles conj :graphics)
:on-close #(swap! toggles disj :graphics)}])
:open? (open-box? :graphics)}])
(when show-colors?
[:& colors-box {:file-id (:id file)
:local? local?
:locale locale
:colors colors
:open? (contains? @toggles :colors)
:on-open #(swap! toggles conj :colors)
:on-close #(swap! toggles disj :colors)}])
:open? (open-box? :colors)}])
(when show-typography?
[:& typography-box {:file file
@@ -657,9 +662,7 @@
:local? local?
:locale locale
:typographies typographies
:open? (contains? @toggles :typographies)
:on-open #(swap! toggles conj :typographies)
:on-close #(swap! toggles disj :typographies)}])
:open? (open-box? :typographies)}])
(when (and (not show-components?) (not show-graphics?) (not show-colors?))
[:div.asset-group
@@ -733,7 +736,7 @@
{:file file
:locale locale
:local? true
:open? true
:default-open? true
:filters @filters}]
(for [file (->> libraries
@@ -743,6 +746,6 @@
:file file
:local? false
:locale locale
:open? false
:default-open? false
:filters @filters}])]]))

View File

@@ -19,6 +19,7 @@
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i]
[app.main.ui.keyboard :as kbd]
[app.main.ui.keyboard :as kbd]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [t]]
[app.util.object :as obj]
@@ -140,10 +141,10 @@
(:hidden item))
nil
(.-shiftKey event)
(kbd/shift? event)
(st/emit! (dw/shift-select-shapes id))
(.-ctrlKey event)
(or (kbd/ctrl? event) (kbd/meta? event))
(st/emit! (dw/select-shape id true))
(> (count selected) 1)

View File

@@ -47,7 +47,12 @@
handle-change
(fn [value]
(change! #(assoc-in % [:blur :value] value)))
(change! #(cond-> %
(not (contains? % :blur))
(assoc :blur (create-blur))
:always
(assoc-in [:blur :value] value))))
handle-toggle-visibility
(fn []

View File

@@ -9,17 +9,18 @@
(ns app.main.ui.workspace.sidebar.options.fill
(:require
[rumext.alpha :as mf]
[app.common.pages :as cp]
[app.main.data.colors :as dc]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.texts :as dwt]
[app.main.data.colors :as dc]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]
[app.util.color :as uc]
[app.util.i18n :as i18n :refer [tr t]]
[app.util.object :as obj]))
[app.util.object :as obj]
[rumext.alpha :as mf]))
(def fill-attrs
[:fill-color
@@ -57,19 +58,24 @@
(mf/use-callback
(mf/deps ids)
(fn [event]
(st/emit! (dc/change-fill ids nil))))
(st/emit! (dc/change-fill ids (into {} uc/empty-color)))))
on-change
(mf/use-callback
(mf/deps ids)
(fn [color]
(st/emit! (dc/change-fill ids color))))
(let [remove-multiple (fn [[key value]] (not= value :multiple))
color (into {} (filter remove-multiple) color)]
(st/emit! (dc/change-fill ids color)))))
on-detach
(mf/use-callback
(mf/deps ids)
(fn []
(st/emit! (dc/change-fill ids (dissoc color :id :file-id)))))
(let [remove-multiple (fn [[key value]] (not= value :multiple))
color (-> (into {} (filter remove-multiple) color)
(assoc :id nil :file-id nil))]
(st/emit! (dc/change-fill ids color)))))
on-open-picker
(mf/use-callback

View File

@@ -123,6 +123,9 @@
[v]
(when v (select-keys v blur-keys)))
(defn empty-map [keys]
(into {} (map #(hash-map % nil)) keys))
(defn get-attrs
"Given a `type` of options that we want to extract and the shapes to extract them from
returns a list of tuples [id, values] with the extracted properties for the shapes that
@@ -142,7 +145,9 @@
result (case props
:ignore [ids values]
:shape [(conj ids id)
(merge-attrs values (select-keys shape attrs))]
(merge-attrs values (merge
(empty-map attrs)
(select-keys shape attrs)))]
:text [(conj ids id)
(-> values
(merge-attrs (select-keys shape attrs))

View File

@@ -82,10 +82,13 @@
(dissoc :gradient)))))
change-opacity (fn [new-opacity]
(when on-change (on-change (assoc color :opacity new-opacity))))
(when on-change (on-change (assoc color
:opacity new-opacity
:id nil
:file-id nil))))
handle-pick-color (fn [color]
(when on-change (on-change color)))
(when on-change (on-change (merge uc/empty-color color))))
handle-open (fn [] (when on-open (on-open)))
@@ -133,7 +136,7 @@
(cond
;; Rendering a color with ID
(:id color)
(and (:id color) (not (uc/multiple? color)))
[:*
[:div.color-info
[:div.color-name (str (get-color-name color))]]
@@ -146,7 +149,8 @@
;; Rendering a gradient
(and (not (uc/multiple? color))
(:gradient color) (get-in color [:gradient :type]))
(:gradient color)
(get-in color [:gradient :type]))
[:div.color-info
[:div.color-name (cb/gradient-type->string (get-in color [:gradient :type]))]]

View File

@@ -78,11 +78,12 @@
update-color
(fn [index]
(fn [color opacity]
(st/emit! (dwc/update-shapes
ids
#(-> %
(assoc-in [:shadow index :color] color)
(assoc-in [:shadow index :opacity] opacity))))))
(let [color (d/without-keys color [:id :file-id :gradient])]
(st/emit! (dwc/update-shapes
ids
#(-> %
(assoc-in [:shadow index :color] color)
(assoc-in [:shadow index :opacity] opacity)))))))
detach-color
(fn [index]

View File

@@ -66,7 +66,9 @@
(mf/use-callback
(mf/deps ids)
(fn [color]
(st/emit! (dc/change-stroke ids color))))
(let [remove-multiple (fn [[key value]] (not= value :multiple))
color (into {} (filter remove-multiple) color)]
(st/emit! (dc/change-stroke ids color)))))
handle-detach
(mf/use-callback

View File

@@ -277,7 +277,7 @@
[:div.row-flex
[:> grow-options opts]
[:> text-decoration-options opts]]
]]))
(mf/defc options

View File

@@ -81,9 +81,9 @@
;; --- Coordinates Widget
(mf/defc coordinates
[]
[{:keys [colorpalette?]}]
(let [coords (hooks/use-rxsub ms/mouse-position)]
[:ul.coordinates
[:ul.coordinates {:class (when colorpalette? "color-palette-open")}
[:span {:alt "x"}
(str "X: " (:x coords "-"))]
[:span {:alt "y"}
@@ -508,7 +508,7 @@
(let [node (mf/ref-val viewport-ref)
target (dom/get-target event)]
(cond
(kbd/ctrl? event)
(or (kbd/ctrl? event) (kbd/meta? event))
(let [event (.getBrowserEvent ^js event)
pos @ms/mouse-position]
(dom/prevent-default event)

View File

@@ -109,12 +109,16 @@
:else "transparent")))
(defn multiple? [{:keys [value color gradient]}]
(defn multiple? [{:keys [id file-id value color gradient]}]
(or (= value :multiple)
(= color :multiple)
(= gradient :multiple)
(and gradient color)))
(= id :multiple)
(= file-id :multiple)))
(defn parse-color [^string color-str]
(let [result (gcolor/parse color-str)]
(str (.-hex ^js result))))
(def empty-color
(into {} (map #(vector % nil)) [:color :id :file-id :gradient :opacity]))

View File

@@ -289,7 +289,8 @@
(:c2x params) (update-in [index :params :c2x] + (:c2x params))
(:c2y params) (update-in [index :params :c2y] + (:c2y params)))
content))]
(reduce apply-to-index content modifiers)))
(let [content (if (vector? content) content (into [] content))]
(reduce apply-to-index content modifiers))))
(defn command->point [command]
(when-not (nil? command)

View File

@@ -11,6 +11,7 @@
"A http client with rx streams interface."
(:refer-clojure :exclude [get])
(:require
[app.config :as cfg]
[app.util.object :as obj]
[app.util.transit :as t]
[beicon.core :as rx]
@@ -68,11 +69,15 @@
(.setQueryData uri dt)))
(.toString uri)))
(def default-headers
{"x-frontend-version" (:full @cfg/version)})
(defn- fetch
[{:keys [method uri query-string query headers body] :as request}
{:keys [timeout credentials? response-type]
:or {timeout 0 credentials? false response-type :text}}]
(let [uri (create-uri uri query-string query)
headers (merge default-headers headers)
headers (if headers (clj->js headers) #js {})
method (translate-method method)
xhr (doto (XhrIo.)

View File

@@ -114,15 +114,13 @@ function build-bundle {
sed -i -re "s/\%version\%/$version/g" ./bundle/backend/main/app/config.clj;
local generate_tar=${PENPOT_BUNDLE_GENERATE_TAR:-"true"};
if [ $generate_tar == "true" ]; then
pushd bundle/
tar -cvf ../$name.tar *;
tar -I lz4 -cvf ../$name.tar.lz4 *;
popd
xz -vez1f -T4 $name.tar
echo "##############################################################";
echo "# Generated $name.tar.xz";
echo "# Generated $name.tar.lz4";
echo "# Version $version";
echo "##############################################################";
fi
@@ -137,19 +135,18 @@ function build-image {
set -x
pushd ./docker/images;
docker buildx build --platform linux/amd64 -t $docker_image:$tag -f Dockerfile.$image .;
docker tag $docker_image:$tag $docker_image:$version;
# docker tag $docker_image:$tag $docker_image:$version;
# docker buildx build --platform linux/arm64 -t $docker_image:$version-arm64 .;
popd;
}
function build-images {
local version="$CURRENT_VERSION";
local bundle_file="penpot-$CURRENT_BRANCH.tar.xz";
local bundle_file="penpot-$CURRENT_BRANCH.tar.lz4";
if [ $CURRENT_BRANCH != "main" ]; then
version="$CURRENT_BRANCH-$CURRENT_VERSION";
bundle_file="penpot-$CURRENT_BRANCH.tar.xz";
bundle_file="penpot-$CURRENT_BRANCH.tar.lz4";
fi;
if [ ! -f $bundle_file ]; then
@@ -164,7 +161,7 @@ function build-images {
mkdir -p ./docker/images/bundle;
pushd ./docker/images/bundle;
tar xvf $bundle_file_path;
tar -I lz4 -xvf $bundle_file_path;
popd
build-image "backend" $CURRENT_BRANCH $version;
@@ -180,9 +177,9 @@ function publish-latest-images {
set -x
docker tag $ORGANIZATION/frontend:$CURRENT_VERSION $ORGANIZATION/frontend:latest;
docker tag $ORGANIZATION/backend:$CURRENT_VERSION $ORGANIZATION/backend:latest;
docker tag $ORGANIZATION/exporter:$CURRENT_VERSION $ORGANIZATION/exporter:latest;
docker tag $ORGANIZATION/frontend:$CURRENT_BRANCH $ORGANIZATION/frontend:latest;
docker tag $ORGANIZATION/backend:$CURRENT_BRANCH $ORGANIZATION/backend:latest;
docker tag $ORGANIZATION/exporter:$CURRENT_BRANCH $ORGANIZATION/exporter:latest;
# docker push $ORGANIZATION/frontend:$CURRENT_VERSION;
# docker push $ORGANIZATION/backend:$CURRENT_VERSION;