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