mirror of
https://github.com/penpot/penpot.git
synced 2026-02-07 13:13:31 -05:00
Compare commits
43 Commits
1.6.1-alph
...
1.6.5-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
beaea73276 | ||
|
|
50e0284084 | ||
|
|
e08788190d | ||
|
|
44441ae928 | ||
|
|
e42e1e8751 | ||
|
|
ae4b743ea4 | ||
|
|
782e060448 | ||
|
|
bb5804cde3 | ||
|
|
7819757759 | ||
|
|
b861e261ed | ||
|
|
17b32d6518 | ||
|
|
d2359046c4 | ||
|
|
8a700170b0 | ||
|
|
8c68e29bf3 | ||
|
|
1a81631886 | ||
|
|
634fe2c458 | ||
|
|
053d46144e | ||
|
|
b2e7bb6be1 | ||
|
|
bae709df5b | ||
|
|
1b495ebad1 | ||
|
|
4e0289b341 | ||
|
|
056fce9187 | ||
|
|
9f034c7e7e | ||
|
|
a6de4e3742 | ||
|
|
2d6a375afc | ||
|
|
585e5d0199 | ||
|
|
71524fe649 | ||
|
|
55d2768807 | ||
|
|
3d7a3f27d5 | ||
|
|
46448bc5c7 | ||
|
|
2f8f1f0b9a | ||
|
|
d572fdac9b | ||
|
|
ac41ed1af4 | ||
|
|
f47bb6bcd0 | ||
|
|
a3eb5e2928 | ||
|
|
d4bf3ef6fd | ||
|
|
ca5c374ecd | ||
|
|
69ea8229ca | ||
|
|
4d19b87fff | ||
|
|
8847047fd1 | ||
|
|
6e8a5015c9 | ||
|
|
e8919ee340 | ||
|
|
f8f506a8be |
@@ -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"]}
|
||||
|
||||
|
||||
11
.clj-kondo/hooks/export.clj
Normal file
11
.clj-kondo/hooks/export.clj
Normal 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
2
.gitignore
vendored
@@ -30,6 +30,8 @@ node_modules
|
||||
/exporter/target
|
||||
/exporter/.shadow-cljs
|
||||
/docker/images/bundle*
|
||||
/common/.shadow-cljs
|
||||
/common/target
|
||||
/.clj-kondo/.cache
|
||||
/bundle*
|
||||
/media
|
||||
|
||||
64
CHANGES.md
64
CHANGES.md
@@ -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
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)))
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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})))))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {}}))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" \
|
||||
|
||||
@@ -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))))))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]}]
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) [] []]
|
||||
|
||||
@@ -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})))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)))))))]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]]])
|
||||
|
||||
@@ -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)))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}]])]]))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
27
frontend/tests/app/test_draft_conversion.cljs
Normal file
27
frontend/tests/app/test_draft_conversion.cljs
Normal 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))))
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.6.1-alpha
|
||||
1.6.5-alpha
|
||||
|
||||
Reference in New Issue
Block a user