0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-28 07:31:25 -05:00

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

This commit is contained in:
Andrés Moya 2023-01-30 12:47:20 +01:00
commit e1d1ecbc24
89 changed files with 2234 additions and 725 deletions

View file

@ -23,6 +23,16 @@
- Handoff visual improvements [Taiga #3124](https://tree.taiga.io/project/penpot/us/3124)
- Dynamic alignment only in sight [Github 1971](https://github.com/penpot/penpot/issues/1971)
- Add some accessibility to shortcut panel [Taiga #4713](https://tree.taiga.io/project/penpot/issue/4713)
- Add shortcuts for text editing [Taiga #2052](https://tree.taiga.io/project/penpot/us/2052)
- Second level boards treated as groups in terms of selection [Taiga #4269](https://tree.taiga.io/project/penpot/us/4269)
- Performance improvements both for backend and frontend
- Accessibility improvements for login area [Taiga #4353](https://tree.taiga.io/project/penpot/us/4353)
- Outbound webhooks [Taiga #4577](https://tree.taiga.io/project/penpot/us/4577)
- Add copy invitation link to the invitation options [Taiga #4213](https://tree.taiga.io/project/penpot/us/4213)
- Dynamic alignment only in sight [Taiga #3537](https://tree.taiga.io/project/penpot/us/3537)
- Improve naming of layers [Taiga #4036](https://tree.taiga.io/project/penpot/us/4036)
- Add zoom lense [Taiga #4691](https://tree.taiga.io/project/penpot/us/4691)
- Detect potential problems with custom font vertical metrics [Taiga #4697](https://tree.taiga.io/project/penpot/us/4697)
### :bug: Bugs fixed
@ -63,6 +73,18 @@
- Fix hidden layers inside groups become visible after the group visibility is changed[Taiga #4710](https://tree.taiga.io/project/penpot/issue/4710)
- Fix format of HSLA color on viewer [Taiga #4393](https://tree.taiga.io/project/penpot/issue/4393)
- Fix some typos [Taiga #4724](https://tree.taiga.io/project/penpot/issue/4724)
- Fix ctrl+c for inspect code [Taiga #4739](https://tree.taiga.io/project/penpot/issue/4739)
- Fix text in custom font is not at the expected position at export [Taiga #4394](https://tree.taiga.io/project/penpot/issue/4394)
- Fix unneeded popup when updating local components [Taiga #4430](https://tree.taiga.io/project/penpot/issue/4430)
- Fix multiuser - "Shadow" element is not updating immediately [Taiga #4709](https://tree.taiga.io/project/penpot/issue/4709)
- Fix paths not flagged as modified when resized [Taiga #4742](https://tree.taiga.io/project/penpot/issue/4742)
- Fix resend invitation doesn't reset the expiration date [Taiga #4741](https://tree.taiga.io/project/penpot/issue/4741)
- Fix incorrect state after undo page creation [Taiga #4690](https://tree.taiga.io/project/penpot/issue/4690)
- Fix copy paste texts with typography assets linked [Taiga #4750](https://tree.taiga.io/project/penpot/issue/4750)
### :heart: Community contributions by (Thank you!)
- To @iprithvitharun: let's make UX Writing contributions in Open Source a trend!
## 1.16.2-beta

View file

@ -493,6 +493,7 @@
(library-summary [{:keys [id data] :as file}]
(binding [pmap/*load-fn* (partial load-pointer conn id)]
{:components (assets-sample (:components data) 4)
:media (assets-sample (:media data) 3)
:colors (assets-sample (:colors data) 3)
:typographies (assets-sample (:typographies data) 3)}))]

View file

@ -612,7 +612,7 @@
"insert into team_invitation(team_id, email_to, role, valid_until)
values (?, ?, ?, ?)
on conflict(team_id, email_to) do
update set role = ?, updated_at = now();")
update set role = ?, valid_until = ?, updated_at = now();")
(defn- create-invitation-token
[cfg {:keys [profile-id valid-until team-id member-id member-email role]}]
@ -683,7 +683,7 @@
{:id (:id member)})))
(do
(db/exec-one! conn [sql:upsert-team-invitation
(:id team) (str/lower email) (name role) expire (name role)])
(:id team) (str/lower email) (name role) expire (name role) expire])
(eml/send! {::eml/conn conn
::eml/factory eml/invite-to-team
:public-uri (cf/get :public-uri)

View file

@ -41,3 +41,35 @@
([file state]
(repair-orphaned-shapes (:data file))
(update state :total (fnil inc 0))))
(defn rename-layout-attrs
([file]
(let [found? (volatile! false)]
(letfn [(update-shape
[shape]
(when (or (= (:layout-flex-dir shape) :reverse-row)
(= (:layout-flex-dir shape) :reverse-column)
(= (:layout-wrap-type shape) :no-wrap))
(vreset! found? true))
(cond-> shape
(= (:layout-flex-dir shape) :reverse-row)
(assoc :layout-flex-dir :row-reverse)
(= (:layout-flex-dir shape) :reverse-column)
(assoc :layout-flex-dir :column-reverse)
(= (:layout-wrap-type shape) :no-wrap)
(assoc :layout-wrap-type :nowrap)))
(update-page
[page]
(h/update-shapes page update-shape))]
(let [new-file (update file :data h/update-pages update-page)]
(when @found?
(l/info :hint "Found attrs to rename in file"
:id (:id file)
:name (:name file)))
new-file))))
([file state]
(rename-layout-attrs file)
(update state :total (fnil inc 0))))

View file

@ -87,11 +87,27 @@
(let [parent-id (:id parent)
parent-bounds @(get bounds parent-id)
row? (ctl/row? parent)
col? (ctl/col? parent)
space-around? (ctl/space-around? parent)
content-around? (ctl/content-around? parent)
[layout-gap-row layout-gap-col] (ctl/gaps parent)
row-pad (if (or (and col? space-around?)
(and row? content-around?))
layout-gap-row
0)
col-pad (if (or(and row? space-around?)
(and col? content-around?))
layout-gap-col
0)
{pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding
pad-top (or pad-top 0)
pad-right (or pad-right 0)
pad-bottom (or pad-bottom 0)
pad-left (or pad-left 0)
pad-top (+ (or pad-top 0) row-pad)
pad-right (+ (or pad-right 0) col-pad)
pad-bottom (+ (or pad-bottom 0) row-pad)
pad-left (+ (or pad-left 0) col-pad)
child-bounds
(fn [child]

View file

@ -26,6 +26,7 @@
(let [col? (ctl/col? shape)
row? (ctl/row? shape)
space-around? (ctl/space-around? shape)
wrap? (and (ctl/wrap? shape)
(or col? (not (ctl/auto-width? shape)))
@ -77,8 +78,18 @@
next-max-width (+ child-margin-width (if fill-width? child-max-width child-width))
next-max-height (+ child-margin-height (if fill-height? child-max-height child-height))
next-line-min-width (+ line-min-width next-min-width (* layout-gap-col num-children))
next-line-min-height (+ line-min-height next-min-height (* layout-gap-row num-children))]
total-gap-col (if space-around?
(* layout-gap-col (+ num-children 2))
(* layout-gap-col num-children))
total-gap-row (if space-around?
(* layout-gap-row (+ num-children 2))
(* layout-gap-row num-children))
next-line-min-width (+ line-min-width next-min-width total-gap-col)
next-line-min-height (+ line-min-height next-min-height total-gap-row)
]
(if (and (some? line-data)
(or (not wrap?)
@ -141,6 +152,8 @@
(let [row? (ctl/row? parent)
col? (ctl/col? parent)
auto-width? (ctl/auto-width? parent)
auto-height? (ctl/auto-height? parent)
[layout-gap-row layout-gap-col] (ctl/gaps parent)
@ -175,12 +188,12 @@
;; When align-items is stretch we need to adjust the main axis size to grow for the full content
stretch-width-fix
(if (and col? (ctl/content-stretch? parent))
(if (and col? (ctl/content-stretch? parent) (not auto-width?))
(/ (- layout-width (* layout-gap-col (dec num-lines)) total-max-width) num-lines)
0)
stretch-height-fix
(if (and row? (ctl/content-stretch? parent))
(if (and row? (ctl/content-stretch? parent) (not auto-height?))
(/ (- layout-height (* layout-gap-row (dec num-lines)) total-max-height) num-lines)
0)
@ -226,17 +239,39 @@
row? (ctl/row? shape)
col? (ctl/col? shape)
auto-height? (ctl/auto-height? shape)
auto-width? (ctl/auto-width? shape)
space-between? (ctl/space-between? shape)
space-around? (ctl/space-around? shape)
[layout-gap-row layout-gap-col] (ctl/gaps shape)
margin-x
(cond (and row? space-around? (not auto-width?))
(max layout-gap-col (/ (- width line-width) (inc num-children)))
(and row? space-around? auto-width?)
layout-gap-col
:else
0)
margin-y
(cond (and col? space-around? (not auto-height?))
(max layout-gap-row (/ (- height line-height) (inc num-children)))
(and col? space-around? auto-height?)
layout-gap-row
:else
0)
layout-gap-col
(cond (and row? space-around?)
0
(and row? space-between?)
(/ (- width line-width) (dec num-children))
(and row? space-between? (not auto-width?))
(max layout-gap-col (/ (- width line-width) (dec num-children)))
:else
layout-gap-col)
@ -245,21 +280,11 @@
(cond (and col? space-around?)
0
(and col? space-between?)
(/ (- height line-height) (dec num-children))
(and col? space-between? (not auto-height?))
(max layout-gap-row (/ (- height line-height) (dec num-children)))
:else
layout-gap-row)
margin-x
(if (and row? space-around?)
(/ (- width line-width) (inc num-children))
0)
margin-y
(if (and col? space-around?)
(/ (- height line-height) (inc num-children))
0)]
layout-gap-row)]
(assoc line-data
:layout-bounds layout-bounds
:layout-gap-row layout-gap-row
@ -308,4 +333,3 @@
{:layout-lines layout-lines
:layout-bounds layout-bounds
:reverse? reverse?}))

View file

@ -43,7 +43,7 @@
(gpt/add (vv free-height-gap))
around?
(gpt/add (vv (/ free-height (inc num-lines)))))
(gpt/add (vv (max lines-gap-row (/ free-height (inc num-lines))))))
col?
(cond-> center?
@ -53,7 +53,7 @@
(gpt/add (hv free-width-gap))
around?
(gpt/add (hv (/ free-width (inc num-lines))))))))
(gpt/add (hv (max lines-gap-col (/ free-width (inc num-lines)))))))))
(defn get-next-line
[parent layout-bounds {:keys [line-width line-height]} base-p total-width total-height num-lines]
@ -63,6 +63,9 @@
row? (ctl/row? parent)
col? (ctl/col? parent)
auto-width? (ctl/auto-width? parent)
auto-height? (ctl/auto-height? parent)
[layout-gap-row layout-gap-col] (ctl/gaps parent)
hv #(gpo/start-hv layout-bounds %)
@ -75,8 +78,11 @@
free-width (- layout-width total-width)
free-height (- layout-height total-height)
line-gap-row
line-gap-col
(cond
auto-width?
layout-gap-col
stretch?
(/ free-width num-lines)
@ -89,8 +95,11 @@
:else
layout-gap-col)
line-gap-col
line-gap-row
(cond
auto-height?
layout-gap-row
stretch?
(/ free-height num-lines)
@ -105,10 +114,10 @@
(cond-> base-p
row?
(gpt/add (vv (+ line-height (max layout-gap-row line-gap-col))))
(gpt/add (vv (+ line-height (max layout-gap-row line-gap-row))))
col?
(gpt/add (hv (+ line-width (max layout-gap-col line-gap-row)))))))
(gpt/add (hv (+ line-width (max layout-gap-col line-gap-col)))))))
(defn get-start-line
"Cross axis line. It's position is fixed along the different lines"
@ -126,18 +135,20 @@
v-center? (ctl/v-center? parent)
v-end? (ctl/v-end? parent)
content-stretch? (ctl/content-stretch? parent)
auto-width? (ctl/auto-width? parent)
auto-height? (ctl/auto-height? parent)
hv (partial gpo/start-hv layout-bounds)
vv (partial gpo/start-vv layout-bounds)
children-gap-width (* layout-gap-col (dec num-children))
children-gap-height (* layout-gap-row (dec num-children))
line-height
(if (and row? content-stretch?)
(if (and row? content-stretch? (not auto-height?))
(+ line-height (/ (- layout-height total-height) num-lines))
line-height)
line-width
(if (and col? content-stretch?)
(if (and col? content-stretch? (not auto-width?))
(+ line-width (/ (- layout-width total-width) num-lines))
line-width)
@ -263,7 +274,7 @@
col?
(-> (gpt/add (vv (+ margin-top margin-bottom)))
(gpt/add (vv (+ child-height layout-gap-row))))
(some? margin-x)
(gpt/add (hv margin-x))

View file

@ -17,6 +17,7 @@
[app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.path :as gpa]
[app.common.geom.shapes.rect :as gpr]
[app.common.math :as mth]
[app.common.pages.helpers :as cph]
[app.common.types.modifiers :as ctm]
[app.common.uuid :as uuid]))
@ -159,67 +160,79 @@
"Calculate the transform matrix to convert from the selrect to the points bounds
TargetM = SourceM * Transform ==> Transform = TargetM * inv(SourceM)"
[{:keys [x1 y1 x2 y2]} [d1 d2 _ d4]]
#?(:clj
;; NOTE: the source matrix may not be invertible we can't
;; calculate the transform, so on exception we return `nil`
(ex/ignoring
(let [target-points-matrix
(->> (list (:x d1) (:x d2) (:x d4)
(:y d1) (:y d2) (:y d4)
1 1 1 )
(into-array Double/TYPE)
(Matrix/from1DArray 3 3))
;; If the coordinates are very close to zero (but not zero) the rounding can mess with the
;; transforms. So we round to zero the values
(let [x1 (mth/round-to-zero x1)
y1 (mth/round-to-zero y1)
x2 (mth/round-to-zero x2)
y2 (mth/round-to-zero y2)
d1x (mth/round-to-zero (:x d1))
d1y (mth/round-to-zero (:y d1))
d2x (mth/round-to-zero (:x d2))
d2y (mth/round-to-zero (:y d2))
d4x (mth/round-to-zero (:x d4))
d4y (mth/round-to-zero (:y d4))]
#?(:clj
;; NOTE: the source matrix may not be invertible we can't
;; calculate the transform, so on exception we return `nil`
(ex/ignoring
(let [target-points-matrix
(->> (list d1x d2x d4x
d1y d2y d4y
1 1 1)
(into-array Double/TYPE)
(Matrix/from1DArray 3 3))
source-points-matrix
(->> (list x1 x2 x1
y1 y1 y2
1 1 1)
(into-array Double/TYPE)
(Matrix/from1DArray 3 3))
source-points-matrix
(->> (list x1 x2 x1
y1 y1 y2
1 1 1)
(into-array Double/TYPE)
(Matrix/from1DArray 3 3))
;; May throw an exception if the matrix is not invertible
source-points-matrix-inv
(.. source-points-matrix
(withInverter LinearAlgebra/GAUSS_JORDAN)
(inverse))
;; May throw an exception if the matrix is not invertible
source-points-matrix-inv
(.. source-points-matrix
(withInverter LinearAlgebra/GAUSS_JORDAN)
(inverse))
transform-jvm
(.. target-points-matrix
(multiply source-points-matrix-inv))]
transform-jvm
(.. target-points-matrix
(multiply source-points-matrix-inv))]
(gmt/matrix (.get transform-jvm 0 0)
(.get transform-jvm 1 0)
(.get transform-jvm 0 1)
(.get transform-jvm 1 1)
(.get transform-jvm 0 2)
(.get transform-jvm 1 2))))
(gmt/matrix (.get transform-jvm 0 0)
(.get transform-jvm 1 0)
(.get transform-jvm 0 1)
(.get transform-jvm 1 1)
(.get transform-jvm 0 2)
(.get transform-jvm 1 2))))
:cljs
(let [target-points-matrix
(Matrix. #js [#js [(:x d1) (:x d2) (:x d4)]
#js [(:y d1) (:y d2) (:y d4)]
#js [ 1 1 1]])
:cljs
(let [target-points-matrix
(Matrix. #js [#js [d1x d2x d4x]
#js [d1y d2y d4y]
#js [ 1 1 1]])
source-points-matrix
(Matrix. #js [#js [x1 x2 x1]
#js [y1 y1 y2]
#js [ 1 1 1]])
source-points-matrix
(Matrix. #js [#js [x1 x2 x1]
#js [y1 y1 y2]
#js [ 1 1 1]])
;; returns nil if not invertible
source-points-matrix-inv (.getInverse source-points-matrix)
;; returns nil if not invertible
source-points-matrix-inv (.getInverse source-points-matrix)
;; TargetM = SourceM * Transform ==> Transform = TargetM * inv(SourceM)
transform-js
(when source-points-matrix-inv
(.multiply target-points-matrix source-points-matrix-inv))]
;; TargetM = SourceM * Transform ==> Transform = TargetM * inv(SourceM)
transform-js
(when source-points-matrix-inv
(.multiply target-points-matrix source-points-matrix-inv))]
(when transform-js
(gmt/matrix (.getValueAt transform-js 0 0)
(.getValueAt transform-js 1 0)
(.getValueAt transform-js 0 1)
(.getValueAt transform-js 1 1)
(.getValueAt transform-js 0 2)
(.getValueAt transform-js 1 2))))))
(when transform-js
(gmt/matrix (.getValueAt transform-js 0 0)
(.getValueAt transform-js 1 0)
(.getValueAt transform-js 0 1)
(.getValueAt transform-js 1 1)
(.getValueAt transform-js 0 2)
(.getValueAt transform-js 1 2)))))))
(defn calculate-geometry
[points]

View file

@ -174,6 +174,13 @@
(defn almost-zero? [num]
(< (abs (double num)) 1e-4))
(defn round-to-zero
"Given a number if it's close enough to zero round to the zero to avoid precision problems"
[num]
(if (almost-zero? num)
0
num))
(defonce float-equal-precision 0.001)
(defn close?

View file

@ -9,7 +9,8 @@
[clojure.spec.alpha :as s]
[cuerdas.core :as str]))
(def valid-font-types #{"font/ttf" "font/woff", "application/font-woff", "font/otf"})
;; We have added ".ttf" as string to solve a problem with chrome input selector
(def valid-font-types #{"font/ttf", ".ttf", "font/woff", "application/font-woff", "font/otf"})
(def valid-image-types #{"image/jpeg", "image/png", "image/webp", "image/gif", "image/svg+xml"})
(def str-image-types (str/join "," valid-image-types))
(def str-font-types (str/join "," valid-font-types))

View file

@ -387,6 +387,10 @@
is-geometry? (and (or (= group :geometry-group)
(and (= group :content-group) (= (:type shape) :path)))
(not (#{:width :height} attr))) ;; :content in paths are also considered geometric
;; TODO: the check of :width and :height probably may be removed
;; after the check added in data/workspace/modifiers/check-delta
;; function. Better check it and test toroughly when activating
;; components-v2 mode.
shape-ref (:shape-ref shape)
root-name? (and (= group :name-group)
(:component-root? shape))

View file

@ -9,7 +9,7 @@
[app.common.colors :as clr]
[app.common.uuid :as uuid]))
(def file-version 19)
(def file-version 20)
(def default-color clr/gray-20)
(def root uuid/zero)

View file

@ -7,11 +7,12 @@
(ns app.common.pages.migrations
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt]
[app.common.geom.shapes :as gsh]
[app.common.geom.shapes.path :as gsp]
[app.common.geom.shapes.text :as gsht]
[app.common.logging :as l]
[app.common.logging :as log]
[app.common.math :as mth]
[app.common.pages :as cp]
[app.common.pages.helpers :as cph]
@ -23,13 +24,15 @@
(defmulti migrate :version)
(log/set-level! :info)
(defn migrate-data
([data] (migrate-data data cp/file-version))
([data to-version]
(if (= (:version data) to-version)
data
(let [migrate-fn #(do
(l/trace :hint "migrate file" :id (:id %) :version-from %2 :version-to (inc %2))
(log/trace :hint "migrate file" :id (:id %) :version-from %2 :version-to (inc %2))
(migrate (assoc %1 :version (inc %2))))]
(reduce migrate-fn data (range (:version data 0) to-version))))))
@ -427,5 +430,31 @@
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
(defmethod migrate 20
[data]
(letfn [(update-object [objects object]
(let [frame-id (:frame-id object)
calculated-frame-id
(or (->> (cph/get-parent-ids objects (:id object))
(map (d/getf objects))
(d/seek cph/frame-shape?)
:id)
;; If we cannot find any we let the frame-id as it was before
frame-id)]
(when (not= frame-id calculated-frame-id)
(log/info :hint "Fix wrong frame-id"
:shape (:name object)
:id (:id object)
:current (dm/get-in objects [frame-id :name])
:calculated (get-in objects [calculated-frame-id :name])))
(assoc object :frame-id calculated-frame-id)))
(update-container [container]
(update container :objects #(update-vals % (partial update-object %))))]
(-> data
(update :pages-index update-vals update-container)
(update :components update-vals update-container))))
;; TODO: pending to do a migration for delete already not used fill
;; and stroke props. This should be done for >1.14.x version.

View file

@ -11,13 +11,13 @@
[clojure.spec.alpha :as s]))
;; :layout ;; :flex, :grid in the future
;; :layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column
;; :layout-flex-dir ;; :row, :row-reverse, :column, :column-reverse
;; :layout-gap-type ;; :simple, :multiple
;; :layout-gap ;; {:row-gap number , :column-gap number}
;; :layout-align-items ;; :start :end :center :stretch
;; :layout-justify-content ;; :start :center :end :space-between :space-around
;; :layout-align-content ;; :start :center :end :space-between :space-around :stretch (by default)
;; :layout-wrap-type ;; :wrap, :no-wrap
;; :layout-wrap-type ;; :wrap, :nowrap
;; :layout-padding-type ;; :simple, :multiple
;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
@ -32,13 +32,13 @@
;; :layout-item-min-w ;; num
(s/def ::layout #{:flex :grid})
(s/def ::layout-flex-dir #{:row :reverse-row :column :reverse-column})
(s/def ::layout-flex-dir #{:row :reverse-row :row-reverse :column :reverse-column :column-reverse}) ;;TODO remove reverse-column and reverse-row after script
(s/def ::layout-gap-type #{:simple :multiple})
(s/def ::layout-gap ::us/safe-number)
(s/def ::layout-align-items #{:start :end :center :stretch})
(s/def ::layout-align-content #{:start :end :center :space-between :space-around :stretch})
(s/def ::layout-justify-content #{:start :center :end :space-between :space-around})
(s/def ::layout-wrap-type #{:wrap :no-wrap})
(s/def ::layout-wrap-type #{:wrap :nowrap :no-wrap}) ;;TODO remove no-wrap after script
(s/def ::layout-padding-type #{:simple :multiple})
(s/def ::p1 ::us/safe-number)
@ -148,11 +148,11 @@
(defn col?
[{:keys [layout-flex-dir]}]
(or (= :column layout-flex-dir) (= :reverse-column layout-flex-dir)))
(or (= :column layout-flex-dir) (= :column-reverse layout-flex-dir)))
(defn row?
[{:keys [layout-flex-dir]}]
(or (= :row layout-flex-dir) (= :reverse-row layout-flex-dir)))
(or (= :row layout-flex-dir) (= :row-reverse layout-flex-dir)))
(defn gaps
[{:keys [layout-gap]}]
@ -278,8 +278,8 @@
(defn reverse?
[{:keys [layout-flex-dir]}]
(or (= :reverse-row layout-flex-dir)
(= :reverse-column layout-flex-dir)))
(or (= :row-reverse layout-flex-dir)
(= :column-reverse layout-flex-dir)))
(defn space-between?
[{:keys [layout-justify-content]}]

View file

@ -155,21 +155,19 @@
[base index-base-a index-base-b]))
(defn is-shape-over-shape?
[objects base-shape-id over-shape-id {:keys [top-frames?]}]
[objects base-shape-id over-shape-id]
(let [[base index-a index-b] (get-base objects base-shape-id over-shape-id)]
(cond
(= base base-shape-id)
(and (not top-frames?)
(let [object (get objects base-shape-id)]
(or (cph/frame-shape? object)
(cph/root-frame? object))))
(let [object (get objects base-shape-id)]
(or (cph/frame-shape? object)
(cph/root-frame? object)))
(= base over-shape-id)
(or top-frames?
(let [object (get objects over-shape-id)]
(or (not (cph/frame-shape? object))
(not (cph/root-frame? object)))))
(let [object (get objects over-shape-id)]
(or (not (cph/frame-shape? object))
(not (cph/root-frame? object))))
:else
(< index-a index-b))))
@ -183,20 +181,20 @@
(let [type-a (dm/get-in objects [id-a :type])
type-b (dm/get-in objects [id-b :type])]
(cond
(and (= :frame type-a) (not= :frame type-b))
(if bottom-frames? 1 -1)
(and (not= :frame type-a) (= :frame type-b))
(if bottom-frames? -1 1)
(and (= :frame type-a) (not= :frame type-b))
(if bottom-frames? 1 -1)
(= id-a id-b)
0
(is-shape-over-shape? objects id-a id-b options)
1
(is-shape-over-shape? objects id-b id-a)
-1
:else
-1)))]
1)))]
(sort comp ids))))
(defn frame-id-by-position
@ -268,7 +266,7 @@
(if all-frames?
identity
(remove :hide-in-viewer)))
(sort-z-index objects (get-frames-ids objects) {:top-frames? true}))))
(sort-z-index objects (get-frames-ids objects)))))
(defn start-page-index
[objects]

View file

@ -70,3 +70,14 @@
remap-typography
content)))))
(defn remove-external-typographies
"Change the shape so that any use of an external typography now is removed"
[shape file-id]
(let [remove-ref-file #(dissoc % :typography-ref-file :typography-ref-id)]
(update shape :content
(fn [content]
(txt/transform-nodes #(not= (:typography-ref-file %) file-id)
remove-ref-file
content)))))

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 401 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

View file

@ -129,6 +129,7 @@ $width-settings-bar: 256px;
overflow: hidden;
flex-direction: column;
justify-content: flex-start;
position: relative;
}
.inspect-svg-container {

View file

@ -23,9 +23,9 @@
flex-direction: column;
align-items: center;
justify-content: flex-start;
background-color: #feecfd;
background-image: url("/images/login-pink.svg");
background-position: center;
background-color: #151035;
background-image: url("/images/login-penpot.svg");
background-position: center 30vh;
background-size: 96%;
background-repeat: no-repeat;
@ -34,12 +34,12 @@
width: 280px;
font-size: $fs18;
margin-top: 2vh;
color: #2c233e;
color: white;
}
.logo {
svg {
fill: #2c233e;
fill: white;
max-width: 11vw;
height: 80px;
}

View file

@ -132,6 +132,7 @@
.options {
display: flex;
justify-content: flex-end;
min-width: 180px;
.icon {
width: $size-5;
@ -140,6 +141,12 @@
margin-left: 10px;
justify-content: center;
align-items: center;
&.failure {
margin-right: 10px;
svg {
fill: $color-warning;
}
}
svg {
width: 16px;
height: 16px;
@ -171,7 +178,6 @@
.dashboard-fonts-hero {
font-size: $fs14;
padding: $size-6;
background-color: $color-white;
margin-top: $size-6;
@ -179,17 +185,29 @@
justify-content: space-between;
.banner {
background-color: unset;
display: flex;
background-color: $color-info-lighter;
display: grid;
grid-template-columns: 40px 1fr;
&:not(:last-child) {
margin-bottom: 10px;
}
.icon {
display: flex;
align-items: center;
padding-left: 0px;
padding-right: 10px;
align-items: start;
justify-content: center;
padding-top: 10px;
background-color: $color-info;
svg {
fill: $color-info;
fill: $color-white;
}
}
.content {
margin: 10px;
}
&.warning {
background-color: $color-warning-lighter;
.icon {
background-color: $color-warning;
}
}
}

View file

@ -314,11 +314,15 @@
}
}
.attributes-shadow-block {
.attributes-shadow-block,
.attributes-stroke-block,
.attributes-fill-block {
border-top: 1px solid $color-gray-60;
}
.attributes-shadow-blocks :first-child {
.attributes-shadow-blocks :first-child,
.attributes-stroke-blocks :first-child,
.attributes-fill-blocks :first-child {
border-top: none;
}
}

View file

@ -693,9 +693,7 @@
.section-list-item {
padding: $size-4 0;
border-bottom: 1px solid $color-gray-20;
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
.item-name {
color: $color-gray-60;
@ -720,6 +718,8 @@
color: $color-black;
padding: $size-2;
margin-bottom: 0;
position: absolute;
top: 1rem;
&:hover {
color: $color-primary;
@ -1195,9 +1195,10 @@
}
&.right {
left: 731px;
top: 100px;
left: auto;
color: $color-primary;
top: 100px;
right: -40px;
}
&.square {

View file

@ -1707,13 +1707,13 @@
cursor: pointer;
border-right: 1px solid $color-gray-60;
padding: 2px;
&.reverse-row {
&.row-reverse {
svg {
transform: rotate(180deg);
}
}
&.reverse-column {
&.column-reverse {
svg {
transform: rotate(-90deg);
}

View file

@ -85,7 +85,7 @@
}
& .viewer-go-next.right-bar {
right: 264px;
right: 0;
}
& .viewer-go-prev {
@ -97,7 +97,7 @@
}
& .viewer-go-prev.left-bar {
left: 256px;
left: 0;
}
& .viewer-bottom {
@ -111,7 +111,7 @@
z-index: 2;
&.left-bar {
width: calc(100% - 512px);
width: 100%;
}
.reset {

View file

@ -15,7 +15,7 @@
display: grid;
grid-template-areas: "left center right";
grid-template-columns: auto 1fr auto;
grid-template-columns: 1fr auto 1fr;
grid-template-rows: 100%;
padding: 0;
@ -32,6 +32,7 @@
.right-area {
grid-area: right;
display: flex;
justify-content: flex-end;
height: 100%;
}

View file

@ -273,7 +273,6 @@ $height-palette-max: 80px;
.frame-thumbnail-wrapper {
.fills,
.strokes,
.frame-clip-def {
opacity: 0;
}

View file

@ -84,16 +84,44 @@
map with temporal ID's associated to each font entry."
[blobs team-id]
(letfn [(prepare [{:keys [font type name data] :as params}]
(let [family (or (.getEnglishName ^js font "preferredFamily")
(.getEnglishName ^js font "fontFamily"))
variant (or (.getEnglishName ^js font "preferredSubfamily")
(.getEnglishName ^js font "fontSubfamily"))]
(let [family (or (.getEnglishName ^js font "preferredFamily")
(.getEnglishName ^js font "fontFamily"))
variant (or (.getEnglishName ^js font "preferredSubfamily")
(.getEnglishName ^js font "fontSubfamily"))
;; Vertical metrics determine the baseline in a text and the space between lines of text.
;; For historical reasons, there are three pairs of ascender/descender values, known as hhea, OS/2 and uSWin metrics.
;; Depending on the font, operating system and application a different set will be used to render text on the screen.
;; On Mac, Safari and Chrome use the hhea values to render text. Firefox will respect the useTypoMetrics setting and will use the OS/2 if it is set.
;; If the useTypoMetrics is not set, Firefox will also use metrics from the hhea table.
;; On Windows, all browsers use the usWin metrics, but respect the useTypoMetrics setting and if set will use the OS/2 values.
hhea-ascender (abs (-> font .-tables .-hhea .-ascender))
hhea-descender (abs (-> font .-tables .-hhea .-descender))
win-ascent (abs (-> font .-tables .-os2 .-usWinAscent))
win-descent (abs (-> font .-tables .-os2 .-usWinDescent))
os2-ascent (abs (-> font .-tables .-os2 .-sTypoAscender))
os2-descent (abs (-> font .-tables .-os2 .-sTypoDescender))
;; useTypoMetrics can be read from the 7th bit
f-selection (-> (-> font .-tables .-os2 .-fsSelection)
(bit-test 7))
height-warning? (or (not= hhea-ascender win-ascent)
(not= hhea-descender win-descent)
(and f-selection (or
(not= hhea-ascender os2-ascent)
(not= hhea-descender os2-descent))))]
{:content {:data (js/Uint8Array. data)
:name name
:type type}
:font-family (or family "")
:font-weight (cm/parse-font-weight variant)
:font-style (cm/parse-font-style variant)}))
:font-style (cm/parse-font-style variant)
:height-warning? height-warning?}))
(join [res {:keys [content] :as font}]
(let [key-fn (juxt :font-family :font-weight :font-style)

View file

@ -89,6 +89,10 @@
[key]
(-> key meta alt))
(defn alt-shift
[key]
(-> key alt shift))
(defn supr
[]
(if (cf/check-platform? :macos)
@ -133,17 +137,23 @@
[key cb]
(fn [event]
(log/debug :msg (str "Shortcut" key))
(.preventDefault event)
(when (aget event "preventDefault")
(.preventDefault event))
(cb event)))
(defn- bind!
[shortcuts]
(let [msbind (fn [command callback type]
(if type
(mousetrap/bind command callback type)
(mousetrap/bind command callback)))]
(->> shortcuts
(remove #(:disabled (second %)))
(run! (fn [[key {:keys [command fn type]}]]
(if (vector? command)
(run! #(mousetrap/bind % (wrap-cb key fn) type) command)
(mousetrap/bind command (wrap-cb key fn) type))))))
(let [callback (wrap-cb key fn)]
(if (vector? command)
(run! #(msbind % callback type) command)
(msbind command callback type))))))))
(defn- reset!
([]

View file

@ -27,6 +27,7 @@
[app.common.types.shape :as cts]
[app.common.types.shape-tree :as ctst]
[app.common.types.shape.layout :as ctl]
[app.common.types.typography :as ctt]
[app.common.uuid :as uuid]
[app.config :as cf]
[app.main.data.comments :as dcm]
@ -609,6 +610,7 @@
objects (wsh/lookup-page-objects state page-id)
selected-ids (wsh/lookup-selected state)
selected-shapes (map (d/getf objects) selected-ids)
undo-id (js/Symbol)
move-shape
(fn [changes shape]
@ -631,7 +633,10 @@
(pcb/with-objects objects))
selected-shapes)]
(rx/of (dch/commit-changes changes))))))
(rx/of (dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(ptk/data-event :layout/update selected-ids)
(dwu/commit-undo-transaction undo-id))))))
;; --- Change Shape Order (D&D Ordering)
@ -835,13 +840,22 @@
(ptk/reify ::start-editing-selected
ptk/WatchEvent
(watch [_ state _]
(let [selected (wsh/lookup-selected state)]
(if-not (= 1 (count selected))
(rx/empty)
(let [selected (wsh/lookup-selected state)
objects (wsh/lookup-page-objects state)]
(let [objects (wsh/lookup-page-objects state)
{:keys [id type shapes]} (get objects (first selected))]
(if (> (count selected) 1)
(let [shapes-to-select
(->> selected
(reduce
(fn [result shape-id]
(let [children (dm/get-in objects [shape-id :shapes])]
(if (empty? children)
(conj result shape-id)
(into result children))))
(d/ordered-set)))]
(rx/of (dws/select-shapes shapes-to-select)))
(let [{:keys [id type shapes]} (get objects (first selected))]
(case type
:text
(rx/of (dwe/start-edition-mode id))
@ -895,9 +909,13 @@
(align-objects-list objects selected axis))
moved-objects (->> moved (group-by :id))
ids (keys moved-objects)
update-fn (fn [shape] (first (get moved-objects (:id shape))))]
update-fn (fn [shape] (first (get moved-objects (:id shape))))
undo-id (js/Symbol)]
(when (can-align? selected objects)
(rx/of (dch/update-shapes ids update-fn {:reg-objects? true})))))))
(rx/of (dwu/start-undo-transaction undo-id)
(dch/update-shapes ids update-fn {:reg-objects? true})
(ptk/data-event :layout/update ids)
(dwu/commit-undo-transaction undo-id)))))))
(defn align-object-to-parent
[objects object-id axis]
@ -1307,16 +1325,22 @@
:file-id (:current-file-id state)
:selected selected
:objects {}
:images #{}}]
:images #{}}
selected_text (.. js/window getSelection toString)]
(->> (rx/from (seq (vals pdata)))
(rx/merge-map (partial prepare-object objects selected+children))
(rx/reduce collect-data initial)
(rx/map (partial sort-selected state))
(rx/map t/encode-str)
(rx/map wapi/write-to-clipboard)
(rx/catch on-copy-error)
(rx/ignore)))))))
(if (not-empty selected_text)
(try
(wapi/write-to-clipboard selected_text)
(catch :default e
(on-copy-error e)))
(->> (rx/from (seq (vals pdata)))
(rx/merge-map (partial prepare-object objects selected+children))
(rx/reduce collect-data initial)
(rx/map (partial sort-selected state))
(rx/map t/encode-str)
(rx/map wapi/write-to-clipboard)
(rx/catch on-copy-error)
(rx/ignore))))))))
(declare paste-shape)
(declare paste-text)
@ -1527,7 +1551,8 @@
;; Proceed with the standard shape paste process.
(do-paste [it state mouse-pos media]
(let [page (wsh/lookup-page state)
(let [file-id (:current-file-id state)
page (wsh/lookup-page state)
media-idx (d/index-by :prev-id media)
;; Calculate position for the pasted elements
@ -1542,7 +1567,10 @@
;; if foreign instance, detach the shape
(cond-> (foreign-instance? shape paste-objects state)
(dissoc :component-id :component-file :component-root?
:remote-synced? :shape-ref :touched))))
:remote-synced? :shape-ref :touched))
;; if is a text, remove references to external typographies
(cond-> (= (:type shape) :text)
(ctt/remove-external-typographies file-id))))
paste-objects (->> paste-objects (d/mapm process-shape))

View file

@ -9,6 +9,7 @@
[app.common.logging :as log]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.undo :as dwu]
[app.util.router :as rt]
[beicon.core :as rx]
[potok.core :as ptk]))
@ -26,6 +27,25 @@
(defn interrupt? [e] (= e :interrupt))
(defn- assure-valid-current-page
[]
(ptk/reify ::assure-valid-current-page
ptk/WatchEvent
(watch [_ state _]
(let [current_page (:current-page-id state)
pages (get-in state [:workspace-data :pages])
exists? (some #(= current_page %) pages)
project-id (:current-project-id state)
file-id (:current-file-id state)
pparams {:file-id file-id :project-id project-id}
qparams {:page-id (first pages)}]
(if exists?
(rx/empty)
(rx/of (rt/nav :workspace pparams qparams)))))))
;; These functions should've been in `src/app/main/data/workspace/undo.cljs` but doing that causes
;; a circular dependency with `src/app/main/data/workspace/changes.cljs`
(def undo
@ -45,7 +65,8 @@
(dch/commit-changes {:redo-changes changes
:undo-changes []
:save-undo? false
:origin it}))))))))))
:origin it})
(assure-valid-current-page))))))))))
(def redo
(ptk/reify ::redo

View file

@ -79,15 +79,22 @@
(gpt/point (- (gsh/left-bound transformed-shape) (gsh/left-bound transformed-root))
(- (gsh/top-bound transformed-shape) (gsh/top-bound transformed-root))))
;; There are cases in that the coordinates change slightly (e.g. when
;; rounding to pixel, or when recalculating text positions in different
;; zoom levels). To take this into account, we ignore movements smaller
;; than 1 pixel.
distance (if (and shape-delta transformed-shape-delta)
(gpt/distance-vector shape-delta transformed-shape-delta)
(gpt/point 0 0))
ignore-geometry? (and (< (:x distance) 1) (< (:y distance) 1))]
selrect (:selrect shape)
transformed-selrect (:selrect transformed-shape)
;; There are cases in that the coordinates change slightly (e.g. when rounding
;; to pixel, or when recalculating text positions in different zoom levels).
;; To take this into account, we ignore movements smaller than 1 pixel.
;;
;; When the change is a resize, also has a transformation that may have the
;; shape position unchanged. But in this case we do not want to ignore it.
ignore-geometry? (and (and (< (:x distance) 1) (< (:y distance) 1))
(mth/close? (:width selrect) (:width transformed-selrect))
(mth/close? (:height selrect) (:height transformed-selrect)))]
[root transformed-root ignore-geometry?]))
@ -157,6 +164,15 @@
(us/verify (s/coll-of uuid?) ids)
(into {} (map #(vector % {:modifiers (get-modifier (get objects %))})) ids))
(defn modifier-remove-from-parent
[modif-tree objects shapes]
(->> shapes
(reduce
(fn [modif-tree child-id]
(let [parent-id (get-in objects [child-id :parent-id])]
(update-in modif-tree [parent-id :modifiers] ctm/remove-children [child-id])))
modif-tree)))
(defn build-change-frame-modifiers
[modif-tree objects selected target-frame drop-index]
@ -186,7 +202,7 @@
(filterv #(contains? child-set %)))]
(cond-> modif-tree
(not= original-frame target-frame)
(-> (update-in [original-frame :modifiers] ctm/remove-children shapes)
(-> (modifier-remove-from-parent objects shapes)
(update-in [target-frame :modifiers] ctm/add-children shapes drop-index)
(set-parent-ids shapes target-frame))

View file

@ -281,8 +281,6 @@
;; --- Duplicate Shapes
(declare prepare-duplicate-change)
(declare prepare-duplicate-frame-change)
(declare prepare-duplicate-shape-change)
(declare prepare-duplicate-flows)
(declare prepare-duplicate-guides)
@ -304,91 +302,59 @@
changes
(->> shapes
(reduce #(prepare-duplicate-change %1
all-objects
page
unames
update-unames!
ids-map
%2
delta)
(reduce #(prepare-duplicate-shape-change %1
all-objects
page
unames
update-unames!
ids-map
%2
delta)
init-changes))]
(-> changes
(prepare-duplicate-flows shapes page ids-map)
(prepare-duplicate-guides shapes page ids-map delta))))
(defn- prepare-duplicate-change
[changes objects page unames update-unames! ids-map shape delta]
(if (cph/frame-shape? shape)
(prepare-duplicate-frame-change changes objects page unames update-unames! ids-map shape delta)
(prepare-duplicate-shape-change changes objects page unames update-unames! ids-map shape delta (:frame-id shape) (:parent-id shape))))
(defn- prepare-duplicate-frame-change
[changes objects page unames update-unames! ids-map obj delta]
(let [new-id (ids-map (:id obj))
frame-name (:name obj)
new-frame (-> obj
(assoc :id new-id
:name frame-name
:shapes [])
(dissoc :use-for-thumbnail?)
(gsh/move delta)
(d/update-when :interactions #(ctsi/remap-interactions % ids-map objects)))
changes (-> (pcb/add-object changes new-frame)
(pcb/amend-last-change #(assoc % :old-id (:id obj))))
changes (reduce (fn [changes child]
(prepare-duplicate-shape-change changes
objects
page
unames
update-unames!
ids-map
child
delta
new-id
new-id))
changes
(map (d/getf objects) (:shapes obj)))]
changes))
(defn- prepare-duplicate-shape-change
[changes objects page unames update-unames! ids-map obj delta frame-id parent-id]
(if (some? obj)
(let [new-id (ids-map (:id obj))
parent-id (or parent-id frame-id)
name (:name obj)
([changes objects page unames update-unames! ids-map obj delta]
(prepare-duplicate-shape-change changes objects page unames update-unames! ids-map obj delta (:frame-id obj) (:parent-id obj)))
new-obj (-> obj
(assoc :id new-id
:name name
:parent-id parent-id
:frame-id frame-id)
(dissoc :shapes
:main-instance?)
(gsh/move delta)
(d/update-when :interactions #(ctsi/remap-interactions % ids-map objects)))
([changes objects page unames update-unames! ids-map obj delta frame-id parent-id]
(if (some? obj)
(let [frame? (cph/frame-shape? obj)
new-id (ids-map (:id obj))
parent-id (or parent-id frame-id)
name (:name obj)
changes (-> (pcb/add-object changes new-obj {:ignore-touched true})
(pcb/amend-last-change #(assoc % :old-id (:id obj))))]
new-obj (-> obj
(assoc :id new-id
:name name
:parent-id parent-id
:frame-id frame-id)
(dissoc :shapes
:main-instance?
:use-for-thumbnail?)
(gsh/move delta)
(d/update-when :interactions #(ctsi/remap-interactions % ids-map objects)))
(reduce (fn [changes child]
(prepare-duplicate-shape-change changes
objects
page
unames
update-unames!
ids-map
child
delta
frame-id
new-id))
changes
(map (d/getf objects) (:shapes obj))))
changes))
changes (-> (pcb/add-object changes new-obj {:ignore-touched true})
(pcb/amend-last-change #(assoc % :old-id (:id obj))))]
(reduce (fn [changes child]
(prepare-duplicate-shape-change changes
objects
page
unames
update-unames!
ids-map
child
delta
(if frame? new-id frame-id)
new-id))
changes
(map (d/getf objects) (:shapes obj))))
changes)))
(defn- prepare-duplicate-flows
[changes shapes page ids-map]

View file

@ -48,7 +48,7 @@
:layout-align-items :start
:layout-justify-content :start
:layout-align-content :stretch
:layout-wrap-type :no-wrap
:layout-wrap-type :nowrap
:layout-padding-type :simple
:layout-padding {:p1 0 :p2 0 :p3 0 :p4 0}})
@ -136,8 +136,8 @@
direction
(cond
(mth/close? tmin t1) :row
(mth/close? tmin t2) :reverse-column
(mth/close? tmin t3) :reverse-row
(mth/close? tmin t2) :column-reverse
(mth/close? tmin t3) :row-reverse
(mth/close? tmin t4) :column)]
{:layout-flex-dir direction}))

View file

@ -17,6 +17,7 @@
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dws]
[app.main.data.workspace.text.shortcuts :as dwtxts]
[app.main.data.workspace.texts :as dwtxt]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.undo :as dwu]
@ -107,6 +108,7 @@
:subsections [:edit]
:fn #(st/emit! :interrupt (dw/deselect-all true))}
;; MODIFY LAYERS
@ -245,7 +247,7 @@
:command "t"
:subsections [:tools]
:fn #(emit-when-no-readonly dwtxt/start-edit-if-selected
(dwd/select-for-drawing :text))}
(dwd/select-for-drawing :text))}
:draw-path {:tooltip "P"
:command "p"
@ -419,14 +421,14 @@
:subsections [:panels]
:fn #(do (r/set-resize-type! :bottom)
(emit-when-no-readonly (dw/remove-layout-flag :textpalette)
(toggle-layout-flag :colorpalette)))}
(toggle-layout-flag :colorpalette)))}
:toggle-textpalette {:tooltip (ds/alt "T")
:command (ds/a-mod "t")
:subsections [:panels]
:fn #(do (r/set-resize-type! :bottom)
(emit-when-no-readonly (dw/remove-layout-flag :colorpalette)
(toggle-layout-flag :textpalette)))}
(toggle-layout-flag :textpalette)))}
:hide-ui {:tooltip "\\"
:command "\\"
@ -460,6 +462,16 @@
:subsections [:zoom-workspace]
:fn #(st/emit! dw/zoom-to-selected-shape)}
:zoom-lense-increase {:tooltip "Z"
:command "z"
:subsections [:zoom-workspace]
:fn identity}
:zoom-lense-decrease {:tooltip (ds/alt "Z")
:command "alt+z"
:subsections [:zoom-workspace]
:fn identity}
;; NAVIGATION
@ -516,7 +528,7 @@
:fn #(emit-when-no-readonly (dwly/pressed-opacity n))}])))))
(def shortcuts
(merge base-shortcuts opacity-shortcuts))
(merge base-shortcuts opacity-shortcuts dwtxts/shortcuts))
(defn get-tooltip [shortcut]
(assert (contains? shortcuts shortcut) (str shortcut))

View file

@ -0,0 +1,273 @@
;; 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.data.workspace.text.shortcuts
(:require
[app.common.data :as d]
[app.main.data.shortcuts :as ds]
[app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.main.fonts :as fonts]
[app.main.refs :as refs]
[app.main.store :as st]
[cuerdas.core :as str]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts format https://github.com/ccampbell/mousetrap
(defn- is-bold? [variant-id]
(some #(str/includes? variant-id %) ["bold" "black" "700"]))
(defn- is-italic? [variant-id]
(some #(str/includes? variant-id %) ["italic" "cursive"]))
(defn- generate-variant-props
[text-values variant-id]
(let [first-intersection (fn [list1 list2] (first (filter (set list1) list2)))
current-variant (:font-variant-id text-values)
bold-options (cond
(str/includes? current-variant "black")
["black" "bold" "700"]
(str/includes? current-variant "700")
["700" "black" "bold"]
:else
["bold" "black" "700"])
current-variant-no-italic (cond
(str/includes? current-variant "italic")
(subs current-variant 0 (- (count current-variant) 6))
(str/includes? current-variant "cursive")
(subs current-variant 0 (- (count current-variant) 7))
:else nil)
regular-options [current-variant-no-italic "regular" "normal" "400"]
italic-options [(when (and (not (str/includes? current-variant "bold"))
(not (str/includes? current-variant "black"))
(not (str/includes? current-variant "700")))
(str current-variant "italic"))
"italic" "cursive"]
bold-italic-options (cond
(str/includes? current-variant "black")
["blackitalic" "blackcursive" "bolditalic" "700italic" "boldcursive" "700cursive"]
(str/includes? current-variant "700")
["700italic" "700cursive" "bolditalic" "blackitalic" "boldcursive" "blackcursive"]
:else
["bolditalic" "700italic" "blackitalic" "boldcursive" "700cursive" "blackcursive"])
font-id (:font-id text-values)
fonts (deref fonts/fontsdb)
font (get fonts font-id)
variants (map :id (:variants font))
choose-regular (fn [] (first-intersection variants regular-options))
choose-bold (fn [] (first-intersection variants bold-options))
choose-italic (fn [] (first-intersection variants italic-options))
choose-bold-italic (fn [] (or (first-intersection variants bold-italic-options) (choose-bold)))
choose-italic-bold (fn [] (or (first-intersection variants bold-italic-options) (choose-italic)))
new-variant (let [bold? (is-bold? current-variant)
italic? (is-italic? current-variant)
add-bold? (and (not bold?)
(or (= variant-id "add-bold")
(= variant-id "toggle-bold")))
remove-bold? (and bold?
(or (= variant-id "remove-bold")
(= variant-id "toggle-bold")))
add-italic? (and (not italic?)
(or (= variant-id "add-italic")
(= variant-id "toggle-italic")))
remove-italic? (and italic?
(or (= variant-id "remove-italic")
(= variant-id "toggle-italic")))]
(cond
(and add-bold? italic?) ;; it is italic, set it to bold+italic
(choose-bold-italic)
(and add-bold? (not italic?)) ;; it is regular, set it to bold
(choose-bold)
(and remove-bold? italic?) ;; it is bold+italic, set it to italic
(choose-italic)
(and remove-bold? (not italic?)) ;; it is bold set it to regular
(choose-regular)
(and add-italic? bold?) ;; it is bold, set it to italic+bold
(choose-italic-bold)
(and add-italic? (not bold?)) ;; it is regular, set it to italic
(choose-italic)
(and remove-italic? bold?) ;; it is bold+italic, set it to bold
(choose-bold)
(and remove-italic? (not bold?)) ;; it is italic, set it to regular
(choose-regular)))
new-weight (when new-variant
(->> (:variants font)
(filter #(= (:id %) new-variant))
first
:weight))]
(when new-variant
{:font-variant-id new-variant,
:font-weight new-weight})))
(defn calculate-text-values
[shape]
(let [state-map (deref refs/workspace-editor-state)
editor-state (get state-map (:id shape))]
(d/merge
(dwt/current-root-values
{:shape shape
:attrs dwt/root-attrs})
(dwt/current-paragraph-values
{:editor-state editor-state
:shape shape
:attrs dwt/paragraph-attrs})
(dwt/current-text-values
{:editor-state editor-state
:shape shape
:attrs dwt/text-attrs}))))
(defn- update-attrs [shape props]
(let [
text-values (calculate-text-values shape)
font-size (d/parse-double (:font-size text-values))
line-height (d/parse-double (:line-height text-values))
letter-spacing (d/parse-double (:letter-spacing text-values))
props (cond
(:font-size-inc props)
{:font-size (str (inc font-size))}
(:font-size-dec props)
{:font-size (str (dec font-size))}
(:line-height-inc props)
{:line-height (str (+ line-height 0.1))}
(:line-height-dec props)
{:line-height (str (- line-height 0.1))}
(:letter-spacing-inc props)
{:letter-spacing (str (+ letter-spacing 0.1))}
(:letter-spacing-dec props)
{:letter-spacing (str (- letter-spacing 0.1))}
(= (:text-decoration props) "toggle-underline") ;;toggle
(if (= (:text-decoration text-values) "underline")
{:text-decoration "none"}
{:text-decoration "underline"})
(= (:text-decoration props) "toggle-line-through") ;;toggle
(if (= (:text-decoration text-values) "line-through")
{:text-decoration "none"}
{:text-decoration "line-through"})
(:font-variant-id props)
(generate-variant-props text-values (:font-variant-id props))
:else props)]
(when (and shape props)
(st/emit! (dwt/update-attrs (:id shape) props)))))
(defn blend-props
[shapes props]
(let [text-values (map calculate-text-values shapes)
all-underline? (every? #(= (:text-decoration %) "underline") text-values)
all-line-through? (every? #(= (:text-decoration %) "line-through") text-values)
all-bold? (every? #(is-bold? (:font-variant-id %)) text-values)
all-italic? (every? #(is-italic? (:font-variant-id %)) text-values)
]
(cond
(= (:text-decoration props) "toggle-underline")
(if all-underline?
{:text-decoration "none"}
{:text-decoration "underline"})
(= (:text-decoration props) "toggle-line-through")
(if all-line-through?
{:text-decoration "none"}
{:text-decoration "line-through"})
(= (:font-variant-id props) "toggle-bold")
(if all-bold?
{:font-variant-id "remove-bold"}
{:font-variant-id "add-bold"})
(= (:font-variant-id props) "toggle-italic")
(if all-italic?
{:font-variant-id "remove-italic"}
{:font-variant-id "add-italic"})
:else
props)))
(defn- update-attrs-when-no-readonly [props]
(let [undo-id (js/Symbol)
read-only? (deref refs/workspace-read-only?)
shapes-with-children (deref refs/selected-shapes-with-children)
text-shapes (filter #(= (:type %) :text) shapes-with-children)
props (if (> (count text-shapes) 1)
(blend-props text-shapes props)
props)]
(when (and (not read-only?) text-shapes)
(st/emit! (dwu/start-undo-transaction undo-id))
(run! #(update-attrs % props) text-shapes)
(st/emit! (dwu/commit-undo-transaction undo-id)))))
(def shortcuts
{:align-left {:tooltip (ds/meta (ds/alt "l"))
:command (ds/c-mod "alt+l")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:text-align "left"})}
:align-right {:tooltip (ds/meta (ds/alt "r"))
:command (ds/c-mod "alt+r")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:text-align "right"})}
:align-center {:tooltip (ds/meta (ds/alt "t"))
:command (ds/c-mod "alt+t")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:text-align "center"})}
:align-justify {:tooltip (ds/meta (ds/alt "j"))
:command (ds/c-mod "alt+j")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:text-align "justify"})}
:underline {:tooltip (ds/meta "u")
:command (ds/c-mod "u")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:text-decoration "toggle-underline"})}
:line-through {:tooltip (ds/alt (ds/meta-shift "5"))
:command "alt+shift+5"
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:text-decoration "toggle-line-through"})}
:font-size-inc {:tooltip (ds/meta-shift ds/up-arrow)
:command (ds/c-mod "shift+up")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:font-size-inc true})}
:font-size-dec {:tooltip (ds/meta-shift ds/down-arrow)
:command (ds/c-mod "shift+down")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:font-size-dec true})}
:line-height-inc {:tooltip (ds/alt-shift ds/up-arrow)
:command (ds/a-mod "shift+up")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:line-height-inc true})}
:line-height-dec {:tooltip (ds/alt-shift ds/down-arrow)
:command (ds/a-mod "shift+down")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:line-height-dec true})}
:letter-spacing-inc {:tooltip (ds/alt ds/up-arrow)
:command (ds/a-mod "up")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:letter-spacing-inc true})}
:letter-spacing-dec {:tooltip (ds/alt ds/down-arrow)
:command (ds/a-mod "down")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:letter-spacing-dec true})}
:bold {:tooltip (ds/meta "b")
:command (ds/c-mod "b")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:font-variant-id "toggle-bold"})}
:italic {:tooltip (ds/meta "i")
:command (ds/c-mod "i")
:subsections [:text-editor]
:fn #(update-attrs-when-no-readonly {:font-variant-id "toggle-italic"})}})

View file

@ -28,6 +28,68 @@
[beicon.core :as rx]
[potok.core :as ptk]))
;; -- Attrs
(def text-typography-attrs
[:typography-ref-id
:typography-ref-file])
(def text-fill-attrs
[:fill-color
:fill-opacity
:fill-color-ref-id
:fill-color-ref-file
:fill-color-gradient])
(def text-font-attrs
[:font-id
:font-family
:font-variant-id
:font-size
:font-weight
:font-style])
(def text-align-attrs
[:text-align])
(def text-direction-attrs
[:text-direction])
(def text-spacing-attrs
[:line-height
:letter-spacing])
(def text-valign-attrs
[:vertical-align])
(def text-decoration-attrs
[:text-decoration])
(def text-transform-attrs
[:text-transform])
(def shape-attrs
[:grow-type])
(def root-attrs text-valign-attrs)
(def paragraph-attrs
(d/concat-vec
text-align-attrs
text-direction-attrs))
(def text-attrs
(d/concat-vec
text-typography-attrs
text-font-attrs
text-spacing-attrs
text-decoration-attrs
text-transform-attrs))
(def attrs (d/concat-set shape-attrs root-attrs paragraph-attrs text-attrs))
;; -- Editor
(defn update-editor
[editor]
(ptk/reify ::update-editor
@ -182,8 +244,8 @@
(defn- update-text-content
[shape pred-fn update-fn attrs]
(let [update-attrs #(update-fn % attrs)
transform #(txt/transform-nodes pred-fn update-attrs %)]
(let [update-attrs-fn #(update-fn % attrs)
transform #(txt/transform-nodes pred-fn update-attrs-fn %)]
(-> shape
(update :content transform))))
@ -525,3 +587,24 @@
(rx/take-until stopper))
(rx/of (update-position-data id position-data))))
(rx/empty))))))
(defn update-attrs
[id attrs]
(ptk/reify ::update-attrs
ptk/WatchEvent
(watch [_ _ _]
(rx/concat
(let [attrs (select-keys attrs root-attrs)]
(if-not (empty? attrs)
(rx/of (update-root-attrs {:id id :attrs attrs}))
(rx/empty)))
(let [attrs (select-keys attrs paragraph-attrs)]
(if-not (empty? attrs)
(rx/of (update-paragraph-attrs {:id id :attrs attrs}))
(rx/empty)))
(let [attrs (select-keys attrs text-attrs)]
(if-not (empty? attrs)
(rx/of (update-text-attrs {:id id :attrs attrs}))
(rx/empty)))))))

View file

@ -15,6 +15,7 @@
[app.main.repo :as rp]
[app.main.store :as st]
[app.util.dom :as dom]
[app.util.timers :as ts]
[app.util.webapi :as wapi]
[beicon.core :as rx]
[potok.core :as ptk]))
@ -31,7 +32,6 @@
(defn thumbnail-canvas-blob-stream
[object-id]
;; Look for the thumbnail canvas to send the data to the backend
(let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%'][data-ready='true']" object-id))
stopper (->> st/stream
(rx/filter (ptk/type? :app.main.data.workspace/finalize-page))
@ -40,10 +40,11 @@
;; Success: we generate the blob (async call)
(rx/create
(fn [subs]
(.toBlob node (fn [blob]
(rx/push! subs blob)
(rx/end! subs))
"image/png")))
(ts/raf
#(.toBlob node (fn [blob]
(rx/push! subs blob)
(rx/end! subs))
"image/png"))))
;; Not found, we retry after delay
(->> (rx/timer 250)
@ -94,7 +95,9 @@
(rx/catch #(rx/empty))
(rx/ignore))))
(rx/empty)))))))))))
(rx/empty))))
(rx/catch #(do (.error js/console %)
(rx/empty))))))))))
(defn- extract-frame-changes
"Process a changes set in a commit to extract the frames that are changing"

View file

@ -283,7 +283,7 @@
(let [variant (d/seek #(= (:id %) font-variant-id) variants)]
(-> (generate-gfonts-url
{:family family
:variants [{:id variant}]})
:variants [variant]})
(http/fetch-text)))
(= :custom backend)

View file

@ -194,3 +194,14 @@
(rx/dedupe))]
(rx/subscribe-with ob sub)
sub))
(defonce keyboard-z
(let [sub (rx/behavior-subject nil)
ob (->> st/stream
(rx/filter keyboard-event?)
(rx/filter kbd/z?)
(rx/filter (comp not kbd/editing?))
(rx/map #(= :down (:type %)))
(rx/dedupe))]
(rx/subscribe-with ob sub)
sub))

View file

@ -120,7 +120,9 @@
on-dismiss-all
(fn [items]
(run! on-delete items))]
(run! on-delete items))
problematic-fonts? (some :height-warning? (vals @fonts))]
[:div.dashboard-fonts-upload
[:div.dashboard-fonts-hero
@ -132,7 +134,14 @@
[:div.icon i/msg-info]
[:div.content
[:& i18n/tr-html {:tag-name "span"
:label "dashboard.fonts.hero-text2"}]]]]
:label "dashboard.fonts.hero-text2"}]]]
(when problematic-fonts?
[:div.banner.warning
[:div.icon i/msg-warning]
[:div.content
[:& i18n/tr-html {:tag-name "span"
:label "dashboard.fonts.warning-text"}]]])]
[:button.btn-primary
{:on-click on-click
@ -171,6 +180,8 @@
[:span item])]
[:div.table-field.options
(when (:height-warning? item)
[:span.icon.failure i/msg-warning])
[:button.btn-primary.upload-button
{:on-click #(on-upload item)
:class (dom/classnames :disabled uploading?)

View file

@ -460,7 +460,8 @@
(mf/use-fn
(fn []
(st/emit! (msg/success (tr "notifications.invitation-email-sent"))
(modal/hide))))
(modal/hide)
(dd/fetch-team-invitations))))
on-copy-success
(mf/use-fn

View file

@ -55,7 +55,7 @@
{:p1 p1 :p2 p2 :p3 p3 :p4 p4}
(and (= p1 p3) (= p2 p4))
{:p1 p1 :p3 p3}
{:p1 p1 :p2 p2}
(and (not= p1 p3) (= p2 p4))
{:p1 p1 :p2 p2 :p3 p3}
@ -67,9 +67,11 @@
(let [sizing (if (= type :width)
(:layout-item-h-sizing shape)
(:layout-item-v-sizing shape))]
(if (= sizing :fill)
"100%"
(str (format-pixels value)))))
(cond
(= sizing :fill) "100%"
(= sizing :auto) "auto"
(number? value) (format-pixels value)
:else value)))
(defn format-padding
[padding-values type]

View file

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

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-17
(:require
[app.main.ui.releases.common :as c]
[rumext.v2 :as mf]))
(defmethod c/render-release-notes "1.17"
[{: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.17"}]]
[:div.modal-right
[:div.modal-title
[:h2 "What's new?"]]
[:span.release "Version " version]
[:div.modal-content
[:p "This is the first release in which Penpot is no longer Beta (hooray!) and it comes with very special features, starring the long awaited Flex Layout."]
[:p "On this 1.17 release, youll also be able to inspect the code and properties of your designs right from the workspace and to manage webhooks. Weve also implemented a lot of accessibility improvements."]]
[: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.17-flex-layout.gif" :border "0" :alt "Flex-Layout"}]]
[:div.modal-right
[:div.modal-title
[:h2 "Flex-Layout"]]
[:div.modal-content
[:p "The Flex Layout allows you to automatically adapt your designs. Resize, fit, and fill content and containers without the need to do it manually."]
[:p "Penpot brings a layout system like no other. As described by one of our beta testers: 'I love the fact that Penpot is following the CSS FlexBox, which is making UI Design a step closer to the logic and behavior behind how things will be actually built after design.'"]]
[: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.17-inspect.gif" :border "0" :alt "Inspect at the workspace"}]]
[:div.modal-right
[:div.modal-title
[:h2 "Inspect at the workspace"]]
[:div.modal-content
[:p "Now you can inspect designs to get measures, properties and production-ready code right at the workspace, so designers and developers can share the same space while working."]
[:p "Also, inspect mode provides a safer view-only mode and other improvements."]]
[: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.17-webhook.gif" :border "0" :alt "Webhooks"}]]
[:div.modal-right
[:div.modal-title
[:h2 "Webhooks"]]
[:div.modal-content
[:p "Webhooks allow other websites and apps to be notified when certain events happen at Penpot, ensuring to create integrations with other services."]
[:p "While we are still working on a plugin system, this is a great and simple way to create integrations with other services."]]
[: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.17-ally.gif" :border "0" :alt "Accessibility improvements"}]]
[:div.modal-right
[:div.modal-title
[:h2 "Accessibility improvements"]]
[:div.modal-content
[:p "We're working to ensure that people with visual or physical impairments can use Penpot in the same conditions."]
[:p "This release comes with improvements on color contrasts, alt texts, semantic labels, focusable items and keyboard navigation at login and dashboard, but more will come."]]
[:div.modal-navigation
[:button.btn-secondary {:on-click finish} "Start!"]
[:& c/navigation-bullets
{:slide @slide
:navigate navigate
:total 4}]]]]]])))

View file

@ -8,6 +8,7 @@
(:require
[app.common.data.macros :as dm]
[app.common.geom.shapes :as gsh]
[app.config :as cf]
[app.main.ui.context :as muc]
[app.main.ui.shapes.attrs :as attrs]
[app.main.ui.shapes.custom-stroke :refer [shape-fills shape-strokes]]
@ -90,15 +91,25 @@
bounds (or (obj/get props "bounds") (gsh/points->selrect (:points shape)))]
(when (:thumbnail shape)
[:image.frame-thumbnail
{:id (dm/str "thumbnail-" (:id shape))
:href (:thumbnail shape)
:x (:x bounds)
:y (:y bounds)
:width (:width bounds)
:height (:height bounds)
;; DEBUG
:style {:filter (when (debug? :thumbnails) "sepia(1)")}}])))
[:*
[:image.frame-thumbnail
{:id (dm/str "thumbnail-" (:id shape))
:href (:thumbnail shape)
:x (:x bounds)
:y (:y bounds)
:width (:width bounds)
:height (:height bounds)
;; DEBUG
:style {:filter (when (and (not (cf/check-browser? :safari))(debug? :thumbnails)) "sepia(1)")}}]
;; Safari don't support filters so instead we add a rectangle around the thumbnail
(when (and (cf/check-browser? :safari) (debug? :thumbnails))
[:rect {:x (+ (:x bounds) 4)
:y (+ (:y bounds) 4)
:width (- (:width bounds) 8)
:height (- (:height bounds) 8)
:stroke "red"
:stroke-width 2}])])))
(mf/defc frame-thumbnail
{::mf/wrap-props false}

View file

@ -8,7 +8,6 @@
(:require
[app.common.text :as txt]
[app.main.fonts :as fonts]
[app.main.ui.shapes.text.fo-text :as fo]
[app.main.ui.shapes.text.svg-text :as svg]
[app.util.object :as obj]
[rumext.v2 :as mf]))
@ -28,6 +27,5 @@
(mf/with-memo [content]
(load-fonts! content))
(if (some? position-data)
[:> svg/text-shape props]
[:> fo/text-shape props])))
(when (some? position-data)
[:> svg/text-shape props])))

View file

@ -84,9 +84,7 @@
:color (if show-text? text-color "transparent")
:caretColor (or text-color "black")
:overflowWrap "initial"
:lineBreak "auto"
:whiteSpace "break-spaces"
:textRendering "geometricPrecision"}
:lineBreak "auto"}
fills
(cond
(some? (:fills data))

View file

@ -318,7 +318,7 @@
mod? (kbd/mod? event)
shift? (kbd/shift? event)
delta (.-pixelY norm-event)
viewer-section (mf/ref-val viewer-section-ref)
viewer-section (.target event)
scroll-pos (if shift?
(dom/get-h-scroll-pos viewer-section)
(dom/get-scroll-pos viewer-section))

View file

@ -46,30 +46,14 @@
(.-deltaX ^js event))]
(if (pos? delta)
(st/emit! dv/decrease-zoom)
(st/emit! dv/increase-zoom))))
(when-not (kbd/mod? event)
(let [event (.getBrowserEvent ^js event)
shift? (kbd/shift? event)
inspect-svg-container (mf/ref-val inspect-svg-container-ref)
delta (+ (.-deltaY ^js event)
(.-deltaX ^js event))
scroll-pos (if shift?
(dom/get-h-scroll-pos inspect-svg-container)
(dom/get-scroll-pos inspect-svg-container))
new-scroll-pos (+ scroll-pos delta)]
(do
(dom/prevent-default event)
(dom/stop-propagation event)
(if shift?
(dom/set-h-scroll-pos! inspect-svg-container new-scroll-pos)
(dom/set-scroll-pos! inspect-svg-container new-scroll-pos))))))
(st/emit! dv/increase-zoom)))))
on-mount
(fn []
;; bind with passive=false to allow the event to be cancelled
;; https://stackoverflow.com/a/57582286/3219895
(let [key1 (events/listen goog/global EventType.WHEEL
on-mouse-wheel #js {"passive" false "capture" true})]
on-mouse-wheel #js {"passive" false})]
(fn []
(events/unlistenByKey key1))))]

View file

@ -27,7 +27,7 @@
:group [:layout :svg :layout-flex-item]
:rect [:layout :fill :stroke :shadow :blur :svg :layout-flex-item]
:circle [:layout :fill :stroke :shadow :blur :svg :layout-flex-item]
:path [:layout :fill :stroke :shadow :blur :svg]
:path [:layout :fill :stroke :shadow :blur :svg :layout-flex-item]
:image [:image :layout :fill :stroke :shadow :blur :svg :layout-flex-item]
:text [:layout :text :shadow :blur :stroke :layout-flex-item]})

View file

@ -6,7 +6,6 @@
(ns app.main.ui.viewer.inspect.attributes.fill
(:require
[app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.viewer.inspect.attributes.common :refer [color-row]]
[app.util.code-gen :as cg]
[app.util.color :as uc]
@ -40,10 +39,11 @@
(let [color-format (mf/use-state :hex)
color (shape->color shape)]
[:& color-row {:color color
:format @color-format
:on-change-format #(reset! color-format %)
:copy-data (copy-data shape)}]))
[:div.attributes-fill-block
[:& color-row {:color color
:format @color-format
:on-change-format #(reset! color-format %)
:copy-data (copy-data shape)}]]))
(mf/defc fill-panel
[{:keys [shapes]}]
@ -51,14 +51,13 @@
(when (seq shapes)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (tr "inspect.attributes.fill")]
(when (= (count shapes) 1)
[:& copy-button {:data (copy-data (first shapes))}])]
[:div.attributes-block-title-text (tr "inspect.attributes.fill")]]
(for [shape shapes]
[:div.attributes-fill-blocks
(for [shape shapes]
(if (seq (:fills shape))
(for [value (:fills shape [])]
[:& fill-block {:key (str "fill-block-" (:id shape) value)
:shape value}])
[:& fill-block {:key (str "fill-block-only" (:id shape))
:shape shape}]))])))
:shape shape}]))]])))

View file

@ -23,31 +23,31 @@
:rx "border-radius"
:r1 "border-radius"}
:format {:rotation #(str/fmt "rotate(%sdeg)" %)
:r1 #(apply str/fmt "%spx, %spx, %spx, %spx" %)
:width (partial fmt/format-size :width)
:height (partial fmt/format-size :height)}
:r1 #(apply str/fmt "%spx, %spx, %spx, %spx" %)
:width #(cg/get-size :width %)
:height #(cg/get-size :height %)}
:multi {:r1 [:r1 :r2 :r3 :r4]}})
(defn copy-data
([shape]
(apply copy-data shape properties))
([shape & properties]
([shape & properties]
(cg/generate-css-props shape properties params)))
(mf/defc layout-block
[{:keys [shape]}]
(let [selrect (:selrect shape)
{:keys [x y]} selrect]
{:keys [x y width height]} selrect]
[:*
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.layout.width")]
[:div.attributes-value (fmt/format-size :width (:width shape) shape)]
[:& copy-button {:data (copy-data shape :width)}]]
[:div.attributes-value (fmt/format-size :width width shape)]
[:& copy-button {:data (copy-data selrect :width)}]]
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.layout.height")]
[:div.attributes-value (fmt/format-size :height (:height shape) shape)]
[:& copy-button {:data (copy-data shape :height)}]]
[:div.attributes-value (fmt/format-size :height height shape)]
[:& copy-button {:data (copy-data selrect :height)}]]
(when (not= (:x shape) 0)
[:div.attributes-unit-row
@ -73,7 +73,7 @@
[:div.attributes-value
(fmt/format-number (:r1 shape)) ", "
(fmt/format-number (:r2 shape)) ", "
(fmt/format-number (:r3 shape))", "
(fmt/format-number (:r3 shape)) ", "
(fmt/format-pixels (:r4 shape))]
[:& copy-button {:data (copy-data shape :r1)}]])

View file

@ -38,18 +38,18 @@
:layout-wrap-type "flex-wrap"
:layout-gap "gap"
:layout-padding "padding"}
:format {:layout name
:layout-flex-dir name
:layout-align-items name
:layout-justify-content name
:layout-wrap-type name
:format {:layout d/name
:layout-flex-dir d/name
:layout-align-items d/name
:layout-justify-content d/name
:layout-wrap-type d/name
:layout-gap fm/format-gap
:layout-padding fm/format-padding}})
(def layout-align-content-params
{:props [:layout-align-content]
:to-prop {:layout-align-content "align-content"}
:format {:layout-align-content name}})
:format {:layout-align-content d/name}})
(defn copy-data
([shape]
@ -72,7 +72,7 @@
(for [[k v] values]
[:span.items {:key (str type "-" k "-" v)} v "px"])]))
(mf/defc layout-block
(mf/defc layout-flex-block
[{:keys [shape]}]
[:*
[:div.attributes-unit-row
@ -129,11 +129,11 @@
(let [shapes (->> shapes (filter has-flex?))]
(when (seq shapes)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text "Layout"]
(when (= (count shapes) 1)
[:& copy-button {:data (copy-data (first shapes))}])]
[:div.attributes-block-title
[:div.attributes-block-title-text "Layout"]
(when (= (count shapes) 1)
[:& copy-button {:data (copy-data (first shapes))}])]
(for [shape shapes]
[:& layout-block {:shape shape
:key (:id shape)}])])))
(for [shape shapes]
[:& layout-flex-block {:shape shape
:key (:id shape)}])])))

View file

@ -43,7 +43,7 @@
:layout-item-max-w "max-width"
:layout-item-min-w "min-width"}
:format {:layout-item-margin format-margin
:layout-item-align-self name}})
:layout-item-align-self d/name}})
(defn copy-data
([shape]

View file

@ -55,12 +55,7 @@
[{:keys [shape]}]
(let [color-format (mf/use-state :hex)
color (shape->color shape)]
[:*
[:& color-row {:color color
:format @color-format
:copy-data (copy-color-data shape)
:on-change-format #(reset! color-format %)}]
[:div.attributes-stroke-block
(let [{:keys [stroke-style stroke-alignment]} shape
stroke-style (if (= stroke-style :svg) :solid stroke-style)
stroke-alignment (or stroke-alignment :center)]
@ -78,7 +73,11 @@
;; inspect.attributes.stroke.alignment.inner
;; inspect.attributes.stroke.alignment.outer
[:div.attributes-label (->> stroke-alignment d/name (str "inspect.attributes.stroke.alignment.") (tr))]
[:& copy-button {:data (copy-stroke-data shape)}]])]))
[:& copy-button {:data (copy-stroke-data shape)}]])
[:& color-row {:color color
:format @color-format
:copy-data (copy-color-data shape)
:on-change-format #(reset! color-format %)}]]))
(mf/defc stroke-panel
[{:keys [shapes]}]
@ -86,14 +85,13 @@
(when (seq shapes)
[:div.attributes-block
[:div.attributes-block-title
[:div.attributes-block-title-text (tr "inspect.attributes.stroke")]
(when (= (count shapes) 1)
[:& copy-button {:data (copy-stroke-data (first shapes))}])]
[:div.attributes-block-title-text (tr "inspect.attributes.stroke")]]
(for [shape shapes]
[:div.attributes-stroke-blocks
(for [shape shapes]
(if (seq (:strokes shape))
(for [value (:strokes shape [])]
[:& stroke-block {:key (str "stroke-color-" (:id shape) value)
:shape value}])
[:& stroke-block {:key (str "stroke-color-only" (:id shape))
:shape shape}]))])))
:shape shape}]))]])))

View file

@ -7,6 +7,7 @@
(ns app.main.ui.viewer.inspect.attributes.text
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.text :as txt]
[app.main.fonts :as fonts]
[app.main.store :as st]
@ -42,6 +43,7 @@
:font-family
:font-style
:font-size
:font-weight
:line-height
:letter-spacing
:text-decoration
@ -57,13 +59,14 @@
(def params
{:to-prop {:fill-color "color"
:fill-color-gradient "color"}
:format {:font-family #(str "'" % "'")
:font-style #(str "'" % "'")
:font-size #(str (format-number %) "px")
:format {:font-family #(dm/str "'" % "'")
:font-style #(dm/str % )
:font-size #(dm/str (format-number %) "px")
:font-weight d/name
:line-height #(format-number %)
:letter-spacing #(str (format-number %) "px")
:text-decoration name
:text-transform name
:letter-spacing #(dm/str (format-number %) "px")
:text-decoration d/name
:text-transform d/name
:fill-color #(-> %2 shape->color uc/color->background)
:fill-color-gradient #(-> %2 shape->color uc/color->background)}})
@ -131,6 +134,12 @@
[:div.attributes-value (str (format-number (:font-size style))) "px"]
[:& copy-button {:data (copy-style-data style :font-size)}]])
(when (:font-weight style)
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.typography.font-weight")]
[:div.attributes-value (str (:font-weight style))]
[:& copy-button {:data (copy-style-data style :font-weight)}]])
(when (:line-height style)
[:div.attributes-unit-row
[:div.attributes-label (tr "inspect.attributes.typography.line-height")]

View file

@ -7,37 +7,34 @@
(ns app.main.ui.viewer.inspect.code
(:require
["js-beautify" :as beautify]
["react-dom/server" :as rds]
[app.common.data.macros :as dm]
[app.common.geom.shapes :as gsh]
[app.common.uuid :as uuid]
[app.main.data.events :as ev]
[app.main.refs :as refs]
[app.main.render :as render]
[app.main.store :as st]
[app.main.ui.components.code-block :refer [code-block]]
[app.main.ui.components.copy-button :refer [copy-button]]
[app.main.ui.hooks :as hooks]
[app.main.ui.icons :as i]
[app.util.code-gen :as cg]
[app.util.dom :as dom]
[cuerdas.core :as str]
[potok.core :as ptk]
[rumext.v2 :as mf]))
(defn generate-markup-code [_type shapes from]
(let [frame (if (= from :workspace)
(dom/query js/document (dm/str "#shape-" uuid/zero))
(dom/query js/document "#svg-frame"))
markup-shape
(fn [shape]
(let [selector (str "#shape-" (:id shape) (when (= :text (:type shape)) " .root"))]
(when-let [el (and frame (dom/query frame selector))]
(str
(str/fmt "<!-- %s -->" (:name shape))
(.-outerHTML el)))))]
(->> shapes
(map markup-shape )
(remove nil?)
(str/join "\n\n"))))
(defn generate-markup-code [objects shapes]
;; Here we can render specific HTML code
(->> shapes
(map (fn [shape]
(dm/str
"<!-- Shape: " (:name shape) " -->"
(rds/renderToStaticMarkup
(mf/element
render/object-svg
#js {:objects objects
:object-id (-> shape :id)})))))
(str/join "\n\n")))
(defn format-code [code type]
(let [code (-> code
@ -55,6 +52,16 @@
(mf/deref get-layout-children-refs)))
(defn get-objects [from]
(let [page-objects-ref
(mf/use-memo
(mf/deps from)
(fn []
(if (= from :workspace)
refs/workspace-page-objects
(refs/get-viewer-objects))))]
(mf/deref page-objects-ref)))
(mf/defc code
[{:keys [shapes frame on-expand from]}]
(let [style-type (mf/use-state "css")
@ -64,12 +71,14 @@
route (mf/deref refs/route)
page-id (:page-id (:query-params route))
flex-items (get-flex-elements page-id shapes from)
objects (get-objects from)
shapes (map #(assoc % :flex-items flex-items) shapes)
style-code (-> (cg/generate-style-code @style-type shapes)
(format-code "css"))
markup-code (-> (mf/use-memo (mf/deps shapes) #(generate-markup-code @markup-type shapes from))
(format-code "svg"))
markup-code
(-> (mf/use-memo (mf/deps shapes) #(generate-markup-code objects shapes))
(format-code "svg"))
on-markup-copied
(mf/use-callback

View file

@ -411,6 +411,8 @@
current-file-id (mf/use-ctx ctx/current-file-id)
local-component? (= component-file current-file-id)
remote-components (filter #(not= (:component-file %) current-file-id)
component-shapes)
workspace-data (deref refs/workspace-data)
workspace-libraries (deref refs/workspace-libraries)
@ -442,16 +444,19 @@
:accept-style :primary
:on-accept do-update-component}))
do-update-in-bulk #(st/emit! (modal/show
{:type :confirm
:message ""
:title (tr "modals.update-remote-component-in-bulk.message")
:hint (tr "modals.update-remote-component-in-bulk.hint")
:items component-shapes
:cancel-label (tr "modals.update-remote-component.cancel")
:accept-label (tr "modals.update-remote-component.accept")
:accept-style :primary
:on-accept do-update-component-in-bulk}))]
do-update-in-bulk (fn []
(if (empty? remote-components)
(do-update-component-in-bulk)
#(st/emit! (modal/show
{:type :confirm
:message ""
:title (tr "modals.update-remote-component-in-bulk.message")
:hint (tr "modals.update-remote-component-in-bulk.hint")
:items remote-components
:cancel-label (tr "modals.update-remote-component.cancel")
:accept-label (tr "modals.update-remote-component.accept")
:accept-style :primary
:on-accept do-update-component-in-bulk}))))]
[:*
[:*
(when (or (not is-non-root?) (and has-component? (not single?)))

View file

@ -12,7 +12,7 @@
[app.main.data.workspace.libraries :as dwl]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.icons :as i]
[app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.assets :as a]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
@ -24,31 +24,42 @@
(def workspace-file
(l/derived :workspace-file st/state))
(defn contents-str
(defn library-str
[components-count graphics-count colors-count typography-count]
(str
(str/join " · "
(cond-> []
(< 0 components-count)
(conj (tr "workspace.libraries.components" components-count))
(< 0 graphics-count)
(conj (tr "workspace.libraries.graphics" graphics-count))
(< 0 colors-count)
(conj (tr "workspace.libraries.colors" colors-count))
(< 0 typography-count)
(conj (tr "workspace.libraries.typography" typography-count))))
"\u00A0"))
(defn local-library-str
[library]
(let [components-count (count (get-in library [:data :components] []))
graphics-count (count (get-in library [:data :media] []))
colors-count (count (get-in library [:data :colors] []))
typography-count (count (get-in library [:data :typographies] []))]
;; Include a &nbsp; so this block has always some content
(str
(str/join " · "
(cond-> []
(< 0 components-count)
(conj (tr "workspace.libraries.components" components-count))
(library-str components-count graphics-count colors-count typography-count)))
(< 0 graphics-count)
(conj (tr "workspace.libraries.graphics" graphics-count))
(< 0 colors-count)
(conj (tr "workspace.libraries.colors" colors-count))
(< 0 typography-count)
(conj (tr "workspace.libraries.typography" typography-count))))
"\u00A0")))
(defn external-library-str
[library]
(let [components-count (get-in library [:library-summary :components :count] 0)
graphics-count (get-in library [:library-summary :media :count] 0)
colors-count (get-in library [:library-summary :colors :count] 0)
typography-count (get-in library [:library-summary :typographies :count] 0)]
(library-str components-count graphics-count colors-count typography-count)))
(mf/defc libraries-tab
[{:keys [file libraries shared-files] :as props}]
[{:keys [file colors typographies media components libraries shared-files] :as props}]
(let [search-term (mf/use-state "")
sorted-libraries (->> (vals libraries)
@ -116,21 +127,21 @@
[:div.section-list
[:div.section-list-item
[:div
[:div.item-name (tr "workspace.libraries.file-library")]
[:div.item-contents (contents-str file)]]
[:div
(if (:is-shared file)
[:input.item-button {:type "button"
:value (tr "common.unpublish")
:on-click del-shared}]
[:input.item-button {:type "button"
:value (tr "common.publish")
:on-click add-shared}])]]
[:div.item-name (tr "workspace.libraries.file-library")]
[:div.item-contents (library-str (count components) (count media) (count colors) (count typographies) )]]
[:div
(if (:is-shared file)
[:input.item-button {:type "button"
:value (tr "common.unpublish")
:on-click del-shared}]
[:input.item-button {:type "button"
:value (tr "common.publish")
:on-click add-shared}])]]
(for [library sorted-libraries]
[:div.section-list-item {:key (:id library)}
[:div.item-name (:name library)]
[:div.item-contents (contents-str library)]
[:div.item-contents (local-library-str library)]
[:input.item-button {:type "button"
:value (tr "labels.remove")
:on-click #(unlink-library (:id library))}]])
@ -155,7 +166,7 @@
(for [file filtered-files]
[:div.section-list-item {:key (:id file)}
[:div.item-name (:name file)]
[:div.item-contents (contents-str file)]
[:div.item-contents (external-library-str file)]
[:input.item-button {:type "button"
:value (tr "workspace.libraries.add")
:on-click #(link-library (:id file))}]])]
@ -163,10 +174,10 @@
[:div.section-list-empty
(if (nil? shared-files)
i/loader-pencil
[:* i/library
(if (str/empty? @search-term)
(tr "workspace.libraries.no-shared-libraries-available")
(tr "workspace.libraries.no-matches-for" @search-term))])])]]))
[:* i/library
(if (str/empty? @search-term)
(tr "workspace.libraries.no-shared-libraries-available")
(tr "workspace.libraries.no-matches-for" @search-term))])])]]))
(mf/defc updates-tab
@ -185,7 +196,7 @@
(for [library libraries-need-sync]
[:div.section-list-item {:key (:id library)}
[:div.item-name (:name library)]
[:div.item-contents (contents-str library)]
[:div.item-contents (external-library-str library)]
[:input.item-button {:type "button"
:value (tr "workspace.libraries.update")
:on-click #(update-library (:id library))}]])]])]))
@ -197,10 +208,23 @@
(let [selected-tab (mf/use-state :libraries)
project (mf/deref refs/workspace-project)
file (mf/deref workspace-file)
libraries (->> (mf/deref refs/workspace-libraries)
(d/removem (fn [[_ val]] (:is-indirect val))))
shared-files (mf/deref refs/workspace-shared-files)
colors-ref (mf/use-memo (mf/deps (:id file)) #(a/file-colors-ref (:id file)))
colors (mf/deref colors-ref)
typography-ref (mf/use-memo (mf/deps (:id file)) #(a/file-typography-ref (:id file)))
typographies (mf/deref typography-ref)
media-ref (mf/use-memo (mf/deps (:id file)) #(a/file-media-ref (:id file)))
media (mf/deref media-ref)
components-ref (mf/use-memo (mf/deps (:id file)) #(a/file-components-ref (:id file)))
components (mf/deref components-ref)
change-tab #(reset! selected-tab %)
close #(modal/hide!)]
@ -227,6 +251,10 @@
(case @selected-tab
:libraries
[:& libraries-tab {:file file
:colors colors
:typographies typographies
:media media
:components components
:libraries libraries
:shared-files shared-files}]
:updates

View file

@ -10,6 +10,7 @@
[app.common.data.macros :as dm]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.config :as cf]
[app.main.data.workspace.thumbnails :as dwt]
[app.main.refs :as refs]
[app.main.store :as st]
@ -24,18 +25,23 @@
(defn- draw-thumbnail-canvas!
[canvas-node img-node]
(try
(when (and (some? canvas-node) (some? img-node))
(let [canvas-context (.getContext canvas-node "2d")
canvas-width (.-width canvas-node)
canvas-height (.-height canvas-node)]
(.clearRect canvas-context 0 0 canvas-width canvas-height)
(.drawImage canvas-context img-node 0 0 canvas-width canvas-height)
(dom/set-property! canvas-node "data-ready" "true")
true))
(catch :default err
(.error js/console err)
false)))
(ts/raf
(fn []
(try
(when (and (some? canvas-node) (some? img-node))
(let [canvas-context (.getContext canvas-node "2d")
canvas-width (.-width canvas-node)
canvas-height (.-height canvas-node)]
(.clearRect canvas-context 0 0 canvas-width canvas-height)
(.drawImage canvas-context img-node 0 0 canvas-width canvas-height)
;; Set a true on the next animation frame, we make sure the drawImage is completed
(ts/raf
#(dom/set-data! canvas-node "ready" "true"))
true))
(catch :default err
(.error js/console err)
false)))))
(defn- remove-image-loading
"Remove the changes related to change a url for its embed value. This is necessary
@ -97,19 +103,18 @@
(mf/use-callback
(mf/deps @show-frame-thumbnail)
(fn []
(ts/raf
#(let [canvas-node (mf/ref-val frame-canvas-ref)
img-node (mf/ref-val frame-image-ref)]
(when (draw-thumbnail-canvas! canvas-node img-node)
(reset! image-url nil)
(when @show-frame-thumbnail
(reset! show-frame-thumbnail false))
;; If we don't have the thumbnail data saved (normally the first load) we update the data
;; when available
(when (not @thumbnail-data-ref)
(st/emit! (dwt/update-thumbnail page-id id) ))
(let [canvas-node (mf/ref-val frame-canvas-ref)
img-node (mf/ref-val frame-image-ref)]
(when (draw-thumbnail-canvas! canvas-node img-node)
(reset! image-url nil)
(when @show-frame-thumbnail
(reset! show-frame-thumbnail false))
;; If we don't have the thumbnail data saved (normally the first load) we update the data
;; when available
(when (not @thumbnail-data-ref)
(st/emit! (dwt/update-thumbnail page-id id) ))
(reset! render-frame? false))))))
(reset! render-frame? false)))))
generate-thumbnail
(mf/use-callback
@ -117,8 +122,6 @@
(try
;; When starting generating the canvas we mark it as not ready so its not send to back until
;; we have time to update it
(let [canvas-node (mf/ref-val frame-canvas-ref)]
(dom/set-property! canvas-node "data-ready" "false"))
(let [node @node-ref]
(if (dom/has-children? node)
;; The frame-content need to have children in order to generate the thumbnail
@ -160,6 +163,9 @@
on-update-frame
(mf/use-callback
(fn []
(let [canvas-node (mf/ref-val frame-canvas-ref)]
(when (not= "false" (dom/get-data canvas-node "ready"))
(dom/set-data! canvas-node "ready" "false")))
(when (not @disable-ref?)
(reset! render-frame? true)
(reset! regenerate-thumbnail true))))
@ -251,15 +257,26 @@
:width fixed-width
:height fixed-height
;; DEBUG
:style {:filter (when (debug? :thumbnails) "invert(1)")
:style {:filter (when (and (not (cf/check-browser? :safari)) (debug? :thumbnails)) "invert(1)")
:width "100%"
:height "100%"}}]]
;; Safari don't support filters so instead we add a rectangle around the thumbnail
(when (and (cf/check-browser? :safari) (debug? :thumbnails))
[:rect {:x (+ x 2)
:y (+ y 2)
:width (- width 4)
:height (- height 4)
:stroke "blue"
:stroke-width 2}])
(when (some? @image-url)
[:image {:ref frame-image-ref
:x x
:y y
:href @image-url
:width width
:height height
:on-load on-image-load}])])]))
[:foreignObject {:x x
:y y
:width width
:height height}
[:img {:ref frame-image-ref
:src @image-url
:width width
:height height
:on-load on-image-load}]])])]))

View file

@ -188,6 +188,7 @@
(fn [editor]
(st/emit! (dwt/update-editor editor))
(when editor
(dom/add-class! (dom/get-element-by-class "public-DraftEditor-content") "mousetrap")
(.focus ^js editor))))
handle-return

View file

@ -432,7 +432,8 @@
(or
(= uuid/zero id)
(and
(str/includes? (str/lower (:name shape)) (str/lower search))
(or (str/includes? (str/lower (:name shape)) (str/lower search))
(str/includes? (dm/str (:id shape)) (str/lower search)))
(or
(empty? filters)
(and

View file

@ -17,13 +17,13 @@
(def layout-container-flex-attrs
[:layout ;; :flex, :grid in the future
:layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column
:layout-flex-dir ;; :row, :row-reverse, :column, :column-reverse
:layout-gap-type ;; :simple, :multiple
:layout-gap ;; {:row-gap number , :column-gap number}
:layout-align-items ;; :start :end :center :stretch
:layout-justify-content ;; :start :center :end :space-between :space-around
:layout-align-content ;; :start :center :end :space-between :space-around :stretch (by default)
:layout-wrap-type ;; :wrap, :no-wrap
:layout-wrap-type ;; :wrap, :nowrap
:layout-padding-type ;; :simple, :multiple
:layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative
])
@ -101,8 +101,8 @@
[:button.dir.tooltip.tooltip-bottom
{:class (dom/classnames :active (= saved-dir dir)
:row (= :row dir)
:reverse-row (= :reverse-row dir)
:reverse-column (= :reverse-column dir)
:row-reverse (= :row-reverse dir)
:column-reverse (= :column-reverse dir)
:column (= :column dir))
:key (dm/str "direction-" dir)
:alt (str/replace (str/capital (d/name dir)) "-" " ")
@ -113,9 +113,9 @@
[{:keys [wrap-type set-wrap] :as props}]
[:*
[:button.tooltip.tooltip-bottom
{:class (dom/classnames :active (= wrap-type :no-wrap))
:alt "No-wrap"
:on-click #(set-wrap :no-wrap)
{:class (dom/classnames :active (= wrap-type :nowrap))
:alt "Nowrap"
:on-click #(set-wrap :nowrap)
:style {:padding 0}}
[:span.no-wrap i/minus]]
[:button.wrap.tooltip.tooltip-bottom
@ -252,10 +252,10 @@
:on-click (fn [event]
(reset! gap-selected? :column-gap)
(dom/select-target event))
:on-change (partial set-gap (= :no-wrap wrap-type) :column-gap)
:on-change (partial set-gap (= :nowrap wrap-type) :column-gap)
:on-blur #(reset! gap-selected? :none)
:value (:column-gap gap-value)
:disabled (and (= :no-wrap wrap-type) is-col?)}]]
:disabled (and (= :nowrap wrap-type) is-col?)}]]
[:div.gap-row.tooltip.tooltip-bottom-left
{:alt "Row gap"}
@ -266,10 +266,10 @@
:on-click (fn [event]
(reset! gap-selected? :row-gap)
(dom/select-target event))
:on-change (partial set-gap (= :no-wrap wrap-type) :row-gap)
:on-change (partial set-gap (= :nowrap wrap-type) :row-gap)
:on-blur #(reset! gap-selected? :none)
:value (:row-gap gap-value)
:disabled (and (= :no-wrap wrap-type) (not is-col?))}]]]])
:disabled (and (= :nowrap wrap-type) (not is-col?))}]]]])
(mf/defc layout-container-menu
{::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type" "multiple"]))]}
@ -302,7 +302,7 @@
;; Flex-direction
saved-dir (:layout-flex-dir values)
is-col? (or (= :column saved-dir) (= :reverse-column saved-dir))
is-col? (or (= :column saved-dir) (= :column-reverse saved-dir))
set-direction
(fn [dir]
(st/emit! (dwsl/update-layout ids {:layout-flex-dir dir})))
@ -386,7 +386,7 @@
[:div.btn-wrapper
[:div.direction
[:*
(for [dir [:row :reverse-row :column :reverse-column]]
(for [dir [:row :row-reverse :column :column-reverse]]
[:& direction-btn {:key (d/name dir)
:dir dir
:saved-dir saved-dir

View file

@ -49,6 +49,8 @@
adv-blur-ref (mf/use-ref nil)
adv-spread-ref (mf/use-ref nil)
shadow-style (str (:style value))
remove-shadow-by-index
(fn [values index] (->> (d/enumerate values)
(filterv (fn [[idx _]] (not= idx index)))
@ -116,12 +118,12 @@
;; :value (:blur value)}]
[:select.input-select
{:default-value (str (:style value))
{:default-value shadow-style
:on-change (fn [event]
(let [value (-> event dom/get-target dom/get-value d/read-string)]
(st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index :style] value)))))}
[:option {:value ":drop-shadow"} (tr "workspace.options.shadow-options.drop-shadow")]
[:option {:value ":inner-shadow"} (tr "workspace.options.shadow-options.inner-shadow")]]
[:option {:value ":drop-shadow" :selected (when (= shadow-style ":drop-shadow") "selected")} (tr "workspace.options.shadow-options.drop-shadow")]
[:option {:value ":inner-shadow" :selected (when (= shadow-style ":inner-shadow") "selected")} (tr "workspace.options.shadow-options.inner-shadow")]]
[:div.element-set-actions
[:div.element-set-actions-button {:on-click (toggle-visibility index)}

View file

@ -25,63 +25,7 @@
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(def text-typography-attrs
[:typography-ref-id
:typography-ref-file])
(def text-fill-attrs
[:fill-color
:fill-opacity
:fill-color-ref-id
:fill-color-ref-file
:fill-color-gradient])
(def text-font-attrs
[:font-id
:font-family
:font-variant-id
:font-size
:font-weight
:font-style])
(def text-align-attrs
[:text-align])
(def text-direction-attrs
[:text-direction])
(def text-spacing-attrs
[:line-height
:letter-spacing])
(def text-valign-attrs
[:vertical-align])
(def text-decoration-attrs
[:text-decoration])
(def text-transform-attrs
[:text-transform])
(def shape-attrs
[:grow-type])
(def root-attrs text-valign-attrs)
(def paragraph-attrs
(d/concat-vec
text-align-attrs
text-direction-attrs))
(def text-attrs
(d/concat-vec
text-typography-attrs
text-font-attrs
text-spacing-attrs
text-decoration-attrs
text-transform-attrs))
(def attrs (d/concat-set shape-attrs root-attrs paragraph-attrs text-attrs))
(mf/defc text-align-options
[{:keys [values on-change on-blur] :as props}]
@ -237,20 +181,9 @@
(mf/use-callback
(mf/deps values)
(fn [id attrs]
(st/emit! (dwt/save-font (-> (merge txt/default-text-attrs values attrs)
(select-keys text-attrs))))
(let [attrs (select-keys attrs root-attrs)]
(when-not (empty? attrs)
(st/emit! (dwt/update-root-attrs {:id id :attrs attrs}))))
(let [attrs (select-keys attrs paragraph-attrs)]
(when-not (empty? attrs)
(st/emit! (dwt/update-paragraph-attrs {:id id :attrs attrs}))))
(let [attrs (select-keys attrs text-attrs)]
(when-not (empty? attrs)
(st/emit! (dwt/update-text-attrs {:id id :attrs attrs}))))))
(st/emit! (dwt/save-font (-> (merge txt/default-text-attrs values attrs)
(select-keys dwt/text-attrs)))
(dwt/update-attrs id attrs))))
on-change
(mf/use-callback
@ -279,9 +212,9 @@
(fn [_]
(let [set-values (-> (d/without-nils values)
(select-keys
(d/concat-vec text-font-attrs
text-spacing-attrs
text-transform-attrs)))
(d/concat-vec dwt/text-font-attrs
dwt/text-spacing-attrs
dwt/text-transform-attrs)))
typography (merge txt/default-typography set-values)
typography (generate-typography-name typography)
id (uuid/next)]

View file

@ -11,6 +11,7 @@
[app.common.geom.shapes :as gsh]
[app.common.pages.common :as cpc]
[app.common.text :as txt]
[app.main.data.workspace.texts :as dwt]
[app.main.refs :as refs]
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-attrs blur-menu]]
@ -158,7 +159,7 @@
:shadow shadow-attrs
:blur blur-attrs
:stroke stroke-attrs
:text ot/attrs
:text dwt/attrs
:exports exports-attrs
:layout-container layout-container-flex-attrs
:layout-item layout-item-attrs})

View file

@ -7,7 +7,7 @@
(ns app.main.ui.workspace.sidebar.options.shapes.text
(:require
[app.common.data :as d]
[app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.texts :as dwt :refer [text-fill-attrs root-attrs paragraph-attrs text-attrs]]
[app.main.refs :as refs]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu]]
@ -19,7 +19,7 @@
[app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]]
[app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]]
[app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]]
[app.main.ui.workspace.sidebar.options.menus.text :refer [text-menu text-fill-attrs root-attrs paragraph-attrs text-attrs]]
[app.main.ui.workspace.sidebar.options.menus.text :refer [text-menu]]
[rumext.v2 :as mf]))
(mf/defc options

View file

@ -106,6 +106,7 @@
alt? (mf/use-state false)
mod? (mf/use-state false)
space? (mf/use-state false)
z? (mf/use-state false)
cursor (mf/use-state (utils/get-cursor :pointer-inner))
hover-ids (mf/use-state nil)
hover (mf/use-state nil)
@ -154,9 +155,9 @@
workspace-read-only? (mf/use-ctx ctx/workspace-read-only?)
mode-inspect? (= options-mode :inspect)
on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect)
on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect z?)
on-context-menu (actions/on-context-menu hover hover-ids workspace-read-only?)
on-double-click (actions/on-double-click hover hover-ids drawing-path? base-objects edition workspace-read-only?)
on-double-click (actions/on-double-click hover hover-ids drawing-path? base-objects edition drawing-tool z? workspace-read-only?)
on-drag-enter (actions/on-drag-enter)
on-drag-over (actions/on-drag-over)
on-drop (actions/on-drop file)
@ -212,11 +213,11 @@
(hooks/setup-dom-events viewport-ref zoom disable-paste in-viewport? workspace-read-only?)
(hooks/setup-viewport-size viewport-ref)
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing? workspace-read-only?)
(hooks/setup-keyboard alt? mod? space?)
(hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing? z? workspace-read-only?)
(hooks/setup-keyboard alt? mod? space? z?)
(hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover hover-ids hover-top-frame-id @hover-disabled? focus zoom show-measures?)
(hooks/setup-viewport-modifiers modifiers base-objects)
(hooks/setup-shortcuts node-editing? drawing-path?)
(hooks/setup-shortcuts node-editing? drawing-path? text-editing?)
(hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox)
[:div.viewport
@ -505,6 +506,7 @@
(when show-prototypes?
[:& interactions/interactions
{:selected selected
:page-id page-id
:zoom zoom
:objects objects-modified
:current-transform transform

View file

@ -34,8 +34,7 @@
(defn on-mouse-down
[{:keys [id blocked hidden type]} selected edition drawing-tool text-editing?
node-editing? drawing-path? create-comment? space? panning
workspace-read-only?]
node-editing? drawing-path? create-comment? space? panning workspace-read-only?]
(mf/use-callback
(mf/deps id blocked hidden type selected edition drawing-tool text-editing?
node-editing? drawing-path? create-comment? @space?
@ -140,9 +139,9 @@
(reset! frame-hover nil))))
(defn on-click
[hover selected edition drawing-path? drawing-tool space? selrect]
[hover selected edition drawing-path? drawing-tool space? selrect z?]
(mf/use-callback
(mf/deps @hover selected edition drawing-path? drawing-tool @space? selrect)
(mf/deps @hover selected edition drawing-path? drawing-tool @space? selrect @z?)
(fn [event]
(when (and (nil? selrect)
(or (dom/class? (dom/get-target event) "viewport-controls")
@ -151,7 +150,9 @@
shift? (kbd/shift? event)
alt? (kbd/alt? event)
meta? (kbd/meta? event)
hovering? (some? @hover)]
hovering? (some? @hover)
raw-pt (dom/get-client-position event)
pt (uwvv/point->viewport raw-pt)]
(st/emit! (ms/->MouseEvent :click ctrl? shift? alt? meta?))
(when (and hovering?
@ -159,42 +160,52 @@
(not edition)
(not drawing-path?)
(not drawing-tool))
(st/emit! (dw/select-shape (:id @hover) shift?))))))))
(st/emit! (dw/select-shape (:id @hover) shift?)))
(when (and @z?
(not @space?)
(not edition)
(not drawing-path?)
(not drawing-tool))
(if alt?
(st/emit! (dw/decrease-zoom pt))
(st/emit! (dw/increase-zoom pt)))))))))
(defn on-double-click
[hover hover-ids drawing-path? objects edition workspace-read-only?]
[hover hover-ids drawing-path? objects edition drawing-tool z? workspace-read-only?]
(mf/use-callback
(mf/deps @hover @hover-ids drawing-path? edition workspace-read-only?)
(mf/deps @hover @hover-ids drawing-path? edition drawing-tool @z? workspace-read-only?)
(fn [event]
(dom/stop-propagation event)
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
alt? (kbd/alt? event)
meta? (kbd/meta? event)
(when-not @z?
(let [ctrl? (kbd/ctrl? event)
shift? (kbd/shift? event)
alt? (kbd/alt? event)
meta? (kbd/meta? event)
{:keys [id type] :as shape} (or @hover (get objects (first @hover-ids)))
{:keys [id type] :as shape} (or @hover (get objects (first @hover-ids)))
editable? (contains? #{:text :rect :path :image :circle} type)]
editable? (contains? #{:text :rect :path :image :circle} type)]
(st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt? meta?))
(st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt? meta?))
;; Emit asynchronously so the double click to exit shapes won't break
(timers/schedule
(fn []
(when (and (not drawing-path?) shape)
(cond
(and editable? (not= id edition) (not workspace-read-only?))
(st/emit! (dw/select-shape id)
(dw/start-editing-selected))
(timers/schedule
(fn []
(when (and (not drawing-path?) shape)
(cond
(and editable? (not= id edition) (not workspace-read-only?))
(st/emit! (dw/select-shape id)
(dw/start-editing-selected))
:else
(let [;; We only get inside childrens of the hovering shape
hover-ids (->> @hover-ids (filter (partial cph/is-child? objects id)))
selected (get objects (first hover-ids))]
(when (some? selected)
(reset! hover selected)
(st/emit! (dw/select-shape (:id selected)))))))))))))
:else
(let [;; We only get inside childrens of the hovering shape
hover-ids (->> @hover-ids (filter (partial cph/is-child? objects id)))
selected (get objects (first hover-ids))]
(when (some? selected)
(reset! hover selected)
(st/emit! (dw/select-shape (:id selected))))))))))))))
(defn on-context-menu
[hover hover-ids workspace-read-only?]

View file

@ -16,6 +16,7 @@
[app.main.data.workspace :as dw]
[app.main.data.workspace.path.shortcuts :as psc]
[app.main.data.workspace.shortcuts :as wsc]
[app.main.data.workspace.text.shortcuts :as tsc]
[app.main.store :as st]
[app.main.streams :as ms]
[app.main.ui.hooks :as hooks]
@ -71,13 +72,19 @@
;; We schedule the event so it fires after `initialize-page` event
(timers/schedule #(st/emit! (dw/initialize-viewport size)))))))
(defn setup-cursor [cursor alt? mod? space? panning drawing-tool drawing-path? path-editing? workspace-read-only?]
(defn setup-cursor [cursor alt? mod? space? panning drawing-tool drawing-path? path-editing? z? workspace-read-only?]
(mf/use-effect
(mf/deps @cursor @alt? @mod? @space? panning drawing-tool drawing-path? path-editing? workspace-read-only?)
(mf/deps @cursor @alt? @mod? @space? panning drawing-tool drawing-path? path-editing? z? workspace-read-only?)
(fn []
(let [show-pen? (or (= drawing-tool :path)
(and drawing-path?
(not= drawing-tool :curve)))
show-zoom? (and @z?
(not @space?)
(not @mod?)
(not drawing-path?)
(not drawing-tool))
new-cursor
(cond
(and @mod? @space?) (utils/get-cursor :zoom)
@ -86,6 +93,8 @@
(= drawing-tool :frame) (utils/get-cursor :create-artboard)
(= drawing-tool :rect) (utils/get-cursor :create-rectangle)
(= drawing-tool :circle) (utils/get-cursor :create-ellipse)
(and show-zoom? (not @alt?)) (utils/get-cursor :zoom-in)
(and show-zoom? @alt?) (utils/get-cursor :zoom-out)
show-pen? (utils/get-cursor :pen)
(= drawing-tool :curve) (utils/get-cursor :pencil)
drawing-tool (utils/get-cursor :create-shape)
@ -98,10 +107,11 @@
(when (not= @cursor new-cursor)
(reset! cursor new-cursor))))))
(defn setup-keyboard [alt? mod? space?]
(defn setup-keyboard [alt? mod? space? z?]
(hooks/use-stream ms/keyboard-alt #(reset! alt? %))
(hooks/use-stream ms/keyboard-mod #(reset! mod? %))
(hooks/use-stream ms/keyboard-space #(reset! space? %)))
(hooks/use-stream ms/keyboard-space #(reset! space? %))
(hooks/use-stream ms/keyboard-z #(reset! z? %)))
(defn group-empty-space?
"Given a group `group-id` check if `hover-ids` contains any of its children. If it doesn't means
@ -233,6 +243,7 @@
(filter #(or (empty? focus) (cp/is-in-focus? objects focus %)))
(first)
(get objects))]
(reset! hover hover-shape)
(reset! hover-ids ids)
(reset! hover-top-frame-id (ctt/top-nested-frame objects (deref last-point-ref))))))))
@ -328,11 +339,15 @@
;; this shortcuts outside the viewport?
(defn setup-shortcuts
[path-editing? drawing-path?]
[path-editing? drawing-path? text-editing?]
(hooks/use-shortcuts ::workspace wsc/shortcuts)
(mf/use-effect
(mf/deps path-editing? drawing-path?)
(fn []
(when (or drawing-path? path-editing?)
(st/emit! (dsc/push-shortcuts ::path psc/shortcuts))
#(st/emit! (dsc/pop-shortcuts ::path))))))
(cond
(or drawing-path? path-editing?)
(do (st/emit! (dsc/push-shortcuts ::path psc/shortcuts))
#(st/emit! (dsc/pop-shortcuts ::path)))
text-editing?
(do (st/emit! (dsc/push-shortcuts ::text tsc/shortcuts))
#(st/emit! (dsc/pop-shortcuts ::text)))))))

View file

@ -9,11 +9,16 @@
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.pages.helpers :as cph]
[app.common.types.shape.interactions :as ctsi]
[app.main.data.workspace :as dw]
[app.main.refs :as refs]
[app.main.render :as render]
[app.main.store :as st]
[app.main.ui.context :as muc]
[app.main.ui.shapes.embed :as embed]
[app.main.ui.workspace.viewport.outline :refer [outline]]
[app.util.dom :as dom]
[cuerdas.core :as str]
@ -214,7 +219,7 @@
(mf/defc overlay-marker
[{:keys [index orig-shape dest-shape position objects hover-disabled?] :as props}]
[{:keys [page-id index orig-shape dest-shape position objects hover-disabled?] :as props}]
(let [start-move-position
(fn [_]
(st/emit! (dw/start-move-overlay-pos index)))]
@ -226,13 +231,28 @@
width (:width dest-shape)
height (:height dest-shape)
dest-x (:x dest-shape)
dest-y (:y dest-shape)]
dest-y (:y dest-shape)
shape-wrapper
(mf/use-memo
(mf/deps objects)
#(render/shape-wrapper-factory objects))
dest-shape-id (:id dest-shape)
thumbnail-data-ref (mf/use-memo (mf/deps page-id dest-shape-id) #(refs/thumbnail-frame-data page-id dest-shape-id))
thumbnail-data (mf/deref thumbnail-data-ref)
dest-shape (cond-> dest-shape
(some? thumbnail-data)
(assoc :thumbnail thumbnail-data))]
[:g {:on-mouse-down start-move-position
:on-mouse-enter #(reset! hover-disabled? true)
:on-mouse-leave #(reset! hover-disabled? false)}
[:use {:href (str "#shape-" (:id dest-shape))
:x (- marker-x dest-x)
:y (- marker-y dest-y)}]
[:g {:transform (gmt/translate-matrix (gpt/point (- marker-x dest-x) (- marker-y dest-y))) }
[:& (mf/provider muc/render-thumbnails) {:value true}
[:& (mf/provider embed/context) {:value false}
[:& shape-wrapper {:shape dest-shape}]]]]
[:path {:stroke "var(--color-primary)"
:fill "var(--color-black)"
:fill-opacity 0.5
@ -251,7 +271,7 @@
:fill "var(--color-primary)"}]]))))
(mf/defc interactions
[{:keys [current-transform objects zoom selected hover-disabled?] :as props}]
[{:keys [current-transform objects zoom selected hover-disabled? page-id] :as props}]
(let [active-shapes (into []
(comp (filter #(seq (:interactions %))))
(vals objects))
@ -323,13 +343,15 @@
(= (:overlay-pos-type interaction) :manual))
(if (and (some? move-overlay-to)
(= move-overlay-index index))
[:& overlay-marker {:index index
[:& overlay-marker {:page-id page-id
:index index
:orig-shape shape
:dest-shape dest-shape
:position move-overlay-to
:objects objects
:hover-disabled? hover-disabled?}]
[:& overlay-marker {:index index
[:& overlay-marker {:page-id page-id
:index index
:orig-shape shape
:dest-shape dest-shape
:position (:overlay-position interaction)
@ -343,4 +365,3 @@
:shape shape
:selected selected
:zoom zoom}])))]]))

View file

@ -35,11 +35,11 @@
(or (ex/ignoring (upf/format-path (:content shape)))
"")))
{:keys [x y width height selrect rx ry]} shape
{:keys [x y width height selrect]} shape
border-radius-attrs (.-d (attrs/extract-border-radius shape))
border-radius-attrs (attrs/extract-border-radius shape)
path? (some? border-radius-attrs)
path? (some? (.-d border-radius-attrs))
outline-type (case (:type shape)
:circle "ellipse"
@ -67,9 +67,9 @@
:y (:y selrect)
:width (:width selrect)
:height (:height selrect)
:rx rx
:ry ry
:d border-radius-attrs})]
:rx (.-rx border-radius-attrs)
:ry (.-ry border-radius-attrs)
:d (.-d border-radius-attrs)})]
[:> outline-type (map->obj (merge common props))]))

View file

@ -31,7 +31,7 @@
:duplicate cur/duplicate
:zoom cur/zoom
:zoom-in cur/zoom-in
:zooom-out cur/zoom-out
:zoom-out cur/zoom-out
cur/pointer-inner))
;; Ensure that the label has always the same font

View file

@ -7,6 +7,8 @@
(ns app.util.code-gen
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.pages.helpers :as cph]
[app.common.text :as txt]
[app.main.ui.formats :as fmt]
[app.util.color :as uc]
@ -15,7 +17,7 @@
(defn shadow->css [shadow]
(let [{:keys [style offset-x offset-y blur spread]} shadow
css-color (uc/color->background (:color shadow))]
(str
(dm/str
(if (= style :inner-shadow) "inset " "")
(str/fmt "%spx %spx %spx %spx %s" offset-x offset-y blur spread css-color))))
@ -25,40 +27,78 @@
(str/fmt "%spx" row-gap)
(str/fmt "%spx %spx" row-gap column-gap)))
(defn fill-color->background
[fill]
(uc/color->background {:color (:fill-color fill)
:opacity (:fill-opacity fill)
:gradient (:fill-color-gradient fill)}))
(defn format-fill-color [_ shape]
(let [color {:color (:fill-color shape)
:opacity (:fill-opacity shape)
:gradient (:fill-color-gradient shape)}]
(uc/color->background color)))
(let [fills (:fills shape)
first-fill (first fills)
colors (if (> (count fills) 1)
(map (fn [fill]
(let [color (fill-color->background fill)]
(if (some? (:fill-color-gradient fill))
color
(str/format "linear-gradient(%s,%s)" color color))))
(:fills shape))
[(fill-color->background first-fill)])]
(str/join ", " colors)))
(defn format-stroke [_ shape]
(let [width (:stroke-width shape)
style (let [style (:stroke-style shape)]
(when (keyword? style) (name style)))
color {:color (:stroke-color shape)
:opacity (:stroke-opacity shape)
:gradient (:stroke-color-gradient shape)}]
(when-not (= :none (:stroke-style shape))
(let [first-stroke (first (:strokes shape))
width (:stroke-width first-stroke)
style (let [style (:stroke-style first-stroke)]
(when (keyword? style) (d/name style)))
color {:color (:stroke-color first-stroke)
:opacity (:stroke-opacity first-stroke)
:gradient (:stroke-color-gradient first-stroke)}]
(when-not (= :none (:stroke-style first-stroke))
(str/format "%spx %s %s" width style (uc/color->background color)))))
(def styles-data
{:layout {:props [:width :height :x :y :radius :rx :r1]
(defn format-position [_ shape]
(cond
(cph/frame-shape? shape) "relative"
(empty? (:flex-items shape)) "absolute"
:else "static"))
(defn get-size
[type values]
(let [value (cond
(number? values) values
(string? values) values
(type values) (type values)
:else (type (:selrect values)))]
(if (= :width type)
(fmt/format-size :width value values)
(fmt/format-size :heigth value values))))
(defn styles-data
[shape]
{:position {:props [:type]
:to-prop {:type "position"}
:format {:type format-position}}
:layout {:props (if (empty? (:flex-items shape))
[:width :height :x :y :radius :rx :r1]
[:width :height :radius :rx :r1])
:to-prop {:x "left"
:y "top"
:rotation "transform"
:rx "border-radius"
:r1 "border-radius"}
:format {:rotation #(str/fmt "rotate(%sdeg)" %)
:r1 #(apply str/fmt "%spx, %spx, %spx, %spx" %)
:width (partial fmt/format-size :width)
:height (partial fmt/format-size :height)}
:r1 #(apply str/fmt "%spx, %spx, %spx, %spx" %)
:width #(get-size :width %)
:height #(get-size :height %)}
:multi {:r1 [:r1 :r2 :r3 :r4]}}
:fill {:props [:fill-color :fill-color-gradient]
:to-prop {:fill-color "background" :fill-color-gradient "background"}
:format {:fill-color format-fill-color :fill-color-gradient format-fill-color}}
:stroke {:props [:stroke-style]
:to-prop {:stroke-style "border"}
:format {:stroke-style format-stroke}}
:fill {:props [:fills]
:to-prop {:fills (if (> (count (:fills shape)) 1) "background-image" "background")}
:format {:fills format-fill-color}}
:stroke {:props [:strokes]
:to-prop {:strokes "border"}
:format {:strokes format-stroke}}
:shadow {:props [:shadow]
:to-prop {:shadow :box-shadow}
:format {:shadow #(str/join ", " (map shadow->css %1))}}
@ -66,8 +106,8 @@
:to-prop {:blur "filter"}
:format {:blur #(str/fmt "blur(%spx)" (:value %))}}
:layout-flex {:props [:layout
:layout-align-items
:layout-flex-dir
:layout-align-items
:layout-justify-content
:layout-gap
:layout-padding
@ -79,32 +119,34 @@
:layout-wrap-type "flex-wrap"
:layout-gap "gap"
:layout-padding "padding"}
:format {:layout name
:layout-flex-dir name
:layout-align-items name
:layout-justify-content name
:layout-wrap-type name
:format {:layout d/name
:layout-flex-dir d/name
:layout-align-items d/name
:layout-justify-content d/name
:layout-wrap-type d/name
:layout-gap format-gap
:layout-padding fmt/format-padding}}})
(def style-text
{:props [:fill-color
{:props [:fills
:font-family
:font-style
:font-size
:font-weight
:line-height
:letter-spacing
:text-decoration
:text-transform]
:to-prop {:fill-color "color"}
:format {:font-family #(str "'" % "'")
:font-style #(str "'" % "'")
:font-size #(str % "px")
:line-height #(str % "px")
:letter-spacing #(str % "px")
:text-decoration name
:text-transform name
:fill-color format-fill-color}})
:to-prop {:fills "color"}
:format {:font-family #(dm/str "'" % "'")
:font-style #(dm/str %)
:font-size #(dm/str % "px")
:font-weight #(dm/str %)
:line-height #(dm/str %)
:letter-spacing #(dm/str % "px")
:text-decoration d/name
:text-transform d/name
:fills format-fill-color}})
(def layout-flex-item-params
{:props [:layout-item-margin
@ -120,11 +162,30 @@
:layout-item-min-w "min-width"
:layout-item-align-self "align-self"}
:format {:layout-item-margin fmt/format-margin
:layout-item-max-h #(str % "px")
:layout-item-min-h #(str % "px")
:layout-item-max-w #(str % "px")
:layout-item-min-w #(str % "px")
:layout-item-align-self name}})
:layout-item-max-h #(dm/str % "px")
:layout-item-min-h #(dm/str % "px")
:layout-item-max-w #(dm/str % "px")
:layout-item-min-w #(dm/str % "px")
:layout-item-align-self d/name}})
(def layout-align-content
{:props [:layout-align-content]
:to-prop {:layout-align-content "align-content"}
:format {:layout-align-content d/name}})
(defn get-specific-value
[values prop]
(let [result (if (get values prop)
(get values prop)
(get (:selrect values) prop))
result (if (= :width prop)
(get-size :width values)
result)
result (if (= :height prop)
(get-size :height values)
result)]
result))
(defn generate-css-props
([values properties]
@ -150,20 +211,20 @@
get-value (fn [prop]
(if-let [props (prop multi)]
(map #(get values %) props)
(get values prop)))
(get-specific-value values prop)))
null? (fn [value]
(if (coll? value)
(every? #(or (nil? %) (= % 0)) value)
(or (nil? value) (= value 0))))
default-format (fn [value] (str (fmt/format-pixels value)))
default-format (fn [value] (dm/str (fmt/format-pixels value)))
format-property (fn [prop]
(let [css-prop (or (prop to-prop) (name prop))
(let [css-prop (or (prop to-prop) (d/name prop))
format-fn (or (prop format) default-format)
css-val (format-fn (get-value prop) values)]
(when css-val
(str
(dm/str
(str/repeat " " tab-size)
(str/fmt "%s: %s;" css-prop css-val)))))]
@ -178,20 +239,19 @@
;; it will come with a vector of flex-items if any.
;; If there are none it will continue as usual.
flex-items (:flex-items shape)
props (->> styles-data vals (mapcat :props))
to-prop (->> styles-data vals (map :to-prop) (reduce merge))
format (->> styles-data vals (map :format) (reduce merge))
multi (->> styles-data vals (map :multi) (reduce merge))
props (if (seq flex-items)
(concat props (:props layout-flex-item-params))
props)
to-prop (if (seq flex-items)
(merge to-prop (:to-prop layout-flex-item-params))
to-prop)
format (if (seq flex-items)
(merge format (:format layout-flex-item-params))
format)]
props (->> (styles-data shape) vals (mapcat :props))
to-prop (->> (styles-data shape) vals (map :to-prop) (reduce merge))
format (->> (styles-data shape) vals (map :format) (reduce merge))
multi (->> (styles-data shape) vals (map :multi) (reduce merge))
props (cond-> props
(seq flex-items) (concat (:props layout-flex-item-params))
(= :wrap (:layout-wrap-type shape)) (concat (:props layout-align-content)))
to-prop (cond-> to-prop
(seq flex-items) (merge (:to-prop layout-flex-item-params))
(= :wrap (:layout-wrap-type shape)) (merge (:to-prop layout-align-content)))
format (cond-> format
(seq flex-items) (merge (:format layout-flex-item-params))
(= :wrap (:layout-wrap-type shape)) (merge (:format layout-align-content)))]
(generate-css-props shape props {:to-prop to-prop
:format format
:multi multi
@ -208,40 +268,48 @@
(defn parse-style-text-blocks
[node attrs]
(letfn
[(rec-style-text-map [acc node style]
(let [node-style (merge style (select-keys node attrs))
head (or (-> acc first) [{} ""])
[head-style head-text] head
[(rec-style-text-map [acc node style]
(let [node-style (merge style (select-keys node attrs))
head (or (-> acc first) [{} ""])
[head-style head-text] head
new-acc
(cond
(:children node)
(reduce #(rec-style-text-map %1 %2 node-style) acc (:children node))
new-acc
(cond
(:children node)
(reduce #(rec-style-text-map %1 %2 node-style) acc (:children node))
(not= head-style node-style)
(cons [node-style (:text node "")] acc)
(not= head-style node-style)
(cons [node-style (:text node "")] acc)
:else
(cons [node-style (str head-text "" (:text node))] (rest acc)))
:else
(cons [node-style (dm/str head-text "" (:text node))] (rest acc)))
;; We add an end-of-line when finish a paragraph
new-acc
(if (= (:type node) "paragraph")
(let [[hs ht] (first new-acc)]
(cons [hs (str ht "\n")] (rest new-acc)))
new-acc)]
new-acc))]
new-acc
(if (= (:type node) "paragraph")
(let [[hs ht] (first new-acc)]
(cons [hs (dm/str ht "\n")] (rest new-acc)))
new-acc)]
new-acc))]
(-> (rec-style-text-map [] node {})
reverse)))
(defn text->properties [shape]
(let [text-shape-style (select-keys styles-data [:layout :shadow :blur])
(let [flex-items (:flex-items shape)
text-shape-style (select-keys (styles-data shape) [:layout :shadow :blur])
shape-props (->> text-shape-style vals (mapcat :props))
shape-to-prop (->> text-shape-style vals (map :to-prop) (reduce merge))
shape-format (->> text-shape-style vals (map :format) (reduce merge))
shape-props (cond-> shape-props
(seq flex-items) (concat (:props layout-flex-item-params)))
shape-to-prop (cond-> shape-to-prop
(seq flex-items) (merge (:to-prop layout-flex-item-params)))
shape-format (cond-> shape-format
(seq flex-items) (merge (:format layout-flex-item-params)))
text-values (->> (search-text-attrs
(:content shape)
(conj (:props style-text) :fill-color-gradient :fill-opacity))
@ -257,16 +325,13 @@
(:props style-text)
{:to-prop (:to-prop style-text)
:format (:format style-text)
:tab-size 2})]))
)
:tab-size 2})])))
(defn generate-css [shape]
(let [name (:name shape)
properties (if (= :text (:type shape))
(text->properties shape)
(shape->properties shape))
selector (str/css-selector name)
selector (if (str/starts-with? selector "-") (subs selector 1) selector)]
(str/join "\n" [(str/fmt "/* %s */" name)

View file

@ -478,7 +478,11 @@
(defn get-data [^js node ^string attr]
(when (some? node)
(.getAttribute node (str "data-" attr))))
(.getAttribute node (dm/str "data-" attr))))
(defn set-data! [^js node ^string attr value]
(when (some? node)
(.setAttribute node (dm/str "data-" attr) (dm/str value))))
(defn set-attribute! [^js node ^string attr value]
(when (some? node)

View file

@ -6,13 +6,19 @@
(ns app.util.keyboard
(:require
[app.config :as cfg]))
[app.config :as cfg]
[cuerdas.core :as str]))
(defn is-key?
[^string key]
(fn [^js e]
(= (.-key e) key)))
(defn is-key-ignore-case?
[^string key]
(fn [^js e]
(= (str/upper (.-key e)) (str/upper key))))
(defn ^boolean alt?
[^js event]
(.-altKey event))
@ -38,6 +44,7 @@
(def esc? (is-key? "Escape"))
(def enter? (is-key? "Enter"))
(def space? (is-key? " "))
(def z? (is-key-ignore-case? "z"))
(def up-arrow? (is-key? "ArrowUp"))
(def down-arrow? (is-key? "ArrowDown"))
(def left-arrow? (is-key? "ArrowLeft"))

View file

@ -267,6 +267,30 @@
[]
(dump-selected' @st/state))
(defn ^:export parent
[]
(let [state @st/state
page-id (get state :current-page-id)
objects (get-in state [:workspace-data :pages-index page-id :objects])
selected (first (get-in state [:workspace-local :selected]))
parent-id (get-in objects [selected :parent-id])
parent (get objects parent-id)]
(when parent
(prn (str (:name parent) " - " (:id parent))))
nil))
(defn ^:export frame
[]
(let [state @st/state
page-id (get state :current-page-id)
objects (get-in state [:workspace-data :pages-index page-id :objects])
selected (first (get-in state [:workspace-local :selected]))
frame-id (get-in objects [selected :frame-id])
frame (get objects frame-id)]
(when frame
(prn (str (:name frame) " - " (:id frame))))
nil))
(defn dump-tree'
([state] (dump-tree' state false false))
([state show-ids] (dump-tree' state show-ids false))

View file

@ -3372,11 +3372,11 @@ msgid "workspace.options.layout.direction.column"
msgstr "Columna"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.reverse-column"
msgid "workspace.options.layout.direction.column-reverse"
msgstr "Columna invertida"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.reverse-row"
msgid "workspace.options.layout.direction.row-reverse"
msgstr "Fila invertida"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs

View file

@ -3643,11 +3643,11 @@ msgid "workspace.options.layout.direction.column"
msgstr "Spalte"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.reverse-column"
msgid "workspace.options.layout.direction.column-reverse"
msgstr "Spalte-umgekehrt"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.reverse-row"
msgid "workspace.options.layout.direction.row-reverse"
msgstr "Reihe-umgekehrt"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs

View file

@ -419,6 +419,16 @@ msgstr ""
"Service](https://penpot.app/terms.html). You also might want to read about "
"[font licensing](https://www.typography.com/faq)."
#, markdown
msgid "dashboard.fonts.warning-text"
msgstr ""
"We have detected a possible problem in your fonts "
"related to vertical metrics for different operative systems. "
"In order to check it you can use font vertical metrics services "
"like [this one](https://vertical-metrics.netlify.app/). "
"In addition, we recommend using [Transfonter](https://transfonter.org/) "
"to generate webfonts and fix errors. "
#: src/app/main/ui/dashboard/fonts.cljs
msgid "dashboard.fonts.upload-all"
msgstr "Upload all"
@ -1045,6 +1055,10 @@ msgstr "Font Family"
msgid "inspect.attributes.typography.font-size"
msgstr "Font Size"
#: src/app/main/ui/inspect/attributes/text.cljs
msgid "inspect.attributes.typography.font-size"
msgstr "Font Weight"
#: src/app/main/ui/inspect/attributes/text.cljs
msgid "inspect.attributes.typography.font-style"
msgstr "Font Style"
@ -2201,6 +2215,9 @@ msgstr "Paths"
msgid "shortcut-subsection.shape"
msgstr "Shapes"
msgid "shortcut-subsection.text-editor"
msgstr "Texts"
msgid "shortcut-subsection.tools"
msgstr "Tools"
@ -2530,7 +2547,7 @@ msgid "shortcuts.toggle-layers"
msgstr "Toggle layers"
msgid "shortcuts.toggle-layout-flex"
msgstr "Add/remove layout flex"
msgstr "Add/remove flex layout"
msgid "shortcuts.toggle-lock"
msgstr "Lock selected"
@ -2574,6 +2591,48 @@ msgstr "Distribute vertically"
msgid "shortcuts.zoom-selected"
msgstr "Zoom to selected"
msgid "shortcuts.align-center"
msgstr "Align center"
msgid "shortcuts.align-justify"
msgstr "Align justify"
msgid "shortcuts.bold"
msgstr "Toggle bold"
msgid "shortcuts.italic"
msgstr "Toggle italic"
msgid "shortcuts.font-size-dec"
msgstr "Decrement font size"
msgid "shortcuts.font-size-inc"
msgstr "Increment font size"
msgid "shortcuts.letter-spacing-dec"
msgstr "Decrement letter spacing"
msgid "shortcuts.letter-spacing-inc"
msgstr "Increment letter spacing"
msgid "shortcuts.line-height-dec"
msgstr "Decrement line height"
msgid "shortcuts.line-height-inc"
msgstr "Increment line height"
msgid "shortcuts.line-through"
msgstr "Toggle line through"
msgid "shortcuts.underline"
msgstr "Toggle underline"
msgid "shortcuts.zoom-lense-increase"
msgstr "Zoom lense increase"
msgid "shortcuts.zoom-lense-decrease"
msgstr "Zoom lense decrease"
#: src/app/main/ui/dashboard/files.cljs
msgid "title.dashboard.files"
msgstr "%s - Penpot"
@ -3679,11 +3738,11 @@ msgid "workspace.options.layout.direction.column"
msgstr "Column"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.reverse-column"
msgid "workspace.options.layout.direction.column-reverse"
msgstr "Reverse column"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.reverse-row"
msgid "workspace.options.layout.direction.row-reverse"
msgstr "Reverse row"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
@ -4092,7 +4151,7 @@ msgstr "Updating %s..."
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.add-flex"
msgstr "Add layout flex"
msgstr "Add flex layout"
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.back"
@ -4206,7 +4265,7 @@ msgstr "Path"
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.remove-flex"
msgstr "Remove layout flex"
msgstr "Remove flex layout"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.reset-overrides"
@ -4493,4 +4552,4 @@ msgid "workspace.updates.update"
msgstr "Update"
msgid "workspace.viewport.click-to-close-path"
msgstr "Click to close the path"
msgstr "Click to close the path"

View file

@ -424,6 +424,16 @@ msgstr ""
"más sobre licencias tipográficas: [font "
"licensing](https://www.typography.com/faq)."
#, markdown
msgid "dashboard.fonts.warning-text"
msgstr ""
"Hemos detectado un posible problema en tus fuentes "
"relacionado con las métricas verticales en diferentes sistemas operativos. "
"Puedes comprobar la visualización de tu fuente utilizando servicios "
"[como este](https://vertical-metrics.netlify.app/). "
"Además, recomendamos usar [Transfonter](https://transfonter.org/) "
"para generar fuentes web y corregir posibles errores."
#: src/app/main/ui/dashboard/fonts.cljs
msgid "dashboard.fonts.upload-all"
msgstr "Cargar todas"
@ -1100,6 +1110,10 @@ msgstr "Familia tipográfica"
msgid "inspect.attributes.typography.font-size"
msgstr "Tamaño de fuente"
#: src/app/main/ui/inspect/attributes/text.cljs
msgid "inspect.attributes.typography.font-weight"
msgstr "Grosor de fuente"
#: src/app/main/ui/inspect/attributes/text.cljs
msgid "inspect.attributes.typography.font-style"
msgstr "Estilo de fuente"
@ -2344,6 +2358,9 @@ msgstr "Ruta"
msgid "shortcut-subsection.shape"
msgstr "Formas"
msgid "shortcut-subsection.text-editor"
msgstr "Textos"
msgid "shortcut-subsection.tools"
msgstr "Herramientas"
@ -2673,7 +2690,7 @@ msgid "shortcuts.toggle-layers"
msgstr "Mostrar/ocultar capas"
msgid "shortcuts.toggle-layout-flex"
msgstr "Añadir/eliminar layout flex"
msgstr "Añadir/eliminar flex layout"
msgid "shortcuts.toggle-lock"
msgstr "Bloquear/Desbloquear"
@ -2717,6 +2734,42 @@ msgstr "Distribuir verticalmente"
msgid "shortcuts.zoom-selected"
msgstr "Zoom a selección"
msgid "shortcuts.align-center"
msgstr "Alinear al centro"
msgid "shortcuts.align-justify"
msgstr "Alinear justificado"
msgid "shortcuts.bold"
msgstr "Alternar negrita"
msgid "shortcuts.italic"
msgstr "Alternar cursiva"
msgid "shortcuts.font-size-dec"
msgstr "Decrementar el tamaño de fuente"
msgid "shortcuts.font-size-inc"
msgstr "Incrementar el tamaño de fuente"
msgid "shortcuts.letter-spacing-dec"
msgstr "Decrementar el espaciado de letras"
msgid "shortcuts.letter-spacing-inc"
msgstr "Incrementar el espaciado de letras"
msgid "shortcuts.line-height-dec"
msgstr "Decrementar el interlineado"
msgid "shortcuts.line-height-inc"
msgstr "Incrementar el interlineado"
msgid "shortcuts.line-through"
msgstr "Alternar tachado"
msgid "shortcuts.underline"
msgstr "Alternar subrayado"
#: src/app/main/ui/dashboard/files.cljs
msgid "title.dashboard.files"
msgstr "%s - Penpot"
@ -3192,6 +3245,12 @@ msgstr "Pantalla completa"
msgid "workspace.header.zoom-selected"
msgstr "Zoom a selección"
msgid "shortcuts.zoom-lense-increase"
msgstr "Incrementar zoom a objetivo"
msgid "shortcuts.zoom-lense-decrease"
msgstr "Decrementar zoom a objetivo"
#: src/app/main/ui/workspace/libraries.cljs
msgid "workspace.libraries.add"
msgstr "Añadir"
@ -3864,11 +3923,11 @@ msgid "workspace.options.layout.direction.column"
msgstr "Columna"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.reverse-column"
msgid "workspace.options.layout.direction.column-reverse"
msgstr "Columna invertida"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.reverse-row"
msgid "workspace.options.layout.direction.row-reverse"
msgstr "Fila invertida"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
@ -4283,7 +4342,7 @@ msgstr "Actualizando %s..."
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.add-flex"
msgstr "Añadir layout flex"
msgstr "Añadir flex layout"
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.back"
@ -4404,7 +4463,7 @@ msgstr "Ruta"
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.remove-flex"
msgstr "Eliminar layout flex"
msgstr "Eliminar flex layout"
#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs,
#: src/app/main/ui/workspace/context_menu.cljs,

View file

@ -3541,11 +3541,11 @@ msgid "workspace.options.layout.direction.column"
msgstr "Zutabea"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.reverse-column"
msgid "workspace.options.layout.direction.column-reverse"
msgstr "Alderantzikatu zutabea"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.reverse-row"
msgid "workspace.options.layout.direction.row-reverse"
msgstr "Alderantzikatu lerroa"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs

View file

@ -3605,11 +3605,11 @@ msgid "workspace.options.layout.direction.column"
msgstr "עמודה"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.reverse-column"
msgid "workspace.options.layout.direction.column-reverse"
msgstr "היפוך עמודה"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.reverse-row"
msgid "workspace.options.layout.direction.row-reverse"
msgstr "היפוך שורה"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs

View file

@ -3635,11 +3635,11 @@ msgid "workspace.options.layout.direction.column"
msgstr "Sütun"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.reverse-column"
msgid "workspace.options.layout.direction.column-reverse"
msgstr "Ters sütun"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs
msgid "workspace.options.layout.direction.reverse-row"
msgid "workspace.options.layout.direction.row-reverse"
msgstr "Ters satır"
#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs