Compare commits

..

1 Commits

Author SHA1 Message Date
Alejandro Alonso
222481fa0d 🎉 Basic graph wasm support 2025-12-22 06:51:56 +01:00
2723 changed files with 1261925 additions and 4983 deletions

View File

@@ -12,9 +12,7 @@
### :sparkles: New features & Enhancements
- Add new Box Shadow Tokens [Taiga #10201](https://tree.taiga.io/project/penpot/us/10201)
- Make i18n translation files load on-demand [Taiga #11474](https://tree.taiga.io/project/penpot/us/11474)
- Add deleted files to dashboard [Taiga #8149](https://tree.taiga.io/project/penpot/us/8149)
### :bug: Bugs fixed
@@ -85,7 +83,6 @@ example. It's still usable as before, we just removed the example.
- Ensure consistent snap behavior across all zoom levels [Github #7774](https://github.com/penpot/penpot/pull/7774) by [@Tokytome](https://github.com/Tokytome)
- Fix crash in token grid view due to tooltip validation (by @dfelinto) [Github #7887](https://github.com/penpot/penpot/pull/7887)
- Enable Hindi translations on the application
### :sparkles: New features & Enhancements
@@ -119,7 +116,6 @@ example. It's still usable as before, we just removed the example.
- Fix switch variants with paths [Taiga #12841](https://tree.taiga.io/project/penpot/issue/12841)
- Fix referencing typography tokens on font-family tokens [Taiga #12492](https://tree.taiga.io/project/penpot/issue/12492)
- Fix horizontal scroll on layer panel [Taiga #12843](https://tree.taiga.io/project/penpot/issue/12843)
- Fix unicode handling on email template abbreviation filter [Github #7966](https://github.com/penpot/penpot/pull/7966)
## 2.11.1

View File

@@ -240,4 +240,4 @@
</div>
</body>
</html>
</html>

View File

@@ -331,81 +331,6 @@
(set/difference cfeat/backend-only-features))
#{}))))
(defn check-file-exists
[cfg id & {:keys [include-deleted?]
:or {include-deleted? false}
:as options}]
(db/get-with-sql cfg [sql:get-minimal-file id]
{:db/remove-deleted (not include-deleted?)}))
(def ^:private sql:file-permissions
"select fpr.is_owner,
fpr.is_admin,
fpr.can_edit
from file_profile_rel as fpr
inner join file as f on (f.id = fpr.file_id)
where fpr.file_id = ?
and fpr.profile_id = ?
union all
select tpr.is_owner,
tpr.is_admin,
tpr.can_edit
from team_profile_rel as tpr
inner join project as p on (p.team_id = tpr.team_id)
inner join file as f on (p.id = f.project_id)
where f.id = ?
and tpr.profile_id = ?
union all
select ppr.is_owner,
ppr.is_admin,
ppr.can_edit
from project_profile_rel as ppr
inner join file as f on (f.project_id = ppr.project_id)
where f.id = ?
and ppr.profile_id = ?")
(defn- get-file-permissions*
[conn profile-id file-id]
(when (and profile-id file-id)
(db/exec! conn [sql:file-permissions
file-id profile-id
file-id profile-id
file-id profile-id])))
(defn get-file-permissions
([conn profile-id file-id]
(let [rows (get-file-permissions* conn profile-id file-id)
is-owner (boolean (some :is-owner rows))
is-admin (boolean (some :is-admin rows))
can-edit (boolean (some :can-edit rows))]
(when (seq rows)
{:type :membership
:is-owner is-owner
:is-admin (or is-owner is-admin)
:can-edit (or is-owner is-admin can-edit)
:can-read true
:is-logged (some? profile-id)})))
([conn profile-id file-id share-id]
(let [perms (get-file-permissions conn profile-id file-id)
ldata (some-> (db/get* conn :share-link {:id share-id :file-id file-id})
(dissoc :flags)
(update :pages db/decode-pgarray #{}))]
;; NOTE: in a future when share-link becomes more powerful and
;; will allow us specify which parts of the app is available, we
;; will probably need to tweak this function in order to expose
;; this flags to the frontend.
(cond
(some? perms) perms
(some? ldata) {:type :share-link
:can-read true
:pages (:pages ldata)
:is-logged (some? profile-id)
:who-comment (:who-comment ldata)
:who-inspect (:who-inspect ldata)}))))
(defn get-project
[cfg project-id]
(db/get cfg :project {:id project-id}))

View File

@@ -821,10 +821,9 @@
entries (keep (match-storage-entry-fn) entries)]
(doseq [{:keys [id entry]} entries]
(let [object (-> (read-entry input entry)
(decode-storage-object)
(update :bucket d/nilv sto/default-bucket)
(validate-storage-object))
(let [object (->> (read-entry input entry)
(decode-storage-object)
(validate-storage-object))
ext (cmedia/mtype->extension (:content-type object))
path (str "objects/" id ext)

View File

@@ -30,7 +30,7 @@
(defn- get-file-media-object
[pool id]
(db/get pool :file-media-object {:id id} {::db/remove-deleted false}))
(db/get pool :file-media-object {:id id}))
(defn- serve-object-from-s3
[{:keys [::sto/storage] :as cfg} obj]

View File

@@ -309,7 +309,7 @@
(fn [request]
(let [key (yreq/get-header request "x-shared-key")]
(if (= key shared-key)
(handler (assoc request ::http/auth-with-shared-key true))
(handler request)
{::yres/status 403}))))
(fn [_ _]
{::yres/status 403})))

View File

@@ -14,7 +14,6 @@
[app.common.spec :as us]
[app.common.time :as ct]
[app.common.uri :as u]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.db :as db]
[app.http :as-alias http]
@@ -93,11 +92,7 @@
(let [handler-name (:type path-params)
etag (yreq/get-header request "if-none-match")
profile-id (or (::session/profile-id request)
(::actoken/profile-id request)
(if (::http/auth-with-shared-key request)
uuid/zero
nil))
(::actoken/profile-id request))
ip-addr (inet/parse-request request)
data (-> params

View File

@@ -307,8 +307,7 @@
:content-type (:mtype input)})]
(:id sobject))
(catch Throwable cause
(l/wrn :hint "unable to import profile picture"
:uri uri
(l/err :hint "unable to import profile picture"
:cause cause)
nil)))

View File

@@ -79,14 +79,85 @@
;; --- FILE PERMISSIONS
(def ^:private sql:file-permissions
"select fpr.is_owner,
fpr.is_admin,
fpr.can_edit
from file_profile_rel as fpr
inner join file as f on (f.id = fpr.file_id)
where fpr.file_id = ?
and fpr.profile_id = ?
and f.deleted_at is null
union all
select tpr.is_owner,
tpr.is_admin,
tpr.can_edit
from team_profile_rel as tpr
inner join project as p on (p.team_id = tpr.team_id)
inner join file as f on (p.id = f.project_id)
where f.id = ?
and tpr.profile_id = ?
and f.deleted_at is null
union all
select ppr.is_owner,
ppr.is_admin,
ppr.can_edit
from project_profile_rel as ppr
inner join file as f on (f.project_id = ppr.project_id)
where f.id = ?
and ppr.profile_id = ?
and f.deleted_at is null")
(defn get-file-permissions
[conn profile-id file-id]
(when (and profile-id file-id)
(db/exec! conn [sql:file-permissions
file-id profile-id
file-id profile-id
file-id profile-id])))
(defn get-permissions
([conn profile-id file-id]
(let [rows (get-file-permissions conn profile-id file-id)
is-owner (boolean (some :is-owner rows))
is-admin (boolean (some :is-admin rows))
can-edit (boolean (some :can-edit rows))]
(when (seq rows)
{:type :membership
:is-owner is-owner
:is-admin (or is-owner is-admin)
:can-edit (or is-owner is-admin can-edit)
:can-read true
:is-logged (some? profile-id)})))
([conn profile-id file-id share-id]
(let [perms (get-permissions conn profile-id file-id)
ldata (some-> (db/get* conn :share-link {:id share-id :file-id file-id})
(dissoc :flags)
(update :pages db/decode-pgarray #{}))]
;; NOTE: in a future when share-link becomes more powerful and
;; will allow us specify which parts of the app is available, we
;; will probably need to tweak this function in order to expose
;; this flags to the frontend.
(cond
(some? perms) perms
(some? ldata) {:type :share-link
:can-read true
:pages (:pages ldata)
:is-logged (some? profile-id)
:who-comment (:who-comment ldata)
:who-inspect (:who-inspect ldata)}))))
(def has-edit-permissions?
(perms/make-edition-predicate-fn bfc/get-file-permissions))
(perms/make-edition-predicate-fn get-permissions))
(def has-read-permissions?
(perms/make-read-predicate-fn bfc/get-file-permissions))
(perms/make-read-predicate-fn get-permissions))
(def has-comment-permissions?
(perms/make-comment-predicate-fn bfc/get-file-permissions))
(perms/make-comment-predicate-fn get-permissions))
(def check-edition-permissions!
(perms/make-check-fn has-edit-permissions?))
@@ -99,7 +170,7 @@
(defn check-comment-permissions!
[conn profile-id file-id share-id]
(let [perms (bfc/get-file-permissions conn profile-id file-id share-id)
(let [perms (get-permissions conn profile-id file-id share-id)
can-read (has-read-permissions? perms)
can-comment (has-comment-permissions? perms)]
(when-not (or can-read can-comment)
@@ -151,7 +222,7 @@
(defn- get-minimal-file-with-perms
[cfg {:keys [:id ::rpc/profile-id]}]
(let [mfile (get-minimal-file cfg id)
perms (bfc/get-file-permissions cfg profile-id id)]
perms (get-permissions cfg profile-id id)]
(assoc mfile :permissions perms)))
(defn get-file-etag
@@ -177,7 +248,7 @@
;; will be already prefetched and we just reuse them instead
;; of making an additional database queries.
(let [perms (or (:permissions (::cond/object params))
(bfc/get-file-permissions conn profile-id id))]
(get-permissions conn profile-id id))]
(check-read-permissions! perms)
(let [team (teams/get-team conn
@@ -240,7 +311,7 @@
::sm/result schema:file-fragment}
[cfg {:keys [::rpc/profile-id file-id fragment-id share-id]}]
(db/run! cfg (fn [cfg]
(let [perms (bfc/get-file-permissions cfg profile-id file-id share-id)]
(let [perms (get-permissions cfg profile-id file-id share-id)]
(check-read-permissions! perms)
(-> (get-file-fragment cfg file-id fragment-id)
(rph/with-http-cache long-cache-duration))))))
@@ -385,7 +456,8 @@
:code :params-validation
:hint "page-id is required when object-id is provided"))
(let [perms (bfc/get-file-permissions conn profile-id file-id share-id)
(let [perms (get-permissions conn profile-id file-id share-id)
file (bfc/get-file cfg file-id :read-only? true)
proj (db/get conn :project {:id (:project-id file)})
@@ -616,10 +688,11 @@
"Get libraries used by the specified file."
{::doc/added "1.17"
::sm/params schema:get-file-libraries}
[cfg {:keys [::rpc/profile-id file-id]}]
(bfc/check-file-exists cfg file-id)
(check-read-permissions! cfg profile-id file-id)
(bfc/get-file-libraries cfg file-id))
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id]}]
(dm/with-open [conn (db/open pool)]
(check-read-permissions! conn profile-id file-id)
(bfc/get-file-libraries conn file-id)))
;; --- COMMAND QUERY: Files that use this File library
@@ -712,7 +785,8 @@
FROM file AS f
INNER JOIN project AS p ON (p.id = f.project_id)
LEFT JOIN file_thumbnail AS ft on (ft.file_id = f.id
AND ft.revn = f.revn)
AND ft.revn = f.revn
AND ft.deleted_at is null)
WHERE p.team_id = ?
AND (p.deleted_at > ?::timestamptz OR
f.deleted_at > ?::timestamptz)

View File

@@ -199,13 +199,15 @@
[cfg {:keys [::rpc/profile-id file-id strip-frames-with-thumbnails] :as params}]
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
(files/check-read-permissions! conn profile-id file-id)
(let [team (teams/get-team conn
:profile-id profile-id
:file-id file-id)
file (bfc/get-file cfg file-id
:include-deleted? true
:realize? true
:read-only? true)
strip-frames-with-thumbnails
(or (nil? strip-frames-with-thumbnails) ;; if not present, default to true
(true? strip-frames-with-thumbnails))]
@@ -331,16 +333,12 @@
;; --- MUTATION COMMAND: create-file-thumbnail
(defn- create-file-thumbnail
[{:keys [::db/conn ::sto/storage] :as cfg} {:keys [file-id revn props media] :as params}]
(defn- create-file-thumbnail!
[{:keys [::db/conn ::sto/storage]} {:keys [file-id revn props media] :as params}]
(media/validate-media-type! media)
(media/validate-media-size! media)
(let [file (bfc/get-file cfg file-id
:include-deleted? true
:load-data? false)
props (db/tjson (or props {}))
(let [props (db/tjson (or props {}))
path (:path media)
mtype (:mtype media)
hash (sto/calculate-hash path)
@@ -369,7 +367,7 @@
(db/update! conn :file-thumbnail
{:media-id (:id media)
:deleted-at (:deleted-at file)
:deleted-at nil
:updated-at tnow
:props props}
{:file-id file-id
@@ -380,7 +378,6 @@
:revn revn
:created-at tnow
:updated-at tnow
:deleted-at (:deleted-at file)
:props props
:media-id (:id media)}))
@@ -405,8 +402,6 @@
::rtry/when rtry/conflict-exception?
::sm/params schema:create-file-thumbnail}
;; FIXME: do not run the thumbnail upload inside a transaction
[cfg {:keys [::rpc/profile-id file-id] :as params}]
(db/tx-run! cfg (fn [{:keys [::db/conn] :as cfg}]
;; TODO For now we check read permissions instead of write,
@@ -414,6 +409,6 @@
;; review this approach on the future.
(files/check-read-permissions! conn profile-id file-id)
(when-not (db/read-only? conn)
(let [media (create-file-thumbnail cfg params)]
(let [media (create-file-thumbnail! cfg params)]
{:uri (files/resolve-public-uri (:id media))
:id (:id media)})))))

View File

@@ -6,7 +6,6 @@
(ns app.rpc.commands.fonts
(:require
[app.binfile.common :as bfc]
[app.common.data.macros :as dm]
[app.common.exceptions :as ex]
[app.common.schema :as sm]
@@ -67,7 +66,7 @@
(uuid? file-id)
(let [file (db/get-by-id conn :file file-id {:columns [:id :project-id]})
project (db/get-by-id conn :project (:project-id file) {:columns [:id :team-id]})
perms (bfc/get-file-permissions conn profile-id file-id share-id)]
perms (files/get-permissions conn profile-id file-id share-id)]
(files/check-read-permissions! perms)
(db/query conn :team-font-variant
{:team-id (:team-id project)

View File

@@ -13,6 +13,7 @@
[app.config :as cf]
[app.db :as db]
[app.rpc :as-alias rpc]
[app.rpc.commands.files :as files]
[app.rpc.commands.teams :as teams]
[app.rpc.cond :as-alias cond]
[app.rpc.doc :as-alias doc]
@@ -120,7 +121,7 @@
[system {:keys [::rpc/profile-id file-id share-id] :as params}]
(db/run! system
(fn [{:keys [::db/conn] :as system}]
(let [perms (bfc/get-file-permissions conn profile-id file-id share-id)
(let [perms (files/get-permissions conn profile-id file-id share-id)
params (-> params
(assoc ::perms perms)
(assoc :profile-id profile-id))]

View File

@@ -104,29 +104,28 @@
(def ^:private schema:limit
[:and
[:map
[::name :keyword]
[::name :any]
[::strategy schema:strategy]
[::key :string]
[::opts :string]
[::capacity {:optional true} ::sm/int]
[::rate {:optional true} ::sm/int]
[::interval {:optional true} ::ct/duration]
[::params {:optional true} [::sm/vec :any]]
[::permits {:optional true} ::sm/int]
[::unit {:optional true} [:enum :days :hours :minutes :seconds :weeks]]]
[:fn (fn [attrs]
(let [contains-fn (partial contains? attrs)]
(or (every? contains-fn [::capacity ::rate ::interval])
(every? contains-fn [::permits ::unit]))))]])
[::opts :string]]
[:or
[:map
[::capacity ::sm/int]
[::rate ::sm/int]
[::internal ::ct/duration]
[::params [::sm/vec :any]]]
[:map
[::nreq ::sm/int]
[::unit [:enum :days :hours :minutes :seconds :weeks]]]]])
(def ^:private schema:limits
[:map-of :keyword [::sm/vec schema:limit]])
(def ^:private valid-limit-tuple?
(sm/validator schema:limit-tuple))
(sm/lazy-validator schema:limit-tuple))
(def ^:private valid-rlimit-instance?
(sm/validator ::rpc/rlimit))
(sm/lazy-validator ::rpc/rlimit))
(defmethod parse-limit :window
[[name strategy opts :as vlimit]]
@@ -135,16 +134,16 @@
(merge
{::name name
::strategy strategy}
(if-let [[_ permits unit] (re-find window-opts-re opts)]
(let [permits (parse-long permits)]
{::permits permits
(if-let [[_ nreq unit] (re-find window-opts-re opts)]
(let [nreq (parse-long nreq)]
{::nreq nreq
::unit (case unit
"d" :days
"h" :hours
"m" :minutes
"s" :seconds
"w" :weeks)
::key (str "penpot.rlimit." (cf/get :tenant) ".window." (d/name name))
::key (str "ratelimit.window." (d/name name))
::opts opts})
(ex/raise :type :validation
:code :invalid-window-limit-opts
@@ -165,15 +164,15 @@
::interval interval
::opts opts
::params [(->seconds interval) rate capacity]
::key (str "penpot.rlimit." (cf/get :tenant) ".bucket." (d/name name))})
::key (str "ratelimit.bucket." (d/name name))})
(ex/raise :type :validation
:code :invalid-bucket-limit-opts
:hint (str/ffmt "looks like '%' does not have a valid format" opts))))
(defmethod process-limit :bucket
[rconn profile-id now {:keys [::key ::params ::service ::capacity ::interval ::rate] :as limit}]
[rconn user-id now {:keys [::key ::params ::service ::capacity ::interval ::rate] :as limit}]
(let [script (-> bucket-rate-limit-script
(assoc ::rscript/keys [(str key "." service "." profile-id)])
(assoc ::rscript/keys [(str key "." service "." user-id)])
(assoc ::rscript/vals (conj params (->seconds now))))
result (rds/eval rconn script)
allowed? (boolean (nth result 0))
@@ -193,18 +192,18 @@
(assoc ::lresult/remaining remaining))))
(defmethod process-limit :window
[rconn profile-id now {:keys [::permits ::unit ::key ::service] :as limit}]
[rconn user-id now {:keys [::nreq ::unit ::key ::service] :as limit}]
(let [ts (ct/truncate now unit)
ttl (ct/diff now (ct/plus ts {unit 1}))
script (-> window-rate-limit-script
(assoc ::rscript/keys [(str key "." service "." profile-id "." (ct/format-inst ts))])
(assoc ::rscript/vals [permits (->seconds ttl)]))
(assoc ::rscript/keys [(str key "." service "." user-id "." (ct/format-inst ts))])
(assoc ::rscript/vals [nreq (->seconds ttl)]))
result (rds/eval rconn script)
allowed? (boolean (nth result 0))
remaining (nth result 1)]
(l/trace :hint "limit processed"
:service service
:name (name (::name limit))
:limit (name (::name limit))
:strategy (name (::strategy limit))
:opts (::opts limit)
:allowed allowed?
@@ -215,8 +214,8 @@
(assoc ::lresult/reset (ct/plus ts {unit 1})))))
(defn- process-limits
[rconn profile-id limits now]
(let [results (into [] (map (partial process-limit rconn profile-id now)) limits)
[rconn user-id limits now]
(let [results (into [] (map (partial process-limit rconn user-id now)) limits)
remaining (->> results
(d/index-by ::name ::lresult/remaining)
(uri/map->query-string))
@@ -228,7 +227,7 @@
(when rejected
(l/warn :hint "rejected rate limit"
:profile-id (str profile-id)
:user-id (str user-id)
:limit-service (-> rejected ::service name)
:limit-name (-> rejected ::name name)
:limit-strategy (-> rejected ::strategy name)))
@@ -372,9 +371,12 @@
(defn- on-refresh-error
[_ cause]
(when-not (instance? java.util.concurrent.RejectedExecutionException cause)
(l/warn :hint "unexpected exception on loading config"
:cause cause
::l/sync? true)))
(if-let [explain (-> cause ex-data ex/explain)]
(l/warn ::l/raw (str "unable to refresh config, invalid format:\n" explain)
::l/sync? true)
(l/warn :hint "unexpected exception on loading config"
:cause cause
::l/sync? true))))
(defn- get-config-path
[]

View File

@@ -25,9 +25,9 @@ local allowed = filled >= requested
local newTokens = filled
if allowed then
newTokens = filled - requested
redis.call("hset", tokensKey, "tokens", newTokens, "timestamp", timestamp)
end
redis.call("hset", tokensKey, "tokens", newTokens, "timestamp", timestamp)
redis.call("expire", tokensKey, ttl)
return { allowed, newTokens }

View File

@@ -35,9 +35,6 @@
:assets-s3 :s3
nil)))
(def default-bucket
"file-media-object")
(def valid-buckets
#{"file-media-object"
"team-font-variant"

View File

@@ -25,7 +25,7 @@
[app.common.time :as ct]
[app.config :as cf]
[app.db :as db]
[app.storage :as sto]
[app.storage :as-alias sto]
[app.storage.impl :as impl]
[integrant.core :as ig]))
@@ -130,7 +130,7 @@
[{:keys [metadata]}]
(or (some-> metadata :bucket)
(some-> metadata :reference d/name)
sto/default-bucket))
"file-media-object"))
(defn- process-objects!
[conn has-refs? bucket objects]

View File

@@ -7,18 +7,10 @@
(ns app.util.template
(:require
[app.common.exceptions :as ex]
[cuerdas.core :as str]
[selmer.filters :as sf]
[selmer.parser :as sp]))
;; (sp/cache-off!)
(sf/add-filter! :abbreviate
(fn [s n]
(let [n (parse-long n)]
(str/abbreviate s n))))
(defn render
[path context]
(try

View File

@@ -137,34 +137,33 @@ RETURNING task.id, task.queue")
::wait)))
(run-batch []
(try
(let [rconn (rds/connect cfg)]
(try
(-> cfg
(assoc ::rds/conn rconn)
(db/tx-run! run-batch'))
(finally
(.close ^AutoCloseable rconn))))
(let [rconn (rds/connect cfg)]
(try
(-> cfg
(assoc ::rds/conn rconn)
(db/tx-run! run-batch'))
(catch InterruptedException cause
(throw cause))
(catch InterruptedException cause
(throw cause))
(catch Exception cause
(cond
(rds/exception? cause)
(do
(l/wrn :hint "redis exception (will retry in an instant)" :cause cause)
(px/sleep timeout))
(catch Exception cause
(cond
(rds/exception? cause)
(do
(l/wrn :hint "redis exception (will retry in an instant)" :cause cause)
(px/sleep timeout))
(db/sql-exception? cause)
(do
(l/wrn :hint "database exception (will retry in an instant)" :cause cause)
(px/sleep timeout))
(db/sql-exception? cause)
(do
(l/wrn :hint "database exception (will retry in an instant)" :cause cause)
(px/sleep timeout))
:else
(do
(l/err :hint "unhandled exception (will retry in an instant)" :cause cause)
(px/sleep timeout))))
:else
(do
(l/err :hint "unhandled exception (will retry in an instant)" :cause cause)
(px/sleep timeout))))))
(finally
(.close ^AutoCloseable rconn)))))
(dispatcher []
(l/inf :hint "started")
@@ -177,7 +176,7 @@ RETURNING task.id, task.queue")
(catch InterruptedException _
(l/trc :hint "interrupted"))
(catch Throwable cause
(l/err :hint "unexpected exception" :cause cause))
(l/err :hint " unexpected exception" :cause cause))
(finally
(l/inf :hint "terminated"))))]

View File

@@ -29,7 +29,8 @@
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
integrant/integrant {:mvn/version "1.0.0"}
funcool/cuerdas {:mvn/version "2026.415"}
funcool/tubax {:mvn/version "2021.05.20-0"}
funcool/cuerdas {:mvn/version "2025.06.16-414"}
funcool/promesa
{:git/sha "46048fc0d4bf5466a2a4121f5d52aefa6337f2e8"
:git/url "https://github.com/funcool/promesa"}

View File

@@ -56,6 +56,7 @@
"text-editor/v2-html-paste"
"text-editor/v2"
"render-wasm/v1"
"graph-wasm/v1"
"variants/v1"})
;; A set of features enabled by default
@@ -79,7 +80,8 @@
"text-editor/v2-html-paste"
"text-editor/v2"
"tokens/numeric-input"
"render-wasm/v1"})
"render-wasm/v1"
"graph-wasm/v1"})
;; Features that are mainly backend only or there are a proper
;; fallback when frontend reports no support for it
@@ -128,6 +130,7 @@
:feature-text-editor-v2 "text-editor/v2"
:feature-text-editor-v2-html-paste "text-editor/v2-html-paste"
:feature-render-wasm "render-wasm/v1"
:feature-graph-wasm "graph-wasm/v1"
:feature-variants "variants/v1"
:feature-token-input "tokens/numeric-input"
nil))

View File

@@ -169,7 +169,6 @@
:enable-component-thumbnails
:enable-render-wasm-dpr
:enable-token-color
:enable-token-shadow
:enable-inspect-styles
:enable-feature-fdata-objects-map])

View File

@@ -340,7 +340,7 @@
(dfn-diff t2 t1)))
#?(:cljs
(defn set-default-locale
(defn set-default-locale!
[locale]
(when-let [locale (unchecked-get locales locale)]
(dfn-set-default-options #js {:locale locale}))))

View File

@@ -362,24 +362,24 @@
component (ctkl/get-component component-file (:component-id top-instance) true)
remote-shape (get-ref-shape component-file component shape)
component-container (get-component-container component-file component)
[remote-shape component-container component-file]
[remote-shape component-container]
(if (some? remote-shape)
[remote-shape component-container component-file]
[remote-shape component-container]
;; If not found, try the case of this being a fostered or swapped children
(let [head-instance (ctn/get-head-shape (:objects container) shape)
component-file (get-in libraries [(:component-file head-instance) :data])
head-component (ctkl/get-component component-file (:component-id head-instance) true)
remote-shape' (get-ref-shape component-file head-component shape)
component-container' (get-component-container component-file head-component)]
[remote-shape' component-container' component-file]))]
(let [head-instance (ctn/get-head-shape (:objects container) shape)
component-file (get-in libraries [(:component-file head-instance) :data])
head-component (ctkl/get-component component-file (:component-id head-instance) true)
remote-shape' (get-ref-shape component-file head-component shape)
component-container (get-component-container component-file component)]
[remote-shape' component-container]))]
(if (nil? remote-shape)
nil
(if (nil? (:shape-ref remote-shape))
(cond-> remote-shape
(and remote-shape with-context?)
(with-meta {:file {:id (:id component-file)
:data component-file}
(with-meta {:file {:id (:id file-data)
:data file-data}
:container component-container}))
(find-remote-shape component-container libraries remote-shape :with-context? with-context?)))))

View File

@@ -59,7 +59,6 @@
:dimensions "dimension"
:font-family "fontFamilies"
:font-size "fontSizes"
:font-weight "fontWeights"
:letter-spacing "letterSpacing"
:number "number"
:opacity "opacity"
@@ -71,6 +70,7 @@
:stroke-width "borderWidth"
:text-case "textCase"
:text-decoration "textDecoration"
:font-weight "fontWeights"
:typography "typography"})
(def dtcg-token-type->token-type

View File

@@ -1410,8 +1410,8 @@ Will return a value that matches this schema:
;; NOTE: we can't assign statically at eval time the value of a
;; function that is declared but not defined; so we need to pass
;; an anonymous function and delegate the resolution to runtime
{:encode/json #(some-> % export-dtcg-json)
:decode/json #(some-> % read-multi-set-dtcg)
{:encode/json #(export-dtcg-json %)
:decode/json #(read-multi-set-dtcg %)
;; FIXME: add better, more reallistic generator
:gen/gen (->> (sg/small-int)
(sg/fmap (fn [_]
@@ -1545,7 +1545,7 @@ Will return a value that matches this schema:
(and (not (contains? decoded-json "$metadata"))
(not (contains? decoded-json "$themes"))))
(defn convert-dtcg-font-family
(defn- convert-dtcg-font-family
"Convert font-family token value from DTCG format to internal format.
- If value is a string, split it into a collection of font families
- If value is already an array, keep it as is
@@ -1556,7 +1556,7 @@ Will return a value that matches this schema:
(sequential? value) value
:else value))
(defn convert-dtcg-typography-composite
(defn- convert-dtcg-typography-composite
"Convert typography token value keys from DTCG format to internal format."
[value]
(if (map? value)
@@ -1568,7 +1568,7 @@ Will return a value that matches this schema:
;; Reference value
value))
(defn convert-dtcg-shadow-composite
(defn- convert-dtcg-shadow-composite
"Convert shadow token value from DTCG format to internal format."
[value]
(let [process-shadow (fn [shadow]

View File

@@ -10,7 +10,3 @@ localhost:3449 {
http://localhost:3450 {
reverse_proxy localhost:4449
}
http://penpot-devenv-main:3450 {
reverse_proxy localhost:4449
}

View File

@@ -4,7 +4,7 @@
"license": "MPL-2.0",
"author": "Kaleidos INC",
"private": true,
"packageManager": "yarn@4.12.0+sha512.f45ab632439a67f8bc759bf32ead036a1f413287b9042726b7cc4818b7b49e14e9423ba49b18f9e06ea4941c1ad062385b1d8760a8d5091a1a31e5f6219afca8",
"packageManager": "yarn@4.10.3+sha512.c38cafb5c7bb273f3926d04e55e1d8c9dfa7d9c3ea1f36a4868fa028b9e5f72298f0b7f401ad5eb921749eb012eb1c3bb74bf7503df3ee43fd600d14a018266f",
"repository": {
"type": "git",
"url": "https://github.com/penpot/penpot"
@@ -16,9 +16,9 @@
"date-fns": "^4.1.0",
"generic-pool": "^3.9.0",
"inflation": "^2.1.0",
"ioredis": "^5.8.2",
"playwright": "^1.57.0",
"raw-body": "^3.0.2",
"ioredis": "^5.8.1",
"playwright": "^1.55.1",
"raw-body": "^3.0.1",
"source-map-support": "^0.5.21",
"svgo": "penpot/svgo#v3.1",
"undici": "^7.16.0",

View File

@@ -243,7 +243,7 @@ __metadata:
languageName: node
linkType: hard
"bytes@npm:~3.1.2":
"bytes@npm:3.1.2":
version: 3.1.2
resolution: "bytes@npm:3.1.2"
checksum: 10c0/76d1c43cbd602794ad8ad2ae94095cddeb1de78c5dddaa7005c51af10b0176c69971a6d88e805a90c2b6550d76636e43c40d8427a808b8645ede885de4a0358e
@@ -442,7 +442,7 @@ __metadata:
languageName: node
linkType: hard
"depd@npm:~2.0.0":
"depd@npm:2.0.0, depd@npm:~2.0.0":
version: 2.0.0
resolution: "depd@npm:2.0.0"
checksum: 10c0/58bd06ec20e19529b06f7ad07ddab60e504d9e0faca4bd23079fac2d279c3594334d736508dc350e06e510aba5e22e4594483b3a6562ce7c17dd797f4cc4ad2c
@@ -577,9 +577,9 @@ __metadata:
date-fns: "npm:^4.1.0"
generic-pool: "npm:^3.9.0"
inflation: "npm:^2.1.0"
ioredis: "npm:^5.8.2"
playwright: "npm:^1.57.0"
raw-body: "npm:^3.0.2"
ioredis: "npm:^5.8.1"
playwright: "npm:^1.55.1"
raw-body: "npm:^3.0.1"
source-map-support: "npm:^0.5.21"
svgo: "penpot/svgo#v3.1"
undici: "npm:^7.16.0"
@@ -683,16 +683,16 @@ __metadata:
languageName: node
linkType: hard
"http-errors@npm:~2.0.1":
version: 2.0.1
resolution: "http-errors@npm:2.0.1"
"http-errors@npm:2.0.0":
version: 2.0.0
resolution: "http-errors@npm:2.0.0"
dependencies:
depd: "npm:~2.0.0"
inherits: "npm:~2.0.4"
setprototypeof: "npm:~1.2.0"
statuses: "npm:~2.0.2"
toidentifier: "npm:~1.0.1"
checksum: 10c0/fb38906cef4f5c83952d97661fe14dc156cb59fe54812a42cd448fa57b5c5dfcb38a40a916957737bd6b87aab257c0648d63eb5b6a9ca9f548e105b6072712d4
depd: "npm:2.0.0"
inherits: "npm:2.0.4"
setprototypeof: "npm:1.2.0"
statuses: "npm:2.0.1"
toidentifier: "npm:1.0.1"
checksum: 10c0/fc6f2715fe188d091274b5ffc8b3657bd85c63e969daa68ccb77afb05b071a4b62841acb7a21e417b5539014dff2ebf9550f0b14a9ff126f2734a7c1387f8e19
languageName: node
linkType: hard
@@ -716,6 +716,15 @@ __metadata:
languageName: node
linkType: hard
"iconv-lite@npm:0.7.0":
version: 0.7.0
resolution: "iconv-lite@npm:0.7.0"
dependencies:
safer-buffer: "npm:>= 2.1.2 < 3.0.0"
checksum: 10c0/2382400469071c55b6746c531eed5fa4d033e5db6690b7331fb2a5f59a30d7a9782932e92253db26df33c1cf46fa200a3fbe524a2a7c62037c762283f188ec2f
languageName: node
linkType: hard
"iconv-lite@npm:^0.6.2":
version: 0.6.3
resolution: "iconv-lite@npm:0.6.3"
@@ -725,15 +734,6 @@ __metadata:
languageName: node
linkType: hard
"iconv-lite@npm:~0.7.0":
version: 0.7.0
resolution: "iconv-lite@npm:0.7.0"
dependencies:
safer-buffer: "npm:>= 2.1.2 < 3.0.0"
checksum: 10c0/2382400469071c55b6746c531eed5fa4d033e5db6690b7331fb2a5f59a30d7a9782932e92253db26df33c1cf46fa200a3fbe524a2a7c62037c762283f188ec2f
languageName: node
linkType: hard
"ieee754@npm:^1.2.1":
version: 1.2.1
resolution: "ieee754@npm:1.2.1"
@@ -755,16 +755,16 @@ __metadata:
languageName: node
linkType: hard
"inherits@npm:~2.0.3, inherits@npm:~2.0.4":
"inherits@npm:2.0.4, inherits@npm:~2.0.3":
version: 2.0.4
resolution: "inherits@npm:2.0.4"
checksum: 10c0/4e531f648b29039fb7426fb94075e6545faa1eb9fe83c29f0b6d9e7263aceb4289d2d4557db0d428188eeb449cc7c5e77b0a0b2c4e248ff2a65933a0dee49ef2
languageName: node
linkType: hard
"ioredis@npm:^5.8.2":
version: 5.8.2
resolution: "ioredis@npm:5.8.2"
"ioredis@npm:^5.8.1":
version: 5.8.1
resolution: "ioredis@npm:5.8.1"
dependencies:
"@ioredis/commands": "npm:1.4.0"
cluster-key-slot: "npm:^1.1.0"
@@ -775,7 +775,7 @@ __metadata:
redis-errors: "npm:^1.2.0"
redis-parser: "npm:^3.0.0"
standard-as-callback: "npm:^2.1.0"
checksum: 10c0/305e385f811d49908899e32c2de69616cd059f909afd9e0a53e54f596b1a5835ee3449bfc6a3c49afbc5a2fd27990059e316cc78f449c94024957bd34c826d88
checksum: 10c0/4ed66444017150da027bce940a24bf726994691e2a7b3aa11d52f8aeb37f258068cc171af4d9c61247acafc28eb086fa8a7c79420b8e8d2907d2f74f39584465
languageName: node
linkType: hard
@@ -1106,27 +1106,27 @@ __metadata:
languageName: node
linkType: hard
"playwright-core@npm:1.57.0":
version: 1.57.0
resolution: "playwright-core@npm:1.57.0"
"playwright-core@npm:1.55.1":
version: 1.55.1
resolution: "playwright-core@npm:1.55.1"
bin:
playwright-core: cli.js
checksum: 10c0/798e35d83bf48419a8c73de20bb94d68be5dde68de23f95d80a0ebe401e3b83e29e3e84aea7894d67fa6c79d2d3d40cc5bcde3e166f657ce50987aaa2421b6a9
checksum: 10c0/39837a8c1232ec27486eac8c3fcacc0b090acc64310f7f9004b06715370fc426f944e3610fe8c29f17cd3d68280ed72c75f660c02aa5b5cf0eb34bab0031308f
languageName: node
linkType: hard
"playwright@npm:^1.57.0":
version: 1.57.0
resolution: "playwright@npm:1.57.0"
"playwright@npm:^1.55.1":
version: 1.55.1
resolution: "playwright@npm:1.55.1"
dependencies:
fsevents: "npm:2.3.2"
playwright-core: "npm:1.57.0"
playwright-core: "npm:1.55.1"
dependenciesMeta:
fsevents:
optional: true
bin:
playwright: cli.js
checksum: 10c0/ab03c99a67b835bdea9059f516ad3b6e42c21025f9adaa161a4ef6bc7ca716dcba476d287140bb240d06126eb23f889a8933b8f5f1f1a56b80659d92d1358899
checksum: 10c0/b84a97b0d764403df512f5bbb10c7343974e151a28202cc06f90883a13e8a45f4491a0597f0ae5fb03a026746cbc0d200f0f32195bfaa381aee5ca5770626771
languageName: node
linkType: hard
@@ -1161,15 +1161,15 @@ __metadata:
languageName: node
linkType: hard
"raw-body@npm:^3.0.2":
version: 3.0.2
resolution: "raw-body@npm:3.0.2"
"raw-body@npm:^3.0.1":
version: 3.0.1
resolution: "raw-body@npm:3.0.1"
dependencies:
bytes: "npm:~3.1.2"
http-errors: "npm:~2.0.1"
iconv-lite: "npm:~0.7.0"
unpipe: "npm:~1.0.0"
checksum: 10c0/d266678d08e1e7abea62c0ce5864344e980fa81c64f6b481e9842c5beaed2cdcf975f658a3ccd67ad35fc919c1f6664ccc106067801850286a6cbe101de89f29
bytes: "npm:3.1.2"
http-errors: "npm:2.0.0"
iconv-lite: "npm:0.7.0"
unpipe: "npm:1.0.0"
checksum: 10c0/892f4fbd21ecab7e2fed0f045f7af9e16df7e8050879639d4e482784a2f4640aaaa33d916a0e98013f23acb82e09c2e3c57f84ab97104449f728d22f65a7d79a
languageName: node
linkType: hard
@@ -1270,7 +1270,7 @@ __metadata:
languageName: node
linkType: hard
"setprototypeof@npm:~1.2.0":
"setprototypeof@npm:1.2.0":
version: 1.2.0
resolution: "setprototypeof@npm:1.2.0"
checksum: 10c0/68733173026766fa0d9ecaeb07f0483f4c2dc70ca376b3b7c40b7cda909f94b0918f6c5ad5ce27a9160bdfb475efaa9d5e705a11d8eaae18f9835d20976028bc
@@ -1368,10 +1368,10 @@ __metadata:
languageName: node
linkType: hard
"statuses@npm:~2.0.2":
version: 2.0.2
resolution: "statuses@npm:2.0.2"
checksum: 10c0/a9947d98ad60d01f6b26727570f3bcceb6c8fa789da64fe6889908fe2e294d57503b14bf2b5af7605c2d36647259e856635cd4c49eab41667658ec9d0080ec3f
"statuses@npm:2.0.1":
version: 2.0.1
resolution: "statuses@npm:2.0.1"
checksum: 10c0/34378b207a1620a24804ce8b5d230fea0c279f00b18a7209646d5d47e419d1cc23e7cbf33a25a1e51ac38973dc2ac2e1e9c647a8e481ef365f77668d72becfd0
languageName: node
linkType: hard
@@ -1500,7 +1500,7 @@ __metadata:
languageName: node
linkType: hard
"toidentifier@npm:~1.0.1":
"toidentifier@npm:1.0.1":
version: 1.0.1
resolution: "toidentifier@npm:1.0.1"
checksum: 10c0/93937279934bd66cc3270016dd8d0afec14fb7c94a05c72dc57321f8bd1fa97e5bea6d1f7c89e728d077ca31ea125b78320a616a6c6cd0e6b9cb94cb864381c1
@@ -1539,7 +1539,7 @@ __metadata:
languageName: node
linkType: hard
"unpipe@npm:~1.0.0":
"unpipe@npm:1.0.0":
version: 1.0.0
resolution: "unpipe@npm:1.0.0"
checksum: 10c0/193400255bd48968e5c5383730344fbb4fa114cdedfab26e329e50dd2d81b134244bb8a72c6ac1b10ab0281a58b363d06405632c9d49ca9dfd5e90cbd7d0f32c

View File

@@ -8,11 +8,6 @@
metosin/reitit-core {:mvn/version "0.9.1"}
funcool/okulary {:mvn/version "2022.04.11-16"}
funcool/tubax
{:git/tag "v2025.11.28"
:git/sha "2d9a986"
:git/url "https://github.com/funcool/tubax.git"}
funcool/potok2
{:git/tag "v2.2"
:git/sha "0f7e15a"
@@ -50,7 +45,7 @@
{thheller/shadow-cljs {:mvn/version "3.2.2"}
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
org.clojure/tools.namespace {:mvn/version "RELEASE"}
criterium/criterium {:mvn/version "0.4.6"}
criterium/criterium {:mvn/version "RELEASE"}
cider/cider-nrepl {:mvn/version "0.57.0"}}}
:shadow-cljs

View File

@@ -53,74 +53,83 @@
"watch:storybook:assets": "node ./scripts/watch-storybook.js"
},
"devDependencies": {
"@penpot/draft-js": "portal:./packages/draft-js",
"@penpot/mousetrap": "portal:./packages/mousetrap",
"@penpot/plugins-runtime": "1.3.2",
"@penpot/svgo": "penpot/svgo#v3.2",
"@penpot/text-editor": "portal:./text-editor",
"@playwright/test": "1.52.0",
"@storybook/addon-docs": "10.0.4",
"@storybook/addon-themes": "10.0.4",
"@storybook/addon-vitest": "10.0.4",
"@storybook/react-vite": "10.0.4",
"@tokens-studio/sd-transforms": "1.2.11",
"@types/node": "^22.15.21",
"@vitest/browser": "3.2.4",
"@vitest/coverage-v8": "3.2.4",
"@zip.js/zip.js": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch",
"autoprefixer": "^10.4.21",
"compression": "^1.8.1",
"concurrently": "^9.2.1",
"date-fns": "^4.1.0",
"esbuild": "^0.25.9",
"eventsource-parser": "^3.0.6",
"express": "^5.1.0",
"fancy-log": "^2.0.0",
"getopts": "^2.3.0",
"gettext-parser": "^8.0.0",
"highlight.js": "^11.10.0",
"js-beautify": "^1.15.4",
"gulp-concat": "^2.6.1",
"gulp-gzip": "^1.4.2",
"gulp-mustache": "^5.0.0",
"gulp-postcss": "^10.0.0",
"gulp-rename": "^2.0.0",
"gulp-sourcemaps": "^3.0.0",
"gulp-svg-sprite": "^2.0.3",
"jsdom": "^27.0.0",
"lodash": "^4.17.21",
"lodash.debounce": "^4.0.8",
"map-stream": "0.0.7",
"marked": "^15.0.12",
"mkdirp": "^3.0.1",
"mustache": "^4.2.0",
"nodemon": "^3.1.10",
"npm-run-all": "^4.1.5",
"opentype.js": "^1.3.4",
"p-limit": "^6.2.0",
"playwright": "1.56.1",
"postcss": "^8.5.4",
"postcss-clean": "^1.2.2",
"postcss-modules": "^6.0.1",
"prettier": "3.5.3",
"pretty-time": "^1.1.0",
"prop-types": "^15.8.1",
"rimraf": "^6.0.1",
"sass": "^1.89.0",
"sass-embedded": "^1.89.0",
"storybook": "10.0.4",
"svg-sprite": "^2.0.4",
"typescript": "^5.9.2",
"vite": "^6.3.5",
"vitest": "^3.2.0",
"wasm-pack": "^0.13.1",
"watcher": "^2.3.1",
"workerpool": "^9.3.2"
},
"dependencies": {
"@penpot/draft-js": "portal:./vendor/draft-js",
"@penpot/hljs": "portal:./vendor/hljs",
"@penpot/mousetrap": "portal:./vendor/mousetrap",
"@penpot/plugins-runtime": "1.3.2",
"@penpot/svgo": "penpot/svgo#v3.2",
"@penpot/text-editor": "portal:./text-editor",
"@tokens-studio/sd-transforms": "1.2.11",
"@zip.js/zip.js": "patch:@zip.js/zip.js@npm%3A2.7.60#~/.yarn/patches/@zip.js-zip.js-npm-2.7.60-b6b814410b.patch",
"compression": "^1.8.1",
"date-fns": "^4.1.0",
"eventsource-parser": "^3.0.6",
"js-beautify": "^1.15.4",
"lodash": "^4.17.21",
"lodash.debounce": "^4.0.8",
"opentype.js": "^1.3.4",
"postcss-modules": "^6.0.1",
"randomcolor": "^0.6.2",
"react": "19.1.1",
"react-dom": "19.1.1",
"react-error-boundary": "^6.0.0",
"react-virtualized": "^9.22.6",
"rimraf": "^6.0.1",
"rxjs": "8.0.0-alpha.14",
"sass": "^1.89.0",
"sass-embedded": "^1.89.0",
"sax": "^1.4.1",
"source-map-support": "^0.5.21",
"storybook": "10.0.4",
"style-dictionary": "5.0.0-rc.1",
"svg-sprite": "^2.0.4",
"tdigest": "^0.1.2",
"tinycolor2": "^1.6.0",
"typescript": "^5.9.2",
"ua-parser-js": "2.0.5",
"vite": "^6.3.5",
"vitest": "^3.2.0",
"wasm-pack": "^0.13.1",
"watcher": "^2.3.1",
"workerpool": "^9.3.2",
"xregexp": "^5.1.2"
}
}

View File

@@ -1 +0,0 @@
[]

View File

@@ -1,47 +0,0 @@
[
{
"~:id": "~uc7ce0794-0992-8105-8004-38e630f41234",
"~:revn": 1,
"~:vern": 1,
"~:project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
"~:created-at": "~m1705307400000",
"~:modified-at": "~m1732111500000",
"~:deleted-at": "~m1732111500000",
"~:name": "Deleted Design File 1",
"~:is-shared": false,
"~:will-be-deleted-at": "~m1732716300000",
"~:thumbnail-id": null,
"~:row-num": 1,
"~:team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d"
},
{
"~:id": "~uc7ce0794-0992-8105-8004-38e630f41235",
"~:revn": 2,
"~:vern": 2,
"~:project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
"~:created-at": "~m1704875700000",
"~:modified-at": "~m1732025400000",
"~:deleted-at": "~m1732025400000",
"~:name": "Deleted Design File 2",
"~:is-shared": true,
"~:will-be-deleted-at": "~m1732630200000",
"~:thumbnail-id": null,
"~:row-num": 2,
"~:team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d"
},
{
"~:id": "~uc7ce0794-0992-8105-8004-38e630f41236",
"~:revn": 3,
"~:vern": 3,
"~:project-id": "~uc7ce0794-0992-8105-8004-38e630f7920c",
"~:created-at": "~m1706792400000",
"~:modified-at": "~m1731939600000",
"~:deleted-at": "~m1731939600000",
"~:name": "Old Project Design",
"~:is-shared": false,
"~:will-be-deleted-at": "~m1732544400000",
"~:thumbnail-id": null,
"~:row-num": 3,
"~:team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d"
}
]

View File

@@ -106,13 +106,6 @@ export class DashboardPage extends BaseWebSocketPage {
);
}
async setupDeletedFiles() {
await this.mockRPC(
"get-team-deleted-files?team-id=*",
"dashboard/get-team-deleted-files.json",
);
}
async setupDrafts() {
await this.mockRPC(
"get-project-files?project-id=*",
@@ -167,10 +160,6 @@ export class DashboardPage extends BaseWebSocketPage {
});
await this.mockRPC("search-files", "dashboard/search-files.json");
await this.mockRPC("get-teams", "logged-in-user/get-teams-complete.json");
await this.mockRPC(
"get-team-deleted-files?team-id=*",
"dashboard/get-team-deleted-files.json",
);
}
async setupAccessTokensEmpty() {
@@ -300,13 +289,6 @@ export class DashboardPage extends BaseWebSocketPage {
await expect(this.mainHeading).toHaveText("Libraries");
}
async goToDeleted() {
await this.page.goto(
`#/dashboard/deleted?team-id=${DashboardPage.anyTeamId}`,
);
await expect(this.mainHeading).toHaveText("Projects");
}
async openProfileMenu() {
await this.userAccount.click();
}

View File

@@ -1,31 +0,0 @@
import { test, expect } from "@playwright/test";
import DashboardPage from "../pages/DashboardPage";
test.beforeEach(async ({ page }) => {
await DashboardPage.init(page);
await DashboardPage.mockRPC(
page,
"get-profile",
"logged-in-user/get-profile-logged-in-no-onboarding.json",
);
});
test.describe("Dashboard Deleted Page", () => {
test("User can navigate to deleted page", async ({ page }) => {
const dashboardPage = new DashboardPage(page);
// Setup mock for deleted files API
await dashboardPage.setupDeletedFiles();
// Navigate directly to deleted page
await dashboardPage.goToDeleted();
// Check for the restore all and clear trash buttons
await expect(
page.getByRole("button", { name: "Restore All" }),
).toBeVisible();
await expect(
page.getByRole("button", { name: "Clear trash" }),
).toBeVisible();
});
});

View File

@@ -24,8 +24,6 @@
<link rel="icon" href="images/favicon.png" />
<script type="importmap">{{& manifest.importmap }}</script>
<script type="module">
globalThis.penpotVersion = "{{& version}}";
globalThis.penpotBuildDate = "{{& build_date}}";
@@ -35,6 +33,7 @@
{{# manifest}}
<script src="{{& config}}"></script>
<script src="{{& polyfills}}"></script>
<script type="importmap">{{& importmap }}</script>
{{/manifest}}
<!--cookie-consent-->
@@ -50,9 +49,7 @@
<script type="module" src="{{& libs}}"></script>
<script type="module">
import { init } from "{{& app_main}}";
import defaultTranslations from "{{& default_translations}}";
init({defaultTranslations});
init();
</script>
{{/manifest}}
</body>

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8" />
</head>
<body>
<script type="module">
import initWasmModule from '/js/graph-wasm.js';
let Module = null;
function init(moduleInstance) {
Module = moduleInstance;
}
console.log("Loading module");
initWasmModule().then(Module => {
init(Module);
Module._hello();
});
</script>
</body>
</html>

View File

@@ -187,7 +187,7 @@ async function readManifestFile(resource) {
return JSON.parse(content);
}
async function generateManifest() {
async function readShadowManifest() {
const index = {
app_main: "./js/main.js",
render_main: "./js/render.js",
@@ -197,7 +197,6 @@ async function generateManifest() {
polyfills: "./js/polyfills.js?version=" + CURRENT_VERSION,
libs: "./js/libs.js?version=" + CURRENT_VERSION,
worker_main: "./js/worker/main.js?version=" + CURRENT_VERSION,
default_translations: "./js/translation.en.js?version=" + CURRENT_VERSION,
importmap: JSON.stringify({
"imports": {
@@ -277,7 +276,6 @@ export async function compileTranslations() {
"id",
"ru",
"tr",
"hi",
"zh_CN",
"zh_Hant",
"hr",
@@ -393,7 +391,7 @@ async function generateTemplates() {
const isDebug = process.env.NODE_ENV !== "production";
await fs.mkdir("./resources/public/", { recursive: true });
const manifest = await generateManifest();
const manifest = await readShadowManifest();
let content;
const iconsSprite = await fs.readFile(

View File

@@ -92,7 +92,7 @@
{:main
{:entries [app.worker]
:web-worker true
:prepend-js "importScripts('./render.js');"
:prepend-js "importScripts('./render.js', './graph-wasm-worker.js');"
:depends-on #{}}}
:js-options

View File

@@ -0,0 +1,12 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.graph-wasm
"A WASM based render API"
(:require
[app.graph-wasm.api :as wasm.api]))
(def module wasm.api/module)

View File

@@ -0,0 +1,91 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.graph-wasm.api
(:require
[app.common.data.macros :as dm]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.graph-wasm.wasm :as wasm]
[app.render-wasm.helpers :as h]
[app.render-wasm.serializers :as sr]
[app.util.modules :as mod]
[promesa.core :as p]))
(defn hello []
(h/call wasm/internal-module "_hello"))
(defn init []
(h/call wasm/internal-module "_init"))
(defn use-shape
[id]
(let [buffer (uuid/get-u32 id)]
(println "use-shape" id)
(h/call wasm/internal-module "_use_shape"
(aget buffer 0)
(aget buffer 1)
(aget buffer 2)
(aget buffer 3))))
(defn set-shape-parent-id
[id]
(let [buffer (uuid/get-u32 id)]
(h/call wasm/internal-module "_set_shape_parent"
(aget buffer 0)
(aget buffer 1)
(aget buffer 2)
(aget buffer 3))))
(defn set-shape-type
[type]
(h/call wasm/internal-module "_set_shape_type" (sr/translate-shape-type type)))
(defn set-shape-selrect
[selrect]
(h/call wasm/internal-module "_set_shape_selrect"
(dm/get-prop selrect :x1)
(dm/get-prop selrect :y1)
(dm/get-prop selrect :x2)
(dm/get-prop selrect :y2)))
(defn set-object
[shape]
(let [id (dm/get-prop shape :id)
type (dm/get-prop shape :type)
parent-id (get shape :parent-id)
selrect (get shape :selrect)
children (get shape :shapes)]
(use-shape id)
(set-shape-type type)
(set-shape-parent-id parent-id)
(set-shape-selrect selrect)))
(defn set-objects
[objects]
(doseq [shape (vals objects)]
(set-object shape)))
(defn init-wasm-module
[module]
(let [default-fn (unchecked-get module "default")
href (cf/resolve-href "js/graph-wasm.wasm")]
(default-fn #js {:locateFile (constantly href)})))
(defonce module
(delay
(if (exists? js/dynamicImport)
(let [uri (cf/resolve-href "js/graph-wasm.js")]
(->> (mod/import uri)
(p/mcat init-wasm-module)
(p/fmap (fn [default]
(set! wasm/internal-module default)
true))
(p/merr
(fn [cause]
(js/console.error cause)
(p/resolved false)))))
(p/resolved false))))

View File

@@ -0,0 +1,9 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.graph-wasm.wasm)
(defonce internal-module #js {})

View File

@@ -90,10 +90,7 @@
(rx/map #(ws/initialize)))))))
(defn ^:export init
[options]
(some-> (unchecked-get options "defaultTranslations")
(i18n/set-default-translations))
[]
(mw/init!)
(i18n/init)
(cur/init-styles)

View File

@@ -386,21 +386,3 @@
(rx/of ::dps/force-persist
(rt/nav :viewer params options))))))
(defn go-to-dashboard-deleted
[& {:keys [team-id] :as options}]
(ptk/reify ::go-to-dashboard-deleted
ptk/WatchEvent
(watch [_ state _]
(let [profile (get state :profile)
team-id (cond
(= :default team-id)
(:default-team-id profile)
(uuid? team-id)
team-id
:else
(:current-team-id state))
params {:team-id team-id}]
(rx/of (modal/hide)
(rt/nav :dashboard-deleted params options))))))

View File

@@ -21,7 +21,6 @@
[app.main.data.modal :as modal]
[app.main.data.websocket :as dws]
[app.main.repo :as rp]
[app.main.store :as st]
[app.util.i18n :as i18n :refer [tr]]
[app.util.sse :as sse]
[beicon.v2.core :as rx]
@@ -77,8 +76,7 @@
ptk/UpdateEvent
(update [_ state]
(reduce (fn [state {:keys [id] :as project}]
;; Replace completely instead of merge to ensure deleted-at is removed
(assoc-in state [:projects id] project))
(update-in state [:projects id] merge project))
state
projects))))
@@ -154,34 +152,6 @@
(->> (rp/cmd! :get-builtin-templates)
(rx/map builtin-templates-fetched)))))
;; --- EVENT: deleted-files
(defn- deleted-files-fetched
[files]
(ptk/reify ::deleted-files-fetched
ptk/UpdateEvent
(update [_ state]
(let [now (ct/now)
filtered-files (filterv (fn [file]
(let [will-be-deleted-at (:will-be-deleted-at file)]
(or (nil? will-be-deleted-at)
(ct/is-after? will-be-deleted-at now))))
files)
files (d/index-by :id filtered-files)]
(-> state
(assoc :deleted-files files)
(update :files d/merge files))))))
(defn fetch-deleted-files
([] (fetch-deleted-files nil))
([team-id]
(ptk/reify ::fetch-deleted-files
ptk/WatchEvent
(watch [_ state _]
(when-let [team-id (or team-id (:current-team-id state))]
(->> (rp/cmd! :get-team-deleted-files {:team-id team-id})
(rx/map deleted-files-fetched)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data Selection
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -490,7 +460,6 @@
(-> state
(d/update-in-when [:files file-id] assoc :thumbnail-id thumbnail-id)
(d/update-in-when [:recent-files file-id] assoc :thumbnail-id thumbnail-id)
(d/update-in-when [:deleted-files file-id] assoc :thumbnail-id thumbnail-id)
(d/update-when :dashboard-search-result update-search-files))))))
;; --- EVENT: create-file
@@ -687,156 +656,3 @@
:team-role-change (handle-change-team-role msg)
:team-membership-change (dcm/team-membership-change msg)
nil))
;; --- Delete files immediately
(defn delete-files-immediately
[{:keys [team-id ids] :as params}]
(assert (uuid? team-id))
(assert (set? ids))
(assert (every? uuid? ids))
(ptk/reify ::delete-files-immediately
ev/Event
(-data [_]
{:team-id team-id
:num-files (count ids)})
ptk/WatchEvent
(watch [_ _ _]
(let [{:keys [on-success on-error]
:or {on-success identity
on-error rx/throw}} (meta params)]
(->> (rp/cmd! :permanently-delete-team-files {:team-id team-id :ids ids})
(rx/tap on-success)
(rx/catch on-error))))))
;; --- Restore deleted files immediately
(defn- initialize-restore-status
[files]
(ptk/reify ::init-restore-status
ptk/UpdateEvent
(update [_ state]
(let [restore-state {:in-progress true
:healthy? true
:error false
:progress 0
:widget-visible true
:detail-visible true
:files files
:last-update (ct/now)
:cmd :restore-files}]
(assoc state :restore restore-state)))))
(defn- update-restore-status
[{:keys [index total] :as data}]
(ptk/reify ::upd-restore-status
ptk/UpdateEvent
(update [_ state]
(let [time-diff (ct/diff-ms (get-in state [:restore :last-update]) (ct/now))
healthy? (< time-diff 6000)]
(update state :restore assoc
:progress index
:total total
:last-update (ct/now)
:healthy? healthy?)))))
(defn- complete-restore-status
[]
(ptk/reify ::comp-restore-status
ptk/UpdateEvent
(update [_ state]
(let [total (get-in state [:restore :total])]
(update state :restore assoc
:in-progress false
:progress total ; Ensure progress equals total on completion
:last-update (ct/now))))))
(defn- error-restore-status
[error]
(ptk/reify ::err-restore-status
ptk/UpdateEvent
(update [_ state]
(update state :restore assoc
:in-progress false
:error error
:last-update (ct/now)
:healthy? false))))
(defn toggle-restore-detail-visibility
[]
(ptk/reify ::toggle-restore-detail
ptk/UpdateEvent
(update [_ state]
(update-in state [:restore :detail-visible] not))))
(defn retry-last-restore
[]
(ptk/reify ::retry-restore
ptk/UpdateEvent
(update [_ state]
;; Reset restore state for retry - actual retry will be handled by UI
(if (get state :restore)
(update state :restore assoc :error false :in-progress false)
state))))
(defn clear-restore-state
[]
(ptk/reify ::clear-restore
ptk/UpdateEvent
(update [_ state]
(dissoc state :restore))))
(defn- projects-restored
[team-id]
(ptk/reify ::projects-restored
ptk/WatchEvent
(watch [_ _ _]
;; Refetch projects to get the updated state without deleted-at
(rx/of (fetch-projects team-id)))))
(defn restore-files-immediately
[{:keys [team-id ids] :as params}]
(dm/assert! (uuid? team-id))
(dm/assert! (set? ids))
(dm/assert! (every? uuid? ids))
(ptk/reify ::restore-files-immediately
ev/Event
(-data [_]
{:team-id team-id
:num-files (count ids)})
ptk/WatchEvent
(watch [_ _ _]
(let [{:keys [on-success on-error]
:or {on-success identity
on-error rx/throw}} (meta params)
files (mapv #(hash-map :id %) ids)]
(rx/merge
(rx/of (initialize-restore-status files))
(->> (rp/cmd! ::sse/restore-deleted-team-files {:team-id team-id :ids ids})
(rx/tap (fn [event]
(let [payload (sse/get-payload event)
type (sse/get-type event)]
(when (and payload (= type "progress"))
(let [{:keys [index total]} payload]
(when (and index total)
;; Dispatch progress update
(st/emit! (update-restore-status {:index index :total total}))))))))
(rx/filter sse/end-of-stream?)
(rx/map sse/get-payload)
(rx/tap on-success)
(rx/mapcat (fn [_]
(rx/of (complete-restore-status)
(projects-restored team-id))))
(rx/catch (fn [error]
(rx/concat
(rx/of (error-restore-status (ex-message error)))
(on-error error)))))
(rx/of (ptk/data-event ::restore-start {:total (count ids)})))))))

View File

@@ -98,7 +98,9 @@
(def context
(atom (d/without-nils (collect-context))))
(add-watch i18n/locale "events" #(swap! context assoc :locale %4))
(add-watch i18n/state "events"
(fn [_ _ _ v]
(swap! context assoc :locale (get v :locale))))
;; --- EVENT TRANSLATION

View File

@@ -270,12 +270,8 @@
(ptk/reify ::process-wasm-object
ptk/EffectEvent
(effect [_ state _]
(let [objects (dsh/lookup-page-objects state)
shape (get objects id)]
;; Only process objects that exist in the current page
;; This prevents errors when processing changes from other pages
(when shape
(wasm.api/process-object shape))))))
(let [objects (dsh/lookup-page-objects state)]
(wasm.api/process-object (get objects id))))))
(defn initialize-workspace
[team-id file-id]

View File

@@ -14,7 +14,7 @@
[app.common.types.fills :as types.fills]
[app.common.types.library :as ctl]
[app.common.types.shape :as shp]
[app.common.types.shape.shadow :as types.shadow]
[app.common.types.shape.shadow :refer [check-shadow]]
[app.common.types.text :as txt]
[app.main.broadcast :as mbc]
[app.main.data.helpers :as dsh]
@@ -406,30 +406,30 @@
(defn change-shadow
[ids attrs index]
(letfn [(update-shadow [shape]
(let [;; If we try to set a gradient to a shadow (for
;; example using the color selection from
;; multiple shapes) let's use the first stop
;; color
attrs (cond-> attrs
(:gradient attrs)
(-> (dm/get-in [:gradient :stops 0])
(select-keys types.shadow/color-attrs)))
(ptk/reify ::change-shadow
ptk/WatchEvent
(watch [_ _ _]
(rx/of (dwsh/update-shapes
ids
(fn [shape]
(let [;; If we try to set a gradient to a shadow (for
;; example using the color selection from
;; multiple shapes) let's use the first stop
;; color
attrs (cond-> attrs
(:gradient attrs)
(dm/get-in [:gradient :stops 0]))
attrs' (-> (dm/get-in shape [:shadow index :color])
(merge attrs)
(d/without-nils))]
(assoc-in shape [:shadow index :color] attrs')))]
(ptk/reify ::change-shadow
ptk/WatchEvent
(watch [_ _ _]
(rx/of (dwsh/update-shapes ids update-shadow))))))
attrs' (-> (dm/get-in shape [:shadow index :color])
(merge attrs)
(d/without-nils))]
(assoc-in shape [:shadow index :color] attrs'))))))))
(defn add-shadow
[ids shadow]
(assert
(types.shadow/check-shadow shadow)
(check-shadow shadow)
"expected a valid shadow struct")
(assert
@@ -1146,16 +1146,16 @@
(defn- shadow->color-attr
"Given a stroke map enriched with :shape-id, :index, and optionally
:has-token-applied / :token-name, returns a color attribute map.
If :has-token-applied is true, adds token metadata to :attrs:
{:has-token-applied true
:token-name <token-name>}
Args:
- stroke: map with stroke info, including :shape-id and :index
- file-id: current file UUID
- libraries: map of shared color libraries
Returns:
A map like:
{:attrs {...color data...}
@@ -1260,12 +1260,12 @@
will include extra attributes in its :attrs map:
{:has-token-applied true
:token-name <token-name>}
Args:
- shapes: vector of shape maps
- file-id: current file UUID
- libraries: map of shared color libraries
Returns:
A vector of color attribute maps with metadata for each shape."
[shapes file-id libraries]

View File

@@ -0,0 +1,288 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
;;
;; High level helpers to turn a shape subtree into a component and
;; replace equivalent subtrees by instances of that component.
(ns app.main.data.workspace.componentize
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.geom.point :as gpt]
[app.common.logic.libraries :as cll]
[app.common.logic.shapes :as cls]
[app.common.types.shape :as cts]
[app.common.uuid :as uuid]
[app.main.data.changes :as dch]
[app.main.data.helpers :as dsh]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.undo :as dwu]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
;; NOTE: We keep this separate from `workspace.libraries` to avoid
;; introducing more complexity in that already big namespace.
(def ^:private instance-structural-keys
"Keys we do NOT want to copy from the original shape when creating a
new component instance. These are identity / structural / component
metadata keys that must be managed by the component system itself."
#{:id
:parent-id
:frame-id
:shapes
;; Component metadata
:component-id
:component-file
:component-root
:main-instance
:remote-synced
:shape-ref
:touched})
(def ^:private instance-geometry-keys
"Geometry-related keys that we *do* want to override per instance when
copying props from an existing subtree to a component instance."
#{:x
:y
:width
:height
:rotation
:flip-x
:flip-y
:selrect
:points
:proportion
:proportion-lock
:transform
:transform-inverse})
(defn- instantiate-similar-subtrees
"Internal helper. Given an atom `id-ref` that will contain the
`component-id`, replace each subtree rooted at the ids in
`similar-ids` by an instance of that component.
The operation is performed in a single undo transaction:
- Instantiate the component once per similar id, roughly at the same
top-left position as the original root.
- Delete the original subtrees.
- Select the main instance plus all the new instances."
[id-ref root-id similar-ids]
(ptk/reify ::instantiate-similar-subtrees
ptk/WatchEvent
(watch [it state _]
(let [component-id @id-ref
similar-ids (vec (or similar-ids []))]
(if (or (uuid/zero? component-id)
(empty? similar-ids))
(rx/empty)
(let [file-id (:current-file-id state)
page (dsh/lookup-page state)
page-id (:id page)
objects (:objects page)
libraries (dsh/lookup-libraries state)
fdata (dsh/lookup-file-data state file-id)
;; Reference subtree: shapes used to build the component.
;; We'll compute per-shape deltas against this subtree so
;; that we only override attributes that actually differ.
ref-subtree-ids (cfh/get-children-ids objects root-id)
ref-all-ids (into [root-id] ref-subtree-ids)
undo-id (js/Symbol)
;; 1) Instantiate component at each similar root position,
;; preserving per-instance overrides (geometry, style, etc.)
[changes new-root-ids]
(reduce
(fn [[changes acc] sid]
(if-let [shape (get objects sid)]
(let [position (gpt/point (:x shape) (:y shape))
;; Remember original parent and index so we can keep
;; the same ordering among the parent's children.
orig-root (get objects sid)
orig-parent-id (:parent-id orig-root)
orig-index (when orig-parent-id
(cfh/get-position-on-parent objects sid))
;; Instantiate a new component instance at the same position
[new-shape changes']
(cll/generate-instantiate-component
(or changes
(-> (pcb/empty-changes it page-id)
(pcb/with-objects objects)))
objects
file-id
component-id
position
page
libraries)
;; Build a structural mapping between the original subtree
;; (rooted at `sid`) and the new instance subtree.
;; NOTE 1: instantiating a component can introduce an extra
;; wrapper frame, so we try to align the original root
;; with the "equivalent" root inside the instance.
;; NOTE 2: by default the instance may be created *inside*
;; the original shape (because of layout / hit-testing).
;; We explicitly move the new instance to the same parent
;; and index as the original root, so that later deletes of
;; the original subtree don't remove the new instances and
;; the ordering among siblings is preserved.
changes' (cond-> changes'
(some? orig-parent-id)
(pcb/change-parent orig-parent-id [new-shape] orig-index
{:allow-altering-copies true
:ignore-touched true}))
objects' (pcb/get-objects changes')
orig-root (get objects sid)
new-root new-shape
orig-type (:type orig-root)
new-type (:type new-root)
;; Full original subtree (root + descendants)
orig-subtree-ids (cfh/get-children-ids objects sid)
orig-all-ids (into [sid] orig-subtree-ids)
;; Try to find an inner instance root matching the original type
;; when the outer instance root type differs (e.g. rect -> frame+rect).
direct-new-children (cfh/get-children-ids objects' (:id new-root))
candidate-instance-root
(when (and orig-type (not= orig-type new-type))
(let [cands (->> direct-new-children
(filter (fn [nid]
(when-let [s (get objects' nid)]
(= (:type s) orig-type)))))]
(when (= 1 (count cands))
(first cands))))
instance-root-id (or candidate-instance-root (:id new-root))
instance-root (get objects' instance-root-id)
new-subtree-ids (cfh/get-children-ids objects' instance-root-id)
new-all-ids (into [instance-root-id] new-subtree-ids)
id-pairs (map vector orig-all-ids new-all-ids)
changes''
;; Compute per-shape deltas against the reference
;; subtree (root-id) and apply only the differences
;; to the new instance subtree, so we don't blindly
;; overwrite attributes that are the same.
(reduce
(fn [ch [idx orig-id new-id]]
(let [ref-id (nth ref-all-ids idx nil)
ref-shape (get objects ref-id)
orig-shape (get objects orig-id)]
(if (and ref-shape orig-shape)
(let [;; Style / layout / text props (see `extract-props`)
ref-style (cts/extract-props ref-shape)
orig-style (cts/extract-props orig-shape)
style-delta (reduce (fn [m k]
(let [rv (get ref-style k ::none)
ov (get orig-style k ::none)]
(if (= rv ov)
m
(assoc m k ov))))
{}
(keys orig-style))
;; Geometry props
ref-geom (select-keys ref-shape instance-geometry-keys)
orig-geom (select-keys orig-shape instance-geometry-keys)
geom-delta (reduce (fn [m k]
(let [rv (get ref-geom k ::none)
ov (get orig-geom k ::none)]
(if (= rv ov)
m
(assoc m k ov))))
{}
(keys orig-geom))
;; Text content: if the subtree reference and the
;; original differ in `:content`, treat the whole
;; content tree as an override for this instance.
content-delta? (not= (:content ref-shape) (:content orig-shape))]
(-> ch
;; First patch style/text/layout props using the
;; canonical helpers so we don't touch structural ids.
(cond-> (seq style-delta)
(pcb/update-shapes
[new-id]
(fn [s objs] (cts/patch-props s style-delta objs))
{:with-objects? true}))
;; Then patch geometry directly on the instance.
(cond-> (seq geom-delta)
(pcb/update-shapes
[new-id]
(d/patch-object geom-delta)))
;; Finally, if text content differs between the
;; reference subtree and the similar subtree,
;; override the instance content with the original.
(cond-> content-delta?
(pcb/update-shapes
[new-id]
#(assoc % :content (:content orig-shape))))))
ch)))
changes'
(map-indexed (fn [idx [orig-id new-id]]
[idx orig-id new-id])
id-pairs))]
[changes'' (conj acc (:id new-shape))])
;; If the shape does not exist we just skip it
[changes acc]))
[nil []]
similar-ids)
changes (or changes
(-> (pcb/empty-changes it page-id)
(pcb/with-objects objects)))
;; 2) Delete original similar subtrees
;; NOTE: `d/ordered-set` with a single arg treats it as a single
;; element, so we must use `into` when we already have a collection.
ids-to-delete (into (d/ordered-set) similar-ids)
[all-parents changes]
(cls/generate-delete-shapes
changes
fdata
page
objects
ids-to-delete
{:allow-altering-copies true})
;; 3) Select main instance + new instances
;; Root id is kept as-is; add all new roots.
sel-ids (into (d/ordered-set) (cons root-id new-root-ids))]
(rx/of
(dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(ptk/data-event :layout/update {:ids all-parents})
(dwu/commit-undo-transaction undo-id))))))))
(defn componentize-similar-subtrees
"Turn the subtree rooted at `root-id` into a component, then replace
the subtrees rooted at `similar-ids` with instances of that component.
This is implemented in two phases:
1) Use the existing `dwl/add-component` flow to create a component
from `root-id` (and obtain its `component-id`).
2) Using the new `component-id`, instantiate the component once per
entry in `similar-ids` and delete the old subtrees."
[root-id similar-ids]
(dm/assert!
"expected valid uuid for `root-id`"
(uuid? root-id))
(let [similar-ids (vec (or similar-ids []))]
(ptk/reify ::componentize-similar-subtrees
ptk/WatchEvent
(watch [_ _ _]
(let [id-ref (atom uuid/zero)]
(rx/concat
;; 1) Create component using the existing pipeline
(rx/of (dwl/add-component id-ref (d/ordered-set root-id)))
;; 2) Replace similar subtrees by instances of the new component
(rx/of (instantiate-similar-subtrees id-ref root-id similar-ids))))))))

View File

@@ -636,6 +636,3 @@
(def persistence-state
(l/derived (comp :status :persistence) st/state))
(def restore
(l/derived :restore st/state))

View File

@@ -87,9 +87,6 @@
{:stream? true
:form-data? true}
::sse/restore-deleted-team-files
{:stream? true}
:export-binfile {:response-type :blob}
:retrieve-list-of-builtin-templates {:query-params :all}})

View File

@@ -224,8 +224,7 @@
:dashboard-members
:dashboard-invitations
:dashboard-webhooks
:dashboard-settings
:dashboard-deleted)
:dashboard-settings)
(let [params (get params :query)
team-id (some-> params :team-id uuid/parse*)
project-id (some-> params :project-id uuid/parse*)

View File

@@ -20,7 +20,6 @@
[app.main.router :as rt]
[app.main.store :as st]
[app.main.ui.context :as ctx]
[app.main.ui.dashboard.deleted :refer [deleted-section*]]
[app.main.ui.dashboard.files :refer [files-section*]]
[app.main.ui.dashboard.fonts :refer [fonts-page* font-providers-page*]]
[app.main.ui.dashboard.import]
@@ -30,7 +29,6 @@
[app.main.ui.dashboard.sidebar :refer [sidebar*]]
[app.main.ui.dashboard.team :refer [team-settings-page* team-members-page* team-invitations-page* webhooks-page*]]
[app.main.ui.dashboard.templates :refer [templates-section*]]
[app.main.ui.exports.assets :refer [progress-widget]]
[app.main.ui.hooks :as hooks]
[app.main.ui.modal :refer [modal-container*]]
[app.main.ui.workspace.plugins]
@@ -86,9 +84,6 @@
[:div {:class (stl/css :dashboard-content)
:on-click clear-selected-fn
:ref container}
[:& progress-widget {:operation :restore}]
(case section
:dashboard-recent
(when (seq projects)
@@ -145,11 +140,6 @@
:dashboard-settings
[:> team-settings-page* {:team team :profile profile}]
:dashboard-deleted
[:> deleted-section* {:team team
:projects projects
:profile profile}]
nil)]))
(def ref:dashboard-initialized

View File

@@ -1,331 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.main.ui.dashboard.deleted
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.common.geom.point :as gpt]
[app.main.data.common :as dcm]
[app.main.data.dashboard :as dd]
[app.main.data.modal :as modal]
[app.main.data.notifications :as ntf]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.context-menu-a11y :refer [context-menu*]]
[app.main.ui.dashboard.grid :refer [grid*]]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as deprecated-icon]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
[okulary.core :as l]
[rumext.v2 :as mf]))
(def ^:private menu-icon
(deprecated-icon/icon-xref :menu (stl/css :menu-icon)))
(mf/defc header*
{::mf/props :obj
::mf/private true}
[]
[:header {:class (stl/css :dashboard-header) :data-testid "dashboard-header"}
[:div#dashboard-deleted-title {:class (stl/css :dashboard-title)}
[:h1 (tr "dashboard.projects-title")]]])
(mf/defc deleted-project-menu*
[{:keys [project files team-id show on-close top left]}]
(let [top (d/nilv top 0)
left (d/nilv left 0)
file-ids
(mf/with-memo [files]
(into #{} d/xf:map-id files))
restore-fn
(fn [_]
(st/emit! (dd/restore-files-immediately
(with-meta {:team-id team-id :ids file-ids}
{:on-success #(st/emit! (ntf/success (tr "restore-modal.success-restore-immediately" (:name project)))
(dd/fetch-projects team-id)
(dd/fetch-deleted-files team-id))
:on-error #(st/emit! (ntf/error (tr "restore-modal.error-restore-project" (:name project))))}))))
on-restore-project
(fn []
(st/emit!
(modal/show {:type :confirm
:title (tr "restore-modal.restore-project.title")
:message (tr "restore-modal.restore-project.description" (:name project))
:accept-style :primary
:accept-label (tr "labels.continue")
:on-accept restore-fn})))
delete-fn
(fn [_]
(st/emit! (ntf/success (tr "delete-forever-modal.success-delete-immediately" (:name project)))
(dd/delete-files-immediately
{:team-id team-id
:ids file-ids})
(dd/fetch-projects team-id)
(dd/fetch-deleted-files team-id)))
on-delete-project
(fn []
(st/emit!
(modal/show {:type :confirm
:title (tr "delete-forever-modal.title")
:message (tr "delete-forever-modal.delete-project.description" (:name project))
:accept-label (tr "dashboard.deleted.delete-forever")
:on-accept delete-fn})))
options
[{:name (tr "dashboard.deleted.restore-project")
:id "project-restore"
:handler on-restore-project}
{:name (tr "dashboard.deleted.delete-project")
:id "project-delete"
:handler on-delete-project}]]
[:> context-menu*
{:on-close on-close
:show show
:fixed (or (not= top 0) (not= left 0))
:min-width true
:top top
:left left
:options options}]))
(mf/defc deleted-project-item*
{::mf/props :obj
::mf/private true}
[{:keys [project team files]}]
(let [project-files (filterv #(= (:project-id %) (:id project)) files)
empty? (empty? project-files)
selected-files (mf/deref refs/selected-files)
dstate (mf/deref refs/dashboard-local)
edit-id (:project-for-edit dstate)
local (mf/use-state
#(do {:menu-open false
:menu-pos nil
:edition (= (:id project) edit-id)}))
[rowref limit] (hooks/use-dynamic-grid-item-width)
on-menu-click
(mf/use-fn
(fn [event]
(dom/prevent-default event)
(let [client-position (dom/get-client-position event)
position (if (and (nil? (:y client-position)) (nil? (:x client-position)))
(let [target-element (dom/get-target event)
points (dom/get-bounding-rect target-element)
y (:top points)
x (:left points)]
(gpt/point x y))
client-position)]
(swap! local assoc
:menu-open true
:menu-pos position))))
on-menu-close
(mf/use-fn #(swap! local assoc :menu-open false))
handle-menu-click
(mf/use-callback
(mf/deps on-menu-click)
(fn [event]
(when (kbd/enter? event)
(dom/stop-propagation event)
(on-menu-click event))))]
[:article {:class (stl/css-case :dashboard-project-row true)}
[:header {:class (stl/css :project)}
[:div {:class (stl/css :project-name-wrapper)}
[:h2 {:class (stl/css :project-name)
:title (:name project)}
(:name project)]
(when (:deleted-at project)
[:div {:class (stl/css :info-wrapper)}
[:div {:class (stl/css-case :project-actions true)}
[:button {:class (stl/css :options-btn)
:on-click on-menu-click
:title (tr "dashboard.options")
:aria-label (tr "dashboard.options")
:data-testid "project-options"
:on-key-down handle-menu-click}
menu-icon]]
(when (:menu-open @local)
[:> deleted-project-menu*
{:project project
:files project-files
:team-id (:id team)
:show (:menu-open @local)
:left (+ 24 (:x (:menu-pos @local)))
:top (:y (:menu-pos @local))
:on-close on-menu-close}])])]]
[:div {:class (stl/css :grid-container) :ref rowref}
(if ^boolean empty?
[:> empty-placeholder* {:title (tr "dashboard.empty-placeholder-files-title")
:class (stl/css :placeholder-placement)
:type 1
:subtitle (tr "dashboard.empty-placeholder-files-subtitle")}]
[:> grid*
{:project project
:files project-files
:origin :deleted
:can-edit false
:can-restore true
:limit limit
:selected-files selected-files}])]]))
(def ^:private ref:deleted-files
(l/derived :deleted-files st/state))
(mf/defc deleted-section*
[{:keys [team projects]}]
(let [deleted-map
(mf/deref ref:deleted-files)
projects
(mf/with-memo [projects deleted-map]
(->> projects
(filter (fn [project]
(or (:deleted-at project)
(when deleted-map
(some #(= (:id project) (:project-id %))
(vals deleted-map))))))
(filter (fn [project]
(when deleted-map
(some #(= (:id project) (:project-id %))
(vals deleted-map)))))
(sort-by :modified-at)
(reverse)))
team-id
(get team :id)
;; Calculate deletion days based on team subscription
deletion-days
(let [subscription (get team :subscription)
sub-type (get subscription :type)
sub-status (get subscription :status)
canceled? (contains? #{"canceled" "unpaid"} sub-status)]
(cond
(and (= "unlimited" sub-type) (not canceled?)) 30
(and (= "enterprise" sub-type) (not canceled?)) 90
:else 7))
on-clear
(mf/use-fn
(mf/deps team-id deleted-map)
(fn []
(when deleted-map
(let [file-ids (into #{} (keys deleted-map))]
(when (seq file-ids)
(st/emit!
(modal/show {:type :confirm
:title (tr "delete-forever-modal.title")
:message (tr "delete-forever-modal.delete-all.description" (count file-ids))
:accept-label (tr "dashboard.deleted.delete-forever")
:on-accept #(st/emit!
(dd/delete-files-immediately
{:team-id team-id
:ids file-ids})
(dd/fetch-projects team-id)
(dd/fetch-deleted-files team-id))})))))))
restore-fn
(fn [file-ids]
(st/emit! (dd/restore-files-immediately
(with-meta {:team-id team-id :ids file-ids}
{:on-success #(st/emit! (dd/fetch-projects team-id)
(dd/fetch-deleted-files team-id))
:on-error #(st/emit! (ntf/error (tr "restore-modal.error-restore-files")))}))))
on-restore-all
(mf/use-fn
(mf/deps team-id deleted-map)
(fn []
(when deleted-map
(let [file-ids (into #{} (keys deleted-map))]
(when (seq file-ids)
(st/emit!
(modal/show {:type :confirm
:title (tr "restore-modal.restore-all.title")
:message (tr "restore-modal.restore-all.description" (count file-ids))
:accept-label (tr "labels.continue")
:accept-style :primary
:on-accept #(restore-fn file-ids)})))))))
on-recent-click
(mf/use-fn
(mf/deps team-id)
(fn []
(st/emit! (dcm/go-to-dashboard-recent :team-id team-id))))]
(mf/with-effect [team-id]
(st/emit! (dd/fetch-projects team-id)
(dd/fetch-deleted-files team-id)
(dd/clear-selected-files)))
[:*
[:> header* {:team team}]
[:section {:class (stl/css :dashboard-container :no-bg)}
[:*
[:div {:class (stl/css :no-bg)}
[:div {:class (stl/css :nav-options)}
[:> button* {:variant "ghost"
:data-testid "recent-tab"
:type "button"
:on-click on-recent-click}
(tr "dashboard.labels.recent")]
[:div {:class (stl/css :selected)
:data-testid "deleted-tab"}
(tr "dashboard.labels.deleted")]]
[:div {:class (stl/css :deleted-content)}
[:div {:class (stl/css :deleted-info)}
[:div
(tr "dashboard.deleted.info-text")
[:span {:class (stl/css :info-text-highlight)}
(tr "dashboard.deleted.info-days" deletion-days)]
(tr "dashboard.deleted.info-text2")]
[:div
(tr "dashboard.deleted.restore-text")]]
[:div {:class (stl/css :deleted-options)}
[:> button* {:variant "ghost"
:type "button"
:on-click on-restore-all}
(tr "dashboard.deleted.restore-all")]
[:> button* {:variant "destructive"
:type "button"
:icon "delete"
:on-click on-clear}
(tr "dashboard.deleted.clear")]]]
(when (seq projects)
(for [{:keys [id] :as project} projects]
(let [files (when deleted-map
(->> (vals deleted-map)
(filterv #(= id (:project-id %)))
(sort-by :modified-at #(compare %2 %1))))]
[:> deleted-project-item* {:project project
:team team
:files files
:key id}])))]]]]))

View File

@@ -1,125 +0,0 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) KALEIDOS INC
@use "common/refactor/common-dashboard";
@use "../ds/typography.scss" as t;
@use "../ds/_borders.scss" as *;
@use "../ds/spacing.scss" as *;
@use "../ds/_sizes.scss" as *;
@use "../ds/z-index.scss" as *;
.dashboard-container {
flex: 1 0 0;
width: 100%;
margin-inline-end: var(--sp-l);
border-top: $b-1 solid var(--panel-border-color);
overflow-y: auto;
padding-block-end: var(--sp-xxxl);
}
.deleted-content {
display: flex;
gap: var(--sp-l);
justify-content: space-between;
margin-inline-start: var(--sp-l);
margin-block-start: var(--sp-xxl);
}
.deleted-info {
@include t.use-typography("body-medium");
color: var(--color-foreground-secondary);
}
.info-text-highlight {
color: var(--color-accent-primary);
}
.deleted-options {
display: flex;
gap: 5px;
flex-shrink: 0;
}
.nav-options {
display: flex;
gap: var(--sp-l);
justify-content: space-between;
border-bottom: $b-1 solid var(--panel-border-color);
padding-inline-start: var(--sp-l);
background: var(--color-background-default);
position: sticky;
top: 0;
z-index: var(--z-index-panels);
}
.selected {
@include t.use-typography("headline-small");
display: flex;
align-items: center;
justify-content: center;
color: var(--color-foreground-primary);
border: $b-1 solid transparent;
border-bottom: $b-1 solid var(--color-foreground-primary);
padding: 0 var(--sp-m);
}
.project {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: var(--sp-s);
width: 99%;
max-height: $sz-40;
padding: var(--sp-s) var(--sp-s) var(--sp-s) var(--sp-l);
margin-block-start: var(--sp-l);
border-radius: $br-4;
}
.project-name-wrapper {
display: flex;
align-items: center;
justify-content: flex-start;
width: 100%;
min-height: var(--sp-xxxl);
margin-inline-start: var(--sp-s);
}
.project-name {
@include t.use-typography("body-large");
width: fit-content;
margin-inline-end: var(--sp-m);
line-height: 0.8;
color: var(--title-foreground-color-hover);
height: var(--sp-l);
}
.project-actions {
display: flex;
opacity: var(--actions-opacity);
margin-inline-start: var(--sp-xxxl);
}
.add-file-btn,
.options-btn {
@extend .button-tertiary;
height: var(--sp-xxxl);
width: var(--sp-xxxl);
margin: 0 var(--sp-s);
padding: var(--sp-s);
}
.info-wrapper {
display: flex;
align-items: center;
gap: var(--sp-s);
}
.add-icon,
.menu-icon {
@extend .button-icon;
stroke: var(--icon-foreground);
}

View File

@@ -55,7 +55,7 @@
projects))
(mf/defc file-menu*
[{:keys [files on-edit on-close top left navigate origin parent-id can-edit can-restore]}]
[{:keys [files on-edit on-close top left navigate origin parent-id can-edit]}]
(assert (seq files) "missing `files` prop")
(assert (fn? on-edit) "missing `on-edit` prop")
@@ -187,46 +187,7 @@
on-export-binary-files
(fn []
(st/emit! (-> (fexp/open-export-dialog files)
(with-meta {::ev/origin "dashboard"}))))
restore-fn
(fn [_]
(st/emit! (dd/restore-files-immediately
(with-meta {:team-id (:id current-team)
:ids #{(:id file)}}
{:on-success #(st/emit! (ntf/success (tr "restore-modal.success-restore-immediately" (:name file)))
(dd/fetch-projects (:id current-team))
(dd/fetch-deleted-files (:id current-team)))
:on-error #(st/emit! (ntf/error (tr "restore-modal.error-restore-file" (:name file))))}))))
on-restore-immediately
(fn []
(st/emit!
(modal/show {:type :confirm
:title (tr "restore-modal.restore-file.title")
:message (tr "restore-modal.restore-file.description" (:name file))
:accept-label (tr "labels.continue")
:accept-style :primary
:on-accept restore-fn})))
delete-fn
(fn [_]
(st/emit! (ntf/success (tr "delete-forever-modal.success-delete-immediately" (:name file)))
(dd/delete-files-immediately
{:team-id (:id current-team)
:ids #{(:id file)}})
(dd/fetch-projects (:id current-team))
(dd/fetch-deleted-files (:id current-team))))
on-delete-immediately
(fn []
(st/emit!
(modal/show {:type :confirm
:title (tr "delete-forever-modal.title")
:message (tr "delete-forever-modal.delete-file.description" (:name file))
:accept-label (tr "delete-forever-modal.title")
:on-accept delete-fn})))]
(with-meta {::ev/origin "dashboard"}))))]
(mf/with-effect []
(->> (rp/cmd! :get-all-projects)
@@ -266,85 +227,76 @@
(:id sub-project))})})}]))
options
(if can-restore
[(when can-restore
{:name (tr "dashboard.restore-file")
:id "restore-file"
:handler on-restore-immediately})
(when can-restore
{:name (tr "dashboard.delete-file")
:id "delete-file"
:handler on-delete-immediately})]
(if multi?
[(when can-edit
{:name (tr "dashboard.duplicate-multi" file-count)
:id "duplicate-multi"
:handler on-duplicate})
(if multi?
[(when can-edit
{:name (tr "dashboard.duplicate-multi" file-count)
:id "duplicate-multi"
:handler on-duplicate})
(when (and (or (seq current-projects) (seq other-teams)) can-edit)
{:name (tr "dashboard.move-to-multi" file-count)
:id "file-move-multi"
:options sub-options})
(when (and (or (seq current-projects) (seq other-teams)) can-edit)
{:name (tr "dashboard.move-to-multi" file-count)
:id "file-move-multi"
:options sub-options})
{:name (tr "dashboard.export-binary-multi" file-count)
:id "file-binary-export-multi"
:handler on-export-binary-files}
{:name (tr "dashboard.export-binary-multi" file-count)
:id "file-binary-export-multi"
:handler on-export-binary-files}
(when (and (:is-shared file) can-edit)
{:name (tr "labels.unpublish-multi-files" file-count)
:id "file-unpublish-multi"
:handler on-del-shared})
(when (and (:is-shared file) can-edit)
{:name (tr "labels.unpublish-multi-files" file-count)
:id "file-unpublish-multi"
:handler on-del-shared})
(when (and (not is-lib-page?) can-edit)
{:name :separator}
{:name (tr "labels.delete-multi-files" file-count)
:id "file-delete-multi"
:handler on-delete})]
(when (and (not is-lib-page?) can-edit)
{:name :separator}
{:name (tr "labels.delete-multi-files" file-count)
:id "file-delete-multi"
:handler on-delete})]
[{:name (tr "dashboard.open-in-new-tab")
:id "file-open-new-tab"
:handler on-new-tab}
(when (and (not is-search-page?) can-edit)
{:name (tr "labels.rename")
:id "file-rename"
:handler on-edit})
[{:name (tr "dashboard.open-in-new-tab")
:id "file-open-new-tab"
:handler on-new-tab}
(when (and (not is-search-page?) can-edit)
{:name (tr "labels.rename")
:id "file-rename"
:handler on-edit})
(when (and (not is-search-page?) can-edit)
{:name (tr "dashboard.duplicate")
:id "file-duplicate"
:handler on-duplicate})
(when (and (not is-search-page?) can-edit)
{:name (tr "dashboard.duplicate")
:id "file-duplicate"
:handler on-duplicate})
(when (and (not is-lib-page?)
(not is-search-page?)
(or (seq current-projects) (seq other-teams))
can-edit)
{:name (tr "dashboard.move-to")
:id "file-move-to"
:options sub-options})
(when (and (not is-lib-page?)
(not is-search-page?)
(or (seq current-projects) (seq other-teams))
can-edit)
{:name (tr "dashboard.move-to")
:id "file-move-to"
:options sub-options})
(when (and (not is-search-page?)
can-edit)
(if (:is-shared file)
{:name (tr "dashboard.unpublish-shared")
:id "file-del-shared"
:handler on-del-shared}
{:name (tr "dashboard.add-shared")
:id "file-add-shared"
:handler on-add-shared}))
(when (and (not is-search-page?)
can-edit)
(if (:is-shared file)
{:name (tr "dashboard.unpublish-shared")
:id "file-del-shared"
:handler on-del-shared}
{:name (tr "dashboard.add-shared")
:id "file-add-shared"
:handler on-add-shared}))
{:name :separator}
{:name :separator}
{:name (tr "dashboard.download-binary-file")
:id "download-binary-file"
:handler on-export-binary-files}
{:name (tr "dashboard.download-binary-file")
:id "download-binary-file"
:handler on-export-binary-files}
(when (and (not is-lib-page?) (not is-search-page?) can-edit)
{:name :separator})
(when (and (not is-lib-page?) (not is-search-page?) can-edit)
{:name :separator})
(when (and (not is-lib-page?) (not is-search-page?) can-edit)
{:name (tr "labels.delete")
:id "file-delete"
:handler on-delete})]))]
(when (and (not is-lib-page?) (not is-search-page?) can-edit)
{:name (tr "labels.delete")
:id "file-delete"
:handler on-delete})])]
[:> context-menu*
{:on-close on-close

View File

@@ -86,7 +86,7 @@
(mf/defc grid-item-thumbnail*
{::mf/props :obj
::mf/private true}
[{:keys [can-edit file can-restore]}]
[{:keys [can-edit file]}]
(let [file-id (get file :id)
revn (get file :revn)
thumbnail-id (get file :thumbnail-id)
@@ -109,8 +109,7 @@
:message (ex-message cause)))))]
(partial rx/dispose! subscription))))
[:div {:class (stl/css-case :grid-item-th true
:deleted-item can-restore)
[:div {:class (stl/css :grid-item-th)
:style {:background-color bg-color}
:ref container}
(when visible?
@@ -132,15 +131,13 @@
(mf/defc grid-item-library*
{::mf/props :obj}
[{:keys [file can-restore]}]
[{:keys [file]}]
(mf/with-effect [file]
(when file
(let [font-ids (map :font-id (get-in file [:library-summary :typographies :sample] []))]
(run! fonts/ensure-loaded! font-ids))))
[:div {:class (stl/css-case :grid-item-th true
:library true
:deleted-item can-restore)}
[:div {:class (stl/css :grid-item-th :library)}
(if (nil? file)
[:> loader* {:class (stl/css :grid-loader)
:overlay true
@@ -253,7 +250,7 @@
counter-el))
(mf/defc grid-item*
[{:keys [file origin can-edit selected-files can-restore]}]
[{:keys [file origin can-edit selected-files]}]
(let [file-id (get file :id)
state (mf/deref refs/dashboard-local)
@@ -292,13 +289,12 @@
on-navigate
(mf/use-fn
(mf/deps file-id can-restore)
(mf/deps file-id)
(fn [event]
(when-not can-restore
(let [menu-icon (mf/ref-val menu-ref)
target (dom/get-target event)]
(when-not (dom/child? target menu-icon)
(st/emit! (dcm/go-to-workspace :file-id file-id)))))))
(let [menu-icon (mf/ref-val menu-ref)
target (dom/get-target event)]
(when-not (dom/child? target menu-icon)
(st/emit! (dcm/go-to-workspace :file-id file-id))))))
on-drag-start
(mf/use-fn
@@ -416,8 +412,8 @@
[:div {:class (stl/css :overlay)}]
(if ^boolean is-library-view?
[:> grid-item-library* {:file file :can-restore can-restore}]
[:> grid-item-thumbnail* {:file file :can-edit can-edit :can-restore can-restore}])
[:> grid-item-library* {:file file}]
[:> grid-item-thumbnail* {:file file :can-edit can-edit}])
(when (and (:is-shared file) (not is-library-view?))
[:div {:class (stl/css :item-badge)} deprecated-icon/library])
@@ -455,12 +451,11 @@
:on-edit on-edit
:on-close on-menu-close
:origin origin
:parent-id (dm/str file-id "-action-menu")
:can-restore can-restore}]])]]]]]))
:parent-id (dm/str file-id "-action-menu")}]])]]]]]))
(mf/defc grid*
{::mf/props :obj}
[{:keys [files project origin limit create-fn can-edit selected-files can-restore]}]
[{:keys [files project origin limit create-fn can-edit selected-files]}]
(let [dragging? (mf/use-state false)
project-id (get project :id)
team-id (get project :team-id)
@@ -540,8 +535,7 @@
:key (dm/str (:id item))
:origin origin
:selected-files selected-files
:can-edit can-edit
:can-restore can-restore}])])
:can-edit can-edit}])])
:else
[:> empty-grid-placeholder*
@@ -554,7 +548,7 @@
:on-finish-import on-finish-import}])]))
(mf/defc line-grid-row
[{:keys [files selected-files dragging? limit can-edit can-restore] :as props}]
[{:keys [files selected-files dragging? limit can-edit] :as props}]
(let [elements limit
limit (if dragging? (dec limit) limit)]
[:ul {:class (stl/css :grid-row :no-wrap)
@@ -569,11 +563,10 @@
:file item
:selected-files selected-files
:can-edit can-edit
:key (dm/str (:id item))
:can-restore can-restore}])]))
:key (dm/str (:id item))}])]))
(mf/defc line-grid
[{:keys [project team files limit create-fn can-edit can-restore] :as props}]
[{:keys [project team files limit create-fn can-edit] :as props}]
(let [dragging? (mf/use-state false)
project-id (:id project)
team-id (:id team)
@@ -671,8 +664,7 @@
:selected-files selected-files
:dragging? @dragging?
:can-edit can-edit
:limit limit
:can-restore can-restore}]
:limit limit}]
:else
[:> empty-grid-placeholder*

View File

@@ -375,7 +375,3 @@ $thumbnail-default-height: deprecated.$s-168; // Default width
.grid-loader {
--icon-width: calc(var(--th-width, #{$thumbnail-default-width}) * 0.25);
}
.deleted-item {
opacity: 0.5;
}

View File

@@ -21,7 +21,6 @@
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
[app.main.ui.dashboard.pin-button :refer [pin-button*]]
[app.main.ui.dashboard.project-menu :refer [project-menu*]]
[app.main.ui.ds.buttons.button :refer [button*]]
[app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as deprecated-icon]
@@ -343,13 +342,7 @@
(fn []
(reset! show-team-hero* false)
(st/emit! (ptk/data-event ::ev/event {::ev/name "dont-show-team-up-hero"
::ev/origin "dashboard"}))))
on-deleted-click
(mf/use-fn
(mf/deps team-id)
(fn []
(st/emit! (dcm/go-to-dashboard-deleted :team-id team-id))))]
::ev/origin "dashboard"}))))]
(mf/with-effect [show-team-hero?]
(swap! storage/global assoc ::show-team-hero show-team-hero?))
@@ -383,15 +376,6 @@
(not is-defalt-team?)
show-team-hero?
can-invite))}
[:div {:class (stl/css :nav-options)}
[:div {:class (stl/css :selected)
:data-testid "recent-tab"}
(tr "dashboard.labels.recent")]
[:> button* {:variant "ghost"
:type "button"
:data-testid "deleted-tab"
:on-click on-deleted-click}
(tr "dashboard.labels.deleted")]]
(for [{:keys [id] :as project} projects]
;; FIXME: refactor this, looks inneficient
(let [files (when recent-map

View File

@@ -4,21 +4,16 @@
//
// Copyright (c) KALEIDOS INC
@use "common/refactor/common-refactor.scss" as deprecated;
@use "refactor/common-refactor.scss" as deprecated;
@use "common/refactor/common-dashboard";
@use "../ds/typography.scss" as t;
@use "../ds/_borders.scss" as *;
@use "../ds/spacing.scss" as *;
@use "../ds/_sizes.scss" as *;
@use "../ds/z-index.scss" as *;
.dashboard-container {
flex: 1 0 0;
width: 100%;
margin-inline-end: var(--sp-l);
border-top: $b-1 solid var(--panel-border-color);
margin-right: deprecated.$s-16;
border-top: deprecated.$s-1 solid var(--panel-border-color);
overflow-y: auto;
padding-bottom: var(--sp-xxxl);
padding-bottom: deprecated.$s-32;
}
.dashboard-projects {
@@ -32,16 +27,16 @@
.dashboard-shared {
width: calc(100vw - deprecated.$s-320);
margin-inline-end: deprecated.$s-52;
margin-right: deprecated.$s-52;
}
.search {
margin-block-start: var(--sp-m);
margin-top: deprecated.$s-12;
}
.dashboard-project-row {
--actions-opacity: 0;
margin-block-end: var(--sp-xxl);
margin-bottom: deprecated.$s-24;
position: relative;
&:hover,
@@ -65,12 +60,12 @@
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: var(--sp-s);
gap: deprecated.$s-8;
width: 99%;
max-height: $sz-40;
padding: var(--sp-s) var(--sp-s) var(--sp-s) var(--sp-l);
margin-block-start: var(--sp-l);
border-radius: $br-4;
max-height: deprecated.$s-40;
padding: deprecated.$s-8 deprecated.$s-8 deprecated.$s-8 deprecated.$s-16;
margin-top: deprecated.$s-16;
border-radius: deprecated.$br-4;
}
.project-name-wrapper {
@@ -78,29 +73,30 @@
align-items: center;
justify-content: flex-start;
width: 100%;
min-height: var(--sp-xxxl);
margin-inline-start: var(--sp-s);
min-height: deprecated.$s-32;
margin-left: deprecated.$s-8;
}
.project-name {
@include t.use-typography("body-large");
@include deprecated.bodyLargeTypography;
@include deprecated.textEllipsis;
width: fit-content;
margin-inline-end: var(--sp-m);
margin-right: deprecated.$s-12;
line-height: 0.8;
color: var(--title-foreground-color-hover);
cursor: pointer;
height: var(--sp-l);
height: deprecated.$s-16;
}
.info-wrapper {
display: flex;
align-items: center;
gap: var(--sp-s);
gap: deprecated.$s-8;
}
.info,
.recent-files-row-title-info {
@include t.use-typography("body-medium");
@include deprecated.bodyMediumTypography;
color: var(--title-foreground-color);
@media (max-width: 760px) {
display: none;
@@ -110,16 +106,16 @@
.project-actions {
display: flex;
opacity: var(--actions-opacity);
margin-inline-start: var(--sp-xxxl);
margin-left: deprecated.$s-32;
}
.add-file-btn,
.options-btn {
@extend .button-tertiary;
height: var(--sp-xxxl);
width: var(--sp-xxxl);
margin: 0 var(--sp-s);
padding: var(--sp-s);
height: deprecated.$s-32;
width: deprecated.$s-32;
margin: 0 deprecated.$s-8;
padding: deprecated.$s-8;
}
.add-icon,
@@ -130,24 +126,24 @@
.grid-container {
width: 100%;
padding: 0 var(--sp-xs);
padding: 0 deprecated.$s-4;
}
.placeholder-placement {
margin: var(--sp-l) var(--sp-xxxl);
margin: deprecated.$s-16 deprecated.$s-32;
}
.show-more {
--show-more-color: var(--button-secondary-foreground-color-rest);
@include deprecated.buttonStyle;
@include t.use-typography("body-medium");
@include deprecated.bodyMediumTypography;
position: absolute;
top: var(--sp-s);
top: deprecated.$s-8;
right: deprecated.$s-52;
display: flex;
align-items: center;
justify-content: space-between;
column-gap: var(--sp-m);
column-gap: deprecated.$s-12;
color: var(--show-more-color);
&:hover {
@@ -156,8 +152,8 @@
}
.show-more-icon {
height: var(--sp-l);
width: var(--sp-l);
height: deprecated.$s-16;
width: deprecated.$s-16;
fill: none;
stroke: var(--show-more-color);
}
@@ -168,13 +164,13 @@
border-radius: deprecated.$br-8;
border: none;
display: flex;
margin: var(--sp-l);
padding: var(--sp-s);
margin: deprecated.$s-16;
padding: deprecated.$s-8;
position: relative;
img {
border-radius: $br-4;
height: var(--sp-xl) 0;
border-radius: deprecated.$br-4;
height: deprecated.$s-200;
width: auto;
@media (max-width: 1200px) {
@@ -189,18 +185,18 @@
flex-direction: column;
align-items: flex-start;
flex-grow: 1;
padding: var(--sp-xl) var(--sp-xl);
padding: deprecated.$s-20 deprecated.$s-20;
}
.title {
font-size: $sz-24;
font-size: deprecated.$fs-24;
color: var(--color-foreground-primary);
font-weight: deprecated.$fw400;
}
.info {
flex: 1;
font-size: $sz-16;
font-size: deprecated.$fs-16;
span {
color: var(--color-foreground-secondary);
display: block;
@@ -208,15 +204,15 @@
a {
color: var(--color-accent-primary);
}
padding: var(--sp-s) 0;
padding: deprecated.$s-8 0;
}
.close {
--close-icon-foreground-color: var(--icon-foreground);
position: absolute;
top: var(--sp-xl);
right: var(--sp-xxl);
width: var(--sp-xxl);
top: deprecated.$s-20;
right: deprecated.$s-24;
width: deprecated.$s-24;
background-color: transparent;
border: none;
cursor: pointer;
@@ -231,7 +227,7 @@
}
.invite {
height: var(--sp-xxxl);
height: deprecated.$s-32;
width: deprecated.$s-180;
}
@@ -239,8 +235,8 @@
display: flex;
align-items: center;
justify-content: center;
width: var(--sp-xl) 0;
height: var(--sp-xl) 0;
width: deprecated.$s-200;
height: deprecated.$s-200;
overflow: hidden;
border-radius: deprecated.$br-4;
@media (max-width: 1200px) {
@@ -248,26 +244,3 @@
width: 0;
}
}
.nav-options {
display: flex;
gap: var(--sp-l);
justify-content: space-between;
border-bottom: $b-1 solid var(--panel-border-color);
padding-inline-start: var(--sp-l);
background: var(--color-background-default);
position: sticky;
top: 0;
z-index: var(--z-index-panels);
}
.selected {
@include t.use-typography("headline-small");
display: flex;
align-items: center;
justify-content: center;
color: var(--color-foreground-primary);
border: $b-1 solid transparent;
border-bottom: $b-1 solid var(--color-foreground-primary);
padding: 0 var(--sp-m);
}

View File

@@ -27,11 +27,11 @@
[app.main.ui.dashboard.comments :refer [comments-icon* comments-section]]
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
[app.main.ui.dashboard.project-menu :refer [project-menu*]]
[app.main.ui.dashboard.subscription :refer [dashboard-cta*
get-subscription-type
[app.main.ui.dashboard.subscription :refer [subscription-sidebar*
menu-team-icon*
dashboard-cta*
show-subscription-dashboard-banner?
subscription-sidebar*]]
get-subscription-type]]
[app.main.ui.dashboard.team-form]
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i]
[app.main.ui.icons :as deprecated-icon]

View File

@@ -12,7 +12,6 @@
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.types.color :as clr]
[app.main.data.dashboard :as dd]
[app.main.data.exports.assets :as de]
[app.main.data.modal :as modal]
[app.main.refs :as refs]
@@ -206,13 +205,10 @@
:cmd :export-frames
:origin origin}]))
(mf/defc progress-widget
(mf/defc export-progress-widget
{::mf/wrap [mf/memo]}
[{:keys [operation] :or {operation :export}}]
(let [state (mf/deref (case operation
:export refs/export
:restore refs/restore
refs/export))
[]
(let [state (mf/deref refs/export)
profile (mf/deref refs/profile)
theme (or (:theme profile) theme/default)
is-default-theme? (= theme/default theme)
@@ -221,14 +217,11 @@
detail-visible? (:detail-visible state)
widget-visible? (:widget-visible state)
progress (:progress state)
items (case operation
:export (:exports state)
:restore (:files state)
[])
total (or (:total state) (count items))
exports (:exports state)
total (count exports)
complete? (= progress total)
circ (* 2 Math/PI 12)
pct (if (zero? total) circ (- circ (* circ (/ progress total))))
pct (- circ (* circ (/ progress total)))
pwidth
(if error?
@@ -250,43 +243,19 @@
title
(cond
error? (case operation
:export (tr "workspace.options.exporting-object-error")
:restore (tr "workspace.options.restoring-object-error")
(tr "workspace.options.processing-object-error"))
complete? (case operation
:export (tr "workspace.options.exporting-complete")
:restore (tr "workspace.options.restoring-complete")
(tr "workspace.options.processing-complete"))
healthy? (case operation
:export (tr "workspace.options.exporting-object")
:restore (tr "workspace.options.restoring-object")
(tr "workspace.options.processing-object"))
(not healthy?) (case operation
:export (tr "workspace.options.exporting-object-slow")
:restore (tr "workspace.options.restoring-object-slow")
(tr "workspace.options.processing-object-slow")))
error? (tr "workspace.options.exporting-object-error")
complete? (tr "workspace.options.exporting-complete")
healthy? (tr "workspace.options.exporting-object")
(not healthy?) (tr "workspace.options.exporting-object-slow"))
retry-last-operation
(mf/use-fn
(mf/deps operation)
(fn []
(case operation
:export (st/emit! (de/retry-last-export))
:restore (st/emit! (dd/retry-last-restore))
nil)))
retry-last-export
(mf/use-fn #(st/emit! (de/retry-last-export)))
toggle-detail-visibility
(mf/use-fn
(mf/deps operation)
(fn []
(case operation
:export (st/emit! (de/toggle-detail-visibililty))
:restore (st/emit! (dd/toggle-restore-detail-visibility))
nil)))]
(mf/use-fn #(st/emit! (de/toggle-detail-visibililty)))]
[:*
(when (and widget-visible? (= operation :export))
(when widget-visible?
[:div {:class (stl/css :export-progress-widget)
:on-click toggle-detail-visibility}
[:svg {:width "24" :height "24"}
@@ -314,11 +283,11 @@
error-icon
neutral-icon)
[:div {:class (stl/css :export-progress-title)}
[:div {:class (stl/css :title-text)} title]
[:p {:class (stl/css :export-progress-title)}
title
(if error?
[:button {:class (stl/css :retry-btn)
:on-click retry-last-operation}
:on-click retry-last-export}
(tr "workspace.options.retry")]
[:span {:class (stl/css :progress)}

View File

@@ -64,8 +64,7 @@
["/fonts" :dashboard-fonts]
["/fonts/providers" :dashboard-font-providers]
["/libraries" :dashboard-libraries]
["/files" :dashboard-files]
["/deleted" :dashboard-deleted]]
["/files" :dashboard-files]]
["/dashboard/team/:team-id"
["/members" :dashboard-legacy-team-members]

View File

@@ -14,7 +14,7 @@
[app.main.data.viewer.shortcuts :as sc]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.exports.assets :refer [progress-widget]]
[app.main.ui.exports.assets :refer [export-progress-widget]]
[app.main.ui.formats :as fmt]
[app.main.ui.icons :as deprecated-icon]
[app.main.ui.viewer.comments :refer [comments-menu]]
@@ -167,7 +167,7 @@
(open-share-dialog)))
[:div {:class (stl/css :options-zone)}
[:& progress-widget {:operation :export}]
[:& export-progress-widget]
(case section
:interactions [:*

View File

@@ -22,7 +22,7 @@
[app.main.ui.dashboard.team]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.exports.assets :refer [progress-widget]]
[app.main.ui.exports.assets :refer [export-progress-widget]]
[app.main.ui.formats :as fmt]
[app.main.ui.icons :as deprecated-icon]
[app.main.ui.workspace.presence :refer [active-sessions]]
@@ -200,7 +200,7 @@
[:div {:class (stl/css :users-section)}
[:& active-sessions]]
[:& progress-widget {:operation :export}]
[:& export-progress-widget]
[:div {:class (stl/css :separator)}]

View File

@@ -6,7 +6,7 @@
(ns app.main.ui.workspace.shapes.text.editor
(:require
["@penpot/draft-js" :as draft]
["draft-js" :as draft]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]

View File

@@ -271,10 +271,8 @@
:font-variant-id new-variant-id
:font-weight (:weight variant)
:font-style (:style variant)}))
;; NOTE: the select component we are using does not fire on-blur event
;; so we need to call on-blur manually
(when (some? on-blur)
(on-blur)))))
(dom/blur! (dom/get-target new-variant-id)))))
on-font-select
(mf/use-fn

View File

@@ -268,8 +268,8 @@
:on-click modal/hide!}
(tr "labels.cancel")]
[:> import-type-dropdown*
{:options [{:label (tr "workspace.tokens.import-menu-json-option") :value :file}
{:label (tr "workspace.tokens.import-menu-zip-option") :value :zip}
{:options [{:label (tr "workspace.tokens.import-menu-zip-option") :value :zip}
{:label (tr "workspace.tokens.import-menu-json-option") :value :file}
{:label (tr "workspace.tokens.import-menu-folder-option") :value :folder}]
:on-click handle-import-action
:text-render render-button-text

View File

@@ -13,11 +13,16 @@
[app.common.geom.shapes :as gsh]
[app.common.types.color :as clr]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn]
[app.common.types.path :as path]
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctt]
[app.common.types.shape.layout :as ctl]
[app.config :as cf]
[app.graph-wasm.api :as graph-wasm.api]
[app.main.data.workspace.componentize :as dwc]
[app.main.data.workspace.modifiers :as dwm]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.variants :as dwv]
[app.main.features :as features]
[app.main.refs :as refs]
@@ -57,8 +62,11 @@
[app.main.ui.workspace.viewport.utils :as utils]
[app.main.ui.workspace.viewport.viewport-ref :refer [create-viewport-ref]]
[app.main.ui.workspace.viewport.widgets :as widgets]
[app.main.worker :as worker]
[app.util.debug :as dbg]
[app.util.modules :as mod]
[beicon.v2.core :as rx]
[promesa.core :as p]
[rumext.v2 :as mf]))
;; --- Viewport
@@ -134,6 +142,7 @@
mod? (mf/use-state false)
space? (mf/use-state false)
z? (mf/use-state false)
g? (mf/use-state false)
cursor (mf/use-state #(utils/get-cursor :pointer-inner))
hover-ids (mf/use-state nil)
hover (mf/use-state nil)
@@ -302,12 +311,79 @@
(mf/use-fn
(mf/deps first-shape)
#(st/emit!
(dwv/add-new-variant (:id first-shape))))]
(dwv/add-new-variant (:id first-shape))))
graph-wasm-enabled? (features/use-feature "graph-wasm/v1")]
(mf/with-effect [page-id]
(when graph-wasm-enabled?
;; Initialize graph-wasm in the worker to avoid blocking main thread
(let [subscription
(->> (worker/ask! {:cmd :graph-wasm/init})
(rx/filter #(= (:status %) :ok))
(rx/take 1)
(rx/merge-map (fn [_]
(worker/ask! {:cmd :graph-wasm/set-objects
:objects base-objects}))))]
(rx/subscribe subscription
(fn [result]
(when (= (:status result) :ok)
(js/console.debug "Graph WASM initialized in worker"
(select-keys result [:processed]))))
(fn [error]
(js/console.error "Error initializing graph-wasm in worker:" error))
(fn []
(js/console.debug "Graph WASM worker operations completed"))))))
(mf/with-effect [selected @g?]
(when graph-wasm-enabled?
;; Search for similar shapes when selection changes or when
;; the user presses the \"c\" key while having a single
;; selection.
(when (and @g?
(some? selected)
(= (count selected) 1))
(let [selected-id (first selected)
selected-shape (get base-objects selected-id)
;; Skip shapes that already belong to a component
non-component? (and (some? selected-shape)
(not (ctn/in-any-component? base-objects selected-shape)))]
(println selected-shape)
(println (ctn/in-any-component? base-objects selected-shape))
(when non-component?
(let [subscription
(worker/ask! {:cmd :graph-wasm/search-similar-shapes
:shape-id selected-id})]
(rx/subscribe subscription
(fn [result]
(when (= (:status result) :ok)
(let [raw-similar-shapes (:similar-shapes result)
;; Filter out shapes that already belong to some component
;; (main instance, instance head or inside a component copy).
similar-shapes (->> raw-similar-shapes
(remove (fn [sid]
(when-let [s (get base-objects sid)]
(ctn/in-any-component? base-objects s))))
(into []))]
(when (d/not-empty? similar-shapes)
;; Transform the selected subtree into a component and
;; replace similar subtrees by instances of that component.
(st/emit! (dwc/componentize-similar-subtrees
selected-id
similar-shapes))))))
(fn [error]
(js/console.error "Error searching similar shapes:" error))
(fn []
(js/console.debug "Similar shapes search completed")))))))))
(hooks/setup-dom-events zoom disable-paste-ref in-viewport-ref read-only? drawing-tool path-drawing?)
(hooks/setup-viewport-size vport viewport-ref)
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool path-drawing? path-editing? z? read-only?)
(hooks/setup-keyboard alt? mod? space? z? shift?)
(hooks/setup-keyboard alt? mod? space? z? shift? g?)
(hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover measure-hover
hover-ids hover-top-frame-id @hover-disabled? focus zoom show-measures?)
(hooks/setup-viewport-modifiers modifiers base-objects)

View File

@@ -19,5 +19,5 @@
}
.threads {
position: absolute;
position: fixed;
}

View File

@@ -124,7 +124,7 @@
(reset! cursor new-cursor))))))
(defn setup-keyboard
[alt* mod* space* z* shift*]
[alt* mod* space* z* shift* g*]
(let [kbd-zoom-s
(mf/with-memo []
(->> ms/keyboard
@@ -151,12 +151,22 @@
(rx/filter kbd/z?)
(rx/filter (complement kbd/editing-event?))
(rx/map kbd/key-down-event?)
(rx/pipe (rxo/distinct-contiguous))))]
(rx/pipe (rxo/distinct-contiguous))))
kbd-g-s
(mf/with-memo []
(let [c-pred (kbd/is-key-ignore-case? "g")]
(->> ms/keyboard
(rx/filter c-pred)
(rx/filter (complement kbd/editing-event?))
(rx/map kbd/key-down-event?)
(rx/pipe (rxo/distinct-contiguous)))))]
(hooks/use-stream ms/keyboard-alt (partial reset! alt*))
(hooks/use-stream ms/keyboard-space (partial reset! space*))
(hooks/use-stream kbd-z-s (partial reset! z*))
(hooks/use-stream kbd-shift-s (partial reset! shift*))
(hooks/use-stream kbd-g-s (partial reset! g*))
(hooks/use-stream ms/keyboard-mod
(fn [value]
(reset! mod* value)

View File

@@ -122,6 +122,7 @@
mod? (mf/use-state false)
space? (mf/use-state false)
z? (mf/use-state false)
c? (mf/use-state false)
cursor (mf/use-state (utils/get-cursor :pointer-inner))
hover-ids (mf/use-state nil)
hover (mf/use-state nil)
@@ -360,7 +361,7 @@
(hooks/setup-dom-events zoom disable-paste-ref in-viewport-ref read-only? drawing-tool path-drawing?)
(hooks/setup-viewport-size vport viewport-ref)
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool path-drawing? path-editing? z? read-only?)
(hooks/setup-keyboard alt? mod? space? z? shift?)
(hooks/setup-keyboard alt? mod? space? z? shift? c?)
(hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover measure-hover
hover-ids hover-top-frame-id @hover-disabled? focus zoom show-measures?)
(hooks/setup-shortcuts path-editing? path-drawing? text-editing? grid-editing?)

View File

@@ -54,7 +54,6 @@
[app.plugins.ruler-guides :as rg]
[app.plugins.text :as text]
[app.plugins.utils :as u]
[app.util.http :as http]
[app.util.object :as obj]
[beicon.v2.core :as rx]
[cuerdas.core :as str]))
@@ -1196,12 +1195,7 @@
(js/Promise.
(fn [resolve reject]
(->> (rp/cmd! :export payload)
(rx/mapcat (fn [{:keys [uri]}]
(->> (http/send! {:method :get
:uri uri
:response-type :blob
:omit-default-headers true})
(rx/map :body))))
(rx/mapcat #(rp/cmd! :export {:cmd :get-resource :wait true :id (:id %) :blob? true}))
(rx/mapcat #(.arrayBuffer %))
(rx/map #(js/Uint8Array. %))
(rx/subs! resolve reject))))))))

View File

@@ -194,12 +194,7 @@
:addToken
(fn [type-str name value]
(let [type (cto/dtcg-token-type->token-type type-str)
value (case type
:font-family (ctob/convert-dtcg-font-family (js->clj value))
:typography (ctob/convert-dtcg-typography-composite (js->clj value))
:shadow (ctob/convert-dtcg-shadow-composite (js->clj value))
(js->clj value))]
(let [type (cto/dtcg-token-type->token-type type-str)]
(cond
(nil? type)
(u/display-not-valid :addTokenType type-str)

View File

@@ -18,7 +18,6 @@
[app.common.types.path :as path]
[app.common.types.path.impl :as path.impl]
[app.common.types.shape.layout :as ctl]
[app.common.types.text :as txt]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.refs :as refs]
@@ -965,8 +964,8 @@
(set-shape-children children)
(set-shape-corners corners)
(set-shape-blur blur)
(when (= type :group)
(set-masked (boolean masked)))
(when (and (= type :group) masked)
(set-masked masked))
(when (= type :bool)
(set-shape-bool-type bool-type))
(when (and (some? content)
@@ -1412,23 +1411,20 @@
(get span))
text (subs (:text element) start-pos end-pos)]
(d/patch-object
txt/default-text-attrs
(d/without-nils
{:x x
:y (+ y height)
:width width
:height height
:direction (dr/translate-direction direction)
:font-family (get element :font-family)
:font-size (get element :font-size)
:font-weight (get element :font-weight)
:text-transform (get element :text-transform)
:text-decoration (get element :text-decoration)
:letter-spacing (get element :letter-spacing)
:font-style (get element :font-style)
:fills (get element :fills)
:text text}))))))]
{:x x
:y (+ y height)
:width width
:height height
:direction (dr/translate-direction direction)
:font-family (get element :font-family)
:font-size (get element :font-size)
:font-weight (get element :font-weight)
:text-transform (get element :text-transform)
:text-decoration (get element :text-decoration)
:letter-spacing (get element :letter-spacing)
:font-style (get element :font-style)
:fills (get element :fills)
:text text}))))]
(mem/free)
result)))

View File

@@ -233,8 +233,8 @@
(api/set-shape-shadows (:shadow shape)))
:masked-group
(when (cfh/group-shape? shape)
(api/set-masked (boolean (:masked-group shape))))
(when (cfh/mask-shape? shape)
(api/set-masked (:masked-group shape)))
:content
(cond

View File

@@ -6,11 +6,11 @@
(ns app.util.code-highlight
(:require
["highlight.js" :as hljs]
["@penpot/hljs" :as hljs]
[app.util.dom :as dom]))
(defn highlight!
{:lazy-loadable true}
[node]
(dom/set-data! node "highlighted" nil)
(.highlightElement hljs/default node))
(hljs/highlightElement node))

View File

@@ -50,7 +50,6 @@
{:label "Føroyskt mál (community)" :value "fo"}
{:label "Korean (community)" :value "ko"}
{:label "עִבְרִית (community)" :value "he"}
{:label "आधुनिक मानक हिन्दी (community)" :value "hi"}
{:label "عربي/عربى (community)" :value "ar"}
{:label "فارسی (community)" :value "fa"}
{:label "日本語 (Community)" :value "ja_jp"}
@@ -70,6 +69,7 @@
(-> (.-language globals/navigator)
(parse-locale))))
;; Set initial translation loading state as globaly stored variable;
;; this facilitates hot reloading
(when-not (exists? (unchecked-get globals/global "penpotTranslations"))
@@ -93,8 +93,14 @@
(def ^:dynamic *current-locale*
(get-current))
(defonce locale
(l/atom *current-locale*))
(defonce state
(l/atom {:render 0 :locale *current-locale*}))
(defn- assign-current-locale
[state locale]
(-> state
(update :render inc)
(assoc :locale locale)))
(defn- get-translations
"Get globaly stored mutable object with all loaded translations"
@@ -108,10 +114,6 @@
(unchecked-set translations locale data)
nil))
(defn set-default-translations
[data]
(set-translations cf/default-language data))
(defn- load
[locale]
(let [path (str "./translation." locale ".js?version=" (:full cf/version))]
@@ -120,14 +122,15 @@
(p/fnly (fn [data cause]
(if cause
(js/console.error "unexpected error on fetching locale" cause)
(set-translations locale data)))))))
(do
(set! *current-locale* locale)
(set-translations locale data)
(swap! state assign-current-locale locale))))))))
(defn init
"Initialize the i18n module"
[]
(load *current-locale*)
(when-not (= *current-locale* cf/default-language)
(load cf/default-language)))
(load *current-locale*))
(defn set-locale
[lname]
@@ -142,10 +145,7 @@
(recur (rest locales)))
cf/default-language))))]
(->> (load lname)
(p/fnly (fn [_r _c]
(set! *current-locale* lname)
(reset! locale lname))))))
(load lname)))
(deftype C [val]
IDeref
@@ -206,7 +206,9 @@
:className class
:on-click on-click}]))
(add-watch locale "common.time"
(add-watch state "common.time"
(fn [_ _ pv cv]
(when (not= pv cv)
(ct/set-default-locale cv))))
(let [pv (get pv :locale)
cv (get cv :locale)]
(when (not= pv cv)
(ct/set-default-locale! cv)))))

View File

@@ -11,6 +11,7 @@
[app.common.schema :as sm]
[app.common.types.objects-map]
[app.util.object :as obj]
[app.worker.graph-wasm]
[app.worker.impl :as impl]
[app.worker.import]
[app.worker.index]

View File

@@ -0,0 +1,181 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) KALEIDOS INC
(ns app.worker.graph-wasm
"Graph WASM operations within the worker."
(:require
[app.common.data.macros :as dm]
[app.common.logging :as log]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.graph-wasm.wasm :as wasm]
[app.render-wasm.helpers :as h]
[app.render-wasm.serializers :as sr]
[app.worker.impl :as impl]
[beicon.v2.core :as rx]
[promesa.core :as p]))
(log/set-level! :info)
(defn- use-shape
[module id]
(let [buffer (uuid/get-u32 id)]
(h/call module "_use_shape"
(aget buffer 0)
(aget buffer 1)
(aget buffer 2)
(aget buffer 3))))
(defn- set-shape-parent-id
[module id]
(let [buffer (uuid/get-u32 id)]
(h/call module "_set_shape_parent"
(aget buffer 0)
(aget buffer 1)
(aget buffer 2)
(aget buffer 3))))
(defn- set-shape-type
[module type]
(h/call module "_set_shape_type" (sr/translate-shape-type type)))
(defn- set-shape-selrect
[module selrect]
(h/call module "_set_shape_selrect"
(dm/get-prop selrect :x1)
(dm/get-prop selrect :y1)
(dm/get-prop selrect :x2)
(dm/get-prop selrect :y2)))
(defn- set-object
[module shape]
(let [id (dm/get-prop shape :id)
type (dm/get-prop shape :type)
parent-id (get shape :parent-id)
selrect (get shape :selrect)]
(use-shape module id)
(set-shape-type module type)
(set-shape-parent-id module parent-id)
(set-shape-selrect module selrect)))
(defonce ^:private graph-wasm-module
(delay
(let [module (unchecked-get js/globalThis "GraphWasmModule")
init-fn (unchecked-get module "default")
href (cf/resolve-href "js/graph-wasm.wasm")]
(->> (init-fn #js {:locateFile (constantly href)})
(p/fnly (fn [module cause]
(if cause
(js/console.error cause)
(set! wasm/internal-module module))))))))
(defmethod impl/handler :graph-wasm/init
[message transfer]
(rx/create
(fn [subs]
(-> @graph-wasm-module
(p/then (fn [module]
(if module
(try
(h/call module "_init")
(rx/push! subs {:status :ok})
(rx/end! subs)
(catch :default cause
(log/error :hint "Error in graph-wasm/init" :cause cause)
(rx/error! subs cause)
(rx/end! subs)))
(do
(log/warn :hint "Graph WASM module not available")
(rx/push! subs {:status :error :message "Module not available"})
(rx/end! subs)))))
(p/catch (fn [cause]
(log/error :hint "Error loading graph-wasm module" :cause cause)
(rx/error! subs cause)
(rx/end! subs))))
nil)))
(defmethod impl/handler :graph-wasm/set-objects
[message transfer]
(let [objects (:objects message)]
(rx/create
(fn [subs]
(-> @graph-wasm-module
(p/then (fn [module]
(if module
(try
(doseq [shape (vals objects)]
(set-object module shape))
(h/call module "_generate_db")
(rx/push! subs {:status :ok :processed (count objects)})
(rx/end! subs)
(catch :default cause
(log/error :hint "Error in graph-wasm/set-objects" :cause cause)
(rx/error! subs cause)
(rx/end! subs)))
(do
(log/warn :hint "Graph WASM module not available")
(rx/push! subs {:status :error :message "Module not available"})
(rx/end! subs)))))
(p/catch (fn [cause]
(log/error :hint "Error loading graph-wasm module" :cause cause)
(rx/error! subs cause)
(rx/end! subs))))
nil))))
(defmethod impl/handler :graph-wasm/search-similar-shapes
[message transfer]
(let [shape-id (:shape-id message)]
(rx/create
(fn [subs]
(-> @graph-wasm-module
(p/then (fn [module]
(if module
(try
(let [buffer (uuid/get-u32 shape-id)
ptr-raw (h/call module "_search_similar_shapes"
(aget buffer 0)
(aget buffer 1)
(aget buffer 2)
(aget buffer 3))
;; Convert pointer to unsigned 32-bit (handle negative numbers from WASM)
;; Use unsigned right shift to convert signed to unsigned 32-bit
ptr (unsigned-bit-shift-right ptr-raw 0)
heapu8 (unchecked-get module "HEAPU8")
;; Read count (first 4 bytes, little-endian u32)
count (bit-or (aget heapu8 ptr)
(bit-shift-left (aget heapu8 (+ ptr 1)) 8)
(bit-shift-left (aget heapu8 (+ ptr 2)) 16)
(bit-shift-left (aget heapu8 (+ ptr 3)) 24))
;; Read UUIDs (16 bytes each, starting at offset 4)
similar-shapes (loop [offset (+ ptr 4)
remaining count
result []]
(if (zero? remaining)
result
(let [uuid-bytes (.slice heapu8 offset (+ offset 16))]
(recur (+ offset 16)
(dec remaining)
(conj result (uuid/from-bytes uuid-bytes))))))]
;; Free the buffer
(h/call module "_free_similar_shapes_buffer")
(rx/push! subs {:status :ok :similar-shapes similar-shapes})
(rx/end! subs))
(catch :default cause
(log/error :hint "Error in graph-wasm/search-similar-shapes" :cause cause)
(rx/error! subs cause)
(rx/end! subs)))
(do
(log/warn :hint "Graph WASM module not available")
(rx/push! subs {:status :error :message "Module not available"})
(rx/end! subs)))))
(p/catch (fn [cause]
(log/error :hint "Error loading graph-wasm module" :cause cause)
(rx/error! subs cause)
(rx/end! subs))))
nil))))

View File

@@ -47,3 +47,6 @@
result (svg-filters/apply-svg-filters shape)]
(is (= shape result))))

View File

@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"Last-Translator: Shuaib Zahda <shuaib.zahda@gmail.com>\n"
"PO-Revision-Date: 2025-11-22 10:51+0000\n"
"Last-Translator: jonnysemon <jonnysemon@users.noreply.hosted.weblate.org>\n"
"Language-Team: Arabic <https://hosted.weblate.org/projects/penpot/frontend/"
"ar/>\n"
"Language: ar\n"
@@ -10,7 +10,7 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 "
"&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n"
"X-Generator: Weblate 5.15.1\n"
"X-Generator: Weblate 5.15-dev\n"
#: src/app/main/ui/auth/register.cljs:215, src/app/main/ui/static.cljs:159, src/app/main/ui/viewer/login.cljs:100
msgid "auth.already-have-account"
@@ -402,10 +402,8 @@ msgstr ""
"أصولهم*؟"
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgstr ""
"سيتم ادراج الملفات التي لها مكتبات مشتركة في التصدير، مع الحفاظ على روابطهم."
msgstr "سيتم ادراج الملفات التي لها مكتبات مشتركة في التصدير، مع الحفاظ على روابطهم."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"

View File

@@ -1575,6 +1575,10 @@ msgstr ""
"Pokud se chcete dozvědět více o inspektorovi designu, navštivte centrum "
"nápovědy společnosti Penpot"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more-info"
msgstr "Více informací o inspektorovi"
#: src/app/main/ui/inspect/right_sidebar.cljs:232
msgid "inspect.empty.select"
msgstr ""

View File

@@ -1,19 +1,19 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"Last-Translator: Anonymous <noreply@weblate.org>\n"
"Language-Team: German <https://hosted.weblate.org/projects/penpot/frontend/"
"de/>\n"
"PO-Revision-Date: 2025-11-03 20:51+0000\n"
"Last-Translator: Stas Haas <stas@girafic.de>\n"
"Language-Team: German "
"<https://hosted.weblate.org/projects/penpot/frontend/de/>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.15.1\n"
"X-Generator: Weblate 5.14.1-dev\n"
#: src/app/main/ui/auth/register.cljs:215, src/app/main/ui/static.cljs:159, src/app/main/ui/viewer/login.cljs:100
msgid "auth.already-have-account"
msgstr "Sie haben bereits ein Konto?"
msgstr "Sie haben schon ein Konto?"
#: src/app/main/ui/auth/recovery_request.cljs:113, src/app/main/ui/auth/register.cljs:238
msgid "auth.check-mail"
@@ -584,11 +584,10 @@ msgstr ""
"machen?"
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgstr ""
"Dateien mit geteilten Bibliotheken werden exportiert, und ihre Verknüpfungen "
"bleiben erhalten."
"Dateien mit geteilten Bibliotheken werden exportiert, und ihre "
"Verknüpfungen bleiben erhalten."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
@@ -1800,6 +1799,10 @@ msgstr ""
"Für weitere Informationen zum Thema \"Auswerten von "
"Design-Spezifikationen\", besuchen Sie bitte das Penpot-Hilfezentrum"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more-info"
msgstr "Weitere Informationen zur Inspektion"
#: src/app/main/ui/inspect/right_sidebar.cljs:232
msgid "inspect.empty.select"
msgstr ""
@@ -1807,7 +1810,6 @@ msgstr ""
"Eigenschaften und Code zu überprüfen"
#: src/app/main/ui/inspect/right_sidebar.cljs:166
#, fuzzy
msgid "inspect.layer-info"
msgstr "Info zur Ebene"
@@ -2628,7 +2630,7 @@ msgstr "Eigene Schriftarten hochladen"
#: src/app/main/ui/dashboard/fonts.cljs:252
msgid "labels.uploading"
msgstr "Lädt hoch…"
msgstr "Hochladen…"
#: src/app/main/ui/inspect/right_sidebar.cljs:65, src/app/main/ui/workspace/sidebar/options/menus/component.cljs:1028
msgid "labels.variant"
@@ -3905,7 +3907,7 @@ msgstr "Kopieren"
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:96
msgid "shortcuts.copy-link"
msgstr "Link kopieren"
msgstr "Link in die Zwischenablage kopieren"
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:106
#, unused
@@ -6703,7 +6705,7 @@ msgstr "Als CSS kopieren (verschachtelte Ebenen)"
#: src/app/main/ui/workspace/context_menu.cljs:202
msgid "workspace.shape.menu.copy-link"
msgstr "Link kopieren"
msgstr "Link in die Zwischenablage kopieren"
#: src/app/main/ui/workspace/context_menu.cljs:215
msgid "workspace.shape.menu.copy-paste-as"
@@ -7502,662 +7504,3 @@ msgstr "Automatisch gespeicherte Versionen werden für %s Tage aufbewahrt."
#, unused
msgid "workspace.viewport.click-to-close-path"
msgstr "Klicken Sie, um den Pfad zu schließen"
#: src/app/main/ui/dashboard/team.cljs:765
msgid "dashboard.invitation-modal.delete"
msgstr "Diese Einladungen werden gelöscht:"
#: src/app/main/ui/dashboard/team.cljs:766
msgid "dashboard.invitation-modal.resend"
msgstr "Diese Einladungen werden erneut gesendet:"
#: src/app/main/ui/dashboard/team.cljs:933
msgid "team.invitations-selected"
msgid_plural "team.invitations-selected"
msgstr[0] "Eine Einladung ausgewählt"
msgstr[1] "%s Einladungen ausgewählt"
#: src/app/main/ui/workspace/tokens/themes/create_modal.cljs:121
msgid "workspace.tokens.themes-description"
msgstr ""
"Hier können Sie ihre Themes verwalten, ein- oder ausschalten und aktive Sets "
"auswählen."
#: src/app/main/ui/workspace/tokens/management.cljs:143
msgid "workspace.tokens.inactive-set"
msgstr "Inaktiv"
#: src/app/main/ui/workspace/tokens/themes/create_modal.cljs:174
msgid "workspace.tokens.no-active-sets"
msgstr "Keine Sets aktiviert"
#: src/app/main/ui/workspace/tokens/themes/create_modal.cljs:173
msgid "workspace.tokens.num-active-sets"
msgstr "%s aktivierte Sets"
#: src/app/main/ui/workspace/colorpicker/color_tokens.cljs:35
msgid "color-token.empty-state"
msgstr ""
"Keine Farb-Tokens vorhanden. Aktivieren sie Sets und/oder Themes oder fügen "
"Sie neue Tokens hinzu."
#: src/app/main/ui/ds/controls/numeric_input.cljs:99
msgid "ds.inputs.numeric-input.no-applicable-tokens"
msgstr ""
"Es sind keine passenden Tokens in aktivierten Sets oder Themes vorhanden."
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:41, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:98, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:105
msgid "ds.inputs.token-field.no-active-token-option"
msgstr "Dieser Token ist nicht Teil eines aktiven Sets oder ungültig."
#: src/app/main/errors.cljs:307
msgid "errors.deprecated"
msgstr ""
"Tut uns leid! Diese Datei beinhaltet veraltete Penpot-Assets und kann "
"deshalb nicht geöffnet werden."
#: src/app/main/errors.cljs:310
msgid "errors.deprecated.contact.after"
msgstr "damit wir Ihnen helfen können."
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:52, src/app/main/ui/workspace/tokens/management/create/form.cljs:80
msgid "errors.field-max-length"
msgstr "Darf maximal %s Zeichen enthalten."
#: src/app/main/ui/settings/feedback.cljs:143
msgid "feedback.other-ways-contact"
msgstr "Weitere Möglichkeiten uns zu kontaktieren"
#: src/app/main/ui/settings/feedback.cljs:122
msgid "feedback.description-placeholder"
msgstr "Bitte beschreiben Sie ihr Feedback"
#: src/app/main/ui/settings/feedback.cljs:126
msgid "feedback.penpot.link"
msgstr ""
"Wenn sich Ihr Feedback auf eine Datei oder Projekt bezieht, können Sie einen "
"Link hinzufügen:"
#: src/app/main/ui/settings/feedback.cljs:101
msgid "feedback.title-contact-us"
msgstr "Kontakt"
#: src/app/main/ui/settings/feedback.cljs:110, src/app/main/ui/settings/feedback.cljs:111
msgid "feedback.type"
msgstr "Art"
#: src/app/main/ui/settings/feedback.cljs:115
msgid "feedback.type.doubt"
msgstr "Bedenken"
#: src/app/main/ui/settings/feedback.cljs:113
msgid "feedback.type.idea"
msgstr "Idee"
#: src/app/main/ui/settings/feedback.cljs:114
msgid "feedback.type.issue"
msgstr "Problem"
#: src/app/main/ui/inspect/styles/rows/color_properties_row.cljs:120
msgid "inspect.attributes.image.preview"
msgstr "Bildvorschau"
#: src/app/main/ui/inspect/right_sidebar.cljs:170
msgid "inspect.color-space-label"
msgstr "Farbraum auswählen"
#: src/app/main/ui/inspect/styles/style_box.cljs:68
msgid "inspect.tabs.styles.copy-shorthand"
msgstr "CSS-Stile kopieren"
#: src/app/main/ui/inspect/styles/style_box.cljs:60, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:179
msgid "inspect.tabs.styles.toggle-style"
msgstr "%s-Schaltfläche ein-/ausblenden"
#: src/app/main/ui/inspect/styles/style_box.cljs:21
msgid "inspect.tabs.styles.token-panel"
msgstr "Token Sets und Themes"
#: src/app/main/ui/inspect/styles/rows/color_properties_row.cljs:102, src/app/main/ui/inspect/styles/rows/properties_row.cljs:60
msgid "inspect.tabs.styles.token-resolved-value"
msgstr "Festgelegter Wert:"
#: src/app/main/ui/inspect/styles/style_box.cljs:26
msgid "labels.blur"
msgstr "Weichzeichnen"
#: src/app/main/ui/dashboard/sidebar.cljs:1031
msgid "labels.community-contributions"
msgstr "Community & Beiträge"
#: src/app/main/ui/static.cljs:406
msgid "labels.contact-support"
msgstr "Support kontaktieren"
#: src/app/main/ui/settings/sidebar.cljs:136
msgid "labels.contact-us"
msgstr "Kontakt"
#: src/app/main/ui/static.cljs:68
msgid "labels.copyright-period"
msgstr "Kaleidos © 2019-heute"
#: src/app/main/ui/settings/feedback.cljs:134, src/app/main/ui/static.cljs:400
msgid "labels.download"
msgstr "%s herunterladen"
#: src/app/main/ui/inspect/styles/style_box.cljs:23
msgid "labels.fill"
msgstr "Fläche"
#: src/app/main/ui/dashboard/sidebar.cljs:1020
msgid "labels.help-learning"
msgstr "Hilfe & Lernen"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:667
msgid "labels.reference"
msgstr "Referenz"
#: src/app/main/ui/dashboard/team.cljs:788
msgid "labels.resend"
msgstr "Erneut senden"
#: src/app/main/ui/dashboard/sidebar.cljs:873
msgid "labels.version-notes"
msgstr "Hinweise zu Version %s"
#: src/app/main/ui/inspect/styles/style_box.cljs:32
msgid "labels.visibility"
msgstr "Sichtbarkeit"
#: src/app/main/ui/static.cljs:397
msgid "labels.internal-error.desc-message-second"
msgstr "Probieren Sie es erneut oder kontaktieren Sie unseren Support."
#: src/app/main/ui/ds/product/loader.cljs:26
msgid "loader.tips.04.title"
msgstr ""
#: src/app/main/ui/dashboard/team.cljs:825
msgid "notifications.invitation-deleted"
msgstr "Einladung erfolgreich widerrufen"
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:97
msgid "shortcuts.create-component-variant"
msgstr "Komponente / Variante erstellen"
#: src/app/main/ui/workspace/sidebar/assets/groups.cljs:81
msgid "workspace.assets.component-group-options"
msgstr "Optionen für diese Gruppe"
#: src/app/main/ui/workspace/colorpicker.cljs:427, src/app/main/ui/workspace/colorpicker.cljs:439
msgid "workspace.colorpicker.color-tokens"
msgstr "Farb-Tokens"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:499
msgid "workspace.component.swap.loop-error"
msgstr "Komponenten können nicht in sich selbst verschachtelt werden."
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:498
msgid "workspace.component.switch.loop-error-multi"
msgstr ""
"Einige Elemente konnten nicht ausgetauscht werden. Komponenten können nicht "
"in sich selbst verschachtelt werden."
#: src/app/main/ui/workspace/libraries.cljs:107, src/app/main/ui/workspace/libraries.cljs:133
msgid "workspace.libraries.colors"
msgid_plural "workspace.libraries.colors"
msgstr[0] "1 Farbe"
msgstr[1] "%s Farben"
#: src/app/main/ui/workspace/libraries.cljs:101, src/app/main/ui/workspace/libraries.cljs:125
msgid "workspace.libraries.components"
msgid_plural "workspace.libraries.components"
msgstr[0] "1 Komponente"
msgstr[1] "%s Komponenten"
#: src/app/main/ui/workspace/libraries.cljs:104, src/app/main/ui/workspace/libraries.cljs:129
msgid "workspace.libraries.graphics"
msgid_plural "workspace.libraries.graphics"
msgstr[0] "%s Grafik"
msgstr[1] "%s Grafiken"
#: src/app/main/ui/workspace/libraries.cljs:110, src/app/main/ui/workspace/libraries.cljs:137
msgid "workspace.libraries.typography"
msgid_plural "workspace.libraries.typography"
msgstr[0] "1 Textstil"
msgstr[1] "%s Textstile"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:1055
msgid "workspace.options.component.unlinked"
msgstr "Getrennt"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:1330
msgid "workspace.options.component.variant.duplicated.group.locate"
msgstr "Doppelte Varianten anzeigen"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:1327
msgid "workspace.options.component.variant.duplicated.group.title"
msgstr "Einige Varianten haben identische Eigenschaften und Werte"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:268
msgid "workspace.options.component.variant.duplicated.single.all"
msgstr ""
"Diese Varianten haben identische Eigenschaften und Werte. Geben Sie jeder "
"Variante einen eindeutigen Wert."
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:265
msgid "workspace.options.component.variant.duplicated.single.one"
msgstr ""
"Diese Variante hat die selben Eigenschaften order Werte wie eine andere "
"Variante. Geben Sie jeder Variante einen eindeutigen Wert."
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:271
msgid "workspace.options.component.variant.duplicated.single.some"
msgstr ""
"Einige dieser Varianten haben identische Eigenschaften und Werte. Geben Sie "
"jeder Variante einen eindeutigen Wert."
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:550
msgid "workspace.options.component.variant.malformed.copy"
msgstr ""
"Diese Komponente hat Varianten mit ungültigen Namen. Stellen Sie sicher, das "
"jede Variante korrekt benannt ist."
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:424
msgid "workspace.options.component.variant.malformed.structure.title"
msgstr "Versuchen Sie diesem Schema zu folgen:"
#: src/app/main/ui/workspace/sidebar/options/menus/variants_help_modal.cljs:54
msgid "workspace.options.component.variants-help-modal.intro"
msgstr ""
"Um Änderungen beizubehalten wenn Sie Varianten wechseln, verbindet Penpot "
"Ebenen die:"
#: src/app/main/ui/workspace/sidebar/options/menus/variants_help_modal.cljs:91
msgid "workspace.options.component.variants-help-modal.outro"
msgstr ""
"Jede Änderung (z.B. Ebenen umbenennen oder gruppieren) trennt die "
"Verbindung. Änderungen können rückgängig gemacht werden, um die Verbindung "
"wiederherzustellen."
#: src/app/main/ui/workspace/sidebar/options/menus/variants_help_modal.cljs:67
msgid "workspace.options.component.variants-help-modal.rule1"
msgstr "Den gleichen Namen haben."
#: src/app/main/ui/workspace/sidebar/options/menus/variants_help_modal.cljs:76
msgid "workspace.options.component.variants-help-modal.rule2"
msgstr "Vom gleichen Typ sind."
#: src/app/main/ui/workspace/sidebar/options/menus/variants_help_modal.cljs:77
msgid "workspace.options.component.variants-help-modal.rule2.detail"
msgstr ""
"Rechtecke, Ellipsen, Pfade und boolesche Operationen gelten als der gleiche "
"Typ."
#: src/app/main/data/workspace/tokens/library_edit.cljs:209, src/app/main/data/workspace/tokens/library_edit.cljs:452
msgid "workspace.tokens.duplicate-suffix"
msgstr "Kopie"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:581
msgid "workspace.tokens.edit-token"
msgstr "%s Token bearbeiten"
#: src/app/main/data/workspace/tokens/errors.cljs:41
msgid "workspace.tokens.empty-input"
msgstr "Der Token-Wert darf nicht leer sein"
#: src/app/main/data/workspace/tokens/errors.cljs:15
msgid "workspace.tokens.error-parse"
msgstr "Fehler beim Importieren. JSON konnte nicht verarbeitet werden."
#: src/app/main/ui/workspace/tokens/export/modal.cljs:49
msgid "workspace.tokens.export"
msgstr "Export"
#: src/app/main/ui/workspace/tokens/export/modal.cljs:125
msgid "workspace.tokens.export-tokens"
msgstr "Tokens Exportieren"
#: src/app/main/ui/workspace/tokens/export/modal.cljs:118
msgid "workspace.tokens.export.multiple-files"
msgstr "Mehrere Dateien"
#: src/app/main/ui/workspace/tokens/export/modal.cljs:38
msgid "workspace.tokens.export.no-tokens-themes-sets"
msgstr "Es sind keine Tokens, Themes oder Sets zum Exportieren vorhanden."
#: src/app/main/ui/workspace/tokens/export/modal.cljs:35
msgid "workspace.tokens.export.preview"
msgstr "Vorschau:"
#: src/app/main/ui/workspace/tokens/export/modal.cljs:116
msgid "workspace.tokens.export.single-file"
msgstr "Einzeln"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1339
msgid "workspace.tokens.font-size-value-enter"
msgstr "Schriftgröße oder {alias}"
#: src/app/main/data/workspace/tokens/application.cljs:323
msgid "workspace.tokens.font-variant-not-found"
msgstr ""
"Fehler beim Festlegen der Schriftstärke/des Schriftstils. Dieser Schriftstil "
"ist in der aktuellen Schriftart nicht vorhanden"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1328, src/app/main/ui/workspace/tokens/management/create/form.cljs:1343
msgid "workspace.tokens.font-weight-value-enter"
msgstr "Schriftstärke (300, Fett Kursiv) oder {alias}"
#: src/app/main/ui/workspace/tokens/management/context_menu.cljs:225
msgid "workspace.tokens.gaps"
msgstr "Zwischenräume"
#: src/app/main/ui/workspace/tokens/style_dictionary.cljs
#, unused
msgid "workspace.tokens.generic-error"
msgstr "Fehler: "
#: src/app/main/ui/workspace/tokens/themes/create_modal.cljs:129
msgid "workspace.tokens.group-name"
msgstr "Gruppenname"
#: src/app/main/ui/workspace/tokens/import/modal.cljs:233
msgid "workspace.tokens.import-button-prefix"
msgstr "%s importieren"
#: src/app/main/data/workspace/tokens/errors.cljs:32, src/app/main/data/workspace/tokens/errors.cljs:37
msgid "workspace.tokens.import-error"
msgstr "Fehler beim Import:"
#: src/app/main/ui/workspace/tokens/import/modal.cljs:273
msgid "workspace.tokens.import-menu-folder-option"
msgstr "Ordner"
#: src/app/main/ui/workspace/tokens/import/modal.cljs:272
msgid "workspace.tokens.import-menu-json-option"
msgstr "Einzelne JSON-Datei"
#: src/app/main/ui/workspace/tokens/import/modal.cljs:271
msgid "workspace.tokens.import-menu-zip-option"
msgstr "ZIP-Datei"
#: src/app/main/ui/workspace/tokens/import/modal.cljs:241
msgid "workspace.tokens.import-multiple-files"
msgstr ""
"Bei mehreren Dateien werden die Datei- oder Pfadnamen zum Benennen der Sets "
"verwendet."
#: src/app/main/ui/workspace/tokens/import/modal.cljs:240
msgid "workspace.tokens.import-single-file"
msgstr ""
"Bei einer einzelnen JSON-Datei sollten die Keys der ersten Ebene die Namen "
"der Token-Sets sein."
#: src/app/main/ui/workspace/tokens/import/modal.cljs:237
msgid "workspace.tokens.import-tokens"
msgstr "Token importieren"
#: src/app/main/ui/workspace/tokens/sidebar.cljs:414, src/app/main/ui/workspace/tokens/sidebar.cljs:415
#, unused
msgid "workspace.tokens.import-tooltip"
msgstr ""
"Beim Importieren einer JSON-Datei werden alle bestehenden Tokens, Sets und "
"Themes überschrieben"
#: src/app/main/ui/workspace/tokens/import/modal.cljs:247
msgid "workspace.tokens.import-warning"
msgstr ""
"Beim Import werden alle bestehenden Tokens, Sets und Themes überschrieben."
#: src/app/main/ui/workspace/tokens/management.cljs:134
msgid "workspace.tokens.inactive-set-description"
msgstr ""
"Dieses Set ist nicht aktiv. Ändern Sie das Theme oder aktivieren Sie dieses "
"Set, um Änderungen im Anzeigebereich sehen zu können"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:741
msgid "workspace.tokens.individual-tokens"
msgstr "Einzelne Token verwenden"
#: src/app/main/data/workspace/tokens/errors.cljs:49
msgid "workspace.tokens.invalid-color"
msgstr "Ungültiger Farbwert: %s"
#: src/app/main/data/workspace/tokens/errors.cljs:89
msgid "workspace.tokens.invalid-font-weight-token-value"
msgstr ""
"Ungültiger Wert für die Schriftstärke: Verwenden Sie numerische Werte (100"
"950) oder Standardbezeichnungen (dünn, leicht, normal, fett usw.), optional "
"gefolgt von „kursiv”"
#: src/app/main/data/workspace/tokens/errors.cljs:23
msgid "workspace.tokens.invalid-json"
msgstr "Fehler beim Importieren: Ihre JSON-Datei enthält ungültige Token-Daten."
#: src/app/main/data/workspace/tokens/errors.cljs:27
msgid "workspace.tokens.invalid-json-token-name"
msgstr "Fehler beim Importieren: Ihre JSON-Datei enthält ungültige Token-Namen."
#: src/app/main/data/workspace/tokens/errors.cljs:28
msgid "workspace.tokens.invalid-json-token-name-detail"
msgstr ""
"„%s“ ist kein gültiger Namen für ein Token.\n"
"Token-Namen dürfen nur Buchstaben und Ziffern enthalten, die durch Punkte "
"getrennt sind und dürfen nicht mit einem Dollarzeichen beginnen."
#: src/app/main/data/workspace/tokens/errors.cljs:101
msgid "workspace.tokens.invalid-shadow-type-token-value"
msgstr ""
"Ungültiger Schattentyp: Es werden nur „innerShadow” oder „dropShadow” "
"akzeptiert"
#: src/app/main/data/workspace/tokens/errors.cljs:81
msgid "workspace.tokens.invalid-text-case-token-value"
msgstr ""
"Ungültiger Token-Wert: Es werden nur „none“, „Uppercase“, „Lowercase“ oder „"
"Capitalize“ akzeptiert"
#: src/app/main/data/workspace/tokens/errors.cljs:85
msgid "workspace.tokens.invalid-text-decoration-token-value"
msgstr ""
"Ungültiger Token-Wert: Es werden nur „none“, „underline“ oder „strike-through"
"“ akzeptiert"
#: src/app/main/data/workspace/tokens/errors.cljs:93
msgid "workspace.tokens.invalid-token-value-typography"
msgstr ""
"Ungültiger Wert: Der Wert muss auf ein zusammengesetztes Typografie-Token "
"verweisen."
#: src/app/main/data/workspace/tokens/errors.cljs:61, src/app/main/data/workspace/tokens/errors.cljs:73, src/app/main/data/workspace/tokens/errors.cljs:77
msgid "workspace.tokens.invalid-value"
msgstr "Ungültiger Token-Wert: %s"
#: src/app/main/ui/workspace/tokens/themes/create_modal.cljs:229
msgid "workspace.tokens.label.group-placeholder"
msgstr "Gruppe hinzufügen (z. B. „Modus“)"
#: src/app/main/ui/workspace/tokens/themes/create_modal.cljs:236
msgid "workspace.tokens.label.theme-placeholder"
msgstr "Theme hinzufügen (z. B. „Hell“)"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1351
msgid "workspace.tokens.letter-spacing-value-enter-composite"
msgstr "Zeichenabstand oder {alias}"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1347
msgid "workspace.tokens.line-height-value-enter"
msgstr "Zeilenabstand (Multiplikator, px, %) oder {alias}"
#: src/app/main/ui/workspace/tokens/management/context_menu.cljs:217
msgid "workspace.tokens.margins"
msgstr "Abstände"
#: src/app/main/ui/workspace/tokens/management/context_menu.cljs:253
msgid "workspace.tokens.max-size"
msgstr "Maximale Größe"
#: src/app/main/ui/workspace/tokens/management/context_menu.cljs:247
msgid "workspace.tokens.min-size"
msgstr "Mindestgröße"
#: src/app/main/data/workspace/tokens/errors.cljs:57
msgid "workspace.tokens.missing-references"
msgstr "Fehlende Token-Referenzen: "
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:123
msgid "workspace.tokens.more-options"
msgstr "Rechtsklick, um Optionen anzuzeigen"
#: src/app/main/data/workspace/tokens/errors.cljs:19
msgid "workspace.tokens.no-token-files-found"
msgstr "In dieser Datei wurden keine Tokens, Sets oder Themen gefunden."
#: src/app/main/data/workspace/tokens/errors.cljs:53
msgid "workspace.tokens.number-too-large"
msgstr "Ungültiger Tokenwert. Der ermittelte Wert ist zu hoch: %s"
#: src/app/main/data/workspace/tokens/errors.cljs:73, src/app/main/data/workspace/tokens/warnings.cljs:15
msgid "workspace.tokens.opacity-range"
msgstr ""
"Die Deckkraft muss zwischen 0 und 100 % oder zwischen 0 und 1 liegen (z. B. "
"50 % oder 0,5)."
#: src/app/main/ui/workspace/tokens/management/context_menu.cljs:201
msgid "workspace.tokens.paddings"
msgstr "Innenabstände"
#: src/app/main/ui/workspace/tokens/management/context_menu.cljs:277
msgid "workspace.tokens.radius"
msgstr "Radius"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:775
msgid "workspace.tokens.reference-composite"
msgstr "Geben Sie einen Typografie-Alias für diesen Token ein"
#: src/app/main/ui/workspace/tokens/style_dictionary.cljs
#, unused
msgid "workspace.tokens.reference-error"
msgstr "Referenzfehler: "
#: src/app/main/data/workspace/tokens/errors.cljs:45, src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:55
msgid "workspace.tokens.self-reference"
msgstr "Der Token referenziert sich selbst"
#: src/app/main/ui/workspace/tokens/sets/lists.cljs:60
msgid "workspace.tokens.set-edit-placeholder"
msgstr "Namen eingeben (für Gruppen „/“ verwenden)"
#: src/app/main/ui/workspace/tokens/themes/create_modal.cljs:168
msgid "workspace.tokens.sets-hint"
msgstr "Theme bearbeiten und Sets verwalten"
#: src/app/main/ui/workspace/tokens/settings/menu.cljs:91
msgid "workspace.tokens.setting-description"
msgstr ""
"Hier können Sie die grundlegende Schriftgröße festlegen, die als Wert für "
"1rem verwendet wird:"
#: src/app/main/ui/workspace/tokens/settings/menu.cljs:84
msgid "workspace.tokens.settings"
msgstr "Token-Einstellungen"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1084
msgid "workspace.tokens.shadow-add-shadow"
msgstr "Schatten hinzufügen"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:981, src/app/main/ui/workspace/tokens/management/create/form.cljs:982
msgid "workspace.tokens.shadow-blur"
msgstr "Weichzeichnen"
#: src/app/main/data/workspace/tokens/errors.cljs:105
msgid "workspace.tokens.shadow-blur-range"
msgstr "Wert muss größer oder gleich 0 sein."
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:987, src/app/main/ui/workspace/tokens/management/create/form.cljs:988
msgid "workspace.tokens.shadow-color"
msgstr "Farbe"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:990, src/app/main/ui/workspace/tokens/management/create/form.cljs:991
msgid "workspace.tokens.shadow-inset"
msgstr "Innerer Schatten"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1091
msgid "workspace.tokens.shadow-remove-shadow"
msgstr "Schatten entfernen"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1215
msgid "workspace.tokens.shadow-title"
msgstr "Schatten"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:975, src/app/main/ui/workspace/tokens/management/create/form.cljs:976
msgid "workspace.tokens.shadow-x"
msgstr "X"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:978, src/app/main/ui/workspace/tokens/management/create/form.cljs:979
msgid "workspace.tokens.shadow-y"
msgstr "Y"
#: src/app/main/ui/workspace/tokens/management/context_menu.cljs:241
msgid "workspace.tokens.size"
msgstr "Größe"
#: src/app/main/data/workspace/tokens/errors.cljs:77, src/app/main/data/workspace/tokens/warnings.cljs:19
msgid "workspace.tokens.stroke-width-range"
msgstr "Die Rahmenbreite muss größer oder gleich 0 sein."
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1322, src/app/main/ui/workspace/tokens/management/create/form.cljs:1359
msgid "workspace.tokens.text-decoration-value-enter"
msgstr "none | underline | strike-through oder {alias}"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1316, src/app/main/ui/workspace/tokens/management/create/form.cljs:1355
msgid "workspace.tokens.text-case-value-enter"
msgstr "none | uppercase | lowercase | capitalize oder {alias}"
#: src/app/main/ui/workspace/tokens/themes/create_modal.cljs:52
msgid "workspace.tokens.theme-name-already-exists"
msgstr "Ein Theme mit diesem Namen existiert bereits"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1283, src/app/main/ui/workspace/tokens/management/create/form.cljs:1335
msgid "workspace.tokens.token-font-family-value-enter"
msgstr "Schriftfamilie oder eine durch Kommas (,) getrennte Liste von Schriften"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:44, src/app/main/ui/workspace/tokens/management/create/form.cljs:70
msgid "workspace.tokens.token-name-duplication-validation-error"
msgstr "Unter diesem Speicherort existiert bereits ein Token: %s"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:42, src/app/main/ui/workspace/tokens/management/create/form.cljs:68
msgid "workspace.tokens.token-name-length-validation-error"
msgstr "Der Name muss mindestens 1 Zeichen lang sein"
#: src/app/main/ui/workspace/tokens/style_dictionary.cljs:259
#, unused
msgid "workspace.tokens.token-not-resolved"
msgstr "Das Token mit dem Namen „%s” konnte nicht gefunden werden"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:185, src/app/main/ui/workspace/tokens/management/create/form.cljs:605
msgid "workspace.tokens.token-value-enter"
msgstr "Geben Sie einen Wert oder einen Alias mittels {alias} ein"
#: src/app/main/data/workspace/tokens/import_export.cljs:49
msgid "workspace.tokens.unknown-token-type-section"
msgstr "„%s“ wird nicht als Datentyp unterstützt (%s)\n"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:745
msgid "workspace.tokens.use-reference"
msgstr "Referenz verwenden"
#: src/app/main/data/workspace/tokens/errors.cljs:65
msgid "workspace.tokens.value-with-units"
msgstr "Ungültiger Wert: Einheiten sind hier nicht zulässig."
#: src/app/main/ui/workspace/top_toolbar.cljs:129
msgid "workspace.toolbar.frame-first-time"
msgstr ""
"Zeichenfläche erstellen. Zum Festlegen der Größe anklicken und ziehen. (%s)"
#, unused
msgid "workspace.versions.locked-by-other"
msgstr ""
"Diese Version ist durch %s gesperrt und kann gerade nicht bearbeitet werden"

View File

@@ -8421,111 +8421,3 @@ msgstr "Autosaved versions will be kept for %s days."
#, unused
msgid "workspace.viewport.click-to-close-path"
msgstr "Click to close the path"
msgid "dashboard.labels.recent"
msgstr "Recent"
msgid "dashboard.labels.deleted"
msgstr "Deleted"
msgid "dashboard.deleted.restore-all"
msgstr "Restore All"
msgid "dashboard.deleted.clear"
msgstr "Clear trash"
msgid "dashboard.restore-file"
msgstr "Restore file"
msgid "dashboard.delete-file"
msgstr "Delete file"
msgid "dashboard.deleted.restore-project"
msgstr "Restore project"
msgid "dashboard.deleted.delete-project"
msgstr "Delete project"
msgid "dashboard.deleted.info-text"
msgstr "Deleted files will remain in the trash for"
msgid "dashboard.deleted.info-days"
msgstr " %s days. "
msgid "dashboard.deleted.info-text2"
msgstr "After that, they will be permanently deleted."
msgid "dashboard.deleted.restore-text"
msgstr "If you change your mind, you can restore them or delete them permanently from each file's menu."
msgid "dashboard.deleted.delete-forever"
msgstr "Delete forever"
msgid "restore-modal.restore-all.title"
msgstr "Restore all projects and files"
msgid "restore-modal.restore-all.description"
msgstr "You're going to restore all your projects and files. This may take a while."
msgid "restore-modal.restore-file.title"
msgstr "Restore file"
msgid "restore-modal.restore-file.description"
msgstr "You're going to restore %s."
msgid "restore-modal.restore-project.title"
msgstr "Restore Project"
msgid "restore-modal.restore-project.description"
msgstr "You're going to restore %s project and all the files contained in it."
msgid "delete-forever-modal.title"
msgstr "Delete forever"
msgid "delete-forever-modal.delete-all.description"
msgstr "Are you sure you want to delete forever all your deleted projects and files? This is a non reversible action."
msgid "delete-forever-modal.delete-file.description"
msgstr "Are you sure you want to delete forever %s? This is a non reversible action."
msgid "delete-forever-modal.delete-project.description"
msgstr "Are you sure you want to delete forever %s project? You're going to delete it forever an all of the files contained in it. This is a non reeversible action."
msgid "restore-modal.success-restore-immediately"
msgstr "%s has been successfully restored."
msgid "delete-forever-modal.success-delete-immediately"
msgstr "%s has been successfully deleted."
msgid "restore-modal.error-restore-files"
msgstr "There was an error while restoring the files."
msgid "restore-modal.error-restore-file"
msgstr "There was an error while restoring the file %s."
msgid "restore-modal.error-restore-project"
msgstr "There was an error while restoring the project %s and its files."
msgid "restore-modal.normal-progress-label"
msgstr "Restoring files…"
msgid "restore-modal.failed-progress-label"
msgstr "Restore failed"
msgid "restore-modal.slow-progress-label"
msgstr "Restore unexpectedly slow"
msgid "restore-modal.complete-process-label"
msgstr "Restore completed"
msgid "progress-widget.default-normal-progress-label"
msgstr "Processing…"
msgid "progress-widget.default-failed-progress-label"
msgstr "Process failed"
msgid "progress-widget.default-slow-progress-label"
msgstr "Process unexpectedly slow"
msgid "progress-widget.default-complete-progress-label"
msgstr "Process completed"

View File

@@ -1,15 +1,15 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"Last-Translator: Anonymous <noreply@weblate.org>\n"
"Language-Team: Spanish <https://hosted.weblate.org/projects/penpot/frontend/"
"es/>\n"
"PO-Revision-Date: 2025-10-07 16:35+0000\n"
"Last-Translator: Deleted User <noreply+94857@weblate.org>\n"
"Language-Team: Spanish "
"<https://hosted.weblate.org/projects/penpot/frontend/es/>\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.15.1\n"
"X-Generator: Weblate 5.14-dev\n"
#: src/app/main/ui/auth/register.cljs:215, src/app/main/ui/static.cljs:159, src/app/main/ui/viewer/login.cljs:100
msgid "auth.already-have-account"
@@ -1843,7 +1843,6 @@ msgid "inspect.empty.select"
msgstr "Elige una forma, tablero o grupo para inspeccionar sus propiedades y código"
#: src/app/main/ui/inspect/right_sidebar.cljs:166
#, fuzzy
msgid "inspect.layer-info"
msgstr "Info. de capa"
@@ -8277,111 +8276,3 @@ msgstr "Los autoguardados duran %s días."
#, unused
msgid "workspace.viewport.click-to-close-path"
msgstr "Pulsar para cerrar la ruta"
msgid "dashboard.labels.recent"
msgstr "Recientes"
msgid "dashboard.labels.deleted"
msgstr "Eliminados"
msgid "dashboard.deleted.restore-all"
msgstr "Restaurar todo"
msgid "dashboard.deleted.clear"
msgstr "Vaciar papelera"
msgid "dashboard.restore-file"
msgstr "Restaurar archivo"
msgid "dashboard.delete-file"
msgstr "Eliminar archivo"
msgid "dashboard.deleted.restore-project"
msgstr "Restaurar proyecto"
msgid "dashboard.deleted.delete-project"
msgstr "Eliminar proyecto"
msgid "dashboard.deleted.info-text"
msgstr "Los archivos eliminados permanecerán en la papelera durante"
msgid "dashboard.deleted.info-days"
msgstr " %s días. "
msgid "dashboard.deleted.info-text2"
msgstr "Después de eso, serán eliminados permanentemente."
msgid "dashboard.deleted.restore-text"
msgstr "Si cambias de opinión, puedes restaurarlos o eliminarlos permanentemente desde el menú de cada archivo."
msgid "dashboard.deleted.delete-forever"
msgstr "Eliminar para siempre"
msgid "restore-modal.restore-all.title"
msgstr "Restaurar todos los proyectos y archivos"
msgid "restore-modal.restore-all.description"
msgstr "Vas a restaurar todos tus proyectos y archivos. Esto puede tardar un poco."
msgid "restore-modal.restore-file.title"
msgstr "Restaurar archivo"
msgid "restore-modal.restore-file.description"
msgstr "Vas a restaurar %s."
msgid "restore-modal.restore-project.title"
msgstr "Restaurar proyecto"
msgid "restore-modal.restore-project.description"
msgstr "Vas a restaurar el proyecto %s y todos los archivos que contiene."
msgid "delete-forever-modal.title"
msgstr "Eliminar para siempre"
msgid "delete-forever-modal.delete-all.description"
msgstr "¿Estás seguro de que quieres eliminar para siempre todos tus proyectos y archivos eliminados? Esta es una acción irreversible."
msgid "delete-forever-modal.delete-file.description"
msgstr "¿Estás seguro de que quieres eliminar para siempre %s? Esta es una acción irreversible."
msgid "delete-forever-modal.delete-project.description"
msgstr "¿Estás seguro de que quieres eliminar para siempre el proyecto %s? Vas a eliminarlo para siempre junto con todos los archivos que contiene. Esta es una acción irreversible."
msgid "restore-modal.success-restore-immediately"
msgstr "%s ha sido restaurado correctamente."
msgid "delete-forever-modal.success-delete-immediately"
msgstr "%s ha sido eliminado correctamente."
msgid "restore-modal.error-restore-files"
msgstr "Hubo un error al restaurar los archivos."
msgid "restore-modal.error-restore-file"
msgstr "Hubo un error al restaurar el archivo %s."
msgid "restore-modal.error-restore-project"
msgstr "Hubo un error al restaurar el proyecto %s y sus archivos."
msgid "restore-modal.normal-progress-label"
msgstr "Restaurando archivos…"
msgid "restore-modal.failed-progress-label"
msgstr "Falló la restauración"
msgid "restore-modal.slow-progress-label"
msgstr "Restauración lenta"
msgid "restore-modal.complete-process-label"
msgstr "Restauración completada"
msgid "progress-widget.default-normal-progress-label"
msgstr "Procesando…"
msgid "progress-widget.default-failed-progress-label"
msgstr "Falló el procesamiento"
msgid "progress-widget.default-slow-progress-label"
msgstr "Procesamiento lento"
msgid "progress-widget.default-complete-progress-label"
msgstr "Procesamiento completado"

View File

@@ -1,15 +1,15 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"Last-Translator: Yessenia Villarte Vaca <yesseniavillarte@gmail.com>\n"
"Language-Team: Spanish (Latin America) <https://hosted.weblate.org/projects/"
"penpot/frontend/es_419/>\n"
"PO-Revision-Date: 2024-06-17 08:07+0000\n"
"Last-Translator: Anonymous <noreply@weblate.org>\n"
"Language-Team: Spanish (Latin America) "
"<https://hosted.weblate.org/projects/penpot/frontend/es_419/>\n"
"Language: es_419\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.15.1\n"
"X-Generator: Weblate 5.6-dev\n"
#: src/app/main/ui/auth/register.cljs:215, src/app/main/ui/static.cljs:159, src/app/main/ui/viewer/login.cljs:100
msgid "auth.already-have-account"
@@ -454,7 +454,6 @@ msgstr ""
"¿Qué quiere hacer con sus activos*?"
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgstr ""
"Los archivos con bibliotecas compartidas se incluirán en la exportación, "

View File

@@ -1188,6 +1188,10 @@ msgstr ""
"Diseinua ikuskatzeari buruz gehiago jakin nahi baduzu zoaz Penpoten "
"laguntza zentrora"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more-info"
msgstr "Informazio gehiago ikuskatzeari buruz"
#: src/app/main/ui/inspect/right_sidebar.cljs:232
msgid "inspect.empty.select"
msgstr ""

View File

@@ -1,15 +1,15 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"PO-Revision-Date: 2025-11-10 07:51+0000\n"
"Last-Translator: Ahmad HosseinBor <123hozeifeh@gmail.com>\n"
"Language-Team: Persian <https://hosted.weblate.org/projects/penpot/frontend/"
"fa/>\n"
"Language-Team: Persian "
"<https://hosted.weblate.org/projects/penpot/frontend/fa/>\n"
"Language: fa\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.15.1\n"
"X-Generator: Weblate 5.15-dev\n"
#: src/app/main/ui/auth/register.cljs:215, src/app/main/ui/static.cljs:159, src/app/main/ui/viewer/login.cljs:100
msgid "auth.already-have-account"
@@ -501,11 +501,10 @@ msgstr ""
"می‌کنند. با دارایی‌های آن‌ها چه می‌خواهید بکنید*؟"
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgstr ""
"فایل‌های دارای کتابخانه‌های مشترک در اکسپورت گنجانده می‌شوند و پیوند خود را حفظ "
"می‌کنند."
"فایل‌های دارای کتابخانه‌های مشترک در اکسپورت گنجانده می‌شوند و پیوند خود را "
"حفظ می‌کنند."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"

View File

@@ -1,15 +1,15 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"Last-Translator: Anonymous <noreply@weblate.org>\n"
"Language-Team: French <https://hosted.weblate.org/projects/penpot/frontend/"
"fr/>\n"
"PO-Revision-Date: 2025-10-27 12:02+0000\n"
"Last-Translator: Ingrid Pigueron <ingridp.uxr@gmail.com>\n"
"Language-Team: French "
"<https://hosted.weblate.org/projects/penpot/frontend/fr/>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n!=1);\n"
"X-Generator: Weblate 5.15.1\n"
"X-Generator: Weblate 5.14.1-dev\n"
#: src/app/main/ui/auth/register.cljs:215, src/app/main/ui/static.cljs:159, src/app/main/ui/viewer/login.cljs:100
msgid "auth.already-have-account"
@@ -585,7 +585,6 @@ msgstr ""
"bibliothèques partagées. Que voulez-vous faire avec leurs ressources?"
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgstr ""
"Les fichiers avec des bibliothèques partagées seront inclus dans "
@@ -1841,6 +1840,10 @@ msgstr "Propriétés des variantes"
msgid "inspect.empty.help"
msgstr "Pour en savoir plus sur l'inspection, visitez le centre d'aide de Penpot"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more-info"
msgstr "Plus d'informations sur l'inspection"
#: src/app/main/ui/inspect/right_sidebar.cljs:232
msgid "inspect.empty.select"
msgstr ""
@@ -1848,7 +1851,6 @@ msgstr ""
"leurs propriétés et le code"
#: src/app/main/ui/inspect/right_sidebar.cljs:166
#, fuzzy
msgid "inspect.layer-info"
msgstr "Info sur la couche"
@@ -8226,86 +8228,3 @@ msgstr "Les versions auto-enregistrées seront gardées %s jours."
#, unused
msgid "workspace.viewport.click-to-close-path"
msgstr "Cliquez pour fermer le chemin"
#: src/app/main/ui/settings/feedback.cljs:122
msgid "feedback.description-placeholder"
msgstr "Décrivez la raison de votre commentaire"
#: src/app/main/ui/settings/feedback.cljs:143
msgid "feedback.other-ways-contact"
msgstr "Autres méthodes de contact"
#: src/app/main/ui/settings/feedback.cljs:126
msgid "feedback.penpot.link"
msgstr ""
"Si le commentaire concerne un fichier ou un projet, ajoutez le lien penpot "
"ici :"
#: src/app/main/ui/settings/feedback.cljs:110, src/app/main/ui/settings/feedback.cljs:111
#, fuzzy
msgid "feedback.type"
msgstr "Type"
#: src/app/main/ui/settings/feedback.cljs:113
msgid "feedback.type.idea"
msgstr "Idée"
#: src/app/main/ui/inspect/styles/rows/color_properties_row.cljs:120
msgid "inspect.attributes.image.preview"
msgstr "Aperçu de l'image de remplissage de la forme"
#: src/app/main/ui/static.cljs:68
msgid "labels.copyright-period"
msgstr "Kaleidos © depuis 2019"
#: src/app/main/ui/settings/feedback.cljs:134, src/app/main/ui/static.cljs:400
msgid "labels.download"
msgstr "Télécharger %s"
#: src/app/main/ui/dashboard/sidebar.cljs:1020
msgid "labels.help-learning"
msgstr "Aide et formation"
#: src/app/main/ui/static.cljs:396
msgid "labels.internal-error.desc-message-first"
msgstr "Un incident est survenu."
#: src/app/main/ui/static.cljs:397
msgid "labels.internal-error.desc-message-second"
msgstr ""
"Vous pouvez réessayer d'effectuer l'opération ou contacter l'assistance "
"technique pour signaler l'erreur."
#: src/app/main/ui/dashboard/sidebar.cljs:799
msgid "labels.learning-center"
msgstr "Centre de formation"
#: src/app/main/ui/settings/feedback.cljs:101
msgid "feedback.title-contact-us"
msgstr "Contact"
#: src/app/main/ui/inspect/right_sidebar.cljs:170
msgid "inspect.color-space-label"
msgstr "Sélectionner un espace de couleur"
#: src/app/main/ui/static.cljs:406
msgid "labels.contact-support"
msgstr "Contacter l'assistance technique"
#: src/app/main/ui/settings/sidebar.cljs:136
msgid "labels.contact-us"
msgstr "Contact"
#: src/app/main/ui/dashboard/subscription.cljs:83
msgid "subscription.dashboard.power-up.professional.bottom-description"
msgstr ""
"Bénéficiez de stockage supplémentaire, de la récupération de fichiers et de "
"bien plus encore pour vos équipes."
#: src/app/main/ui/dashboard/subscription.cljs:101
#, markdown
msgid "subscription.dashboard.power-up.unlimited.bottom-text"
msgstr ""
"Bénéficiez d'un stockage illimité, de la récupération de fichier étendue et "
"d'un nombre illimité d'éditeurs pour toutes vos équipes à un tarif fixe. "
"[Consultez le forfait Entreprise.|target:self](%s)"

View File

@@ -1244,6 +1244,10 @@ msgstr "manyan baqaqe"
msgid "inspect.empty.help"
msgstr "domin neman qarin bayani game da fenfot a tuntubi sashen agaji"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more-info"
msgstr "qarin bayani a fagen lura"
#: src/app/main/ui/inspect/right_sidebar.cljs:232
msgid "inspect.empty.select"
msgstr "zabar zubi, hukumar masu sa ido akan bangarorinsu da lambobinsu"

View File

@@ -1,16 +1,16 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"PO-Revision-Date: 2025-10-14 17:07+0000\n"
"Last-Translator: Yaron Shahrabani <sh.yaron@gmail.com>\n"
"Language-Team: Hebrew <https://hosted.weblate.org/projects/penpot/frontend/"
"he/>\n"
"Language-Team: Hebrew "
"<https://hosted.weblate.org/projects/penpot/frontend/he/>\n"
"Language: he\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=4; plural=(n == 1) ? 0 : ((n == 2) ? 1 : ((n > 10 && "
"n % 10 == 0) ? 2 : 3));\n"
"X-Generator: Weblate 5.15.1\n"
"X-Generator: Weblate 5.14-dev\n"
#: src/app/main/ui/auth/register.cljs:215, src/app/main/ui/static.cljs:159, src/app/main/ui/viewer/login.cljs:100
msgid "auth.already-have-account"
@@ -564,7 +564,6 @@ msgstr ""
"המשאבים שלהן*?"
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgstr "קבצים עם ספריות משותפות יצורפו לייצוא, תוך שימור הקישוריות שלהם."
@@ -1770,14 +1769,17 @@ msgstr "מאפייני הגוונים"
msgid "inspect.empty.help"
msgstr "למידע נוסף על חקירת עיצוב אפשר לבקר במרכז העזרה של Penpot"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more-info"
msgstr "מידע נוסף על חקירה"
#: src/app/main/ui/inspect/right_sidebar.cljs:232
msgid "inspect.empty.select"
msgstr "ניתן לבחור צורה, לוח או קבוצה ולראות את המאפיינים והקוד שלהם"
#: src/app/main/ui/inspect/right_sidebar.cljs:166
#, fuzzy
msgid "inspect.layer-info"
msgstr "בחירת לשונית חקירה"
msgstr "פרטי שכבה"
#: src/app/main/ui/inspect/right_sidebar.cljs:137
msgid "inspect.multiple-selected"
@@ -2192,7 +2194,7 @@ msgstr "בלתי פעיל"
#: src/app/main/ui/inspect/right_sidebar.cljs:114
msgid "labels.info"
msgstr "פירוט"
msgstr "מידע"
#: src/app/main/ui/dashboard/fonts.cljs:428
msgid "labels.installed-fonts"
@@ -4661,6 +4663,7 @@ msgid "subscription.settings.sucess.dialog.title"
msgstr "התוכנית שלך היא %s!"
#: src/app/main/ui/settings/subscription.cljs:440
#, fuzzy
msgid "subscription.settings.support-us-since"
msgstr "תמכת בנו עם התוכנית הזאת מאז: %s"
@@ -7580,7 +7583,7 @@ msgstr "הוספת ערכת עיצוב (למשל: בהירה)"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1351
msgid "workspace.tokens.letter-spacing-value-enter-composite"
msgstr "ריווח תווים או {alias}"
msgstr "הוספת ריווח תווים או {alias}"
#: src/app/main/ui/workspace/tokens/management/context_menu.cljs:217
msgid "workspace.tokens.margins"
@@ -7651,6 +7654,7 @@ msgid "workspace.tokens.opacity-range"
msgstr "שקיפות צריכה להיות בין 0 ל־100% או 0 ו־1 (כלומר 50% או 0.5)."
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:120
#, fuzzy
msgid "workspace.tokens.original-value"
msgstr "ערך מקורי: %s"
@@ -7676,6 +7680,7 @@ msgid "workspace.tokens.reference-error"
msgstr "שגיאות הפניה: "
#: src/app/main/data/workspace/tokens/warnings.cljs:15, src/app/main/data/workspace/tokens/warnings.cljs:19, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:56, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:84, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:102, src/app/main/ui/workspace/tokens/management/create/form_input_token.cljs:109, src/app/main/ui/workspace/tokens/management/create/input_tokens_value.cljs:41, src/app/main/ui/workspace/tokens/management/create/input_tokens_value.cljs:46, src/app/main/ui/workspace/tokens/management/token_pill.cljs:121
#, fuzzy
msgid "workspace.tokens.resolved-value"
msgstr "ערך פתור: %s"
@@ -7739,6 +7744,7 @@ msgid "workspace.tokens.themes-list"
msgstr "רשימת ערכות עיצוב"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:194, src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:195, src/app/main/ui/workspace/tokens/management/create/form.cljs:629, src/app/main/ui/workspace/tokens/management/create/form.cljs:630
#, fuzzy
msgid "workspace.tokens.token-description"
msgstr "תיאור"
@@ -8141,214 +8147,3 @@ msgstr "גרסאות שנשמרו אוטומטית תישמרנה למשך %s י
#, unused
msgid "workspace.viewport.click-to-close-path"
msgstr "לחיצה תסגור את הנתיב"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:52, src/app/main/ui/workspace/tokens/management/create/form.cljs:80
msgid "errors.field-max-length"
msgstr "חייב להכיל עד %s תווים."
#: src/app/main/ui/settings/feedback.cljs:122
msgid "feedback.description-placeholder"
msgstr "נא לתאר את הסיבה למשוב שלך"
#: src/app/main/ui/settings/feedback.cljs:143
msgid "feedback.other-ways-contact"
msgstr "דרכים אחרות ליצור איתנו קשר"
#: src/app/main/ui/settings/feedback.cljs:126
msgid "feedback.penpot.link"
msgstr ""
"אם המשוב הוא משהו שקשור לקובץ או למיזם, יש להוסיף את קישור ה־penpot לכאן:"
#: src/app/main/ui/settings/feedback.cljs:101
msgid "feedback.title-contact-us"
msgstr "ליצור איתנו קשר"
#: src/app/main/ui/settings/feedback.cljs:110, src/app/main/ui/settings/feedback.cljs:111
msgid "feedback.type"
msgstr "סוג"
#: src/app/main/ui/settings/feedback.cljs:115
msgid "feedback.type.doubt"
msgstr "ספק"
#: src/app/main/ui/settings/feedback.cljs:113
msgid "feedback.type.idea"
msgstr "רעיון"
#: src/app/main/ui/settings/feedback.cljs:114
msgid "feedback.type.issue"
msgstr "בעיה"
#: src/app/main/ui/inspect/styles/rows/color_properties_row.cljs:120
msgid "inspect.attributes.image.preview"
msgstr "תצוגה מקדימה של תמונת מילוי הצורה"
#: src/app/main/ui/inspect/right_sidebar.cljs:170
msgid "inspect.color-space-label"
msgstr "בחירת מרחב צבע"
#: src/app/main/ui/inspect/styles/style_box.cljs:68
msgid "inspect.tabs.styles.copy-shorthand"
msgstr "העתקת קיצור CSS ללוח הגזירים"
#: src/app/main/ui/static.cljs:406
msgid "labels.contact-support"
msgstr "יצירת קשר עם התמיכה"
#: src/app/main/ui/settings/sidebar.cljs:136
msgid "labels.contact-us"
msgstr "יצירת קשר איתנו"
#: src/app/main/ui/static.cljs:68
msgid "labels.copyright-period"
msgstr "Kaleidos ‏© 2019-עכשיו"
#: src/app/main/ui/settings/feedback.cljs:134, src/app/main/ui/static.cljs:400
msgid "labels.download"
msgstr "הורדת %s"
#: src/app/main/ui/static.cljs:396
msgid "labels.internal-error.desc-message-first"
msgstr "משהו גרוע התרחש."
#: src/app/main/ui/static.cljs:397
msgid "labels.internal-error.desc-message-second"
msgstr ""
"אפשר לנסות לבצע את הפעולה שוב או ליצור קשר עם התמיכה כדי לדווח על השגיאה."
#: src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs:229
msgid "labels.switch"
msgstr "החלפה"
#: src/app/main/ui/dashboard/subscription.cljs:84
msgid "subscription.dashboard.power-up.professional.bottom-button"
msgstr "עליית מדרגה!"
#: src/app/main/ui/dashboard/subscription.cljs:83
msgid "subscription.dashboard.power-up.professional.bottom-description"
msgstr "קבלת אחסון נוסף, שחזור קבצים ועוד למען הצוותים שלך."
msgid "subscription.settings.management-dialog.step-2-title"
msgstr "נשמח לסיוע בצמיחה ולהפוך את ההתנסות שלך לפשוטה יותר"
msgid "subscription.settings.management-dialog.step-2-skip-button"
msgstr "לדלג לבינתיים ולהתחיל בהתנסות"
msgid "subscription.settings.management-dialog.step-2-add-payment-button"
msgstr "הוספת פרטי תשלום"
#: src/app/main/ui/settings/subscription.cljs:209
msgid "subscription.settings.management.dialog.unlimited-capped-warning"
msgstr ""
"עצה: אפשר להגדיל את כמות המושבים שלך כעת ולהקדים את ההזמנות. עם יותר מ־25 "
"עורכים על פני הצוותים, אנו מציעים 175$ לחודש בלי חיובים נוספים."
#: src/app/main/ui/settings/subscription.cljs:50
msgid "subscription.settings.recommended"
msgstr "מומלץ"
#: src/app/main/ui/dashboard/team.cljs:933
msgid "team.invitations-selected"
msgid_plural "team.invitations-selected"
msgstr[0] "הזמנה נבחרה"
msgstr[1] "שתי הזמנות נבחרו"
msgstr[2] "%s הזמנות נבחרו"
msgstr[3] "%s הזמנות נבחרו"
#: src/app/main/ui/workspace/libraries.cljs:110, src/app/main/ui/workspace/libraries.cljs:137
msgid "workspace.libraries.typography"
msgid_plural "workspace.libraries.typography"
msgstr[0] "טיפוגרפיה אחת"
msgstr[1] "שתי טיפוגרפיות"
msgstr[2] "%s טיפוגרפיות"
msgstr[3] "%s טיפוגרפיות"
#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs:264
msgid "workspace.options.more-token-colors"
msgstr "עוד אסימוני צבע"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1339
msgid "workspace.tokens.font-size-value-enter"
msgstr "גודל גופן או {כינוי}"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1328, src/app/main/ui/workspace/tokens/management/create/form.cljs:1343
msgid "workspace.tokens.font-weight-value-enter"
msgstr "משקל גופן (300, מודגש נטוי…) או {כינוי}"
#: src/app/main/data/workspace/tokens/errors.cljs:89
msgid "workspace.tokens.invalid-font-weight-token-value"
msgstr ""
"ערך משקל גופן שגוי: יש להשתמש בערכים מספריים (100-950) או שמות תקניים (thin, "
"light, regular, bold ועוד), אפשר גם לצרף בסוף Italic (נטוי) במקרה הצורך"
#: src/app/main/data/workspace/tokens/errors.cljs:101
msgid "workspace.tokens.invalid-shadow-type-token-value"
msgstr "סוג הצללה שגוי: רק innerShadow או dropShadow מורשים"
#: src/app/main/data/workspace/tokens/errors.cljs:81
msgid "workspace.tokens.invalid-text-case-token-value"
msgstr "ערך אסימון שגוי: רק none, Uppercase, Lowercase או Capitalize מורשים"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1347
msgid "workspace.tokens.line-height-value-enter"
msgstr "גובה שורה (מכפיל, פיקסלים, %) או {כינוי}"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1084
msgid "workspace.tokens.shadow-add-shadow"
msgstr "הוספת הצללה"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:981, src/app/main/ui/workspace/tokens/management/create/form.cljs:982
msgid "workspace.tokens.shadow-blur"
msgstr "טשטוש"
#: src/app/main/data/workspace/tokens/errors.cljs:105
msgid "workspace.tokens.shadow-blur-range"
msgstr "טשטוש הצל חייב להיות גדול או שווה ל־0."
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:987, src/app/main/ui/workspace/tokens/management/create/form.cljs:988
msgid "workspace.tokens.shadow-color"
msgstr "צבע"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:990, src/app/main/ui/workspace/tokens/management/create/form.cljs:991
msgid "workspace.tokens.shadow-inset"
msgstr "כיווץ פנימה"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1091
msgid "workspace.tokens.shadow-remove-shadow"
msgstr "הסרת הצללה"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:984, src/app/main/ui/workspace/tokens/management/create/form.cljs:985
msgid "workspace.tokens.shadow-spread"
msgstr "התפרסות"
#: src/app/main/data/workspace/tokens/errors.cljs:109
msgid "workspace.tokens.shadow-spread-range"
msgstr "התפרסות ההצללה חייב להיות גדולה או שווה ל־0."
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1215
msgid "workspace.tokens.shadow-title"
msgstr "הצללות"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:975, src/app/main/ui/workspace/tokens/management/create/form.cljs:976
msgid "workspace.tokens.shadow-x"
msgstr "X"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:978, src/app/main/ui/workspace/tokens/management/create/form.cljs:979
msgid "workspace.tokens.shadow-y"
msgstr "Y"
#: src/app/main/ui/workspace/tokens/themes/create_modal.cljs:52
msgid "workspace.tokens.theme-name-already-exists"
msgstr "כבר קיימת ערכת עיצוב בשם הזה"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:44, src/app/main/ui/workspace/tokens/management/create/form.cljs:70
msgid "workspace.tokens.token-name-duplication-validation-error"
msgstr "כבר קיים אסימון בנתיב: %s"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:42, src/app/main/ui/workspace/tokens/management/create/form.cljs:68
msgid "workspace.tokens.token-name-length-validation-error"
msgstr "אורך השם חייב להיות תו אחד לפחות"
#: src/app/main/data/workspace/tokens/errors.cljs:85
msgid "workspace.tokens.invalid-text-decoration-token-value"
msgstr "ערך אסימון שגוי: מותר רק none, underline ו־strike-through"

View File

@@ -1,15 +1,15 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"PO-Revision-Date: 2025-10-13 09:26+0000\n"
"Last-Translator: VKing9 <vaibhavrathod2282@gmail.com>\n"
"Language-Team: Hindi <https://hosted.weblate.org/projects/penpot/frontend/"
"hi/>\n"
"Language-Team: Hindi "
"<https://hosted.weblate.org/projects/penpot/frontend/hi/>\n"
"Language: hi\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n > 1;\n"
"X-Generator: Weblate 5.15.1\n"
"X-Generator: Weblate 5.14-dev\n"
#: src/app/main/ui/auth/register.cljs:215, src/app/main/ui/static.cljs:159, src/app/main/ui/viewer/login.cljs:100
msgid "auth.already-have-account"
@@ -569,11 +569,10 @@ msgstr ""
"लाइब्रेरीज़ का उपयोग कर रही हैं। आप उनके एसेट्स के साथ क्या करना चाहते हैं?"
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgstr ""
"साझा की गई लाइब्रेरीज़ वाली फ़ाइलें निर्यात में शामिल की जाएँगी, और उनका लिंक बनाए रखा "
"जाएगा।"
"साझा की गई लाइब्रेरीज़ वाली फ़ाइलें निर्यात में शामिल की जाएँगी, और उनका "
"लिंक बनाए रखा जाएगा।"
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
@@ -1727,6 +1726,10 @@ msgstr ""
"यदि आप डिजाइन निरीक्षण के बारे में अधिक जानना चाहते हैं, तो कृपया पेनपॉट के "
"हेल्प सेंटर पर जाएं"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more-info"
msgstr "निरीक्षण के बारे में अधिक जानकारी"
#: src/app/main/ui/inspect/right_sidebar.cljs:232
msgid "inspect.empty.select"
msgstr "उनके गुणधर्म और कोड का निरीक्षण करने के लिए कोई आकृति, बोर्ड या समूह चुनें"
@@ -4318,7 +4321,7 @@ msgstr ""
#: src/app/main/ui/settings/subscription.cljs:202
msgid "subscription.settings.management.dialog.payment-explanation"
msgstr "परीक्षण के बाद शुल्क लिया जाएगा। अभी क्रेडिट कार्ड की आवश्यकता नहीं है।"
msgstr "(अभी कोई भुगतान नहीं किया जाएगा)"
#: src/app/main/ui/settings/subscription.cljs:195, src/app/main/ui/settings/subscription.cljs:199
#, markdown
@@ -4380,8 +4383,9 @@ msgid "subscription.settings.sucess.dialog.title"
msgstr "आप %s हैं!"
#: src/app/main/ui/settings/subscription.cljs:440
#, fuzzy
msgid "subscription.settings.support-us-since"
msgstr "आप इस योजना में हमारा समर्थन तब से कर रहे हैं: %s"
msgstr "आप इस योजना के साथ %s से हमारा समर्थन कर रहे हैं"
#: src/app/main/ui/settings/subscription.cljs:472, src/app/main/ui/settings/subscription.cljs:488
msgid "subscription.settings.try-it-free"
@@ -7165,6 +7169,7 @@ msgid "workspace.tokens.opacity-range"
msgstr "अपारदर्शिता 0 और 100% या 0 और 1 (जैसे 50% या 0.5) के बीच होनी चाहिए।"
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:120
#, fuzzy
msgid "workspace.tokens.original-value"
msgstr "मूल मान: %s"
@@ -7186,8 +7191,9 @@ msgid "workspace.tokens.reference-error"
msgstr "संदर्भ त्रुटियाँ: "
#: src/app/main/data/workspace/tokens/warnings.cljs:15, src/app/main/data/workspace/tokens/warnings.cljs:19, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:56, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:84, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:102, src/app/main/ui/workspace/tokens/management/create/form_input_token.cljs:109, src/app/main/ui/workspace/tokens/management/create/input_tokens_value.cljs:41, src/app/main/ui/workspace/tokens/management/create/input_tokens_value.cljs:46, src/app/main/ui/workspace/tokens/management/token_pill.cljs:121
#, fuzzy
msgid "workspace.tokens.resolved-value"
msgstr "हल किया गया मान: %s"
msgstr "समाधानित मान: %s"
#: src/app/main/ui/workspace/tokens/themes/create_modal.cljs:272
msgid "workspace.tokens.save-theme"
@@ -7253,6 +7259,7 @@ msgid "workspace.tokens.themes-list"
msgstr "थीम्स सूची"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:194, src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:195, src/app/main/ui/workspace/tokens/management/create/form.cljs:629, src/app/main/ui/workspace/tokens/management/create/form.cljs:630
#, fuzzy
msgid "workspace.tokens.token-description"
msgstr "वर्णन"
@@ -7621,826 +7628,3 @@ msgstr "स्वतः सहेजे गए संस्करण %s दि
#, unused
msgid "workspace.viewport.click-to-close-path"
msgstr "पथ बंद करने के लिए क्लिक करें"
#: src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:100, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:107
msgid "color-row.token-color-row.deleted-token"
msgstr "यह token मौजूद नहीं है या हटा दिया गया है।"
#: src/app/main/ui/workspace/colorpicker/color_tokens.cljs:35
msgid "color-token.empty-state"
msgstr "कोई रंग tokens उपलब्ध नहीं है। सक्रिय सेट/थीम देखें या नए टोकन जोड़ें।"
#: src/app/main/ui/dashboard/team.cljs:765
msgid "dashboard.invitation-modal.delete"
msgstr "आप निम्न आमंत्रणों को हटाने जा रहे हैं:"
#: src/app/main/ui/dashboard/team.cljs:766
msgid "dashboard.invitation-modal.resend"
msgstr "आप निम्नलिखित को पुनः निमंत्रण भेजने जा रहे हैं:"
#: src/app/main/ui/dashboard/team.cljs:756
msgid "dashboard.invitation-modal.title.delete-invitations"
msgstr "निमंत्रण हटाएँ"
#: src/app/main/ui/dashboard/team.cljs:757
msgid "dashboard.invitation-modal.title.resend-invitations"
msgstr "निमंत्रण पुनः भेजें"
#: src/app/main/ui/dashboard/team.cljs:949
msgid "dashboard.order-invitations-by-role"
msgstr "भूमिका के अनुसार क्रमबद्ध करें"
#: src/app/main/ui/dashboard/team.cljs:958
msgid "dashboard.order-invitations-by-status"
msgstr "स्थिति के अनुसार क्रमबद्ध करें"
#: src/app/main/ui/ds/controls/numeric_input.cljs:99
msgid "ds.inputs.numeric-input.no-applicable-tokens"
msgstr "सक्रिय सेट या थीम में कोई लागू tokens नहीं।"
#: src/app/main/ui/ds/controls/numeric_input.cljs:100
msgid "ds.inputs.numeric-input.no-matches"
msgstr "कोई मेल नहीं मिले।"
#: src/app/main/ui/ds/controls/numeric_input.cljs:650, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:140
msgid "ds.inputs.numeric-input.open-token-list-dropdown"
msgstr "token सूची खोलें"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:87, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:135
msgid "ds.inputs.token-field.detach-token"
msgstr "token अलग करें"
#: src/app/main/ui/ds/controls/utilities/token_field.cljs:41, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:98, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:105
msgid "ds.inputs.token-field.no-active-token-option"
msgstr "यह token किसी भी सक्रिय सेट में नहीं है या इसका मान अमान्य है।"
#: src/app/main/ui/auth/register.cljs:89
msgid "errors.email-does-not-match-invitation"
msgstr "ईमेल आमंत्रण से मेल नहीं खाता।"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:52, src/app/main/ui/workspace/tokens/management/create/form.cljs:80
msgid "errors.field-max-length"
msgstr "इसमें अधिकतम %s वर्ण होने चाहिए।"
#: src/app/main/ui/dashboard/team.cljs:853
msgid "errors.max-quote-reached"
msgstr "|"
#: src/app/main/errors.cljs:167
msgid "errors.only-creator-can-lock"
msgstr "केवल संस्करण निर्माता ही इसे लॉक कर सकता है"
#: src/app/main/errors.cljs:175
msgid "errors.only-creator-can-unlock"
msgstr "केवल संस्करण निर्माता ही इसे अनलॉक कर सकता है"
#: src/app/main/errors.cljs:183
msgid "errors.version-already-locked"
msgstr "यह संस्करण पहले से ही लॉक है"
#: src/app/main/errors.cljs:159
msgid "errors.version-locked"
msgstr "यह संस्करण लॉक है और इसे अन्य लोग हटा नहीं सकते"
#: src/app/main/ui/settings/feedback.cljs:122
msgid "feedback.description-placeholder"
msgstr "कृपया अपनी प्रतिक्रिया का कारण बताएँ"
#: src/app/main/ui/settings/feedback.cljs:143
msgid "feedback.other-ways-contact"
msgstr "हमसे संपर्क करने के अन्य तरीके"
#: src/app/main/ui/settings/feedback.cljs:126
msgid "feedback.penpot.link"
msgstr ""
"यदि फीडबैक किसी फ़ाइल या प्रोजेक्ट से संबंधित है, तो यहां पेनपॉट लिंक जोड़ें:"
#: src/app/main/ui/settings/feedback.cljs:101
msgid "feedback.title-contact-us"
msgstr "हमसे संपर्क करें"
#: src/app/main/ui/settings/feedback.cljs:110, src/app/main/ui/settings/feedback.cljs:111
msgid "feedback.type"
msgstr "प्रकार"
#: src/app/main/ui/settings/feedback.cljs:115
msgid "feedback.type.doubt"
msgstr "संदेह"
#: src/app/main/ui/settings/feedback.cljs:113
msgid "feedback.type.idea"
msgstr "विचार"
#: src/app/main/ui/settings/feedback.cljs:114
msgid "feedback.type.issue"
msgstr "मुद्दा"
#: src/app/main/ui/inspect/styles/rows/color_properties_row.cljs:120
msgid "inspect.attributes.image.preview"
msgstr "आकृति की भरण छवि का पूर्वावलोकन"
#, unused
msgid "inspect.attributes.typography.text-decoration.line-through"
msgstr "स्ट्राइकथ्रू"
#: src/app/main/ui/inspect/attributes/text.cljs:125, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs:429
msgid "inspect.attributes.typography.text-transform.capitalize"
msgstr "प्रमुख अक्षर करना"
#: src/app/main/ui/inspect/right_sidebar.cljs:170
msgid "inspect.color-space-label"
msgstr "रंग स्थान चुनें"
#: src/app/main/ui/inspect/right_sidebar.cljs:166
#, fuzzy
msgid "inspect.layer-info"
msgstr "निरीक्षण टैब चुनें"
#: src/app/main/ui/inspect/styles/panels/tokens_panel.cljs:26
msgid "inspect.tabs.styles.active-sets"
msgstr "सक्रिय सेट"
#: src/app/main/ui/inspect/styles/panels/tokens_panel.cljs:21
msgid "inspect.tabs.styles.active-themes"
msgstr "सक्रिय थीम"
#: src/app/main/ui/inspect/styles/style_box.cljs:68
msgid "inspect.tabs.styles.copy-shorthand"
msgstr "CSS शॉर्टहैंड को क्लिपबोर्ड पर कॉपी करें"
#: src/app/main/ui/inspect/styles/property_detail_copiable.cljs:51
msgid "inspect.tabs.styles.copy-to-clipboard"
msgstr "क्लिपबोर्ड पर कॉपी करें"
#: src/app/main/ui/inspect/styles/style_box.cljs:22
msgid "inspect.tabs.styles.geometry-panel"
msgstr "आकार & स्थिति"
#: src/app/main/ui/inspect/styles/style_box.cljs:60, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:179
msgid "inspect.tabs.styles.toggle-style"
msgstr "टॉगल पैनल %s"
#: src/app/main/ui/inspect/styles/style_box.cljs:21
msgid "inspect.tabs.styles.token-panel"
msgstr "Token सेट और थीम"
#: src/app/main/ui/inspect/styles/rows/color_properties_row.cljs:102, src/app/main/ui/inspect/styles/rows/properties_row.cljs:60
msgid "inspect.tabs.styles.token-resolved-value"
msgstr "हल किया गया मान:"
#: src/app/main/ui/inspect/styles/style_box.cljs:20
msgid "inspect.tabs.styles.variants-panel"
msgstr "भिन्न गुण"
#: src/app/main/ui/dashboard/sidebar.cljs:1044
msgid "labels.about-penpot"
msgstr "पेनपोट के बारे में"
#: src/app/main/ui/inspect/styles/style_box.cljs:26
msgid "labels.blur"
msgstr "धुंधला"
#: src/app/main/ui/workspace/colorpicker.cljs:423
msgid "labels.color"
msgstr "रंग"
#: src/app/main/ui/dashboard/sidebar.cljs:1031
msgid "labels.community-contributions"
msgstr "समुदाय & योगदान"
#: src/app/main/ui/inspect/right_sidebar.cljs:109
msgid "labels.computed"
msgstr "परिकलित"
#: src/app/main/ui/static.cljs:406
msgid "labels.contact-support"
msgstr "समर्थन से संपर्क करें"
#: src/app/main/ui/settings/sidebar.cljs:136
msgid "labels.contact-us"
msgstr "हमसे संपर्क करें"
#: src/app/main/ui/static.cljs:68
msgid "labels.copyright-period"
msgstr "कैलिडोस © 2019-वर्तमान"
#: src/app/main/ui/settings/feedback.cljs:134, src/app/main/ui/static.cljs:400
msgid "labels.download"
msgstr "%s डाउनलोड करें"
#: src/app/main/ui/inspect/styles/style_box.cljs:23
msgid "labels.fill"
msgstr "भरना"
#: src/app/main/ui/dashboard/sidebar.cljs:1020
msgid "labels.help-learning"
msgstr "मदद & सीखना"
#: src/app/main/ui/static.cljs:396
msgid "labels.internal-error.desc-message-first"
msgstr "कुछ बुरा हुआ।"
#: src/app/main/ui/static.cljs:397
msgid "labels.internal-error.desc-message-second"
msgstr ""
"आप ऑपरेशन पुनः प्रयास कर सकते हैं या त्रुटि की रिपोर्ट करने के लिए समर्थन से संपर्क कर सकते हैं"
"।"
#: src/app/main/ui/inspect/styles/style_box.cljs:28
msgid "labels.layout"
msgstr "लेआउट"
#: src/app/main/ui/dashboard/sidebar.cljs:799
msgid "labels.learning-center"
msgstr "अध्ययन केन्द्र"
#: src/app/main/ui/workspace/sidebar/versions.cljs:209
msgid "labels.lock"
msgstr "ताला"
#: src/app/main/ui/ds/controls/numeric_input.cljs:628
msgid "labels.mixed-values"
msgstr "मिश्रित"
#: src/app/main/ui/dashboard/sidebar.cljs:879
msgid "labels.penpot-changelog"
msgstr "पेनपॉट चेंजलॉग"
#: src/app/main/ui/dashboard/sidebar.cljs:805
msgid "labels.penpot-hub"
msgstr "पेनपॉट हब"
#: src/app/main/ui/dashboard/sidebar.cljs:752
msgid "labels.pinned-projects"
msgstr "पिन किए गए प्रोजेक्ट"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:667
msgid "labels.reference"
msgstr "संदर्भ"
#: src/app/main/ui/dashboard/team.cljs:788
msgid "labels.resend"
msgstr "पुन: भेजें"
#: src/app/main/ui/inspect/styles/style_box.cljs:27
msgid "labels.shadow"
msgstr "छाया"
#: src/app/main/ui/dashboard/sidebar.cljs:731
msgid "labels.sources"
msgstr "स्त्रोत"
#: src/app/main/ui/inspect/styles/style_box.cljs:24, src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs:46
msgid "labels.stroke"
msgstr "स्ट्रोक"
#: src/app/main/ui/inspect/right_sidebar.cljs:107, src/app/main/ui/inspect/styles.cljs:134
msgid "labels.styles"
msgstr "शैलियों"
#: src/app/main/ui/inspect/styles/style_box.cljs:33
msgid "labels.svg"
msgstr "SVG"
#: src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs:229
msgid "labels.switch"
msgstr "बदलना"
#: src/app/main/ui/inspect/styles/style_box.cljs:25
msgid "labels.text"
msgstr "मूलपाठ"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1452
msgid "labels.typography"
msgstr "अक्षर विन्यास"
#: src/app/main/ui/workspace/sidebar/versions.cljs:203
msgid "labels.unlock"
msgstr "अनलॉक"
#: src/app/main/ui/inspect/right_sidebar.cljs:65, src/app/main/ui/workspace/sidebar/options/menus/component.cljs:1028
msgid "labels.variant"
msgstr "रूपांतर"
#: src/app/main/ui/dashboard/sidebar.cljs:873
msgid "labels.version-notes"
msgstr "संस्करण %s नोट्स"
#: src/app/main/ui/inspect/styles/style_box.cljs:32
msgid "labels.visibility"
msgstr "दृश्यता"
#: src/app/main/ui/dashboard/team.cljs:825
msgid "notifications.invitation-deleted"
msgstr "आमंत्रण सफलतापूर्वक हटा दिया गया"
#: src/app/main/ui/workspace/sidebar/shortcuts.cljs:97
msgid "shortcuts.create-component-variant"
msgstr "घटक/संस्करण बनाएं"
#: src/app/main/ui/dashboard/subscription.cljs:109
msgid "subscription.dashboard.power-up.enterprise-trial.top-title"
msgstr "एंटरप्राइज़ योजना (परीक्षण)"
#: src/app/main/ui/dashboard/subscription.cljs:84
msgid "subscription.dashboard.power-up.professional.bottom-button"
msgstr "शक्तिप्रापक!"
#: src/app/main/ui/dashboard/subscription.cljs:83
msgid "subscription.dashboard.power-up.professional.bottom-description"
msgstr ""
"अपनी टीमों के लिए अतिरिक्त संग्रहण, फ़ाइल पुनर्प्राप्ति और बहुत कुछ प्राप्त करें।"
#: src/app/main/ui/dashboard/subscription.cljs:101
#, markdown
msgid "subscription.dashboard.power-up.unlimited.bottom-text"
msgstr ""
"अपनी सभी टीमों के लिए एक निश्चित कीमत पर असीमित स्टोरेज, विस्तारित फ़ाइल रिकवरी और "
"असीमित एडिटर प्राप्त करें। [एंटरप्राइज़ प्लान देखें।|target:self](%s)"
#: src/app/main/ui/dashboard/subscription.cljs:194
msgid "subscription.dashboard.professional-dashboard-cta-title"
msgstr ""
"आपकी स्वामित्व वाली टीमों में %s संपादक हैं, जबकि आपकी व्यावसायिक योजना 8 तक को कवर "
"करती है।"
#: src/app/main/ui/dashboard/subscription.cljs:202
#, markdown
msgid "subscription.dashboard.professional-dashboard-cta-upgrade-owner"
msgstr ""
"कृपया ज़्यादा एडिटर, स्टोरेज और फ़ाइल रिकवरी के लिए अभी अनलिमिटेड या एंटरप्राइज़ में "
"अपग्रेड करें। [अभी सब्सक्राइब करें।|target:self](%s)"
#: src/app/main/ui/dashboard/subscription.cljs:197
msgid "subscription.dashboard.unlimited-dashboard-cta-title"
msgstr ""
"आपकी टीम बढ़ती जा रही है! आपकी अनलिमिटेड योजना में %s संपादकों तक की सेवाएँ शामिल हैं, "
"लेकिन अब आपके पास %s हैं।"
#: src/app/main/ui/dashboard/subscription.cljs:205
#, markdown
msgid "subscription.dashboard.unlimited-dashboard-cta-upgrade-owner"
msgstr ""
"कृपया अपनी वर्तमान संपादक संख्या से मेल खाने के लिए अभी अपग्रेड करें। [अभी सदस्यता लें"
"।|target:self](%s)"
#: src/app/main/ui/dashboard/subscription.cljs:182
msgid "subscription.dashboard.unlimited-members-extra-editors-cta-text"
msgstr ""
"आपकी स्वामित्व वाली टीमों के केवल नए संपादक ही भविष्य के बिलिंग में शामिल होंगे। 25+ "
"संपादकों के लिए अभी भी $175/माह का एक समान शुल्क लागू है।"
#: src/app/main/ui/dashboard/subscription.cljs:178
msgid "subscription.dashboard.unlimited-members-extra-editors-cta-title"
msgstr "असीमित योजना के दौरान लोगों को आमंत्रित करना"
#: src/app/main/ui/settings/subscription.cljs:53
msgid "subscription.settings.editors"
msgstr "(x %s संपादक)"
#: src/app/main/ui/settings/subscription.cljs:418, src/app/main/ui/settings/subscription.cljs:428, src/app/main/ui/settings/subscription.cljs:486
msgid "subscription.settings.enterprise.autosave"
msgstr "90-दिन के ऑटोसेव संस्करण और फ़ाइल पुनर्प्राप्ति"
#: src/app/main/ui/settings/subscription.cljs:419, src/app/main/ui/settings/subscription.cljs:429, src/app/main/ui/settings/subscription.cljs:487
msgid "subscription.settings.enterprise.capped-bill"
msgstr "फ्लैट मासिक बिल"
#: src/app/main/ui/settings/subscription.cljs:417, src/app/main/ui/settings/subscription.cljs:427, src/app/main/ui/settings/subscription.cljs:485
msgid "subscription.settings.enterprise.unlimited-storage-benefit"
msgstr "असीमित भंडारण"
#: src/app/main/ui/settings/subscription.cljs:154
msgid "subscription.settings.management.dialog.currently-editors-title"
msgid_plural "subscription.settings.management.dialog.currently-editors-title"
msgstr[0] "वर्तमान में, आपकी टीम में %s व्यक्ति हैं जो संपादन कर सकते हैं।"
msgstr[1] "वर्तमान में, आपकी टीम में %s लोग हैं जो संपादन कर सकते हैं।"
#: src/app/main/ui/inspect/attributes/text.cljs:112
msgid "inspect.attributes.typography.text-decoration.strikethrough"
msgstr " "
#: src/app/main/ui/inspect/right_sidebar.cljs:177
msgid "inspect.tabs-switcher-label"
msgstr " "
#: src/app/main/ui/settings/subscription.cljs:156
msgid "subscription.settings.management.dialog.editors"
msgstr "संपादनकर्ता"
#: src/app/main/ui/settings/subscription.cljs:163
msgid "subscription.settings.management.dialog.editors-explanation"
msgstr "(स्वामी, व्यवस्थापक और संपादक। दर्शकों को संपादक नहीं माना जाएगा)"
#: src/app/main/ui/settings/subscription.cljs:206
msgid "subscription.settings.management.dialog.input-error"
msgstr ""
"आप मौजूदा संपादकों की संख्या से कम संपादक नहीं सेट कर सकते। टीम सेटिंग में उन लोगों की "
"भूमिका (संपादक/व्यवस्थापक से दर्शक) बदलें जो वास्तव में फ़ाइलें संपादित नहीं करते हैं।"
msgid "subscription.settings.management-dialog.step-2-title"
msgstr "हमें आगे बढ़ने में मदद करें और अपने परीक्षण को आसान बनाएं"
msgid "subscription.settings.management-dialog.step-2-description"
msgstr ""
"परीक्षण अवधि के बाद अपनी सदस्यता को सुचारू रूप से जारी रखने और हमारे ओपन-सोर्स प्रोजेक्ट "
"का समर्थन जारी रखने के लिए अभी अपनी भुगतान जानकारी जोड़ें। आपसे अभी कोई शुल्क नहीं लिया "
"जाएगा।"
msgid "subscription.settings.management-dialog.step-2-skip-button"
msgstr "अभी छोड़ें और परीक्षण शुरू करें"
msgid "subscription.settings.management-dialog.step-2-add-payment-button"
msgstr "भुगतान विवरण जोड़ें"
#: src/app/main/ui/settings/subscription.cljs:209
msgid "subscription.settings.management.dialog.unlimited-capped-warning"
msgstr ""
"सुझाव: आमंत्रणों से आगे रहने के लिए आप अभी अपनी सीटों की संख्या बढ़ा सकते हैं। 25+ संपादकों "
"वाली टीमों में, आपको प्रति माह ₹175 का एकमुश्त शुल्क मिलेगा।"
#: src/app/main/ui/settings/subscription.cljs:385, src/app/main/ui/settings/subscription.cljs:456
msgid "subscription.settings.professional.autosave-benefit"
msgstr "7-दिन का स्वतः सहेजा गया संस्करण और फ़ाइल पुनर्प्राप्ति"
#: src/app/main/ui/settings/subscription.cljs:384, src/app/main/ui/settings/subscription.cljs:455
msgid "subscription.settings.professional.storage-benefit"
msgstr "10GB स्टोरेज"
#: src/app/main/ui/settings/subscription.cljs:386, src/app/main/ui/settings/subscription.cljs:457
msgid "subscription.settings.professional.teams-editors-benefit"
msgstr "असीमित टीमें। आपकी स्वामित्व वाली टीमों में अधिकतम 8 संपादक।"
#: src/app/main/ui/settings/subscription.cljs:50
msgid "subscription.settings.recommended"
msgstr "अनुशंसित"
#: src/app/main/ui/settings/subscription.cljs:263
msgid "subscription.settings.success.dialog.thanks"
msgstr "पेनपोट %s योजना चुनने के लिए धन्यवाद!"
#: src/app/main/ui/settings/subscription.cljs:394, src/app/main/ui/settings/subscription.cljs:406, src/app/main/ui/settings/subscription.cljs:470
msgid "subscription.settings.unlimited.autosave-benefit"
msgstr "30-दिन का स्वतः सहेजा गया संस्करण और फ़ाइल पुनर्प्राप्ति"
#: src/app/main/ui/settings/subscription.cljs:393, src/app/main/ui/settings/subscription.cljs:405, src/app/main/ui/settings/subscription.cljs:469
msgid "subscription.settings.unlimited.storage-benefit"
msgstr "25GB स्टोरेज"
#: src/app/main/ui/workspace/sidebar/versions.cljs:56
#, markdown
msgid "subscription.workspace.versions.warning.enterprise.subtext-owner"
msgstr "यदि आप इस सीमा को बढ़ाना चाहते हैं, तो हमें [%s](mailto) पर लिखें"
#: src/app/main/ui/workspace/sidebar/versions.cljs:58
#, markdown
msgid "subscription.workspace.versions.warning.subtext-member"
msgstr ""
"यदि आप इस सीमा को बढ़ाना चाहते हैं, तो टीम के मालिक से संपर्क करें: [%s](mailto)"
#: src/app/main/ui/workspace/sidebar/versions.cljs:57
#, markdown
msgid "subscription.workspace.versions.warning.subtext-owner"
msgstr ""
"यदि आप इस सीमा को बढ़ाना चाहते हैं, तो [अपना प्लान अपग्रेड करें|target:self](%s)"
#: src/app/main/ui/dashboard/team.cljs:933
msgid "team.invitations-selected"
msgid_plural "team.invitations-selected"
msgstr[0] "1 आमंत्रण चयनित"
msgstr[1] "%s आमंत्रण चयनित"
#: src/app/main/ui/workspace/sidebar/assets/groups.cljs:81
msgid "workspace.assets.component-group-options"
msgstr "घटक समूह विकल्प"
#: src/app/main/ui/workspace/colorpicker.cljs:427, src/app/main/ui/workspace/colorpicker.cljs:439
msgid "workspace.colorpicker.color-tokens"
msgstr "रंग टोकन"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:499
msgid "workspace.component.swap.loop-error"
msgstr "घटकों को अपने अंदर नहीं रखा जा सकता।"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:498
msgid "workspace.component.switch.loop-error-multi"
msgstr ""
"कुछ प्रतियों को स्विच नहीं किया जा सका। घटकों को आपस में नेस्ट नहीं किया जा सकता।"
#: src/app/main/ui/workspace/libraries.cljs:107, src/app/main/ui/workspace/libraries.cljs:133
msgid "workspace.libraries.colors"
msgid_plural "workspace.libraries.colors"
msgstr[0] "1 रंग"
msgstr[1] "%s रंग"
#: src/app/main/ui/workspace/libraries.cljs:101, src/app/main/ui/workspace/libraries.cljs:125
msgid "workspace.libraries.components"
msgid_plural "workspace.libraries.components"
msgstr[0] "1 घटक"
msgstr[1] "%s घटक"
#: src/app/main/ui/workspace/libraries.cljs:349
msgid "workspace.libraries.connected-to"
msgstr "से जुड़ा"
#: src/app/main/ui/workspace/libraries.cljs:104, src/app/main/ui/workspace/libraries.cljs:129
msgid "workspace.libraries.graphics"
msgid_plural "workspace.libraries.graphics"
msgstr[0] "1 ग्राफ़िक"
msgstr[1] "%s ग्राफ़िक्स"
#: src/app/main/ui/workspace/libraries.cljs:110, src/app/main/ui/workspace/libraries.cljs:137
msgid "workspace.libraries.typography"
msgid_plural "workspace.libraries.typography"
msgstr[0] "1 अक्षर विन्यास"
msgstr[1] "%s अक्षर विन्यास"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:563
msgid "workspace.options.component.variant.duplicated.copy.locate"
msgstr "परस्पर विरोधी वेरिएंट का पता लगाएं"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:560
msgid "workspace.options.component.variant.duplicated.copy.title"
msgstr ""
"इस घटक में परस्पर विरोधी वैरिएंट हैं। सुनिश्चित करें कि प्रत्येक वैरिएंट में गुण मानों का एक "
"विशिष्ट सेट हो।"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:1330
msgid "workspace.options.component.variant.duplicated.group.locate"
msgstr "डुप्लिकेट वेरिएंट का पता लगाएँ"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:1327
msgid "workspace.options.component.variant.duplicated.group.title"
msgstr "कुछ वेरिएंट में समान गुण और मूल्य होते हैं"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:268
msgid "workspace.options.component.variant.duplicated.single.all"
msgstr ""
"इन वेरिएंट के गुण और मान समान हैं। मानों को समायोजित करें ताकि उन्हें पुनर्प्राप्त किया जा सके"
"।"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:265
msgid "workspace.options.component.variant.duplicated.single.one"
msgstr ""
"इस संस्करण के गुण और मान दूसरे संस्करण के समान हैं। मानों को समायोजित करें ताकि उन्हें "
"पुनर्प्राप्त किया जा सके।"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:271
msgid "workspace.options.component.variant.duplicated.single.some"
msgstr ""
"इनमें से कुछ वेरिएंट के गुण और मान समान हैं। मानों को समायोजित करें ताकि उन्हें पुनर्प्राप्त "
"किया जा सके।"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:550
msgid "workspace.options.component.variant.malformed.copy"
msgstr ""
"इस घटक के कुछ वेरिएंट अमान्य नामों वाले हैं। सुनिश्चित करें कि प्रत्येक वेरिएंट सही संरचना का "
"पालन कर रहा है।"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:553
msgid "workspace.options.component.variant.malformed.locate"
msgstr "अमान्य वेरिएंट का पता लगाएं"
#: src/app/main/ui/workspace/sidebar/options/menus/variants_help_modal.cljs:54
msgid "workspace.options.component.variants-help-modal.intro"
msgstr ""
"वेरिएंट के बीच स्विच करते समय परिवर्तनों को बनाए रखने के लिए, पेनपॉट उन परतों को जोड़ता "
"है जो:"
#: src/app/main/ui/workspace/sidebar/options/menus/variants_help_modal.cljs:91
msgid "workspace.options.component.variants-help-modal.outro"
msgstr ""
"इनमें से किसी भी परिवर्तन (जैसे, परत का नाम बदलना या समूह बनाना) से कनेक्शन टूट जाता है, "
"लेकिन परिवर्तन को पूर्ववत करने से यह पुनः स्थापित हो जाएगा।"
#: src/app/main/ui/workspace/sidebar/options/menus/variants_help_modal.cljs:67
msgid "workspace.options.component.variants-help-modal.rule1"
msgstr "एक ही नाम साझा करें।"
#: src/app/main/ui/workspace/sidebar/options/menus/variants_help_modal.cljs:76
msgid "workspace.options.component.variants-help-modal.rule2"
msgstr "एक ही प्रकार के हैं।"
#: src/app/main/ui/workspace/sidebar/options/menus/variants_help_modal.cljs:77
msgid "workspace.options.component.variants-help-modal.rule2.detail"
msgstr "आयत, दीर्घवृत्त, पथ और बूलियन ऑपरेशन एक ही प्रकार के माने जाते हैं।"
#: src/app/main/ui/workspace/sidebar/options/menus/variants_help_modal.cljs:87
msgid "workspace.options.component.variants-help-modal.rule3"
msgstr "समान पदानुक्रम स्तर रखें।"
#: src/app/main/ui/workspace/sidebar/options/menus/variants_help_modal.cljs:88
msgid "workspace.options.component.variants-help-modal.rule3.detail"
msgstr "समूह, बोर्ड और लेआउट को समतुल्य माना जाता है।"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:1034, src/app/main/ui/workspace/sidebar/options/menus/component.cljs:1278, src/app/main/ui/workspace/sidebar/options/menus/variants_help_modal.cljs:47
msgid "workspace.options.component.variants-help-modal.title"
msgstr "वेरिएंट कैसे जुड़े रहते हैं"
#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs:264
msgid "workspace.options.more-token-colors"
msgstr "अधिक रंग के टोकन"
#: src/app/main/ui/workspace/plugins.cljs:287
msgid "workspace.plugins.permissions.allow-localstorage"
msgstr "ब्राउज़र में डेटा संग्रहीत करें।"
#: src/app/main/ui/workspace/context_menu.cljs:617, src/app/main/ui/workspace/sidebar/assets/components.cljs:634, src/app/main/ui/workspace/sidebar/assets/groups.cljs:75, src/app/main/ui/workspace/sidebar/options/menus/component.cljs:1095
msgid "workspace.shape.menu.combine-as-variants"
msgstr "वैरिएंट के रूप में संयोजित करें"
#: src/app/main/ui/workspace/sidebar/assets/components.cljs:636
msgid "workspace.shape.menu.combine-as-variants-error"
msgstr "घटकों को एक ही पृष्ठ पर होना आवश्यक है"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs:1145
msgid "workspace.shape.menu.remove-variant-property.last-property"
msgstr "वैरिएंट में कम से कम एक प्रॉपर्टी होनी चाहिए"
#: src/app/main/data/workspace/tokens/errors.cljs:97
msgid "workspace.tokens.composite-line-height-needs-font-size"
msgstr ""
"पंक्ति की ऊँचाई फ़ॉन्ट आकार पर निर्भर करती है। हल किया गया मान प्राप्त करने के लिए फ़ॉन्ट "
"आकार जोड़ें।"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:581
msgid "workspace.tokens.edit-token"
msgstr "%s token संपादित करें"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1339
msgid "workspace.tokens.font-size-value-enter"
msgstr "फ़ॉन्ट आकार या {उपनाम}"
#: src/app/main/data/workspace/tokens/application.cljs:323
msgid "workspace.tokens.font-variant-not-found"
msgstr ""
"फ़ॉन्ट वज़न/शैली सेट करते समय त्रुटि हुई। यह फ़ॉन्ट शैली वर्तमान फ़ॉन्ट में मौजूद नहीं है"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1328, src/app/main/ui/workspace/tokens/management/create/form.cljs:1343
msgid "workspace.tokens.font-weight-value-enter"
msgstr "फ़ॉन्ट वज़न (300, बोल्ड इटैलिक...) या {उपनाम}"
#: src/app/main/ui/workspace/tokens/import/modal.cljs:233
msgid "workspace.tokens.import-button-prefix"
msgstr "%s आयात करें"
#: src/app/main/ui/workspace/tokens/import/modal.cljs:273
msgid "workspace.tokens.import-menu-folder-option"
msgstr "फ़ोल्डर"
#: src/app/main/ui/workspace/tokens/import/modal.cljs:272
msgid "workspace.tokens.import-menu-json-option"
msgstr "एकल JSON फ़ाइल"
#: src/app/main/ui/workspace/tokens/import/modal.cljs:271
msgid "workspace.tokens.import-menu-zip-option"
msgstr "ज़िप फ़ाइल"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:741
msgid "workspace.tokens.individual-tokens"
msgstr "व्यक्तिगत टोकन का प्रयोग करें"
#: src/app/main/data/workspace/tokens/errors.cljs:89
msgid "workspace.tokens.invalid-font-weight-token-value"
msgstr ""
"अमान्य फ़ॉन्ट भार मान: संख्यात्मक मान (100-950) या मानक नाम (पतला, हल्का, नियमित, "
"बोल्ड, आदि) का उपयोग करें, वैकल्पिक रूप से उसके बाद 'इटैलिक' लिखें"
#: src/app/main/data/workspace/tokens/errors.cljs:101
msgid "workspace.tokens.invalid-shadow-type-token-value"
msgstr ""
"अमान्य छाया प्रकार: केवल 'innerShadow' या 'dropShadow' स्वीकार किए जाते हैं"
#: src/app/main/data/workspace/tokens/errors.cljs:81
msgid "workspace.tokens.invalid-text-case-token-value"
msgstr ""
"अमान्य token मान: केवल कोई नहीं, अपरकेस, लोअरकेस या कैपिटलाइज़ स्वीकार किए जाते हैं"
#: src/app/main/data/workspace/tokens/errors.cljs:85
msgid "workspace.tokens.invalid-text-decoration-token-value"
msgstr ""
"अमान्य token मान: केवल कोई नहीं, रेखांकित और स्ट्राइक-थ्रू स्वीकार किए जाते हैं"
#: src/app/main/data/workspace/tokens/errors.cljs:93
msgid "workspace.tokens.invalid-token-value-typography"
msgstr "अमान्य मान: एक संयुक्त टाइपोग्राफी token का संदर्भ होना चाहिए।"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1351
msgid "workspace.tokens.letter-spacing-value-enter-composite"
msgstr "अक्षर रिक्ति या {उपनाम}"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1347
msgid "workspace.tokens.line-height-value-enter"
msgstr "पंक्ति ऊँचाई (गुणक, पिक्सेल, %) या {उपनाम}"
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:123
msgid "workspace.tokens.more-options"
msgstr "विकल्प देखने के लिए राइट क्लिक करें"
#: src/app/main/data/workspace/tokens/errors.cljs:19
msgid "workspace.tokens.no-token-files-found"
msgstr "इस फ़ाइल में कोई टोकन, सेट या थीम नहीं मिला।"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:775
msgid "workspace.tokens.reference-composite"
msgstr "token टाइपोग्राफी उपनाम दर्ज करें"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1084
msgid "workspace.tokens.shadow-add-shadow"
msgstr "छाया जोड़ें"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:981, src/app/main/ui/workspace/tokens/management/create/form.cljs:982
msgid "workspace.tokens.shadow-blur"
msgstr "धुंधला"
#: src/app/main/data/workspace/tokens/errors.cljs:105
msgid "workspace.tokens.shadow-blur-range"
msgstr "छाया धुंधलापन 0 से अधिक या उसके बराबर होना चाहिए।"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:987, src/app/main/ui/workspace/tokens/management/create/form.cljs:988
msgid "workspace.tokens.shadow-color"
msgstr "रंग"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:990, src/app/main/ui/workspace/tokens/management/create/form.cljs:991
msgid "workspace.tokens.shadow-inset"
msgstr "अंतर्भूत"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1091
msgid "workspace.tokens.shadow-remove-shadow"
msgstr "छाया हटाएँ"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:984, src/app/main/ui/workspace/tokens/management/create/form.cljs:985
msgid "workspace.tokens.shadow-spread"
msgstr "फैलाना"
#: src/app/main/data/workspace/tokens/errors.cljs:109
msgid "workspace.tokens.shadow-spread-range"
msgstr "छाया प्रसार 0 से अधिक या उसके बराबर होना चाहिए।"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1215
msgid "workspace.tokens.shadow-title"
msgstr "छायाएँ"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:975, src/app/main/ui/workspace/tokens/management/create/form.cljs:976
msgid "workspace.tokens.shadow-x"
msgstr "X"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:978, src/app/main/ui/workspace/tokens/management/create/form.cljs:979
msgid "workspace.tokens.shadow-y"
msgstr "Y"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1316, src/app/main/ui/workspace/tokens/management/create/form.cljs:1355
msgid "workspace.tokens.text-case-value-enter"
msgstr "कोई नहीं | अपरकेस | लोअरकेस | कैपिटलाइज़ या {उपनाम}"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1322, src/app/main/ui/workspace/tokens/management/create/form.cljs:1359
msgid "workspace.tokens.text-decoration-value-enter"
msgstr "कोई नहीं | रेखांकित | स्ट्राइक-थ्रू या {उपनाम}"
#: src/app/main/ui/workspace/tokens/themes/create_modal.cljs:52
msgid "workspace.tokens.theme-name-already-exists"
msgstr "इस नाम वाली थीम पहले से मौजूद है"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1277
msgid "workspace.tokens.token-font-family-select"
msgstr "फ़ॉन्ट परिवार चुनें"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1333
msgid "workspace.tokens.token-font-family-value"
msgstr "फॉन्ट परिवार"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1283, src/app/main/ui/workspace/tokens/management/create/form.cljs:1335
msgid "workspace.tokens.token-font-family-value-enter"
msgstr "फ़ॉन्ट परिवार या अल्पविराम (,) द्वारा अलग किए गए फ़ॉन्ट की सूची"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:44, src/app/main/ui/workspace/tokens/management/create/form.cljs:70
msgid "workspace.tokens.token-name-duplication-validation-error"
msgstr "पथ पर एक token पहले से मौजूद है: %s"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:42, src/app/main/ui/workspace/tokens/management/create/form.cljs:68
msgid "workspace.tokens.token-name-length-validation-error"
msgstr "नाम कम से कम 1 अक्षर का होना चाहिए"
#: src/app/main/data/workspace/tokens/import_export.cljs:47
msgid "workspace.tokens.unknown-token-type-message"
msgstr "आयात सफल रहा। कुछ टोकन शामिल नहीं किए गए।"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:745
msgid "workspace.tokens.use-reference"
msgstr "एक संदर्भ का प्रयोग करें"
#: src/app/main/data/workspace/tokens/errors.cljs:69
msgid "workspace.tokens.value-with-percent"
msgstr "अमान्य मान: % की अनुमति नहीं है।"
#, unused
msgid "workspace.versions.locked-by-other"
msgstr "यह संस्करण %s द्वारा लॉक किया गया है और इसे संशोधित नहीं किया जा सकता"
#, unused
msgid "workspace.versions.locked-by-you"
msgstr "यह संस्करण आपके द्वारा लॉक किया गया है"
#, unused
msgid "workspace.versions.tooltip.locked-version"
msgstr "लॉक किया गया संस्करण - केवल निर्माता ही इसे संशोधित कर सकता है"

View File

@@ -1569,6 +1569,10 @@ msgstr ""
"Ako želite saznati više o pregledu dizajna, posjetite Penpotov centar za "
"pomoć"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more-info"
msgstr "Više informacija o inspekciji"
#: src/app/main/ui/inspect/right_sidebar.cljs:232
msgid "inspect.empty.select"
msgstr "Odaberite oblik, ploču ili grupu da provjerite njihova svojstva i kod"

View File

@@ -1653,6 +1653,10 @@ msgstr ""
"Jika Anda ingin mengetahui lebih lanjut tentang inspeksi desain kunjungi "
"pusat bantuan Penpot"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more-info"
msgstr "Lebih banyak info tentang inspeksi"
#: src/app/main/ui/inspect/right_sidebar.cljs:232
msgid "inspect.empty.select"
msgstr "Pilih bentuk, papan, atau grup untuk menginskpeksi properti dan kodenya"

View File

@@ -1,15 +1,15 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"Last-Translator: Alejandro Alonso <alejandro.alonso@kaleidos.net>\n"
"Language-Team: Igbo <https://hosted.weblate.org/projects/penpot/frontend/ig/>"
"\n"
"PO-Revision-Date: 2025-10-07 16:35+0000\n"
"Last-Translator: Anonymous <noreply@weblate.org>\n"
"Language-Team: Igbo "
"<https://hosted.weblate.org/projects/penpot/frontend/ig/>\n"
"Language: ig\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Weblate 5.15.1\n"
"X-Generator: Weblate 5.14-dev\n"
#: src/app/main/ui/auth/register.cljs:277
#, unused
@@ -402,7 +402,6 @@ msgid "dashboard.export.detail"
msgstr "* Nwere Ike ịgụnye ngwa , esereese gasị, agwụgwara na/ma ọ bụ akara nkụpụta."
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgstr ""
"A ga-atinye ederede nwere ọba ederede nkekọrịta ma gụnyere mbupu , ma "

View File

@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"Last-Translator: Anonymous <noreply@weblate.org>\n"
"PO-Revision-Date: 2025-11-23 12:51+0000\n"
"Last-Translator: Nicola Bortoletto <nicola.bortoletto@live.com>\n"
"Language-Team: Italian <https://hosted.weblate.org/projects/penpot/frontend/"
"it/>\n"
"Language: it\n"
@@ -9,7 +9,7 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.15.1\n"
"X-Generator: Weblate 5.15-dev\n"
#: src/app/main/ui/auth/register.cljs:215, src/app/main/ui/static.cljs:159, src/app/main/ui/viewer/login.cljs:100
msgid "auth.already-have-account"
@@ -582,11 +582,10 @@ msgstr ""
"cosa desideri fare con le loro risorse*?"
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgstr ""
"I file con librerie condivise verranno inclusi nell'esportazione, mantenendo "
"il loro collegamento."
"I file con librerie condivise verranno inclusi nell'esportazione, "
"mantenendo il loro collegamento."
#: src/app/main/ui/exports/files.cljs:165
msgid "dashboard.export.options.all.title"
@@ -1825,6 +1824,10 @@ msgstr ""
"Per ulteriori informazioni su l'ispezione, visita il centro di supporto di "
"Penpot"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more-info"
msgstr "Maggiori informazioni sull'ispezione"
#: src/app/main/ui/inspect/right_sidebar.cljs:232
msgid "inspect.empty.select"
msgstr ""
@@ -1832,7 +1835,6 @@ msgstr ""
"loro proprietà e il loro codice"
#: src/app/main/ui/inspect/right_sidebar.cljs:166
#, fuzzy
msgid "inspect.layer-info"
msgstr "Informazioni livello"
@@ -4741,8 +4743,9 @@ msgid "subscription.settings.sucess.dialog.title"
msgstr "Sei %s!"
#: src/app/main/ui/settings/subscription.cljs:440
#, fuzzy
msgid "subscription.settings.support-us-since"
msgstr "Ci stai supportando con questo piano dal %s"
msgstr "Ci supporti con questo piano dal %s"
#: src/app/main/ui/settings/subscription.cljs:472, src/app/main/ui/settings/subscription.cljs:488
msgid "subscription.settings.try-it-free"
@@ -7769,6 +7772,7 @@ msgstr ""
"0.5)."
#: src/app/main/ui/workspace/tokens/management/token_pill.cljs:120
#, fuzzy
msgid "workspace.tokens.original-value"
msgstr "Valore originale: %s"
@@ -7794,6 +7798,7 @@ msgid "workspace.tokens.reference-error"
msgstr "Errori di riferimento: "
#: src/app/main/data/workspace/tokens/warnings.cljs:15, src/app/main/data/workspace/tokens/warnings.cljs:19, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:56, src/app/main/ui/workspace/colorpicker/color_tokens.cljs:84, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs:102, src/app/main/ui/workspace/tokens/management/create/form_input_token.cljs:109, src/app/main/ui/workspace/tokens/management/create/input_tokens_value.cljs:41, src/app/main/ui/workspace/tokens/management/create/input_tokens_value.cljs:46, src/app/main/ui/workspace/tokens/management/token_pill.cljs:121
#, fuzzy
msgid "workspace.tokens.resolved-value"
msgstr "Valore risolto: %"
@@ -7867,6 +7872,7 @@ msgid "workspace.tokens.themes-list"
msgstr "Elenco temi"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:194, src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:195, src/app/main/ui/workspace/tokens/management/create/form.cljs:629, src/app/main/ui/workspace/tokens/management/create/form.cljs:630
#, fuzzy
msgid "workspace.tokens.token-description"
msgstr "Descrizione"
@@ -8413,96 +8419,3 @@ msgstr "Esiste già un tema con questo nome"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:44, src/app/main/ui/workspace/tokens/management/create/form.cljs:70
msgid "workspace.tokens.token-name-duplication-validation-error"
msgstr "Un token con questo nome esiste già nel percorso: %s"
#: src/app/main/ui/static.cljs:396
msgid "labels.internal-error.desc-message-first"
msgstr "Qualcosa è andato storto."
#: src/app/main/ui/static.cljs:397
msgid "labels.internal-error.desc-message-second"
msgstr ""
"Puoi riprovare loperazione oppure contattare il supporto per segnalare "
"lerrore."
#: src/app/main/ui/dashboard/subscription.cljs:84
msgid "subscription.dashboard.power-up.professional.bottom-button"
msgstr "Potenzia!"
#: src/app/main/ui/dashboard/subscription.cljs:83
msgid "subscription.dashboard.power-up.professional.bottom-description"
msgstr ""
"Ottieni spazio aggiuntivo, recupero dei file e altro ancora per i tuoi team."
msgid "subscription.settings.management-dialog.step-2-title"
msgstr "Aiutaci a crescere e rendi la tua prova ancora più semplice"
msgid "subscription.settings.management-dialog.step-2-description"
msgstr ""
"Aggiungi subito i tuoi dati di pagamento per mantenere attiva la "
"sottoscrizione dopo il periodo di prova e continuare a supportare il nostro "
"progetto open-source. Non ti verrà ancora addebitato nulla."
#: src/app/main/ui/inspect/styles/style_box.cljs:68
msgid "inspect.tabs.styles.copy-shorthand"
msgstr "Copia l'abbreviazione CSS negli appunti"
#: src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs:229
msgid "labels.switch"
msgstr "Cambia"
msgid "subscription.settings.management-dialog.step-2-add-payment-button"
msgstr "Aggiungi dettagli di pagamento"
#: src/app/main/ui/settings/subscription.cljs:50
msgid "subscription.settings.recommended"
msgstr "Consigliato"
#: src/app/main/ui/dashboard/team.cljs:933
msgid "team.invitations-selected"
msgid_plural "team.invitations-selected"
msgstr[0] "1 invito selezionato"
msgstr[1] "%s inviti selezionati"
#: src/app/main/ui/workspace/libraries.cljs:107, src/app/main/ui/workspace/libraries.cljs:133
msgid "workspace.libraries.colors"
msgid_plural "workspace.libraries.colors"
msgstr[0] "1 colore"
msgstr[1] "%s colori"
#: src/app/main/ui/workspace/libraries.cljs:101, src/app/main/ui/workspace/libraries.cljs:125
msgid "workspace.libraries.components"
msgid_plural "workspace.libraries.components"
msgstr[0] "1 componente"
msgstr[1] "%s componenti"
#: src/app/main/ui/workspace/libraries.cljs:104, src/app/main/ui/workspace/libraries.cljs:129
msgid "workspace.libraries.graphics"
msgid_plural "workspace.libraries.graphics"
msgstr[0] "1 grafica"
msgstr[1] "%s grafiche"
#: src/app/main/ui/workspace/libraries.cljs:110, src/app/main/ui/workspace/libraries.cljs:137
msgid "workspace.libraries.typography"
msgid_plural "workspace.libraries.typography"
msgstr[0] "1 tipografia"
msgstr[1] "%s tipografie"
#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs:264
msgid "workspace.options.more-token-colors"
msgstr "Altri token colore"
#: src/app/main/ui/workspace/tokens/management/create/form.cljs:1339
msgid "workspace.tokens.font-size-value-enter"
msgstr "Dimensione carattere o {alias}"
#: src/app/main/data/workspace/tokens/errors.cljs:101
msgid "workspace.tokens.invalid-shadow-type-token-value"
msgstr ""
"Tipologia ombra non valida: sono consentite solo 'innerShadow' o 'dropShadow'"
#: src/app/main/ui/workspace/tokens/management/create/border_radius.cljs:42, src/app/main/ui/workspace/tokens/management/create/form.cljs:68
msgid "workspace.tokens.token-name-length-validation-error"
msgstr "Il nome deve essere di almeno 1 carattere"
msgid "subscription.settings.management-dialog.step-2-skip-button"
msgstr "Salta per ora e inizia la prova"

View File

@@ -1,6 +1,6 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-11-25 16:51+0000\n"
"PO-Revision-Date: 2025-11-23 12:51+0000\n"
"Last-Translator: Edgars Andersons <Edgars+Weblate@gaitenis.id.lv>\n"
"Language-Team: Latvian <https://hosted.weblate.org/projects/penpot/frontend/"
"lv/>\n"
@@ -1809,6 +1809,10 @@ msgstr ""
"Ja ir vēlme uzzināt vairāk par dizaina apskati, jāapmeklē Penpot palīdzības "
"centrs"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more-info"
msgstr "Vairāk informācijas par apskatīšanu"
#: src/app/main/ui/inspect/right_sidebar.cljs:232
msgid "inspect.empty.select"
msgstr "Jāatlasa apveids, plātne vai kopa, lai apskatītu to īpašības un kodu"
@@ -7962,12 +7966,3 @@ msgstr "Ierosinājums"
#: src/app/main/ui/settings/feedback.cljs:114
msgid "feedback.type.issue"
msgstr "Sarežģījums"
msgid "subscription.settings.management-dialog.step-2-title"
msgstr "Palīdzi mums attīstīties un padarīt Tavu izmēģinājumu vienkāršāku"
msgid "subscription.settings.management-dialog.step-2-skip-button"
msgstr "Pagaidām izlaist un uzsākt izmēģinājumu"
msgid "subscription.settings.management-dialog.step-2-add-payment-button"
msgstr "Pievienot maksājumu informāciju"

View File

@@ -1297,6 +1297,10 @@ msgstr ""
"Jika anda ingin mengetahui lebih lanjut tentang pemeriksaan reka bentuk, "
"lawati pusat bantuan Penpot"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more-info"
msgstr "Maklumat lanjut tentang inspect"
#: src/app/main/ui/inspect/right_sidebar.cljs:232
msgid "inspect.empty.select"
msgstr "Pilih bentuk, papan atau kumpulan untuk memeriksa sifat dan kod mereka"

View File

@@ -1,15 +1,15 @@
msgid ""
msgstr ""
"PO-Revision-Date: 2025-12-22 15:34+0000\n"
"Last-Translator: Anonymous <noreply@weblate.org>\n"
"Language-Team: Dutch <https://hosted.weblate.org/projects/penpot/frontend/"
"nl/>\n"
"PO-Revision-Date: 2025-11-22 10:51+0000\n"
"Last-Translator: Keunes <keunes@mailbox.org>\n"
"Language-Team: Dutch <https://hosted.weblate.org/projects/penpot/frontend/nl/"
">\n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 5.15.1\n"
"X-Generator: Weblate 5.15-dev\n"
#: src/app/main/ui/auth/register.cljs:215, src/app/main/ui/static.cljs:159, src/app/main/ui/viewer/login.cljs:100
msgid "auth.already-have-account"
@@ -580,7 +580,6 @@ msgstr ""
"bibliotheken. Wat wil je doen met hun assets*?"
#: src/app/main/ui/exports/files.cljs:164
#, fuzzy
msgid "dashboard.export.options.all.message"
msgstr ""
"Bestanden met gedeelde bibliotheken worden opgenomen in de export en hun "
@@ -1826,6 +1825,10 @@ msgstr ""
"Als je meer wilt weten over ontwerpinspectie, ga dan naar het helpcentrum "
"van Penpot"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more-info"
msgstr "Meer info over inspecteren"
#: src/app/main/ui/inspect/right_sidebar.cljs:232
msgid "inspect.empty.select"
msgstr ""
@@ -1833,7 +1836,6 @@ msgstr ""
"inspecteren"
#: src/app/main/ui/inspect/right_sidebar.cljs:166
#, fuzzy
msgid "inspect.layer-info"
msgstr "Laaginfo"
@@ -8382,7 +8384,7 @@ msgstr "Schakelaar"
#: src/app/main/ui/dashboard/subscription.cljs:84
msgid "subscription.dashboard.power-up.professional.bottom-button"
msgstr "Gas geven!"
msgstr "Inschakelen!"
#: src/app/main/ui/dashboard/subscription.cljs:83
#, markdown
@@ -8472,18 +8474,3 @@ msgstr "Naam moet minimaal 1 teken zijn"
#: src/app/main/ui/inspect/styles/style_box.cljs:68
msgid "inspect.tabs.styles.copy-shorthand"
msgstr "CSS-code kopiëren naar klembord"
msgid "subscription.settings.management-dialog.step-2-title"
msgstr "Help ons groeien en maak je proefperiode eenvoudiger"
msgid "subscription.settings.management-dialog.step-2-description"
msgstr ""
"Voeg nu je betalingsgegevens toe om je abonnement na de proefperiode soepel "
"te laten verlopen en ons open-sourceproject te blijven ondersteunen. Er "
"worden nog geen kosten in rekening gebracht."
msgid "subscription.settings.management-dialog.step-2-skip-button"
msgstr "Nu overslaan en proefperiode starten"
msgid "subscription.settings.management-dialog.step-2-add-payment-button"
msgstr "Betalingsgegevens toevoegen"

View File

@@ -1170,6 +1170,10 @@ msgstr ""
"Jeśli chcesz dowiedzieć się więcej o inspekcji projektu, odwiedź centrum "
"pomocy Penpot"
#: src/app/main/ui/inspect/right_sidebar.cljs:240
msgid "inspect.empty.more-info"
msgstr "Więcej informacji o inspekcji"
#: src/app/main/ui/inspect/right_sidebar.cljs:232
msgid "inspect.empty.select"
msgstr "Wybierz kształt, tablicę lub grupę, aby sprawdzić ich właściwości i kod"

Some files were not shown because too many files have changed in this diff Show More