0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-20 03:31:24 -05:00

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

This commit is contained in:
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 change email and password for users signed in via social login [Taiga #4273](https://tree.taiga.io/project/penpot/issue/4273)
- Fix drag and drop files from browser or file explorer under circumstances [Taiga #5054](https://tree.taiga.io/project/penpot/issue/5054)
- Fix problem when copy/pasting shapes [Taiga #4931](https://tree.taiga.io/project/penpot/issue/4931)
- Fix problem with color picker not able to change hue [Taiga #5065](https://tree.taiga.io/project/penpot/issue/5065)
- Fix problem with outer stroke in texts [Taiga #5078](https://tree.taiga.io/project/penpot/issue/5078)
- Fix problem with text carring over next line when changing to fixed [Taiga #5067](https://tree.taiga.io/project/penpot/issue/5067)
- Fix don't show invite user hero to users with editor role [Taiga #5086](https://tree.taiga.io/project/penpot/issue/5086)
- Fix enter emails on onboarding new user creating team [Taiga #5089](https://tree.taiga.io/project/penpot/issue/5089)
- Fix invalid files amount after moving on dashboard [Taiga #5080](https://tree.taiga.io/project/penpot/issue/5080)
- Fix dashboard left sidebar, the [x] overlaps the field [Taiga #5064](https://tree.taiga.io/project/penpot/issue/5064)
- Fix expanded typography on assets sidebar is moving [Taiga #5063](https://tree.taiga.io/project/penpot/issue/5063)
- Fix spelling mistake in confirmation after importing only 1 file [Taiga #5095](https://tree.taiga.io/project/penpot/issue/5095)
- Fix problem with selection colors and texts [Taiga #5079](https://tree.taiga.io/project/penpot/issue/5079)
- Remove "show in view mode" flag when moving frame to frame [Taiga #5091](https://tree.taiga.io/project/penpot/issue/5091)
- Fix problem creating files in project page [Taiga #5060](https://tree.taiga.io/project/penpot/issue/5060)
- Disable empty names on rename files [Taiga #5088](https://tree.taiga.io/project/penpot/issue/5088)
- Fix problem with SVG and flex layout [Taiga #](https://tree.taiga.io/project/penpot/issue/5099)
- Fix unpublish and delete shared library warning messages [Taiga #5090](https://tree.taiga.io/project/penpot/issue/5090)
- Fix last update project timer update after creating new file [Taiga #5096](https://tree.taiga.io/project/penpot/issue/5096)
- Fix dashboard scrolling using 'Page Up' and 'Page Down' [Taiga #5081](https://tree.taiga.io/project/penpot/issue/5081)
- Fix view mode header buttons overlapping in small resolutions [Taiga #5058](https://tree.taiga.io/project/penpot/issue/5058)
- Fix precision for wrap in flex [Taiga #5072](https://tree.taiga.io/project/penpot/issue/5072)
- Fix relative position overlay positioning [Taiga #5092](https://tree.taiga.io/project/penpot/issue/5092)
- Fix hide grid keyboard shortcut [Github #3071](https://github.com/penpot/penpot/pull/3071)
- Fix problem with opacity in imported SVG's [Taiga #4923](https://tree.taiga.io/project/penpot/issue/4923)
### :heart: Community contributions by (Thank you!)
- To @ondrejkonec: for contributing to the code with:

View file

@ -155,8 +155,18 @@
(.isClosed ^HikariDataSource pool))
(defn read-only?
[pool]
(.isReadOnly ^HikariDataSource pool))
[pool-or-conn]
(cond
(instance? HikariDataSource pool-or-conn)
(.isReadOnly ^HikariDataSource pool-or-conn)
(instance? Connection pool-or-conn)
(.isReadOnly ^Connection pool-or-conn)
:else
(ex/raise :type :internal
:code :invalid-connection
:hint "invalid connection provided")))
(defn create-pool
[cfg]

View file

@ -1050,13 +1050,13 @@
;; --- MUTATION COMMAND: upsert-file-thumbnail
(def sql:upsert-file-thumbnail
(def ^:private sql:upsert-file-thumbnail
"insert into file_thumbnail (file_id, revn, data, props)
values (?, ?, ?, ?::jsonb)
on conflict(file_id, revn) do
update set data = ?, props=?, updated_at=now();")
(defn upsert-file-thumbnail
(defn- upsert-file-thumbnail!
[conn {:keys [file-id revn data props]}]
(let [props (db/tjson (or props {}))]
(db/exec-one! conn [sql:upsert-file-thumbnail
@ -1076,5 +1076,6 @@
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(check-edition-permissions! conn profile-id file-id)
(upsert-file-thumbnail conn params)
(when-not (db/read-only? conn)
(upsert-file-thumbnail! conn params))
nil))

View file

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

View file

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

View file

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

View file

@ -32,7 +32,14 @@
links (->> (db/query conn :share-link {:file-id file-id})
(mapv (fn [row]
(update row :pages db/decode-pgarray #{}))))
(-> row
(update :pages db/decode-pgarray #{})
;; NOTE: the flags are deprecated but are still present
;; on the table on old rows. The flags are pgarray and
;; for avoid decoding it (because they are no longer used
;; on frontend) we just dissoc the column attribute from
;; row.
(dissoc :flags)))))
fonts (db/query conn :team-font-variant
{:team-id (:team-id project)

View file

@ -37,6 +37,14 @@
:is-admin false
:can-edit false)))
(defn make-admin-predicate-fn
"A simple factory for admin permission predicate functions."
[qfn]
(us/assert fn? qfn)
(fn check
([perms] (:is-admin perms))
([conn & args] (check (apply qfn conn args)))))
(defn make-edition-predicate-fn
"A simple factory for edition permission predicate functions."
[qfn]

View file

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

View file

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

View file

@ -133,29 +133,38 @@
(-> (get-shape-filter-bounds shape)
(add-padding (calculate-padding shape true))))
bounds (if (or (:masked-group? shape)
(and (cph/frame-shape? shape)
(not (:show-content shape))))
[(calculate-base-bounds shape)]
(cph/reduce-objects
objects
(fn [shape]
(and (d/not-empty? (:shapes shape))
(or (not (cph/frame-shape? shape))
(:show-content shape))
bounds
(cond
(empty? (:shapes shape))
[(calculate-base-bounds shape)]
(or (not (cph/group-shape? shape))
(not (:masked-group? shape)))))
(:masked-group? shape)
[(calculate-base-bounds shape)]
(:id shape)
(and (cph/frame-shape? shape) (not (:show-content shape)))
[(calculate-base-bounds shape)]
(fn [result shape]
(conj result (get-object-bounds objects shape)))
:else
(cph/reduce-objects
objects
(fn [shape]
(and (d/not-empty? (:shapes shape))
(or (not (cph/frame-shape? shape))
(:show-content shape))
[(calculate-base-bounds shape)]))
(or (not (cph/group-shape? shape))
(not (:masked-group? shape)))))
children-bounds (cond->> (gsr/join-selrects bounds)
(not (cph/frame-shape? shape)) (or (:children-bounds shape)))
(:id shape)
(fn [result child]
(conj result (calculate-base-bounds child)))
[(calculate-base-bounds shape)]))
children-bounds
(cond->> (gsr/join-selrects bounds)
(not (cph/frame-shape? shape)) (or (:children-bounds shape)))
filters (shape->filters shape)
blur-value (or (-> shape :blur :value) 0)]

View file

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

View file

@ -104,8 +104,10 @@
(if (and (some? line-data)
(or (not wrap?)
(and row? (<= next-line-min-width layout-width))
(and col? (<= next-line-min-height layout-height))))
(and row? (or (< next-line-min-width layout-width)
(mth/close? next-line-min-width layout-width 0.5)))
(and col? (or (< next-line-min-height layout-height)
(mth/close? next-line-min-height layout-height 0.5)))))
(recur {:line-min-width (if row? (+ line-min-width next-min-width) (max line-min-width next-min-width))
:line-max-width (if row? (+ line-max-width next-max-width) (max line-max-width next-max-width))

View file

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

View file

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

View file

@ -526,19 +526,22 @@
(loop [current-val init-val
current-id (first root-children)
pending-ids (rest root-children)]
pending-ids (rest root-children)
processed #{}]
(if (contains? processed current-id)
(recur current-val (first pending-ids) (rest pending-ids) processed)
(let [current-shape (get objects current-id)
processed (conj processed current-id)
next-val (reducer-fn current-val current-shape)
next-pending-ids
(if (or (nil? check-children?) (check-children? current-shape))
(concat (or (:shapes current-shape) []) pending-ids)
pending-ids)]
(let [current-shape (get objects current-id)
next-val (reducer-fn current-val current-shape)
next-pending-ids
(if (or (nil? check-children?) (check-children? current-shape))
(concat (or (:shapes current-shape) []) pending-ids)
pending-ids)]
(if (empty? next-pending-ids)
next-val
(recur next-val (first next-pending-ids) (rest next-pending-ids)))))))))
(if (empty? next-pending-ids)
next-val
(recur next-val (first next-pending-ids) (rest next-pending-ids) processed)))))))))
(defn selected-with-children
[objects selected]
@ -569,3 +572,25 @@
(d/enumerate)
(sort comparator-layout-z-index)
(mapv second)))
(defn common-parent-frame
"Search for the common frame for the selected shapes. Otherwise returns the root frame"
[objects selected]
(loop [frame-id (get-in objects [(first selected) :frame-id])
frame-parents (get-parent-ids objects frame-id)
selected (rest selected)]
(if (empty? selected)
frame-id
(let [current (first selected)
parent? (into #{} (get-parent-ids objects current))
[frame-id frame-parents]
(if (parent? frame-id)
[frame-id frame-parents]
(let [frame-id (d/seek parent? frame-parents)]
[frame-id (get-parent-ids objects frame-id)]))]
(recur frame-id frame-parents (rest selected))))))

View file

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

View file

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

View file

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

Binary file not shown.

After

(image error) Size: 269 KiB

Binary file not shown.

After

(image error) Size: 560 KiB

Binary file not shown.

After

(image error) Size: 789 KiB

Binary file not shown.

After

(image error) Size: 220 KiB

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -925,6 +925,13 @@
{:num-files (count ids)
:project-id project-id})
ptk/UpdateEvent
(update [_ state]
(let [origin-project (get-in state [:dashboard-files (first ids) :project-id])]
(-> state
(update-in [:dashboard-projects origin-project :count] #(- % (count ids)))
(update-in [:dashboard-projects project-id :count] #(+ % (count ids))))))
ptk/WatchEvent
(watch [_ _ _]
(let [{:keys [on-success on-error]

View file

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

View file

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

View file

@ -138,6 +138,7 @@
(pcb/with-objects objects)
(cond-> (not (ctl/any-layout? objects frame-id))
(pcb/update-shapes ordered-indexes ctl/remove-layout-item-data))
(pcb/update-shapes ordered-indexes #(cond-> % (cph/frame-shape? %) (assoc :hide-in-viewer true)))
(pcb/change-parent frame-id to-move-shapes 0)
(cond-> (ctl/grid-layout? objects frame-id)
(pcb/update-shapes [frame-id] ctl/assign-cells))))]

View file

@ -354,12 +354,14 @@
:fn #(st/emit! (dw/select-all))}
:toggle-grid {:tooltip (ds/meta "'")
:command (ds/c-mod "'")
;;https://github.com/ccampbell/mousetrap/issues/85
:command [(ds/c-mod "'") (ds/c-mod "219")]
:subsections [:main-menu]
:fn #(st/emit! (toggle-layout-flag :display-grid))}
:toggle-snap-grid {:tooltip (ds/meta-shift "'")
:command (ds/c-mod "shift+'")
;;https://github.com/ccampbell/mousetrap/issues/85
:command [(ds/c-mod "shift+'") (ds/c-mod "shift+219")]
:subsections [:main-menu]
:fn #(st/emit! (toggle-layout-flag :snap-grid))}

View file

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

View file

@ -377,7 +377,7 @@
(assoc-in [:workspace-local :edition] (-> selected first :id)))))))
(defn not-changed? [old-dim new-dim]
(> (mth/abs (- old-dim new-dim)) 1))
(> (mth/abs (- old-dim new-dim)) 0.1))
(defn commit-resize-text
[]

View file

@ -104,7 +104,7 @@
(defn start-resize
"Enter mouse resize mode, until mouse button is released."
[handler ids shape]
(letfn [(resize
(letfn [(resize
[shape initial layout [point lock? center? point-snap]]
(let [{:keys [width height]} (:selrect shape)
{:keys [rotation]} shape
@ -745,13 +745,16 @@
(remove (fn [shape]
(and (ctl/layout-absolute? shape)
(= frame-id (:parent-id shape))))))
moving-shapes-ids
(map :id moving-shapes)
changes
(-> (pcb/empty-changes it page-id)
(pcb/with-objects objects)
;; Remove layout-item properties when moving a shape outside a layout
(cond-> (not (ctl/any-layout? objects frame-id))
(pcb/update-shapes (map :id moving-shapes) ctl/remove-layout-item-data))
(pcb/update-shapes moving-shapes-ids ctl/remove-layout-item-data))
(pcb/update-shapes moving-shapes-ids #(cond-> % (cph/frame-shape? %) (assoc :hide-in-viewer true)))
(pcb/change-parent frame-id moving-shapes drop-index)
(pcb/remove-objects empty-parents))]

View file

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

View file

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

View file

@ -269,7 +269,9 @@
(mf/use-fn
(mf/deps file)
(fn [name]
(st/emit! (dd/rename-file (assoc file :name name)))
(let [name (str/trim name)]
(when (not= name "")
(st/emit! (dd/rename-file (assoc file :name name)))))
(swap! local assoc :edition false)))
on-edit

View file

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

View file

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

View file

@ -177,7 +177,9 @@
:on-submit on-submit}]]
[:div.action-buttons
[:& fm/submit-button {:label (tr "modals.invite-member-confirm.accept")}]]]]))
[:& fm/submit-button {:label (tr "modals.invite-member-confirm.accept")
:disabled (and (boolean (some current-data-emails current-members-emails))
(empty? (remove current-members-emails current-data-emails)))}]]]]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; MEMBERS SECTION
@ -586,7 +588,8 @@
[:div.empty-invitations
[:span (tr "labels.no-invitations")]
(when can-invite?
[:span (tr "labels.no-invitations-hint")])])
[:& i18n/tr-html {:label "labels.no-invitations-hint"
:tag-name "span"}])])
(mf/defc invitation-section
[{:keys [team invitations] :as props}]
@ -897,6 +900,10 @@
stats (mf/deref refs/dashboard-team-stats)
you-owner? (get-in team [:permissions :is-owner])
you-admin? (get-in team [:permissions :is-admin])
can-edit? (or you-owner? you-admin?)
on-image-click
(mf/use-callback #(dom/click (mf/ref-val finput)))
@ -928,12 +935,14 @@
[:div.label (tr "dashboard.team-info")]
[:div.name (:name team)]
[:div.icon
[:span.update-overlay {:on-click on-image-click} i/image]
(when can-edit?
[:span.update-overlay {:on-click on-image-click} i/image])
[:img {:src (cfg/resolve-team-photo-url team)}]
[:& file-uploader {:accept "image/jpeg,image/png"
:multi false
:ref finput
:on-selected on-file-selected}]]]
(when can-edit?
[:& file-uploader {:accept "image/jpeg,image/png"
:multi false
:ref finput
:on-selected on-file-selected}])]]
[:div.block.owner-block
[:div.label (tr "dashboard.team-members")]

View file

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

View file

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

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

View file

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

View file

@ -67,7 +67,8 @@
(mf/use-fn
(mf/deps current-color @drag?)
(fn [color]
(when (not= (str/lower (:hex color)) (str/lower (:hex current-color)))
(when (or (not= (str/lower (:hex color)) (str/lower (:hex current-color)))
(not= (:h color) (:h current-color)))
(let [recent-color (merge current-color color)
recent-color (dc/materialize-color-components recent-color)]
(st/emit! (dc/update-colorpicker-color recent-color (not @drag?)))))))

View file

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

View file

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

View file

@ -161,27 +161,37 @@
on-change
(mf/use-fn
(fn [new-color old-color]
(fn [new-color old-color from-picker?]
(let [old-color (-> old-color
(dissoc :name)
(dissoc :path)
(d/without-nils))
prev-color (-> @prev-color*
(dissoc :name)
(dissoc :path)
(d/without-nils))
prev-color (when @prev-color*
(-> @prev-color*
(dissoc :name)
(dissoc :path)
(d/without-nils)))
;; When dragging on the color picker sometimes all the shapes hasn't updated the color to the prev value so we need this extra calculation
shapes-by-old-color (get @grouped-colors* old-color)
shapes-by-prev-color (get @grouped-colors* prev-color)
shapes-by-color (or shapes-by-prev-color shapes-by-old-color)]
(reset! prev-color* new-color)
(st/emit! (dc/change-color-in-selected new-color shapes-by-color old-color)))))
on-open (mf/use-fn
(fn [color]
(reset! prev-color* color)))
(when from-picker?
(reset! prev-color* new-color))
(st/emit! (dc/change-color-in-selected new-color shapes-by-color (or prev-color old-color))))))
on-open
(mf/use-fn
(fn []
(reset! prev-color* nil)))
on-close
(mf/use-fn
(fn []
(reset! prev-color* nil)))
on-detach
(mf/use-fn
@ -212,8 +222,9 @@
:index index
:on-detach on-detach
:select-only select-only
:on-change #(on-change % color)
:on-open on-open}])
:on-change #(on-change %1 color %2)
:on-open on-open
:on-close on-close}])
(when (and (false? @expand-lib-color) (< 3 (count library-colors)))
[:div.expand-colors {:on-click #(reset! expand-lib-color true)}
[:span i/actions]
@ -225,8 +236,9 @@
:index index
:on-detach on-detach
:select-only select-only
:on-change #(on-change % color)
:on-open on-open}]))]
:on-change #(on-change %1 color %2)
:on-open on-open
:on-close on-close}]))]
[:div.selected-colors
(for [[index color] (d/enumerate (take 3 colors))]
@ -234,8 +246,9 @@
:color color
:index index
:select-only select-only
:on-change #(on-change % color)
:on-open on-open}])
:on-change #(on-change %1 color %2)
:on-open on-open
:on-close on-close}])
(when (and (false? @expand-color) (< 3 (count colors)))
[:div.expand-colors {:on-click #(reset! expand-color true)}
[:span i/actions]
@ -246,5 +259,6 @@
:color color
:index index
:select-only select-only
:on-change #(on-change % color)
:on-open on-open}]))]]])))
:on-change #(on-change %1 color %2)
:on-open on-open
:on-close on-close}]))]]])))

View file

@ -113,7 +113,8 @@
:y y
:disable-gradient disable-gradient
:disable-opacity disable-opacity
:on-change #(on-change (merge uc/empty-color %))
;; on-change second parameter means if the source is the color-picker
:on-change #(on-change (merge uc/empty-color %) true)
:on-close (fn [value opacity id file-id]
(when on-close
(on-close value opacity id file-id)))

View file

@ -46,4 +46,5 @@
(defn camelize
[str]
;; str.replace(":", "-").replace(/-./g, x=>x[1].toUpperCase())
(js* "~{}.replace(\":\", \"-\").replace(/-./g, x=>x[1].toUpperCase())", str))
(when (not (nil? str))
(js* "~{}.replace(\":\", \"-\").replace(/-./g, x=>x[1].toUpperCase())", str)))

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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