mirror of
https://github.com/penpot/penpot.git
synced 2025-05-10 17:16:37 +02:00
Merge remote-tracking branch 'origin/main' into develop
This commit is contained in:
commit
3d8c41cd69
52 changed files with 625 additions and 568 deletions
|
@ -18,6 +18,14 @@
|
||||||
### :arrow_up: Deps updates
|
### :arrow_up: Deps updates
|
||||||
### :heart: Community contributions by (Thank you!)
|
### :heart: Community contributions by (Thank you!)
|
||||||
|
|
||||||
|
## 1.13.1-beta
|
||||||
|
|
||||||
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
|
- Fix problem with text positioning
|
||||||
|
- Fix issue with thumbnail generation before fonts loading
|
||||||
|
- Fix unable to hide artboards
|
||||||
|
- Fix problem with fonts cache causing hanging in certain pages
|
||||||
|
|
||||||
## 1.13.0-beta
|
## 1.13.0-beta
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
|
[app.db.sql :as sql]
|
||||||
[app.rpc.mutations.files :as m.files]
|
[app.rpc.mutations.files :as m.files]
|
||||||
[app.rpc.queries.profile :as profile]
|
[app.rpc.queries.profile :as profile]
|
||||||
[app.util.blob :as blob]
|
[app.util.blob :as blob]
|
||||||
|
@ -59,7 +60,7 @@
|
||||||
"select revn, changes, data from file_change where file_id=? and revn = ?")
|
"select revn, changes, data from file_change where file_id=? and revn = ?")
|
||||||
|
|
||||||
(defn prepare-response
|
(defn prepare-response
|
||||||
[{:keys [params] :as request} body]
|
[{:keys [params] :as request} body filename]
|
||||||
(when-not body
|
(when-not body
|
||||||
(ex/raise :type :not-found
|
(ex/raise :type :not-found
|
||||||
:code :enpty-data
|
:code :enpty-data
|
||||||
|
@ -69,8 +70,7 @@
|
||||||
:body body
|
:body body
|
||||||
:headers {"content-type" "application/transit+json"})
|
:headers {"content-type" "application/transit+json"})
|
||||||
(contains? params :download)
|
(contains? params :download)
|
||||||
(update :headers assoc "content-disposition" "attachment")))
|
(update :headers assoc "content-disposition" (str "attachment; filename=" filename))))
|
||||||
|
|
||||||
|
|
||||||
(defn- retrieve-file-data
|
(defn- retrieve-file-data
|
||||||
[{:keys [pool]} {:keys [params] :as request}]
|
[{:keys [pool]} {:keys [params] :as request}]
|
||||||
|
@ -78,8 +78,9 @@
|
||||||
(ex/raise :type :authentication
|
(ex/raise :type :authentication
|
||||||
:code :only-admins-allowed))
|
:code :only-admins-allowed))
|
||||||
|
|
||||||
(let [file-id (some-> (get-in request [:params :file-id]) uuid/uuid)
|
(let [file-id (some-> (get-in request [:params :file-id]) uuid/uuid)
|
||||||
revn (some-> (get-in request [:params :revn]) d/parse-integer)]
|
revn (some-> (get-in request [:params :revn]) d/parse-integer)
|
||||||
|
filename (str file-id)]
|
||||||
(when-not file-id
|
(when-not file-id
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :missing-arguments))
|
:code :missing-arguments))
|
||||||
|
@ -88,9 +89,9 @@
|
||||||
(some-> (db/exec-one! pool [sql:retrieve-single-change file-id revn]) :data)
|
(some-> (db/exec-one! pool [sql:retrieve-single-change file-id revn]) :data)
|
||||||
(some-> (db/get-by-id pool :file file-id) :data))]
|
(some-> (db/get-by-id pool :file file-id) :data))]
|
||||||
(if (contains? params :download)
|
(if (contains? params :download)
|
||||||
(-> (prepare-response request data)
|
(-> (prepare-response request data filename)
|
||||||
(update :headers assoc "content-type" "application/octet-stream"))
|
(update :headers assoc "content-type" "application/octet-stream"))
|
||||||
(prepare-response request (some-> data blob/decode))))))
|
(prepare-response request (some-> data blob/decode) filename)))))
|
||||||
|
|
||||||
(defn- upload-file-data
|
(defn- upload-file-data
|
||||||
[{:keys [pool]} {:keys [profile-id params] :as request}]
|
[{:keys [pool]} {:keys [profile-id params] :as request}]
|
||||||
|
@ -98,12 +99,20 @@
|
||||||
data (some-> params :file :path fs/slurp-bytes blob/decode)]
|
data (some-> params :file :path fs/slurp-bytes blob/decode)]
|
||||||
|
|
||||||
(if (and data project-id)
|
(if (and data project-id)
|
||||||
(let [fname (str "imported-file-" (dt/now))]
|
(let [fname (str "imported-file-" (dt/now))
|
||||||
(m.files/create-file pool {:id (uuid/next)
|
file-id (try
|
||||||
:name fname
|
(uuid/uuid (-> params :file :filename))
|
||||||
:project-id project-id
|
(catch Exception _ (uuid/next)))
|
||||||
:profile-id profile-id
|
file (db/exec-one! pool (sql/select :file {:id file-id}))]
|
||||||
:data data})
|
(if file
|
||||||
|
(db/update! pool :file
|
||||||
|
{:data (blob/encode data)}
|
||||||
|
{:id file-id})
|
||||||
|
(m.files/create-file pool {:id file-id
|
||||||
|
:name fname
|
||||||
|
:project-id project-id
|
||||||
|
:profile-id profile-id
|
||||||
|
:data data}))
|
||||||
(yrs/response 200 "OK"))
|
(yrs/response 200 "OK"))
|
||||||
(yrs/response 500 "ERROR"))))
|
(yrs/response 500 "ERROR"))))
|
||||||
|
|
||||||
|
@ -121,8 +130,9 @@
|
||||||
(ex/raise :type :authentication
|
(ex/raise :type :authentication
|
||||||
:code :only-admins-allowed))
|
:code :only-admins-allowed))
|
||||||
|
|
||||||
(let [file-id (some-> (get-in request [:params :id]) uuid/uuid)
|
(let [file-id (some-> (get-in request [:params :id]) uuid/uuid)
|
||||||
revn (or (get-in request [:params :revn]) "latest")]
|
revn (or (get-in request [:params :revn]) "latest")
|
||||||
|
filename (str file-id)]
|
||||||
|
|
||||||
(when (or (not file-id) (not revn))
|
(when (or (not file-id) (not revn))
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
|
@ -132,7 +142,7 @@
|
||||||
(cond
|
(cond
|
||||||
(d/num-string? revn)
|
(d/num-string? revn)
|
||||||
(let [item (db/exec-one! pool [sql:retrieve-single-change file-id (d/parse-integer revn)])]
|
(let [item (db/exec-one! pool [sql:retrieve-single-change file-id (d/parse-integer revn)])]
|
||||||
(prepare-response request (some-> item :changes blob/decode vec)))
|
(prepare-response request (some-> item :changes blob/decode vec) filename))
|
||||||
|
|
||||||
(str/includes? revn ":")
|
(str/includes? revn ":")
|
||||||
(let [[start end] (->> (str/split revn #":")
|
(let [[start end] (->> (str/split revn #":")
|
||||||
|
@ -144,7 +154,8 @@
|
||||||
(map :changes)
|
(map :changes)
|
||||||
(map blob/decode)
|
(map blob/decode)
|
||||||
(mapcat identity)
|
(mapcat identity)
|
||||||
(vec))))
|
(vec))
|
||||||
|
filename))
|
||||||
:else
|
:else
|
||||||
(ex/raise :type :validation :code :invalid-arguments))))
|
(ex/raise :type :validation :code :invalid-arguments))))
|
||||||
|
|
||||||
|
@ -176,8 +187,7 @@
|
||||||
(some-> report :error :trace))
|
(some-> report :error :trace))
|
||||||
:params (:params report)}]
|
:params (:params report)}]
|
||||||
(-> (io/resource "templates/error-report.tmpl")
|
(-> (io/resource "templates/error-report.tmpl")
|
||||||
(tmpl/render params))))
|
(tmpl/render params))))]
|
||||||
]
|
|
||||||
|
|
||||||
(when-not (authorized? pool request)
|
(when-not (authorized? pool request)
|
||||||
(ex/raise :type :authentication
|
(ex/raise :type :authentication
|
||||||
|
|
|
@ -81,14 +81,28 @@
|
||||||
:timeout 6000
|
:timeout 6000
|
||||||
:method :get}))
|
:method :get}))
|
||||||
|
|
||||||
(validate-response [{:keys [status body] :as res}]
|
(retrieve-emails []
|
||||||
(when-not (= 200 status)
|
(if (some? (:emails-uri provider))
|
||||||
|
(http-client {:uri (:emails-uri provider)
|
||||||
|
:headers {"Authorization" (str (:type tdata) " " (:token tdata))}
|
||||||
|
:timeout 6000
|
||||||
|
:method :get})
|
||||||
|
(p/resolved {:status 200})))
|
||||||
|
|
||||||
|
(validate-response [[retrieve-res emails-res]]
|
||||||
|
(when-not (s/int-in-range? 200 300 (:status retrieve-res))
|
||||||
(ex/raise :type :internal
|
(ex/raise :type :internal
|
||||||
:code :unable-to-retrieve-user-info
|
:code :unable-to-retrieve-user-info
|
||||||
:hint "unable to retrieve user info"
|
:hint "unable to retrieve user info"
|
||||||
:http-status status
|
:http-status (:status retrieve-res)
|
||||||
:http-body body))
|
:http-body (:body retrieve-res)))
|
||||||
res)
|
(when-not (s/int-in-range? 200 300 (:status emails-res))
|
||||||
|
(ex/raise :type :internal
|
||||||
|
:code :unable-to-retrieve-user-info
|
||||||
|
:hint "unable to retrieve user info"
|
||||||
|
:http-status (:status emails-res)
|
||||||
|
:http-body (:body emails-res)))
|
||||||
|
[retrieve-res emails-res])
|
||||||
|
|
||||||
(get-email [info]
|
(get-email [info]
|
||||||
(let [attr-kw (cf/get :oidc-email-attr :email)]
|
(let [attr-kw (cf/get :oidc-email-attr :email)]
|
||||||
|
@ -98,10 +112,13 @@
|
||||||
(let [attr-kw (cf/get :oidc-name-attr :name)]
|
(let [attr-kw (cf/get :oidc-name-attr :name)]
|
||||||
(get info attr-kw)))
|
(get info attr-kw)))
|
||||||
|
|
||||||
(process-response [{:keys [body]}]
|
(process-response [[retrieve-res emails-res]]
|
||||||
(let [info (json/read body)]
|
(let [info (json/read (:body retrieve-res))
|
||||||
|
email (if (some? (:extract-email-callback provider))
|
||||||
|
((:extract-email-callback provider) emails-res)
|
||||||
|
(get-email info))]
|
||||||
{:backend (:name provider)
|
{:backend (:name provider)
|
||||||
:email (get-email info)
|
:email email
|
||||||
:fullname (get-name info)
|
:fullname (get-name info)
|
||||||
:props (->> (dissoc info :name :email)
|
:props (->> (dissoc info :name :email)
|
||||||
(qualify-props provider))}))
|
(qualify-props provider))}))
|
||||||
|
@ -116,7 +133,7 @@
|
||||||
:info info))
|
:info info))
|
||||||
info)]
|
info)]
|
||||||
|
|
||||||
(-> (retrieve)
|
(-> (p/all [(retrieve) (retrieve-emails)])
|
||||||
(p/then' validate-response)
|
(p/then' validate-response)
|
||||||
(p/then' process-response)
|
(p/then' process-response)
|
||||||
(p/then' validate-info))))
|
(p/then' validate-info))))
|
||||||
|
@ -386,15 +403,25 @@
|
||||||
(assoc-in cfg [:providers "google"] opts))
|
(assoc-in cfg [:providers "google"] opts))
|
||||||
cfg)))
|
cfg)))
|
||||||
|
|
||||||
|
(defn extract-github-email
|
||||||
|
[response]
|
||||||
|
(let [emails (json/read (:body response))
|
||||||
|
primary-email (->> emails
|
||||||
|
(filter #(:primary %))
|
||||||
|
first)]
|
||||||
|
(:email primary-email)))
|
||||||
|
|
||||||
(defn- initialize-github-provider
|
(defn- initialize-github-provider
|
||||||
[cfg]
|
[cfg]
|
||||||
(let [opts {:client-id (cf/get :github-client-id)
|
(let [opts {:client-id (cf/get :github-client-id)
|
||||||
:client-secret (cf/get :github-client-secret)
|
:client-secret (cf/get :github-client-secret)
|
||||||
:scopes #{"read:user" "user:email"}
|
:scopes #{"read:user" "user:email"}
|
||||||
:auth-uri "https://github.com/login/oauth/authorize"
|
:auth-uri "https://github.com/login/oauth/authorize"
|
||||||
:token-uri "https://github.com/login/oauth/access_token"
|
:token-uri "https://github.com/login/oauth/access_token"
|
||||||
:user-uri "https://api.github.com/user"
|
:emails-uri "https://api.github.com/user/emails"
|
||||||
:name "github"}]
|
:extract-email-callback extract-github-email
|
||||||
|
:user-uri "https://api.github.com/user"
|
||||||
|
:name "github"}]
|
||||||
(if (and (string? (:client-id opts))
|
(if (and (string? (:client-id opts))
|
||||||
(string? (:client-secret opts)))
|
(string? (:client-secret opts)))
|
||||||
(do
|
(do
|
||||||
|
|
|
@ -123,7 +123,7 @@
|
||||||
(defmethod process-token :team-invitation
|
(defmethod process-token :team-invitation
|
||||||
[cfg {:keys [profile-id token]} {:keys [member-id] :as claims}]
|
[cfg {:keys [profile-id token]} {:keys [member-id] :as claims}]
|
||||||
(us/assert ::team-invitation-claims claims)
|
(us/assert ::team-invitation-claims claims)
|
||||||
#_(let [conn (:conn cfg)
|
(let [conn (:conn cfg)
|
||||||
team-id (:team-id claims)
|
team-id (:team-id claims)
|
||||||
member-email (:member-email claims)
|
member-email (:member-email claims)
|
||||||
invitation (db/get-by-params conn :team-invitation
|
invitation (db/get-by-params conn :team-invitation
|
||||||
|
|
|
@ -99,6 +99,27 @@
|
||||||
|
|
||||||
(update data :pages-index d/update-vals update-page)))
|
(update data :pages-index d/update-vals update-page)))
|
||||||
|
|
||||||
|
(defn repair-idless-components
|
||||||
|
"There are some files that contains components with no :id attribute.
|
||||||
|
This function detects them and repairs it.
|
||||||
|
|
||||||
|
Use it with the update-file function above."
|
||||||
|
[data]
|
||||||
|
(letfn [(update-component [id component]
|
||||||
|
(if (nil? (:id component))
|
||||||
|
(do
|
||||||
|
(prn (:id data) "Broken component" (:name component) id)
|
||||||
|
(assoc component :id id))
|
||||||
|
component))]
|
||||||
|
|
||||||
|
(update data :components #(d/mapm update-component %))))
|
||||||
|
|
||||||
|
(defn analyze-idless-components
|
||||||
|
"Scan all files to check if there are any one with idless components.
|
||||||
|
(Does not save the changes, only used to detect affected files)."
|
||||||
|
[file _]
|
||||||
|
(repair-idless-components (:data file)))
|
||||||
|
|
||||||
;; (defn check-image-shapes
|
;; (defn check-image-shapes
|
||||||
;; [{:keys [data] :as file} stats]
|
;; [{:keys [data] :as file} stats]
|
||||||
;; (println "=> analizing file:" (:name file) (:id file))
|
;; (println "=> analizing file:" (:name file) (:id file))
|
||||||
|
@ -138,9 +159,11 @@
|
||||||
(loop [cursor (dt/now)
|
(loop [cursor (dt/now)
|
||||||
chunks 0]
|
chunks 0]
|
||||||
(when (< chunks max-chunks)
|
(when (< chunks max-chunks)
|
||||||
(when-let [chunk (retrieve-chunk conn cursor)]
|
(let [chunk (retrieve-chunk conn cursor)]
|
||||||
(let [cursor (-> chunk last :modified-at)]
|
(when-not (empty? chunk)
|
||||||
(process-chunk chunk)
|
(let [cursor (-> chunk last :modified-at)]
|
||||||
(Thread/sleep (inst-ms (dt/duration sleep)))
|
(process-chunk chunk)
|
||||||
(recur cursor (inc chunks))))))
|
(Thread/sleep (inst-ms (dt/duration sleep)))
|
||||||
|
(recur cursor (inc chunks)))))))
|
||||||
@stats))))
|
@stats))))
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,15 @@
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) UXBOX Labs SL
|
;; Copyright (c) UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.common.geom.shapes.corners)
|
(ns app.common.geom.shapes.corners
|
||||||
|
(:require
|
||||||
|
[app.common.math :as mth]))
|
||||||
|
|
||||||
|
(defn- zero-div
|
||||||
|
[a b]
|
||||||
|
(if (mth/almost-zero? b)
|
||||||
|
##Inf
|
||||||
|
(/ a b)))
|
||||||
|
|
||||||
(defn fix-radius
|
(defn fix-radius
|
||||||
;; https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
|
;; https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
|
||||||
|
@ -16,17 +24,19 @@
|
||||||
;; > the sum of the two corresponding radii of the corners on side i, and Ltop = Lbottom = the width of the box, and
|
;; > the sum of the two corresponding radii of the corners on side i, and Ltop = Lbottom = the width of the box, and
|
||||||
;; > Lleft = Lright = the height of the box. If f < 1, then all corner radii are reduced by multiplying them by f.
|
;; > Lleft = Lright = the height of the box. If f < 1, then all corner radii are reduced by multiplying them by f.
|
||||||
([width height r]
|
([width height r]
|
||||||
(let [f (min (/ width (* 2 r))
|
(let [f (min 1
|
||||||
(/ height (* 2 r)))]
|
(zero-div width (* 2 r))
|
||||||
|
(zero-div height (* 2 r)))]
|
||||||
(if (< f 1)
|
(if (< f 1)
|
||||||
(* r f)
|
(* r f)
|
||||||
r)))
|
r)))
|
||||||
|
|
||||||
([width height r1 r2 r3 r4]
|
([width height r1 r2 r3 r4]
|
||||||
(let [f (min (/ width (+ r1 r2))
|
(let [f (min 1
|
||||||
(/ height (+ r2 r3))
|
(zero-div width (+ r1 r2))
|
||||||
(/ width (+ r3 r4))
|
(zero-div height (+ r2 r3))
|
||||||
(/ height (+ r4 r1)))]
|
(zero-div width (+ r3 r4))
|
||||||
|
(zero-div height (+ r4 r1)))]
|
||||||
(if (< f 1)
|
(if (< f 1)
|
||||||
[(* r1 f) (* r2 f) (* r3 f) (* r4 f)]
|
[(* r1 f) (* r2 f) (* r3 f) (* r4 f)]
|
||||||
[r1 r2 r3 r4]))))
|
[r1 r2 r3 r4]))))
|
||||||
|
@ -34,7 +44,7 @@
|
||||||
(defn shape-corners-1
|
(defn shape-corners-1
|
||||||
"Retrieve the effective value for the corner given a single value for corner."
|
"Retrieve the effective value for the corner given a single value for corner."
|
||||||
[{:keys [width height rx] :as shape}]
|
[{:keys [width height rx] :as shape}]
|
||||||
(if (some? rx)
|
(if (and (some? rx) (not (mth/almost-zero? rx)))
|
||||||
(fix-radius width height rx)
|
(fix-radius width height rx)
|
||||||
0))
|
0))
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
[app.common.colors :as clr]
|
[app.common.colors :as clr]
|
||||||
[app.common.uuid :as uuid]))
|
[app.common.uuid :as uuid]))
|
||||||
|
|
||||||
(def file-version 17)
|
(def file-version 18)
|
||||||
(def default-color clr/gray-20)
|
(def default-color clr/gray-20)
|
||||||
(def root uuid/zero)
|
(def root uuid/zero)
|
||||||
|
|
||||||
|
|
|
@ -400,5 +400,20 @@
|
||||||
(update :pages-index d/update-vals update-container)
|
(update :pages-index d/update-vals update-container)
|
||||||
(update :components d/update-vals update-container))))
|
(update :components d/update-vals update-container))))
|
||||||
|
|
||||||
|
;;Remove position-data to solve a bug with the text positioning
|
||||||
|
(defmethod migrate 18
|
||||||
|
[data]
|
||||||
|
(letfn [(update-object [object]
|
||||||
|
(cond-> object
|
||||||
|
(cph/text-shape? object)
|
||||||
|
(dissoc :position-data)))
|
||||||
|
|
||||||
|
(update-container [container]
|
||||||
|
(update container :objects d/update-vals update-object))]
|
||||||
|
|
||||||
|
(-> data
|
||||||
|
(update :pages-index d/update-vals update-container)
|
||||||
|
(update :components d/update-vals update-container))))
|
||||||
|
|
||||||
;; TODO: pending to do a migration for delete already not used fill
|
;; TODO: pending to do a migration for delete already not used fill
|
||||||
;; and stroke props. This should be done for >1.14.x version.
|
;; and stroke props. This should be done for >1.14.x version.
|
||||||
|
|
|
@ -44,8 +44,7 @@ marked.use({renderer});
|
||||||
// Templates
|
// Templates
|
||||||
|
|
||||||
function readLocales() {
|
function readLocales() {
|
||||||
// const langs = ["ar", "he", "ca", "de", "el", "en", "es", "fr", "it", "tr", "ru", "zh_CN", "pt_BR", "ro"];
|
const langs = ["ar", "ca", "de", "el", "en", "es", "fa", "fr", "he", "nb_NO", "pl", "pt_BR", "ro", "ru", "tr", "zh_CN", "zh_Hant"];
|
||||||
const langs = ["ar", "he", "ca", "de", "el", "en", "es", "fr", "tr", "ru", "zh_CN", "pt_BR", "ro"];
|
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|
||||||
for (let lang of langs) {
|
for (let lang of langs) {
|
||||||
|
|
|
@ -50,6 +50,10 @@
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-form {
|
.avatar-form {
|
||||||
|
|
|
@ -107,6 +107,30 @@
|
||||||
(rx/of (update-indices page-id changes))))
|
(rx/of (update-indices page-id changes))))
|
||||||
(rx/empty))))))
|
(rx/empty))))))
|
||||||
|
|
||||||
|
(defn changed-frames
|
||||||
|
"Extracts the frame-ids changed in the given changes"
|
||||||
|
[changes objects]
|
||||||
|
|
||||||
|
(let [change->ids
|
||||||
|
(fn [change]
|
||||||
|
(case (:type change)
|
||||||
|
:add-obj
|
||||||
|
[(:parent-id change)]
|
||||||
|
|
||||||
|
(:mod-obj :del-obj)
|
||||||
|
[(:id change)]
|
||||||
|
|
||||||
|
:mov-objects
|
||||||
|
(d/concat-vec (:shapes change) [(:parent-id change)])
|
||||||
|
|
||||||
|
[]))]
|
||||||
|
(into #{}
|
||||||
|
(comp (mapcat change->ids)
|
||||||
|
(keep #(if (= :frame (get-in objects [% :type]))
|
||||||
|
%
|
||||||
|
(get-in objects [% :frame-id])))
|
||||||
|
(remove #(= uuid/zero %)))
|
||||||
|
changes)))
|
||||||
|
|
||||||
(defn commit-changes
|
(defn commit-changes
|
||||||
[{:keys [redo-changes undo-changes
|
[{:keys [redo-changes undo-changes
|
||||||
|
@ -115,15 +139,18 @@
|
||||||
(log/debug :msg "commit-changes"
|
(log/debug :msg "commit-changes"
|
||||||
:js/redo-changes redo-changes
|
:js/redo-changes redo-changes
|
||||||
:js/undo-changes undo-changes)
|
:js/undo-changes undo-changes)
|
||||||
(let [error (volatile! nil)]
|
(let [error (volatile! nil)
|
||||||
|
page-id (:current-page-id @st/state)
|
||||||
|
frames (changed-frames redo-changes (wsh/lookup-page-objects @st/state))]
|
||||||
(ptk/reify ::commit-changes
|
(ptk/reify ::commit-changes
|
||||||
cljs.core/IDeref
|
cljs.core/IDeref
|
||||||
(-deref [_]
|
(-deref [_]
|
||||||
|
|
||||||
{:file-id file-id
|
{:file-id file-id
|
||||||
:hint-events @st/last-events
|
:hint-events @st/last-events
|
||||||
:hint-origin (ptk/type origin)
|
:hint-origin (ptk/type origin)
|
||||||
:changes redo-changes})
|
:changes redo-changes
|
||||||
|
:page-id page-id
|
||||||
|
:frames frames})
|
||||||
|
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
|
|
|
@ -279,7 +279,10 @@
|
||||||
|
|
||||||
:always
|
:always
|
||||||
(d/without-nils))]
|
(d/without-nils))]
|
||||||
(assoc-in shape [:strokes index] new-attrs)))))))))
|
(-> shape
|
||||||
|
(cond-> (not (contains? shape :strokes))
|
||||||
|
(assoc :strokes []))
|
||||||
|
(assoc-in [:strokes index] new-attrs))))))))))
|
||||||
|
|
||||||
(defn change-shadow
|
(defn change-shadow
|
||||||
[ids attrs index]
|
[ids attrs index]
|
||||||
|
|
|
@ -195,13 +195,26 @@
|
||||||
(ptk/reify ::handle-file-change
|
(ptk/reify ::handle-file-change
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ _ _]
|
(watch [_ _ _]
|
||||||
(let [changes-by-pages (group-by :page-id changes)
|
(let [position-data-operation?
|
||||||
|
(fn [{:keys [type attr]}]
|
||||||
|
(and (= :set type) (= attr :position-data)))
|
||||||
|
|
||||||
|
remove-update-position-data
|
||||||
|
(fn [change]
|
||||||
|
(cond-> change
|
||||||
|
(= :mod-obj (:type change))
|
||||||
|
(update :operations #(filterv (comp not position-data-operation?) %))))
|
||||||
|
|
||||||
process-page-changes
|
process-page-changes
|
||||||
(fn [[page-id changes]]
|
(fn [[page-id changes]]
|
||||||
(dch/update-indices page-id changes))]
|
(dch/update-indices page-id changes))
|
||||||
|
|
||||||
|
;; We remove `position-data` from the incomming message
|
||||||
|
changes (->> changes (mapv remove-update-position-data))
|
||||||
|
changes-by-pages (group-by :page-id changes)]
|
||||||
|
|
||||||
(rx/merge
|
(rx/merge
|
||||||
(rx/of (dwp/shapes-changes-persisted file-id msg))
|
(rx/of (dwp/shapes-changes-persisted file-id (assoc msg :changes changes)))
|
||||||
|
|
||||||
(when-not (empty? changes-by-pages)
|
(when-not (empty? changes-by-pages)
|
||||||
(rx/from (map process-page-changes changes-by-pages))))))))
|
(rx/from (map process-page-changes changes-by-pages))))))))
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
(ns app.main.data.workspace.persistence
|
(ns app.main.data.workspace.persistence
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
[app.common.logging :as log]
|
[app.common.logging :as log]
|
||||||
[app.common.pages :as cp]
|
[app.common.pages :as cp]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
[app.main.data.fonts :as df]
|
[app.main.data.fonts :as df]
|
||||||
[app.main.data.workspace.changes :as dch]
|
[app.main.data.workspace.changes :as dch]
|
||||||
[app.main.data.workspace.state-helpers :as wsh]
|
[app.main.data.workspace.state-helpers :as wsh]
|
||||||
|
[app.main.data.workspace.thumbnails :as dwt]
|
||||||
[app.main.repo :as rp]
|
[app.main.repo :as rp]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.util.http :as http]
|
[app.util.http :as http]
|
||||||
|
@ -138,16 +140,27 @@
|
||||||
:revn (:revn file)
|
:revn (:revn file)
|
||||||
:session-id sid
|
:session-id sid
|
||||||
:changes-with-metadata (into [] changes)}]
|
:changes-with-metadata (into [] changes)}]
|
||||||
|
|
||||||
(when (= file-id (:id params))
|
(when (= file-id (:id params))
|
||||||
(->> (rp/mutation :update-file params)
|
(->> (rp/mutation :update-file params)
|
||||||
(rx/mapcat (fn [lagged]
|
(rx/mapcat (fn [lagged]
|
||||||
(log/debug :hint "changes persisted" :lagged (count lagged))
|
(log/debug :hint "changes persisted" :lagged (count lagged))
|
||||||
(let [lagged (cond->> lagged
|
(let [lagged (cond->> lagged
|
||||||
(= #{sid} (into #{} (map :session-id) lagged))
|
(= #{sid} (into #{} (map :session-id) lagged))
|
||||||
(map #(assoc % :changes [])))]
|
(map #(assoc % :changes [])))
|
||||||
(->> (rx/of lagged)
|
|
||||||
(rx/mapcat seq)
|
frame-updates
|
||||||
(rx/map #(shapes-changes-persisted file-id %))))))
|
(-> (group-by :page-id changes)
|
||||||
|
(d/update-vals #(into #{} (mapcat :frames) %)))]
|
||||||
|
|
||||||
|
(rx/merge
|
||||||
|
(->> (rx/from frame-updates)
|
||||||
|
(rx/flat-map (fn [[page-id frames]]
|
||||||
|
(->> frames (map #(vector page-id %)))))
|
||||||
|
(rx/map (fn [[page-id frame-id]] (dwt/update-thumbnail page-id frame-id))))
|
||||||
|
(->> (rx/of lagged)
|
||||||
|
(rx/mapcat seq)
|
||||||
|
(rx/map #(shapes-changes-persisted file-id %)))))))
|
||||||
(rx/catch (fn [cause]
|
(rx/catch (fn [cause]
|
||||||
(rx/concat
|
(rx/concat
|
||||||
(rx/of (rt/assign-exception cause))
|
(rx/of (rt/assign-exception cause))
|
||||||
|
|
|
@ -63,13 +63,17 @@
|
||||||
(ted/get-editor-current-content))]
|
(ted/get-editor-current-content))]
|
||||||
(if (ted/content-has-text? content)
|
(if (ted/content-has-text? content)
|
||||||
(let [content (d/merge (ted/export-content content)
|
(let [content (d/merge (ted/export-content content)
|
||||||
(dissoc (:content shape) :children))]
|
(dissoc (:content shape) :children))
|
||||||
|
modifiers (get-in state [:workspace-text-modifier id])]
|
||||||
(rx/merge
|
(rx/merge
|
||||||
(rx/of (update-editor-state shape nil))
|
(rx/of (update-editor-state shape nil))
|
||||||
(when (and (not= content (:content shape))
|
(when (and (not= content (:content shape))
|
||||||
(some? (:current-page-id state)))
|
(some? (:current-page-id state)))
|
||||||
(rx/of
|
(rx/of
|
||||||
(dch/update-shapes [id] #(assoc % :content content))
|
(dch/update-shapes [id] (fn [shape]
|
||||||
|
(-> shape
|
||||||
|
(assoc :content content)
|
||||||
|
(merge modifiers))))
|
||||||
(dwu/commit-undo-transaction)))))
|
(dwu/commit-undo-transaction)))))
|
||||||
|
|
||||||
(when (some? id)
|
(when (some? id)
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
(ns app.main.data.workspace.thumbnails
|
(ns app.main.data.workspace.thumbnails
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.pages.helpers :as cph]
|
[app.common.pages.helpers :as cph]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
|
@ -14,6 +13,8 @@
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.repo :as rp]
|
[app.main.repo :as rp]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
|
[app.util.dom :as dom]
|
||||||
|
[app.util.webapi :as wapi]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[potok.core :as ptk]))
|
[potok.core :as ptk]))
|
||||||
|
|
||||||
|
@ -26,45 +27,47 @@
|
||||||
(rx/filter #(= % id))
|
(rx/filter #(= % id))
|
||||||
(rx/take 1)))
|
(rx/take 1)))
|
||||||
|
|
||||||
|
(defn thumbnail-stream
|
||||||
|
[object-id]
|
||||||
|
(rx/create
|
||||||
|
(fn [subs]
|
||||||
|
(let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%'" object-id))]
|
||||||
|
(if (some? node)
|
||||||
|
(-> node
|
||||||
|
(.toBlob (fn [blob]
|
||||||
|
(rx/push! subs blob)
|
||||||
|
(rx/end! subs))
|
||||||
|
"image/png"))
|
||||||
|
|
||||||
|
;; If we cannot find the node we send `nil` and the upsert will delete the thumbnail
|
||||||
|
(do (rx/push! subs nil)
|
||||||
|
(rx/end! subs)))))))
|
||||||
|
|
||||||
(defn update-thumbnail
|
(defn update-thumbnail
|
||||||
"Updates the thumbnail information for the given frame `id`"
|
"Updates the thumbnail information for the given frame `id`"
|
||||||
[page-id frame-id data]
|
[page-id frame-id]
|
||||||
(let [lock (uuid/next)
|
(ptk/reify ::update-thumbnail
|
||||||
object-id (dm/str page-id frame-id)]
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [object-id (dm/str page-id frame-id)
|
||||||
|
file-id (:current-file-id state)
|
||||||
|
blob-result (thumbnail-stream object-id)]
|
||||||
|
|
||||||
(ptk/reify ::update-thumbnail
|
(->> blob-result
|
||||||
IDeref
|
(rx/merge-map
|
||||||
(-deref [_] {:object-id object-id :data data})
|
(fn [blob]
|
||||||
|
(if (some? blob)
|
||||||
ptk/UpdateEvent
|
(wapi/read-file-as-data-url blob)
|
||||||
(update [_ state]
|
(rx/of nil))))
|
||||||
(-> state
|
|
||||||
(assoc-in [:workspace-file :thumbnails object-id] data)
|
|
||||||
(cond-> (nil? (get-in state [::update-thumbnail-lock object-id]))
|
|
||||||
(assoc-in [::update-thumbnail-lock object-id] lock))))
|
|
||||||
|
|
||||||
ptk/WatchEvent
|
(rx/merge-map
|
||||||
(watch [_ state stream]
|
(fn [data]
|
||||||
(when (= lock (get-in state [::update-thumbnail-lock object-id]))
|
(let [params {:file-id file-id :object-id object-id :data data}]
|
||||||
(let [stopper (->> stream (rx/filter (ptk/type? :app.main.data.workspace/finalize)))
|
(rx/merge
|
||||||
params {:file-id (:current-file-id state)
|
;; Update the local copy of the thumbnails so we don't need to request it again
|
||||||
:object-id object-id}]
|
(rx/of #(assoc-in % [:workspace-file :thumbnails object-id] data))
|
||||||
;; Sends the first event and debounce the rest. Will only make one update once
|
(->> (rp/mutation! :upsert-file-object-thumbnail params)
|
||||||
;; the 2 second debounce is finished
|
(rx/ignore)))))))))))
|
||||||
(rx/merge
|
|
||||||
(->> stream
|
|
||||||
(rx/filter (ptk/type? ::update-thumbnail))
|
|
||||||
(rx/map deref)
|
|
||||||
(rx/filter #(= object-id (:object-id %)))
|
|
||||||
(rx/debounce 2000)
|
|
||||||
(rx/take 1)
|
|
||||||
(rx/map :data)
|
|
||||||
(rx/flat-map #(rp/mutation! :upsert-file-object-thumbnail (assoc params :data %)))
|
|
||||||
(rx/map #(fn [state] (d/dissoc-in state [::update-thumbnail-lock object-id])))
|
|
||||||
(rx/take-until stopper))
|
|
||||||
|
|
||||||
(->> (rx/of (update-thumbnail page-id frame-id data))
|
|
||||||
(rx/observe-on :async)))))))))
|
|
||||||
|
|
||||||
(defn- extract-frame-changes
|
(defn- extract-frame-changes
|
||||||
"Process a changes set in a commit to extract the frames that are changing"
|
"Process a changes set in a commit to extract the frames that are changing"
|
||||||
|
@ -156,5 +159,3 @@
|
||||||
(let [page-id (get state :current-page-id)
|
(let [page-id (get state :current-page-id)
|
||||||
old-shape-thumbnail (get-in state [:workspace-file :thumbnails (dm/str page-id old-id)])]
|
old-shape-thumbnail (get-in state [:workspace-file :thumbnails (dm/str page-id old-id)])]
|
||||||
(-> state (assoc-in [:workspace-file :thumbnails (dm/str page-id new-id)] old-shape-thumbnail))))))
|
(-> state (assoc-in [:workspace-file :thumbnails (dm/str page-id new-id)] old-shape-thumbnail))))))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
(defonce loaded (l/atom #{}))
|
(defonce loaded (l/atom #{}))
|
||||||
|
(defonce loading (l/atom {}))
|
||||||
|
|
||||||
(defn- create-link-element
|
(defn- create-link-element
|
||||||
[uri]
|
[uri]
|
||||||
|
@ -199,11 +200,34 @@
|
||||||
(p/create (fn [resolve]
|
(p/create (fn [resolve]
|
||||||
(ensure-loaded! id resolve))))
|
(ensure-loaded! id resolve))))
|
||||||
([id on-loaded]
|
([id on-loaded]
|
||||||
(if (contains? @loaded id)
|
(let [font (get @fontsdb id)]
|
||||||
(on-loaded id)
|
(cond
|
||||||
(when-let [font (get @fontsdb id)]
|
;; Font already loaded, we just continue
|
||||||
(load-font (assoc font ::on-loaded on-loaded))
|
(contains? @loaded id)
|
||||||
(swap! loaded conj id)))))
|
(on-loaded id)
|
||||||
|
|
||||||
|
;; Font is currently downloading. We attach the caller to the promise
|
||||||
|
(contains? @loading id)
|
||||||
|
(-> (get @loading id)
|
||||||
|
(p/then #(on-loaded id)))
|
||||||
|
|
||||||
|
;; First caller, we create the promise and then wait
|
||||||
|
:else
|
||||||
|
(let [on-load (fn [resolve]
|
||||||
|
(swap! loaded conj id)
|
||||||
|
(swap! loading dissoc id)
|
||||||
|
(on-loaded id)
|
||||||
|
(resolve id))
|
||||||
|
|
||||||
|
load-p (p/create
|
||||||
|
(fn [resolve _]
|
||||||
|
(-> font
|
||||||
|
(assoc ::on-loaded (partial on-load resolve))
|
||||||
|
(load-font))))]
|
||||||
|
|
||||||
|
(swap! loading assoc id load-p)
|
||||||
|
|
||||||
|
nil)))))
|
||||||
|
|
||||||
(defn ready
|
(defn ready
|
||||||
[cb]
|
[cb]
|
||||||
|
|
|
@ -75,7 +75,6 @@
|
||||||
|
|
||||||
on-succes
|
on-succes
|
||||||
(fn [data]
|
(fn [data]
|
||||||
(prn "SUCCESS" data)
|
|
||||||
(when-let [token (:invitation-token data)]
|
(when-let [token (:invitation-token data)]
|
||||||
(st/emit! (rt/nav :auth-verify-token {} {:token token}))))
|
(st/emit! (rt/nav :auth-verify-token {} {:token token}))))
|
||||||
|
|
||||||
|
|
|
@ -81,13 +81,12 @@
|
||||||
|
|
||||||
[:hr]
|
[:hr]
|
||||||
|
|
||||||
[:h2 (tr "feedback.discussions-title")]
|
[:h2 (tr "feedback.twitter-title")]
|
||||||
[:p (tr "feedback.discussions-subtitle1")]
|
[:p (tr "feedback.twitter-subtitle1")]
|
||||||
[:p (tr "feedback.discussions-subtitle2")]
|
|
||||||
|
|
||||||
[:a.btn-secondary.btn-large
|
[:a.btn-secondary.btn-large
|
||||||
{:href "https://github.com/penpot/penpot/discussions" :target "_blank"}
|
{:href "https://twitter.com/PenpotSupport" :target "_blank"}
|
||||||
(tr "feedback.discussions-go-to")]
|
(tr "feedback.twitter-go-to")]
|
||||||
|
|
||||||
[:hr]
|
[:hr]
|
||||||
|
|
||||||
|
|
|
@ -360,7 +360,7 @@
|
||||||
(-> props
|
(-> props
|
||||||
(obj/set! "style" style)))
|
(obj/set! "style" style)))
|
||||||
|
|
||||||
(some? svg-attrs)
|
(and (some? svg-attrs) (empty? (:fills shape)))
|
||||||
(let [style
|
(let [style
|
||||||
(-> (obj/get props "style")
|
(-> (obj/get props "style")
|
||||||
(obj/clone))
|
(obj/clone))
|
||||||
|
|
|
@ -73,8 +73,12 @@
|
||||||
;; Creates a style tag by replacing the urls with the data uri
|
;; Creates a style tag by replacing the urls with the data uri
|
||||||
style (replace-embeds fonts-css fonts-urls fonts-embed)]
|
style (replace-embeds fonts-css fonts-urls fonts-embed)]
|
||||||
|
|
||||||
(when (d/not-empty? style)
|
(cond
|
||||||
[:style {:data-loading loading?} style])))
|
(d/not-empty? style)
|
||||||
|
[:style {:data-loading loading?} style]
|
||||||
|
|
||||||
|
(d/not-empty? fonts)
|
||||||
|
[:style {:data-loading true}])))
|
||||||
|
|
||||||
(defn shape->fonts
|
(defn shape->fonts
|
||||||
[shape objects]
|
[shape objects]
|
||||||
|
|
|
@ -110,7 +110,10 @@
|
||||||
(let [font-variant (d/seek #(= font-variant-id (:id %)) (:variants font))]
|
(let [font-variant (d/seek #(= font-variant-id (:id %)) (:variants font))]
|
||||||
[(str/quote (or (:family font) (:font-family data)))
|
[(str/quote (or (:family font) (:font-family data)))
|
||||||
(or (:style font-variant) (:font-style data))
|
(or (:style font-variant) (:font-style data))
|
||||||
(or (:weight font-variant) (:font-weight data))]))]
|
(or (:weight font-variant) (:font-weight data))]))
|
||||||
|
|
||||||
|
base (-> base
|
||||||
|
(obj/set! "--font-id" font-id))]
|
||||||
|
|
||||||
(cond-> base
|
(cond-> base
|
||||||
(some? fills)
|
(some? fills)
|
||||||
|
|
|
@ -71,11 +71,6 @@
|
||||||
frame-id (:id shape)
|
frame-id (:id shape)
|
||||||
page-id (mf/use-ctx ctx/current-page-id)
|
page-id (mf/use-ctx ctx/current-page-id)
|
||||||
|
|
||||||
thumbnail-data-ref (mf/use-memo (mf/deps page-id frame-id) #(refs/thumbnail-frame-data page-id frame-id))
|
|
||||||
thumbnail-data (mf/deref thumbnail-data-ref)
|
|
||||||
|
|
||||||
thumbnail? (and thumbnail? (some? thumbnail-data))
|
|
||||||
|
|
||||||
;; References to the current rendered node and the its parentn
|
;; References to the current rendered node and the its parentn
|
||||||
node-ref (mf/use-var nil)
|
node-ref (mf/use-var nil)
|
||||||
|
|
||||||
|
@ -88,11 +83,11 @@
|
||||||
|
|
||||||
disable-thumbnail? (d/not-empty? (dm/get-in modifiers [(:id shape) :modifiers]))
|
disable-thumbnail? (d/not-empty? (dm/get-in modifiers [(:id shape) :modifiers]))
|
||||||
|
|
||||||
[on-load-frame-dom thumb-renderer]
|
[on-load-frame-dom render-frame? thumbnail-renderer]
|
||||||
(ftr/use-render-thumbnail page-id shape node-ref rendered? thumbnail-data-ref disable-thumbnail?)
|
(ftr/use-render-thumbnail page-id shape node-ref rendered? disable-thumbnail?)
|
||||||
|
|
||||||
on-frame-load
|
on-frame-load
|
||||||
(fns/use-node-store thumbnail? node-ref rendered?)]
|
(fns/use-node-store thumbnail? node-ref rendered? render-frame?)]
|
||||||
|
|
||||||
(fdm/use-dynamic-modifiers objects @node-ref modifiers)
|
(fdm/use-dynamic-modifiers objects @node-ref modifiers)
|
||||||
|
|
||||||
|
@ -115,9 +110,9 @@
|
||||||
(rx/dispose! sub)))))
|
(rx/dispose! sub)))))
|
||||||
|
|
||||||
(mf/use-effect
|
(mf/use-effect
|
||||||
(mf/deps shape fonts thumbnail? on-load-frame-dom @force-render)
|
(mf/deps shape fonts thumbnail? on-load-frame-dom @force-render render-frame?)
|
||||||
(fn []
|
(fn []
|
||||||
(when (and (some? @node-ref) (or @rendered? (not thumbnail?) @force-render))
|
(when (and (some? @node-ref) (or @rendered? (not thumbnail?) @force-render render-frame?))
|
||||||
(mf/mount
|
(mf/mount
|
||||||
(mf/element frame-shape
|
(mf/element frame-shape
|
||||||
#js {:ref on-load-frame-dom :shape shape :fonts fonts})
|
#js {:ref on-load-frame-dom :shape shape :fonts fonts})
|
||||||
|
@ -128,12 +123,11 @@
|
||||||
[:& (mf/provider ctx/render-ctx) {:value render-id}
|
[:& (mf/provider ctx/render-ctx) {:value render-id}
|
||||||
[:g.frame-container {:id (dm/str "frame-container-" (:id shape))
|
[:g.frame-container {:id (dm/str "frame-container-" (:id shape))
|
||||||
:key "frame-container"
|
:key "frame-container"
|
||||||
:ref on-frame-load}
|
:ref on-frame-load
|
||||||
|
:opacity (when (:hidden shape) 0)}
|
||||||
[:& ff/fontfaces-style {:fonts fonts}]
|
[:& ff/fontfaces-style {:fonts fonts}]
|
||||||
[:g.frame-thumbnail-wrapper {:id (dm/str "thumbnail-container-" (:id shape))}
|
[:g.frame-thumbnail-wrapper
|
||||||
[:> frame/frame-thumbnail {:key (dm/str (:id shape))
|
{:id (dm/str "thumbnail-container-" (:id shape))
|
||||||
:shape (cond-> shape
|
;; Hide the thumbnail when not displaying
|
||||||
(some? thumbnail-data)
|
:opacity (when (and @rendered? (not thumbnail?)) 0)}
|
||||||
(assoc :thumbnail thumbnail-data))}]
|
thumbnail-renderer]]]))))
|
||||||
|
|
||||||
thumb-renderer]]]))))
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
(defn use-node-store
|
(defn use-node-store
|
||||||
"Hook responsible of storing the rendered DOM node in memory while not being used"
|
"Hook responsible of storing the rendered DOM node in memory while not being used"
|
||||||
[thumbnail? node-ref rendered?]
|
[thumbnail? node-ref rendered? render-frame?]
|
||||||
|
|
||||||
(let [;; when `true` the node is in memory
|
(let [;; when `true` the node is in memory
|
||||||
in-memory? (mf/use-var true)
|
in-memory? (mf/use-var true)
|
||||||
|
@ -33,13 +33,13 @@
|
||||||
(swap! re-render inc)))))]
|
(swap! re-render inc)))))]
|
||||||
|
|
||||||
(mf/use-effect
|
(mf/use-effect
|
||||||
(mf/deps thumbnail?)
|
(mf/deps thumbnail? render-frame?)
|
||||||
(fn []
|
(fn []
|
||||||
(when (and (some? @parent-ref) (some? @node-ref) @rendered? thumbnail?)
|
(when (and (some? @parent-ref) (some? @node-ref) @rendered? (and thumbnail? (not render-frame?)))
|
||||||
(.removeChild @parent-ref @node-ref)
|
(.removeChild @parent-ref @node-ref)
|
||||||
(reset! in-memory? true))
|
(reset! in-memory? true))
|
||||||
|
|
||||||
(when (and (some? @node-ref) @in-memory? (not thumbnail?))
|
(when (and (some? @node-ref) @in-memory? (or (not thumbnail?) render-frame?))
|
||||||
(.appendChild @parent-ref @node-ref)
|
(.appendChild @parent-ref @node-ref)
|
||||||
(reset! in-memory? false))))
|
(reset! in-memory? false))))
|
||||||
|
|
||||||
|
|
|
@ -9,19 +9,20 @@
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.math :as mth]
|
[app.common.math :as mth]
|
||||||
[app.main.data.workspace :as dw]
|
[app.main.data.workspace.thumbnails :as dwt]
|
||||||
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.hooks :as hooks]
|
[app.main.ui.hooks :as hooks]
|
||||||
|
[app.main.ui.shapes.frame :as frame]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[app.util.timers :as ts]
|
[app.util.timers :as ts]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
|
[debug :refer [debug?]]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
;; (def thumbnail-scale-factor 2)
|
(defn- draw-thumbnail-canvas!
|
||||||
|
|
||||||
(defn- draw-thumbnail-canvas
|
|
||||||
[canvas-node img-node]
|
[canvas-node img-node]
|
||||||
(try
|
(try
|
||||||
(when (and (some? canvas-node) (some? img-node))
|
(when (and (some? canvas-node) (some? img-node))
|
||||||
|
@ -29,19 +30,12 @@
|
||||||
canvas-width (.-width canvas-node)
|
canvas-width (.-width canvas-node)
|
||||||
canvas-height (.-height canvas-node)]
|
canvas-height (.-height canvas-node)]
|
||||||
|
|
||||||
;; TODO: Expermient with different scale factors
|
|
||||||
;; (set! (.-width canvas-node) (* thumbnail-scale-factor canvas-width))
|
|
||||||
;; (set! (.-height canvas-node) (* thumbnail-scale-factor canvas-height))
|
|
||||||
;; (.setTransform canvas-context thumbnail-scale-factor 0 0 thumbnail-scale-factor 0 0)
|
|
||||||
;; (set! (.-imageSmoothingEnabled canvas-context) true)
|
|
||||||
;; (set! (.-imageSmoothingQuality canvas-context) "high")
|
|
||||||
|
|
||||||
(.clearRect canvas-context 0 0 canvas-width canvas-height)
|
(.clearRect canvas-context 0 0 canvas-width canvas-height)
|
||||||
(.drawImage canvas-context img-node 0 0 canvas-width canvas-height)
|
(.drawImage canvas-context img-node 0 0 canvas-width canvas-height)
|
||||||
(.toDataURL canvas-node "image/png" 1.0)))
|
true))
|
||||||
(catch :default err
|
(catch :default err
|
||||||
(.error js/console err)
|
(.error js/console err)
|
||||||
nil)))
|
false)))
|
||||||
|
|
||||||
(defn- remove-image-loading
|
(defn- remove-image-loading
|
||||||
"Remove the changes related to change a url for its embed value. This is necessary
|
"Remove the changes related to change a url for its embed value. This is necessary
|
||||||
|
@ -59,7 +53,7 @@
|
||||||
|
|
||||||
(defn use-render-thumbnail
|
(defn use-render-thumbnail
|
||||||
"Hook that will create the thumbnail thata"
|
"Hook that will create the thumbnail thata"
|
||||||
[page-id {:keys [id x y width height] :as shape} node-ref rendered? thumbnail-data-ref disable?]
|
[page-id {:keys [id x y width height] :as shape} node-ref rendered? disable?]
|
||||||
|
|
||||||
(let [frame-canvas-ref (mf/use-ref nil)
|
(let [frame-canvas-ref (mf/use-ref nil)
|
||||||
frame-image-ref (mf/use-ref nil)
|
frame-image-ref (mf/use-ref nil)
|
||||||
|
@ -78,16 +72,25 @@
|
||||||
|
|
||||||
updates-str (mf/use-memo #(rx/subject))
|
updates-str (mf/use-memo #(rx/subject))
|
||||||
|
|
||||||
|
thumbnail-data-ref (mf/use-memo (mf/deps page-id id) #(refs/thumbnail-frame-data page-id id))
|
||||||
|
thumbnail-data (mf/deref thumbnail-data-ref)
|
||||||
|
|
||||||
|
render-frame? (mf/use-state (not thumbnail-data))
|
||||||
|
|
||||||
on-image-load
|
on-image-load
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(fn []
|
(fn []
|
||||||
(ts/raf
|
(ts/raf
|
||||||
#(let [canvas-node (mf/ref-val frame-canvas-ref)
|
#(let [canvas-node (mf/ref-val frame-canvas-ref)
|
||||||
img-node (mf/ref-val frame-image-ref)
|
img-node (mf/ref-val frame-image-ref)]
|
||||||
thumb-data (draw-thumbnail-canvas canvas-node img-node)]
|
(when (draw-thumbnail-canvas! canvas-node img-node)
|
||||||
(when (some? thumb-data)
|
(reset! image-url nil)
|
||||||
(st/emit! (dw/update-thumbnail page-id id thumb-data))
|
(reset! render-frame? false))
|
||||||
(reset! image-url nil))))))
|
|
||||||
|
;; If we don't have the thumbnail data saved (normaly the first load) we update the data
|
||||||
|
;; when available
|
||||||
|
(when (not @thumbnail-data-ref)
|
||||||
|
(st/emit! (dwt/update-thumbnail page-id id) ))))))
|
||||||
|
|
||||||
generate-thumbnail
|
generate-thumbnail
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
|
@ -115,7 +118,7 @@
|
||||||
(fn []
|
(fn []
|
||||||
(when (and (some? @node-ref) @regenerate-thumbnail)
|
(when (and (some? @node-ref) @regenerate-thumbnail)
|
||||||
(let [loading-images? (some? (dom/query @node-ref "[data-loading='true']"))
|
(let [loading-images? (some? (dom/query @node-ref "[data-loading='true']"))
|
||||||
loading-fonts? (some? (dom/query (dm/str "#frame-container-" (:id shape) " style[data-loading='true']")))]
|
loading-fonts? (some? (dom/query (dm/str "#frame-container-" (:id shape) " > style[data-loading='true']")))]
|
||||||
(when (and (not loading-images?) (not loading-fonts?))
|
(when (and (not loading-images?) (not loading-fonts?))
|
||||||
(generate-thumbnail)
|
(generate-thumbnail)
|
||||||
(reset! regenerate-thumbnail false))))))
|
(reset! regenerate-thumbnail false))))))
|
||||||
|
@ -172,18 +175,28 @@
|
||||||
(reset! observer-ref nil)))))
|
(reset! observer-ref nil)))))
|
||||||
|
|
||||||
[on-load-frame-dom
|
[on-load-frame-dom
|
||||||
(when (some? @image-url)
|
@render-frame?
|
||||||
(mf/html
|
(mf/html
|
||||||
[:g.thumbnail-rendering
|
[:*
|
||||||
[:foreignObject {:x x :y y :width width :height height}
|
[:> frame/frame-thumbnail {:key (dm/str (:id shape))
|
||||||
[:canvas {:ref frame-canvas-ref
|
:shape (cond-> shape
|
||||||
:width fixed-width
|
(some? thumbnail-data)
|
||||||
:height fixed-height}]]
|
(assoc :thumbnail thumbnail-data))}]
|
||||||
|
|
||||||
|
[:foreignObject {:x x :y y :width width :height height}
|
||||||
|
[:canvas.thumbnail-canvas
|
||||||
|
{:ref frame-canvas-ref
|
||||||
|
:data-object-id (dm/str page-id (:id shape))
|
||||||
|
:width fixed-width
|
||||||
|
:height fixed-height
|
||||||
|
;; DEBUG
|
||||||
|
:style {:filter (when (debug? :thumbnails) "invert(1)")}}]]
|
||||||
|
|
||||||
|
(when (some? @image-url)
|
||||||
[:image {:ref frame-image-ref
|
[:image {:ref frame-image-ref
|
||||||
:x (:x shape)
|
:x (:x shape)
|
||||||
:y (:y shape)
|
:y (:y shape)
|
||||||
:href @image-url
|
:href @image-url
|
||||||
:width (:width shape)
|
:width (:width shape)
|
||||||
:height (:height shape)
|
:height (:height shape)
|
||||||
:on-load on-image-load}]]))]))
|
:on-load on-image-load}])])]))
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
[app.util.text-editor :as ted]
|
[app.util.text-editor :as ted]
|
||||||
[app.util.text-svg-position :as utp]
|
[app.util.text-svg-position :as utp]
|
||||||
[app.util.timers :as ts]
|
[app.util.timers :as ts]
|
||||||
|
[promesa.core :as p]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
(defn strip-position-data [shape]
|
(defn strip-position-data [shape]
|
||||||
|
@ -61,37 +62,39 @@
|
||||||
(assoc :content (d/txt-merge content editor-content)))))
|
(assoc :content (d/txt-merge content editor-content)))))
|
||||||
|
|
||||||
(defn- update-text-shape
|
(defn- update-text-shape
|
||||||
[{:keys [grow-type id]} node]
|
[{:keys [grow-type id migrate]} node]
|
||||||
;; Check if we need to update the size because it's auto-width or auto-height
|
;; Check if we need to update the size because it's auto-width or auto-height
|
||||||
(when (contains? #{:auto-height :auto-width} grow-type)
|
|
||||||
(let [{:keys [width height]}
|
|
||||||
(-> (dom/query node ".paragraph-set")
|
|
||||||
(dom/get-client-size))
|
|
||||||
width (mth/ceil width)
|
|
||||||
height (mth/ceil height)]
|
|
||||||
(when (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height)))
|
|
||||||
(st/emit! (dwt/resize-text id width height)))))
|
|
||||||
|
|
||||||
;; Update the position-data of every text fragment
|
;; Update the position-data of every text fragment
|
||||||
(let [position-data (utp/calc-position-data node)]
|
(p/let [position-data (utp/calc-position-data node)]
|
||||||
(st/emit! (dwt/update-position-data id position-data)))
|
(st/emit! (dwt/update-position-data id position-data))
|
||||||
|
|
||||||
|
(when (contains? #{:auto-height :auto-width} grow-type)
|
||||||
|
(let [{:keys [width height]}
|
||||||
|
(-> (dom/query node ".paragraph-set")
|
||||||
|
(dom/get-client-size))
|
||||||
|
width (mth/ceil width)
|
||||||
|
height (mth/ceil height)]
|
||||||
|
(when (and (not (mth/almost-zero? width))
|
||||||
|
(not (mth/almost-zero? height))
|
||||||
|
(not migrate))
|
||||||
|
(st/emit! (dwt/resize-text id width height))))))
|
||||||
|
|
||||||
(st/emit! (dwt/clean-text-modifier id)))
|
(st/emit! (dwt/clean-text-modifier id)))
|
||||||
|
|
||||||
(defn- update-text-modifier
|
(defn- update-text-modifier
|
||||||
[{:keys [grow-type id]} node]
|
[{:keys [grow-type id]} node]
|
||||||
(let [position-data (utp/calc-position-data node)
|
(p/let [position-data (utp/calc-position-data node)
|
||||||
props {:position-data position-data}
|
props {:position-data position-data}
|
||||||
|
|
||||||
props
|
props
|
||||||
(if (contains? #{:auto-height :auto-width} grow-type)
|
(if (contains? #{:auto-height :auto-width} grow-type)
|
||||||
(let [{:keys [width height]} (-> (dom/query node ".paragraph-set") (dom/get-client-size))
|
(let [{:keys [width height]} (-> (dom/query node ".paragraph-set") (dom/get-client-size))
|
||||||
width (mth/ceil width)
|
width (mth/ceil width)
|
||||||
height (mth/ceil height)]
|
height (mth/ceil height)]
|
||||||
(if (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height)))
|
(if (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height)))
|
||||||
(assoc props :width width :height height)
|
(assoc props :width width :height height)
|
||||||
props))
|
props))
|
||||||
props)]
|
props)]
|
||||||
|
|
||||||
(st/emit! (dwt/update-text-modifier id props))))
|
(st/emit! (dwt/update-text-modifier id props))))
|
||||||
|
|
||||||
|
@ -228,7 +231,14 @@
|
||||||
(mf/deps text-shapes modifiers)
|
(mf/deps text-shapes modifiers)
|
||||||
#(d/update-vals text-shapes (partial process-shape modifiers)))
|
#(d/update-vals text-shapes (partial process-shape modifiers)))
|
||||||
|
|
||||||
editing-shape (get text-shapes edition)]
|
editing-shape (get text-shapes edition)
|
||||||
|
|
||||||
|
;; This memo is necesary so the viewport-text-wrapper memoize its props correctly
|
||||||
|
text-shapes-wrapper
|
||||||
|
(mf/use-memo
|
||||||
|
(mf/deps text-shapes edition)
|
||||||
|
(fn []
|
||||||
|
(dissoc text-shapes edition)))]
|
||||||
|
|
||||||
;; We only need the effect to run on "mount" because the next fonts will be changed when the texts are
|
;; We only need the effect to run on "mount" because the next fonts will be changed when the texts are
|
||||||
;; edited
|
;; edited
|
||||||
|
@ -242,5 +252,5 @@
|
||||||
(when editing-shape
|
(when editing-shape
|
||||||
[:& viewport-text-editing {:shape editing-shape}])
|
[:& viewport-text-editing {:shape editing-shape}])
|
||||||
|
|
||||||
[:& viewport-texts-wrapper {:text-shapes (dissoc text-shapes edition)
|
[:& viewport-texts-wrapper {:text-shapes text-shapes-wrapper
|
||||||
:modifiers modifiers}]]))
|
:modifiers modifiers}]]))
|
||||||
|
|
|
@ -71,8 +71,8 @@
|
||||||
focus (mf/deref refs/workspace-focus-selected)
|
focus (mf/deref refs/workspace-focus-selected)
|
||||||
|
|
||||||
objects-ref (mf/use-memo #(refs/workspace-page-objects-by-id page-id))
|
objects-ref (mf/use-memo #(refs/workspace-page-objects-by-id page-id))
|
||||||
base-objects (-> (mf/deref objects-ref)
|
objects (mf/deref objects-ref)
|
||||||
(ui-hooks/with-focus-objects focus))
|
base-objects (-> objects (ui-hooks/with-focus-objects focus))
|
||||||
|
|
||||||
modifiers (mf/deref refs/workspace-modifiers)
|
modifiers (mf/deref refs/workspace-modifiers)
|
||||||
|
|
||||||
|
@ -245,7 +245,7 @@
|
||||||
[:g {:pointer-events "none" :opacity 0}
|
[:g {:pointer-events "none" :opacity 0}
|
||||||
[:& stv/viewport-texts {:key (dm/str "texts-" page-id)
|
[:& stv/viewport-texts {:key (dm/str "texts-" page-id)
|
||||||
:page-id page-id
|
:page-id page-id
|
||||||
:objects base-objects
|
:objects objects
|
||||||
:modifiers modifiers
|
:modifiers modifiers
|
||||||
:edition edition}]]]
|
:edition edition}]]]
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
[app.util.globals :as globals]
|
[app.util.globals :as globals]
|
||||||
[app.util.timers :as timers]
|
[app.util.timers :as timers]
|
||||||
[beicon.core :as rx]
|
[beicon.core :as rx]
|
||||||
|
[debug :refer [debug?]]
|
||||||
[goog.events :as events]
|
[goog.events :as events]
|
||||||
[rumext.alpha :as mf])
|
[rumext.alpha :as mf])
|
||||||
(:import goog.events.EventType))
|
(:import goog.events.EventType))
|
||||||
|
@ -269,7 +270,11 @@
|
||||||
|
|
||||||
;; We only allow active frames that are contained in the vbox
|
;; We only allow active frames that are contained in the vbox
|
||||||
(filter (partial inside-vbox vbox objects)))
|
(filter (partial inside-vbox vbox objects)))
|
||||||
all-frames)]
|
all-frames)
|
||||||
|
|
||||||
|
;; Debug only: Disable the thumbnails
|
||||||
|
new-active-frames
|
||||||
|
(if (debug? :disable-frame-thumbnails) (into #{} all-frames) new-active-frames)]
|
||||||
|
|
||||||
(when (not= @active-frames new-active-frames)
|
(when (not= @active-frames new-active-frames)
|
||||||
(reset! active-frames new-active-frames)))))))
|
(reset! active-frames new-active-frames)))))))
|
||||||
|
|
|
@ -135,35 +135,36 @@
|
||||||
(on-frame-leave (:id frame))))
|
(on-frame-leave (:id frame))))
|
||||||
text-pos-x (if (:use-for-thumbnail? frame) 15 0)]
|
text-pos-x (if (:use-for-thumbnail? frame) 15 0)]
|
||||||
|
|
||||||
[:*
|
(when (not (:hidden frame))
|
||||||
(when (:use-for-thumbnail? frame)
|
[:*
|
||||||
[:g {:transform (str (when (and selected? modifiers)
|
(when (:use-for-thumbnail? frame)
|
||||||
(str (:displacement modifiers) " "))
|
[:g {:transform (str (when (and selected? modifiers)
|
||||||
(text-transform label-pos zoom))}
|
(str (:displacement modifiers) " "))
|
||||||
[:svg {:x 0
|
(text-transform label-pos zoom))}
|
||||||
:y -9
|
[:svg {:x 0
|
||||||
:width 12
|
:y -9
|
||||||
:height 12
|
:width 12
|
||||||
:class "workspace-frame-icon"
|
:height 12
|
||||||
|
:class "workspace-frame-icon"
|
||||||
|
:style {:fill (when selected? "var(--color-primary-dark)")}
|
||||||
|
:visibility (if show-artboard-names? "visible" "hidden")}
|
||||||
|
[:use {:href "#icon-set-thumbnail"}]]])
|
||||||
|
[:text {:x text-pos-x
|
||||||
|
:y 0
|
||||||
|
:width width
|
||||||
|
:height 20
|
||||||
|
:class "workspace-frame-label"
|
||||||
|
:transform (str (when (and selected? modifiers)
|
||||||
|
(str (:displacement modifiers) " "))
|
||||||
|
(text-transform label-pos zoom))
|
||||||
:style {:fill (when selected? "var(--color-primary-dark)")}
|
:style {:fill (when selected? "var(--color-primary-dark)")}
|
||||||
:visibility (if show-artboard-names? "visible" "hidden")}
|
:visibility (if show-artboard-names? "visible" "hidden")
|
||||||
[:use {:href "#icon-set-thumbnail"}]]])
|
:on-mouse-down on-mouse-down
|
||||||
[:text {:x text-pos-x
|
:on-double-click on-double-click
|
||||||
:y 0
|
:on-context-menu on-context-menu
|
||||||
:width width
|
:on-pointer-enter on-pointer-enter
|
||||||
:height 20
|
:on-pointer-leave on-pointer-leave}
|
||||||
:class "workspace-frame-label"
|
(:name frame)]])))
|
||||||
:transform (str (when (and selected? modifiers)
|
|
||||||
(str (:displacement modifiers) " "))
|
|
||||||
(text-transform label-pos zoom))
|
|
||||||
:style {:fill (when selected? "var(--color-primary-dark)")}
|
|
||||||
:visibility (if show-artboard-names? "visible" "hidden")
|
|
||||||
:on-mouse-down on-mouse-down
|
|
||||||
:on-double-click on-double-click
|
|
||||||
:on-context-menu on-context-menu
|
|
||||||
:on-pointer-enter on-pointer-enter
|
|
||||||
:on-pointer-leave on-pointer-leave}
|
|
||||||
(:name frame)]]))
|
|
||||||
|
|
||||||
(mf/defc frame-titles
|
(mf/defc frame-titles
|
||||||
{::mf/wrap-props false
|
{::mf/wrap-props false
|
||||||
|
|
|
@ -10,17 +10,34 @@
|
||||||
[beicon.core :as rx]))
|
[beicon.core :as rx]))
|
||||||
|
|
||||||
(defonce cache (atom {}))
|
(defonce cache (atom {}))
|
||||||
|
(defonce pending (atom {}))
|
||||||
|
|
||||||
(defn with-cache
|
(defn with-cache
|
||||||
[{:keys [key max-age]} observable]
|
[{:keys [key max-age]} observable]
|
||||||
(let [entry (get @cache key)
|
(let [entry (get @cache key)
|
||||||
|
pending-entry (get @pending key)
|
||||||
|
|
||||||
age (when entry
|
age (when entry
|
||||||
(dt/diff (dt/now)
|
(dt/diff (dt/now)
|
||||||
(:created-at entry)))]
|
(:created-at entry)))]
|
||||||
(if (and (some? entry) (< age max-age))
|
(cond
|
||||||
|
(and (some? entry) (< age max-age))
|
||||||
(rx/of (:data entry))
|
(rx/of (:data entry))
|
||||||
(->> observable
|
|
||||||
(rx/tap
|
(some? pending-entry)
|
||||||
(fn [data]
|
pending-entry
|
||||||
(let [entry {:created-at (dt/now) :data data}]
|
|
||||||
(swap! cache assoc key entry))))))))
|
:else
|
||||||
|
(let [subject (rx/subject)]
|
||||||
|
(swap! pending assoc key subject)
|
||||||
|
(->> observable
|
||||||
|
(rx/catch #(do (rx/error! subject %)
|
||||||
|
(swap! pending dissoc key)
|
||||||
|
(rx/throw %)))
|
||||||
|
(rx/tap
|
||||||
|
(fn [data]
|
||||||
|
(let [entry {:created-at (dt/now) :data data}]
|
||||||
|
(swap! cache assoc key entry))
|
||||||
|
(rx/push! subject data)
|
||||||
|
(rx/end! subject)
|
||||||
|
(swap! pending dissoc key))))))))
|
||||||
|
|
|
@ -558,3 +558,11 @@
|
||||||
(seq (.-children node)))]
|
(seq (.-children node)))]
|
||||||
(->> root-node
|
(->> root-node
|
||||||
(tree-seq branch? get-children))))
|
(tree-seq branch? get-children))))
|
||||||
|
|
||||||
|
(defn check-font? [font]
|
||||||
|
(let [fonts (.-fonts globals/document)]
|
||||||
|
(.check fonts font)))
|
||||||
|
|
||||||
|
(defn load-font [font]
|
||||||
|
(let [fonts (.-fonts globals/document)]
|
||||||
|
(.load fonts font)))
|
||||||
|
|
|
@ -173,32 +173,34 @@
|
||||||
(fetch-data-uri uri false))
|
(fetch-data-uri uri false))
|
||||||
|
|
||||||
([uri throw-err?]
|
([uri throw-err?]
|
||||||
(c/with-cache {:key uri :max-age (dt/duration {:hours 4})}
|
(let [request-str
|
||||||
(let [request-stream
|
(->> (send! {:method :get
|
||||||
(send! {:method :get
|
:uri uri
|
||||||
:uri uri
|
:response-type :blob
|
||||||
:response-type :blob
|
:omit-default-headers true})
|
||||||
:omit-default-headers true})
|
(rx/tap
|
||||||
|
(fn [resp]
|
||||||
|
(when (or (< (:status resp) 200) (>= (:status resp) 300))
|
||||||
|
(rx/throw (js/Error. "Error fetching data uri" #js {:cause (clj->js resp)})))))
|
||||||
|
|
||||||
request-stream
|
(rx/map :body)
|
||||||
(if throw-err?
|
(rx/mapcat wapi/read-file-as-data-url)
|
||||||
(rx/tap #(when-not (and (>= (:status %) 200) (< (:status %) 300))
|
(rx/map #(hash-map uri %))
|
||||||
;; HTTP ERRROR
|
(c/with-cache {:key uri :max-age (dt/duration {:hours 4})}))]
|
||||||
(throw (js/Error. "Error fetching data uri" #js {:cause (clj->js %)})))
|
|
||||||
request-stream)
|
;; We need to check `throw-err?` after the cache is resolved otherwise we cannot cache request
|
||||||
(rx/filter #(= 200 (:status %))
|
;; with different values of throw-err. By default we throw always the exception and then we just
|
||||||
request-stream))]
|
;; ignore when `throw-err?` is `true`
|
||||||
(->> request-stream
|
(if (not throw-err?)
|
||||||
(rx/map :body)
|
(->> request-str (rx/catch #(rx/empty)))
|
||||||
(rx/mapcat wapi/read-file-as-data-url)
|
request-str))))
|
||||||
(rx/map #(hash-map uri %)))))))
|
|
||||||
|
|
||||||
(defn fetch-text [url]
|
(defn fetch-text [url]
|
||||||
(c/with-cache {:key url :max-age (dt/duration {:hours 4})}
|
(->> (send!
|
||||||
(->> (send!
|
{:method :get
|
||||||
{:method :get
|
:mode :cors
|
||||||
:mode :cors
|
:omit-default-headers true
|
||||||
:omit-default-headers true
|
:uri url
|
||||||
:uri url
|
:response-type :text})
|
||||||
:response-type :text})
|
(rx/map :body)
|
||||||
(rx/map :body))))
|
(c/with-cache {:key url :max-age (dt/duration {:hours 4})})))
|
||||||
|
|
|
@ -23,14 +23,18 @@
|
||||||
{:label "Français (community)" :value "fr"}
|
{:label "Français (community)" :value "fr"}
|
||||||
{:label "Deutsch (community)" :value "de"}
|
{:label "Deutsch (community)" :value "de"}
|
||||||
;; {:label "Italiano (community)" :value "it"}
|
;; {:label "Italiano (community)" :value "it"}
|
||||||
|
{:label "Norsk - Bokmål (community)" :value "nb_no"}
|
||||||
|
{:label "Portuguese - Brazil (community)" :value "pt_br"}
|
||||||
|
{:label "Polski (community)" :value "pl"}
|
||||||
{:label "Русский (community)" :value "ru"}
|
{:label "Русский (community)" :value "ru"}
|
||||||
|
{:label "Rumanian (community)" :value "ro"}
|
||||||
{:label "Türkçe (community)" :value "tr"}
|
{:label "Türkçe (community)" :value "tr"}
|
||||||
{:label "Rumanian (communit)" :value "ro"}
|
|
||||||
{:label "Portuguese (Brazil, community)" :value "pt_br"}
|
|
||||||
{:label "Ελληνική γλώσσα (community)" :value "el"}
|
{:label "Ελληνική γλώσσα (community)" :value "el"}
|
||||||
{:label "עִבְרִית (community)" :value "he"}
|
{:label "עִבְרִית (community)" :value "he"}
|
||||||
{:label "عربي/عربى (community)" :value "ar"}
|
{:label "عربي/عربى (community)" :value "ar"}
|
||||||
{:label "简体中文 (community)" :value "zh_cn"}])
|
{:label "فارسی (community)" :value "fa"}
|
||||||
|
{:label "简体中文 (community)" :value "zh_cn"}
|
||||||
|
{:label "中国传统的 (community)" :value "zh_hant"}])
|
||||||
|
|
||||||
(defn- parse-locale
|
(defn- parse-locale
|
||||||
[locale]
|
[locale]
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
(ns app.util.import.parser
|
(ns app.util.import.parser
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
|
[app.common.data.macros :as dm]
|
||||||
[app.common.geom.matrix :as gmt]
|
[app.common.geom.matrix :as gmt]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.spec.interactions :as cti]
|
[app.common.spec.interactions :as cti]
|
||||||
|
@ -213,16 +214,29 @@
|
||||||
(reduce add-attrs node-attrs))
|
(reduce add-attrs node-attrs))
|
||||||
|
|
||||||
(= type :frame)
|
(= type :frame)
|
||||||
(let [;; The nodes with the "frame-background" class can have some anidation depending on the strokes they have
|
(let [;; Old .penpot files doesn't have "g" nodes. They have a clipPath reference as a node attribute
|
||||||
|
to-url #(dm/str "url(#" % ")")
|
||||||
|
frame-clip-rect-node (->> (find-all-nodes node :defs)
|
||||||
|
(mapcat #(find-all-nodes % :clipPath))
|
||||||
|
(filter #(= (to-url (:id (:attrs %))) (:clip-path node-attrs)))
|
||||||
|
(mapcat #(find-all-nodes % #{:rect :path}))
|
||||||
|
(first))
|
||||||
|
|
||||||
|
;; The nodes with the "frame-background" class can have some anidation depending on the strokes they have
|
||||||
g-nodes (find-all-nodes node :g)
|
g-nodes (find-all-nodes node :g)
|
||||||
defs-nodes (flatten (map #(find-all-nodes % :defs) g-nodes))
|
defs-nodes (flatten (map #(find-all-nodes % :defs) g-nodes))
|
||||||
gg-nodes (flatten (map #(find-all-nodes % :g) g-nodes))
|
gg-nodes (flatten (map #(find-all-nodes % :g) g-nodes))
|
||||||
|
|
||||||
|
|
||||||
rect-nodes (flatten [[(find-all-nodes node :rect)]
|
rect-nodes (flatten [[(find-all-nodes node :rect)]
|
||||||
(map #(find-all-nodes % #{:rect :path}) defs-nodes)
|
(map #(find-all-nodes % #{:rect :path}) defs-nodes)
|
||||||
(map #(find-all-nodes % #{:rect :path}) g-nodes)
|
(map #(find-all-nodes % #{:rect :path}) g-nodes)
|
||||||
(map #(find-all-nodes % #{:rect :path}) gg-nodes)])
|
(map #(find-all-nodes % #{:rect :path}) gg-nodes)])
|
||||||
svg-node (d/seek #(= "frame-background" (get-in % [:attrs :class])) rect-nodes)]
|
svg-node (d/seek #(= "frame-background" (get-in % [:attrs :class])) rect-nodes)]
|
||||||
(merge (add-attrs {} (:attrs svg-node)) node-attrs))
|
(merge
|
||||||
|
(add-attrs {} (:attrs frame-clip-rect-node))
|
||||||
|
(add-attrs {} (:attrs svg-node))
|
||||||
|
node-attrs))
|
||||||
|
|
||||||
(= type :svg-raw)
|
(= type :svg-raw)
|
||||||
(let [svg-content (get-data node :penpot:svg-content)
|
(let [svg-content (get-data node :penpot:svg-content)
|
||||||
|
|
|
@ -9,9 +9,11 @@
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.transit :as transit]
|
[app.common.transit :as transit]
|
||||||
|
[app.main.fonts :as fonts]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.text-position-data :as tpd]))
|
[app.util.text-position-data :as tpd]
|
||||||
|
[promesa.core :as p]))
|
||||||
|
|
||||||
(defn parse-text-nodes
|
(defn parse-text-nodes
|
||||||
"Given a text node retrieves the rectangles for everyone of its paragraphs and its text."
|
"Given a text node retrieves the rectangles for everyone of its paragraphs and its text."
|
||||||
|
@ -27,6 +29,27 @@
|
||||||
(map parse-entry)
|
(map parse-entry)
|
||||||
(tpd/parse-text-nodes parent-node text-node))))
|
(tpd/parse-text-nodes parent-node text-node))))
|
||||||
|
|
||||||
|
(def load-promises (atom {}))
|
||||||
|
|
||||||
|
(defn load-font
|
||||||
|
[font]
|
||||||
|
(if (contains? @load-promises font)
|
||||||
|
(get @load-promises font)
|
||||||
|
(let [load-promise (dom/load-font font)]
|
||||||
|
(swap! load-promises assoc font load-promise)
|
||||||
|
load-promise)))
|
||||||
|
|
||||||
|
(defn resolve-font
|
||||||
|
[^js node]
|
||||||
|
|
||||||
|
(let [styles (js/getComputedStyle node)
|
||||||
|
font (.getPropertyValue styles "font")]
|
||||||
|
(if (dom/check-font? font)
|
||||||
|
(p/resolved font)
|
||||||
|
(let [font-id (.getPropertyValue styles "--font-id")]
|
||||||
|
(-> (fonts/ensure-loaded! font-id)
|
||||||
|
(p/then #(when (not (dom/check-font? font))
|
||||||
|
(load-font font))))))))
|
||||||
|
|
||||||
(defn calc-text-node-positions
|
(defn calc-text-node-positions
|
||||||
[base-node viewport zoom]
|
[base-node viewport zoom]
|
||||||
|
@ -58,22 +81,25 @@
|
||||||
:width (- (:x p2) (:x p1))
|
:width (- (:x p2) (:x p1))
|
||||||
:height (- (:y p2) (:y p1)))))
|
:height (- (:y p2) (:y p1)))))
|
||||||
|
|
||||||
text-nodes (dom/query-all base-node ".text-node, span[data-text]")]
|
text-nodes (dom/query-all base-node ".text-node, span[data-text]")
|
||||||
|
load-fonts (->> text-nodes (map resolve-font))]
|
||||||
(->> text-nodes
|
(-> (p/all load-fonts)
|
||||||
(mapcat
|
(p/then
|
||||||
(fn [parent-node]
|
(fn []
|
||||||
(let [direction (.-direction (js/getComputedStyle parent-node))]
|
(->> text-nodes
|
||||||
(->> (.-childNodes parent-node)
|
(mapcat
|
||||||
(mapcat #(parse-text-nodes parent-node direction %))))))
|
(fn [parent-node]
|
||||||
(mapv #(update % :position translate-rect))))))
|
(let [direction (.-direction (js/getComputedStyle parent-node))]
|
||||||
|
(->> (.-childNodes parent-node)
|
||||||
|
(mapcat #(parse-text-nodes parent-node direction %))))))
|
||||||
|
(mapv #(update % :position translate-rect)))))))))
|
||||||
|
|
||||||
(defn calc-position-data
|
(defn calc-position-data
|
||||||
[base-node]
|
[base-node]
|
||||||
(let [viewport (dom/get-element "render")
|
(let [viewport (dom/get-element "render")
|
||||||
zoom (or (get-in @st/state [:workspace-local :zoom]) 1)]
|
zoom (or (get-in @st/state [:workspace-local :zoom]) 1)]
|
||||||
(when (and (some? base-node) (some? viewport))
|
(when (and (some? base-node) (some? viewport))
|
||||||
(let [text-data (calc-text-node-positions base-node viewport zoom)]
|
(p/let [text-data (calc-text-node-positions base-node viewport zoom)]
|
||||||
(when (d/not-empty? text-data)
|
(when (d/not-empty? text-data)
|
||||||
(->> text-data
|
(->> text-data
|
||||||
(mapv (fn [{:keys [node position text direction]}]
|
(mapv (fn [{:keys [node position text direction]}]
|
||||||
|
|
|
@ -125,7 +125,8 @@
|
||||||
match-criteria?
|
match-criteria?
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
(and (not (:hidden shape))
|
(and (not (:hidden shape))
|
||||||
(not (:blocked shape))
|
(or (= :frame (:type shape)) ;; We return frames even if blocked
|
||||||
|
(not (:blocked shape)))
|
||||||
(or (not frame-id) (= frame-id (:frame-id shape)))
|
(or (not frame-id) (= frame-id (:frame-id shape)))
|
||||||
(case (:type shape)
|
(case (:type shape)
|
||||||
:frame include-frames?
|
:frame include-frames?
|
||||||
|
|
|
@ -64,6 +64,9 @@
|
||||||
|
|
||||||
;; Disable thumbnail cache
|
;; Disable thumbnail cache
|
||||||
:disable-thumbnail-cache
|
:disable-thumbnail-cache
|
||||||
|
|
||||||
|
;; Disable frame thumbnails
|
||||||
|
:disable-frame-thumbnails
|
||||||
})
|
})
|
||||||
|
|
||||||
;; These events are excluded when we activate the :events flag
|
;; These events are excluded when we activate the :events flag
|
||||||
|
|
|
@ -567,24 +567,6 @@ msgstr "ترغب في الكلام؟ تحدث معنا في Gitter"
|
||||||
msgid "feedback.description"
|
msgid "feedback.description"
|
||||||
msgstr "وصف"
|
msgstr "وصف"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-go-to"
|
|
||||||
msgstr "اذهب إلى المناقشات"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle1"
|
|
||||||
msgstr "انضم إلى منتدى التواصل التعاوني لفريق Penpot."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle2"
|
|
||||||
msgstr ""
|
|
||||||
"يمكنك طرح الأسئلة والإجابة عليها، إجراء محادثات مفتوحة، ومتابعة القرارات "
|
|
||||||
"التي تؤثر على المشروع."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-title"
|
|
||||||
msgstr "مناقشات الفريق"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
msgid "feedback.subject"
|
msgid "feedback.subject"
|
||||||
msgstr "موضوع"
|
msgstr "موضوع"
|
||||||
|
|
|
@ -729,24 +729,6 @@ msgstr "Voleu parlar? Xategeu amb nosaltres a Gitter"
|
||||||
msgid "feedback.description"
|
msgid "feedback.description"
|
||||||
msgstr "Descripció"
|
msgstr "Descripció"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-go-to"
|
|
||||||
msgstr "Ves als debats"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle1"
|
|
||||||
msgstr "Uniu-vos al fòrum col·laboratiu del Penpot."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle2"
|
|
||||||
msgstr ""
|
|
||||||
"Podeu fer i respondre preguntes, tenir converses obertes i seguir les "
|
|
||||||
"decisions que afecten el projecte."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-title"
|
|
||||||
msgstr "Debats de l'equip"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
msgid "feedback.subject"
|
msgid "feedback.subject"
|
||||||
msgstr "Tema"
|
msgstr "Tema"
|
||||||
|
|
|
@ -804,24 +804,6 @@ msgstr "Möchten Sie sprechen? Chatten Sie mit uns bei Gitter"
|
||||||
msgid "feedback.description"
|
msgid "feedback.description"
|
||||||
msgstr "Beschreibung"
|
msgstr "Beschreibung"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-go-to"
|
|
||||||
msgstr "Zur Diskussion"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle1"
|
|
||||||
msgstr "Treten Sie dem kollaborativen Kommunikationsforum des Penpot-Teams bei."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle2"
|
|
||||||
msgstr ""
|
|
||||||
"Sie können Fragen stellen und beantworten, offene Gespräche führen und "
|
|
||||||
"Entscheidungen verfolgen, die das Projekt betreffen."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-title"
|
|
||||||
msgstr "Teambesprechungen"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
msgid "feedback.subject"
|
msgid "feedback.subject"
|
||||||
msgstr "Betreff"
|
msgstr "Betreff"
|
||||||
|
|
|
@ -480,24 +480,6 @@ msgstr "Νιώθετε σαν να μιλάτε; Συνομιλήστε μαζί
|
||||||
msgid "feedback.description"
|
msgid "feedback.description"
|
||||||
msgstr "Περιγραφή"
|
msgstr "Περιγραφή"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-go-to"
|
|
||||||
msgstr "Μετάβαση στις συζητήσεις"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle1"
|
|
||||||
msgstr "Μπείτε στο συνεργατικό φόρουμ του Penpot"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle2"
|
|
||||||
msgstr ""
|
|
||||||
"Μπορείτε να κάνετε και να απαντήσετε σε ερωτήσεις, να έχετε συνομιλίες "
|
|
||||||
"ανοιχτού τύπου και να συνεχίσετε να επηρεάζετε το έργο"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-title"
|
|
||||||
msgstr "Ομαδικές συζητήσεις "
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
msgid "feedback.subject"
|
msgid "feedback.subject"
|
||||||
msgstr "Επιχείρηση"
|
msgstr "Επιχείρηση"
|
||||||
|
|
|
@ -763,24 +763,6 @@ msgstr "Feeling like talking? Chat with us at Gitter"
|
||||||
msgid "feedback.description"
|
msgid "feedback.description"
|
||||||
msgstr "Description"
|
msgstr "Description"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-go-to"
|
|
||||||
msgstr "Go to discussions"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle1"
|
|
||||||
msgstr "Join Penpot team collaborative communication forum."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle2"
|
|
||||||
msgstr ""
|
|
||||||
"You can ask and answer questions, have open-ended conversations, and follow "
|
|
||||||
"along on decisions affecting the project."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-title"
|
|
||||||
msgstr "Team discussions"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
msgid "feedback.subject"
|
msgid "feedback.subject"
|
||||||
msgstr "Subject"
|
msgstr "Subject"
|
||||||
|
@ -795,6 +777,18 @@ msgstr ""
|
||||||
msgid "feedback.title"
|
msgid "feedback.title"
|
||||||
msgstr "Email"
|
msgstr "Email"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
|
msgid "feedback.twitter-go-to"
|
||||||
|
msgstr "Go to Twitter"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
|
msgid "feedback.twitter-subtitle1"
|
||||||
|
msgstr "Here to help with your technical queries."
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
|
msgid "feedback.twitter-title"
|
||||||
|
msgstr "Twitter support account"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/password.cljs
|
#: src/app/main/ui/settings/password.cljs
|
||||||
msgid "generic.error"
|
msgid "generic.error"
|
||||||
msgstr "An error has occurred"
|
msgstr "An error has occurred"
|
||||||
|
|
|
@ -606,8 +606,7 @@ msgstr "Resultados de búsqueda"
|
||||||
msgid "dashboard.type-something"
|
msgid "dashboard.type-something"
|
||||||
msgstr "Escribe algo para buscar"
|
msgstr "Escribe algo para buscar"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/password.cljs,
|
#: src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs
|
||||||
#: src/app/main/ui/settings/options.cljs
|
|
||||||
msgid "dashboard.update-settings"
|
msgid "dashboard.update-settings"
|
||||||
msgstr "Actualizar opciones"
|
msgstr "Actualizar opciones"
|
||||||
|
|
||||||
|
@ -837,6 +836,18 @@ msgstr ""
|
||||||
msgid "feedback.title"
|
msgid "feedback.title"
|
||||||
msgstr "Correo electrónico"
|
msgstr "Correo electrónico"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
|
msgid "feedback.twitter-go-to"
|
||||||
|
msgstr "Ir a Twitter"
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
|
msgid "feedback.twitter-subtitle1"
|
||||||
|
msgstr "Cuenta habilitada para responder todas tus dudas técnicas."
|
||||||
|
|
||||||
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
|
msgid "feedback.twitter-title"
|
||||||
|
msgstr "Cuenta de Twitter para soporte"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/password.cljs
|
#: src/app/main/ui/settings/password.cljs
|
||||||
msgid "generic.error"
|
msgid "generic.error"
|
||||||
msgstr "Ha ocurrido un error"
|
msgstr "Ha ocurrido un error"
|
||||||
|
@ -3168,18 +3179,15 @@ msgstr[1] "Exportar %s elementos"
|
||||||
msgid "workspace.options.export.suffix"
|
msgid "workspace.options.export.suffix"
|
||||||
msgstr "Sufijo"
|
msgstr "Sufijo"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs,
|
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
||||||
#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
|
||||||
msgid "workspace.options.exporting-complete"
|
msgid "workspace.options.exporting-complete"
|
||||||
msgstr "Exportación completa"
|
msgstr "Exportación completa"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs,
|
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
||||||
#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
|
||||||
msgid "workspace.options.exporting-object"
|
msgid "workspace.options.exporting-object"
|
||||||
msgstr "Exportando…"
|
msgstr "Exportando…"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs,
|
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
||||||
#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
|
||||||
msgid "workspace.options.exporting-object-error"
|
msgid "workspace.options.exporting-object-error"
|
||||||
msgstr "Exportación fallida"
|
msgstr "Exportación fallida"
|
||||||
|
|
||||||
|
@ -4326,4 +4334,4 @@ msgid "workspace.updates.update"
|
||||||
msgstr "Actualizar"
|
msgstr "Actualizar"
|
||||||
|
|
||||||
msgid "workspace.viewport.click-to-close-path"
|
msgid "workspace.viewport.click-to-close-path"
|
||||||
msgstr "Pulsar para cerrar la ruta"
|
msgstr "Pulsar para cerrar la ruta"
|
||||||
|
|
|
@ -657,24 +657,6 @@ msgstr "Envie de parler? Discutez avec nous sur Gitter"
|
||||||
msgid "feedback.description"
|
msgid "feedback.description"
|
||||||
msgstr "Description"
|
msgstr "Description"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-go-to"
|
|
||||||
msgstr "Aller aux discussions"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle1"
|
|
||||||
msgstr "Rejoignez le forum de communication collaborative de l'équipe Penpot."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle2"
|
|
||||||
msgstr ""
|
|
||||||
"Vous pouvez poser des questions et y répondre, avoir des conversations "
|
|
||||||
"ouvertes et suivre les décisions affectant le projet."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-title"
|
|
||||||
msgstr "Discussions en équipe"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
msgid "feedback.subject"
|
msgid "feedback.subject"
|
||||||
msgstr "Sujet"
|
msgstr "Sujet"
|
||||||
|
|
|
@ -665,8 +665,7 @@ msgstr "כתובת הדוא״ל לאימות חייבת להיות תואמת"
|
||||||
msgid "errors.email-spam-or-permanent-bounces"
|
msgid "errors.email-spam-or-permanent-bounces"
|
||||||
msgstr "כתובת הדוא״ל „%s” דווחה כספאם או שההודעות תוקפצנה לצמיתות."
|
msgstr "כתובת הדוא״ל „%s” דווחה כספאם או שההודעות תוקפצנה לצמיתות."
|
||||||
|
|
||||||
#: src/app/main/ui/auth/verify_token.cljs,
|
#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs
|
||||||
#: src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs
|
|
||||||
msgid "errors.generic"
|
msgid "errors.generic"
|
||||||
msgstr "קרה משהו לא טוב."
|
msgstr "קרה משהו לא טוב."
|
||||||
|
|
||||||
|
@ -768,24 +767,6 @@ msgstr "מעניין אותך לדבר? מזמינים אותו ל־Gitter"
|
||||||
msgid "feedback.description"
|
msgid "feedback.description"
|
||||||
msgstr "תיאור"
|
msgstr "תיאור"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-go-to"
|
|
||||||
msgstr "מעבר לדיונים"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle1"
|
|
||||||
msgstr "מזמינים אותך להצטרף לפורום התקשורת של Penpot."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle2"
|
|
||||||
msgstr ""
|
|
||||||
"ניתן לשאול ולענות על שאלות, לנהל דיונים פתוחים ולעקוב אחר החלטות שמשפיעות "
|
|
||||||
"על המיזם."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-title"
|
|
||||||
msgstr "דיונים צוותיים"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
msgid "feedback.subject"
|
msgid "feedback.subject"
|
||||||
msgstr "נושא"
|
msgstr "נושא"
|
||||||
|
@ -1206,8 +1187,7 @@ msgstr ""
|
||||||
msgid "labels.internal-error.main-message"
|
msgid "labels.internal-error.main-message"
|
||||||
msgstr "שגיאה פנימית"
|
msgstr "שגיאה פנימית"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs,
|
#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
|
||||||
#: src/app/main/ui/dashboard/sidebar.cljs
|
|
||||||
msgid "labels.invitations"
|
msgid "labels.invitations"
|
||||||
msgstr "הזמנות"
|
msgstr "הזמנות"
|
||||||
|
|
||||||
|
@ -1229,11 +1209,11 @@ msgstr "יציאה"
|
||||||
msgid "labels.manage-fonts"
|
msgid "labels.manage-fonts"
|
||||||
msgstr "ניהול גופנים"
|
msgstr "ניהול גופנים"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
|
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
|
||||||
msgid "labels.member"
|
msgid "labels.member"
|
||||||
msgstr "חבר"
|
msgstr "חבר"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
|
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
|
||||||
msgid "labels.members"
|
msgid "labels.members"
|
||||||
msgstr "חברים"
|
msgstr "חברים"
|
||||||
|
|
||||||
|
@ -1349,9 +1329,7 @@ msgstr "הסרה"
|
||||||
msgid "labels.remove-member"
|
msgid "labels.remove-member"
|
||||||
msgstr "הסרת חבר"
|
msgstr "הסרת חבר"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/sidebar.cljs,
|
#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||||
#: src/app/main/ui/dashboard/project_menu.cljs,
|
|
||||||
#: src/app/main/ui/dashboard/file_menu.cljs
|
|
||||||
msgid "labels.rename"
|
msgid "labels.rename"
|
||||||
msgstr "שינוי שם"
|
msgstr "שינוי שם"
|
||||||
|
|
||||||
|
@ -1363,7 +1341,7 @@ msgstr "שינוי שם לצוות"
|
||||||
msgid "labels.resend-invitation"
|
msgid "labels.resend-invitation"
|
||||||
msgstr "שליחת ההזמנה מחדש"
|
msgstr "שליחת ההזמנה מחדש"
|
||||||
|
|
||||||
#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs
|
#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs
|
||||||
msgid "labels.retry"
|
msgid "labels.retry"
|
||||||
msgstr "ניסיון חוזר"
|
msgstr "ניסיון חוזר"
|
||||||
|
|
||||||
|
@ -1455,7 +1433,7 @@ msgstr "סביבת עבודה"
|
||||||
msgid "labels.write-new-comment"
|
msgid "labels.write-new-comment"
|
||||||
msgstr "כתיבת הערה חדשה"
|
msgstr "כתיבת הערה חדשה"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
|
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
|
||||||
msgid "labels.you"
|
msgid "labels.you"
|
||||||
msgstr "(אני)"
|
msgstr "(אני)"
|
||||||
|
|
||||||
|
@ -2277,8 +2255,7 @@ msgstr "מיקוד פעיל"
|
||||||
msgid "workspace.focus.selection"
|
msgid "workspace.focus.selection"
|
||||||
msgstr "בחירה"
|
msgstr "בחירה"
|
||||||
|
|
||||||
#: src/app/main/data/workspace/libraries.cljs,
|
#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs
|
||||||
#: src/app/main/ui/components/color_bullet.cljs
|
|
||||||
msgid "workspace.gradients.linear"
|
msgid "workspace.gradients.linear"
|
||||||
msgstr "מדרג קווי"
|
msgstr "מדרג קווי"
|
||||||
|
|
||||||
|
@ -2648,13 +2625,11 @@ msgstr "עיצוב"
|
||||||
msgid "workspace.options.export"
|
msgid "workspace.options.export"
|
||||||
msgstr "ייצוא"
|
msgstr "ייצוא"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs,
|
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs
|
||||||
#: src/app/main/ui/handoff/exports.cljs
|
|
||||||
msgid "workspace.options.export-multiple"
|
msgid "workspace.options.export-multiple"
|
||||||
msgstr "ייצוא הבחירה"
|
msgstr "ייצוא הבחירה"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs,
|
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs
|
||||||
#: src/app/main/ui/handoff/exports.cljs
|
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "workspace.options.export-object"
|
msgid "workspace.options.export-object"
|
||||||
msgstr "ייצוא רכיב"
|
msgstr "ייצוא רכיב"
|
||||||
|
@ -2668,18 +2643,15 @@ msgstr "סיומת"
|
||||||
msgid "workspace.options.exporting-complete"
|
msgid "workspace.options.exporting-complete"
|
||||||
msgstr "הייצוא הושלם"
|
msgstr "הייצוא הושלם"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs,
|
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs
|
||||||
#: src/app/main/ui/handoff/exports.cljs
|
|
||||||
msgid "workspace.options.exporting-object"
|
msgid "workspace.options.exporting-object"
|
||||||
msgstr "מתבצע ייצוא…"
|
msgstr "מתבצע ייצוא…"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs,
|
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
||||||
#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
|
||||||
msgid "workspace.options.exporting-object-error"
|
msgid "workspace.options.exporting-object-error"
|
||||||
msgstr "הייצוא נכשל"
|
msgstr "הייצוא נכשל"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs,
|
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
||||||
#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
|
||||||
msgid "workspace.options.exporting-object-slow"
|
msgid "workspace.options.exporting-object-slow"
|
||||||
msgstr "הייצוא אטי בהגזמה"
|
msgstr "הייצוא אטי בהגזמה"
|
||||||
|
|
||||||
|
@ -3123,8 +3095,7 @@ msgstr "פינות בודדות"
|
||||||
msgid "workspace.options.recent-fonts"
|
msgid "workspace.options.recent-fonts"
|
||||||
msgstr "אחרונים"
|
msgstr "אחרונים"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs,
|
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
||||||
#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
|
||||||
msgid "workspace.options.retry"
|
msgid "workspace.options.retry"
|
||||||
msgstr "לנסות שוב"
|
msgstr "לנסות שוב"
|
||||||
|
|
||||||
|
@ -3634,8 +3605,7 @@ msgstr "צורות"
|
||||||
msgid "workspace.sidebar.layers.texts"
|
msgid "workspace.sidebar.layers.texts"
|
||||||
msgstr "טקסטים"
|
msgstr "טקסטים"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs,
|
#: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, src/app/main/ui/handoff/attributes/svg.cljs
|
||||||
#: src/app/main/ui/handoff/attributes/svg.cljs
|
|
||||||
msgid "workspace.sidebar.options.svg-attrs.title"
|
msgid "workspace.sidebar.options.svg-attrs.title"
|
||||||
msgstr "מאפייני SVG יובאו"
|
msgstr "מאפייני SVG יובאו"
|
||||||
|
|
||||||
|
@ -3823,4 +3793,4 @@ msgid "workspace.updates.update"
|
||||||
msgstr "עדכון"
|
msgstr "עדכון"
|
||||||
|
|
||||||
msgid "workspace.viewport.click-to-close-path"
|
msgid "workspace.viewport.click-to-close-path"
|
||||||
msgstr "לחיצה תסגור את הנתיב"
|
msgstr "לחיצה תסגור את הנתיב"
|
||||||
|
|
|
@ -235,14 +235,6 @@ msgstr "Ta del i sludringen"
|
||||||
msgid "feedback.description"
|
msgid "feedback.description"
|
||||||
msgstr "Beskrivelse"
|
msgstr "Beskrivelse"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-go-to"
|
|
||||||
msgstr "Gå til diskusjoner"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-title"
|
|
||||||
msgstr "Lagdiskusjoner"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
msgid "feedback.subject"
|
msgid "feedback.subject"
|
||||||
msgstr "Emne"
|
msgstr "Emne"
|
||||||
|
|
|
@ -546,24 +546,6 @@ msgstr "Com vontade de falar? Converse conosco no Gitter"
|
||||||
msgid "feedback.description"
|
msgid "feedback.description"
|
||||||
msgstr "Descrição"
|
msgstr "Descrição"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-go-to"
|
|
||||||
msgstr "Ir para discussões"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle1"
|
|
||||||
msgstr "Junte-se ao fórum de comunicação colaborativa da equipe da Penpot."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle2"
|
|
||||||
msgstr ""
|
|
||||||
"Você pode fazer e responder perguntas, ter conversas abertas e acompanhar "
|
|
||||||
"as decisões que afetam o projeto."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-title"
|
|
||||||
msgstr "Discussões da equipe"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
msgid "feedback.subject"
|
msgid "feedback.subject"
|
||||||
msgstr "Assunto"
|
msgstr "Assunto"
|
||||||
|
|
|
@ -537,24 +537,6 @@ msgstr "Te simți sociabil? Hai să vorbim pe Gitter"
|
||||||
msgid "feedback.description"
|
msgid "feedback.description"
|
||||||
msgstr "Descriere"
|
msgstr "Descriere"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-go-to"
|
|
||||||
msgstr "Du-te la discuții"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle1"
|
|
||||||
msgstr "Alătură-te forumului de comunicare al echipei Penpot."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle2"
|
|
||||||
msgstr ""
|
|
||||||
"Poți pune întrebări, iei parte la discuții deschise și poți contribui la "
|
|
||||||
"dezvoltarea proiectului."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-title"
|
|
||||||
msgstr "Discuțiile echipei"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
msgid "feedback.subject"
|
msgid "feedback.subject"
|
||||||
msgstr "Subiect"
|
msgstr "Subiect"
|
||||||
|
|
|
@ -720,25 +720,6 @@ msgstr "Поговорим? Заходите в чат Gitter!"
|
||||||
msgid "feedback.description"
|
msgid "feedback.description"
|
||||||
msgstr "Подробности"
|
msgstr "Подробности"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-go-to"
|
|
||||||
msgstr "Зайти в обсуждения"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle1"
|
|
||||||
msgstr "Посетите форум для совместного обсуждения планов на Penpot."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle2"
|
|
||||||
msgstr ""
|
|
||||||
"Там вы можете задавать вопросы, проследить за неоднозначными задачами, и "
|
|
||||||
"поучаствовать в принятии решений по проекту."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
#, fuzzy
|
|
||||||
msgid "feedback.discussions-title"
|
|
||||||
msgstr "Командный форум"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid "feedback.subject"
|
msgid "feedback.subject"
|
||||||
|
|
|
@ -682,8 +682,7 @@ msgstr "Doğrulama e-postası eşleşmiyor"
|
||||||
msgid "errors.email-spam-or-permanent-bounces"
|
msgid "errors.email-spam-or-permanent-bounces"
|
||||||
msgstr "«%s» e-postasının spam veya kalıcı olarak geri döndüğü bildirildi."
|
msgstr "«%s» e-postasının spam veya kalıcı olarak geri döndüğü bildirildi."
|
||||||
|
|
||||||
#: src/app/main/ui/auth/verify_token.cljs,
|
#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs
|
||||||
#: src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs
|
|
||||||
msgid "errors.generic"
|
msgid "errors.generic"
|
||||||
msgstr "Bir şeyler ters gitti."
|
msgstr "Bir şeyler ters gitti."
|
||||||
|
|
||||||
|
@ -789,24 +788,6 @@ msgstr "Sohbet etmek ister misin? Glitter'da bizimle sohbet edebilirsin"
|
||||||
msgid "feedback.description"
|
msgid "feedback.description"
|
||||||
msgstr "Açıklama"
|
msgstr "Açıklama"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-go-to"
|
|
||||||
msgstr "Tartışmalar bölümüne git"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle1"
|
|
||||||
msgstr "Penpot takımı ortak iletişim forumuna katıl."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle2"
|
|
||||||
msgstr ""
|
|
||||||
"Soru sorabilir ve soruları cevaplayabilir, açık uçlu tartışmalar yapabilir "
|
|
||||||
"ve projeyi etkileyen kararları takip edebilirsin."
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-title"
|
|
||||||
msgstr "Takım tartışmaları"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
msgid "feedback.subject"
|
msgid "feedback.subject"
|
||||||
msgstr "Konu"
|
msgstr "Konu"
|
||||||
|
@ -1230,8 +1211,7 @@ msgstr ""
|
||||||
msgid "labels.internal-error.main-message"
|
msgid "labels.internal-error.main-message"
|
||||||
msgstr "İç Hata"
|
msgstr "İç Hata"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs,
|
#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
|
||||||
#: src/app/main/ui/dashboard/sidebar.cljs
|
|
||||||
msgid "labels.invitations"
|
msgid "labels.invitations"
|
||||||
msgstr "Davetler"
|
msgstr "Davetler"
|
||||||
|
|
||||||
|
@ -1253,11 +1233,11 @@ msgstr "Oturumu kapat"
|
||||||
msgid "labels.manage-fonts"
|
msgid "labels.manage-fonts"
|
||||||
msgstr "Yazı tiplerini yönet"
|
msgstr "Yazı tiplerini yönet"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
|
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
|
||||||
msgid "labels.member"
|
msgid "labels.member"
|
||||||
msgstr "Üye"
|
msgstr "Üye"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
|
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
|
||||||
msgid "labels.members"
|
msgid "labels.members"
|
||||||
msgstr "Üyeler"
|
msgstr "Üyeler"
|
||||||
|
|
||||||
|
@ -1369,9 +1349,7 @@ msgstr "Kaldır"
|
||||||
msgid "labels.remove-member"
|
msgid "labels.remove-member"
|
||||||
msgstr "Üyeyi kaldır"
|
msgstr "Üyeyi kaldır"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/sidebar.cljs,
|
#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||||
#: src/app/main/ui/dashboard/project_menu.cljs,
|
|
||||||
#: src/app/main/ui/dashboard/file_menu.cljs
|
|
||||||
msgid "labels.rename"
|
msgid "labels.rename"
|
||||||
msgstr "Yeniden adlandır"
|
msgstr "Yeniden adlandır"
|
||||||
|
|
||||||
|
@ -1383,7 +1361,7 @@ msgstr "Takımı yeniden adlandır"
|
||||||
msgid "labels.resend-invitation"
|
msgid "labels.resend-invitation"
|
||||||
msgstr "Daveti yeniden gönder"
|
msgstr "Daveti yeniden gönder"
|
||||||
|
|
||||||
#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs
|
#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs
|
||||||
msgid "labels.retry"
|
msgid "labels.retry"
|
||||||
msgstr "Yeniden dene"
|
msgstr "Yeniden dene"
|
||||||
|
|
||||||
|
@ -1476,7 +1454,7 @@ msgstr "Çalışma alanı"
|
||||||
msgid "labels.write-new-comment"
|
msgid "labels.write-new-comment"
|
||||||
msgstr "Yeni yorum yaz"
|
msgstr "Yeni yorum yaz"
|
||||||
|
|
||||||
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
|
#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs
|
||||||
msgid "labels.you"
|
msgid "labels.you"
|
||||||
msgstr "(siz)"
|
msgstr "(siz)"
|
||||||
|
|
||||||
|
@ -2324,8 +2302,7 @@ msgstr "Odaklanma açık"
|
||||||
msgid "workspace.focus.selection"
|
msgid "workspace.focus.selection"
|
||||||
msgstr "Seçim"
|
msgstr "Seçim"
|
||||||
|
|
||||||
#: src/app/main/data/workspace/libraries.cljs,
|
#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs
|
||||||
#: src/app/main/ui/components/color_bullet.cljs
|
|
||||||
msgid "workspace.gradients.linear"
|
msgid "workspace.gradients.linear"
|
||||||
msgstr "Doğrusal degrade"
|
msgstr "Doğrusal degrade"
|
||||||
|
|
||||||
|
@ -2695,6 +2672,15 @@ msgstr "Tasarım"
|
||||||
msgid "workspace.options.export"
|
msgid "workspace.options.export"
|
||||||
msgstr "Dışa aktar"
|
msgstr "Dışa aktar"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs
|
||||||
|
msgid "workspace.options.export-multiple"
|
||||||
|
msgstr "Seçimi dışa aktar"
|
||||||
|
|
||||||
|
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs
|
||||||
|
#, fuzzy
|
||||||
|
msgid "workspace.options.export-object"
|
||||||
|
msgstr "Dışa aktar"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs,
|
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs,
|
||||||
#: src/app/main/ui/handoff/exports.cljs
|
#: src/app/main/ui/handoff/exports.cljs
|
||||||
msgid "workspace.options.export-multiple"
|
msgid "workspace.options.export-multiple"
|
||||||
|
@ -2720,13 +2706,11 @@ msgstr "Dışa aktarma tamamlandı"
|
||||||
msgid "workspace.options.exporting-object"
|
msgid "workspace.options.exporting-object"
|
||||||
msgstr "Dışa aktarılıyor…"
|
msgstr "Dışa aktarılıyor…"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs,
|
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
||||||
#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
|
||||||
msgid "workspace.options.exporting-object-error"
|
msgid "workspace.options.exporting-object-error"
|
||||||
msgstr "Dışa aktarılamadı"
|
msgstr "Dışa aktarılamadı"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs,
|
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
||||||
#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
|
||||||
msgid "workspace.options.exporting-object-slow"
|
msgid "workspace.options.exporting-object-slow"
|
||||||
msgstr "Dışa aktarma beklenmedik şekilde yavaş"
|
msgstr "Dışa aktarma beklenmedik şekilde yavaş"
|
||||||
|
|
||||||
|
@ -3170,8 +3154,7 @@ msgstr "Tek köşe"
|
||||||
msgid "workspace.options.recent-fonts"
|
msgid "workspace.options.recent-fonts"
|
||||||
msgstr "Son kullanılanlar"
|
msgstr "Son kullanılanlar"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs,
|
#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
||||||
#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs
|
|
||||||
msgid "workspace.options.retry"
|
msgid "workspace.options.retry"
|
||||||
msgstr "Yeniden dene"
|
msgstr "Yeniden dene"
|
||||||
|
|
||||||
|
@ -3683,8 +3666,7 @@ msgstr "Şekiller"
|
||||||
msgid "workspace.sidebar.layers.texts"
|
msgid "workspace.sidebar.layers.texts"
|
||||||
msgstr "Metinler"
|
msgstr "Metinler"
|
||||||
|
|
||||||
#: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs,
|
#: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, src/app/main/ui/handoff/attributes/svg.cljs
|
||||||
#: src/app/main/ui/handoff/attributes/svg.cljs
|
|
||||||
msgid "workspace.sidebar.options.svg-attrs.title"
|
msgid "workspace.sidebar.options.svg-attrs.title"
|
||||||
msgstr "İçe Aktarılan SVG Öznitelikleri"
|
msgstr "İçe Aktarılan SVG Öznitelikleri"
|
||||||
|
|
||||||
|
@ -3872,4 +3854,4 @@ msgid "workspace.updates.update"
|
||||||
msgstr "Güncelle"
|
msgstr "Güncelle"
|
||||||
|
|
||||||
msgid "workspace.viewport.click-to-close-path"
|
msgid "workspace.viewport.click-to-close-path"
|
||||||
msgstr "Yolu kapatmak için tıklayın"
|
msgstr "Yolu kapatmak için tıklayın"
|
||||||
|
|
|
@ -715,22 +715,6 @@ msgstr "想说两句?来Gitter和我们聊聊"
|
||||||
msgid "feedback.description"
|
msgid "feedback.description"
|
||||||
msgstr "描述"
|
msgstr "描述"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-go-to"
|
|
||||||
msgstr "前往讨论"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle1"
|
|
||||||
msgstr "加入Penpot团队协作交流论坛。"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle2"
|
|
||||||
msgstr "你可以提问、回答问题,来一场开放的对话,并对影响项目的决策保持关注。"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-title"
|
|
||||||
msgstr "团队讨论"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
msgid "feedback.subject"
|
msgid "feedback.subject"
|
||||||
msgstr "话题"
|
msgstr "话题"
|
||||||
|
|
|
@ -607,18 +607,6 @@ msgstr "加入聊天"
|
||||||
msgid "feedback.description"
|
msgid "feedback.description"
|
||||||
msgstr "狀況描述"
|
msgstr "狀況描述"
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-go-to"
|
|
||||||
msgstr "前往討論"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-subtitle1"
|
|
||||||
msgstr "加入 Penpot 團隊的協作溝通論壇。"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
|
||||||
msgid "feedback.discussions-title"
|
|
||||||
msgstr "團隊討論"
|
|
||||||
|
|
||||||
#: src/app/main/ui/settings/feedback.cljs
|
#: src/app/main/ui/settings/feedback.cljs
|
||||||
msgid "feedback.title"
|
msgid "feedback.title"
|
||||||
msgstr "電子郵件"
|
msgstr "電子郵件"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue