diff --git a/CHANGES.md b/CHANGES.md index adfe0d38b..ae1886fb9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,8 +9,30 @@ - Add advanced prototyping [#244](https://tree.taiga.io/project/penpot/us/244). ### :bug: Bugs fixed + +- Fix problem with overflow dropdown on stroke-cap [#1216](https://github.com/penpot/penpot/issues/1216) +- Fix menu context for single element nested in components [#1186](https://github.com/penpot/penpot/issues/1186) +- Fix error screen when operations over comments fail [#1219](https://github.com/penpot/penpot/issues/1219) +- Fix undo problem when changing typography/color from library [#1230](https://github.com/penpot/penpot/issues/1230) +- Fix problem with text margin while rendering [#1231](https://github.com/penpot/penpot/issues/1231) +- Fix problem with masked texts on exporting [Taiga #2116](https://tree.taiga.io/project/penpot/issue/2116) +- Fix text editor enter behaviour with centered texts [Taiga #2126](https://tree.taiga.io/project/penpot/issue/2126) +- Fix residual stroke on imported svg [Taiga #2125](https://tree.taiga.io/project/penpot/issue/2125) +- Add links for terms of service and privacy policy in register checkbox [Taiga #2020](https://tree.taiga.io/project/penpot/issue/2020) +- Allow three character hex and web colors in color picker hex input [#1184](https://github.com/penpot/penpot/issues/1184) +- Allow lowercase search for fonts [#1180](https://github.com/penpot/penpot/issues/1180) +- Fix group renaming problem [Taiga #1969](https://tree.taiga.io/project/penpot/issue/1969) +- Fix export group with shadows on children [Taiga #2036](https://tree.taiga.io/project/penpot/issue/2036) +- Fix zoom context menu in viewer [Taiga #2041](https://tree.taiga.io/project/penpot/issue/2041) +- Fix stroke caps adjustments in relation with stroke size [Taiga #2123](https://tree.taiga.io/project/penpot/issue/2123) +- Fix problem duplicating paths [Taiga #2147](https://tree.taiga.io/project/penpot/issue/2147) + ### :arrow_up: Deps updates ### :boom: Breaking changes + +- Some stroke-caps can change behaviour +- Text display bug fix could potentialy make some texts jump a line + ### :heart: Community contributions by (Thank you!) ## 1.8.3-alpha diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 43b069eb3..30b3cb357 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -141,6 +141,7 @@ (d/export gpr/points->selrect) (d/export gpr/points->rect) (d/export gpr/center->rect) +(d/export gpr/join-rects) (d/export gtr/move) (d/export gtr/absolute-move) diff --git a/common/src/app/common/geom/shapes/bool.cljc b/common/src/app/common/geom/shapes/bool.cljc index b0b47c057..93b7ccc72 100644 --- a/common/src/app/common/geom/shapes/bool.cljc +++ b/common/src/app/common/geom/shapes/bool.cljc @@ -7,6 +7,8 @@ (ns app.common.geom.shapes.bool (:require [app.common.geom.shapes.path :as gsp] + [app.common.geom.shapes.rect :as gpr] + [app.common.geom.shapes.transforms :as gtr] [app.common.path.bool :as pb] [app.common.path.shapes-to-path :as stp])) @@ -19,7 +21,12 @@ (mapv :content) (pb/content-bool (:bool-type shape))) - [points selrect] (gsp/content->points+selrect shape content)] + [points selrect] + (if (empty? content) + (let [selrect (gtr/selection-rect children) + points (gpr/rect->points selrect)] + [points selrect]) + (gsp/content->points+selrect shape content))] (-> shape (assoc :selrect selrect) (assoc :points points)))) diff --git a/common/src/app/common/geom/shapes/path.cljc b/common/src/app/common/geom/shapes/path.cljc index a4be29985..b50a4ba76 100644 --- a/common/src/app/common/geom/shapes/path.cljc +++ b/common/src/app/common/geom/shapes/path.cljc @@ -12,7 +12,8 @@ [app.common.geom.shapes.common :as gsc] [app.common.geom.shapes.rect :as gpr] [app.common.math :as mth] - [app.common.path.commands :as upc])) + [app.common.path.commands :as upc] + [app.common.path.subpaths :as sp])) (def ^:const curve-curve-precision 0.1) (def ^:const curve-range-precision 2) @@ -818,19 +819,33 @@ (defn is-point-in-content? [point content] + (let [selrect (content->selrect content) + ray-line [point (gpt/point (inc (:x point)) (:y point))] - (letfn [(cast-ray [cmd] - (let [ray-line [point (gpt/point (inc (:x point)) (:y point))]] - (case (:command cmd) - :line-to (ray-line-intersect point (command->line cmd)) - :curve-to (ray-curve-intersect ray-line (command->bezier cmd)) - #_:else [])))] + closed-subpaths + (->> content + (sp/close-subpaths) + (sp/get-subpaths) + (filterv sp/is-closed?)) - (->> content - (mapcat cast-ray) - (map second) - (reduce +) - (not= 0)))) + cast-ray + (fn [cmd] + (case (:command cmd) + :line-to (ray-line-intersect point (command->line cmd)) + :curve-to (ray-curve-intersect ray-line (command->bezier cmd)) + #_:else [])) + + is-point-in-subpath? + (fn [subpath] + (and (gpr/contains-point? (content->selrect (:data subpath)) point) + (->> (:data subpath) + (mapcat cast-ray) + (map second) + (reduce +) + (not= 0))))] + + (and (gpr/contains-point? selrect point) + (some is-point-in-subpath? closed-subpaths)))) (defn split-line-to "Given a point and a line-to command will create a two new line-to commands diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc index f4a0bd043..047781a70 100644 --- a/common/src/app/common/geom/shapes/rect.cljc +++ b/common/src/app/common/geom/shapes/rect.cljc @@ -48,6 +48,16 @@ (defn rect->selrect [rect] (-> rect rect->points points->selrect)) +(defn join-rects [rects] + (let [minx (transduce (comp (map :x) (remove nil?)) min ##Inf rects) + miny (transduce (comp (map :y) (remove nil?)) min ##Inf rects) + maxx (transduce (comp (map #(+ (:x %) (:width %))) (remove nil?)) max ##-Inf rects) + maxy (transduce (comp (map #(+ (:y %) (:height %))) (remove nil?)) max ##-Inf rects)] + {:x minx + :y miny + :width (- maxx minx) + :height (- maxy miny)})) + (defn join-selrects [selrects] (let [minx (transduce (comp (map :x1) (remove nil?)) min ##Inf selrects) miny (transduce (comp (map :y1) (remove nil?)) min ##Inf selrects) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 7f9bfa7a5..56e6534e1 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -18,7 +18,6 @@ [app.common.spec :as us] [app.common.text :as txt])) - ;; --- Relative Movement (defn- move-selrect [selrect {dx :x dy :y}] @@ -681,3 +680,12 @@ (assoc :resize-transform (:resize-transform parent-modifiers) :resize-transform-inverse (:resize-transform-inverse parent-modifiers))))) + +(defn selection-rect + "Returns a rect that contains all the shapes and is aware of the + rotation of each shape. Mainly used for multiple selection." + [shapes] + (->> shapes + (transform-shape) + (map (comp gpr/points->selrect :points)) + (gpr/join-selrects))) diff --git a/common/src/app/common/path/subpaths.cljc b/common/src/app/common/path/subpaths.cljc index ca92a6c30..7dc19720d 100644 --- a/common/src/app/common/path/subpaths.cljc +++ b/common/src/app/common/path/subpaths.cljc @@ -116,6 +116,9 @@ (->> subpaths (reduce merge-with-candidate [candidate []])))) +(defn is-closed? [subpath] + (pt= (:from subpath) (:to subpath))) + (defn close-subpaths "Searches a path for posible supaths that can create closed loops and merge them" [content] @@ -127,7 +130,7 @@ (if (some? current) (let [[new-current new-subpaths] - (if (pt= (:from current) (:to current)) + (if (is-closed? current) [current subpaths] (merge-paths current subpaths))] diff --git a/exporter/src/app/renderer/svg.cljs b/exporter/src/app/renderer/svg.cljs index 93e0649f4..54544aa24 100644 --- a/exporter/src/app/renderer/svg.cljs +++ b/exporter/src/app/renderer/svg.cljs @@ -210,11 +210,10 @@ :height (.. attrs -height -value) :colors (.split colors ",")})) - (extract-single-node [node] + (extract-single-node [[shot node]] (log/trace :fn :extract-single-node) - (p/let [attrs (bw/eval! node extract-element-attrs) - shot (bw/screenshot node {:omit-background? true :type "png"})] + (p/let [attrs (bw/eval! node extract-element-attrs)] {:id (unchecked-get attrs "id") :x (unchecked-get attrs "x") :y (unchecked-get attrs "y") @@ -223,13 +222,21 @@ :colors (vec (unchecked-get attrs "colors")) :data shot})) + (resolve-text-node [page node] + (p/let [attrs (bw/eval! node extract-element-attrs) + id (unchecked-get attrs "id") + text-node (bw/select page (str "#screenshot-text-" id " foreignObject")) + shot (bw/screenshot text-node {:omit-background? true :type "png"})] + [shot node])) + (clean-temp-data [{:keys [tempdir] :as node}] (p/do! (sh/rmdir! tempdir) (dissoc node :tempdir))) - (process-text-node [item] + (process-text-node [page item] (-> (p/resolved item) + (p/then (partial resolve-text-node page)) (p/then extract-single-node) (p/then trace-node) (p/then clean-temp-data))) @@ -237,7 +244,7 @@ (process-text-nodes [page] (log/trace :fn :process-text-nodes) (-> (bw/select-all page "#screenshot foreignObject") - (p/then (fn [nodes] (p/all (map process-text-node nodes)))))) + (p/then (fn [nodes] (p/all (map (partial process-text-node page) nodes)))))) (extract-svg [page] (p/let [dom (bw/select page "#screenshot") @@ -271,7 +278,7 @@ (p/let [page (render-in-page page rctx)] (extract-svg page)))] - (let [path (str "/render-object/" file-id "/" page-id "/" object-id) + (let [path (str "/render-object/" file-id "/" page-id "/" object-id "?render-texts=true") uri (-> (u/uri (cf/get :public-uri)) (assoc :path "/") (assoc :fragment path)) diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index a659d469c..b3f555d76 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -874,7 +874,6 @@ position: relative; top: 2px; width: 100%; - z-index: 20; } .btn-options { @@ -1539,7 +1538,8 @@ right: 5px; top: 30px; z-index: 12; - min-width: 200px; + width: 200px; + height: 320px; position: fixed; & li.separator { diff --git a/frontend/resources/styles/main/partials/viewer-header.scss b/frontend/resources/styles/main/partials/viewer-header.scss index 00012e383..ff7808da9 100644 --- a/frontend/resources/styles/main/partials/viewer-header.scss +++ b/frontend/resources/styles/main/partials/viewer-header.scss @@ -57,13 +57,6 @@ flex-shrink: 0; } - .zoom-widget { - .dropdown { - top: 45px; - left: 25px; - } - } - .view-options { align-items: center; cursor: pointer; diff --git a/frontend/src/app/main/data/comments.cljs b/frontend/src/app/main/data/comments.cljs index 95387f5ec..e3f26495e 100644 --- a/frontend/src/app/main/data/comments.cljs +++ b/frontend/src/app/main/data/comments.cljs @@ -77,7 +77,8 @@ (watch [_ _ _] (->> (rp/mutation :create-comment-thread params) (rx/mapcat #(rp/query :comment-thread {:file-id (:file-id %) :id (:id %)})) - (rx/map #(partial created %))))))) + (rx/map #(partial created %)) + (rx/catch #(rx/throw {:type :comment-error}))))))) (defn update-comment-thread-status [{:keys [id] :as thread}] @@ -87,7 +88,8 @@ (watch [_ _ _] (let [done #(d/update-in-when % [:comment-threads id] assoc :count-unread-comments 0)] (->> (rp/mutation :update-comment-thread-status {:id id}) - (rx/map (constantly done))))))) + (rx/map (constantly done)) + (rx/catch #(rx/throw {:type :comment-error}))))))) (defn update-comment-thread @@ -104,6 +106,7 @@ ptk/WatchEvent (watch [_ _ _] (->> (rp/mutation :update-comment-thread {:id id :is-resolved is-resolved}) + (rx/catch #(rx/throw {:type :comment-error})) (rx/ignore))))) @@ -118,7 +121,8 @@ (watch [_ _ _] (rx/concat (->> (rp/mutation :add-comment {:thread-id (:id thread) :content content}) - (rx/map #(partial created %))) + (rx/map #(partial created %)) + (rx/catch #(rx/throw {:type :comment-error}))) (rx/of (refresh-comment-thread thread))))))) (defn update-comment @@ -132,6 +136,7 @@ ptk/WatchEvent (watch [_ _ _] (->> (rp/mutation :update-comment {:id id :content content}) + (rx/catch #(rx/throw {:type :comment-error})) (rx/ignore))))) (defn delete-comment-thread @@ -147,6 +152,7 @@ ptk/WatchEvent (watch [_ _ _] (->> (rp/mutation :delete-comment-thread {:id id}) + (rx/catch #(rx/throw {:type :comment-error})) (rx/ignore))))) (defn delete-comment @@ -160,6 +166,7 @@ ptk/WatchEvent (watch [_ _ _] (->> (rp/mutation :delete-comment {:id id}) + (rx/catch #(rx/throw {:type :comment-error})) (rx/ignore))))) (defn refresh-comment-thread @@ -171,7 +178,8 @@ ptk/WatchEvent (watch [_ _ _] (->> (rp/query :comment-thread {:file-id file-id :id id}) - (rx/map #(partial fetched %))))))) + (rx/map #(partial fetched %)) + (rx/catch #(rx/throw {:type :comment-error}))))))) (defn retrieve-comment-threads [file-id] @@ -182,7 +190,8 @@ ptk/WatchEvent (watch [_ _ _] (->> (rp/query :comment-threads {:file-id file-id}) - (rx/map #(partial fetched %))))))) + (rx/map #(partial fetched %)) + (rx/catch #(rx/throw {:type :comment-error}))))))) (defn retrieve-comments [thread-id] @@ -193,7 +202,8 @@ ptk/WatchEvent (watch [_ _ _] (->> (rp/query :comments {:thread-id thread-id}) - (rx/map #(partial fetched %))))))) + (rx/map #(partial fetched %)) + (rx/catch #(rx/throw {:type :comment-error}))))))) (defn retrieve-unread-comment-threads "A event used mainly in dashboard for retrieve all unread threads of a team." @@ -204,7 +214,8 @@ (watch [_ _ _] (let [fetched #(assoc %2 :comment-threads (d/index-by :id %1))] (->> (rp/query :unread-comment-threads {:team-id team-id}) - (rx/map #(partial fetched %))))))) + (rx/map #(partial fetched %)) + (rx/catch #(rx/throw {:type :comment-error}))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 449bc5956..23629fb39 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1324,10 +1324,33 @@ (ptk/reify ::show-context-menu ptk/UpdateEvent (update [_ state] - (let [mdata (cond-> params - (some? shape) - (assoc :selected - (wsh/lookup-selected state)))] + (let [selected (wsh/lookup-selected state) + objects (wsh/lookup-page-objects state) + + selected-with-children + (into [] + (mapcat #(cp/get-object-with-children % objects)) + selected) + + head (get objects (first selected)) + + first-not-group-like? + (and (= (count selected) 1) + (not (contains? #{:group :bool} (:type head)))) + + has-invalid-shapes? (->> selected-with-children + (some (comp #{:frame :text} :type))) + + disable-booleans? (or (empty? selected) has-invalid-shapes? first-not-group-like?) + disable-flatten? (or (empty? selected) has-invalid-shapes?) + + mdata + (-> params + (assoc :disable-booleans? disable-booleans?) + (assoc :disable-flatten? disable-flatten?) + (cond-> (some? shape) + (assoc :selected selected)))] + (assoc-in state [:workspace-local :context-menu] mdata))))) (defn show-shape-context-menu diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 4e4e0444b..9ba3a9a79 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -19,6 +19,7 @@ [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.libraries-helpers :as dwlh] [app.main.data.workspace.state-helpers :as wsh] + [app.main.data.workspace.undo :as dwu] [app.main.repo :as rp] [app.main.store :as st] [app.util.i18n :refer [tr]] @@ -134,10 +135,12 @@ :color color} uchg {:type :mod-color :color prev}] - (rx/of (dch/commit-changes {:redo-changes [rchg] + (rx/of (dwu/start-undo-transaction) + (dch/commit-changes {:redo-changes [rchg] :undo-changes [uchg] :origin it}) - (sync-file (:current-file-id state) file-id)))))) + (sync-file (:current-file-id state) file-id) + (dwu/commit-undo-transaction)))))) (defn delete-color [{:keys [id] :as params}] @@ -244,10 +247,12 @@ :typography typography} uchg {:type :mod-typography :typography prev}] - (rx/of (dch/commit-changes {:redo-changes [rchg] + (rx/of (dwu/start-undo-transaction) + (dch/commit-changes {:redo-changes [rchg] :undo-changes [uchg] :origin it}) - (sync-file (:current-file-id state) file-id)))))) + (sync-file (:current-file-id state) file-id) + (dwu/commit-undo-transaction)))))) (defn delete-typography [id] diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 55ea9aac8..53127984d 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -420,13 +420,13 @@ (gpt/point (+ (:width obj) 50) 0) (gpt/point 0 0)) - (let [obj-original (get objects id-original) - obj-duplicated (get objects id-duplicated) - distance (gpt/subtract (gpt/point obj-duplicated) - (gpt/point obj-original)) - new-pos (gpt/add (gpt/point obj-duplicated) distance) - delta (gpt/subtract new-pos (gpt/point obj))] - delta)))) + (let [pt-original (-> (get objects id-original) :selrect gpt/point) + pt-duplicated (-> (get objects id-duplicated) :selrect gpt/point) + pt-obj (-> obj :selrect gpt/point) + distance (gpt/subtract pt-duplicated pt-original) + new-pos (gpt/add pt-duplicated distance)] + + (gpt/subtract new-pos pt-obj))))) (defn duplicate-selected [move-delta?] (ptk/reify ::duplicate-selected diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 841bf0181..53e123c16 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -180,12 +180,10 @@ shape (get objects id) merge-fn (fn [node attrs] - (reduce-kv (fn [node k v] - (if (= (get node k) v) - (dissoc node k) - (assoc node k v))) - node - attrs)) + (reduce-kv + (fn [node k v] (assoc node k v)) + node + attrs)) update-fn #(update-shape % txt/is-paragraph-node? merge-fn attrs) shape-ids (cond (= (:type shape) :text) [id] diff --git a/frontend/src/app/main/errors.cljs b/frontend/src/app/main/errors.cljs index 316a9e16c..39350c180 100644 --- a/frontend/src/app/main/errors.cljs +++ b/frontend/src/app/main/errors.cljs @@ -94,6 +94,14 @@ :type :error :timeout 3000})))) +(defmethod ptk/handle-error :comment-error + [_] + (ts/schedule + (st/emitf + (dm/show {:content "There was an error with the comment" + :type :error + :timeout 3000})))) + ;; This is a pure frontend error that can be caused by an active ;; assertion (assertion that is preserved on production builds). From ;; the user perspective this should be treated as internal error. diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index 9a939bd49..bd75ba4f7 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -152,12 +152,14 @@ :render-object (do - (let [file-id (uuid (get-in route [:path-params :file-id])) - page-id (uuid (get-in route [:path-params :page-id])) - object-id (uuid (get-in route [:path-params :object-id]))] + (let [file-id (uuid (get-in route [:path-params :file-id])) + page-id (uuid (get-in route [:path-params :page-id])) + object-id (uuid (get-in route [:path-params :object-id])) + render-texts (get-in route [:query-params :render-texts])] [:& render/render-object {:file-id file-id :page-id page-id - :object-id object-id}])) + :object-id object-id + :render-texts? (and (some? render-texts) (= render-texts "true"))}])) :render-sprite (do diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs index f10181e77..71cc32eeb 100644 --- a/frontend/src/app/main/ui/auth/register.cljs +++ b/frontend/src/app/main/ui/auth/register.cljs @@ -210,8 +210,13 @@ [:div.fields-row [:& fm/input {:name :accept-terms-and-privacy :class "check-primary" - :label (tr "auth.terms-privacy-agreement") - :type "checkbox"}]] + :type "checkbox"} + [:span + (tr "auth.terms-privacy-agreement") + [:div + [:a {:href "https://penpot.app/terms.html" :target "_blank"} (tr "auth.terms-of-service")] + [:span ",\u00A0"] + [:a {:href "https://penpot.app/privacy.html" :target "_blank"} (tr "auth.privacy-policy")]]]]] ;; (when (contains? @cf/flags :newsletter-registration-check) ;; [:div.fields-row diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index 3ae683238..f65abba07 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -302,21 +302,22 @@ (when-let [node (mf/ref-val ref)] (.scrollIntoViewIfNeeded ^js node)))) - [:div.thread-content - {:style {:top (str pos-y "px") - :left (str pos-x "px")} - :on-click dom/stop-propagation} + (when (some? comment) + [:div.thread-content + {:style {:top (str pos-y "px") + :left (str pos-x "px")} + :on-click dom/stop-propagation} - [:div.comments - [:& comment-item {:comment comment - :users users - :thread thread}] - (for [item (rest comments)] - [:* - [:hr] - [:& comment-item {:comment item :users users}]]) - [:div {:ref ref}]] - [:& reply-form {:thread thread}]])) + [:div.comments + [:& comment-item {:comment comment + :users users + :thread thread}] + (for [item (rest comments)] + [:* + [:hr] + [:& comment-item {:comment item :users users}]]) + [:div {:ref ref}]] + [:& reply-form {:thread thread}]]))) (mf/defc thread-bubble {::mf/wrap [mf/memo]} diff --git a/frontend/src/app/main/ui/components/forms.cljs b/frontend/src/app/main/ui/components/forms.cljs index 2827f5a6e..fd15aa757 100644 --- a/frontend/src/app/main/ui/components/forms.cljs +++ b/frontend/src/app/main/ui/components/forms.cljs @@ -19,7 +19,7 @@ (def use-form fm/use-form) (mf/defc input - [{:keys [label help-icon disabled form hint trim] :as props}] + [{:keys [label help-icon disabled form hint trim children] :as props}] (let [input-type (get props :type "text") input-name (get props :name) more-classes (get props :class) @@ -82,7 +82,7 @@ (swap! form assoc-in [:touched input-name] true))) props (-> props - (dissoc :help-icon :form :trim) + (dissoc :help-icon :form :trim :children) (assoc :id (name input-name) :value value :auto-focus auto-focus? @@ -97,7 +97,13 @@ {:class klass} [:* [:> :input props] - [:label {:for (name input-name)} label] + (cond + (some? label) + [:label {:for (name input-name)} label] + + (some? children) + [:label {:for (name input-name)} children]) + (when help-icon' [:div.help-icon {:style {:cursor "pointer"} diff --git a/frontend/src/app/main/ui/render.cljs b/frontend/src/app/main/ui/render.cljs index 9cea9c60a..9e550db3e 100644 --- a/frontend/src/app/main/ui/render.cljs +++ b/frontend/src/app/main/ui/render.cljs @@ -25,9 +25,26 @@ [cuerdas.core :as str] [rumext.alpha :as mf])) +(defn bounds + [object objects] + (if (= :group (:type object)) + (let [children-bounds + (into [] + (comp (map #(get objects %)) + (map #(bounds % objects))) + (:shapes object))] + (gsh/join-rects children-bounds)) + + (let [padding (filters/calculate-padding object)] + (-> (filters/get-filters-bounds object) + (update :x - padding) + (update :y - padding) + (update :width + (* 2 padding)) + (update :height + (* 2 padding)))))) + (mf/defc object-svg {::mf/wrap [mf/memo]} - [{:keys [objects object-id zoom] :or {zoom 1} :as props}] + [{:keys [objects object-id zoom render-texts?] :or {zoom 1} :as props}] (let [object (get objects object-id) frame-id (if (= :frame (:type object)) (:id object) @@ -47,20 +64,10 @@ objects (reduce updt-fn objects mod-ids) object (get objects object-id) - ;; We need to get the shadows/blurs paddings to create the viewbox properly - {:keys [x y width height]} (filters/get-filters-bounds object) + {:keys [x y width height] :as bs} (bounds object objects) + [_ _ width height :as coords] (->> [x y width height] (map #(* % zoom))) - x (* x zoom) - y (* y zoom) - width (* width zoom) - height (* height zoom) - - padding (* (filters/calculate-padding object) zoom) - - vbox (str/join " " [(- x padding) - (- y padding) - (+ width padding padding) - (+ height padding padding)]) + vbox (str/join " " coords) frame-wrapper (mf/use-memo @@ -76,18 +83,22 @@ (mf/use-memo (mf/deps objects) #(exports/shape-wrapper-factory objects)) - ] + + text-shapes + (->> objects + (filter (fn [[_ shape]] (= :text (:type shape)))) + (mapv second))] (mf/use-effect (mf/deps width height) - #(dom/set-page-style {:size (str (mth/ceil (+ width padding padding)) "px " - (mth/ceil (+ height padding padding)) "px")})) + #(dom/set-page-style {:size (str (mth/ceil width) "px " + (mth/ceil height) "px")})) [:& (mf/provider embed/context) {:value true} [:svg {:id "screenshot" :view-box vbox - :width (+ width padding padding) - :height (+ height padding padding) + :width width + :height height :version "1.1" :xmlns "http://www.w3.org/2000/svg" :xmlnsXlink "http://www.w3.org/1999/xlink" @@ -100,7 +111,19 @@ :frame [:& frame-wrapper {:shape object :view-box vbox}] :group [:> shape-container {:shape object} [:& group-wrapper {:shape object}]] - [:& shape-wrapper {:shape object}])]])) + [:& shape-wrapper {:shape object}])] + + ;; Auxiliary SVG for rendering text-shapes + (when render-texts? + (for [object text-shapes] + [:svg {:id (str "screenshot-text-" (:id object)) + :view-box (str "0 0 " (:width object) " " (:height object)) + :width (:width object) + :height (:height object) + :version "1.1" + :xmlns "http://www.w3.org/2000/svg" + :xmlnsXlink "http://www.w3.org/1999/xlink"} + [:& shape-wrapper {:shape (-> object (assoc :x 0 :y 0))}]]))])) (defn- adapt-root-frame [objects object-id] @@ -120,7 +143,7 @@ ;; backend entry point for download only the data of single page. (mf/defc render-object - [{:keys [file-id page-id object-id] :as props}] + [{:keys [file-id page-id object-id render-texts?] :as props}] (let [objects (mf/use-state nil)] (mf/use-effect (mf/deps file-id page-id object-id) @@ -140,6 +163,7 @@ (when @objects [:& object-svg {:objects @objects :object-id object-id + :render-texts? render-texts? :zoom 1}]))) (mf/defc render-sprite diff --git a/frontend/src/app/main/ui/shapes/custom_stroke.cljs b/frontend/src/app/main/ui/shapes/custom_stroke.cljs index 6a5f92dcb..1ce51046d 100644 --- a/frontend/src/app/main/ui/shapes/custom_stroke.cljs +++ b/frontend/src/app/main/ui/shapes/custom_stroke.cljs @@ -60,8 +60,8 @@ :viewBox "0 0 3 6" :refX "2" :refY "3" - :markerWidth "3" - :markerHeight "6" + :markerWidth "8.5" + :markerHeight "8.5" :orient "auto-start-reverse" :fill stroke-color :fillOpacity stroke-opacity} @@ -72,8 +72,8 @@ :viewBox "0 0 3 6" :refX "2" :refY "3" - :markerWidth "3" - :markerHeight "6" + :markerWidth "8.5" + :markerHeight "8.5" :orient "auto-start-reverse" :fill stroke-color :fillOpacity stroke-opacity} @@ -82,10 +82,10 @@ (when (or (= cap-start :square-marker) (= cap-end :square-marker)) [:marker {:id (str marker-id-prefix "-square-marker") :viewBox "0 0 6 6" - :refX "5" + :refX "3" :refY "3" - :markerWidth "6" - :markerHeight "6" + :markerWidth "4.2426" ;; diagonal length of a 3x3 square + :markerHeight "4.2426" :orient "auto-start-reverse" :fill stroke-color :fillOpacity stroke-opacity} @@ -94,10 +94,10 @@ (when (or (= cap-start :circle-marker) (= cap-end :circle-marker)) [:marker {:id (str marker-id-prefix "-circle-marker") :viewBox "0 0 6 6" - :refX "5" + :refX "3" :refY "3" - :markerWidth "6" - :markerHeight "6" + :markerWidth "4" + :markerHeight "4" :orient "auto-start-reverse" :fill stroke-color :fillOpacity stroke-opacity} @@ -106,7 +106,7 @@ (when (or (= cap-start :diamond-marker) (= cap-end :diamond-marker)) [:marker {:id (str marker-id-prefix "-diamond-marker") :viewBox "0 0 6 6" - :refX "5" + :refX "3" :refY "3" :markerWidth "6" :markerHeight "6" diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index 2dca984ef..1ee53dd4d 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -18,7 +18,8 @@ (let [valign (:vertical-align node "top") width (some-> (:width shape) (+ 1)) base #js {:height (or (:height shape) "100%") - :width (or width "100%")}] + :width (or width "100%") + :fontFamily "sourcesanspro"}] (cond-> base (= valign "top") (obj/set! "justifyContent" "flex-start") (= valign "center") (obj/set! "justifyContent" "center") @@ -40,6 +41,7 @@ :justifyContent "inherit" :minHeight (when-not (or auto-width? auto-height?) "100%") :minWidth (when-not auto-width? "100%") + :marginRight "1px" :verticalAlign "top"})) (defn generate-paragraph-styles diff --git a/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs b/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs index ed86777bd..00a0c7851 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs @@ -27,15 +27,27 @@ :v (mf/use-ref nil) :alpha (mf/use-ref nil)} + setup-hex-color + (fn [hex] + (let [[r g b] (uc/hex->rgb hex) + [h s v] (uc/hex->hsv hex)] + (on-change {:hex hex + :h h :s s :v v + :r r :g g :b b}))) on-change-hex (fn [e] (let [val (-> e dom/get-target-val parse-hex)] (when (uc/hex? val) - (let [[r g b] (uc/hex->rgb val) - [h s v] (uc/hex->hsv hex)] - (on-change {:hex val - :h h :s s :v v - :r r :g g :b b}))))) + (setup-hex-color val)))) + + on-blur-hex + (fn [e] + (let [val (-> e dom/get-target-val) + val (cond + (uc/color? val) (uc/parse-color val) + (uc/hex? (parse-hex val)) (parse-hex val))] + (when (some? val) + (setup-hex-color val)))) on-change-property (fn [property max-value] @@ -81,9 +93,10 @@ [:div.color-values {:class (when disable-opacity "disable-opacity")} [:input {:id "hex-value" - :ref (:hex refs) - :default-value hex - :on-change on-change-hex}] + :ref (:hex refs) + :default-value hex + :on-change on-change-hex + :on-blur on-blur-hex}] (if (= type :rgb) [:* diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 4f35630cc..9cdae0466 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -87,15 +87,15 @@ (mf/defc shape-context-menu [{:keys [mdata] :as props}] - (let [{:keys [id] :as shape} (:shape mdata) - selected (:selected mdata) + (let [{:keys [shape selected disable-booleans? disable-flatten?]} mdata + {:keys [id type]} shape single? (= (count selected) 1) multiple? (> (count selected) 1) - editable-shape? (#{:group :text :path} (:type shape)) + editable-shape? (#{:group :text :path} type) - is-group? (and (some? shape) (= :group (:type shape))) - is-bool? (and (some? shape) (= :bool (:type shape))) + is-group? (and (some? shape) (= :group type)) + is-bool? (and (some? shape) (= :bool type)) options (mf/deref refs/workspace-page-options) flows (:flows options) @@ -235,10 +235,12 @@ :shortcut (sc/get-tooltip :start-editing) :on-click do-start-editing}]) - [:& menu-entry {:title (tr "workspace.shape.menu.transform-to-path") - :on-click do-transform-to-path}] + (when-not disable-flatten? + [:& menu-entry {:title (tr "workspace.shape.menu.transform-to-path") + :on-click do-transform-to-path}]) - (when (or multiple? (and single? (or is-group? is-bool?))) + (when (and (not disable-booleans?) + (or multiple? (and single? (or is-group? is-bool?)))) [:& menu-entry {:title (tr "workspace.shape.menu.path")} [:& menu-entry {:title (tr "workspace.shape.menu.union") :shortcut (sc/get-tooltip :boolean-union) @@ -253,7 +255,7 @@ :shortcut (sc/get-tooltip :boolean-exclude) :on-click (set-bool :exclude)}] - (when (and single? is-bool?) + (when (and single? is-bool? (not disable-flatten?)) [:* [:& menu-separator] [:& menu-entry {:title (tr "workspace.shape.menu.flatten") @@ -279,9 +281,7 @@ [:& menu-entry {:title (tr "workspace.shape.menu.delete-flow-start") :on-click (do-remove-flow flow)}]))) - (when (and (or (nil? (:shape-ref shape)) - (> (count selected) 1)) - (not= (:type shape) :frame)) + (when (not= (:type shape) :frame) [:* [:& menu-separator] [:& menu-entry {:title (tr "workspace.shape.menu.create-component") diff --git a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs index db9d70aad..d00ea37bd 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs @@ -174,7 +174,7 @@ handle-return (mf/use-callback (fn [_ state] - (let [style (ted/get-editor-current-inline-styles state) + (let [style (ted/get-editor-current-block-data state) state (-> (ted/insert-text state "\n" style) (handle-change))] (st/emit! (dwt/update-editor-state shape state))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index c759464b9..16889f759 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -230,7 +230,7 @@ on-fold-group (mf/use-callback - (mf/deps group-open?) + (mf/deps file-id box path group-open?) (fn [event] (dom/stop-propagation event) (st/emit! (dwl/set-assets-group-open file-id diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/booleans.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/booleans.cljs index 64b287417..80b2be852 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/booleans.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/booleans.cljs @@ -17,19 +17,22 @@ (mf/defc booleans-options [] (let [selected (mf/deref refs/selected-objects) + selected-with-children (mf/deref refs/selected-objects-with-children) - disabled-bool-btns - (or (empty? selected) - (and (<= (count selected) 1) - (not (contains? #{:group :bool} (:type (first selected)))))) + has-invalid-shapes? (->> selected-with-children + (some (comp #{:frame :text} :type))) - disabled-flatten - (empty? selected) + first-not-group-like? + (and (= (count selected) 1) + (not (contains? #{:group :bool} (:type (first selected))))) + + disabled-bool-btns (or (empty? selected) has-invalid-shapes? first-not-group-like?) + disabled-flatten (or (empty? selected) has-invalid-shapes?) head (first selected) is-group? (and (some? head) (= :group (:type head))) is-bool? (and (some? head) (= :bool (:type head))) - head-bool-type (and (some? head) (:bool-type head)) + head-bool-type (and (some? head) is-bool? (:bool-type head)) set-bool (fn [bool-type] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs index a5698e4c0..32e592395 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs @@ -73,7 +73,7 @@ :group (tr "workspace.options.group-stroke") (tr "workspace.options.stroke")) - show-options (not= (:stroke-style values :none) :none) + show-options (not= (or (:stroke-style values) :none) :none) show-caps (and show-caps (not (#{:inner :outer} (:stroke-alignment values)))) @@ -141,7 +141,10 @@ target (dom/get-current-target event) rect (dom/get-bounding-rect target) - top (+ (:bottom rect) 5) + top (if (< (+ (:bottom rect) 320) (:height window-size)) + (+ (:bottom rect) 5) + (- (:height window-size) 325)) + left (if (< (+ (:left rect) 200) (:width window-size)) (:left rect) (- (:width window-size) 205))] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs index 45f313c73..b51955e96 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs @@ -74,7 +74,8 @@ (defn filter-fonts [{:keys [term backends]} fonts] - (let [xform (cond-> (map identity) + (let [term (str/lower term) + xform (cond-> (map identity) (seq term) (comp (filter #(str/includes? (str/lower (:name %)) term))) @@ -175,7 +176,7 @@ [:div.font-selector [:div.font-selector-dropdown [:header - [:input {:placeholder "Search font" + [:input {:placeholder (tr "workspace.options.search-font") :value (:term @state) :ref input :spell-check false diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 34c72e50a..53d82208a 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -197,7 +197,7 @@ [:& use/export-page {:options options}] - [:& (mf/provider use/include-metadata-ctx) {:value true} + [:& (mf/provider use/include-metadata-ctx) {:value false} [:& (mf/provider embed/context) {:value true} ;; Render root shape [:& shapes/root-shape {:key page-id diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 66e0c966e..0de44e7a5 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -163,6 +163,12 @@ msgstr "" "When creating a new account, you agree to our terms of service and privacy " "policy." +msgid "auth.terms-of-service" +msgstr "Terms of service" + +msgid "auth.privacy-policy" +msgstr "Privacy policy" + #: src/app/main/ui/auth/register.cljs msgid "auth.verification-email-sent" msgstr "We've sent a verification email to" @@ -3177,3 +3183,6 @@ msgstr "Flatten" msgid "workspace.shape.menu.transform-to-path" msgstr "Transform to path" + +msgid "workspace.options.search-font" +msgstr "Search font" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index a0a538317..873264608 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -167,6 +167,12 @@ msgstr "" "Al crear una nueva cuenta, aceptas nuestros términos de servicio y política " "de privacidad." +msgid "auth.terms-of-service" +msgstr "Terminos de servicio" + +msgid "auth.privacy-policy" +msgstr "Política de privacidad" + #: src/app/main/ui/auth/register.cljs msgid "auth.verification-email-sent" msgstr "Hemos enviado un email de verificación a" @@ -3065,3 +3071,6 @@ msgstr "Aplanar" msgid "workspace.shape.menu.transform-to-path" msgstr "Convertir en vector" + +msgid "workspace.options.search-font" +msgstr "Buscar fuente" diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po index b19d285e7..309267713 100644 --- a/frontend/translations/fr.po +++ b/frontend/translations/fr.po @@ -161,6 +161,12 @@ msgstr "" "En créant un compte, vous acceptez nos conditions générales d'utilisation " "et notre politique de confidentialité." +msgid "auth.terms-of-service" +msgstr "Conditions générales d'utilisation" + +msgid "auth.privacy-policy" +msgstr "Politique de confidentialité" + #: src/app/main/ui/auth/register.cljs msgid "auth.verification-email-sent" msgstr "Nous avons envoyé un e-mail de vérification à"