diff --git a/CHANGES.md b/CHANGES.md index 242fc4407..5ed6eacd3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index 33041c1eb..9809e90a2 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -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)}))] diff --git a/backend/src/app/rpc/commands/teams.clj b/backend/src/app/rpc/commands/teams.clj index 3662b935b..33d087c89 100644 --- a/backend/src/app/rpc/commands/teams.clj +++ b/backend/src/app/rpc/commands/teams.clj @@ -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) diff --git a/backend/src/app/srepl/fixes.clj b/backend/src/app/srepl/fixes.clj index 7192e8149..a294a25d3 100644 --- a/backend/src/app/srepl/fixes.clj +++ b/backend/src/app/srepl/fixes.clj @@ -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)))) \ No newline at end of file diff --git a/common/src/app/common/geom/shapes/flex_layout/bounds.cljc b/common/src/app/common/geom/shapes/flex_layout/bounds.cljc index 64e673fd2..f0ce544ec 100644 --- a/common/src/app/common/geom/shapes/flex_layout/bounds.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/bounds.cljc @@ -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] diff --git a/common/src/app/common/geom/shapes/flex_layout/lines.cljc b/common/src/app/common/geom/shapes/flex_layout/lines.cljc index 48f922856..669dec6fa 100644 --- a/common/src/app/common/geom/shapes/flex_layout/lines.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/lines.cljc @@ -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?})) - diff --git a/common/src/app/common/geom/shapes/flex_layout/positions.cljc b/common/src/app/common/geom/shapes/flex_layout/positions.cljc index 1ad30c891..2e2e44bfd 100644 --- a/common/src/app/common/geom/shapes/flex_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/positions.cljc @@ -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)) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index cbff62e3a..864167f1b 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -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] diff --git a/common/src/app/common/math.cljc b/common/src/app/common/math.cljc index 9def09aff..7760a974c 100644 --- a/common/src/app/common/math.cljc +++ b/common/src/app/common/math.cljc @@ -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? diff --git a/common/src/app/common/media.cljc b/common/src/app/common/media.cljc index 3709168c2..26574bd6a 100644 --- a/common/src/app/common/media.cljc +++ b/common/src/app/common/media.cljc @@ -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)) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index e8abf30fb..2e58f5440 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -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)) diff --git a/common/src/app/common/pages/common.cljc b/common/src/app/common/pages/common.cljc index ab5379504..c738b22d4 100644 --- a/common/src/app/common/pages/common.cljc +++ b/common/src/app/common/pages/common.cljc @@ -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) diff --git a/common/src/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc index effbee310..a8c05724a 100644 --- a/common/src/app/common/pages/migrations.cljc +++ b/common/src/app/common/pages/migrations.cljc @@ -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. diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 88988c275..8d619cc6c 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -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]}] diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index 470d8b68d..aebcb9d1c 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -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] diff --git a/common/src/app/common/types/typography.cljc b/common/src/app/common/types/typography.cljc index 426b00b7b..995552ffe 100644 --- a/common/src/app/common/types/typography.cljc +++ b/common/src/app/common/types/typography.cljc @@ -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))))) + diff --git a/frontend/resources/images/features/1.17-ally.gif b/frontend/resources/images/features/1.17-ally.gif new file mode 100644 index 000000000..05a408152 Binary files /dev/null and b/frontend/resources/images/features/1.17-ally.gif differ diff --git a/frontend/resources/images/features/1.17-flex-layout.gif b/frontend/resources/images/features/1.17-flex-layout.gif new file mode 100644 index 000000000..3cceaaddb Binary files /dev/null and b/frontend/resources/images/features/1.17-flex-layout.gif differ diff --git a/frontend/resources/images/features/1.17-inspect.gif b/frontend/resources/images/features/1.17-inspect.gif new file mode 100644 index 000000000..1dde7b296 Binary files /dev/null and b/frontend/resources/images/features/1.17-inspect.gif differ diff --git a/frontend/resources/images/features/1.17-webhook.gif b/frontend/resources/images/features/1.17-webhook.gif new file mode 100644 index 000000000..01b67d6c3 Binary files /dev/null and b/frontend/resources/images/features/1.17-webhook.gif differ diff --git a/frontend/resources/images/login-penpot.svg b/frontend/resources/images/login-penpot.svg new file mode 100644 index 000000000..828c55546 --- /dev/null +++ b/frontend/resources/images/login-penpot.svg @@ -0,0 +1,481 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/resources/images/onboarding-version.jpg b/frontend/resources/images/onboarding-version.jpg new file mode 100644 index 000000000..6e564b987 Binary files /dev/null and b/frontend/resources/images/onboarding-version.jpg differ diff --git a/frontend/resources/styles/main/layouts/inspect.scss b/frontend/resources/styles/main/layouts/inspect.scss index 8f5d12a46..d0815a370 100644 --- a/frontend/resources/styles/main/layouts/inspect.scss +++ b/frontend/resources/styles/main/layouts/inspect.scss @@ -129,6 +129,7 @@ $width-settings-bar: 256px; overflow: hidden; flex-direction: column; justify-content: flex-start; + position: relative; } .inspect-svg-container { diff --git a/frontend/resources/styles/main/layouts/login.scss b/frontend/resources/styles/main/layouts/login.scss index e4b95c76b..0c89db043 100644 --- a/frontend/resources/styles/main/layouts/login.scss +++ b/frontend/resources/styles/main/layouts/login.scss @@ -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; } diff --git a/frontend/resources/styles/main/partials/dashboard-fonts.scss b/frontend/resources/styles/main/partials/dashboard-fonts.scss index 9b186fdc5..edf1d2017 100644 --- a/frontend/resources/styles/main/partials/dashboard-fonts.scss +++ b/frontend/resources/styles/main/partials/dashboard-fonts.scss @@ -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; } } } diff --git a/frontend/resources/styles/main/partials/inspect.scss b/frontend/resources/styles/main/partials/inspect.scss index 8e0eaa949..28d4fb9fc 100644 --- a/frontend/resources/styles/main/partials/inspect.scss +++ b/frontend/resources/styles/main/partials/inspect.scss @@ -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; } } diff --git a/frontend/resources/styles/main/partials/modal.scss b/frontend/resources/styles/main/partials/modal.scss index 026deb7cf..b0b118cf6 100644 --- a/frontend/resources/styles/main/partials/modal.scss +++ b/frontend/resources/styles/main/partials/modal.scss @@ -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 { diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 5da973616..fb0387ff3 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -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); } diff --git a/frontend/resources/styles/main/partials/viewer.scss b/frontend/resources/styles/main/partials/viewer.scss index 6875d65b3..b5a5e0365 100644 --- a/frontend/resources/styles/main/partials/viewer.scss +++ b/frontend/resources/styles/main/partials/viewer.scss @@ -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 { diff --git a/frontend/resources/styles/main/partials/workspace-header.scss b/frontend/resources/styles/main/partials/workspace-header.scss index b2a0a89d5..755a442c5 100644 --- a/frontend/resources/styles/main/partials/workspace-header.scss +++ b/frontend/resources/styles/main/partials/workspace-header.scss @@ -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%; } diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss index 7cc8587a9..1128ba62f 100644 --- a/frontend/resources/styles/main/partials/workspace.scss +++ b/frontend/resources/styles/main/partials/workspace.scss @@ -273,7 +273,6 @@ $height-palette-max: 80px; .frame-thumbnail-wrapper { .fills, - .strokes, .frame-clip-def { opacity: 0; } diff --git a/frontend/src/app/main/data/fonts.cljs b/frontend/src/app/main/data/fonts.cljs index cdfe3d36d..5e45a4db0 100644 --- a/frontend/src/app/main/data/fonts.cljs +++ b/frontend/src/app/main/data/fonts.cljs @@ -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) diff --git a/frontend/src/app/main/data/shortcuts.cljs b/frontend/src/app/main/data/shortcuts.cljs index ec17b59ae..383aa4e1f 100644 --- a/frontend/src/app/main/data/shortcuts.cljs +++ b/frontend/src/app/main/data/shortcuts.cljs @@ -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! ([] diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 47de657b7..18bb14300 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -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)) diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index a9df53834..0cbd9877b 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -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 diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index de4782c4a..965fa5e8b 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -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)) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 500c75652..48d1b4424 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -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] diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index fcf8f4dbf..3ced06e11 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -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})) diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs index d7e81b0c4..cef4b74a1 100644 --- a/frontend/src/app/main/data/workspace/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/shortcuts.cljs @@ -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)) diff --git a/frontend/src/app/main/data/workspace/text/shortcuts.cljs b/frontend/src/app/main/data/workspace/text/shortcuts.cljs new file mode 100644 index 000000000..5ef7850ee --- /dev/null +++ b/frontend/src/app/main/data/workspace/text/shortcuts.cljs @@ -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"})}}) + + + diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index b98de7713..dd3d79cb0 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -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))))))) diff --git a/frontend/src/app/main/data/workspace/thumbnails.cljs b/frontend/src/app/main/data/workspace/thumbnails.cljs index 027b4b941..78740ac67 100644 --- a/frontend/src/app/main/data/workspace/thumbnails.cljs +++ b/frontend/src/app/main/data/workspace/thumbnails.cljs @@ -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" diff --git a/frontend/src/app/main/fonts.cljs b/frontend/src/app/main/fonts.cljs index e3f7d198c..82709468e 100644 --- a/frontend/src/app/main/fonts.cljs +++ b/frontend/src/app/main/fonts.cljs @@ -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) diff --git a/frontend/src/app/main/streams.cljs b/frontend/src/app/main/streams.cljs index a586077a9..9f63c8572 100644 --- a/frontend/src/app/main/streams.cljs +++ b/frontend/src/app/main/streams.cljs @@ -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)) diff --git a/frontend/src/app/main/ui/dashboard/fonts.cljs b/frontend/src/app/main/ui/dashboard/fonts.cljs index 461452268..f7236a88c 100644 --- a/frontend/src/app/main/ui/dashboard/fonts.cljs +++ b/frontend/src/app/main/ui/dashboard/fonts.cljs @@ -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?) diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 1675b59cc..b1450a734 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -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 diff --git a/frontend/src/app/main/ui/formats.cljs b/frontend/src/app/main/ui/formats.cljs index f972bda81..c4867a9b5 100644 --- a/frontend/src/app/main/ui/formats.cljs +++ b/frontend/src/app/main/ui/formats.cljs @@ -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] diff --git a/frontend/src/app/main/ui/releases.cljs b/frontend/src/app/main/ui/releases.cljs index dafc702d3..840b20c8c 100644 --- a/frontend/src/app/main/ui/releases.cljs +++ b/frontend/src/app/main/ui/releases.cljs @@ -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"))) diff --git a/frontend/src/app/main/ui/releases/v1_17.cljs b/frontend/src/app/main/ui/releases/v1_17.cljs new file mode 100644 index 000000000..1c9bd519a --- /dev/null +++ b/frontend/src/app/main/ui/releases/v1_17.cljs @@ -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, you’ll also be able to inspect the code and properties of your designs right from the workspace and to manage webhooks. We’ve 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}]]]]]]))) diff --git a/frontend/src/app/main/ui/shapes/frame.cljs b/frontend/src/app/main/ui/shapes/frame.cljs index 05c9c3ec7..4d124f29f 100644 --- a/frontend/src/app/main/ui/shapes/frame.cljs +++ b/frontend/src/app/main/ui/shapes/frame.cljs @@ -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} diff --git a/frontend/src/app/main/ui/shapes/text.cljs b/frontend/src/app/main/ui/shapes/text.cljs index 48390ead1..26fdf23c7 100644 --- a/frontend/src/app/main/ui/shapes/text.cljs +++ b/frontend/src/app/main/ui/shapes/text.cljs @@ -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]))) diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index 5ad25a7df..0fd5f0cd7 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -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)) diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs index a9dfb822c..4b7de5729 100644 --- a/frontend/src/app/main/ui/viewer.cljs +++ b/frontend/src/app/main/ui/viewer.cljs @@ -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)) diff --git a/frontend/src/app/main/ui/viewer/inspect.cljs b/frontend/src/app/main/ui/viewer/inspect.cljs index 8f78cc5bc..f1e901ad0 100644 --- a/frontend/src/app/main/ui/viewer/inspect.cljs +++ b/frontend/src/app/main/ui/viewer/inspect.cljs @@ -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))))] diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes.cljs index cb43f937b..830a65cca 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes.cljs @@ -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]}) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs index 29784559c..500ddd500 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/fill.cljs @@ -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}]))]]))) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs index cdcc1c356..f1d4490a2 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/layout.cljs @@ -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)}]]) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex.cljs index 1cc404df8..92ae1521e 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex.cljs @@ -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)}])]))) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex_element.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex_element.cljs index 79b5ca63a..9e23c6a4c 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex_element.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/layout_flex_element.cljs @@ -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] diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs index f38b2c4aa..34e1f70bc 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/stroke.cljs @@ -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}]))]]))) diff --git a/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs b/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs index 4d600a4f9..d5c1c87b4 100644 --- a/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/attributes/text.cljs @@ -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")] diff --git a/frontend/src/app/main/ui/viewer/inspect/code.cljs b/frontend/src/app/main/ui/viewer/inspect/code.cljs index 9e544d565..dff59ed74 100644 --- a/frontend/src/app/main/ui/viewer/inspect/code.cljs +++ b/frontend/src/app/main/ui/viewer/inspect/code.cljs @@ -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 "" (: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 + "" + (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 diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 9fc5c2a06..1bf722ad2 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -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?))) diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index a30e4f7d0..b2ba31971 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -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   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 diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs index f6a990346..4bb5c9864 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs @@ -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}]])])])) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs index f18a8eab3..a866d451a 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index 81f5a8970..d7f61a31d 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs index 66d8f8e05..8c4d50141 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs index 31e8b8a47..fca3df79e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs @@ -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)} diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs index 8db3e57f6..70e539d73 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs @@ -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)] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs index 36493ea16..f8e76adf1 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs @@ -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}) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs index 9105e8ac8..3437d4a8a 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index b0aba960e..f54cc621a 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -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 diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 1a85587f2..4c9ab2cf5 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -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?] diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index 261ee5493..d4dd8c624 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -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))))))) diff --git a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs index 7ecc5989a..de17f3fd2 100644 --- a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs @@ -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}])))]])) - diff --git a/frontend/src/app/main/ui/workspace/viewport/outline.cljs b/frontend/src/app/main/ui/workspace/viewport/outline.cljs index 674e40bc0..2a52f18de 100644 --- a/frontend/src/app/main/ui/workspace/viewport/outline.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/outline.cljs @@ -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))])) diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs index 549489e4c..750c79730 100644 --- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -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 diff --git a/frontend/src/app/util/code_gen.cljs b/frontend/src/app/util/code_gen.cljs index 9a95a580c..acbd5ebfc 100644 --- a/frontend/src/app/util/code_gen.cljs +++ b/frontend/src/app/util/code_gen.cljs @@ -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) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index b0a1488a3..0307321c5 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -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) diff --git a/frontend/src/app/util/keyboard.cljs b/frontend/src/app/util/keyboard.cljs index 09d5dd72b..ccb14ddf6 100644 --- a/frontend/src/app/util/keyboard.cljs +++ b/frontend/src/app/util/keyboard.cljs @@ -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")) diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index e66d23dea..b663cdc49 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -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)) diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po index 1a81daa79..23418410d 100644 --- a/frontend/translations/ca.po +++ b/frontend/translations/ca.po @@ -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 diff --git a/frontend/translations/de.po b/frontend/translations/de.po index ca8513687..a1d478987 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -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 diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 09b1ab248..4438d24bd 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -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" \ No newline at end of file +msgstr "Click to close the path" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 2cb5e9d15..4fae8acc9 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -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, diff --git a/frontend/translations/eu.po b/frontend/translations/eu.po index e78b98c9b..4275a680a 100644 --- a/frontend/translations/eu.po +++ b/frontend/translations/eu.po @@ -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 diff --git a/frontend/translations/he.po b/frontend/translations/he.po index f4f44f3de..42c8df443 100644 --- a/frontend/translations/he.po +++ b/frontend/translations/he.po @@ -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 diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index 204b7486f..81d91ff2c 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -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