Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Alejandro Alonso 2023-04-03 12:21:12 +02:00
commit 68b26d5f41
73 changed files with 693 additions and 295 deletions

View file

@ -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 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 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 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!) ### :heart: Community contributions by (Thank you!)
- To @ondrejkonec: for contributing to the code with: - To @ondrejkonec: for contributing to the code with:

View file

@ -155,8 +155,18 @@
(.isClosed ^HikariDataSource pool)) (.isClosed ^HikariDataSource pool))
(defn read-only? (defn read-only?
[pool] [pool-or-conn]
(.isReadOnly ^HikariDataSource pool)) (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 (defn create-pool
[cfg] [cfg]

View file

@ -1050,13 +1050,13 @@
;; --- MUTATION COMMAND: upsert-file-thumbnail ;; --- 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) "insert into file_thumbnail (file_id, revn, data, props)
values (?, ?, ?, ?::jsonb) values (?, ?, ?, ?::jsonb)
on conflict(file_id, revn) do on conflict(file_id, revn) do
update set data = ?, props=?, updated_at=now();") update set data = ?, props=?, updated_at=now();")
(defn upsert-file-thumbnail (defn- upsert-file-thumbnail!
[conn {:keys [file-id revn data props]}] [conn {:keys [file-id revn data props]}]
(let [props (db/tjson (or props {}))] (let [props (db/tjson (or props {}))]
(db/exec-one! conn [sql:upsert-file-thumbnail (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}] [{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool] (db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id file-id) (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)) nil))

View file

@ -23,6 +23,7 @@
[app.util.objects-map :as omap] [app.util.objects-map :as omap]
[app.util.pointer-map :as pmap] [app.util.pointer-map :as pmap]
[app.util.services :as sv] [app.util.services :as sv]
[app.util.time :as dt]
[clojure.spec.alpha :as s])) [clojure.spec.alpha :as s]))
(defn create-file-role! (defn create-file-role!
@ -71,6 +72,10 @@
(->> (assoc params :file-id id :role :owner) (->> (assoc params :file-id id :role :owner)
(create-file-role! conn)) (create-file-role! conn))
(db/update! conn :project
{:modified-at (dt/now)}
{:id project-id})
(files/decode-row file))) (files/decode-row file)))
(s/def ::create-file (s/def ::create-file

View file

@ -152,10 +152,10 @@
profile)) profile))
(defn update-profile-password! (defn update-profile-password!
[{:keys [::db/conn] :as cfg} {:keys [id password] :as profile}] [conn {:keys [id password] :as profile}]
(let [password (derive-password cfg password)] (when-not (db/read-only? conn)
(db/update! conn :profile (db/update! conn :profile
{:password password} {:password (auth/derive-password password)}
{:id id}))) {:id id})))
;; --- MUTATION: Update Photo ;; --- MUTATION: Update Photo

View file

@ -60,12 +60,18 @@
:can-edit (or is-owner is-admin can-edit) :can-edit (or is-owner is-admin can-edit)
:can-read true}))) :can-read true})))
(def has-admin-permissions?
(perms/make-admin-predicate-fn get-permissions))
(def has-edit-permissions? (def has-edit-permissions?
(perms/make-edition-predicate-fn get-permissions)) (perms/make-edition-predicate-fn get-permissions))
(def has-read-permissions? (def has-read-permissions?
(perms/make-read-predicate-fn get-permissions)) (perms/make-read-predicate-fn get-permissions))
(def check-admin-permissions!
(perms/make-check-fn has-admin-permissions?))
(def check-edition-permissions! (def check-edition-permissions!
(perms/make-check-fn has-edit-permissions?)) (perms/make-check-fn has-edit-permissions?))
@ -592,18 +598,19 @@
(let [team (retrieve-team pool profile-id team-id) (let [team (retrieve-team pool profile-id team-id)
photo (profile/upload-photo cfg params)] photo (profile/upload-photo cfg params)]
;; Mark object as touched for make it ellegible for tentative (db/with-atomic [conn pool]
;; garbage collection. (check-admin-permissions! conn profile-id team-id)
(when-let [id (:photo-id team)] ;; Mark object as touched for make it ellegible for tentative
(sto/touch-object! storage id)) ;; garbage collection.
(when-let [id (:photo-id team)]
(sto/touch-object! storage id))
;; Save new photo ;; Save new photo
(db/update! pool :team (db/update! pool :team
{:photo-id (:id photo)} {:photo-id (:id photo)}
{:id team-id}) {:id team-id})
(assoc team :photo-id (:id photo))))
(assoc team :photo-id (:id photo)))))
;; --- Mutation: Create Team Invitation ;; --- Mutation: Create Team Invitation
@ -728,8 +735,13 @@
(let [perms (get-permissions conn profile-id team-id) (let [perms (get-permissions conn profile-id team-id)
profile (db/get-by-id conn :profile profile-id) profile (db/get-by-id conn :profile profile-id)
team (db/get-by-id conn :team team-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) (run! (partial quotes/check-quote! conn)
(list {::quotes/id ::quotes/invitations-per-team (list {::quotes/id ::quotes/invitations-per-team
@ -753,6 +765,7 @@
(let [cfg (assoc cfg ::db/conn conn) (let [cfg (assoc cfg ::db/conn conn)
invitations (->> emails invitations (->> emails
(remove member?)
(map (fn [email] (map (fn [email]
{:email (str/lower email) {:email (str/lower email)
:team team :team team

View file

@ -32,7 +32,14 @@
links (->> (db/query conn :share-link {:file-id file-id}) links (->> (db/query conn :share-link {:file-id file-id})
(mapv (fn [row] (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 fonts (db/query conn :team-font-variant
{:team-id (:team-id project) {:team-id (:team-id project)

View file

@ -37,6 +37,14 @@
:is-admin false :is-admin false
:can-edit 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 (defn make-edition-predicate-fn
"A simple factory for edition permission predicate functions." "A simple factory for edition permission predicate functions."
[qfn] [qfn]

View file

@ -318,8 +318,10 @@
(defn unit (defn unit
[p1] [p1]
(let [p-length (length p1)] (let [p-length (length p1)]
(Point. (/ (dm/get-prop p1 :x) p-length) (if (mth/almost-zero? p-length)
(/ (dm/get-prop p1 :y) p-length)))) (Point. 0 0)
(Point. (/ (dm/get-prop p1 :x) p-length)
(/ (dm/get-prop p1 :y) p-length)))))
(defn perpendicular (defn perpendicular
[pt] [pt]

View file

@ -17,6 +17,7 @@
[app.common.geom.shapes.modifiers :as gsm] [app.common.geom.shapes.modifiers :as gsm]
[app.common.geom.shapes.path :as gsp] [app.common.geom.shapes.path :as gsp]
[app.common.geom.shapes.rect :as gpr] [app.common.geom.shapes.rect :as gpr]
[app.common.geom.shapes.text :as gst]
[app.common.geom.shapes.transforms :as gtr] [app.common.geom.shapes.transforms :as gtr]
[app.common.math :as mth])) [app.common.math :as mth]))
@ -195,3 +196,6 @@
;; Modifiers ;; Modifiers
(dm/export gsm/set-objects-modifiers) (dm/export gsm/set-objects-modifiers)
;; Text
(dm/export gst/position-data-selrect)

View file

@ -133,29 +133,38 @@
(-> (get-shape-filter-bounds shape) (-> (get-shape-filter-bounds shape)
(add-padding (calculate-padding shape true)))) (add-padding (calculate-padding shape true))))
bounds (if (or (:masked-group? shape) bounds
(and (cph/frame-shape? shape) (cond
(not (:show-content shape)))) (empty? (:shapes shape))
[(calculate-base-bounds 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))
(or (not (cph/group-shape? shape)) (:masked-group? shape)
(not (: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] :else
(conj result (get-object-bounds objects shape))) (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) (:id shape)
(not (cph/frame-shape? shape)) (or (:children-bounds 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) filters (shape->filters shape)
blur-value (or (-> shape :blur :value) 0)] blur-value (or (-> shape :blur :value) 0)]

View file

@ -9,7 +9,8 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.matrix :as gmt] [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes.rect :as gpr])) [app.common.geom.shapes.rect :as gpr]
[app.common.math :as mth]))
(defn center-rect (defn center-rect
[{:keys [x y width height]}] [{:keys [x y width height]}]
@ -71,3 +72,15 @@
[{:keys [x1 y1 x2 y2] :as sr} matrix] [{:keys [x1 y1 x2 y2] :as sr} matrix]
(let [[c1 c2] (transform-points [(gpt/point x1 y1) (gpt/point x2 y2)] matrix)] (let [[c1 c2] (transform-points [(gpt/point x1 y1) (gpt/point x2 y2)] matrix)]
(gpr/corners->selrect c1 c2))) (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)))

View file

@ -104,8 +104,10 @@
(if (and (some? line-data) (if (and (some? line-data)
(or (not wrap?) (or (not wrap?)
(and row? (<= next-line-min-width layout-width)) (and row? (or (< next-line-min-width layout-width)
(and col? (<= next-line-min-height layout-height)))) (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)) (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)) :line-max-width (if row? (+ line-max-width next-max-width) (max line-max-width next-max-width))

View file

@ -9,6 +9,7 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.constraints :as gct] [app.common.geom.shapes.constraints :as gct]
[app.common.geom.shapes.flex-layout :as gcfl] [app.common.geom.shapes.flex-layout :as gcfl]
[app.common.geom.shapes.grid-layout :as gcgl] [app.common.geom.shapes.grid-layout :as gcgl]
@ -180,6 +181,7 @@
(let [children (->> children (let [children (->> children
(map (d/getf objects)) (map (d/getf objects))
(remove :hidden) (remove :hidden)
(remove gco/invalid-geometry?)
(map apply-modifiers)) (map apply-modifiers))
layout-data (gcfl/calc-layout-data parent children @transformed-parent-bounds) layout-data (gcfl/calc-layout-data parent children @transformed-parent-bounds)
children (into [] (cond-> children (not (:reverse? layout-data)) reverse)) children (into [] (cond-> children (not (:reverse? layout-data)) reverse))
@ -215,6 +217,7 @@
modif-tree))] modif-tree))]
(let [children (->> (cph/get-immediate-children objects (:id parent)) (let [children (->> (cph/get-immediate-children objects (:id parent))
(remove :hidden) (remove :hidden)
(remove gco/invalid-geometry?)
(map apply-modifiers)) (map apply-modifiers))
grid-data (gcgl/calc-layout-data parent children @transformed-parent-bounds)] grid-data (gcgl/calc-layout-data parent children @transformed-parent-bounds)]
(loop [modif-tree modif-tree (loop [modif-tree modif-tree
@ -249,7 +252,8 @@
(ctm/resize (gpt/point 1 scale-height) origin (:transform parent) (:transform-inverse parent))))) (ctm/resize (gpt/point 1 scale-height) origin (:transform parent) (:transform-inverse parent)))))
children (->> (cph/get-immediate-children objects parent-id) children (->> (cph/get-immediate-children objects parent-id)
(remove :hidden)) (remove :hidden)
(remove gco/invalid-geometry?))
content-bounds content-bounds
(when (and (d/not-empty? children) (or (ctl/auto-height? parent) (ctl/auto-width? parent))) (when (and (d/not-empty? children) (or (ctl/auto-height? parent) (ctl/auto-width? parent)))

View file

@ -185,8 +185,10 @@
(defn close? (defn close?
"Equality for float numbers. Check if the difference is within a range" "Equality for float numbers. Check if the difference is within a range"
[num1 num2] ([num1 num2]
(<= (abs (- num1 num2)) float-equal-precision)) (close? num1 num2 float-equal-precision))
([num1 num2 precision]
(<= (abs (- num1 num2)) precision)))
(defn lerp (defn lerp
"Calculates a the linear interpolation between two values and a given percent" "Calculates a the linear interpolation between two values and a given percent"

View file

@ -526,19 +526,22 @@
(loop [current-val init-val (loop [current-val init-val
current-id (first root-children) 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) (if (empty? next-pending-ids)
next-val (reducer-fn current-val current-shape) next-val
next-pending-ids (recur next-val (first next-pending-ids) (rest next-pending-ids) processed)))))))))
(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)))))))))
(defn selected-with-children (defn selected-with-children
[objects selected] [objects selected]
@ -569,3 +572,25 @@
(d/enumerate) (d/enumerate)
(sort comparator-layout-z-index) (sort comparator-layout-z-index)
(mapv second))) (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))))))

View file

@ -645,12 +645,16 @@
(recur matrix (next modifiers))))))) (recur matrix (next modifiers)))))))
(defn transform-text-node [value attrs] (defn transform-text-node [value attrs]
(let [font-size (-> (get attrs :font-size 14) (let [font-size (-> (get attrs :font-size 14) d/parse-double (* value) str)
(d/parse-double) letter-spacing (-> (get attrs :letter-spacing 0) d/parse-double (* value) str)]
(* value) (d/txt-merge attrs {:font-size font-size
(str))] :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}))) (d/txt-merge attrs {:font-size font-size})))
(defn update-text-content (defn update-text-content
[shape scale-text-content value] [shape scale-text-content value]
(update shape :content scale-text-content value)) (update shape :content scale-text-content value))
@ -661,9 +665,8 @@
(letfn [(scale-text-content (letfn [(scale-text-content
[content value] [content value]
(->> content (->> content
(txt/transform-nodes (txt/transform-nodes txt/is-text-node? (partial transform-text-node value))
txt/is-text-node? (txt/transform-nodes txt/is-paragraph-node? (partial transform-paragraph-node value))))
(partial transform-text-node value))))
(apply-scale-content (apply-scale-content
[shape value] [shape value]
@ -671,7 +674,7 @@
(cph/text-shape? shape) (cph/text-shape? shape)
(update-text-content scale-text-content value) (update-text-content scale-text-content value)
(cph/rect-shape? shape) :always
(gsc/update-corners-scale value) (gsc/update-corners-scale value)
(d/not-empty? (:strokes shape)) (d/not-empty? (:strokes shape))

View file

@ -9,6 +9,7 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.geom.point :as gpt] [app.common.geom.point :as gpt]
[app.common.geom.shapes.bounds :as gsb] [app.common.geom.shapes.bounds :as gsb]
[app.common.pages.helpers :as cph]
[app.common.spec :as us] [app.common.spec :as us]
[clojure.spec.alpha :as s])) [clojure.spec.alpha :as s]))
@ -363,6 +364,7 @@
(defn calc-overlay-position (defn calc-overlay-position
[interaction ;; interaction data [interaction ;; interaction data
shape ;; Shape with the interaction
objects ;; the objects tree objects ;; the objects tree
relative-to-shape ;; the interaction position is realtive to this sape relative-to-shape ;; the interaction position is realtive to this sape
base-frame ;; the base frame of the current interaction base-frame ;; the base frame of the current interaction
@ -371,56 +373,68 @@
(us/verify ::interaction interaction) (us/verify ::interaction interaction)
(assert (has-overlay-opts interaction)) (assert (has-overlay-opts interaction))
(if (nil? dest-frame) (let [
(gpt/point 0 0) ;; When the interactive item is inside a nested frame we need to add to the offset the position
(let [overlay-size (gsb/get-object-bounds objects dest-frame) ;; of the parent-frame otherwise the position won't match
base-frame-size (:selrect base-frame) shape-frame (cph/get-frame objects shape)
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-left frame-offset (if (or (not= :manual (:overlay-pos-type interaction))
(gpt/point (:x base-position) (:y base-position)) (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 :top-left
(gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size))) (gpt/point (:x base-position) (:y base-position))
(:y base-position))
:top-center :top-right
(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2)) (gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size)))
(:y base-position)) (:y base-position))
:bottom-left :top-center
(gpt/point (:x base-position) (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)))) (:y base-position))
:bottom-right :bottom-left
(gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size))) (gpt/point (:x base-position)
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size)))) (+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
:bottom-center :bottom-right
(gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2)) (gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size)))
(+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size)))) (+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size))))
:manual :bottom-center
(gpt/point (+ (:x base-position) (:x overlay-position)) (gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2))
(+ (:y base-position) (:y overlay-position))))))) (+ (: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? (defn has-animation?
[interaction] [interaction]

View file

@ -324,49 +324,49 @@
interaction-rect (ctsi/set-position-relative-to interaction (:id rect))] interaction-rect (ctsi/set-position-relative-to interaction (:id rect))]
(t/testing "Overlay top-left relative to auto" (t/testing "Overlay top-left relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-left base-frame objects) (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 (= (:x overlay-pos) 0))
(t/is (= (:y overlay-pos) 0)))) (t/is (= (:y overlay-pos) 0))))
(t/testing "Overlay top-center relative to auto" (t/testing "Overlay top-center relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-center base-frame objects) (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 (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 0)))) (t/is (= (:y overlay-pos) 0))))
(t/testing "Overlay top-right relative to auto" (t/testing "Overlay top-right relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-right base-frame objects) (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 (= (:x overlay-pos) 70))
(t/is (= (:y overlay-pos) 0)))) (t/is (= (:y overlay-pos) 0))))
(t/testing "Overlay bottom-left relative to auto" (t/testing "Overlay bottom-left relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-left base-frame objects) (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 (= (:x overlay-pos) 0))
(t/is (= (:y overlay-pos) 80)))) (t/is (= (:y overlay-pos) 80))))
(t/testing "Overlay bottom-center relative to auto" (t/testing "Overlay bottom-center relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-center base-frame objects) (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 (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 80)))) (t/is (= (:y overlay-pos) 80))))
(t/testing "Overlay bottom-right relative to auto" (t/testing "Overlay bottom-right relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-right base-frame objects) (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 (= (:x overlay-pos) 70))
(t/is (= (:y overlay-pos) 80)))) (t/is (= (:y overlay-pos) 80))))
(t/testing "Overlay center relative to auto" (t/testing "Overlay center relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :center base-frame objects) (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 (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 40)))) (t/is (= (:y overlay-pos) 40))))
(t/testing "Overlay manual relative to auto" (t/testing "Overlay manual relative to auto"
(let [i2 (ctsi/set-overlay-pos-type interaction-auto :center base-frame objects) (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 (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 40)))) (t/is (= (:y overlay-pos) 40))))
@ -374,49 +374,49 @@
(let [i2 (-> interaction-auto (let [i2 (-> interaction-auto
(ctsi/set-overlay-pos-type :manual base-frame objects) (ctsi/set-overlay-pos-type :manual base-frame objects)
(ctsi/set-overlay-position (gpt/point 12 62))) (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 (= (:x overlay-pos) 17))
(t/is (= (:y overlay-pos) 67)))) (t/is (= (:y overlay-pos) 67))))
(t/testing "Overlay top-left relative to base-frame" (t/testing "Overlay top-left relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-left base-frame objects) (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 (= (:x overlay-pos) 5))
(t/is (= (:y overlay-pos) 5)))) (t/is (= (:y overlay-pos) 5))))
(t/testing "Overlay top-center relative to base-frame" (t/testing "Overlay top-center relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-center base-frame objects) (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 (= (:x overlay-pos) 40))
(t/is (= (:y overlay-pos) 5)))) (t/is (= (:y overlay-pos) 5))))
(t/testing "Overlay top-right relative to base-frame" (t/testing "Overlay top-right relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-right base-frame objects) (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 (= (:x overlay-pos) 75))
(t/is (= (:y overlay-pos) 5)))) (t/is (= (:y overlay-pos) 5))))
(t/testing "Overlay bottom-left relative to base-frame" (t/testing "Overlay bottom-left relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-left base-frame objects) (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 (= (:x overlay-pos) 5))
(t/is (= (:y overlay-pos) 85)))) (t/is (= (:y overlay-pos) 85))))
(t/testing "Overlay bottom-center relative to base-frame" (t/testing "Overlay bottom-center relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-center base-frame objects) (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 (= (:x overlay-pos) 40))
(t/is (= (:y overlay-pos) 85)))) (t/is (= (:y overlay-pos) 85))))
(t/testing "Overlay bottom-right relative to base-frame" (t/testing "Overlay bottom-right relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-right base-frame objects) (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 (= (:x overlay-pos) 75))
(t/is (= (:y overlay-pos) 85)))) (t/is (= (:y overlay-pos) 85))))
(t/testing "Overlay center relative to base-frame" (t/testing "Overlay center relative to base-frame"
(let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :center base-frame objects) (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 (= (:x overlay-pos) 40))
(t/is (= (:y overlay-pos) 45)))) (t/is (= (:y overlay-pos) 45))))
@ -424,49 +424,49 @@
(let [i2 (-> interaction-base-frame (let [i2 (-> interaction-base-frame
(ctsi/set-overlay-pos-type :manual base-frame objects) (ctsi/set-overlay-pos-type :manual base-frame objects)
(ctsi/set-overlay-position (gpt/point 12 62))) (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 (= (:x overlay-pos) 17))
(t/is (= (:y overlay-pos) 67)))) (t/is (= (:y overlay-pos) 67))))
(t/testing "Overlay top-left relative to popup" (t/testing "Overlay top-left relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-left base-frame objects) (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 (= (:x overlay-pos) 15))
(t/is (= (:y overlay-pos) 15)))) (t/is (= (:y overlay-pos) 15))))
(t/testing "Overlay top-center relative to popup" (t/testing "Overlay top-center relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-center base-frame objects) (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 (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 15)))) (t/is (= (:y overlay-pos) 15))))
(t/testing "Overlay top-right relative to popup" (t/testing "Overlay top-right relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-right base-frame objects) (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 (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 15)))) (t/is (= (:y overlay-pos) 15))))
(t/testing "Overlay bottom-left relative to popup" (t/testing "Overlay bottom-left relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-left base-frame objects) (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 (= (:x overlay-pos) 15))
(t/is (= (:y overlay-pos) 45)))) (t/is (= (:y overlay-pos) 45))))
(t/testing "Overlay bottom-center relative to popup" (t/testing "Overlay bottom-center relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-center base-frame objects) (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 (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 45)))) (t/is (= (:y overlay-pos) 45))))
(t/testing "Overlay bottom-right relative to popup" (t/testing "Overlay bottom-right relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-right base-frame objects) (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 (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 45)))) (t/is (= (:y overlay-pos) 45))))
(t/testing "Overlay center relative to popup" (t/testing "Overlay center relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :center base-frame objects) (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 (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 30)))) (t/is (= (:y overlay-pos) 30))))
@ -474,49 +474,49 @@
(let [i2 (-> interaction-popup (let [i2 (-> interaction-popup
(ctsi/set-overlay-pos-type :manual base-frame objects) (ctsi/set-overlay-pos-type :manual base-frame objects)
(ctsi/set-overlay-position (gpt/point 12 62))) (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 (= (:x overlay-pos) 27))
(t/is (= (:y overlay-pos) 77)))) (t/is (= (:y overlay-pos) 77))))
(t/testing "Overlay top-left relative to popup" (t/testing "Overlay top-left relative to popup"
(let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-left base-frame objects) (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 (= (:x overlay-pos) 15))
(t/is (= (:y overlay-pos) 15)))) (t/is (= (:y overlay-pos) 15))))
(t/testing "Overlay top-center relative to rect" (t/testing "Overlay top-center relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :top-center base-frame objects) (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 (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 15)))) (t/is (= (:y overlay-pos) 15))))
(t/testing "Overlay top-right relative to rect" (t/testing "Overlay top-right relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :top-right base-frame objects) (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 (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 15)))) (t/is (= (:y overlay-pos) 15))))
(t/testing "Overlay bottom-left relative to rect" (t/testing "Overlay bottom-left relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-left base-frame objects) (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 (= (:x overlay-pos) 15))
(t/is (= (:y overlay-pos) 45)))) (t/is (= (:y overlay-pos) 45))))
(t/testing "Overlay bottom-center relative to rect" (t/testing "Overlay bottom-center relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-center base-frame objects) (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 (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 45)))) (t/is (= (:y overlay-pos) 45))))
(t/testing "Overlay bottom-right relative to rect" (t/testing "Overlay bottom-right relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-right base-frame objects) (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 (= (:x overlay-pos) 35))
(t/is (= (:y overlay-pos) 45)))) (t/is (= (:y overlay-pos) 45))))
(t/testing "Overlay center relative to rect" (t/testing "Overlay center relative to rect"
(let [i2 (ctsi/set-overlay-pos-type interaction-rect :center base-frame objects) (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 (= (:x overlay-pos) 25))
(t/is (= (:y overlay-pos) 30)))) (t/is (= (:y overlay-pos) 30))))
@ -524,7 +524,7 @@
(let [i2 (-> interaction-rect (let [i2 (-> interaction-rect
(ctsi/set-overlay-pos-type :manual base-frame objects) (ctsi/set-overlay-pos-type :manual base-frame objects)
(ctsi/set-overlay-position (gpt/point 12 62))) (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 (= (:x overlay-pos) 17))
(t/is (= (:y overlay-pos) 67)))))) (t/is (= (:y overlay-pos) 67))))))

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

View file

@ -260,7 +260,7 @@
.close { .close {
background-color: $color-white; background-color: $color-white;
cursor: pointer; cursor: pointer;
padding: 3px 5px; padding-left: 5px;
svg { svg {
fill: $color-gray-30; fill: $color-gray-30;

View file

@ -338,6 +338,10 @@
.typography-container { .typography-container {
position: relative; position: relative;
&:last-child {
padding-bottom: 0.5em;
}
} }
.drag-counter { .drag-counter {

View file

@ -3,7 +3,7 @@
background-color: $color-gray-50; background-color: $color-gray-50;
border-bottom: 1px solid $color-gray-60; border-bottom: 1px solid $color-gray-60;
display: grid; display: grid;
grid-template-columns: 45% 10% 45%; grid-template-columns: 1fr 130px 1fr;
height: 48px; height: 48px;
padding: 0 $size-4 0 55px; padding: 0 $size-4 0 55px;
top: 0; top: 0;

View file

@ -37,7 +37,7 @@
<body> <body>
{{>../public/images/sprites/symbol/icons.svg}} {{>../public/images/sprites/symbol/icons.svg}}
{{>../public/images/sprites/symbol/cursors.svg}} {{>../public/images/sprites/symbol/cursors.svg}}
<div id="app" tabindex="0"></div> <div id="app"></div>
<section id="modal"></section> <section id="modal"></section>
{{# manifest}} {{# manifest}}
<script src="{{& shared}}"></script> <script src="{{& shared}}"></script>

View file

@ -17,7 +17,7 @@
{{/manifest}} {{/manifest}}
</head> </head>
<body> <body>
<div id="app" tabindex="0"></div> <div id="app"></div>
{{# manifest}} {{# manifest}}
<script src="{{& shared}}"></script> <script src="{{& shared}}"></script>
<script src="{{& render}}"></script> <script src="{{& render}}"></script>

View file

@ -925,6 +925,13 @@
{:num-files (count ids) {:num-files (count ids)
:project-id project-id}) :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 ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
(let [{:keys [on-success on-error] (let [{:keys [on-success on-error]

View file

@ -9,6 +9,12 @@
import Mousetrap from 'mousetrap' import Mousetrap from 'mousetrap'
if (Mousetrap.addKeycodes) {
Mousetrap.addKeycodes({
219: '219'
});
}
const target = Mousetrap.prototype || Mousetrap; const target = Mousetrap.prototype || Mousetrap;
target.stopCallback = function(e, element, combo) { target.stopCallback = function(e, element, combo) {
// if the element has the class "mousetrap" then no need to stop // 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' || return element.tagName == 'INPUT' ||
element.tagName == 'SELECT' || element.tagName == 'SELECT' ||
element.tagName == 'TEXTAREA' || element.tagName == 'TEXTAREA' ||
element.tagName == 'BUTTON' || (element.tagName == 'BUTTON' && combo.includes("tab")) ||
(element.contentEditable && element.contentEditable == 'true'); (element.contentEditable && element.contentEditable == 'true');
} }

View file

@ -676,6 +676,10 @@
(cond-> (not (ctl/any-layout? objects parent-id)) (cond-> (not (ctl/any-layout? objects parent-id))
(pcb/update-shapes ordered-indexes ctl/remove-layout-item-data)) (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 ;; Move the shapes
(pcb/change-parent parent-id (pcb/change-parent parent-id
shapes shapes
@ -1311,8 +1315,8 @@
;; Prepare the shape object. Mainly needed for image shapes ;; Prepare the shape object. Mainly needed for image shapes
;; for retrieve the image data and convert it to the ;; for retrieve the image data and convert it to the
;; data-url. ;; data-url.
(prepare-object [objects selected+children {:keys [type] :as obj}] (prepare-object [objects parent-frame-id {:keys [type] :as obj}]
(let [obj (maybe-translate obj objects selected+children)] (let [obj (maybe-translate obj objects parent-frame-id)]
(if (= type :image) (if (= type :image)
(let [url (cf/resolve-file-media (:metadata obj))] (let [url (cf/resolve-file-media (:metadata obj))]
(->> (http/send! {:method :get (->> (http/send! {:method :get
@ -1335,15 +1339,11 @@
(update res :images conj img-part)) (update res :images conj img-part))
res))) res)))
(maybe-translate [shape objects selected+children] (maybe-translate [shape objects parent-frame-id]
(let [root-frame-id (cph/get-shape-id-root-frame objects (:id shape))] (if (= parent-frame-id uuid/zero)
(if (and (not (cph/root-frame? shape)) shape
(not (contains? selected+children root-frame-id))) (let [frame (get objects parent-frame-id)]
;; When the parent frame is not selected we change to relative (gsh/translate-to-frame shape frame))))
;; coordinates
(let [frame (get objects root-frame-id)]
(gsh/translate-to-frame shape frame))
shape)))
(on-copy-error [error] (on-copy-error [error]
(js/console.error "Clipboard blocked:" error) (js/console.error "Clipboard blocked:" error)
@ -1356,7 +1356,7 @@
selected (->> (wsh/lookup-selected state) selected (->> (wsh/lookup-selected state)
(cph/clean-loops objects)) (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) pdata (reduce (partial collect-object-ids objects) {} selected)
initial {:type :copied-shapes initial {:type :copied-shapes
:file-id (:current-file-id state) :file-id (:current-file-id state)
@ -1371,7 +1371,7 @@
(catch :default e (catch :default e
(on-copy-error e))) (on-copy-error e)))
(->> (rx/from (seq (vals pdata))) (->> (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/reduce collect-data initial)
(rx/map (partial sort-selected state)) (rx/map (partial sort-selected state))
(rx/map t/encode-str) (rx/map t/encode-str)

View file

@ -138,6 +138,7 @@
(pcb/with-objects objects) (pcb/with-objects objects)
(cond-> (not (ctl/any-layout? objects frame-id)) (cond-> (not (ctl/any-layout? objects frame-id))
(pcb/update-shapes ordered-indexes ctl/remove-layout-item-data)) (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) (pcb/change-parent frame-id to-move-shapes 0)
(cond-> (ctl/grid-layout? objects frame-id) (cond-> (ctl/grid-layout? objects frame-id)
(pcb/update-shapes [frame-id] ctl/assign-cells))))] (pcb/update-shapes [frame-id] ctl/assign-cells))))]

View file

@ -354,12 +354,14 @@
:fn #(st/emit! (dw/select-all))} :fn #(st/emit! (dw/select-all))}
:toggle-grid {:tooltip (ds/meta "'") :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] :subsections [:main-menu]
:fn #(st/emit! (toggle-layout-flag :display-grid))} :fn #(st/emit! (toggle-layout-flag :display-grid))}
:toggle-snap-grid {:tooltip (ds/meta-shift "'") :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] :subsections [:main-menu]
:fn #(st/emit! (toggle-layout-flag :snap-grid))} :fn #(st/emit! (toggle-layout-flag :snap-grid))}

View file

@ -164,11 +164,13 @@
(cond-> shape (cond-> shape
(get-in shape [:svg-attrs :opacity]) (get-in shape [:svg-attrs :opacity])
(-> (update :svg-attrs dissoc :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]) (get-in shape [:svg-attrs :style :opacity])
(-> (update-in [:svg-attrs :style] dissoc :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]) (get-in shape [:svg-attrs :mix-blend-mode])
@ -410,7 +412,8 @@
(assoc :strokes []) (assoc :strokes [])
(assoc :svg-defs (select-keys (:defs svg-data) references)) (assoc :svg-defs (select-keys (:defs svg-data) references))
(setup-fill) (setup-fill)
(setup-stroke)) (setup-stroke)
(setup-opacity))
shape (cond-> shape shape (cond-> shape
hidden (assoc :hidden true)) hidden (assoc :hidden true))

View file

@ -377,7 +377,7 @@
(assoc-in [:workspace-local :edition] (-> selected first :id))))))) (assoc-in [:workspace-local :edition] (-> selected first :id)))))))
(defn not-changed? [old-dim new-dim] (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 (defn commit-resize-text
[] []

View file

@ -745,13 +745,16 @@
(remove (fn [shape] (remove (fn [shape]
(and (ctl/layout-absolute? shape) (and (ctl/layout-absolute? shape)
(= frame-id (:parent-id shape)))))) (= frame-id (:parent-id shape))))))
moving-shapes-ids
(map :id moving-shapes)
changes changes
(-> (pcb/empty-changes it page-id) (-> (pcb/empty-changes it page-id)
(pcb/with-objects objects) (pcb/with-objects objects)
;; Remove layout-item properties when moving a shape outside a layout ;; Remove layout-item properties when moving a shape outside a layout
(cond-> (not (ctl/any-layout? objects frame-id)) (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/change-parent frame-id moving-shapes drop-index)
(pcb/remove-objects empty-parents))] (pcb/remove-objects empty-parents))]

View file

@ -269,7 +269,8 @@
{:option-name (tr "labels.rename") {:option-name (tr "labels.rename")
:id "file-rename" :id "file-rename"
:option-handler on-edit :option-handler on-edit
:data-test "file-rename"} :data-test "file-rename"})
(when (not is-search-page?)
{:option-name (tr "dashboard.duplicate") {:option-name (tr "dashboard.duplicate")
:id "file-duplicate" :id "file-duplicate"
:option-handler on-duplicate :option-handler on-duplicate

View file

@ -144,6 +144,7 @@
create-file create-file
(mf/use-fn (mf/use-fn
(mf/deps project)
(fn [origin] (fn [origin]
(st/emit! (with-meta (dd/create-file {:project-id (:id project)}) (st/emit! (with-meta (dd/create-file {:project-id (:id project)})
{::ev/origin origin}))))] {::ev/origin origin}))))]

View file

@ -269,7 +269,9 @@
(mf/use-fn (mf/use-fn
(mf/deps file) (mf/deps file)
(fn [name] (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))) (swap! local assoc :edition false)))
on-edit on-edit

View file

@ -22,6 +22,7 @@
[app.util.keyboard :as kbd] [app.util.keyboard :as kbd]
[app.util.webapi :as wapi] [app.util.webapi :as wapi]
[beicon.core :as rx] [beicon.core :as rx]
[cuerdas.core :as str]
[potok.core :as ptk] [potok.core :as ptk]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -61,9 +62,11 @@
(->> files (->> files
(mapv (mapv
(fn [file] (fn [file]
(cond-> file (let [new-name (str/trim new-name)]
(= (:file-id file) file-id) (cond-> file
(assoc :name new-name)))))) (and (= (:file-id file) file-id)
(not= "" new-name))
(assoc :name new-name)))))))
(defn remove-file [files file-id] (defn remove-file [files file-id]
(->> files (->> files
@ -378,7 +381,7 @@
[:div.feedback-banner [:div.feedback-banner
[:div.icon i/checkbox-checked] [: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] (for [file files]
(let [editing? (and (some? (:file-id file)) (let [editing? (and (some? (:file-id file))

View file

@ -362,8 +362,13 @@
(reverse)) (reverse))
recent-map (mf/deref recent-files-ref) recent-map (mf/deref recent-files-ref)
props (some-> profile (get :props {})) props (some-> profile (get :props {}))
team-hero? (and (:team-hero? props true) you-owner? (get-in team [:permissions :is-owner])
(not (:is-default team))) 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) tutorial-viewed? (:viewed-tutorial? props true)
walkthrough-viewed? (:viewed-walkthrough? props true) walkthrough-viewed? (:viewed-walkthrough? props true)

View file

@ -177,7 +177,9 @@
:on-submit on-submit}]] :on-submit on-submit}]]
[:div.action-buttons [: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 ;; MEMBERS SECTION
@ -586,7 +588,8 @@
[:div.empty-invitations [:div.empty-invitations
[:span (tr "labels.no-invitations")] [:span (tr "labels.no-invitations")]
(when can-invite? (when can-invite?
[:span (tr "labels.no-invitations-hint")])]) [:& i18n/tr-html {:label "labels.no-invitations-hint"
:tag-name "span"}])])
(mf/defc invitation-section (mf/defc invitation-section
[{:keys [team invitations] :as props}] [{:keys [team invitations] :as props}]
@ -897,6 +900,10 @@
stats (mf/deref refs/dashboard-team-stats) 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 on-image-click
(mf/use-callback #(dom/click (mf/ref-val finput))) (mf/use-callback #(dom/click (mf/ref-val finput)))
@ -928,12 +935,14 @@
[:div.label (tr "dashboard.team-info")] [:div.label (tr "dashboard.team-info")]
[:div.name (:name team)] [:div.name (:name team)]
[:div.icon [: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)}] [:img {:src (cfg/resolve-team-photo-url team)}]
[:& file-uploader {:accept "image/jpeg,image/png" (when can-edit?
:multi false [:& file-uploader {:accept "image/jpeg,image/png"
:ref finput :multi false
:on-selected on-file-selected}]]] :ref finput
:on-selected on-file-selected}])]]
[:div.block.owner-block [:div.block.owner-block
[:div.label (tr "dashboard.team-members")] [:div.label (tr "dashboard.team-members")]

View file

@ -185,6 +185,7 @@
:auto-focus? true :auto-focus? true
:trim true :trim true
:valid-item-fn us/parse-email :valid-item-fn us/parse-email
:caution-item-fn #{}
:on-submit on-submit :on-submit on-submit
:label (tr "modals.invite-member.emails")}]] :label (tr "modals.invite-member.emails")}]]

View file

@ -18,6 +18,7 @@
[app.main.ui.releases.v1-15] [app.main.ui.releases.v1-15]
[app.main.ui.releases.v1-16] [app.main.ui.releases.v1-16]
[app.main.ui.releases.v1-17] [app.main.ui.releases.v1-17]
[app.main.ui.releases.v1-18]
[app.main.ui.releases.v1-4] [app.main.ui.releases.v1-4]
[app.main.ui.releases.v1-5] [app.main.ui.releases.v1-5]
[app.main.ui.releases.v1-6] [app.main.ui.releases.v1-6]
@ -87,4 +88,4 @@
(defmethod rc/render-release-notes "0.0" (defmethod rc/render-release-notes "0.0"
[params] [params]
(rc/render-release-notes (assoc params :version "1.17"))) (rc/render-release-notes (assoc params :version "1.18")))

View 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}]]]]]])))

View file

@ -38,20 +38,27 @@
[:use {:href (str "#" shape-id)}]])) [:use {:href (str "#" shape-id)}]]))
(mf/defc outer-stroke-mask (mf/defc outer-stroke-mask
[{:keys [shape render-id index]}] [{:keys [shape stroke render-id index]}]
(let [suffix (if index (str "-" index) "") (let [suffix (if index (str "-" index) "")
stroke-mask-id (str "outer-stroke-" render-id "-" (:id shape) suffix) stroke-mask-id (str "outer-stroke-" render-id "-" (:id shape) suffix)
shape-id (str "stroke-shape-" render-id "-" (:id shape) suffix) shape-id (str "stroke-shape-" render-id "-" (:id shape) suffix)
stroke-width (case (:stroke-alignment shape :center) stroke-width (case (:stroke-alignment stroke :center)
:center (/ (:stroke-width shape 0) 2) :center (/ (:stroke-width stroke 0) 2)
:outer (:stroke-width shape 0) :outer (:stroke-width stroke 0)
0) 0)
margin (gsb/shape-stroke-margin shape stroke-width) margin (gsb/shape-stroke-margin stroke stroke-width)
bounding-box (-> (gsh/points->selrect (:points shape))
(update :x - (+ stroke-width margin)) selrect
(update :y - (+ stroke-width margin)) (if (cph/text-shape? shape)
(update :width + (* 2 (+ stroke-width margin))) (gsh/position-data-selrect shape)
(update :height + (* 2 (+ stroke-width margin))))] (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 [:mask {:id stroke-mask-id
:x (:x bounding-box) :x (:x bounding-box)
@ -67,17 +74,17 @@
:stroke "none"}}]])) :stroke "none"}}]]))
(mf/defc cap-markers (mf/defc cap-markers
[{:keys [shape render-id index]}] [{:keys [stroke render-id index]}]
(let [marker-id-prefix (str "marker-" render-id) (let [marker-id-prefix (str "marker-" render-id)
cap-start (:stroke-cap-start shape) cap-start (:stroke-cap-start stroke)
cap-end (:stroke-cap-end shape) 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)) (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 (when-not (:stroke-color-gradient stroke)
(:stroke-opacity shape))] (:stroke-opacity stroke))]
[:* [:*
(when (or (= cap-start :line-arrow) (= cap-end :line-arrow)) (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}]])])) [:rect {:x 3 :y 2.5 :width 0.5 :height 1}]])]))
(mf/defc stroke-defs (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))] (let [open-path? (and (= :path (:type shape)) (gsh/open-path? shape))]
[:* [:*
(cond (some? (:stroke-color-gradient shape)) (cond (some? (:stroke-color-gradient stroke))
(case (:type (:stroke-color-gradient shape)) (case (:type (:stroke-color-gradient stroke))
:linear [:> grad/linear-gradient #js {:id (str (name :stroke-color-gradient) "_" render-id "_" index) :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}] :shape shape}]
:radial [:> grad/radial-gradient #js {:id (str (name :stroke-color-gradient) "_" render-id "_" index) :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}])) :shape shape}]))
(cond (cond
(and (not open-path?) (and (not open-path?)
(= :inner (:stroke-alignment shape :center)) (= :inner (:stroke-alignment stroke :center))
(> (:stroke-width shape 0) 0)) (> (:stroke-width stroke 0) 0))
[:& inner-stroke-clip-path {:shape shape [:& inner-stroke-clip-path {:shape shape
:render-id render-id :render-id render-id
:index index}] :index index}]
(and (not open-path?) (and (not open-path?)
(= :outer (:stroke-alignment shape :center)) (= :outer (:stroke-alignment stroke :center))
(> (:stroke-width shape 0) 0)) (> (:stroke-width stroke 0) 0))
[:& outer-stroke-mask {:shape shape [:& outer-stroke-mask {:shape shape
:stroke stroke
:render-id render-id :render-id render-id
:index index}] :index index}]
(or (some? (:stroke-cap-start shape)) (or (some? (:stroke-cap-start stroke))
(some? (:stroke-cap-end shape))) (some? (:stroke-cap-end stroke)))
[:& cap-markers {:shape shape [:& cap-markers {:stroke stroke
:render-id render-id :render-id render-id
:index index}])])) :index index}])]))
@ -216,8 +224,9 @@
base-props (obj/get child "props") base-props (obj/get child "props")
elem-name (obj/get child "type") elem-name (obj/get child "type")
shape (obj/get props "shape") shape (obj/get props "shape")
stroke (obj/get props "stroke")
index (obj/get props "index") index (obj/get props "index")
stroke-width (:stroke-width shape) stroke-width (:stroke-width stroke)
suffix (if index (str "-" index) "") suffix (if index (str "-" index) "")
stroke-mask-id (str "outer-stroke-" render-id "-" (:id shape) suffix) stroke-mask-id (str "outer-stroke-" render-id "-" (:id shape) suffix)
@ -225,7 +234,7 @@
[:g.outer-stroke-shape [:g.outer-stroke-shape
[:defs [: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) [:> elem-name (-> (obj/clone base-props)
(obj/set! "id" shape-id) (obj/set! "id" shape-id)
(obj/set! (obj/set!
@ -258,10 +267,11 @@
base-props (obj/get child "props") base-props (obj/get child "props")
elem-name (obj/get child "type") elem-name (obj/get child "type")
shape (obj/get props "shape") shape (obj/get props "shape")
stroke (obj/get props "stroke")
index (obj/get props "index") index (obj/get props "index")
transform (obj/get base-props "transform") transform (obj/get base-props "transform")
stroke-width (:stroke-width shape 0) stroke-width (:stroke-width stroke 0)
suffix (if index (str "-" index) "") suffix (if index (str "-" index) "")
clip-id (str "inner-stroke-" render-id "-" (:id shape) suffix) clip-id (str "inner-stroke-" render-id "-" (:id shape) suffix)
@ -275,7 +285,7 @@
[:g.inner-stroke-shape {:transform transform} [:g.inner-stroke-shape {:transform transform}
[:defs [: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]] [:> elem-name shape-props]]
[:use {:href (str "#" shape-id) [:use {:href (str "#" shape-id)
@ -290,33 +300,34 @@
{::mf/wrap-props false} {::mf/wrap-props false}
[props] [props]
(let [child (obj/get props "children") (let [child (obj/get props "children")
shape (obj/get props "shape") shape (obj/get props "shape")
stroke (obj/get props "stroke")
render-id (mf/use-ctx muc/render-id) render-id (mf/use-ctx muc/render-id)
index (obj/get props "index") index (obj/get props "index")
stroke-width (:stroke-width shape 0) stroke-width (:stroke-width stroke 0)
stroke-style (:stroke-style shape :none) stroke-style (:stroke-style stroke :none)
stroke-position (:stroke-alignment shape :center) stroke-position (:stroke-alignment stroke :center)
has-stroke? (and (> stroke-width 0) has-stroke? (and (> stroke-width 0)
(not= stroke-style :none)) (not= stroke-style :none))
closed? (or (not= :path (:type shape)) closed? (or (not= :path (:type shape)) (not (gsh/open-path? shape)))
(not (gsh/open-path? shape)))
inner? (= :inner stroke-position) inner? (= :inner stroke-position)
outer? (= :outer stroke-position)] outer? (= :outer stroke-position)]
(cond (cond
(and has-stroke? inner? closed?) (and has-stroke? inner? closed?)
[:& inner-stroke {:shape shape :index index} [:& inner-stroke {:shape shape :stroke stroke :index index}
child] child]
(and has-stroke? outer? closed?) (and has-stroke? outer? closed?)
[:& outer-stroke {:shape shape :index index} [:& outer-stroke {:shape shape :stroke stroke :index index}
child] child]
:else :else
[:g.stroke-shape [:g.stroke-shape
[:defs [:defs
[:& stroke-defs {:shape shape :render-id render-id :index index}]] [:& stroke-defs {:shape shape :stroke stroke :render-id render-id :index index}]]
child]))) child])))
(defn build-fill-props [shape child position render-id] (defn build-fill-props [shape child position render-id]
@ -426,6 +437,7 @@
[props] [props]
(let [child (obj/get props "children") (let [child (obj/get props "children")
shape (obj/get props "shape") shape (obj/get props "shape")
elem-name (obj/get child "type") elem-name (obj/get child "type")
render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-id)) render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-id))
stroke-id (dm/fmt "strokes-%" (:id shape)) stroke-id (dm/fmt "strokes-%" (:id shape))
@ -445,9 +457,8 @@
(d/not-empty? (:strokes shape)) (d/not-empty? (:strokes shape))
[:> :g stroke-props [:> :g stroke-props
(for [[index value] (-> (d/enumerate (:strokes shape)) reverse)] (for [[index value] (-> (d/enumerate (:strokes shape)) reverse)]
(let [props (build-stroke-props index child value render-id) (let [props (build-stroke-props index child value render-id)]
shape (assoc value :points (:points shape))] [:& shape-custom-stroke {:shape shape :stroke value :index index :key (dm/str index "-" stroke-id)}
[:& shape-custom-stroke {:shape shape :index index :key (dm/str index "-" stroke-id)}
[:> elem-name props]]))])])) [:> elem-name props]]))])]))
(mf/defc shape-custom-strokes (mf/defc shape-custom-strokes

View file

@ -46,6 +46,7 @@
(defn- activate-interaction (defn- activate-interaction
[interaction shape base-frame frame-offset objects overlays] [interaction shape base-frame frame-offset objects overlays]
(case (:action-type interaction) (case (:action-type interaction)
:navigate :navigate
(when-let [frame-id (:destination interaction)] (when-let [frame-id (:destination interaction)]
@ -69,6 +70,7 @@
overlays-ids (set (map :id overlays)) overlays-ids (set (map :id overlays))
relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame) relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame)
position (ctsi/calc-overlay-position interaction position (ctsi/calc-overlay-position interaction
shape
viewer-objects viewer-objects
relative-to-shape relative-to-shape
relative-to-base-frame relative-to-base-frame
@ -91,6 +93,7 @@
overlays-ids (set (map :id overlays)) overlays-ids (set (map :id overlays))
relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame) relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame)
position (ctsi/calc-overlay-position interaction position (ctsi/calc-overlay-position interaction
shape
objects objects
relative-to-shape relative-to-shape
relative-to-base-frame relative-to-base-frame
@ -156,6 +159,7 @@
overlays-ids (set (map :id overlays)) overlays-ids (set (map :id overlays))
relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame) relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame)
position (ctsi/calc-overlay-position interaction position (ctsi/calc-overlay-position interaction
shape
objects objects
relative-to-shape relative-to-shape
relative-to-base-frame relative-to-base-frame

View file

@ -67,7 +67,8 @@
(mf/use-fn (mf/use-fn
(mf/deps current-color @drag?) (mf/deps current-color @drag?)
(fn [color] (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) (let [recent-color (merge current-color color)
recent-color (dc/materialize-color-components recent-color)] recent-color (dc/materialize-color-components recent-color)]
(st/emit! (dc/update-colorpicker-color recent-color (not @drag?))))))) (st/emit! (dc/update-colorpicker-color recent-color (not @drag?)))))))

View file

@ -15,6 +15,7 @@
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.colors :as dc] [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.libraries :as dwl]
[app.main.data.workspace.shortcuts :as sc] [app.main.data.workspace.shortcuts :as sc]
[app.main.refs :as refs] [app.main.refs :as refs]
@ -32,6 +33,7 @@
[app.util.keyboard :as kbd] [app.util.keyboard :as kbd]
[app.util.router :as rt] [app.util.router :as rt]
[beicon.core :as rx] [beicon.core :as rx]
[cuerdas.core :as str]
[okulary.core :as l] [okulary.core :as l]
[potok.core :as ptk] [potok.core :as ptk]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -149,10 +151,12 @@
:on-accept #(st/emit! (dwl/set-file-shared (:id file) false)) :on-accept #(st/emit! (dwl/set-file-shared (:id file) false))
:count-libraries 1})))) :count-libraries 1}))))
handle-blur (fn [_] handle-blur
(let [value (-> edit-input-ref mf/ref-val dom/get-value)] (fn [_]
(st/emit! (dw/rename-file (:id file) value))) (let [value (str/trim (-> edit-input-ref mf/ref-val dom/get-value))]
(reset! editing? false)) (when (not= value "")
(st/emit! (dw/rename-file (:id file) value))))
(reset! editing? false))
handle-name-keydown (fn [event] handle-name-keydown (fn [event]
(when (kbd/enter? event) (when (kbd/enter? event)
@ -306,12 +310,14 @@
[:li {:on-click #(st/emit! (dw/select-all))} [:li {:on-click #(st/emit! (dw/select-all))}
[:span (tr "workspace.header.menu.select-all")] [:span (tr "workspace.header.menu.select-all")]
[:span.shortcut (sc/get-tooltip :select-all)]] [:span.shortcut (sc/get-tooltip :select-all)]]
[:li {:on-click #(st/emit! (toggle-flag :scale-text))}
[:span [:li {:on-click #(st/emit! dwc/undo)}
(if (contains? layout :scale-text) [:span (tr "workspace.header.menu.undo")]
(tr "workspace.header.menu.disable-scale-text") [:span.shortcut (sc/get-tooltip :undo)]]
(tr "workspace.header.menu.enable-scale-text"))]
[:span.shortcut (sc/get-tooltip :toggle-scale-text)]]]] [: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) [:& dropdown {:show (= @show-sub-menu? :view)
:on-close #(reset! show-sub-menu? false)} :on-close #(reset! show-sub-menu? false)}
@ -374,6 +380,13 @@
[:& dropdown {:show (= @show-sub-menu? :preferences) [:& dropdown {:show (= @show-sub-menu? :preferences)
:on-close #(reset! show-sub-menu? false)} :on-close #(reset! show-sub-menu? false)}
[:ul.sub-menu.preferences [: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))} [:li {:on-click #(st/emit! (toggle-flag :snap-guides))}
[:span [:span
(if (contains? layout :snap-guides) (if (contains? layout :snap-guides)

View file

@ -66,7 +66,8 @@
(when (contains? #{:auto-height :auto-width} grow-type) (when (contains? #{:auto-height :auto-width} grow-type)
(let [{:keys [width height]} (let [{:keys [width height]}
(-> (dom/query node ".paragraph-set") (-> (dom/query node ".paragraph-set")
(dom/get-client-size)) (dom/get-bounding-rect))
width (mth/ceil width) width (mth/ceil width)
height (mth/ceil height)] height (mth/ceil height)]
(when (and (not (mth/almost-zero? width)) (when (and (not (mth/almost-zero? width))

View file

@ -161,27 +161,37 @@
on-change on-change
(mf/use-fn (mf/use-fn
(fn [new-color old-color] (fn [new-color old-color from-picker?]
(let [old-color (-> old-color (let [old-color (-> old-color
(dissoc :name) (dissoc :name)
(dissoc :path) (dissoc :path)
(d/without-nils)) (d/without-nils))
prev-color (-> @prev-color* prev-color (when @prev-color*
(dissoc :name) (-> @prev-color*
(dissoc :path) (dissoc :name)
(d/without-nils)) (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 ;; 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-old-color (get @grouped-colors* old-color)
shapes-by-prev-color (get @grouped-colors* prev-color) shapes-by-prev-color (get @grouped-colors* prev-color)
shapes-by-color (or shapes-by-prev-color shapes-by-old-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 (when from-picker?
(fn [color] (reset! prev-color* new-color))
(reset! prev-color* 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 on-detach
(mf/use-fn (mf/use-fn
@ -212,8 +222,9 @@
:index index :index index
:on-detach on-detach :on-detach on-detach
:select-only select-only :select-only select-only
:on-change #(on-change % color) :on-change #(on-change %1 color %2)
:on-open on-open}]) :on-open on-open
:on-close on-close}])
(when (and (false? @expand-lib-color) (< 3 (count library-colors))) (when (and (false? @expand-lib-color) (< 3 (count library-colors)))
[:div.expand-colors {:on-click #(reset! expand-lib-color true)} [:div.expand-colors {:on-click #(reset! expand-lib-color true)}
[:span i/actions] [:span i/actions]
@ -225,8 +236,9 @@
:index index :index index
:on-detach on-detach :on-detach on-detach
:select-only select-only :select-only select-only
:on-change #(on-change % color) :on-change #(on-change %1 color %2)
:on-open on-open}]))] :on-open on-open
:on-close on-close}]))]
[:div.selected-colors [:div.selected-colors
(for [[index color] (d/enumerate (take 3 colors))] (for [[index color] (d/enumerate (take 3 colors))]
@ -234,8 +246,9 @@
:color color :color color
:index index :index index
:select-only select-only :select-only select-only
:on-change #(on-change % color) :on-change #(on-change %1 color %2)
:on-open on-open}]) :on-open on-open
:on-close on-close}])
(when (and (false? @expand-color) (< 3 (count colors))) (when (and (false? @expand-color) (< 3 (count colors)))
[:div.expand-colors {:on-click #(reset! expand-color true)} [:div.expand-colors {:on-click #(reset! expand-color true)}
[:span i/actions] [:span i/actions]
@ -246,5 +259,6 @@
:color color :color color
:index index :index index
:select-only select-only :select-only select-only
:on-change #(on-change % color) :on-change #(on-change %1 color %2)
:on-open on-open}]))]]]))) :on-open on-open
:on-close on-close}]))]]])))

View file

@ -113,7 +113,8 @@
:y y :y y
:disable-gradient disable-gradient :disable-gradient disable-gradient
:disable-opacity disable-opacity :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] :on-close (fn [value opacity id file-id]
(when on-close (when on-close
(on-close value opacity id file-id))) (on-close value opacity id file-id)))

View file

@ -46,4 +46,5 @@
(defn camelize (defn camelize
[str] [str]
;; str.replace(":", "-").replace(/-./g, x=>x[1].toUpperCase()) ;; 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)))

View file

@ -1304,6 +1304,7 @@ msgid "labels.no-invitations"
msgstr "لا توجد دعوات." msgstr "لا توجد دعوات."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "اضغط على الزر \"دعوة إلى الفريق\" لدعوة المزيد من الأعضاء إلى هذا الفريق." msgstr "اضغط على الزر \"دعوة إلى الفريق\" لدعوة المزيد من الأعضاء إلى هذا الفريق."

View file

@ -1298,6 +1298,7 @@ msgid "labels.no-invitations"
msgstr "No hi ha invitacions." msgstr "No hi ha invitacions."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "" msgstr ""
"Feu clic al botó «Convida a l'equip» per convidar més membres a aquest " "Feu clic al botó «Convida a l'equip» per convidar més membres a aquest "

View file

@ -1197,6 +1197,7 @@ msgid "labels.no-invitations"
msgstr "Nejsou žádné pozvánky." msgstr "Nejsou žádné pozvánky."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "" msgstr ""
"Chcete-li do tohoto týmu pozvat další členy, stiskněte tlačítko „Pozvat do " "Chcete-li do tohoto týmu pozvat další členy, stiskněte tlačítko „Pozvat do "

View file

@ -1337,6 +1337,7 @@ msgid "labels.no-invitations"
msgstr "Es gibt keine Einladungen." msgstr "Es gibt keine Einladungen."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "" msgstr ""
"Drücken Sie die Schaltfläche \"Zum Team einladen\", um weitere Mitglieder " "Drücken Sie die Schaltfläche \"Zum Team einladen\", um weitere Mitglieder "

View file

@ -443,7 +443,9 @@ msgid "dashboard.import.import-error"
msgstr "There was a problem importing the file. The file wasn't imported." msgstr "There was a problem importing the file. The file wasn't imported."
msgid "dashboard.import.import-message" 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" msgid "dashboard.import.import-warning"
msgstr "Some files containted invalid objects that have been removed." msgstr "Some files containted invalid objects that have been removed."
@ -1379,6 +1381,7 @@ msgid "labels.no-invitations"
msgstr "No pending invitations." msgstr "No pending invitations."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "Click the **Invite people** button to invite people to this team." 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 "modals.delete-shared-confirm.hint"
msgid_plural "modals.delete-shared-confirm.hint" msgid_plural "modals.delete-shared-confirm.hint"
msgstr[0] "" msgstr[0] ""
"If you delete it, those assets will move to the local library of this file. " "If you delete it, those assets will no longer be available from other files. "
"Any unused assets will be lost." "Assets that have already been used will remain in this file (no design will be broken!)."
msgstr[1] "" msgstr[1] ""
"If you delete them, those assets will move to the local library of this " "If you delete them, those assets will no longer be available from other files. "
"file. Any unused assets will be lost." "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 #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.delete-shared-confirm.hint-many" msgid "modals.delete-shared-confirm.hint-many"
msgid_plural "modals.delete-shared-confirm.hint-many" msgid_plural "modals.delete-shared-confirm.hint-many"
msgstr[0] "" msgstr[0] ""
"If you delete it, those assets will move to the local libraries of these " "If you delete it, those assets will no longer be available from other files. "
"files. Any unused assets will be lost." "Assets that have already been used will remain in these files (no design will be broken!)."
msgstr[1] "" msgstr[1] ""
"If you delete them, those assets will move to the local libraries of these " "If you delete them, those assets will no longer be available from other files. "
"files. Any unused assets will be lost." "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 #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.delete-shared-confirm.message" msgid "modals.delete-shared-confirm.message"
@ -1925,21 +1929,21 @@ msgstr[1] "Unpublish"
msgid "modals.unpublish-shared-confirm.hint" msgid "modals.unpublish-shared-confirm.hint"
msgid_plural "modals.unpublish-shared-confirm.hint" msgid_plural "modals.unpublish-shared-confirm.hint"
msgstr[0] "" msgstr[0] ""
"If you unpublish it, those assets will move to the local library of this " "If you unpublish it, those assets will no longer be available from other files. "
"file." "Assets that have already been used will remain in this file (no design will be broken!)."
msgstr[1] "" msgstr[1] ""
"If you unpublish them, those assets will move to the local library of this " "If you unpublish them, those assets will no longer be available from other files. "
"file." "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 #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.unpublish-shared-confirm.hint-many" msgid "modals.unpublish-shared-confirm.hint-many"
msgid_plural "modals.unpublish-shared-confirm.hint-many" msgid_plural "modals.unpublish-shared-confirm.hint-many"
msgstr[0] "" msgstr[0] ""
"If you unpublish it, those assets will move to the local libraries of these " "If you unpublish it, those assets will no longer be available from other files. "
"files." "Assets that have already been used will remain in these files (no design will be broken!)."
msgstr[1] "" msgstr[1] ""
"If you unpublish them, those assets will move to the local libraries of " "If you unpublish them, those assets will no longer be available from other files. "
"these 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 #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.unpublish-shared-confirm.message" msgid "modals.unpublish-shared-confirm.message"
@ -3052,6 +3056,18 @@ msgstr "Show rulers"
msgid "workspace.header.menu.show-textpalette" msgid "workspace.header.menu.show-textpalette"
msgstr "Show fonts palette" 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 #: src/app/main/ui/workspace/header.cljs
msgid "workspace.header.reset-zoom" msgid "workspace.header.reset-zoom"
msgstr "Reset" msgstr "Reset"

View file

@ -450,7 +450,9 @@ msgid "dashboard.import.import-error"
msgstr "Hubo un problema importando el fichero. No ha podido ser importado." msgstr "Hubo un problema importando el fichero. No ha podido ser importado."
msgid "dashboard.import.import-message" 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" msgid "dashboard.import.import-warning"
msgstr "Algunos ficheros contenían objetos erroneos que no han sido importados." msgstr "Algunos ficheros contenían objetos erroneos que no han sido importados."
@ -1450,6 +1452,7 @@ msgid "labels.no-invitations"
msgstr "No hay invitaciones." msgstr "No hay invitaciones."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "Pulsa el botón 'Invitar al equipo' para añadir más integrantes al equipo." 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 "modals.delete-shared-confirm.hint"
msgid_plural "modals.delete-shared-confirm.hint" msgid_plural "modals.delete-shared-confirm.hint"
msgstr[0] "" msgstr[0] ""
"Si lo borras, esos elementos pasarán a formar parte de la biblioteca local " "Si lo borras, sus elementos no estarán disponibles para otros archivos. "
"de este archivo. Cualquier elemento en desuso se perderá." "Los elementos que hayan sido utilizados permanecerán en el archivo (¡ningún diseño se romperá!)."
msgstr[1] "" msgstr[1] ""
"Si los borras, esos elementos pasarán a formar parte de la biblioteca local " "Si los borras, sus elementos no estarán disponibles para otros archivos. "
"de este archivo. Cualquier elemento en desuso se perderá." "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 #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.delete-shared-confirm.hint-many" msgid "modals.delete-shared-confirm.hint-many"
msgid_plural "modals.delete-shared-confirm.hint-many" msgid_plural "modals.delete-shared-confirm.hint-many"
msgstr[0] "" msgstr[0] ""
"Si lo borras, esos elementos pasarán a formar parte de las bibliotecas " "Si lo borras, sus elementos no estarán disponibles para otros archivos. "
"locales de estos archivos. Cualquier elemento en desuso se perderá." "Los elementos que hayan sido utilizados permanecerán en los archivo (¡ningún diseño se romperá!)."
msgstr[1] "" msgstr[1] ""
"Si los borras, esos elementos pasarán a formar parte de las bibliotecas " "Si los borras, sus elementos no estarán disponibles para otros archivos. "
"locales de estos archivos. Cualquier elemento en desuso se perderá." "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 #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.delete-shared-confirm.message" msgid "modals.delete-shared-confirm.message"
@ -2011,21 +2014,22 @@ msgstr[1] "Despublicar"
msgid "modals.unpublish-shared-confirm.hint" msgid "modals.unpublish-shared-confirm.hint"
msgid_plural "modals.unpublish-shared-confirm.hint" msgid_plural "modals.unpublish-shared-confirm.hint"
msgstr[0] "" msgstr[0] ""
"Si la despublicas, esos elementos pasarán a formar parte de la biblioteca " "Si la despublicas, sus elementos no estarán disponibles para otros archivos. "
"local de este archivo." "Los elementos que hayan sido utilizados permanecerán en el archivo (¡ningún diseño se romperá!)."
msgstr[1] "" msgstr[1] ""
"Si las despublicas, esos elementos pasarán a formar parte de la biblioteca " "Si las despublicas, sus elementos no estarán disponibles para otros archivos. "
"local de este archivo." "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 #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.unpublish-shared-confirm.hint-many" msgid "modals.unpublish-shared-confirm.hint-many"
msgid_plural "modals.unpublish-shared-confirm.hint-many" msgid_plural "modals.unpublish-shared-confirm.hint-many"
msgstr[0] "" msgstr[0] ""
"Si la despublicas, esos elementos pasarán a formar parte de las bibliotecas " "Si la despublicas, sus elementos no estarán disponibles para otros archivos. "
"locales de estos archivos." "Los elementos que hayan sido utilizados permanecerán en los archivo (¡ningún diseño se romperá!)."
msgstr[1] "" msgstr[1] ""
"Si las despublicas, esos elementos pasarán a formar parte de las " "Si las despublicas, sus elementos no estarán disponibles para otros archivos. "
"bibliotecas locales de estos 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 #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs
msgid "modals.unpublish-shared-confirm.message" msgid "modals.unpublish-shared-confirm.message"
@ -3215,6 +3219,18 @@ msgstr "Mostrar reglas"
msgid "workspace.header.menu.show-textpalette" msgid "workspace.header.menu.show-textpalette"
msgstr "Mostrar paleta de textos" 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 #: src/app/main/ui/workspace/header.cljs
msgid "workspace.header.reset-zoom" msgid "workspace.header.reset-zoom"
msgstr "Restablecer" msgstr "Restablecer"

View file

@ -1294,6 +1294,7 @@ msgid "labels.no-invitations"
msgstr "Ez dago gonbidapenik." msgstr "Ez dago gonbidapenik."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "Sakatu 'Taldera gonbdiatu' taldekide gehiago izateko." msgstr "Sakatu 'Taldera gonbdiatu' taldekide gehiago izateko."

View file

@ -1289,6 +1289,7 @@ msgid "labels.no-invitations"
msgstr "هیچ دعوتنامه‌ای وجود ندارد." msgstr "هیچ دعوتنامه‌ای وجود ندارد."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "دکمه \"دعوت به تیم\" را فشار دهید تا اعضای بیشتری را به این تیم دعوت کنید." msgstr "دکمه \"دعوت به تیم\" را فشار دهید تا اعضای بیشتری را به این تیم دعوت کنید."

View file

@ -1339,6 +1339,7 @@ msgid "labels.no-invitations"
msgstr "Il n'y a pas d'invitations." msgstr "Il n'y a pas d'invitations."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "" msgstr ""
"Appuyez sur le bouton \"Inviter à l'équipe\" pour inviter d'autres membres " "Appuyez sur le bouton \"Inviter à l'équipe\" pour inviter d'autres membres "

View file

@ -1301,6 +1301,7 @@ msgid "labels.no-invitations"
msgstr "אין הזמנות." msgstr "אין הזמנות."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "לחיצה על הכפתור „הזמנה לצוות” תאפשר להזמין חברים נוספים לצוות הזה." msgstr "לחיצה על הכפתור „הזמנה לצוות” תאפשר להזמין חברים נוספים לצוות הזה."

View file

@ -1289,6 +1289,7 @@ msgid "labels.no-invitations"
msgstr "Nema pozivnica." msgstr "Nema pozivnica."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "Pritisni gumb \"Pozovi u tim\" da pozoveš više članova u ovaj tim." msgstr "Pritisni gumb \"Pozovi u tim\" da pozoveš više članova u ovaj tim."

View file

@ -1195,6 +1195,7 @@ msgid "labels.no-invitations"
msgstr "Tidak ada undangan." msgstr "Tidak ada undangan."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "" msgstr ""
"Tekan tombol \"Undang ke tim\" untuk mengundang lebih banyak anggota ke tim " "Tekan tombol \"Undang ke tim\" untuk mengundang lebih banyak anggota ke tim "

View file

@ -1294,6 +1294,7 @@ msgid "labels.no-invitations"
msgstr "Non ci sono inviti." msgstr "Non ci sono inviti."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "" msgstr ""
"Premi il pulsante \"Invita nel team\" per invitare altri membri in questo " "Premi il pulsante \"Invita nel team\" per invitare altri membri in questo "

View file

@ -1280,6 +1280,7 @@ msgid "labels.no-invitations"
msgstr "Brak zaproszeń." msgstr "Brak zaproszeń."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "" msgstr ""
"Naciśnij przycisk „Zaproś do zespołu”, aby zaprosić więcej członków do tego " "Naciśnij przycisk „Zaproś do zespołu”, aby zaprosić więcej członków do tego "

View file

@ -1295,6 +1295,7 @@ msgid "labels.no-invitations"
msgstr "Não há convites." msgstr "Não há convites."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "" msgstr ""
"Pressione o botão \"Convidar para equipe\" para convidar mais membros para " "Pressione o botão \"Convidar para equipe\" para convidar mais membros para "

View file

@ -1298,6 +1298,7 @@ msgid "labels.no-invitations"
msgstr "Não há convites." msgstr "Não há convites."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "" msgstr ""
"Clica no botão \"Convidar para a equipa\" para convidar mais membros para " "Clica no botão \"Convidar para a equipa\" para convidar mais membros para "

View file

@ -1291,6 +1291,7 @@ msgid "labels.no-invitations"
msgstr "Nu există invitații." msgstr "Nu există invitații."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "" msgstr ""
"Apăsați butonul „Invitați în echipă” pentru a invita mai mulți membri în " "Apăsați butonul „Invitați în echipă” pentru a invita mai mulți membri în "

View file

@ -1278,6 +1278,7 @@ msgid "labels.no-invitations"
msgstr "Приглашений нет." msgstr "Приглашений нет."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "" msgstr ""
"Нажмите кнопку «Пригласить в команду», чтобы пригласить в эту команду " "Нажмите кнопку «Пригласить в команду», чтобы пригласить в эту команду "

View file

@ -1324,6 +1324,7 @@ msgid "labels.no-invitations"
msgstr "Davet yok." msgstr "Davet yok."
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "" msgstr ""
"Bu takıma daha fazla üye davet etmek için \"Takıma davet et\" düğmesine " "Bu takıma daha fazla üye davet etmek için \"Takıma davet et\" düğmesine "

View file

@ -1252,6 +1252,7 @@ msgid "labels.no-invitations"
msgstr "没有邀请。" msgstr "没有邀请。"
#: src/app/main/ui/dashboard/team.cljs #: src/app/main/ui/dashboard/team.cljs
#, markdown
msgid "labels.no-invitations-hint" msgid "labels.no-invitations-hint"
msgstr "点击\"邀请加入团队\",邀请更多成员加入这个团队。" msgstr "点击\"邀请加入团队\",邀请更多成员加入这个团队。"