mirror of
https://github.com/penpot/penpot.git
synced 2025-03-20 03:31:24 -05:00
Merge remote-tracking branch 'origin/staging' into develop
This commit is contained in:
commit
68b26d5f41
73 changed files with 693 additions and 295 deletions
CHANGES.md
backend/src/app
common
frontend
resources
images/features
styles/main/partials
templates
src/app
main
data
ui
dashboard
onboarding
releases.cljsreleases
shapes
viewer
workspace
util
translations
23
CHANGES.md
23
CHANGES.md
|
@ -66,6 +66,29 @@
|
|||
- Fix problem with guides not showing when moving over nested frames [Taiga #4905](https://tree.taiga.io/project/penpot/issue/4905)
|
||||
- Fix change email and password for users signed in via social login [Taiga #4273](https://tree.taiga.io/project/penpot/issue/4273)
|
||||
- Fix drag and drop files from browser or file explorer under circumstances [Taiga #5054](https://tree.taiga.io/project/penpot/issue/5054)
|
||||
- Fix problem when copy/pasting shapes [Taiga #4931](https://tree.taiga.io/project/penpot/issue/4931)
|
||||
- Fix problem with color picker not able to change hue [Taiga #5065](https://tree.taiga.io/project/penpot/issue/5065)
|
||||
- Fix problem with outer stroke in texts [Taiga #5078](https://tree.taiga.io/project/penpot/issue/5078)
|
||||
- Fix problem with text carring over next line when changing to fixed [Taiga #5067](https://tree.taiga.io/project/penpot/issue/5067)
|
||||
- Fix don't show invite user hero to users with editor role [Taiga #5086](https://tree.taiga.io/project/penpot/issue/5086)
|
||||
- Fix enter emails on onboarding new user creating team [Taiga #5089](https://tree.taiga.io/project/penpot/issue/5089)
|
||||
- Fix invalid files amount after moving on dashboard [Taiga #5080](https://tree.taiga.io/project/penpot/issue/5080)
|
||||
- Fix dashboard left sidebar, the [x] overlaps the field [Taiga #5064](https://tree.taiga.io/project/penpot/issue/5064)
|
||||
- Fix expanded typography on assets sidebar is moving [Taiga #5063](https://tree.taiga.io/project/penpot/issue/5063)
|
||||
- Fix spelling mistake in confirmation after importing only 1 file [Taiga #5095](https://tree.taiga.io/project/penpot/issue/5095)
|
||||
- Fix problem with selection colors and texts [Taiga #5079](https://tree.taiga.io/project/penpot/issue/5079)
|
||||
- Remove "show in view mode" flag when moving frame to frame [Taiga #5091](https://tree.taiga.io/project/penpot/issue/5091)
|
||||
- Fix problem creating files in project page [Taiga #5060](https://tree.taiga.io/project/penpot/issue/5060)
|
||||
- Disable empty names on rename files [Taiga #5088](https://tree.taiga.io/project/penpot/issue/5088)
|
||||
- Fix problem with SVG and flex layout [Taiga #](https://tree.taiga.io/project/penpot/issue/5099)
|
||||
- Fix unpublish and delete shared library warning messages [Taiga #5090](https://tree.taiga.io/project/penpot/issue/5090)
|
||||
- Fix last update project timer update after creating new file [Taiga #5096](https://tree.taiga.io/project/penpot/issue/5096)
|
||||
- Fix dashboard scrolling using 'Page Up' and 'Page Down' [Taiga #5081](https://tree.taiga.io/project/penpot/issue/5081)
|
||||
- Fix view mode header buttons overlapping in small resolutions [Taiga #5058](https://tree.taiga.io/project/penpot/issue/5058)
|
||||
- Fix precision for wrap in flex [Taiga #5072](https://tree.taiga.io/project/penpot/issue/5072)
|
||||
- Fix relative position overlay positioning [Taiga #5092](https://tree.taiga.io/project/penpot/issue/5092)
|
||||
- Fix hide grid keyboard shortcut [Github #3071](https://github.com/penpot/penpot/pull/3071)
|
||||
- Fix problem with opacity in imported SVG's [Taiga #4923](https://tree.taiga.io/project/penpot/issue/4923)
|
||||
|
||||
### :heart: Community contributions by (Thank you!)
|
||||
- To @ondrejkonec: for contributing to the code with:
|
||||
|
|
|
@ -155,8 +155,18 @@
|
|||
(.isClosed ^HikariDataSource pool))
|
||||
|
||||
(defn read-only?
|
||||
[pool]
|
||||
(.isReadOnly ^HikariDataSource pool))
|
||||
[pool-or-conn]
|
||||
(cond
|
||||
(instance? HikariDataSource pool-or-conn)
|
||||
(.isReadOnly ^HikariDataSource pool-or-conn)
|
||||
|
||||
(instance? Connection pool-or-conn)
|
||||
(.isReadOnly ^Connection pool-or-conn)
|
||||
|
||||
:else
|
||||
(ex/raise :type :internal
|
||||
:code :invalid-connection
|
||||
:hint "invalid connection provided")))
|
||||
|
||||
(defn create-pool
|
||||
[cfg]
|
||||
|
|
|
@ -1050,13 +1050,13 @@
|
|||
|
||||
;; --- MUTATION COMMAND: upsert-file-thumbnail
|
||||
|
||||
(def sql:upsert-file-thumbnail
|
||||
(def ^:private sql:upsert-file-thumbnail
|
||||
"insert into file_thumbnail (file_id, revn, data, props)
|
||||
values (?, ?, ?, ?::jsonb)
|
||||
on conflict(file_id, revn) do
|
||||
update set data = ?, props=?, updated_at=now();")
|
||||
|
||||
(defn upsert-file-thumbnail
|
||||
(defn- upsert-file-thumbnail!
|
||||
[conn {:keys [file-id revn data props]}]
|
||||
(let [props (db/tjson (or props {}))]
|
||||
(db/exec-one! conn [sql:upsert-file-thumbnail
|
||||
|
@ -1076,5 +1076,6 @@
|
|||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
|
||||
(db/with-atomic [conn pool]
|
||||
(check-edition-permissions! conn profile-id file-id)
|
||||
(upsert-file-thumbnail conn params)
|
||||
(when-not (db/read-only? conn)
|
||||
(upsert-file-thumbnail! conn params))
|
||||
nil))
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
[app.util.objects-map :as omap]
|
||||
[app.util.pointer-map :as pmap]
|
||||
[app.util.services :as sv]
|
||||
[app.util.time :as dt]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
||||
(defn create-file-role!
|
||||
|
@ -71,6 +72,10 @@
|
|||
(->> (assoc params :file-id id :role :owner)
|
||||
(create-file-role! conn))
|
||||
|
||||
(db/update! conn :project
|
||||
{:modified-at (dt/now)}
|
||||
{:id project-id})
|
||||
|
||||
(files/decode-row file)))
|
||||
|
||||
(s/def ::create-file
|
||||
|
|
|
@ -152,10 +152,10 @@
|
|||
profile))
|
||||
|
||||
(defn update-profile-password!
|
||||
[{:keys [::db/conn] :as cfg} {:keys [id password] :as profile}]
|
||||
(let [password (derive-password cfg password)]
|
||||
[conn {:keys [id password] :as profile}]
|
||||
(when-not (db/read-only? conn)
|
||||
(db/update! conn :profile
|
||||
{:password password}
|
||||
{:password (auth/derive-password password)}
|
||||
{:id id})))
|
||||
|
||||
;; --- MUTATION: Update Photo
|
||||
|
|
|
@ -60,12 +60,18 @@
|
|||
:can-edit (or is-owner is-admin can-edit)
|
||||
:can-read true})))
|
||||
|
||||
(def has-admin-permissions?
|
||||
(perms/make-admin-predicate-fn get-permissions))
|
||||
|
||||
(def has-edit-permissions?
|
||||
(perms/make-edition-predicate-fn get-permissions))
|
||||
|
||||
(def has-read-permissions?
|
||||
(perms/make-read-predicate-fn get-permissions))
|
||||
|
||||
(def check-admin-permissions!
|
||||
(perms/make-check-fn has-admin-permissions?))
|
||||
|
||||
(def check-edition-permissions!
|
||||
(perms/make-check-fn has-edit-permissions?))
|
||||
|
||||
|
@ -592,18 +598,19 @@
|
|||
(let [team (retrieve-team pool profile-id team-id)
|
||||
photo (profile/upload-photo cfg params)]
|
||||
|
||||
;; Mark object as touched for make it ellegible for tentative
|
||||
;; garbage collection.
|
||||
(when-let [id (:photo-id team)]
|
||||
(sto/touch-object! storage id))
|
||||
(db/with-atomic [conn pool]
|
||||
(check-admin-permissions! conn profile-id team-id)
|
||||
;; Mark object as touched for make it ellegible for tentative
|
||||
;; garbage collection.
|
||||
(when-let [id (:photo-id team)]
|
||||
(sto/touch-object! storage id))
|
||||
|
||||
;; Save new photo
|
||||
(db/update! pool :team
|
||||
{:photo-id (:id photo)}
|
||||
{:id team-id})
|
||||
|
||||
(assoc team :photo-id (:id photo))))
|
||||
;; Save new photo
|
||||
(db/update! pool :team
|
||||
{:photo-id (:id photo)}
|
||||
{:id team-id})
|
||||
|
||||
(assoc team :photo-id (:id photo)))))
|
||||
|
||||
;; --- Mutation: Create Team Invitation
|
||||
|
||||
|
@ -728,8 +735,13 @@
|
|||
(let [perms (get-permissions conn profile-id team-id)
|
||||
profile (db/get-by-id conn :profile profile-id)
|
||||
team (db/get-by-id conn :team team-id)
|
||||
emails (cond-> (or emails #{}) (string? email) (conj email))]
|
||||
|
||||
;; Members emails. We don't re-send inviation to already existing members
|
||||
member? (into #{}
|
||||
(map :email)
|
||||
(db/exec! conn [sql:team-members team-id]))
|
||||
|
||||
emails (cond-> (or emails #{}) (string? email) (conj email))]
|
||||
|
||||
(run! (partial quotes/check-quote! conn)
|
||||
(list {::quotes/id ::quotes/invitations-per-team
|
||||
|
@ -753,6 +765,7 @@
|
|||
|
||||
(let [cfg (assoc cfg ::db/conn conn)
|
||||
invitations (->> emails
|
||||
(remove member?)
|
||||
(map (fn [email]
|
||||
{:email (str/lower email)
|
||||
:team team
|
||||
|
|
|
@ -32,7 +32,14 @@
|
|||
|
||||
links (->> (db/query conn :share-link {:file-id file-id})
|
||||
(mapv (fn [row]
|
||||
(update row :pages db/decode-pgarray #{}))))
|
||||
(-> row
|
||||
(update :pages db/decode-pgarray #{})
|
||||
;; NOTE: the flags are deprecated but are still present
|
||||
;; on the table on old rows. The flags are pgarray and
|
||||
;; for avoid decoding it (because they are no longer used
|
||||
;; on frontend) we just dissoc the column attribute from
|
||||
;; row.
|
||||
(dissoc :flags)))))
|
||||
|
||||
fonts (db/query conn :team-font-variant
|
||||
{:team-id (:team-id project)
|
||||
|
|
|
@ -37,6 +37,14 @@
|
|||
:is-admin false
|
||||
:can-edit false)))
|
||||
|
||||
(defn make-admin-predicate-fn
|
||||
"A simple factory for admin permission predicate functions."
|
||||
[qfn]
|
||||
(us/assert fn? qfn)
|
||||
(fn check
|
||||
([perms] (:is-admin perms))
|
||||
([conn & args] (check (apply qfn conn args)))))
|
||||
|
||||
(defn make-edition-predicate-fn
|
||||
"A simple factory for edition permission predicate functions."
|
||||
[qfn]
|
||||
|
|
|
@ -318,8 +318,10 @@
|
|||
(defn unit
|
||||
[p1]
|
||||
(let [p-length (length p1)]
|
||||
(Point. (/ (dm/get-prop p1 :x) p-length)
|
||||
(/ (dm/get-prop p1 :y) p-length))))
|
||||
(if (mth/almost-zero? p-length)
|
||||
(Point. 0 0)
|
||||
(Point. (/ (dm/get-prop p1 :x) p-length)
|
||||
(/ (dm/get-prop p1 :y) p-length)))))
|
||||
|
||||
(defn perpendicular
|
||||
[pt]
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
[app.common.geom.shapes.modifiers :as gsm]
|
||||
[app.common.geom.shapes.path :as gsp]
|
||||
[app.common.geom.shapes.rect :as gpr]
|
||||
[app.common.geom.shapes.text :as gst]
|
||||
[app.common.geom.shapes.transforms :as gtr]
|
||||
[app.common.math :as mth]))
|
||||
|
||||
|
@ -195,3 +196,6 @@
|
|||
|
||||
;; Modifiers
|
||||
(dm/export gsm/set-objects-modifiers)
|
||||
|
||||
;; Text
|
||||
(dm/export gst/position-data-selrect)
|
||||
|
|
|
@ -133,29 +133,38 @@
|
|||
(-> (get-shape-filter-bounds shape)
|
||||
(add-padding (calculate-padding shape true))))
|
||||
|
||||
bounds (if (or (:masked-group? shape)
|
||||
(and (cph/frame-shape? shape)
|
||||
(not (:show-content shape))))
|
||||
[(calculate-base-bounds shape)]
|
||||
(cph/reduce-objects
|
||||
objects
|
||||
(fn [shape]
|
||||
(and (d/not-empty? (:shapes shape))
|
||||
(or (not (cph/frame-shape? shape))
|
||||
(:show-content shape))
|
||||
bounds
|
||||
(cond
|
||||
(empty? (:shapes shape))
|
||||
[(calculate-base-bounds shape)]
|
||||
|
||||
(or (not (cph/group-shape? shape))
|
||||
(not (:masked-group? shape)))))
|
||||
(:masked-group? shape)
|
||||
[(calculate-base-bounds shape)]
|
||||
|
||||
(:id shape)
|
||||
(and (cph/frame-shape? shape) (not (:show-content shape)))
|
||||
[(calculate-base-bounds shape)]
|
||||
|
||||
(fn [result shape]
|
||||
(conj result (get-object-bounds objects shape)))
|
||||
:else
|
||||
(cph/reduce-objects
|
||||
objects
|
||||
(fn [shape]
|
||||
(and (d/not-empty? (:shapes shape))
|
||||
(or (not (cph/frame-shape? shape))
|
||||
(:show-content shape))
|
||||
|
||||
[(calculate-base-bounds shape)]))
|
||||
(or (not (cph/group-shape? shape))
|
||||
(not (:masked-group? shape)))))
|
||||
|
||||
children-bounds (cond->> (gsr/join-selrects bounds)
|
||||
(not (cph/frame-shape? shape)) (or (:children-bounds shape)))
|
||||
(:id shape)
|
||||
|
||||
(fn [result child]
|
||||
(conj result (calculate-base-bounds child)))
|
||||
|
||||
[(calculate-base-bounds shape)]))
|
||||
|
||||
children-bounds
|
||||
(cond->> (gsr/join-selrects bounds)
|
||||
(not (cph/frame-shape? shape)) (or (:children-bounds shape)))
|
||||
|
||||
filters (shape->filters shape)
|
||||
blur-value (or (-> shape :blur :value) 0)]
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.rect :as gpr]))
|
||||
[app.common.geom.shapes.rect :as gpr]
|
||||
[app.common.math :as mth]))
|
||||
|
||||
(defn center-rect
|
||||
[{:keys [x y width height]}]
|
||||
|
@ -71,3 +72,15 @@
|
|||
[{:keys [x1 y1 x2 y2] :as sr} matrix]
|
||||
(let [[c1 c2] (transform-points [(gpt/point x1 y1) (gpt/point x2 y2)] matrix)]
|
||||
(gpr/corners->selrect c1 c2)))
|
||||
|
||||
(defn invalid-geometry?
|
||||
[{:keys [points selrect]}]
|
||||
|
||||
(or (mth/nan? (:x selrect))
|
||||
(mth/nan? (:y selrect))
|
||||
(mth/nan? (:width selrect))
|
||||
(mth/nan? (:height selrect))
|
||||
(some (fn [p]
|
||||
(or (mth/nan? (:x p))
|
||||
(mth/nan? (:y p))))
|
||||
points)))
|
||||
|
|
|
@ -104,8 +104,10 @@
|
|||
|
||||
(if (and (some? line-data)
|
||||
(or (not wrap?)
|
||||
(and row? (<= next-line-min-width layout-width))
|
||||
(and col? (<= next-line-min-height layout-height))))
|
||||
(and row? (or (< next-line-min-width layout-width)
|
||||
(mth/close? next-line-min-width layout-width 0.5)))
|
||||
(and col? (or (< next-line-min-height layout-height)
|
||||
(mth/close? next-line-min-height layout-height 0.5)))))
|
||||
|
||||
(recur {:line-min-width (if row? (+ line-min-width next-min-width) (max line-min-width next-min-width))
|
||||
:line-max-width (if row? (+ line-max-width next-max-width) (max line-max-width next-max-width))
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.common :as gco]
|
||||
[app.common.geom.shapes.constraints :as gct]
|
||||
[app.common.geom.shapes.flex-layout :as gcfl]
|
||||
[app.common.geom.shapes.grid-layout :as gcgl]
|
||||
|
@ -180,6 +181,7 @@
|
|||
(let [children (->> children
|
||||
(map (d/getf objects))
|
||||
(remove :hidden)
|
||||
(remove gco/invalid-geometry?)
|
||||
(map apply-modifiers))
|
||||
layout-data (gcfl/calc-layout-data parent children @transformed-parent-bounds)
|
||||
children (into [] (cond-> children (not (:reverse? layout-data)) reverse))
|
||||
|
@ -215,6 +217,7 @@
|
|||
modif-tree))]
|
||||
(let [children (->> (cph/get-immediate-children objects (:id parent))
|
||||
(remove :hidden)
|
||||
(remove gco/invalid-geometry?)
|
||||
(map apply-modifiers))
|
||||
grid-data (gcgl/calc-layout-data parent children @transformed-parent-bounds)]
|
||||
(loop [modif-tree modif-tree
|
||||
|
@ -249,7 +252,8 @@
|
|||
(ctm/resize (gpt/point 1 scale-height) origin (:transform parent) (:transform-inverse parent)))))
|
||||
|
||||
children (->> (cph/get-immediate-children objects parent-id)
|
||||
(remove :hidden))
|
||||
(remove :hidden)
|
||||
(remove gco/invalid-geometry?))
|
||||
|
||||
content-bounds
|
||||
(when (and (d/not-empty? children) (or (ctl/auto-height? parent) (ctl/auto-width? parent)))
|
||||
|
|
|
@ -185,8 +185,10 @@
|
|||
|
||||
(defn close?
|
||||
"Equality for float numbers. Check if the difference is within a range"
|
||||
[num1 num2]
|
||||
(<= (abs (- num1 num2)) float-equal-precision))
|
||||
([num1 num2]
|
||||
(close? num1 num2 float-equal-precision))
|
||||
([num1 num2 precision]
|
||||
(<= (abs (- num1 num2)) precision)))
|
||||
|
||||
(defn lerp
|
||||
"Calculates a the linear interpolation between two values and a given percent"
|
||||
|
|
|
@ -526,19 +526,22 @@
|
|||
|
||||
(loop [current-val init-val
|
||||
current-id (first root-children)
|
||||
pending-ids (rest root-children)]
|
||||
pending-ids (rest root-children)
|
||||
processed #{}]
|
||||
|
||||
(if (contains? processed current-id)
|
||||
(recur current-val (first pending-ids) (rest pending-ids) processed)
|
||||
(let [current-shape (get objects current-id)
|
||||
processed (conj processed current-id)
|
||||
next-val (reducer-fn current-val current-shape)
|
||||
next-pending-ids
|
||||
(if (or (nil? check-children?) (check-children? current-shape))
|
||||
(concat (or (:shapes current-shape) []) pending-ids)
|
||||
pending-ids)]
|
||||
|
||||
(let [current-shape (get objects current-id)
|
||||
next-val (reducer-fn current-val current-shape)
|
||||
next-pending-ids
|
||||
(if (or (nil? check-children?) (check-children? current-shape))
|
||||
(concat (or (:shapes current-shape) []) pending-ids)
|
||||
pending-ids)]
|
||||
|
||||
(if (empty? next-pending-ids)
|
||||
next-val
|
||||
(recur next-val (first next-pending-ids) (rest next-pending-ids)))))))))
|
||||
(if (empty? next-pending-ids)
|
||||
next-val
|
||||
(recur next-val (first next-pending-ids) (rest next-pending-ids) processed)))))))))
|
||||
|
||||
(defn selected-with-children
|
||||
[objects selected]
|
||||
|
@ -569,3 +572,25 @@
|
|||
(d/enumerate)
|
||||
(sort comparator-layout-z-index)
|
||||
(mapv second)))
|
||||
|
||||
(defn common-parent-frame
|
||||
"Search for the common frame for the selected shapes. Otherwise returns the root frame"
|
||||
[objects selected]
|
||||
|
||||
(loop [frame-id (get-in objects [(first selected) :frame-id])
|
||||
frame-parents (get-parent-ids objects frame-id)
|
||||
selected (rest selected)]
|
||||
(if (empty? selected)
|
||||
frame-id
|
||||
|
||||
(let [current (first selected)
|
||||
parent? (into #{} (get-parent-ids objects current))
|
||||
|
||||
[frame-id frame-parents]
|
||||
(if (parent? frame-id)
|
||||
[frame-id frame-parents]
|
||||
|
||||
(let [frame-id (d/seek parent? frame-parents)]
|
||||
[frame-id (get-parent-ids objects frame-id)]))]
|
||||
|
||||
(recur frame-id frame-parents (rest selected))))))
|
||||
|
|
|
@ -645,12 +645,16 @@
|
|||
(recur matrix (next modifiers)))))))
|
||||
|
||||
(defn transform-text-node [value attrs]
|
||||
(let [font-size (-> (get attrs :font-size 14)
|
||||
(d/parse-double)
|
||||
(* value)
|
||||
(str))]
|
||||
(let [font-size (-> (get attrs :font-size 14) d/parse-double (* value) str)
|
||||
letter-spacing (-> (get attrs :letter-spacing 0) d/parse-double (* value) str)]
|
||||
(d/txt-merge attrs {:font-size font-size
|
||||
:letter-spacing letter-spacing})))
|
||||
|
||||
(defn transform-paragraph-node [value attrs]
|
||||
(let [font-size (-> (get attrs :font-size 14) d/parse-double (* value) str)]
|
||||
(d/txt-merge attrs {:font-size font-size})))
|
||||
|
||||
|
||||
(defn update-text-content
|
||||
[shape scale-text-content value]
|
||||
(update shape :content scale-text-content value))
|
||||
|
@ -661,9 +665,8 @@
|
|||
(letfn [(scale-text-content
|
||||
[content value]
|
||||
(->> content
|
||||
(txt/transform-nodes
|
||||
txt/is-text-node?
|
||||
(partial transform-text-node value))))
|
||||
(txt/transform-nodes txt/is-text-node? (partial transform-text-node value))
|
||||
(txt/transform-nodes txt/is-paragraph-node? (partial transform-paragraph-node value))))
|
||||
|
||||
(apply-scale-content
|
||||
[shape value]
|
||||
|
@ -671,7 +674,7 @@
|
|||
(cph/text-shape? shape)
|
||||
(update-text-content scale-text-content value)
|
||||
|
||||
(cph/rect-shape? shape)
|
||||
:always
|
||||
(gsc/update-corners-scale value)
|
||||
|
||||
(d/not-empty? (:strokes shape))
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
[app.common.data :as d]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes.bounds :as gsb]
|
||||
[app.common.pages.helpers :as cph]
|
||||
[app.common.spec :as us]
|
||||
[clojure.spec.alpha :as s]))
|
||||
|
||||
|
@ -363,6 +364,7 @@
|
|||
|
||||
(defn calc-overlay-position
|
||||
[interaction ;; interaction data
|
||||
shape ;; Shape with the interaction
|
||||
objects ;; the objects tree
|
||||
relative-to-shape ;; the interaction position is realtive to this sape
|
||||
base-frame ;; the base frame of the current interaction
|
||||
|
@ -371,56 +373,68 @@
|
|||
|
||||
(us/verify ::interaction interaction)
|
||||
(assert (has-overlay-opts interaction))
|
||||
(if (nil? dest-frame)
|
||||
(gpt/point 0 0)
|
||||
(let [overlay-size (gsb/get-object-bounds objects dest-frame)
|
||||
base-frame-size (:selrect base-frame)
|
||||
relative-to-shape-size (:selrect relative-to-shape)
|
||||
relative-to-adjusted-to-base-frame {:x (- (:x relative-to-shape-size) (:x base-frame-size))
|
||||
:y (- (:y relative-to-shape-size) (:y base-frame-size))}
|
||||
relative-to-is-auto? (and (nil? (:position-relative-to interaction)) (not= :manual (:overlay-pos-type interaction)))
|
||||
base-position (if relative-to-is-auto?
|
||||
{:x 0 :y 0}
|
||||
{:x (+ (:x frame-offset)
|
||||
(:x relative-to-adjusted-to-base-frame))
|
||||
:y (+ (:y frame-offset)
|
||||
(:y relative-to-adjusted-to-base-frame))})
|
||||
overlay-position (:overlay-position interaction)
|
||||
overlay-position (if (= (:type relative-to-shape) :frame)
|
||||
overlay-position
|
||||
{:x (- (:x overlay-position) (:x relative-to-adjusted-to-base-frame))
|
||||
:y (- (:y overlay-position) (:y relative-to-adjusted-to-base-frame))})]
|
||||
(case (:overlay-pos-type interaction)
|
||||
:center
|
||||
(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
|
||||
(+ (:y base-position) (/ (- (:height relative-to-shape-size) (:height overlay-size)) 2)))
|
||||
(let [
|
||||
;; When the interactive item is inside a nested frame we need to add to the offset the position
|
||||
;; of the parent-frame otherwise the position won't match
|
||||
shape-frame (cph/get-frame objects shape)
|
||||
|
||||
:top-left
|
||||
(gpt/point (:x base-position) (:y base-position))
|
||||
frame-offset (if (or (not= :manual (:overlay-pos-type interaction))
|
||||
(nil? shape-frame)
|
||||
(cph/root-frame? shape-frame)
|
||||
(cph/root? shape-frame))
|
||||
frame-offset
|
||||
(gpt/add frame-offset (gpt/point shape-frame)))
|
||||
]
|
||||
(if (nil? dest-frame)
|
||||
(gpt/point 0 0)
|
||||
(let [overlay-size (gsb/get-object-bounds objects dest-frame)
|
||||
base-frame-size (:selrect base-frame)
|
||||
relative-to-shape-size (:selrect relative-to-shape)
|
||||
relative-to-adjusted-to-base-frame {:x (- (:x relative-to-shape-size) (:x base-frame-size))
|
||||
:y (- (:y relative-to-shape-size) (:y base-frame-size))}
|
||||
relative-to-is-auto? (and (nil? (:position-relative-to interaction)) (not= :manual (:overlay-pos-type interaction)))
|
||||
base-position (if relative-to-is-auto?
|
||||
{:x 0 :y 0}
|
||||
{:x (+ (:x frame-offset)
|
||||
(:x relative-to-adjusted-to-base-frame))
|
||||
:y (+ (:y frame-offset)
|
||||
(:y relative-to-adjusted-to-base-frame))})
|
||||
overlay-position (:overlay-position interaction)
|
||||
overlay-position (if (= (:type relative-to-shape) :frame)
|
||||
overlay-position
|
||||
{:x (- (:x overlay-position) (:x relative-to-adjusted-to-base-frame))
|
||||
:y (- (:y overlay-position) (:y relative-to-adjusted-to-base-frame))})]
|
||||
(case (:overlay-pos-type interaction)
|
||||
:center
|
||||
(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
|
||||
(+ (:y base-position) (/ (- (:height relative-to-shape-size) (:height overlay-size)) 2)))
|
||||
|
||||
:top-right
|
||||
(gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size)))
|
||||
(:y base-position))
|
||||
:top-left
|
||||
(gpt/point (:x base-position) (:y base-position))
|
||||
|
||||
:top-center
|
||||
(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
|
||||
(:y base-position))
|
||||
:top-right
|
||||
(gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size)))
|
||||
(:y base-position))
|
||||
|
||||
:bottom-left
|
||||
(gpt/point (:x base-position)
|
||||
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
|
||||
:top-center
|
||||
(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
|
||||
(:y base-position))
|
||||
|
||||
:bottom-right
|
||||
(gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size)))
|
||||
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
|
||||
:bottom-left
|
||||
(gpt/point (:x base-position)
|
||||
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
|
||||
|
||||
:bottom-center
|
||||
(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
|
||||
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
|
||||
:bottom-right
|
||||
(gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size)))
|
||||
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
|
||||
|
||||
:manual
|
||||
(gpt/point (+ (:x base-position) (:x overlay-position))
|
||||
(+ (:y base-position) (:y overlay-position)))))))
|
||||
:bottom-center
|
||||
(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
|
||||
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
|
||||
|
||||
:manual
|
||||
(gpt/point (+ (:x base-position) (:x overlay-position))
|
||||
(+ (:y base-position) (:y overlay-position))))))))
|
||||
|
||||
(defn has-animation?
|
||||
[interaction]
|
||||
|
|
|
@ -324,49 +324,49 @@
|
|||
interaction-rect (ctsi/set-position-relative-to interaction (:id rect))]
|
||||
(t/testing "Overlay top-left relative to auto"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-left base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects base-frame base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 0))
|
||||
(t/is (= (:y overlay-pos) 0))))
|
||||
|
||||
(t/testing "Overlay top-center relative to auto"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-center base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects base-frame base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 35))
|
||||
(t/is (= (:y overlay-pos) 0))))
|
||||
|
||||
(t/testing "Overlay top-right relative to auto"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-right base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects base-frame base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 70))
|
||||
(t/is (= (:y overlay-pos) 0))))
|
||||
|
||||
(t/testing "Overlay bottom-left relative to auto"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-left base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects base-frame base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 0))
|
||||
(t/is (= (:y overlay-pos) 80))))
|
||||
|
||||
(t/testing "Overlay bottom-center relative to auto"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-center base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects base-frame base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 35))
|
||||
(t/is (= (:y overlay-pos) 80))))
|
||||
|
||||
(t/testing "Overlay bottom-right relative to auto"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-right base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects base-frame base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 70))
|
||||
(t/is (= (:y overlay-pos) 80))))
|
||||
|
||||
(t/testing "Overlay center relative to auto"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :center base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects base-frame base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 35))
|
||||
(t/is (= (:y overlay-pos) 40))))
|
||||
|
||||
(t/testing "Overlay manual relative to auto"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :center base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects base-frame base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 35))
|
||||
(t/is (= (:y overlay-pos) 40))))
|
||||
|
||||
|
@ -374,49 +374,49 @@
|
|||
(let [i2 (-> interaction-auto
|
||||
(ctsi/set-overlay-pos-type :manual base-frame objects)
|
||||
(ctsi/set-overlay-position (gpt/point 12 62)))
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects base-frame base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 17))
|
||||
(t/is (= (:y overlay-pos) 67))))
|
||||
|
||||
(t/testing "Overlay top-left relative to base-frame"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-left base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects base-frame base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 5))
|
||||
(t/is (= (:y overlay-pos) 5))))
|
||||
|
||||
(t/testing "Overlay top-center relative to base-frame"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-center base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects base-frame base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 40))
|
||||
(t/is (= (:y overlay-pos) 5))))
|
||||
|
||||
(t/testing "Overlay top-right relative to base-frame"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-right base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects base-frame base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 75))
|
||||
(t/is (= (:y overlay-pos) 5))))
|
||||
|
||||
(t/testing "Overlay bottom-left relative to base-frame"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-left base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects base-frame base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 5))
|
||||
(t/is (= (:y overlay-pos) 85))))
|
||||
|
||||
(t/testing "Overlay bottom-center relative to base-frame"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-center base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects base-frame base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 40))
|
||||
(t/is (= (:y overlay-pos) 85))))
|
||||
|
||||
(t/testing "Overlay bottom-right relative to base-frame"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-right base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects base-frame base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 75))
|
||||
(t/is (= (:y overlay-pos) 85))))
|
||||
|
||||
(t/testing "Overlay center relative to base-frame"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :center base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects base-frame base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 40))
|
||||
(t/is (= (:y overlay-pos) 45))))
|
||||
|
||||
|
@ -424,49 +424,49 @@
|
|||
(let [i2 (-> interaction-base-frame
|
||||
(ctsi/set-overlay-pos-type :manual base-frame objects)
|
||||
(ctsi/set-overlay-position (gpt/point 12 62)))
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects base-frame base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects base-frame base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 17))
|
||||
(t/is (= (:y overlay-pos) 67))))
|
||||
|
||||
(t/testing "Overlay top-left relative to popup"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-left base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects popup base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 15))
|
||||
(t/is (= (:y overlay-pos) 15))))
|
||||
|
||||
(t/testing "Overlay top-center relative to popup"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-center base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects popup base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 25))
|
||||
(t/is (= (:y overlay-pos) 15))))
|
||||
|
||||
(t/testing "Overlay top-right relative to popup"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-right base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects popup base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 35))
|
||||
(t/is (= (:y overlay-pos) 15))))
|
||||
|
||||
(t/testing "Overlay bottom-left relative to popup"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-left base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects popup base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 15))
|
||||
(t/is (= (:y overlay-pos) 45))))
|
||||
|
||||
(t/testing "Overlay bottom-center relative to popup"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-center base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects popup base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 25))
|
||||
(t/is (= (:y overlay-pos) 45))))
|
||||
|
||||
(t/testing "Overlay bottom-right relative to popup"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-right base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects popup base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 35))
|
||||
(t/is (= (:y overlay-pos) 45))))
|
||||
|
||||
(t/testing "Overlay center relative to popup"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :center base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects popup base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 25))
|
||||
(t/is (= (:y overlay-pos) 30))))
|
||||
|
||||
|
@ -474,49 +474,49 @@
|
|||
(let [i2 (-> interaction-popup
|
||||
(ctsi/set-overlay-pos-type :manual base-frame objects)
|
||||
(ctsi/set-overlay-position (gpt/point 12 62)))
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects popup base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 27))
|
||||
(t/is (= (:y overlay-pos) 77))))
|
||||
|
||||
(t/testing "Overlay top-left relative to popup"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-left base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects popup base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects popup base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 15))
|
||||
(t/is (= (:y overlay-pos) 15))))
|
||||
|
||||
(t/testing "Overlay top-center relative to rect"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :top-center base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects rect base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 25))
|
||||
(t/is (= (:y overlay-pos) 15))))
|
||||
|
||||
(t/testing "Overlay top-right relative to rect"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :top-right base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects rect base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 35))
|
||||
(t/is (= (:y overlay-pos) 15))))
|
||||
|
||||
(t/testing "Overlay bottom-left relative to rect"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-left base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects rect base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 15))
|
||||
(t/is (= (:y overlay-pos) 45))))
|
||||
|
||||
(t/testing "Overlay bottom-center relative to rect"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-center base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects rect base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 25))
|
||||
(t/is (= (:y overlay-pos) 45))))
|
||||
|
||||
(t/testing "Overlay bottom-right relative to rect"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-right base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects rect base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 35))
|
||||
(t/is (= (:y overlay-pos) 45))))
|
||||
|
||||
(t/testing "Overlay center relative to rect"
|
||||
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :center base-frame objects)
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects rect base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 25))
|
||||
(t/is (= (:y overlay-pos) 30))))
|
||||
|
||||
|
@ -524,7 +524,7 @@
|
|||
(let [i2 (-> interaction-rect
|
||||
(ctsi/set-overlay-pos-type :manual base-frame objects)
|
||||
(ctsi/set-overlay-position (gpt/point 12 62)))
|
||||
overlay-pos (ctsi/calc-overlay-position i2 objects rect base-frame overlay-frame frame-offset)]
|
||||
overlay-pos (ctsi/calc-overlay-position i2 rect objects rect base-frame overlay-frame frame-offset)]
|
||||
(t/is (= (:x overlay-pos) 17))
|
||||
(t/is (= (:y overlay-pos) 67))))))
|
||||
|
||||
|
|
BIN
frontend/resources/images/features/1.18-absolute.gif
Normal file
BIN
frontend/resources/images/features/1.18-absolute.gif
Normal file
Binary file not shown.
After ![]() (image error) Size: 269 KiB |
BIN
frontend/resources/images/features/1.18-scale.gif
Normal file
BIN
frontend/resources/images/features/1.18-scale.gif
Normal file
Binary file not shown.
After ![]() (image error) Size: 560 KiB |
BIN
frontend/resources/images/features/1.18-spacing.gif
Normal file
BIN
frontend/resources/images/features/1.18-spacing.gif
Normal file
Binary file not shown.
After ![]() (image error) Size: 789 KiB |
BIN
frontend/resources/images/features/1.18-z-index.gif
Normal file
BIN
frontend/resources/images/features/1.18-z-index.gif
Normal file
Binary file not shown.
After ![]() (image error) Size: 220 KiB |
|
@ -260,7 +260,7 @@
|
|||
.close {
|
||||
background-color: $color-white;
|
||||
cursor: pointer;
|
||||
padding: 3px 5px;
|
||||
padding-left: 5px;
|
||||
|
||||
svg {
|
||||
fill: $color-gray-30;
|
||||
|
|
|
@ -338,6 +338,10 @@
|
|||
|
||||
.typography-container {
|
||||
position: relative;
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.drag-counter {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
background-color: $color-gray-50;
|
||||
border-bottom: 1px solid $color-gray-60;
|
||||
display: grid;
|
||||
grid-template-columns: 45% 10% 45%;
|
||||
grid-template-columns: 1fr 130px 1fr;
|
||||
height: 48px;
|
||||
padding: 0 $size-4 0 55px;
|
||||
top: 0;
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
<body>
|
||||
{{>../public/images/sprites/symbol/icons.svg}}
|
||||
{{>../public/images/sprites/symbol/cursors.svg}}
|
||||
<div id="app" tabindex="0"></div>
|
||||
<div id="app"></div>
|
||||
<section id="modal"></section>
|
||||
{{# manifest}}
|
||||
<script src="{{& shared}}"></script>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
{{/manifest}}
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" tabindex="0"></div>
|
||||
<div id="app"></div>
|
||||
{{# manifest}}
|
||||
<script src="{{& shared}}"></script>
|
||||
<script src="{{& render}}"></script>
|
||||
|
|
|
@ -925,6 +925,13 @@
|
|||
{:num-files (count ids)
|
||||
:project-id project-id})
|
||||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [origin-project (get-in state [:dashboard-files (first ids) :project-id])]
|
||||
(-> state
|
||||
(update-in [:dashboard-projects origin-project :count] #(- % (count ids)))
|
||||
(update-in [:dashboard-projects project-id :count] #(+ % (count ids))))))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(let [{:keys [on-success on-error]
|
||||
|
|
|
@ -9,6 +9,12 @@
|
|||
|
||||
import Mousetrap from 'mousetrap'
|
||||
|
||||
if (Mousetrap.addKeycodes) {
|
||||
Mousetrap.addKeycodes({
|
||||
219: '219'
|
||||
});
|
||||
}
|
||||
|
||||
const target = Mousetrap.prototype || Mousetrap;
|
||||
target.stopCallback = function(e, element, combo) {
|
||||
// if the element has the class "mousetrap" then no need to stop
|
||||
|
@ -20,7 +26,7 @@ target.stopCallback = function(e, element, combo) {
|
|||
return element.tagName == 'INPUT' ||
|
||||
element.tagName == 'SELECT' ||
|
||||
element.tagName == 'TEXTAREA' ||
|
||||
element.tagName == 'BUTTON' ||
|
||||
(element.tagName == 'BUTTON' && combo.includes("tab")) ||
|
||||
(element.contentEditable && element.contentEditable == 'true');
|
||||
}
|
||||
|
||||
|
|
|
@ -676,6 +676,10 @@
|
|||
(cond-> (not (ctl/any-layout? objects parent-id))
|
||||
(pcb/update-shapes ordered-indexes ctl/remove-layout-item-data))
|
||||
|
||||
;; Remove the hide in viewer flag
|
||||
(cond-> (and (not= uuid/zero parent-id) (cph/frame-shape? objects parent-id))
|
||||
(pcb/update-shapes ordered-indexes #(cond-> % (cph/frame-shape? %) (assoc :hide-in-viewer true))))
|
||||
|
||||
;; Move the shapes
|
||||
(pcb/change-parent parent-id
|
||||
shapes
|
||||
|
@ -1311,8 +1315,8 @@
|
|||
;; Prepare the shape object. Mainly needed for image shapes
|
||||
;; for retrieve the image data and convert it to the
|
||||
;; data-url.
|
||||
(prepare-object [objects selected+children {:keys [type] :as obj}]
|
||||
(let [obj (maybe-translate obj objects selected+children)]
|
||||
(prepare-object [objects parent-frame-id {:keys [type] :as obj}]
|
||||
(let [obj (maybe-translate obj objects parent-frame-id)]
|
||||
(if (= type :image)
|
||||
(let [url (cf/resolve-file-media (:metadata obj))]
|
||||
(->> (http/send! {:method :get
|
||||
|
@ -1335,15 +1339,11 @@
|
|||
(update res :images conj img-part))
|
||||
res)))
|
||||
|
||||
(maybe-translate [shape objects selected+children]
|
||||
(let [root-frame-id (cph/get-shape-id-root-frame objects (:id shape))]
|
||||
(if (and (not (cph/root-frame? shape))
|
||||
(not (contains? selected+children root-frame-id)))
|
||||
;; When the parent frame is not selected we change to relative
|
||||
;; coordinates
|
||||
(let [frame (get objects root-frame-id)]
|
||||
(gsh/translate-to-frame shape frame))
|
||||
shape)))
|
||||
(maybe-translate [shape objects parent-frame-id]
|
||||
(if (= parent-frame-id uuid/zero)
|
||||
shape
|
||||
(let [frame (get objects parent-frame-id)]
|
||||
(gsh/translate-to-frame shape frame))))
|
||||
|
||||
(on-copy-error [error]
|
||||
(js/console.error "Clipboard blocked:" error)
|
||||
|
@ -1356,7 +1356,7 @@
|
|||
selected (->> (wsh/lookup-selected state)
|
||||
(cph/clean-loops objects))
|
||||
|
||||
selected+children (cph/selected-with-children objects selected)
|
||||
parent-frame-id (cph/common-parent-frame objects selected)
|
||||
pdata (reduce (partial collect-object-ids objects) {} selected)
|
||||
initial {:type :copied-shapes
|
||||
:file-id (:current-file-id state)
|
||||
|
@ -1371,7 +1371,7 @@
|
|||
(catch :default e
|
||||
(on-copy-error e)))
|
||||
(->> (rx/from (seq (vals pdata)))
|
||||
(rx/merge-map (partial prepare-object objects selected+children))
|
||||
(rx/merge-map (partial prepare-object objects parent-frame-id))
|
||||
(rx/reduce collect-data initial)
|
||||
(rx/map (partial sort-selected state))
|
||||
(rx/map t/encode-str)
|
||||
|
|
|
@ -138,6 +138,7 @@
|
|||
(pcb/with-objects objects)
|
||||
(cond-> (not (ctl/any-layout? objects frame-id))
|
||||
(pcb/update-shapes ordered-indexes ctl/remove-layout-item-data))
|
||||
(pcb/update-shapes ordered-indexes #(cond-> % (cph/frame-shape? %) (assoc :hide-in-viewer true)))
|
||||
(pcb/change-parent frame-id to-move-shapes 0)
|
||||
(cond-> (ctl/grid-layout? objects frame-id)
|
||||
(pcb/update-shapes [frame-id] ctl/assign-cells))))]
|
||||
|
|
|
@ -354,12 +354,14 @@
|
|||
:fn #(st/emit! (dw/select-all))}
|
||||
|
||||
:toggle-grid {:tooltip (ds/meta "'")
|
||||
:command (ds/c-mod "'")
|
||||
;;https://github.com/ccampbell/mousetrap/issues/85
|
||||
:command [(ds/c-mod "'") (ds/c-mod "219")]
|
||||
:subsections [:main-menu]
|
||||
:fn #(st/emit! (toggle-layout-flag :display-grid))}
|
||||
|
||||
:toggle-snap-grid {:tooltip (ds/meta-shift "'")
|
||||
:command (ds/c-mod "shift+'")
|
||||
;;https://github.com/ccampbell/mousetrap/issues/85
|
||||
:command [(ds/c-mod "shift+'") (ds/c-mod "shift+219")]
|
||||
:subsections [:main-menu]
|
||||
:fn #(st/emit! (toggle-layout-flag :snap-grid))}
|
||||
|
||||
|
|
|
@ -164,11 +164,13 @@
|
|||
(cond-> shape
|
||||
(get-in shape [:svg-attrs :opacity])
|
||||
(-> (update :svg-attrs dissoc :opacity)
|
||||
(assoc :opacity (get-in shape [:svg-attrs :opacity])))
|
||||
(assoc :opacity (-> (get-in shape [:svg-attrs :opacity])
|
||||
(d/parse-double))))
|
||||
|
||||
(get-in shape [:svg-attrs :style :opacity])
|
||||
(-> (update-in [:svg-attrs :style] dissoc :opacity)
|
||||
(assoc :opacity (get-in shape [:svg-attrs :style :opacity])))
|
||||
(assoc :opacity (-> (get-in shape [:svg-attrs :style :opacity])
|
||||
(d/parse-double))))
|
||||
|
||||
|
||||
(get-in shape [:svg-attrs :mix-blend-mode])
|
||||
|
@ -410,7 +412,8 @@
|
|||
(assoc :strokes [])
|
||||
(assoc :svg-defs (select-keys (:defs svg-data) references))
|
||||
(setup-fill)
|
||||
(setup-stroke))
|
||||
(setup-stroke)
|
||||
(setup-opacity))
|
||||
|
||||
shape (cond-> shape
|
||||
hidden (assoc :hidden true))
|
||||
|
|
|
@ -377,7 +377,7 @@
|
|||
(assoc-in [:workspace-local :edition] (-> selected first :id)))))))
|
||||
|
||||
(defn not-changed? [old-dim new-dim]
|
||||
(> (mth/abs (- old-dim new-dim)) 1))
|
||||
(> (mth/abs (- old-dim new-dim)) 0.1))
|
||||
|
||||
(defn commit-resize-text
|
||||
[]
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
(defn start-resize
|
||||
"Enter mouse resize mode, until mouse button is released."
|
||||
[handler ids shape]
|
||||
(letfn [(resize
|
||||
(letfn [(resize
|
||||
[shape initial layout [point lock? center? point-snap]]
|
||||
(let [{:keys [width height]} (:selrect shape)
|
||||
{:keys [rotation]} shape
|
||||
|
@ -745,13 +745,16 @@
|
|||
(remove (fn [shape]
|
||||
(and (ctl/layout-absolute? shape)
|
||||
(= frame-id (:parent-id shape))))))
|
||||
moving-shapes-ids
|
||||
(map :id moving-shapes)
|
||||
|
||||
changes
|
||||
(-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects)
|
||||
;; Remove layout-item properties when moving a shape outside a layout
|
||||
(cond-> (not (ctl/any-layout? objects frame-id))
|
||||
(pcb/update-shapes (map :id moving-shapes) ctl/remove-layout-item-data))
|
||||
(pcb/update-shapes moving-shapes-ids ctl/remove-layout-item-data))
|
||||
(pcb/update-shapes moving-shapes-ids #(cond-> % (cph/frame-shape? %) (assoc :hide-in-viewer true)))
|
||||
(pcb/change-parent frame-id moving-shapes drop-index)
|
||||
(pcb/remove-objects empty-parents))]
|
||||
|
||||
|
|
|
@ -269,7 +269,8 @@
|
|||
{:option-name (tr "labels.rename")
|
||||
:id "file-rename"
|
||||
:option-handler on-edit
|
||||
:data-test "file-rename"}
|
||||
:data-test "file-rename"})
|
||||
(when (not is-search-page?)
|
||||
{:option-name (tr "dashboard.duplicate")
|
||||
:id "file-duplicate"
|
||||
:option-handler on-duplicate
|
||||
|
|
|
@ -144,6 +144,7 @@
|
|||
|
||||
create-file
|
||||
(mf/use-fn
|
||||
(mf/deps project)
|
||||
(fn [origin]
|
||||
(st/emit! (with-meta (dd/create-file {:project-id (:id project)})
|
||||
{::ev/origin origin}))))]
|
||||
|
|
|
@ -269,7 +269,9 @@
|
|||
(mf/use-fn
|
||||
(mf/deps file)
|
||||
(fn [name]
|
||||
(st/emit! (dd/rename-file (assoc file :name name)))
|
||||
(let [name (str/trim name)]
|
||||
(when (not= name "")
|
||||
(st/emit! (dd/rename-file (assoc file :name name)))))
|
||||
(swap! local assoc :edition false)))
|
||||
|
||||
on-edit
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
[app.util.keyboard :as kbd]
|
||||
[app.util.webapi :as wapi]
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[potok.core :as ptk]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
|
@ -61,9 +62,11 @@
|
|||
(->> files
|
||||
(mapv
|
||||
(fn [file]
|
||||
(cond-> file
|
||||
(= (:file-id file) file-id)
|
||||
(assoc :name new-name))))))
|
||||
(let [new-name (str/trim new-name)]
|
||||
(cond-> file
|
||||
(and (= (:file-id file) file-id)
|
||||
(not= "" new-name))
|
||||
(assoc :name new-name)))))))
|
||||
|
||||
(defn remove-file [files file-id]
|
||||
(->> files
|
||||
|
@ -378,7 +381,7 @@
|
|||
|
||||
[:div.feedback-banner
|
||||
[:div.icon i/checkbox-checked]
|
||||
[:div.message (tr "dashboard.import.import-message" (if (some? template) 1 success-files))]]))
|
||||
[:div.message (tr "dashboard.import.import-message" (i18n/c (if (some? template) 1 success-files)))]]))
|
||||
|
||||
(for [file files]
|
||||
(let [editing? (and (some? (:file-id file))
|
||||
|
|
|
@ -362,8 +362,13 @@
|
|||
(reverse))
|
||||
recent-map (mf/deref recent-files-ref)
|
||||
props (some-> profile (get :props {}))
|
||||
team-hero? (and (:team-hero? props true)
|
||||
(not (:is-default team)))
|
||||
you-owner? (get-in team [:permissions :is-owner])
|
||||
you-admin? (get-in team [:permissions :is-admin])
|
||||
can-invite? (or you-owner? you-admin?)
|
||||
team-hero? (and can-invite?
|
||||
(:team-hero? props true)
|
||||
(not (:is-default team)))
|
||||
|
||||
tutorial-viewed? (:viewed-tutorial? props true)
|
||||
walkthrough-viewed? (:viewed-walkthrough? props true)
|
||||
|
||||
|
|
|
@ -177,7 +177,9 @@
|
|||
:on-submit on-submit}]]
|
||||
|
||||
[:div.action-buttons
|
||||
[:& fm/submit-button {:label (tr "modals.invite-member-confirm.accept")}]]]]))
|
||||
[:& fm/submit-button {:label (tr "modals.invite-member-confirm.accept")
|
||||
:disabled (and (boolean (some current-data-emails current-members-emails))
|
||||
(empty? (remove current-members-emails current-data-emails)))}]]]]))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; MEMBERS SECTION
|
||||
|
@ -586,7 +588,8 @@
|
|||
[:div.empty-invitations
|
||||
[:span (tr "labels.no-invitations")]
|
||||
(when can-invite?
|
||||
[:span (tr "labels.no-invitations-hint")])])
|
||||
[:& i18n/tr-html {:label "labels.no-invitations-hint"
|
||||
:tag-name "span"}])])
|
||||
|
||||
(mf/defc invitation-section
|
||||
[{:keys [team invitations] :as props}]
|
||||
|
@ -897,6 +900,10 @@
|
|||
|
||||
stats (mf/deref refs/dashboard-team-stats)
|
||||
|
||||
you-owner? (get-in team [:permissions :is-owner])
|
||||
you-admin? (get-in team [:permissions :is-admin])
|
||||
can-edit? (or you-owner? you-admin?)
|
||||
|
||||
on-image-click
|
||||
(mf/use-callback #(dom/click (mf/ref-val finput)))
|
||||
|
||||
|
@ -928,12 +935,14 @@
|
|||
[:div.label (tr "dashboard.team-info")]
|
||||
[:div.name (:name team)]
|
||||
[:div.icon
|
||||
[:span.update-overlay {:on-click on-image-click} i/image]
|
||||
(when can-edit?
|
||||
[:span.update-overlay {:on-click on-image-click} i/image])
|
||||
[:img {:src (cfg/resolve-team-photo-url team)}]
|
||||
[:& file-uploader {:accept "image/jpeg,image/png"
|
||||
:multi false
|
||||
:ref finput
|
||||
:on-selected on-file-selected}]]]
|
||||
(when can-edit?
|
||||
[:& file-uploader {:accept "image/jpeg,image/png"
|
||||
:multi false
|
||||
:ref finput
|
||||
:on-selected on-file-selected}])]]
|
||||
|
||||
[:div.block.owner-block
|
||||
[:div.label (tr "dashboard.team-members")]
|
||||
|
|
|
@ -185,6 +185,7 @@
|
|||
:auto-focus? true
|
||||
:trim true
|
||||
:valid-item-fn us/parse-email
|
||||
:caution-item-fn #{}
|
||||
:on-submit on-submit
|
||||
:label (tr "modals.invite-member.emails")}]]
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
[app.main.ui.releases.v1-15]
|
||||
[app.main.ui.releases.v1-16]
|
||||
[app.main.ui.releases.v1-17]
|
||||
[app.main.ui.releases.v1-18]
|
||||
[app.main.ui.releases.v1-4]
|
||||
[app.main.ui.releases.v1-5]
|
||||
[app.main.ui.releases.v1-6]
|
||||
|
@ -87,4 +88,4 @@
|
|||
|
||||
(defmethod rc/render-release-notes "0.0"
|
||||
[params]
|
||||
(rc/render-release-notes (assoc params :version "1.17")))
|
||||
(rc/render-release-notes (assoc params :version "1.18")))
|
||||
|
|
108
frontend/src/app/main/ui/releases/v1_18.cljs
Normal file
108
frontend/src/app/main/ui/releases/v1_18.cljs
Normal file
|
@ -0,0 +1,108 @@
|
|||
;; 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.releases.v1-18
|
||||
(:require
|
||||
[app.main.ui.releases.common :as c]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defmethod c/render-release-notes "1.18"
|
||||
[{:keys [slide klass next finish navigate version]}]
|
||||
(mf/html
|
||||
(case @slide
|
||||
:start
|
||||
[:div.modal-overlay
|
||||
[:div.animated {:class @klass}
|
||||
[:div.modal-container.onboarding.feature
|
||||
[:div.modal-left
|
||||
[:img {:src "images/onboarding-version.jpg" :border "0" :alt "What's new release 1.18"}]]
|
||||
[:div.modal-right
|
||||
[:div.modal-title
|
||||
[:h2 "What's new?"]]
|
||||
[:span.release "Version " version]
|
||||
[:div.modal-content
|
||||
[:p "On this 1.18 release we make Flex Layout even more powerful with smart spacing, absolute position and z-index management."]
|
||||
[:p "We also continued implementing accessibility improvements to make Penpot more inclusive and published stability and performance enhancements."]]
|
||||
[:div.modal-navigation
|
||||
[:button.btn-secondary {:on-click next} "Continue"]]]
|
||||
[:img.deco {:src "images/deco-left.png" :border "0"}]
|
||||
[:img.deco.right {:src "images/deco-right.png" :border "0"}]]]]
|
||||
|
||||
0
|
||||
[:div.modal-overlay
|
||||
[:div.animated {:class @klass}
|
||||
[:div.modal-container.onboarding.feature
|
||||
[:div.modal-left
|
||||
[:img {:src "images/features/1.18-spacing.gif" :border "0" :alt "Spacing management"}]]
|
||||
[:div.modal-right
|
||||
[:div.modal-title
|
||||
[:h2 "Spacing management for Flex layoutFlex-Layout"]]
|
||||
[:div.modal-content
|
||||
[:p "Managing Flex Layout spacing is much more intuitive now. Visualize paddings, margins and gaps and drag to resize them."]
|
||||
[:p "And not only that, when creating Flex layouts, the spacing is predicted, helping you to maintain your design composition."]]
|
||||
[:div.modal-navigation
|
||||
[:button.btn-secondary {:on-click next} "Continue"]
|
||||
[:& c/navigation-bullets
|
||||
{:slide @slide
|
||||
:navigate navigate
|
||||
:total 4}]]]]]]
|
||||
|
||||
1
|
||||
[:div.modal-overlay
|
||||
[:div.animated {:class @klass}
|
||||
[:div.modal-container.onboarding.feature
|
||||
[:div.modal-left
|
||||
[:img {:src "images/features/1.18-absolute.gif" :border "0" :alt "Position absolute feature"}]]
|
||||
[:div.modal-right
|
||||
[:div.modal-title
|
||||
[:h2 "Absolute position elements in Flex layout"]]
|
||||
[:div.modal-content
|
||||
[:p "Sometimes you need to freely position an element in a specific place regardless of the size of the layout where it belongs."]
|
||||
[:p "Now you can exclude elements from the Flex layout flow using absolute position."]]
|
||||
[:div.modal-navigation
|
||||
[:button.btn-secondary {:on-click next} "Continue"]
|
||||
[:& c/navigation-bullets
|
||||
{:slide @slide
|
||||
:navigate navigate
|
||||
:total 4}]]]]]]
|
||||
|
||||
2
|
||||
[:div.modal-overlay
|
||||
[:div.animated {:class @klass}
|
||||
[:div.modal-container.onboarding.feature
|
||||
[:div.modal-left
|
||||
[:img {:src "images/features/1.18-z-index.gif" :border "0" :alt "Z-index feature"}]]
|
||||
[:div.modal-right
|
||||
[:div.modal-title
|
||||
[:h2 "More on Flex layout: z-index"]]
|
||||
[:div.modal-content
|
||||
[:p "With the new z-index option you can decide the order of overlapping elements while maintaining the layers order."]
|
||||
[:p "This is another capability that brings Penpot Flex layout even closer to the power of CSS standards."]]
|
||||
[:div.modal-navigation
|
||||
[:button.btn-secondary {:on-click next} "Continue"]
|
||||
[:& c/navigation-bullets
|
||||
{:slide @slide
|
||||
:navigate navigate
|
||||
:total 4}]]]]]]
|
||||
|
||||
3
|
||||
[:div.modal-overlay
|
||||
[:div.animated {:class @klass}
|
||||
[:div.modal-container.onboarding.feature
|
||||
[:div.modal-left
|
||||
[:img {:src "images/features/1.18-scale.gif" :border "0" :alt "Scale content proportionally"}]]
|
||||
[:div.modal-right
|
||||
[:div.modal-title
|
||||
[:h2 "Scale content proportionally affects strokes, shadows, blurs and corners"]]
|
||||
[:div.modal-content
|
||||
[:p "Now you can resize your layers and groups preserving their aspect ratio while scaling their properties proportionally, including strokes, shadows, blurs and corners."]
|
||||
[:p "Activate the scale tool by pressing K and scale your elements, maintaining their visual aspect."]]
|
||||
[:div.modal-navigation
|
||||
[:button.btn-secondary {:on-click finish} "Start!"]
|
||||
[:& c/navigation-bullets
|
||||
{:slide @slide
|
||||
:navigate navigate
|
||||
:total 4}]]]]]])))
|
|
@ -38,20 +38,27 @@
|
|||
[:use {:href (str "#" shape-id)}]]))
|
||||
|
||||
(mf/defc outer-stroke-mask
|
||||
[{:keys [shape render-id index]}]
|
||||
[{:keys [shape stroke render-id index]}]
|
||||
(let [suffix (if index (str "-" index) "")
|
||||
stroke-mask-id (str "outer-stroke-" render-id "-" (:id shape) suffix)
|
||||
shape-id (str "stroke-shape-" render-id "-" (:id shape) suffix)
|
||||
stroke-width (case (:stroke-alignment shape :center)
|
||||
:center (/ (:stroke-width shape 0) 2)
|
||||
:outer (:stroke-width shape 0)
|
||||
stroke-width (case (:stroke-alignment stroke :center)
|
||||
:center (/ (:stroke-width stroke 0) 2)
|
||||
:outer (:stroke-width stroke 0)
|
||||
0)
|
||||
margin (gsb/shape-stroke-margin shape stroke-width)
|
||||
bounding-box (-> (gsh/points->selrect (:points shape))
|
||||
(update :x - (+ stroke-width margin))
|
||||
(update :y - (+ stroke-width margin))
|
||||
(update :width + (* 2 (+ stroke-width margin)))
|
||||
(update :height + (* 2 (+ stroke-width margin))))]
|
||||
margin (gsb/shape-stroke-margin stroke stroke-width)
|
||||
|
||||
selrect
|
||||
(if (cph/text-shape? shape)
|
||||
(gsh/position-data-selrect shape)
|
||||
(gsh/points->selrect (:points shape)))
|
||||
|
||||
bounding-box
|
||||
(-> selrect
|
||||
(update :x - (+ stroke-width margin))
|
||||
(update :y - (+ stroke-width margin))
|
||||
(update :width + (* 2 (+ stroke-width margin)))
|
||||
(update :height + (* 2 (+ stroke-width margin))))]
|
||||
|
||||
[:mask {:id stroke-mask-id
|
||||
:x (:x bounding-box)
|
||||
|
@ -67,17 +74,17 @@
|
|||
:stroke "none"}}]]))
|
||||
|
||||
(mf/defc cap-markers
|
||||
[{:keys [shape render-id index]}]
|
||||
[{:keys [stroke render-id index]}]
|
||||
(let [marker-id-prefix (str "marker-" render-id)
|
||||
cap-start (:stroke-cap-start shape)
|
||||
cap-end (:stroke-cap-end shape)
|
||||
cap-start (:stroke-cap-start stroke)
|
||||
cap-end (:stroke-cap-end stroke)
|
||||
|
||||
stroke-color (if (:stroke-color-gradient shape)
|
||||
stroke-color (if (:stroke-color-gradient stroke)
|
||||
(str/format "url(#%s)" (str "stroke-color-gradient_" render-id "_" index))
|
||||
(:stroke-color shape))
|
||||
(:stroke-color stroke))
|
||||
|
||||
stroke-opacity (when-not (:stroke-color-gradient shape)
|
||||
(:stroke-opacity shape))]
|
||||
stroke-opacity (when-not (:stroke-color-gradient stroke)
|
||||
(:stroke-opacity stroke))]
|
||||
|
||||
[:*
|
||||
(when (or (= cap-start :line-arrow) (= cap-end :line-arrow))
|
||||
|
@ -169,36 +176,37 @@
|
|||
[:rect {:x 3 :y 2.5 :width 0.5 :height 1}]])]))
|
||||
|
||||
(mf/defc stroke-defs
|
||||
[{:keys [shape render-id index]}]
|
||||
[{:keys [shape stroke render-id index]}]
|
||||
|
||||
(let [open-path? (and (= :path (:type shape)) (gsh/open-path? shape))]
|
||||
[:*
|
||||
(cond (some? (:stroke-color-gradient shape))
|
||||
(case (:type (:stroke-color-gradient shape))
|
||||
(cond (some? (:stroke-color-gradient stroke))
|
||||
(case (:type (:stroke-color-gradient stroke))
|
||||
:linear [:> grad/linear-gradient #js {:id (str (name :stroke-color-gradient) "_" render-id "_" index)
|
||||
:gradient (:stroke-color-gradient shape)
|
||||
:gradient (:stroke-color-gradient stroke)
|
||||
:shape shape}]
|
||||
:radial [:> grad/radial-gradient #js {:id (str (name :stroke-color-gradient) "_" render-id "_" index)
|
||||
:gradient (:stroke-color-gradient shape)
|
||||
:gradient (:stroke-color-gradient stroke)
|
||||
:shape shape}]))
|
||||
(cond
|
||||
(and (not open-path?)
|
||||
(= :inner (:stroke-alignment shape :center))
|
||||
(> (:stroke-width shape 0) 0))
|
||||
(= :inner (:stroke-alignment stroke :center))
|
||||
(> (:stroke-width stroke 0) 0))
|
||||
[:& inner-stroke-clip-path {:shape shape
|
||||
:render-id render-id
|
||||
:index index}]
|
||||
|
||||
(and (not open-path?)
|
||||
(= :outer (:stroke-alignment shape :center))
|
||||
(> (:stroke-width shape 0) 0))
|
||||
(= :outer (:stroke-alignment stroke :center))
|
||||
(> (:stroke-width stroke 0) 0))
|
||||
[:& outer-stroke-mask {:shape shape
|
||||
:stroke stroke
|
||||
:render-id render-id
|
||||
:index index}]
|
||||
|
||||
(or (some? (:stroke-cap-start shape))
|
||||
(some? (:stroke-cap-end shape)))
|
||||
[:& cap-markers {:shape shape
|
||||
(or (some? (:stroke-cap-start stroke))
|
||||
(some? (:stroke-cap-end stroke)))
|
||||
[:& cap-markers {:stroke stroke
|
||||
:render-id render-id
|
||||
:index index}])]))
|
||||
|
||||
|
@ -216,8 +224,9 @@
|
|||
base-props (obj/get child "props")
|
||||
elem-name (obj/get child "type")
|
||||
shape (obj/get props "shape")
|
||||
stroke (obj/get props "stroke")
|
||||
index (obj/get props "index")
|
||||
stroke-width (:stroke-width shape)
|
||||
stroke-width (:stroke-width stroke)
|
||||
|
||||
suffix (if index (str "-" index) "")
|
||||
stroke-mask-id (str "outer-stroke-" render-id "-" (:id shape) suffix)
|
||||
|
@ -225,7 +234,7 @@
|
|||
|
||||
[:g.outer-stroke-shape
|
||||
[:defs
|
||||
[:& stroke-defs {:shape shape :render-id render-id :index index}]
|
||||
[:& stroke-defs {:shape shape :stroke stroke :render-id render-id :index index}]
|
||||
[:> elem-name (-> (obj/clone base-props)
|
||||
(obj/set! "id" shape-id)
|
||||
(obj/set!
|
||||
|
@ -258,10 +267,11 @@
|
|||
base-props (obj/get child "props")
|
||||
elem-name (obj/get child "type")
|
||||
shape (obj/get props "shape")
|
||||
stroke (obj/get props "stroke")
|
||||
index (obj/get props "index")
|
||||
transform (obj/get base-props "transform")
|
||||
|
||||
stroke-width (:stroke-width shape 0)
|
||||
stroke-width (:stroke-width stroke 0)
|
||||
|
||||
suffix (if index (str "-" index) "")
|
||||
clip-id (str "inner-stroke-" render-id "-" (:id shape) suffix)
|
||||
|
@ -275,7 +285,7 @@
|
|||
|
||||
[:g.inner-stroke-shape {:transform transform}
|
||||
[:defs
|
||||
[:& stroke-defs {:shape shape :render-id render-id :index index}]
|
||||
[:& stroke-defs {:shape shape :stroke stroke :render-id render-id :index index}]
|
||||
[:> elem-name shape-props]]
|
||||
|
||||
[:use {:href (str "#" shape-id)
|
||||
|
@ -290,33 +300,34 @@
|
|||
{::mf/wrap-props false}
|
||||
[props]
|
||||
|
||||
(let [child (obj/get props "children")
|
||||
shape (obj/get props "shape")
|
||||
(let [child (obj/get props "children")
|
||||
shape (obj/get props "shape")
|
||||
stroke (obj/get props "stroke")
|
||||
|
||||
render-id (mf/use-ctx muc/render-id)
|
||||
index (obj/get props "index")
|
||||
stroke-width (:stroke-width shape 0)
|
||||
stroke-style (:stroke-style shape :none)
|
||||
stroke-position (:stroke-alignment shape :center)
|
||||
stroke-width (:stroke-width stroke 0)
|
||||
stroke-style (:stroke-style stroke :none)
|
||||
stroke-position (:stroke-alignment stroke :center)
|
||||
has-stroke? (and (> stroke-width 0)
|
||||
(not= stroke-style :none))
|
||||
closed? (or (not= :path (:type shape))
|
||||
(not (gsh/open-path? shape)))
|
||||
closed? (or (not= :path (:type shape)) (not (gsh/open-path? shape)))
|
||||
inner? (= :inner stroke-position)
|
||||
outer? (= :outer stroke-position)]
|
||||
|
||||
(cond
|
||||
(and has-stroke? inner? closed?)
|
||||
[:& inner-stroke {:shape shape :index index}
|
||||
[:& inner-stroke {:shape shape :stroke stroke :index index}
|
||||
child]
|
||||
|
||||
(and has-stroke? outer? closed?)
|
||||
[:& outer-stroke {:shape shape :index index}
|
||||
[:& outer-stroke {:shape shape :stroke stroke :index index}
|
||||
child]
|
||||
|
||||
:else
|
||||
[:g.stroke-shape
|
||||
[:defs
|
||||
[:& stroke-defs {:shape shape :render-id render-id :index index}]]
|
||||
[:& stroke-defs {:shape shape :stroke stroke :render-id render-id :index index}]]
|
||||
child])))
|
||||
|
||||
(defn build-fill-props [shape child position render-id]
|
||||
|
@ -426,6 +437,7 @@
|
|||
[props]
|
||||
(let [child (obj/get props "children")
|
||||
shape (obj/get props "shape")
|
||||
|
||||
elem-name (obj/get child "type")
|
||||
render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-id))
|
||||
stroke-id (dm/fmt "strokes-%" (:id shape))
|
||||
|
@ -445,9 +457,8 @@
|
|||
(d/not-empty? (:strokes shape))
|
||||
[:> :g stroke-props
|
||||
(for [[index value] (-> (d/enumerate (:strokes shape)) reverse)]
|
||||
(let [props (build-stroke-props index child value render-id)
|
||||
shape (assoc value :points (:points shape))]
|
||||
[:& shape-custom-stroke {:shape shape :index index :key (dm/str index "-" stroke-id)}
|
||||
(let [props (build-stroke-props index child value render-id)]
|
||||
[:& shape-custom-stroke {:shape shape :stroke value :index index :key (dm/str index "-" stroke-id)}
|
||||
[:> elem-name props]]))])]))
|
||||
|
||||
(mf/defc shape-custom-strokes
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
|
||||
(defn- activate-interaction
|
||||
[interaction shape base-frame frame-offset objects overlays]
|
||||
|
||||
(case (:action-type interaction)
|
||||
:navigate
|
||||
(when-let [frame-id (:destination interaction)]
|
||||
|
@ -69,6 +70,7 @@
|
|||
overlays-ids (set (map :id overlays))
|
||||
relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame)
|
||||
position (ctsi/calc-overlay-position interaction
|
||||
shape
|
||||
viewer-objects
|
||||
relative-to-shape
|
||||
relative-to-base-frame
|
||||
|
@ -91,6 +93,7 @@
|
|||
overlays-ids (set (map :id overlays))
|
||||
relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame)
|
||||
position (ctsi/calc-overlay-position interaction
|
||||
shape
|
||||
objects
|
||||
relative-to-shape
|
||||
relative-to-base-frame
|
||||
|
@ -156,6 +159,7 @@
|
|||
overlays-ids (set (map :id overlays))
|
||||
relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame)
|
||||
position (ctsi/calc-overlay-position interaction
|
||||
shape
|
||||
objects
|
||||
relative-to-shape
|
||||
relative-to-base-frame
|
||||
|
|
|
@ -67,7 +67,8 @@
|
|||
(mf/use-fn
|
||||
(mf/deps current-color @drag?)
|
||||
(fn [color]
|
||||
(when (not= (str/lower (:hex color)) (str/lower (:hex current-color)))
|
||||
(when (or (not= (str/lower (:hex color)) (str/lower (:hex current-color)))
|
||||
(not= (:h color) (:h current-color)))
|
||||
(let [recent-color (merge current-color color)
|
||||
recent-color (dc/materialize-color-components recent-color)]
|
||||
(st/emit! (dc/update-colorpicker-color recent-color (not @drag?)))))))
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.colors :as dc]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.data.workspace.shortcuts :as sc]
|
||||
[app.main.refs :as refs]
|
||||
|
@ -32,6 +33,7 @@
|
|||
[app.util.keyboard :as kbd]
|
||||
[app.util.router :as rt]
|
||||
[beicon.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
[okulary.core :as l]
|
||||
[potok.core :as ptk]
|
||||
[rumext.v2 :as mf]))
|
||||
|
@ -149,10 +151,12 @@
|
|||
:on-accept #(st/emit! (dwl/set-file-shared (:id file) false))
|
||||
:count-libraries 1}))))
|
||||
|
||||
handle-blur (fn [_]
|
||||
(let [value (-> edit-input-ref mf/ref-val dom/get-value)]
|
||||
(st/emit! (dw/rename-file (:id file) value)))
|
||||
(reset! editing? false))
|
||||
handle-blur
|
||||
(fn [_]
|
||||
(let [value (str/trim (-> edit-input-ref mf/ref-val dom/get-value))]
|
||||
(when (not= value "")
|
||||
(st/emit! (dw/rename-file (:id file) value))))
|
||||
(reset! editing? false))
|
||||
|
||||
handle-name-keydown (fn [event]
|
||||
(when (kbd/enter? event)
|
||||
|
@ -306,12 +310,14 @@
|
|||
[:li {:on-click #(st/emit! (dw/select-all))}
|
||||
[:span (tr "workspace.header.menu.select-all")]
|
||||
[:span.shortcut (sc/get-tooltip :select-all)]]
|
||||
[:li {:on-click #(st/emit! (toggle-flag :scale-text))}
|
||||
[:span
|
||||
(if (contains? layout :scale-text)
|
||||
(tr "workspace.header.menu.disable-scale-text")
|
||||
(tr "workspace.header.menu.enable-scale-text"))]
|
||||
[:span.shortcut (sc/get-tooltip :toggle-scale-text)]]]]
|
||||
|
||||
[:li {:on-click #(st/emit! dwc/undo)}
|
||||
[:span (tr "workspace.header.menu.undo")]
|
||||
[:span.shortcut (sc/get-tooltip :undo)]]
|
||||
|
||||
[:li {:on-click #(st/emit! dwc/redo)}
|
||||
[:span (tr "workspace.header.menu.redo")]
|
||||
[:span.shortcut (sc/get-tooltip :redo)]]]]
|
||||
|
||||
[:& dropdown {:show (= @show-sub-menu? :view)
|
||||
:on-close #(reset! show-sub-menu? false)}
|
||||
|
@ -374,6 +380,13 @@
|
|||
[:& dropdown {:show (= @show-sub-menu? :preferences)
|
||||
:on-close #(reset! show-sub-menu? false)}
|
||||
[:ul.sub-menu.preferences
|
||||
[:li {:on-click #(st/emit! (toggle-flag :scale-text))}
|
||||
[:span
|
||||
(if (contains? layout :scale-text)
|
||||
(tr "workspace.header.menu.disable-scale-content")
|
||||
(tr "workspace.header.menu.enable-scale-content"))]
|
||||
[:span.shortcut (sc/get-tooltip :toggle-scale-text)]]
|
||||
|
||||
[:li {:on-click #(st/emit! (toggle-flag :snap-guides))}
|
||||
[:span
|
||||
(if (contains? layout :snap-guides)
|
||||
|
|
|
@ -66,7 +66,8 @@
|
|||
(when (contains? #{:auto-height :auto-width} grow-type)
|
||||
(let [{:keys [width height]}
|
||||
(-> (dom/query node ".paragraph-set")
|
||||
(dom/get-client-size))
|
||||
(dom/get-bounding-rect))
|
||||
|
||||
width (mth/ceil width)
|
||||
height (mth/ceil height)]
|
||||
(when (and (not (mth/almost-zero? width))
|
||||
|
|
|
@ -161,27 +161,37 @@
|
|||
|
||||
on-change
|
||||
(mf/use-fn
|
||||
(fn [new-color old-color]
|
||||
(fn [new-color old-color from-picker?]
|
||||
(let [old-color (-> old-color
|
||||
(dissoc :name)
|
||||
(dissoc :path)
|
||||
(d/without-nils))
|
||||
|
||||
prev-color (-> @prev-color*
|
||||
(dissoc :name)
|
||||
(dissoc :path)
|
||||
(d/without-nils))
|
||||
prev-color (when @prev-color*
|
||||
(-> @prev-color*
|
||||
(dissoc :name)
|
||||
(dissoc :path)
|
||||
(d/without-nils)))
|
||||
|
||||
;; When dragging on the color picker sometimes all the shapes hasn't updated the color to the prev value so we need this extra calculation
|
||||
shapes-by-old-color (get @grouped-colors* old-color)
|
||||
shapes-by-prev-color (get @grouped-colors* prev-color)
|
||||
shapes-by-color (or shapes-by-prev-color shapes-by-old-color)]
|
||||
(reset! prev-color* new-color)
|
||||
(st/emit! (dc/change-color-in-selected new-color shapes-by-color old-color)))))
|
||||
|
||||
on-open (mf/use-fn
|
||||
(fn [color]
|
||||
(reset! prev-color* color)))
|
||||
(when from-picker?
|
||||
(reset! prev-color* new-color))
|
||||
|
||||
(st/emit! (dc/change-color-in-selected new-color shapes-by-color (or prev-color old-color))))))
|
||||
|
||||
on-open
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(reset! prev-color* nil)))
|
||||
|
||||
on-close
|
||||
(mf/use-fn
|
||||
(fn []
|
||||
(reset! prev-color* nil)))
|
||||
|
||||
on-detach
|
||||
(mf/use-fn
|
||||
|
@ -212,8 +222,9 @@
|
|||
:index index
|
||||
:on-detach on-detach
|
||||
:select-only select-only
|
||||
:on-change #(on-change % color)
|
||||
:on-open on-open}])
|
||||
:on-change #(on-change %1 color %2)
|
||||
:on-open on-open
|
||||
:on-close on-close}])
|
||||
(when (and (false? @expand-lib-color) (< 3 (count library-colors)))
|
||||
[:div.expand-colors {:on-click #(reset! expand-lib-color true)}
|
||||
[:span i/actions]
|
||||
|
@ -225,8 +236,9 @@
|
|||
:index index
|
||||
:on-detach on-detach
|
||||
:select-only select-only
|
||||
:on-change #(on-change % color)
|
||||
:on-open on-open}]))]
|
||||
:on-change #(on-change %1 color %2)
|
||||
:on-open on-open
|
||||
:on-close on-close}]))]
|
||||
|
||||
[:div.selected-colors
|
||||
(for [[index color] (d/enumerate (take 3 colors))]
|
||||
|
@ -234,8 +246,9 @@
|
|||
:color color
|
||||
:index index
|
||||
:select-only select-only
|
||||
:on-change #(on-change % color)
|
||||
:on-open on-open}])
|
||||
:on-change #(on-change %1 color %2)
|
||||
:on-open on-open
|
||||
:on-close on-close}])
|
||||
(when (and (false? @expand-color) (< 3 (count colors)))
|
||||
[:div.expand-colors {:on-click #(reset! expand-color true)}
|
||||
[:span i/actions]
|
||||
|
@ -246,5 +259,6 @@
|
|||
:color color
|
||||
:index index
|
||||
:select-only select-only
|
||||
:on-change #(on-change % color)
|
||||
:on-open on-open}]))]]])))
|
||||
:on-change #(on-change %1 color %2)
|
||||
:on-open on-open
|
||||
:on-close on-close}]))]]])))
|
||||
|
|
|
@ -113,7 +113,8 @@
|
|||
:y y
|
||||
:disable-gradient disable-gradient
|
||||
:disable-opacity disable-opacity
|
||||
:on-change #(on-change (merge uc/empty-color %))
|
||||
;; on-change second parameter means if the source is the color-picker
|
||||
:on-change #(on-change (merge uc/empty-color %) true)
|
||||
:on-close (fn [value opacity id file-id]
|
||||
(when on-close
|
||||
(on-close value opacity id file-id)))
|
||||
|
|
|
@ -46,4 +46,5 @@
|
|||
(defn camelize
|
||||
[str]
|
||||
;; str.replace(":", "-").replace(/-./g, x=>x[1].toUpperCase())
|
||||
(js* "~{}.replace(\":\", \"-\").replace(/-./g, x=>x[1].toUpperCase())", str))
|
||||
(when (not (nil? str))
|
||||
(js* "~{}.replace(\":\", \"-\").replace(/-./g, x=>x[1].toUpperCase())", str)))
|
||||
|
|
|
@ -1304,6 +1304,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "لا توجد دعوات."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr "اضغط على الزر \"دعوة إلى الفريق\" لدعوة المزيد من الأعضاء إلى هذا الفريق."
|
||||
|
||||
|
|
|
@ -1298,6 +1298,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "No hi ha invitacions."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr ""
|
||||
"Feu clic al botó «Convida a l'equip» per convidar més membres a aquest "
|
||||
|
|
|
@ -1197,6 +1197,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "Nejsou žádné pozvánky."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr ""
|
||||
"Chcete-li do tohoto týmu pozvat další členy, stiskněte tlačítko „Pozvat do "
|
||||
|
|
|
@ -1337,6 +1337,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "Es gibt keine Einladungen."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr ""
|
||||
"Drücken Sie die Schaltfläche \"Zum Team einladen\", um weitere Mitglieder "
|
||||
|
|
|
@ -443,7 +443,9 @@ msgid "dashboard.import.import-error"
|
|||
msgstr "There was a problem importing the file. The file wasn't imported."
|
||||
|
||||
msgid "dashboard.import.import-message"
|
||||
msgstr "%s files have been imported successfully."
|
||||
msgid_plural "dashboard.import.import-message"
|
||||
msgstr[0] "1 file has been imported successfully."
|
||||
msgstr[1] "%s files have been imported successfully."
|
||||
|
||||
msgid "dashboard.import.import-warning"
|
||||
msgstr "Some files containted invalid objects that have been removed."
|
||||
|
@ -1379,6 +1381,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "No pending invitations."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr "Click the **Invite people** button to invite people to this team."
|
||||
|
||||
|
@ -1723,21 +1726,22 @@ msgstr[1] "Delete files"
|
|||
msgid "modals.delete-shared-confirm.hint"
|
||||
msgid_plural "modals.delete-shared-confirm.hint"
|
||||
msgstr[0] ""
|
||||
"If you delete it, those assets will move to the local library of this file. "
|
||||
"Any unused assets will be lost."
|
||||
"If you delete it, those assets will no longer be available from other files. "
|
||||
"Assets that have already been used will remain in this file (no design will be broken!)."
|
||||
|
||||
msgstr[1] ""
|
||||
"If you delete them, those assets will move to the local library of this "
|
||||
"file. Any unused assets will be lost."
|
||||
"If you delete them, those assets will no longer be available from other files. "
|
||||
"Assets that have already been used will remain in this file (no design will be broken!)."
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "modals.delete-shared-confirm.hint-many"
|
||||
msgid_plural "modals.delete-shared-confirm.hint-many"
|
||||
msgstr[0] ""
|
||||
"If you delete it, those assets will move to the local libraries of these "
|
||||
"files. Any unused assets will be lost."
|
||||
"If you delete it, those assets will no longer be available from other files. "
|
||||
"Assets that have already been used will remain in these files (no design will be broken!)."
|
||||
msgstr[1] ""
|
||||
"If you delete them, those assets will move to the local libraries of these "
|
||||
"files. Any unused assets will be lost."
|
||||
"If you delete them, those assets will no longer be available from other files. "
|
||||
"Assets that have already been used will remain in these file (no design will be broken!)."
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "modals.delete-shared-confirm.message"
|
||||
|
@ -1925,21 +1929,21 @@ msgstr[1] "Unpublish"
|
|||
msgid "modals.unpublish-shared-confirm.hint"
|
||||
msgid_plural "modals.unpublish-shared-confirm.hint"
|
||||
msgstr[0] ""
|
||||
"If you unpublish it, those assets will move to the local library of this "
|
||||
"file."
|
||||
"If you unpublish it, those assets will no longer be available from other files. "
|
||||
"Assets that have already been used will remain in this file (no design will be broken!)."
|
||||
msgstr[1] ""
|
||||
"If you unpublish them, those assets will move to the local library of this "
|
||||
"file."
|
||||
"If you unpublish them, those assets will no longer be available from other files. "
|
||||
"Assets that have already been used will remain in this file (no design will be broken!)."
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "modals.unpublish-shared-confirm.hint-many"
|
||||
msgid_plural "modals.unpublish-shared-confirm.hint-many"
|
||||
msgstr[0] ""
|
||||
"If you unpublish it, those assets will move to the local libraries of these "
|
||||
"files."
|
||||
"If you unpublish it, those assets will no longer be available from other files. "
|
||||
"Assets that have already been used will remain in these files (no design will be broken!)."
|
||||
msgstr[1] ""
|
||||
"If you unpublish them, those assets will move to the local libraries of "
|
||||
"these files."
|
||||
"If you unpublish them, those assets will no longer be available from other files. "
|
||||
"Assets that have already been used will remain in these file (no design will be broken!)."
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "modals.unpublish-shared-confirm.message"
|
||||
|
@ -3052,6 +3056,18 @@ msgstr "Show rulers"
|
|||
msgid "workspace.header.menu.show-textpalette"
|
||||
msgstr "Show fonts palette"
|
||||
|
||||
msgid "workspace.header.menu.enable-scale-content"
|
||||
msgstr "Enable proportional scale"
|
||||
|
||||
msgid "workspace.header.menu.disable-scale-content"
|
||||
msgstr "Disable proportional scale"
|
||||
|
||||
msgid "workspace.header.menu.undo"
|
||||
msgstr "Undo"
|
||||
|
||||
msgid "workspace.header.menu.redo"
|
||||
msgstr "Redo"
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs
|
||||
msgid "workspace.header.reset-zoom"
|
||||
msgstr "Reset"
|
||||
|
|
|
@ -450,7 +450,9 @@ msgid "dashboard.import.import-error"
|
|||
msgstr "Hubo un problema importando el fichero. No ha podido ser importado."
|
||||
|
||||
msgid "dashboard.import.import-message"
|
||||
msgstr "%s files have been imported succesfully."
|
||||
msgid_plural "dashboard.import.import-message"
|
||||
msgstr[0] "1 fichero se ha importado correctamente."
|
||||
msgstr[1] "%s ficheros se han importado correctamente."
|
||||
|
||||
msgid "dashboard.import.import-warning"
|
||||
msgstr "Algunos ficheros contenían objetos erroneos que no han sido importados."
|
||||
|
@ -1450,6 +1452,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "No hay invitaciones."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr "Pulsa el botón 'Invitar al equipo' para añadir más integrantes al equipo."
|
||||
|
||||
|
@ -1806,21 +1809,21 @@ msgstr[1] "Borrar archivos"
|
|||
msgid "modals.delete-shared-confirm.hint"
|
||||
msgid_plural "modals.delete-shared-confirm.hint"
|
||||
msgstr[0] ""
|
||||
"Si lo borras, esos elementos pasarán a formar parte de la biblioteca local "
|
||||
"de este archivo. Cualquier elemento en desuso se perderá."
|
||||
"Si lo borras, sus elementos no estarán disponibles para otros archivos. "
|
||||
"Los elementos que hayan sido utilizados permanecerán en el archivo (¡ningún diseño se romperá!)."
|
||||
msgstr[1] ""
|
||||
"Si los borras, esos elementos pasarán a formar parte de la biblioteca local "
|
||||
"de este archivo. Cualquier elemento en desuso se perderá."
|
||||
"Si los borras, sus elementos no estarán disponibles para otros archivos. "
|
||||
"Los elementos que hayan sido utilizados permanecerán en el archivo (¡ningún diseño se romperá!)."
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "modals.delete-shared-confirm.hint-many"
|
||||
msgid_plural "modals.delete-shared-confirm.hint-many"
|
||||
msgstr[0] ""
|
||||
"Si lo borras, esos elementos pasarán a formar parte de las bibliotecas "
|
||||
"locales de estos archivos. Cualquier elemento en desuso se perderá."
|
||||
"Si lo borras, sus elementos no estarán disponibles para otros archivos. "
|
||||
"Los elementos que hayan sido utilizados permanecerán en los archivo (¡ningún diseño se romperá!)."
|
||||
msgstr[1] ""
|
||||
"Si los borras, esos elementos pasarán a formar parte de las bibliotecas "
|
||||
"locales de estos archivos. Cualquier elemento en desuso se perderá."
|
||||
"Si los borras, sus elementos no estarán disponibles para otros archivos. "
|
||||
"Los elementos que hayan sido utilizados permanecerán en los archivo (¡ningún diseño se romperá!)."
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "modals.delete-shared-confirm.message"
|
||||
|
@ -2011,21 +2014,22 @@ msgstr[1] "Despublicar"
|
|||
msgid "modals.unpublish-shared-confirm.hint"
|
||||
msgid_plural "modals.unpublish-shared-confirm.hint"
|
||||
msgstr[0] ""
|
||||
"Si la despublicas, esos elementos pasarán a formar parte de la biblioteca "
|
||||
"local de este archivo."
|
||||
"Si la despublicas, sus elementos no estarán disponibles para otros archivos. "
|
||||
"Los elementos que hayan sido utilizados permanecerán en el archivo (¡ningún diseño se romperá!)."
|
||||
|
||||
msgstr[1] ""
|
||||
"Si las despublicas, esos elementos pasarán a formar parte de la biblioteca "
|
||||
"local de este archivo."
|
||||
"Si las despublicas, sus elementos no estarán disponibles para otros archivos. "
|
||||
"Los elementos que hayan sido utilizados permanecerán en el archivo (¡ningún diseño se romperá!)."
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "modals.unpublish-shared-confirm.hint-many"
|
||||
msgid_plural "modals.unpublish-shared-confirm.hint-many"
|
||||
msgstr[0] ""
|
||||
"Si la despublicas, esos elementos pasarán a formar parte de las bibliotecas "
|
||||
"locales de estos archivos."
|
||||
"Si la despublicas, sus elementos no estarán disponibles para otros archivos. "
|
||||
"Los elementos que hayan sido utilizados permanecerán en los archivo (¡ningún diseño se romperá!)."
|
||||
msgstr[1] ""
|
||||
"Si las despublicas, esos elementos pasarán a formar parte de las "
|
||||
"bibliotecas locales de estos archivos."
|
||||
"Si las despublicas, sus elementos no estarán disponibles para otros archivos. "
|
||||
"Los elementos que hayan sido utilizados permanecerán en los archivo (¡ningún diseño se romperá!)."
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
|
||||
msgid "modals.unpublish-shared-confirm.message"
|
||||
|
@ -3215,6 +3219,18 @@ msgstr "Mostrar reglas"
|
|||
msgid "workspace.header.menu.show-textpalette"
|
||||
msgstr "Mostrar paleta de textos"
|
||||
|
||||
msgid "workspace.header.menu.enable-scale-content"
|
||||
msgstr "Activar escala proporcional"
|
||||
|
||||
msgid "workspace.header.menu.disable-scale-content"
|
||||
msgstr "Desactivar escala proporcional"
|
||||
|
||||
msgid "workspace.header.menu.undo"
|
||||
msgstr "Deshacer"
|
||||
|
||||
msgid "workspace.header.menu.redo"
|
||||
msgstr "Rehacer"
|
||||
|
||||
#: src/app/main/ui/workspace/header.cljs
|
||||
msgid "workspace.header.reset-zoom"
|
||||
msgstr "Restablecer"
|
||||
|
|
|
@ -1294,6 +1294,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "Ez dago gonbidapenik."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr "Sakatu 'Taldera gonbdiatu' taldekide gehiago izateko."
|
||||
|
||||
|
|
|
@ -1289,6 +1289,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "هیچ دعوتنامهای وجود ندارد."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr "دکمه \"دعوت به تیم\" را فشار دهید تا اعضای بیشتری را به این تیم دعوت کنید."
|
||||
|
||||
|
|
|
@ -1339,6 +1339,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "Il n'y a pas d'invitations."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr ""
|
||||
"Appuyez sur le bouton \"Inviter à l'équipe\" pour inviter d'autres membres "
|
||||
|
|
|
@ -1301,6 +1301,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "אין הזמנות."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr "לחיצה על הכפתור „הזמנה לצוות” תאפשר להזמין חברים נוספים לצוות הזה."
|
||||
|
||||
|
|
|
@ -1289,6 +1289,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "Nema pozivnica."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr "Pritisni gumb \"Pozovi u tim\" da pozoveš više članova u ovaj tim."
|
||||
|
||||
|
|
|
@ -1195,6 +1195,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "Tidak ada undangan."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr ""
|
||||
"Tekan tombol \"Undang ke tim\" untuk mengundang lebih banyak anggota ke tim "
|
||||
|
|
|
@ -1294,6 +1294,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "Non ci sono inviti."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr ""
|
||||
"Premi il pulsante \"Invita nel team\" per invitare altri membri in questo "
|
||||
|
|
|
@ -1280,6 +1280,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "Brak zaproszeń."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr ""
|
||||
"Naciśnij przycisk „Zaproś do zespołu”, aby zaprosić więcej członków do tego "
|
||||
|
|
|
@ -1295,6 +1295,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "Não há convites."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr ""
|
||||
"Pressione o botão \"Convidar para equipe\" para convidar mais membros para "
|
||||
|
|
|
@ -1298,6 +1298,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "Não há convites."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr ""
|
||||
"Clica no botão \"Convidar para a equipa\" para convidar mais membros para "
|
||||
|
|
|
@ -1291,6 +1291,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "Nu există invitații."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr ""
|
||||
"Apăsați butonul „Invitați în echipă” pentru a invita mai mulți membri în "
|
||||
|
|
|
@ -1278,6 +1278,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "Приглашений нет."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr ""
|
||||
"Нажмите кнопку «Пригласить в команду», чтобы пригласить в эту команду "
|
||||
|
|
|
@ -1324,6 +1324,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "Davet yok."
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr ""
|
||||
"Bu takıma daha fazla üye davet etmek için \"Takıma davet et\" düğmesine "
|
||||
|
|
|
@ -1252,6 +1252,7 @@ msgid "labels.no-invitations"
|
|||
msgstr "没有邀请。"
|
||||
|
||||
#: src/app/main/ui/dashboard/team.cljs
|
||||
#, markdown
|
||||
msgid "labels.no-invitations-hint"
|
||||
msgstr "点击\"邀请加入团队\",邀请更多成员加入这个团队。"
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue