Compare commits

...

43 Commits

Author SHA1 Message Date
Andrés Moya
beaea73276 📎 Update version number. 2021-06-17 14:00:24 +02:00
Andrés Moya
50e0284084 Merge pull request #1043 from penpot/fix/problem-with-flip-transforms
🐛 Fix problem with paths editing after flip
2021-06-16 17:11:02 +02:00
alonso.torres
e08788190d 🐛 Fix problem with paths editing after flip 2021-06-16 17:05:18 +02:00
Andrey Antukh
44441ae928 💄 Minor lint fix on emails ns. 2021-06-16 16:49:15 +02:00
Andrey Antukh
e42e1e8751 🐛 Properly preserve the font-family name on upload custom font. 2021-06-16 16:32:21 +02:00
Andrey Antukh
ae4b743ea4 🐛 Add missing system deps to the default docker backend image. 2021-06-16 16:14:44 +02:00
Andrey Antukh
782e060448 📎 Add minior adaptations to main docker files. 2021-06-07 11:03:53 +02:00
Andrey Antukh
bb5804cde3 📎 Update changelog and increase version. 2021-06-04 14:15:48 +02:00
Andrey Antukh
7819757759 🐛 Fix unexpected exception on searching without term.
When term is empty on frontend, frontend just does not sends it
to backend, leving it as missing field. This commit makes the
seatch-term as optional.
2021-06-04 14:15:48 +02:00
Andrey Antukh
b861e261ed 🐛 Replace frame term usage by artboard on viewer app.
Replace frame with artboard.
2021-06-04 14:15:48 +02:00
Andrey Antukh
17b32d6518 🐛 Don't allow rename drafts project. 2021-06-04 14:15:48 +02:00
Andrey Antukh
d2359046c4 🐛 Fix problem when moving files with drag & drop. 2021-06-04 14:15:48 +02:00
Andrey Antukh
8a700170b0 🐛 Fix font loading on viewer app. 2021-06-04 13:39:01 +02:00
Andrey Antukh
8c68e29bf3 🐛 Fix custom font rendering on exporting shapes. 2021-06-04 13:26:37 +02:00
Andrey Antukh
1a81631886 📎 Decrease default bulk buffers on storage tasks. 2021-06-04 09:41:42 +02:00
Andrey Antukh
634fe2c458 📎 Reduce file_change preserve interval to 24h. 2021-06-04 01:27:21 +02:00
Andrey Antukh
053d46144e 📎 Fix linter issues. 2021-06-03 17:24:19 +02:00
Andrey Antukh
b2e7bb6be1 🐛 Properly handle nil values on update-shapes function. 2021-06-03 17:19:14 +02:00
Andrey Antukh
bae709df5b 🐛 Fix custom font deletion task. 2021-06-03 12:55:31 +02:00
Andrey Antukh
1b495ebad1 Minor improvements on loki reporter. 2021-06-03 12:40:22 +02:00
Andrey Antukh
4e0289b341 Reduce the deletion window of file_changes. 2021-06-03 12:34:11 +02:00
Andrey Antukh
056fce9187 📎 Minor changes on background tasks cron expr. 2021-06-02 13:13:25 +02:00
Andrey Antukh
9f034c7e7e Disable excesive logging of some modules. 2021-06-02 11:27:22 +02:00
Andrey Antukh
a6de4e3742 📎 Change version.txt file. 2021-06-01 15:19:37 +02:00
Andrey Antukh
2d6a375afc 📎 Update changelog. 2021-06-01 15:18:26 +02:00
Andrey Antukh
585e5d0199 📎 Minor changes on internal audit module buffers. 2021-06-01 15:14:39 +02:00
alonso.torres
71524fe649 🐛 Fix problem with empty path editing 2021-05-31 12:50:24 +02:00
alonso.torres
55d2768807 🐛 Fix problem with create component 2021-05-31 12:50:24 +02:00
alonso.torres
3d7a3f27d5 🐛 Fix problem with move-objects 2021-05-28 11:05:18 +02:00
alonso.torres
46448bc5c7 🐛 Fix problem with merge and join nodes 2021-05-28 10:51:36 +02:00
Andrey Antukh
2f8f1f0b9a 📎 Update changelog. 2021-05-28 08:49:27 +02:00
Andrey Antukh
d572fdac9b 🐛 Fix unexpected exception on duplicate project.
Related to files created out of order.
2021-05-28 08:39:04 +02:00
Andrey Antukh
ac41ed1af4 Add missing cause prop on error loging. 2021-05-28 08:32:30 +02:00
Andrey Antukh
f47bb6bcd0 Minor fix on previous commit. 2021-05-27 18:12:29 +02:00
Andrey Antukh
a3eb5e2928 🐛 Fix incorrect unicode code points handling on draft-to-penpot conversion. 2021-05-27 17:52:16 +02:00
Andrey Antukh
d4bf3ef6fd 📎 Remove mattermost mention-all workds from error report. 2021-05-27 13:29:29 +02:00
Andrey Antukh
ca5c374ecd 🐛 Fix empty font-family handling on custom fonts page. 2021-05-27 13:21:37 +02:00
Andrey Antukh
69ea8229ca :spakles: Minor improvements on svg uploading on libraries.
Mainly reject svgs that have doctype declaration for security reasons.
2021-05-27 13:00:13 +02:00
Andrey Antukh
4d19b87fff Improve error report on uploading invalid image to library. 2021-05-27 12:40:38 +02:00
Andrey Antukh
8847047fd1 🐛 Fix unexpected exception when user leaves typography name empty. 2021-05-27 12:21:40 +02:00
Andrey Antukh
6e8a5015c9 Add better auth module logging. 2021-05-27 11:52:01 +02:00
Andrey Antukh
e8919ee340 🐛 Add missing email scope to OIDC backend.
And additionaly emit a warn log message about the error.
2021-05-27 11:52:01 +02:00
alonso.torres
f8f506a8be 🐛 Fix some problems with paths 2021-05-27 11:10:30 +02:00
60 changed files with 677 additions and 450 deletions

View File

@@ -1,7 +1,12 @@
{:lint-as {potok.core/reify clojure.core/reify
promesa.core/let clojure.core/let
rumext.alpha/defc clojure.core/defn
app.common.data/export clojure.core/def
app.db/with-atomic clojure.core/with-open}
:hooks
{:analyze-call {app.common.data/export hooks.export/export}}
:output
{:exclude-files ["data_readers.clj"]}

View File

@@ -0,0 +1,11 @@
(ns hooks.export
(:require [clj-kondo.hooks-api :as api]))
(defn export
[{:keys [:node]}]
(let [[_ sname] (:children node)
result (api/list-node
[(api/token-node (symbol "def"))
(api/token-node (symbol (name (:value sname))))
sname])]
{:node result}))

2
.gitignore vendored
View File

@@ -30,6 +30,8 @@ node_modules
/exporter/target
/exporter/.shadow-cljs
/docker/images/bundle*
/common/.shadow-cljs
/common/target
/.clj-kondo/.cache
/bundle*
/media

View File

@@ -1,6 +1,5 @@
# CHANGELOG #
## :rocket: Next
### :sparkles: New features
@@ -10,21 +9,72 @@
### :boom: Breaking changes
### :heart: Community contributions by (Thank you!)
## 1.6.5-alpha
### :bug: Bugs fixed
- Fix problem with paths editing after flip [#1040](https://github.com/penpot/penpot/issues/1040)
## 1.6.4-alpha
### :sparkles: Minor improvements
- Decrease default bulk buffers on storage tasks.
- Reduce file_change preserve interval to 24h.
### :bug: Bugs fixed
- Don't allow rename drafts project.
- Fix custom font deletion task.
- Fix custom font rendering on exporting shapes.
- Fix font loading on viewer app.
- Fix problem when moving files with drag & drop.
- Fix unexpected exception on searching without term.
- Properly handle nil values on `update-shapes` function.
- Replace frame term usage by artboard on viewer app.
## 1.6.3-alpha
### :bug: Bugs fixed
- Fix problem with merge and join nodes [#990](https://github.com/penpot/penpot/issues/990)
- Fix problem with empty path editing.
- Fix problem with create component.
- Fix problem with move-objects.
- Fix problem with merge and join nodes.
## 1.6.2-alpha
### :bug: Bugs fixed
- Add better auth module logging.
- Add missing `email` scope to OIDC backend.
- Add missing cause prop on error loging.
- Fix empty font-family handling on custom fonts page.
- Fix incorrect unicode code points handling on draft-to-penpot conversion.
- Fix some problems with paths.
- Fix unexpected exception on duplicate project.
- Fix unexpected exception when user leaves typography name empty.
- Improve error report on uploading invalid image to library.
- Minor fix on previous commit.
- Minor improvements on svg uploading on libraries.
## 1.6.1-alpha
### :bug: Bugs fixed
- Make the navigation async by default.
- Improve editor lifecycle management.
- Fix problem when creating a component with empty data.
- Add safety check on reg-objects change impl.
- Fix custom fonts embbedding issue.
- Fix dashboard ordering issue.
- Fix problem when creating a component with empty data.
- Fix problem with moving shapes into frames.
- Fix problems with mov-objects.
- Fix wrong type usage on libraries changes.
- Fix custom fonts embbedding issue.
- Add safety check on reg-objects change impl.
- Fix unexpected excetion related to rounding integers.
- Fix wrong type usage on libraries changes.
- Improve editor lifecycle management.
- Make the navigation async by default.
## 1.6.0-alpha

View File

@@ -127,9 +127,9 @@
(s/def :internal.emails.invite-to-team/token ::us/string)
(s/def ::invite-to-team
(s/keys :keys [:internal.emails.invite-to-team/invited-by
:internal.emails.invite-to-team/token
:internal.emails.invite-to-team/team]))
(s/keys :req-un [:internal.emails.invite-to-team/invited-by
:internal.emails.invite-to-team/token
:internal.emails.invite-to-team/team]))
(def invite-to-team
"Teams member invitation email."

View File

@@ -117,7 +117,8 @@
(l/error :hint "psql exception"
:error-message (ex-message error)
:error-id (str (:id cdata))
:sql-state state)
:sql-state state
:cause error)
(cond
(= state "57014")

View File

@@ -109,6 +109,17 @@
:cause e)
nil)))
(s/def ::backend ::us/not-empty-string)
(s/def ::email ::us/not-empty-string)
(s/def ::fullname ::us/not-empty-string)
(s/def ::props (s/map-of ::us/keyword any?))
(s/def ::info
(s/keys :req-un [::backend
::email
::fullname
::props]))
(defn retrieve-info
[{:keys [tokens provider] :as cfg} request]
(let [state (get-in request [:params :state])
@@ -116,7 +127,10 @@
info (some->> (get-in request [:params :code])
(retrieve-access-token cfg)
(retrieve-user-info cfg))]
(when-not info
(when-not (s/valid? ::info info)
(l/warn :hint "received incomplete profile info object (please set correct scopes)"
:info (pr-str info))
(ex/raise :type :internal
:code :unable-to-auth
:hint "no user info"))
@@ -228,6 +242,13 @@
:auth-uri (get data "authorization_endpoint")
:user-uri (get data "userinfo_endpoint"))))))
(defn- obfuscate-string
[s]
(if (< (count s) 10)
(apply str (take (count s) (repeat "*")))
(str (subs s 0 5)
(apply str (take (- (count s) 5) (repeat "*"))))))
(defn- initialize-oidc-provider
[cfg]
(let [opts {:base-uri (cf/get :oidc-base-uri)
@@ -236,7 +257,7 @@
:token-uri (cf/get :oidc-token-uri)
:auth-uri (cf/get :oidc-auth-uri)
:user-uri (cf/get :oidc-user-uri)
:scopes (cf/get :oidc-scopes #{"openid" "profile"})
:scopes (cf/get :oidc-scopes #{"openid" "profile" "email"})
:roles-attr (cf/get :oidc-roles-attr)
:roles (cf/get :oidc-roles)
:name "oidc"}]
@@ -247,10 +268,12 @@
(string? (:user-uri opts))
(string? (:auth-uri opts)))
(do
(l/info :action "initialize" :provider "oid" :method "static")
(l/info :action "initialize" :provider "oidc" :method "static"
:opts (pr-str (update opts :client-secret obfuscate-string)))
(assoc-in cfg [:providers "oidc"] opts))
(let [opts (discover-oidc-config opts)]
(l/info :action "initialize" :provider "oid" :method "discover")
(l/info :action "initialize" :provider "oidc" :method "discover"
:opts (pr-str (update opts :client-secret obfuscate-string)))
(assoc-in cfg [:providers "oidc"] opts)))
cfg)))
@@ -266,7 +289,8 @@
(if (and (string? (:client-id opts))
(string? (:client-secret opts)))
(do
(l/info :action "initialize" :provider "google")
(l/info :action "initialize" :provider "google"
:opts (pr-str (update opts :client-secret obfuscate-string)))
(assoc-in cfg [:providers "google"] opts))
cfg)))
@@ -282,7 +306,8 @@
(if (and (string? (:client-id opts))
(string? (:client-secret opts)))
(do
(l/info :action "initialize" :provider "github")
(l/info :action "initialize" :provider "github"
:opts (pr-str (update opts :client-secret obfuscate-string)))
(assoc-in cfg [:providers "github"] opts))
cfg)))
@@ -301,7 +326,8 @@
(if (and (string? (:client-id opts))
(string? (:client-secret opts)))
(do
(l/info :action "initialize" :provider "gitlab")
(l/info :action "initialize" :provider "gitlab"
:opts (pr-str (update opts :client-secret obfuscate-string)))
(assoc-in cfg [:providers "gitlab"] opts))
cfg)))

View File

@@ -140,10 +140,13 @@
(when-let [[reason batch] (a/<! input)]
(let [result (a/<! (update-sessions cfg batch))]
(mcnt :inc)
(if (ex/exception? result)
(cond
(ex/exception? result)
(l/error :task "updater"
:hint "unexpected error on update sessions"
:cause result)
(= :size reason)
(l/debug :task "updater"
:action "update sessions"
:reason (name reason)

View File

@@ -75,15 +75,12 @@
[_ {:keys [enabled] :as cfg}]
(when enabled
(l/info :msg "intializing audit collector")
(let [input (a/chan 1 event-xform)
(let [input (a/chan 512 event-xform)
buffer (aa/batch input {:max-batch-size 100
:max-batch-age (* 5 1000)
:max-batch-age (* 10 1000) ; 10s
:init []})]
(a/go-loop []
(when-let [[type events] (a/<! buffer)]
(l/debug :action "persist-events (batch)"
:reason (name type)
:count (count events))
(when-let [[_type events] (a/<! buffer)]
(let [res (a/<! (persist-events cfg events))]
(when (ex/exception? res)
(l/error :hint "error on persiting events"

View File

@@ -31,7 +31,7 @@
[_ {:keys [receiver uri] :as cfg}]
(when uri
(l/info :msg "intializing loki reporter" :uri uri)
(let [input (a/chan (a/sliding-buffer 1024))]
(let [input (a/chan (a/dropping-buffer 512))]
(receiver :sub input)
(a/go-loop []
(let [msg (a/<! input)]
@@ -69,17 +69,23 @@
:method :post
:headers {"content-type" "application/json"}
:body (json/encode payload)})]
(if (= (:status response) 204)
(cond
(= (:status response) 204)
true
(= (:status response) 400)
(do
(l/error :hint "error on sending log to loki"
:try i
(l/error :hint "error on sending log to loki (no retry)"
:rsp (pr-str response))
true)
:else
(do
(l/error :hint "error on sending log to loki" :try i
:rsp (pr-str response))
false)))
(catch Exception e
(l/error :hint "error on sending message to loki"
:cause e
:try i)
(l/error :hint "error on sending message to loki" :cause e :try i)
false)))
(defn- handle-event

View File

@@ -61,7 +61,7 @@
[cfg {:keys [host version id] :as cdata}]
(try
(let [uri (:uri cfg)
text (str "Unhandled exception (@channel):\n"
text (str "Unhandled exception:\n"
"- detail: " (cfg/get :public-uri) "/dbg/error-by-id/" id "\n"
"- profile-id: `" (:profile-id cdata) "`\n"
"- host: `" host "`\n"

View File

@@ -166,25 +166,25 @@
:tasks (ig/ref :app.worker/registry)
:pool (ig/ref :app.db/pool)
:schedule
[{:cron #app/cron "0 0 0 */1 * ? *" ;; daily
[{:cron #app/cron "0 0 0 * * ? *" ;; daily
:task :file-media-gc}
{:cron #app/cron "0 0 */1 * * ?" ;; hourly
{:cron #app/cron "0 0 * * * ?" ;; hourly
:task :file-xlog-gc}
{:cron #app/cron "0 0 1 */1 * ?" ;; daily (1 hour shift)
{:cron #app/cron "0 0 1 * * ?" ;; daily (1 hour shift)
:task :storage-deleted-gc}
{:cron #app/cron "0 0 2 */1 * ?" ;; daily (2 hour shift)
{:cron #app/cron "0 0 2 * * ?" ;; daily (2 hour shift)
:task :storage-touched-gc}
{:cron #app/cron "0 0 3 */1 * ?" ;; daily (3 hour shift)
{:cron #app/cron "0 0 3 * * ?" ;; daily (3 hour shift)
:task :session-gc}
{:cron #app/cron "0 0 */1 * * ?" ;; hourly
{:cron #app/cron "0 0 * * * ?" ;; hourly
:task :storage-recheck}
{:cron #app/cron "0 0 0 */1 * ?" ;; daily
{:cron #app/cron "0 0 0 * * ?" ;; daily
:task :tasks-gc}
(when (cf/get :audit-archive-enabled)
@@ -230,31 +230,22 @@
:app.tasks.tasks-gc/handler
{:pool (ig/ref :app.db/pool)
:max-age cf/deletion-delay
:metrics (ig/ref :app.metrics/metrics)}
:max-age cf/deletion-delay}
:app.tasks.delete-object/handler
{:pool (ig/ref :app.db/pool)
:metrics (ig/ref :app.metrics/metrics)}
:app.tasks.delete-storage-object/handler
{:pool (ig/ref :app.db/pool)
:storage (ig/ref :app.storage/storage)
:metrics (ig/ref :app.metrics/metrics)}
:storage (ig/ref :app.storage/storage)}
:app.tasks.delete-profile/handler
{:pool (ig/ref :app.db/pool)
:metrics (ig/ref :app.metrics/metrics)}
{:pool (ig/ref :app.db/pool)}
:app.tasks.file-media-gc/handler
{:pool (ig/ref :app.db/pool)
:metrics (ig/ref :app.metrics/metrics)
:max-age cf/deletion-delay}
:app.tasks.file-xlog-gc/handler
{:pool (ig/ref :app.db/pool)
:metrics (ig/ref :app.metrics/metrics)
:max-age cf/deletion-delay}
:max-age (dt/duration {:hours 24})}
:app.tasks.telemetry/handler
{:pool (ig/ref :app.db/pool)

View File

@@ -66,8 +66,7 @@
(defmethod process-error :default
[error]
(ex/raise :type :internal
:cause error))
(throw error))
(defn run
[{:keys [rlimits] :as cfg} {:keys [rlimit] :or {rlimit :image} :as params}]
@@ -184,11 +183,10 @@
(us/assert ::input input)
(let [{:keys [path mtype]} input]
(if (= mtype "image/svg+xml")
(let [data (svg/parse (slurp path))
info (get-basic-info-from-svg data)]
(let [info (some-> path slurp svg/parse get-basic-info-from-svg)]
(when-not info
(ex/raise :type :validation
:code :unable-to-retrieve-dimensions
:code :invalid-svg-file
:hint "uploaded svg does not provides dimensions"))
(assoc info :mtype mtype))
@@ -211,6 +209,7 @@
[error]
(ex/raise :type :validation
:code :invalid-image
:hint "invalid image"
:cause error))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@@ -173,7 +173,7 @@
index {file-id (uuid/next)}
params (assoc params :index index :file file)]
(proj/check-edition-permissions! conn profile-id (:project-id file))
(db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"])
(-> (duplicate-file conn params {:reset-shared-flag true})
(update :data blob/decode)))))
@@ -191,6 +191,7 @@
(db/with-atomic [conn pool]
(let [project (db/get-by-id conn :project project-id)]
(teams/check-edition-permissions! conn profile-id (:team-id project))
(db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"])
(duplicate-project conn (assoc params :project project)))))
(defn duplicate-project

View File

@@ -112,14 +112,16 @@
order by f.created_at asc")
(s/def ::search-files
(s/keys :req-un [::profile-id ::team-id ::search-term]))
(s/keys :req-un [::profile-id ::team-id]
:opt-un [::search-term]))
(sv/defmethod ::search-files
[{:keys [pool] :as cfg} {:keys [profile-id team-id search-term] :as params}]
(db/exec! pool [sql:search-files
profile-id team-id
profile-id team-id
search-term]))
(when search-term
(db/exec! pool [sql:search-files
profile-id team-id
profile-id team-id
search-term])))
;; --- Query: Files

View File

@@ -8,12 +8,16 @@
(:require
[app.common.spec :as us]
[app.db :as db]
[app.rpc.queries.files :as files]
[app.rpc.queries.projects :as projects]
[app.rpc.queries.teams :as teams]
[app.util.services :as sv]
[clojure.spec.alpha :as s]))
;; --- Query: Team Font Variants
;; TODO: deprecated, should be removed on 1.7.x
(s/def ::team-id ::us/uuid)
(s/def ::profile-id ::us/uuid)
(s/def ::team-font-variants
@@ -27,3 +31,43 @@
{:team-id team-id
:deleted-at nil})))
;; --- Query: Font Variants
(s/def ::file-id ::us/uuid)
(s/def ::project-id ::us/uuid)
(s/def ::font-variants
(s/and
(s/keys :req-un [::profile-id]
:opt-un [::team-id
::file-id
::project-id])
(fn [o]
(or (contains? o :team-id)
(contains? o :file-id)
(contains? o :project-id)))))
(sv/defmethod ::font-variants
[{:keys [pool] :as cfg} {:keys [profile-id team-id file-id project-id] :as params}]
(with-open [conn (db/open pool)]
(cond
(uuid? team-id)
(do
(teams/check-read-permissions! conn profile-id team-id)
(db/query conn :team-font-variant
{:team-id team-id
:deleted-at nil}))
(uuid? project-id)
(let [project (db/get-by-id conn :project project-id {:columns [:id :team-id]})]
(projects/check-read-permissions! conn profile-id project-id)
(db/query conn :team-font-variant
{:team-id (:team-id project)
:deleted-at nil}))
(uuid? file-id)
(let [file (db/get-by-id conn :file file-id {:columns [:id :project-id]})
project (db/get-by-id conn :project (:project-id file) {:columns [:id :team-id]})]
(files/check-read-permissions! conn profile-id file-id)
(db/query conn :team-font-variant
{:team-id (:team-id project)
:deleted-at nil})))))

View File

@@ -36,6 +36,7 @@
:message (ex-message e))
(ex/raise :type :validation
:code :invalid-svg-file
:hint "invalid svg file"
:cause e))))
(declare pre-process)
@@ -53,6 +54,6 @@
[data]
(cond-> data
(str/includes? data "<!DOCTYPE")
(str/replace #"<\!DOCTYPE[^>]+>" "")))
(str/replace #"<\!DOCTYPE[^>]*>" "")))
(def pre-process strip-doctype)

View File

@@ -50,9 +50,14 @@
libs (files/retrieve-file-libraries conn false file-id)
users (teams/retrieve-users conn (:team-id project))
fonts (db/query conn :team-font-variant
{:team-id (:team-id project)
:deleted-at nil})
bundle {:file file
:page page
:users users
:fonts fonts
:project project
:libraries libs}]

View File

@@ -319,7 +319,7 @@
where s.deleted_at is not null
and s.deleted_at < (now() - ?::interval)
order by s.deleted_at
limit 500
limit 100
)
delete from storage_object
where id in (select id from items_part)
@@ -396,7 +396,7 @@
from storage_object as so
where so.touched_at is not null
order by so.touched_at
limit 500;")
limit 100;")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Recheck Stalled Task

View File

@@ -18,7 +18,7 @@
(declare handle-deletion)
(defmethod ig/pre-init-spec ::handler [_]
(s/keys :req-un [::db/pool]))
(s/keys :req-un [::db/pool ::sto/storage]))
(defmethod ig/init-key ::handler
[_ {:keys [pool] :as cfg}]

View File

@@ -28,7 +28,7 @@
(let [interval (db/interval max-age)
result (db/exec-one! conn [sql:delete-files-xlog interval])
result (:next.jdbc/update-count result)]
(l/debug :action "trim file-change table" :removed result)
(l/debug :removed result :hint "remove old file changes")
result))))
(def ^:private

View File

@@ -64,12 +64,14 @@
(defn batch
[in {:keys [max-batch-size
max-batch-age
buffer-size
init]
:or {max-batch-size 200
max-batch-age (* 30 1000)
buffer-size 128
init #{}}
:as opts}]
(let [out (a/chan)]
(let [out (a/chan buffer-size)]
(a/go-loop [tch (a/timeout max-batch-age) buf init]
(let [[val port] (a/alts! [tch in])]
(cond

View File

@@ -98,11 +98,11 @@
;; Terminate the loop if close channel is closed or
;; event-loop-fn returns nil.
(or (= port close-ch) (nil? val))
(l/debug :msg "stop condition found")
(l/debug :hint "stop condition found")
(db/pool-closed? pool)
(do
(l/debug :msg "eventloop aborted because pool is closed")
(l/debug :hint "eventloop aborted because pool is closed")
(a/close! close-ch))
(and (instance? java.sql.SQLException val)
@@ -115,7 +115,7 @@
(and (instance? java.sql.SQLException val)
(= "40001" (.getSQLState ^java.sql.SQLException val)))
(do
(l/debug :msg "serialization failure (retrying in some instants)")
(l/debug :hint "serialization failure (retrying in some instants)")
(a/<! (a/timeout poll-ms))
(recur))
@@ -243,7 +243,7 @@
(let [task-fn (get tasks name)]
(if task-fn
(task-fn item)
(l/warn :msg "no task handler found"
(l/warn :hint "no task handler found"
:name (d/name name)))
{:status :completed :task item}))
@@ -281,19 +281,13 @@
[{:keys [tasks]} item]
(let [name (d/name (:name item))]
(try
(l/debug :action "start task"
:name name
(l/debug :action "execute task"
:id (:id item)
:name name
:retry (:retry-num item))
(handle-task tasks item)
(catch Exception e
(handle-exception e item))
(finally
(l/debug :action "end task"
:name name
:id (:id item)
:retry (:retry-num item))))))
(handle-exception e item)))))
(def sql:select-next-tasks
"select * from task as t

View File

@@ -135,7 +135,9 @@
(defmethod pp/simple-dispatch Matrix [obj] (pr obj))
(defn transform-in [pt mtx]
(-> (matrix)
(translate pt)
(multiply mtx)
(translate (gpt/negate pt))))
(if (some? pt)
(-> (matrix)
(translate pt)
(multiply mtx)
(translate (gpt/negate pt)))
mtx))

View File

@@ -304,21 +304,26 @@
:else
(-> shape
(merge rect-shape)))]
(merge rect-shape)))
base-rotation (or (:rotation shape) 0)
modif-rotation (or (get-in shape [:modifiers :rotation]) 0)]
(as-> shape $
(update $ :transform #(gmt/multiply (or % (gmt/matrix)) matrix))
(update $ :transform-inverse #(gmt/multiply matrix-inverse (or % (gmt/matrix))))
(assoc $ :points (into [] points))
(assoc $ :selrect (gpr/rect->selrect rect-shape))
(update $ :rotation #(mod (+ (or % 0)
(or (get-in $ [:modifiers :rotation]) 0)) 360)))))
(assoc $ :rotation (mod (+ base-rotation modif-rotation) 360)))))
(defn set-flip [shape modifiers]
(let [rx (get-in modifiers [:resize-vector :x])
ry (get-in modifiers [:resize-vector :y])]
(cond-> shape
(and rx (< rx 0)) (update :flip-x not)
(and ry (< ry 0)) (update :flip-y not))))
(and rx (< rx 0)) (-> (update :flip-x not)
(update :rotation -))
(and ry (< ry 0)) (-> (update :flip-y not)
(update :rotation -)))))
(defn apply-displacement [shape]
(let [modifiers (:modifiers shape)]
@@ -356,8 +361,8 @@
([shape {:keys [round-coords?]
:or {round-coords? true}}]
(let [shape (apply-displacement shape)
center (gco/center-shape shape)
(let [shape (apply-displacement shape)
center (gco/center-shape shape)
modifiers (:modifiers shape)]
(if (and modifiers center)
(let [transform (modifiers->transform center modifiers)]
@@ -376,7 +381,7 @@
:y (- (:y new-selrect 0) (:y selrect 0))
:width (- (:width new-selrect 1) (:width selrect 1))
:height (- (:height new-selrect 1) (:height selrect 1))}]
(cond-> group
(and (some? svg-viewbox) (some? selrect) (some? new-selrect))
(update :svg-viewbox

View File

@@ -276,7 +276,7 @@
(if (and valid? (seq shapes))
(as-> objects $
;; Add the new shapes to the parent object.
(update $ parent-id #(add-to-parent % index shapes))
(d/update-when $ parent-id #(add-to-parent % index shapes))
;; Update each individual shape link to the new parent
(reduce update-parent-id $ shapes)

View File

@@ -7,6 +7,7 @@
(ns app.common.text
(:require
[app.common.attrs :as attrs]
[app.common.uuid :as uuid]
[app.common.data :as d]
[app.util.transit :as t]
[clojure.walk :as walk]
@@ -74,3 +75,181 @@
(defn ^boolean is-root-node?
[node]
(= "root" (:type node)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; DraftJS <-> Penpot Conversion
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn encode-style-value
[v]
#?(:cljs (t/encode v)
:clj (t/encode-str v)))
(defn decode-style-value
[v]
#?(:cljs (t/decode v)
:clj (t/decode-str v)))
(defn encode-style
[key val]
(let [k (d/name key)
v (encode-style-value val)]
(str "PENPOT$$$" k "$$$" v)))
(defn decode-style
[style]
(let [[_ k v] (str/split style "$$$" 3)]
[(keyword k) (decode-style-value v)]))
(defn attrs-to-styles
[attrs]
(reduce-kv (fn [res k v]
(conj res (encode-style k v)))
#{}
attrs))
(defn styles-to-attrs
[styles]
(persistent!
(reduce (fn [result style]
(if (str/starts-with? style "PENPOT")
(if (= style "PENPOT_SELECTION")
(assoc! result :penpot-selection true)
(let [[_ k v] (str/split style "$$$" 3)]
(assoc! result (keyword k) (decode-style-value v))))
result))
(transient {})
(seq styles))))
(defn- parse-draft-styles
"Parses draft-js style ranges, converting encoded style name into a
key/val pair of data."
[styles]
(->> styles
(filter #(str/starts-with? (get % :style) "PENPOT$$$"))
(map (fn [item]
(let [[_ k v] (-> (get item :style)
(str/split "$$$" 3))]
{:key (keyword k)
:val (decode-style-value v)
:offset (get item :offset)
:length (get item :length)})))))
(defn- build-style-index
"Generates a character based index with associated styles map."
[length ranges]
(loop [result (->> (range length)
(mapv (constantly {}))
(transient))
ranges (seq ranges)]
(if-let [{:keys [offset length] :as item} (first ranges)]
(recur (reduce (fn [result index]
(let [prev (get result index)]
(assoc! result index (assoc prev (:key item) (:val item)))))
result
(range offset (+ offset length)))
(rest ranges))
(persistent! result))))
(defn- text->code-points
[text]
#?(:cljs (into [] (js/Array.from text))
:clj (into [] (iterator-seq (.iterator (.codePoints ^String text))))))
(defn- code-points->text
[cpoints start end]
#?(:cljs (apply str (subvec cpoints start end))
:clj (let [sb (StringBuilder. (- end start))]
(run! #(.appendCodePoint sb (int %)) (subvec cpoints start end))
(.toString sb))))
(defn convert-from-draft
[content]
(letfn [(extract-text [cpoints part]
(let [start (ffirst part)
end (inc (first (last part)))
text (code-points->text cpoints start end)
attrs (second (first part))]
(assoc attrs :text text)))
(split-texts [text styles]
(let [cpoints (text->code-points text)
children (->> (parse-draft-styles styles)
(build-style-index (count cpoints))
(d/enumerate)
(partition-by second)
(mapv #(extract-text cpoints %)))]
(cond-> children
(empty? children)
(conj {:text ""}))))
(build-paragraph [block]
(let [key (get block :key)
text (get block :text)
styles (get block :inlineStyleRanges)
data (get block :data)]
(-> data
(assoc :key key)
(assoc :type "paragraph")
(assoc :children (split-texts text styles)))))]
{:type "root"
:children
[{:type "paragraph-set"
:children (->> (get content :blocks)
(mapv build-paragraph))}]}))
(defn convert-to-draft
[root]
(letfn [(process-attr [children ranges [k v]]
(loop [children (seq children)
start nil
offset 0
ranges ranges]
(if-let [{:keys [text] :as item} (first children)]
(let [cpoints (text->code-points text)]
(if (= v (get item k ::novalue))
(recur (rest children)
(if (nil? start) offset start)
(+ offset (count cpoints))
ranges)
(if (some? start)
(recur (rest children)
nil
(+ offset (count cpoints))
(conj! ranges {:offset start
:length (- offset start)
:style (encode-style k v)}))
(recur (rest children)
start
(+ offset (count cpoints))
ranges))))
(cond-> ranges
(some? start)
(conj! {:offset start
:length (- offset start)
:style (encode-style k v)})))))
(calc-ranges [{:keys [children] :as blok}]
(let [xform (comp (map #(dissoc % :key :text))
(remove empty?)
(mapcat vec)
(distinct))
proc #(process-attr children %1 %2)]
(persistent!
(transduce xform proc (transient []) children))))
(build-block [{:keys [key children] :as paragraph}]
{:key key
:depth 0
:text (apply str (map :text children))
:data (dissoc paragraph :key :children :type)
:type "unstyled"
:entityRanges []
:inlineStyleRanges (calc-ranges paragraph)})]
{:blocks (reduce #(conj %1 (build-block %2)) [] (node-seq #(= (:type %) "paragraph") root))
:entityMap {}}))

View File

@@ -7,7 +7,18 @@ WORKDIR /root
RUN set -ex; \
apt-get -qq update; \
apt-get -qqy --no-install-recommends install curl tzdata locales ca-certificates imagemagick webp fontconfig; \
apt-get -qqy --no-install-recommends install \
curl \
tzdata \
locales \
ca-certificates \
imagemagick \
webp \
fontconfig \
woff-tools \
woff2 \
fontforge \
; \
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen; \
locale-gen; \
rm -rf /var/lib/apt/lists/*;
@@ -40,6 +51,6 @@ RUN set -eux; \
rm -rf /tmp/openjdk.tar.gz;
ENV JAVA_HOME=/usr/lib/jvm/openjdk16 PATH="/usr/lib/jvm/openjdk16/bin:$PATH"
ADD ./bundle-app/backend/ /opt/bundle/
WORKDIR /opt/bundle
ADD ./bundle-backend/ /opt/penpot/backend/
WORKDIR /opt/penpot/backend
CMD ["/bin/bash", "run.sh"]

View File

@@ -1,7 +1,7 @@
FROM nginx:latest
LABEL maintainer="Andrey Antukh <niwi@niwi.nz>"
ADD ./bundle-app/frontend /var/www/app/
ADD ./bundle-frontend/ /var/www/app/
ADD ./files/config.js /var/www/app/js/config.js
ADD ./files/nginx.conf /etc/nginx/nginx.conf
ADD ./files/nginx-entrypoint.sh /entrypoint.sh

View File

@@ -42,7 +42,7 @@ PENPOT_REGISTRATION_ENABLED=true
# Comma separated list of allowed domains to register. Empty for allow
# all.
PENPOT_REGISTRATION_DOMAIN_WHITELIST=""
# PENPOT_REGISTRATION_DOMAIN_WHITELIST=""
# Penpot comes with the facility to create quick demo users that are
# automatically deleted after some time. This settings enables or

View File

@@ -97,7 +97,7 @@ update_registration_enabled() {
fi
}
update_registration_enabled() {
update_analytics_enabled() {
if [ -n "$PENPOT_ANALYTICS_ENABLED" ]; then
sed -i \
-e "s|^//var penpotAnalyticsEnabled = .*;|var penpotAnalyticsEnabled = $PENPOT_ANALYTICS_ENABLED;|g" \

View File

@@ -25,7 +25,7 @@
;; General purpose events & IMPL
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn team-fonts-loaded
(defn fonts-fetched
[fonts]
(letfn [;; Prepare font to the internal font database format.
(prepare-font [[id [item :as items]]]
@@ -74,8 +74,8 @@
(ptk/reify ::load-team-fonts
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :team-font-variants {:team-id team-id})
(rx/map team-fonts-loaded)))))
(->> (rp/query :font-variants {:team-id team-id})
(rx/map fonts-fetched)))))
(defn process-upload
"Given a seq of blobs and the team id, creates a ready-to-use fonts
@@ -89,7 +89,7 @@
{:content {:data (js/Uint8Array. data)
:name name
:type type}
:font-family family
:font-family (or family "")
:font-weight (cm/parse-font-weight variant)
:font-style (cm/parse-font-style variant)}))
@@ -172,7 +172,7 @@
(uuid/next))]
(update current-fonts id (fn [font]
(-> font
(assoc :name name)
(assoc :font-family name)
(assoc :font-id font-id))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@@ -12,9 +12,10 @@
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.main.constants :as c]
[app.main.data.comments :as dcm]
[app.main.data.fonts :as df]
[app.main.repo :as rp]
[app.main.store :as st]
[app.main.data.comments :as dcm]
[app.util.avatars :as avatars]
[app.util.router :as rt]
[beicon.core :as rx]
@@ -97,7 +98,10 @@
:file-id file-id}
(string? token) (assoc :token token))]
(->> (rp/query :viewer-bundle params)
(rx/map bundle-fetched))))))
(rx/mapcat
(fn [{:keys [fonts] :as bundle}]
(rx/of (df/fonts-fetched fonts)
(bundle-fetched bundle)))))))))
(defn- extract-frames
[objects]

View File

@@ -1238,7 +1238,6 @@
qparams {:page-id page-id}]
(rx/of (rt/nav :workspace pparams qparams))))))
(defn go-to-viewer
([] (go-to-viewer {}))
([{:keys [file-id page-id]}]

View File

@@ -66,10 +66,10 @@
(cond-> changes
(not (empty? rops))
(update :rch conj (assoc change :operations rops))
(update :redo-changes conj (assoc change :operations rops))
(not (empty? uops))
(update :uch conj (assoc change :operations uops)))))
(update :undo-changes conj (assoc change :operations uops)))))
(defn update-shapes
([ids f] (update-shapes ids f nil))
@@ -82,25 +82,26 @@
(ptk/reify ::update-shapes
ptk/WatchEvent
(watch [it state stream]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state)
reg-objects {:type :reg-objects :page-id page-id :shapes (vec ids)}
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state)
changes {:redo-changes []
:undo-changes []
:origin it
:save-undo? save-undo?}
{redo-changes :rch undo-changes :uch}
(reduce #(update-shape-changes %1 page-id objects f keys %2)
{:rch [] :uch []} ids)]
ids (into [] (filter some?) ids)
(when-not (empty? redo-changes)
(let [redo-changes (cond-> redo-changes
reg-objects? (conj reg-objects))
changes (reduce #(update-shape-changes %1 page-id objects f keys %2) changes ids)]
undo-changes (cond-> undo-changes
reg-objects? (conj reg-objects))]
(rx/of (commit-changes {:redo-changes redo-changes
:undo-changes undo-changes
:origin it
:save-undo? save-undo?})))))))))
(when-not (empty? (:redo-changes changes))
(let [reg-objs {:type :reg-objects
:page-id page-id
:shapes ids}
changes (cond-> changes
reg-objects?
(-> (update :redo-changes conj reg-objs)
(update :undo-changes conj reg-objs)))]
(rx/of (commit-changes changes)))))))))
(defn update-indices
[page-id changes]

View File

@@ -10,24 +10,21 @@
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as geom]
[app.common.pages :as cp]
[app.common.pages :as cp]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.main.data.messages :as dm]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.libraries-helpers :as dwlh]
[app.main.data.workspace.state-helpers :as wsh]
[app.main.repo :as rp]
[app.main.store :as st]
[app.main.streams :as ms]
[app.util.color :as color]
[app.util.i18n :refer [tr]]
[app.util.logging :as log]
[app.util.router :as rt]
[app.util.time :as dt]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[potok.core :as ptk]))
;; Change this to :info :debug or :trace to debug this module
@@ -228,7 +225,7 @@
ptk/WatchEvent
(watch [it state stream]
(let [[path name] (cp/parse-path-name (:name typography))
typography (assoc typography :path path :name name)
typography (assoc typography :path path :name name)
prev (get-in state [:workspace-data :typographies (:id typography)])
rchg {:type :mod-typography
:typography typography}
@@ -263,10 +260,11 @@
page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
selected (wsh/lookup-selected state)
selected (cp/clean-loops objects selected)]
(when-not (empty? selected)
selected (cp/clean-loops objects selected)
shapes (dwg/shapes-for-grouping objects selected)]
(when-not (empty? shapes)
(let [[group rchanges uchanges]
(dwlh/generate-add-component selected objects page-id file-id)]
(dwlh/generate-add-component shapes objects page-id file-id)]
(when-not (empty? rchanges)
(rx/of (dch/commit-changes {:redo-changes rchanges
:undo-changes uchanges

View File

@@ -124,10 +124,8 @@
"If there is exactly one id, and it's a group, use it as root. Otherwise,
create a group that contains all ids. Then, make a component with it,
and link all shapes to their corresponding one in the component."
[ids objects page-id file-id]
(let [shapes (dwg/shapes-for-grouping objects ids)
[group rchanges uchanges]
[shapes objects page-id file-id]
(let [[group rchanges uchanges]
(if (and (= (count shapes) 1)
(= (:type (first shapes)) :group))
[(first shapes) [] []]

View File

@@ -88,10 +88,10 @@
(let [objects (wsh/lookup-page-objects state)
page-id (:current-page-id state)
id (get-in state [:workspace-local :edition])
old-content (get-in state [:workspace-local :edit-path id :old-content])]
(if (some? old-content)
(let [shape (get-in state (st/get-path state))
[rch uch] (generate-path-changes objects page-id shape old-content (:content shape))]
old-content (get-in state [:workspace-local :edit-path id :old-content])
shape (get-in state (st/get-path state))]
(if (and (some? old-content) (some? (:id shape)))
(let [[rch uch] (generate-path-changes objects page-id shape old-content (:content shape))]
(rx/of (dch/commit-changes {:redo-changes rch
:undo-changes uch
:origin it})))

View File

@@ -60,20 +60,20 @@
old-points (->> content upg/content->points)
new-points (->> new-content upg/content->points)
point-change (->> (map hash-map old-points new-points) (reduce merge))
point-change (->> (map hash-map old-points new-points) (reduce merge))]
[rch uch] (changes/generate-path-changes objects page-id shape (:content shape) new-content)]
(if (empty? new-content)
(rx/of (dch/commit-changes {:redo-changes rch
:undo-changes uch
:origin it})
dwc/clear-edition-mode)
(rx/of (dch/commit-changes {:redo-changes rch
:undo-changes uch
:origin it})
(selection/update-selection point-change)
(fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers :moving-nodes :moving-handler))))))))
(when (and (some? new-content) (some? shape))
(let [[rch uch] (changes/generate-path-changes objects page-id shape (:content shape) new-content)]
(if (empty? new-content)
(rx/of (dch/commit-changes {:redo-changes rch
:undo-changes uch
:origin it})
dwc/clear-edition-mode)
(rx/of (dch/commit-changes {:redo-changes rch
:undo-changes uch
:origin it})
(selection/update-selection point-change)
(fn [state] (update-in state [:workspace-local :edit-path id] dissoc :content-modifiers :moving-nodes :moving-handler))))))))))
(defn modify-content-point
[content {dx :x dy :y} modifiers point]
@@ -263,7 +263,8 @@
(streams/drag-stream
(rx/concat
(->> (streams/move-handler-stream snap-toggled start-point point handler opposite points)
(rx/take-until (->> stream (rx/filter ms/mouse-up?)))
(rx/take-until (->> stream (rx/filter #(or (ms/mouse-up? %)
(streams/finish-edition? %)))))
(rx/map
(fn [{:keys [x y alt? shift?]}]
(let [pos (cond-> (gpt/point x y)

View File

@@ -26,12 +26,31 @@
(= event :interrupt) ;; ESC
(and (ms/mouse-double-click? event))))
(defn content-center
[content]
(-> content
gsh/content->selrect
gsh/center-selrect))
(defn content->points+selrect
"Given the content of a shape, calculate its points and selrect"
[shape content]
(let [transform (:transform shape (gmt/matrix))
transform-inverse (:transform-inverse shape (gmt/matrix))
center (gsh/center-shape shape)
(let [{:keys [flip-x flip-y]} shape
transform
(cond-> (:transform shape (gmt/matrix))
flip-x (gmt/scale (gpt/point -1 1))
flip-y (gmt/scale (gpt/point 1 -1)))
transform-inverse
(cond-> (gmt/matrix)
flip-x (gmt/scale (gpt/point -1 1))
flip-y (gmt/scale (gpt/point 1 -1))
:always (gmt/multiply (:transform-inverse shape (gmt/matrix))))
center (or (gsh/center-shape shape)
(content-center content))
base-content (gsh/transform-content
content
(gmt/transform-in center transform-inverse))
@@ -39,30 +58,22 @@
;; Calculates the new selrect with points given the old center
points (-> (gsh/content->selrect base-content)
(gsh/rect->points)
(gsh/transform-points center (:transform shape (gmt/matrix))))
(gsh/transform-points center transform))
points-center (gsh/center-points points)
;; Points is now the selrect but the center is different so we can create the selrect
;; through points
selrect (-> points
(gsh/transform-points points-center (:transform-inverse shape (gmt/matrix)))
(gsh/transform-points points-center transform-inverse)
(gsh/points->selrect))]
[points selrect]))
(defn update-selrect
"Updates the selrect and points for a path"
[shape]
(if (= (:rotation shape 0) 0)
(let [content (:content shape)
selrect (gsh/content->selrect content)
points (gsh/rect->points selrect)]
(assoc shape :points points :selrect selrect))
(let [content (:content shape)
[points selrect] (content->points+selrect shape content)]
(assoc shape :points points :selrect selrect))))
(let [[points selrect] (content->points+selrect shape (:content shape))]
(assoc shape :points points :selrect selrect)))
(defn closest-angle
[angle]

View File

@@ -24,6 +24,9 @@
(fn [current]
(>= (gpt/distance start current) (/ drag-threshold zoom))))
(defn finish-edition? [event]
(= (ptk/type event) :app.main.data.workspace.common/clear-edition-mode))
(defn drag-stream
([to-stream]
(drag-stream to-stream (rx/empty)))
@@ -31,7 +34,8 @@
([to-stream not-drag-stream]
(let [start @ms/mouse-position
zoom (get-in @st/state [:workspace-local :zoom] 1)
mouse-up (->> st/stream (rx/filter #(ms/mouse-up? %)))
mouse-up (->> st/stream (rx/filter #(or (finish-edition? %)
(ms/mouse-up? %))))
position-stream
(->> ms/mouse-position

View File

@@ -33,7 +33,7 @@
selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{})
points (or points selected-points)]
(when-not (empty? points)
(when (and (not (empty? points)) (some? shape))
(let [new-content (-> (tool-fn (:content shape) points)
(ups/close-subpaths))
[rch uch] (changes/generate-path-changes objects page-id shape (:content shape) new-content)]

View File

@@ -7,26 +7,26 @@
(ns app.main.exports
"The main logic for SVG export functionality."
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[app.common.uuid :as uuid]
[app.common.pages :as cp]
[app.common.math :as mth]
[app.common.geom.shapes :as gsh]
[app.common.geom.align :as gal]
[app.common.geom.point :as gpt]
[app.common.geom.matrix :as gmt]
[app.util.timers :as ts]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.uuid :as uuid]
[app.main.ui.shapes.circle :as circle]
[app.main.ui.shapes.filters :as filters]
[app.main.ui.shapes.frame :as frame]
[app.main.ui.shapes.circle :as circle]
[app.main.ui.shapes.group :as group]
[app.main.ui.shapes.image :as image]
[app.main.ui.shapes.path :as path]
[app.main.ui.shapes.rect :as rect]
[app.main.ui.shapes.text :as text]
[app.main.ui.shapes.group :as group]
[app.main.ui.shapes.shape :refer [shape-container]]
[app.main.ui.shapes.svg-raw :as svg-raw]
[app.main.ui.shapes.shape :refer [shape-container]]))
[app.main.ui.shapes.text :as text]
[app.util.timers :as ts]
[cuerdas.core :as str]
[rumext.alpha :as mf]))
(def ^:private default-color "#E8E9EA") ;; $color-canvas

View File

@@ -20,7 +20,7 @@
(cond
(= 204 status)
;; We need to send "something" so the streams listening downstream can act
(rx/of :empty)
(rx/of nil)
(= 502 status)
(rx/throw {:type :bad-gateway})

View File

@@ -192,9 +192,10 @@
on-save
(fn [event]
(let [font-family @state]
(st/emit! (df/update-font
{:id font-id
:name font-family}))
(when-not (str/blank? font-family)
(st/emit! (df/update-font
{:id font-id
:name font-family})))
(reset! edit? false)))
on-key-down

View File

@@ -329,7 +329,7 @@
(fn [e]
(reset! dragging? false)
(when (not= selected-project project-id)
(let [data {:ids selected-files
(let [data {:ids (into #{} (keys selected-files))
:project-id project-id}
mdata {:on-success on-drop-success}]
(st/emit! (dd/move-files (with-meta data mdata)))))))]

View File

@@ -79,7 +79,8 @@
:min-width? true
:top top
:left left
:options [[(tr "labels.rename") on-edit]
:options [(when-not (:is-default project)
[(tr "labels.rename") on-edit])
[(tr "dashboard.duplicate") on-duplicate]
[(tr "dashboard.pin-unpin") toggle-pin]
(when (seq teams)

View File

@@ -12,11 +12,14 @@
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.uuid :as uuid]
[app.main.store :as st]
[app.main.data.fonts :as df]
[app.main.exports :as exports]
[app.main.repo :as repo]
[app.main.ui.context :as muc]
[app.main.ui.shapes.filters :as filters]
[app.main.ui.shapes.shape :refer [shape-container]]
[app.main.ui.shapes.text.embed :refer [embed-fontfaces-style]]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
@@ -42,6 +45,9 @@
objects (reduce updt-fn objects mod-ids)
object (get objects object-id)
txt-xfm (comp (map #(get objects %))
(filter #(= :text (:type %))))
txt-objs (into [] txt-xfm mod-ids)
{:keys [width height]} (gsh/points->selrect (:points object))
@@ -72,6 +78,9 @@
]
[:& (mf/provider muc/embed-ctx) {:value true}
(when (seq txt-objs)
[:& embed-fontfaces-style {:shapes txt-objs}])
[:svg {:id "screenshot"
:view-box vbox
:width width
@@ -79,6 +88,7 @@
:version "1.1"
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns "http://www.w3.org/2000/svg"}
(case (:type object)
:frame [:& frame-wrapper {:shape object :view-box vbox}]
:group [:> shape-container {:shape object}
@@ -97,7 +107,6 @@
(assoc objects (:id object) object))
objects))
;; NOTE: for now, it is ok download the entire file for render only
;; single page but in a future we need consider to add a specific
;; backend entry point for download only the data of single page.
@@ -106,12 +115,19 @@
[{:keys [file-id page-id object-id] :as props}]
(let [objects (mf/use-state nil)]
(mf/use-effect
#(let [subs (->> (repo/query! :file {:id file-id})
(rx/subs (fn [{:keys [data]}]
(let [objs (get-in data [:pages-index page-id :objects])
objs (adapt-root-frame objs object-id)]
(reset! objects objs)))))]
(fn [] (rx/dispose! subs))))
(mf/deps file-id page-id object-id)
(fn []
(->> (rx/zip
(repo/query! :font-variants {:file-id file-id})
(repo/query! :file {:id file-id}))
(rx/subs
(fn [[fonts {:keys [data]} :as kaka]]
(when (seq fonts)
(st/emit! (df/fonts-fetched fonts)))
(let [objs (get-in data [:pages-index page-id :objects])
objs (adapt-root-frame objs object-id)]
(reset! objects objs)))))
(constantly nil)))
(when @objects
[:& object-svg {:objects @objects

View File

@@ -86,12 +86,6 @@
:weight weight})]
(p/resolved result))))
(defn- to-promise
[observable]
(p/create (fn [resolve reject]
(->> (rx/take 1 observable)
(rx/subs resolve reject)))))
(defn fetch-font-data
"Parses the CSS and retrieves the font data as DataURI."
[^string css]
@@ -137,10 +131,10 @@
{::mf/wrap-props false
::mf/wrap [#(mf/memo' % (mf/check-props ["shapes"]))]}
[props]
(let [shapes (obj/get props "shapes")
node {:children (->> shapes (map :content))}
fonts (-> node get-node-fonts memoize)
style (mf/use-state nil)]
(let [shapes (obj/get props "shapes")
node {:children (->> shapes (map :content))}
fonts (-> node get-node-fonts memoize)
style (mf/use-state nil)]
(mf/use-effect
(mf/deps fonts)

View File

@@ -75,7 +75,7 @@
(mf/defc thumbnails-summary
[{:keys [on-toggle-expand on-close total] :as props}]
[:div.thumbnails-summary
[:span.counter (str total " frames")]
[:span.counter (tr "labels.num-of-frames" (i18n/c total))]
[:span.buttons
[:span.btn-expand {:on-click on-toggle-expand} i/arrow-down]
[:span.btn-close {:on-click on-close} i/close]]])

View File

@@ -102,23 +102,20 @@
{::mf/wrap [#(mf/memo' % check-frame-props) custom-deferred]
::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
objects (unchecked-get props "objects")
thumbnail? (unchecked-get props "thumbnail?")
(let [shape (unchecked-get props "shape")
objects (unchecked-get props "objects")
thumbnail? (unchecked-get props "thumbnail?")
edition (mf/deref refs/selected-edition)
edition (mf/deref refs/selected-edition)
embed-fonts? (mf/use-ctx muc/embed-ctx)
shape (gsh/transform-shape shape)
children (mapv #(get objects %) (:shapes shape))
text-childs (->> objects
vals
(filterv #(and (= :text (:type %))
(= (:id shape) (:frame-id %)))))
shape (gsh/transform-shape shape)
children (mapv #(get objects %) (:shapes shape))
text-childs (->> (vals objects)
(filterv #(and (= :text (:type %))
(= (:id shape) (:frame-id %)))))
ds-modifier (get-in shape [:modifiers :displacement])
rendered? (mf/use-state false)
rendered? (mf/use-state false)
show-thumbnail? (and thumbnail? (some? (:thumbnail shape)))

View File

@@ -9,6 +9,7 @@
["draft-js" :as draft]
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.text :as txt]
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.selection :as dws]
@@ -154,7 +155,7 @@
:handle-return handle-return
:strip-pasted-styles true
:custom-style-fn (fn [styles _]
(-> (ted/styles-to-attrs styles)
(-> (txt/styles-to-attrs styles)
(sts/generate-text-styles)))
:block-renderer-fn #(render-block % shape)
:ref on-editor

View File

@@ -428,26 +428,27 @@
[:> text-transform-options opts]]]))
;; TODO: this need to be refactored, right now combines too much logic
;; and has a dropdown that behaves like a modal but is not a modal.
;; In summary, this need to a good UX/UI/IMPL rework.
(mf/defc typography-entry
[{:keys [typography read-only? selected? on-click on-change on-detach on-context-menu editting? focus-name? file]}]
(let [open? (mf/use-state editting?)
hover-detach (mf/use-state false)
name-input-ref (mf/use-ref nil)
value (mf/use-state (cp/merge-path-item (:path typography) (:name typography)))
(let [open? (mf/use-state editting?)
hover-detach (mf/use-state false)
name-input-ref (mf/use-ref)
#_(rt/resolve router :workspace
{:project-id (:project-id file)
:file-id (:id file)}
{:page-id (get-in file [:data :pages 0])})
handle-change
on-name-blur
(fn [event]
(reset! value (dom/get-target-val event)))
(let [content (dom/get-target-val event)]
(when-not (str/blank? content)
(on-change {:name content}))))
handle-go-to-edit
(fn [] (st/emit! (rt/nav :workspace {:project-id (:project-id file)
:file-id (:id file)}
{:page-id (get-in file [:data :pages 0])})))]
(fn []
(let [pparams {:project-id (:project-id file)
:file-id (:id file)}]
(st/emit! (rt/nav :workspace pparams))))]
(mf/use-effect
(mf/deps editting?)
@@ -459,7 +460,7 @@
(mf/deps focus-name?)
(fn []
(when focus-name?
(ts/schedule 100
(ts/schedule
#(when-let [node (mf/ref-val name-input-ref)]
(dom/focus! node)
(dom/select-text! node))))))
@@ -530,8 +531,7 @@
[:input.element-name.adv-typography-name
{:type "text"
:ref name-input-ref
:value @value
:on-change handle-change
:on-blur #(on-change {:name @value})}]]]
:default-value (cp/merge-path-item (:path typography) (:name typography))
:on-blur on-name-blur}]]]
[:& typography-options {:values typography
:on-change on-change}]])]]))

View File

@@ -310,7 +310,7 @@
[content points]
(let [segments-set (into #{}
(map (fn [[p1 p2 _]] [p1 p2]))
(map (fn [{:keys [start end]}] [start end]))
(get-segments content points))
create-line-command (fn [point other]
@@ -382,7 +382,7 @@
(defn group-segments [segments]
(loop [result []
[point-a point-b :as segment] (first segments)
{point-a :start point-b :end :as segment} (first segments)
segments (rest segments)]
(if (nil? segment)

View File

@@ -19,190 +19,12 @@
[clojure.walk :as walk]
[cuerdas.core :as str]))
;; --- INLINE STYLES ENCODING
(defn encode-style-value
[v]
(cond
(uuid? v) (str "u:" v)
(string? v) (str "s:" v)
(number? v) (str "n:" v)
(keyword? v) (str "k:" (name v))
(map? v) (str "m:" (t/encode v))
(nil? v) (str "z:null")
:else (str "o:" v)))
(defn decode-style-value
[v]
(let [prefix (subs v 0 2)]
(case prefix
"s:" (subs v 2)
"n:" (js/Number (subs v 2))
"k:" (keyword (subs v 2))
"m:" (t/decode (subs v 2))
"u:" (uuid/uuid (subs v 2))
"z:" nil
"o:" (subs v 2)
v)))
(defn encode-style
[key val]
(let [k (d/name key)
v (encode-style-value val)]
(str "PENPOT$$$" k "$$$" v)))
(defn encode-style-prefix
[key]
(let [k (d/name key)]
(str "PENPOT$$$" k "$$$")))
(defn decode-style
[style]
(let [[_ k v] (str/split style "$$$" 3)]
[(keyword k) (decode-style-value v)]))
(defn attrs-to-styles
[attrs]
(reduce-kv (fn [res k v]
(conj res (encode-style k v)))
#{}
attrs))
(defn styles-to-attrs
[styles]
(persistent!
(reduce (fn [result style]
(if (str/starts-with? style "PENPOT")
(if (= style "PENPOT_SELECTION")
(assoc! result :penpot-selection true)
(let [[_ k v] (str/split style "$$$" 3)]
(assoc! result (keyword k) (decode-style-value v))))
result))
(transient {})
(seq styles))))
;; --- CONVERSION
(defn- parse-draft-styles
"Parses draft-js style ranges, converting encoded style name into a
key/val pair of data."
[styles]
(->> styles
(filter #(str/starts-with? (obj/get % "style") "PENPOT$$$"))
(map (fn [item]
(let [[_ k v] (-> (obj/get item "style")
(str/split "$$$" 3))]
{:key (keyword k)
:val (decode-style-value v)
:offset (obj/get item "offset")
:length (obj/get item "length")})))))
(defn- build-style-index
"Generates a character based index with associated styles map."
[text ranges]
(loop [result (->> (range (count text))
(mapv (constantly {}))
(transient))
ranges (seq ranges)]
(if-let [{:keys [offset length] :as item} (first ranges)]
(recur (reduce (fn [result index]
(let [prev (get result index)]
(assoc! result index (assoc prev (:key item) (:val item)))))
result
(range offset (+ offset length)))
(rest ranges))
(persistent! result))))
(defn- convert-from-draft
[content]
(letfn [(build-text [text part]
(let [start (ffirst part)
end (inc (first (last part)))]
(-> (second (first part))
(assoc :text (subs text start end)))))
(split-texts [text styles]
(let [children (->> (parse-draft-styles styles)
(build-style-index text)
(d/enumerate)
(partition-by second)
(mapv #(build-text text %)))]
(cond-> children
(empty? children)
(conj {:text ""}))))
(build-paragraph [block]
(let [key (obj/get block "key")
text (obj/get block "text")
styles (obj/get block "inlineStyleRanges")
data (obj/get block "data")]
(-> (js->clj data :keywordize-keys true)
(assoc :key key)
(assoc :type "paragraph")
(assoc :children (split-texts text styles)))))]
{:type "root"
:children
[{:type "paragraph-set"
:children (->> (obj/get content "blocks")
(mapv build-paragraph))}]}))
(defn- convert-to-draft
[root]
(letfn [(process-attr [children ranges [k v]]
(loop [children (seq children)
start nil
offset 0
ranges ranges]
(if-let [{:keys [text] :as item} (first children)]
(if (= v (get item k ::novalue))
(recur (rest children)
(if (nil? start) offset start)
(+ offset (alength text))
ranges)
(if (some? start)
(recur (rest children)
nil
(+ offset (alength text))
(arr/conj! ranges #js {:offset start
:length (- offset start)
:style (encode-style k v)}))
(recur (rest children)
start
(+ offset (alength text))
ranges)))
(cond-> ranges
(some? start)
(arr/conj! #js {:offset start
:length (- offset start)
:style (encode-style k v)})))))
(calc-ranges [{:keys [children] :as blok}]
(let [xform (comp (map #(dissoc % :key :text))
(remove empty?)
(mapcat vec)
(distinct))
proc #(process-attr children %1 %2)]
(transduce xform proc #js [] children)))
(build-block [result {:keys [key children] :as paragraph}]
(->> #js {:key key
:depth 0
:text (apply str (map :text children))
:data (-> (dissoc paragraph :key :children :type)
(clj->js))
:type "unstyled"
:entityRanges #js []
:inlineStyleRanges (calc-ranges paragraph)}
(arr/conj! result)))]
#js {:blocks (reduce build-block #js [] (txt/node-seq #(= (:type %) "paragraph") root))
:entityMap #js {}}))
(defn immutable-map->map
[obj]
(into {} (map (fn [[k v]] [(keyword k) v])) (seq obj)))
;; --- DRAFT-JS HELPERS
(defn create-editor-state
@@ -219,13 +41,14 @@
(defn import-content
[content]
(-> content convert-to-draft draft/convertFromRaw))
(-> content txt/convert-to-draft clj->js draft/convertFromRaw))
(defn export-content
[content]
(-> content
(draft/convertToRaw)
(convert-from-draft)))
(js->clj :keywordize-keys true)
(txt/convert-from-draft)))
(defn get-editor-current-content
[state]
@@ -256,7 +79,7 @@
(defn get-editor-current-inline-styles
[state]
(-> (.getCurrentInlineStyle ^js state)
(styles-to-attrs)))
(txt/styles-to-attrs)))
(defn update-editor-current-block-data
[state attrs]
@@ -264,7 +87,7 @@
(defn update-editor-current-inline-styles
[state attrs]
(impl/applyInlineStyle state (attrs-to-styles attrs)))
(impl/applyInlineStyle state (txt/attrs-to-styles attrs)))
(defn editor-split-block
[state]

View File

@@ -0,0 +1,27 @@
(ns app.test-draft-conversion
(:require
[app.common.data :as d]
[app.common.text :as txt]
[cljs.test :as t :include-macros true]
[cljs.pprint :refer [pprint]]))
(t/deftest test-basic-conversion-roundtrip
(let [text "qwqw 🠒"
content {:type "root",
:children
[{:type "paragraph-set",
:children
[{:key "cfjh",
:type "paragraph",
:children
[{:font-id "gfont-roboto",
:font-family "Roboto",
:font-variant-id "regular",
:font-weight "400",
:font-style "normal",
:text text}]}]}]}]
;; (cljs.pprint/pprint (txt/convert-to-draft content))
;; (cljs.pprint/pprint (txt/convert-from-draft (txt/convert-to-draft content)))
(t/is (= (txt/convert-from-draft (txt/convert-to-draft content))
content))))

View File

@@ -1,16 +1,17 @@
(ns app.test-helpers.pages
(:require [cljs.test :as t :include-macros true]
[cljs.pprint :refer [pprint]]
[beicon.core :as rx]
[potok.core :as ptk]
[app.common.uuid :as uuid]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
[app.main.data.workspace :as dw]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.libraries-helpers :as dwlh]))
(:require
[cljs.test :as t :include-macros true]
[cljs.pprint :refer [pprint]]
[beicon.core :as rx]
[potok.core :as ptk]
[app.common.uuid :as uuid]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
[app.main.data.workspace :as dw]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.libraries-helpers :as dwlh]))
;; ---- Helpers to manage pages and objects
@@ -81,21 +82,24 @@
([state label ids] (group-shapes state label ids "Group-"))
([state label ids prefix]
(let [page (current-page state)
shapes (dwg/shapes-for-grouping (:objects page) ids)
shapes (dwg/shapes-for-grouping (:objects page) ids)]
(if (empty? shapes)
state
(let [[group rchanges uchanges]
(dwg/prepare-create-group (:objects page) (:id page) shapes prefix true)]
[group rchanges uchanges]
(dwg/prepare-create-group (:id page) shapes prefix true)]
(swap! idmap assoc label (:id group))
(update state :workspace-data
cp/process-changes rchanges))))
(swap! idmap assoc label (:id group))
(update state :workspace-data
cp/process-changes rchanges))))))
(defn make-component
[state label ids]
(let [page (current-page state)
objects (wsh/lookup-page-objects state page-id)
shapes (dwg/shapes-for-grouping objects ids)
[group rchanges uchanges]
(dwlh/generate-add-component ids
(dwlh/generate-add-component shapes
(:objects page)
(:id page)
current-file-id)]

View File

@@ -981,6 +981,11 @@ msgid_plural "labels.num-of-files"
msgstr[0] "1 file"
msgstr[1] "%s files"
msgid "labels.num-of-frames"
msgid_plural "labels.num-of-frames"
msgstr[0] "1 artboard"
msgstr[1] "%s artboards"
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.num-of-projects"
msgid_plural "labels.num-of-projects"
@@ -1438,11 +1443,11 @@ msgstr "%s - Penpot"
#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs
msgid "viewer.empty-state"
msgstr "No frames found on the page."
msgstr "No artboards found on the page."
#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs
msgid "viewer.frame-not-found"
msgstr "Frame not found."
msgstr "Artboard not found."
#: src/app/main/ui/viewer/header.cljs
msgid "viewer.header.dont-show-interactions"

View File

@@ -983,6 +983,11 @@ msgid_plural "labels.num-of-files"
msgstr[0] "1 archivo"
msgstr[1] "%s archivos"
msgid "labels.num-of-frames"
msgid_plural "labels.num-of-frames"
msgstr[0] "1 tablero"
msgstr[1] "%s tableros"
#: src/app/main/ui/dashboard/team.cljs
msgid "labels.num-of-projects"
msgid_plural "labels.num-of-projects"

View File

@@ -1 +1 @@
1.6.1-alpha
1.6.5-alpha