From 477f5536755f060fa48c8f4096a94cdd6226c9e3 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 3 Jan 2022 13:34:46 +0100 Subject: [PATCH 01/10] :bug: Fix problem with booleans --- CHANGES.md | 1 + common/src/app/common/path/bool.cljc | 60 ++++++++++++++++------------ 2 files changed, 35 insertions(+), 26 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 54cc0c325e..9aeeca913a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -32,6 +32,7 @@ - After team onboarding importing a file will import into the team drafts [Taiga #2408](https://tree.taiga.io/project/penpot/issue/2408) - Fix problem exporting shapes from handoff mode [Taiga #2386](https://tree.taiga.io/project/penpot/issue/2386) - Fix lock/hide elements in context menu when multiples shapes selected [Taiga #2340](https://tree.taiga.io/project/penpot/issue/2340) +- Fix problem with booleans [Taiga #2356](https://tree.taiga.io/project/penpot/issue/2356) ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) diff --git a/common/src/app/common/path/bool.cljc b/common/src/app/common/path/bool.cljc index a04e65bbbc..cda5e77dc2 100644 --- a/common/src/app/common/path/bool.cljc +++ b/common/src/app/common/path/bool.cljc @@ -212,6 +212,27 @@ (d/seek overlap-single?) (some?)))) +(defn fix-move-to + [content] + ;; Remove the field `:prev` and makes the necessaries `move-to` + ;; then clean the subpaths + + (loop [current (first content) + content (rest content) + prev nil + result []] + + (if (nil? current) + result + + (let [result (if (not= (:prev current) prev) + (conj result (upc/make-move-to (:prev current))) + result)] + (recur (first content) + (rest content) + (gsp/command->point current) + (conj result (dissoc current :prev))))))) + (defn create-union [content-a content-a-split content-b content-b-split sr-a sr-b] ;; Pick all segments in content-a that are not inside content-b ;; Pick all segments in content-b that are not inside content-a @@ -225,7 +246,7 @@ content-geom (gsp/content->geom-data content) - content-sr (gsp/content->selrect content) + content-sr (gsp/content->selrect (fix-move-to content)) ;; Overlapping segments should be added when they are part of the border border-content @@ -263,38 +284,25 @@ (defn create-exclusion [content-a content-b] ;; Pick all segments - (d/concat-vec content-a content-b)) - -(defn fix-move-to - [content] - ;; Remove the field `:prev` and makes the necessaries `move-to` - ;; then clean the subpaths - - (loop [current (first content) - content (rest content) - prev nil - result []] - - (if (nil? current) - result - - (let [result (if (not= (:prev current) prev) - (conj result (upc/make-move-to (:prev current))) - result)] - (recur (first content) - (rest content) - (gsp/command->point current) - (conj result (dissoc current :prev))))))) + (let [] + (d/concat-vec content-a content-b))) (defn content-bool-pair [bool-type content-a content-b] - (let [content-a (-> content-a (close-paths) (add-previous)) + (let [;; We need to reverse the second path when making a difference/intersection/exclude + ;; and both shapes are in the same direction + should-reverse? (and (not= :union bool-type) + (= (ups/clockwise? content-b) + (ups/clockwise? content-a))) + + content-a (-> content-a + (close-paths) + (add-previous)) content-b (-> content-b (close-paths) - (cond-> (ups/clockwise? content-b) - (ups/reverse-content)) + (cond-> should-reverse? (ups/reverse-content)) (add-previous)) sr-a (gsp/content->selrect content-a) From 6354883a6f7851e8bb6ea2c243f862a5de79630c Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 3 Jan 2022 13:43:02 +0100 Subject: [PATCH 02/10] :bug: Fix line-height/letter-spacing inputs behaviour --- CHANGES.md | 1 + common/src/app/common/path/bool.cljc | 3 +- .../app/main/ui/components/numeric_input.cljs | 54 +++++++++++++------ .../sidebar/options/menus/typography.cljs | 26 ++++----- 4 files changed, 54 insertions(+), 30 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9aeeca913a..571f08c556 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -33,6 +33,7 @@ - Fix problem exporting shapes from handoff mode [Taiga #2386](https://tree.taiga.io/project/penpot/issue/2386) - Fix lock/hide elements in context menu when multiples shapes selected [Taiga #2340](https://tree.taiga.io/project/penpot/issue/2340) - Fix problem with booleans [Taiga #2356](https://tree.taiga.io/project/penpot/issue/2356) +- Fix line-height/letter-spacing inputs behaviour [Taiga #2331](https://tree.taiga.io/project/penpot/issue/2331) ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) diff --git a/common/src/app/common/path/bool.cljc b/common/src/app/common/path/bool.cljc index cda5e77dc2..8b6a66cf5a 100644 --- a/common/src/app/common/path/bool.cljc +++ b/common/src/app/common/path/bool.cljc @@ -284,8 +284,7 @@ (defn create-exclusion [content-a content-b] ;; Pick all segments - (let [] - (d/concat-vec content-a content-b))) + (d/concat-vec content-a content-b)) (defn content-bool-pair [bool-type content-a content-b] diff --git a/frontend/src/app/main/ui/components/numeric_input.cljs b/frontend/src/app/main/ui/components/numeric_input.cljs index 5f5158aaf6..04483405fe 100644 --- a/frontend/src/app/main/ui/components/numeric_input.cljs +++ b/frontend/src/app/main/ui/components/numeric_input.cljs @@ -20,17 +20,26 @@ (not (math/nan? val)) (math/finite? val))) +(defn fixed [value precision] + (try + (.toFixed value precision) + (catch :default _ + (str value)))) + (mf/defc numeric-input {::mf/wrap-props false ::mf/forward-ref true} [props external-ref] - (let [value-str (obj/get props "value") - min-val-str (obj/get props "min") - max-val-str (obj/get props "max") - wrap-value? (obj/get props "data-wrap") - on-change (obj/get props "onChange") - title (obj/get props "title") - default-val (obj/get props "default" 0) + (let [value-str (obj/get props "value") + min-val-str (obj/get props "min") + max-val-str (obj/get props "max") + step-val-str (obj/get props "step") + wrap-value? (obj/get props "data-wrap") + on-change (obj/get props "onChange") + on-blur (obj/get props "onBlur") + title (obj/get props "title") + default-val (obj/get props "default" 0) + precision (obj/get props "precision") ;; We need a ref pointing to the input dom element, but the user ;; of this component may provide one (that is forwarded here). @@ -56,6 +65,15 @@ (string? max-val-str) (d/parse-integer max-val-str)) + step-val (cond + (number? step-val-str) + step-val-str + + (string? step-val-str) + (d/parse-integer step-val-str) + + :else 1) + parse-value (mf/use-callback (mf/deps ref min-val max-val value) @@ -65,7 +83,10 @@ (sm/expr-eval value))] (when (num? new-value) (-> new-value - (math/round) + (cond-> (number? precision) + (math/precision precision)) + (cond-> (nil? precision) + (math/round)) (cljs.core/max us/min-safe-int) (cljs.core/min us/max-safe-int) (cond-> @@ -80,7 +101,9 @@ (mf/deps ref) (fn [new-value] (let [input-node (mf/ref-val ref)] - (dom/set-value! input-node (str new-value))))) + (dom/set-value! input-node (if (some? precision) + (fixed new-value precision) + (str new-value)))))) apply-value (mf/use-callback @@ -97,18 +120,18 @@ (let [current-value (parse-value)] (when current-value (let [increment (if (kbd/shift? event) - (if up? 10 -10) - (if up? 1 -1)) + (if up? (* step-val 10) (* step-val -10)) + (if up? step-val (- step-val))) new-value (+ current-value increment) new-value (cond (and wrap-value? (num? max-val) (num? min-val) (> new-value max-val) up?) - (-> new-value (- max-val) (+ min-val) (- 1)) + (-> new-value (- max-val) (+ min-val) (- step-val)) (and wrap-value? (num? min-val) (num? max-val) (< new-value min-val) down?) - (-> new-value (- min-val) (+ max-val) (+ 1)) + (-> new-value (- min-val) (+ max-val) (+ step-val)) (and (num? min-val) (< new-value min-val)) min-val @@ -144,12 +167,13 @@ handle-blur (mf/use-callback - (mf/deps parse-value apply-value update-input) + (mf/deps parse-value apply-value update-input on-blur) (fn [_] (let [new-value (or (parse-value) default-val)] (if new-value (apply-value new-value) - (update-input new-value))))) + (update-input new-value))) + (when on-blur (on-blur)))) props (-> props (obj/without ["value" "onChange"]) 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 39551e41da..d5bf868b5b 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 @@ -15,6 +15,7 @@ [app.main.fonts :as fonts] [app.main.store :as st] [app.main.ui.components.editable-select :refer [editable-select]] + [app.main.ui.components.numeric-input :refer [numeric-input]] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.common :refer [advanced-options]] [app.util.dom :as dom] @@ -350,20 +351,19 @@ letter-spacing (or letter-spacing "0") handle-change - (fn [event attr] - (let [new-spacing (dom/get-target-val event)] - (on-change {attr new-spacing})))] + (fn [value attr] + (on-change {attr (str value)}))] [:div.spacing-options [:div.input-icon [:span.icon-before.tooltip.tooltip-bottom {:alt (tr "workspace.options.text-options.line-height")} i/line-height] - [:input.input-text - {:type "number" - :step "0.1" - :min "-200" - :max "200" + [:> numeric-input + {:min -200 + :max 200 + :step 0.1 + :precision 2 :value (attr->string line-height) :placeholder (tr "settings.multiple") :on-change #(handle-change % :line-height) @@ -373,11 +373,11 @@ [:span.icon-before.tooltip.tooltip-bottom {:alt (tr "workspace.options.text-options.letter-spacing")} i/letter-spacing] - [:input.input-text - {:type "number" - :step "0.1" - :min "-200" - :max "200" + [:> numeric-input + {:min -200 + :max 200 + :step 0.1 + :precision 2 :value (attr->string letter-spacing) :placeholder (tr "settings.multiple") :on-change #(handle-change % :letter-spacing) From 6334520c66426c5ee5d35688e7f4a0edbb9d8b42 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 3 Jan 2022 14:44:26 +0100 Subject: [PATCH 03/10] :bug: Fix dotted style in strokes --- CHANGES.md | 1 + frontend/src/app/main/ui/shapes/attrs.cljs | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 571f08c556..dcba356abc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -34,6 +34,7 @@ - Fix lock/hide elements in context menu when multiples shapes selected [Taiga #2340](https://tree.taiga.io/project/penpot/issue/2340) - Fix problem with booleans [Taiga #2356](https://tree.taiga.io/project/penpot/issue/2356) - Fix line-height/letter-spacing inputs behaviour [Taiga #2331](https://tree.taiga.io/project/penpot/issue/2331) +- Fix dotted style in strokes [Taiga #2312](https://tree.taiga.io/project/penpot/issue/2312) ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) diff --git a/frontend/src/app/main/ui/shapes/attrs.cljs b/frontend/src/app/main/ui/shapes/attrs.cljs index 9cf576dede..8c72f72943 100644 --- a/frontend/src/app/main/ui/shapes/attrs.cljs +++ b/frontend/src/app/main/ui/shapes/attrs.cljs @@ -18,7 +18,8 @@ [width style] (let [values (case style :mixed [5 5 1 5] - :dotted [5 5] + ;; We want 0 so they are circles + :dotted [(- width) 5] :dashed [10 10] nil)] @@ -132,9 +133,13 @@ ;; for inner or outer strokes. (and (spec/stroke-caps-line (:stroke-cap-start shape)) (= (:stroke-cap-start shape) (:stroke-cap-end shape)) - (not (#{:inner :outer} (:stroke-alignment shape)))) + (not (#{:inner :outer} (:stroke-alignment shape))) + (not= :dotted stroke-style)) (assoc :strokeLinecap (:stroke-cap-start shape)) + (= :dotted stroke-style) + (assoc :strokeLinecap "round") + ;; For other cap types we use markers. (and (or (spec/stroke-caps-marker (:stroke-cap-start shape)) (and (spec/stroke-caps-line (:stroke-cap-start shape)) From 51ea354bcb1b30171be42d84a4c270321deb185e Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 4 Jan 2022 14:19:21 +0100 Subject: [PATCH 04/10] :bug: Fix problem when resizing texts inside groups --- CHANGES.md | 1 + .../app/common/geom/shapes/transforms.cljc | 6 +- .../src/app/main/ui/shapes/text/styles.cljs | 7 +- .../app/main/ui/workspace/viewport/utils.cljs | 98 ++++-- frontend/src/app/util/dom.cljs | 288 +++++++++++------- 5 files changed, 250 insertions(+), 150 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index dcba356abc..9c621028cd 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -35,6 +35,7 @@ - Fix problem with booleans [Taiga #2356](https://tree.taiga.io/project/penpot/issue/2356) - Fix line-height/letter-spacing inputs behaviour [Taiga #2331](https://tree.taiga.io/project/penpot/issue/2331) - Fix dotted style in strokes [Taiga #2312](https://tree.taiga.io/project/penpot/issue/2312) +- Fix problem when resizing texts inside groups [Taiga #2310](https://tree.taiga.io/project/penpot/issue/2310) ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 44cc73b063..7ff2aec822 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -451,9 +451,6 @@ rt-modif (:rotation modifiers)] (cond-> (gmt/matrix) - (some? displacement) - (gmt/multiply displacement) - (some? resize-1) (-> (gmt/translate origin-1) (gmt/multiply resize-transform) @@ -468,6 +465,9 @@ (gmt/multiply resize-transform-inverse) (gmt/translate (gpt/negate origin-2))) + (some? displacement) + (gmt/multiply displacement) + (some? rt-modif) (-> (gmt/translate center) (gmt/multiply (gmt/rotate-matrix rt-modif)) diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index 263424b68a..fe7efceefb 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -14,11 +14,10 @@ [cuerdas.core :as str])) (defn generate-root-styles - [shape node] + [_shape node] (let [valign (:vertical-align node "top") - width (some-> (:width shape) (+ 1)) - base #js {:height (or (:height shape) "100%") - :width (or width "100%") + base #js {:height "100%" + :width "100%" :fontFamily "sourcesanspro"}] (cond-> base (= valign "top") (obj/set! "justifyContent" "flex-start") diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs index 67621fa360..fe92fa46f4 100644 --- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -16,30 +16,54 @@ (defn- text-corrected-transform "If we apply a scale directly to the texts it will show deformed so we need to create this correction matrix to \"undo\" the resize but keep the other transformations." - [{:keys [points transform transform-inverse]} current-transform modifiers] + [{:keys [x y width height points transform transform-inverse] :as shape} current-transform modifiers] (let [corner-pt (first points) - transform (or transform (gmt/matrix)) - transform-inverse (or transform-inverse (gmt/matrix)) + corner-pt (cond-> corner-pt (some? transform-inverse) (gpt/transform transform-inverse)) - current-transform - (if (some? (:resize-vector modifiers)) - (gmt/multiply - current-transform - transform - (gmt/scale-matrix (gpt/inverse (:resize-vector modifiers)) (gpt/transform corner-pt transform-inverse)) - transform-inverse) - current-transform) + resize-x? (some? (:resize-vector modifiers)) + resize-y? (some? (:resize-vector-2 modifiers)) - current-transform - (if (some? (:resize-vector-2 modifiers)) - (gmt/multiply - current-transform - transform - (gmt/scale-matrix (gpt/inverse (:resize-vector-2 modifiers)) (gpt/transform corner-pt transform-inverse)) - transform-inverse) - current-transform)] - current-transform)) + flip-x? (neg? (get-in modifiers [:resize-vector :x])) + flip-y? (or (neg? (get-in modifiers [:resize-vector :y])) + (neg? (get-in modifiers [:resize-vector-2 :y]))) + + result (cond-> (gmt/matrix) + (and (some? transform) (or resize-x? resize-y?)) + (gmt/multiply transform) + + resize-x? + (gmt/scale (gpt/inverse (:resize-vector modifiers)) corner-pt) + + resize-y? + (gmt/scale (gpt/inverse (:resize-vector-2 modifiers)) corner-pt) + + flip-x? + (gmt/scale (gpt/point -1 1) corner-pt) + + flip-y? + (gmt/scale (gpt/point 1 -1) corner-pt) + + (and (some? transform) (or resize-x? resize-y?)) + (gmt/multiply transform-inverse)) + + [width height] + (if (or resize-x? resize-y?) + (let [pc (-> (gpt/point x y) + (gpt/transform transform) + (gpt/transform current-transform)) + + pw (-> (gpt/point (+ x width) y) + (gpt/transform transform) + (gpt/transform current-transform)) + + ph (-> (gpt/point x (+ y height)) + (gpt/transform transform) + (gpt/transform current-transform))] + [(gpt/distance pc pw) (gpt/distance pc ph)]) + [width height])] + + [result width height])) (defn get-nodes "Retrieve the DOM nodes to apply the matrix transformation" @@ -48,6 +72,7 @@ frame? (= :frame type) group? (= :group type) + text? (= :text type) mask? (and group? masked-group?) ;; When the shape is a frame we maybe need to move its thumbnail @@ -68,6 +93,11 @@ group? [] + text? + [shape-node + (dom/query shape-node "foreignObject") + (dom/query shape-node ".text-shape")] + :else [shape-node]))) @@ -76,11 +106,23 @@ (when-let [nodes (get-nodes shape)] (let [transform (get transforms id) modifiers (get-in modifiers [id :modifiers]) - transform (case type - :text (text-corrected-transform shape transform modifiers) - transform)] + + [text-transform text-width text-height] + (when (= :text type) + (text-corrected-transform shape transform modifiers))] + (doseq [node nodes] - (when (and (some? transform) (some? node)) + (cond + (dom/class? node "text-shape") + (when (some? text-transform) + (dom/set-attribute node "transform" (str text-transform))) + + (= (dom/get-tag-name node) "foreignObject") + (when (and (some? text-width) (some? text-height)) + (dom/set-attribute node "width" text-width) + (dom/set-attribute node "height" text-height)) + + (and (some? transform) (some? node)) (dom/set-attribute node "transform" (str transform)))))))) (defn remove-transform [shapes] @@ -88,7 +130,13 @@ (when-let [nodes (get-nodes shape)] (doseq [node nodes] (when (some? node) - (dom/remove-attribute node "transform")))))) + (cond + (= (dom/get-tag-name node) "foreignObject") + ;; The shape width/height will be automaticaly setup when the modifiers are applied + nil + + :else + (dom/remove-attribute node "transform"))))))) (defn format-viewbox [vbox] (str/join " " [(+ (:x vbox 0) (:left-offset vbox 0)) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 25a59a1998..682a1963b8 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -17,21 +17,24 @@ ;; --- Deprecated methods (defn event->inner-text - [e] - (.-innerText (.-target e))) + [^js e] + (when (some? e) + (.-innerText (.-target e)))) (defn event->value - [e] - (.-value (.-target e))) + [^js e] + (when (some? e) + (.-value (.-target e)))) (defn event->target - [e] - (.-target e)) + [^js e] + (when (some? e) + (.-target e))) ;; --- New methods (defn set-html-title - [title] + [^string title] (set! (.-title globals/document) title)) (defn set-page-style @@ -61,98 +64,117 @@ (dom/getElement id)) (defn get-elements-by-tag - [node tag] - (.getElementsByTagName node tag)) + [^js node tag] + (when (some? node) + (.getElementsByTagName node tag))) (defn stop-propagation - [e] - (when e - (.stopPropagation e))) + [^js event] + (when event + (.stopPropagation event))) (defn prevent-default - [e] - (when e - (.preventDefault e))) + [^js event] + (when event + (.preventDefault event))) (defn get-target "Extract the target from event instance." - [event] - (.-target event)) + [^js event] + (when (some? event) + (.-target event))) (defn get-current-target "Extract the current target from event instance (different from target when event triggered in a child of the subscribing element)." - [event] - (.-currentTarget event)) + [^js event] + (when (some? event) + (.-currentTarget event))) (defn get-parent - [dom] - (.-parentElement ^js dom)) + [^js node] + (when (some? node) + (.-parentElement ^js node))) (defn get-value "Extract the value from dom node." - [node] - (.-value node)) + [^js node] + (when (some? node) + (.-value node))) (defn get-attribute "Extract the value of one attribute of a dom node." - [node attr-name] - (.getAttribute ^js node attr-name)) + [^js node ^string attr-name] + (when (some? node) + (.getAttribute ^js node attr-name))) (def get-target-val (comp get-value get-target)) (defn click "Click a node" - [node] - (.click node)) + [^js node] + (when (some? node) + (.click node))) (defn get-files "Extract the files from dom node." - [node] - (array-seq (.-files node))) + [^js node] + (when (some? node) + (array-seq (.-files node)))) (defn checked? "Check if the node that represents a radio or checkbox is checked or not." - [node] - (.-checked node)) + [^js node] + (when (some? node) + (.-checked node))) (defn valid? "Check if the node that is a form input has a valid value, against html5 form validation properties (required, min/max, pattern...)." - [node] - (.-valid (.-validity node))) + [^js node] + (when (some? node) + (when-let [validity (.-validity node)] + (.-valid validity)))) (defn set-validity! "Manually set the validity status of a node that is a form input. If the state is an empty string, the input will be valid. If not, the string will be set as the error message." - [node status] - (.setCustomValidity node status) - (.reportValidity node)) + [^js node status] + (when (some? node) + (.setCustomValidity node status) + (.reportValidity node))) (defn clean-value! - [node] - (set! (.-value node) "")) + [^js node] + (when (some? node) + (set! (.-value node) ""))) (defn set-value! - [node value] - (set! (.-value ^js node) value)) + [^js node value] + (when (some? node) + (set! (.-value ^js node) value))) (defn select-text! - [node] - (.select ^js node)) + [^js node] + (when (some? node) + (.select ^js node))) (defn ^boolean equals? - [node-a node-b] - (.isEqualNode ^js node-a node-b)) + [^js node-a ^js node-b] + + (or (and (nil? node-a) (nil? node-b)) + (and (some? node-a) + (.isEqualNode ^js node-a node-b)))) (defn get-event-files "Extract the files from event instance." - [event] - (get-files (get-target event))) + [^js event] + (when (some? event) + (get-files (get-target event)))) (defn create-element ([tag] @@ -161,50 +183,58 @@ (.createElementNS globals/document ns tag))) (defn set-html! - [el html] - (set! (.-innerHTML el) html)) + [^js el html] + (when (some? el) + (set! (.-innerHTML el) html))) (defn append-child! - [el child] - (.appendChild ^js el child)) + [^js el child] + (when (some? el) + (.appendChild ^js el child))) (defn get-first-child - [el] - (.-firstChild el)) + [^js el] + (when (some? el) + (.-firstChild el))) (defn get-tag-name - [el] - (.-tagName el)) + [^js el] + (when (some? el) + (.-tagName el))) (defn get-outer-html - [el] - (.-outerHTML el)) + [^js el] + (when (some? el) + (.-outerHTML el))) (defn get-inner-text - [el] - (.-innerText el)) + [^js el] + (when (some? el) + (.-innerText el))) (defn query - [el query] + [^js el ^string query] (when (some? el) (.querySelector el query))) (defn get-client-position - [event] + [^js event] (let [x (.-clientX event) y (.-clientY event)] (gpt/point x y))) (defn get-offset-position - [event] - (let [x (.-offsetX event) - y (.-offsetY event)] - (gpt/point x y))) + [^js event] + (when (some? event) + (let [x (.-offsetX event) + y (.-offsetY event)] + (gpt/point x y)))) (defn get-client-size - [node] - {:width (.-clientWidth ^js node) - :height (.-clientHeight ^js node)}) + [^js node] + (when (some? node) + {:width (.-clientWidth ^js node) + :height (.-clientHeight ^js node)})) (defn get-bounding-rect [node] @@ -222,12 +252,12 @@ :height (.-innerHeight ^js js/window)}) (defn focus! - [node] + [^js node] (when (some? node) (.focus node))) (defn blur! - [node] + [^js node] (when (some? node) (.blur node))) @@ -245,8 +275,9 @@ :hint "seems like the current browser does not support fullscreen api."))) (defn ^boolean blob? - [v] - (instance? js/Blob v)) + [^js v] + (when (some? v) + (instance? js/Blob v))) (defn create-blob "Create a blob from content." @@ -265,20 +296,24 @@ {:pre [(blob? b)]} (js/URL.createObjectURL b)) -(defn set-property! [node property value] - (.setAttribute node property value)) +(defn set-property! [^js node property value] + (when (some? node) + (.setAttribute node property value))) -(defn set-text! [node text] - (set! (.-textContent node) text)) +(defn set-text! [^js node text] + (when (some? node) + (set! (.-textContent node) text))) -(defn set-css-property! [node property value] - (.setProperty (.-style ^js node) property value)) +(defn set-css-property! [^js node property value] + (when (some? node) + (.setProperty (.-style ^js node) property value))) -(defn capture-pointer [event] - (-> event get-target (.setPointerCapture (.-pointerId event)))) +(defn capture-pointer [^js event] + (when (some? event) + (-> event get-target (.setPointerCapture (.-pointerId event))))) -(defn release-pointer [event] - (when (.-pointerId event) +(defn release-pointer [^js event] + (when (and (some? event) (.-pointerId event)) (-> event get-target (.releasePointerCapture (.-pointerId event))))) (defn get-root [] @@ -295,19 +330,23 @@ (partition 2 params)))) (defn ^boolean class? [node class-name] - (let [class-list (.-classList ^js node)] - (.contains ^js class-list class-name))) + (when (some? node) + (let [class-list (.-classList ^js node)] + (.contains ^js class-list class-name)))) -(defn add-class! [node class-name] - (let [class-list (.-classList ^js node)] - (.add ^js class-list class-name))) +(defn add-class! [^js node class-name] + (when (some? node) + (let [class-list (.-classList ^js node)] + (.add ^js class-list class-name)))) -(defn remove-class! [node class-name] - (let [class-list (.-classList ^js node)] - (.remove ^js class-list class-name))) +(defn remove-class! [^js node class-name] + (when (some? node) + (let [class-list (.-classList ^js node)] + (.remove ^js class-list class-name)))) -(defn child? [node1 node2] - (.contains ^js node2 ^js node1)) +(defn child? [^js node1 ^js node2] + (when (some? node1) + (.contains ^js node2 ^js node1))) (defn get-user-agent [] (.-userAgent globals/navigator)) @@ -315,11 +354,13 @@ (defn get-active [] (.-activeElement globals/document)) -(defn active? [node] - (= (get-active) node)) +(defn active? [^js node] + (when (some? node) + (= (get-active) node))) (defn get-data [^js node ^string attr] - (.getAttribute node (str "data-" attr))) + (when (some? node) + (.getAttribute node (str "data-" attr)))) (defn mtype->extension [mtype] ;; https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types @@ -336,42 +377,53 @@ nil)) (defn set-attribute [^js node ^string attr value] - (.setAttribute node attr value)) + (when (some? node) + (.setAttribute node attr value))) (defn remove-attribute [^js node ^string attr] - (.removeAttribute node attr)) + (when (some? node) + (.removeAttribute node attr))) (defn get-scroll-pos - [element] - (.-scrollTop ^js element)) + [^js element] + (when (some? element) + (.-scrollTop element))) (defn set-scroll-pos! - [element scroll] - (obj/set! ^js element "scrollTop" scroll)) + [^js element scroll] + (when (some? element) + (obj/set! element "scrollTop" scroll))) (defn scroll-into-view! - ([element] - (.scrollIntoView ^js element false)) - ([element scroll-top] - (.scrollIntoView ^js element scroll-top))) + ([^js element] + (when (some? element) + (.scrollIntoView element false))) + + ([^js element scroll-top] + (when (some? element) + (.scrollIntoView element scroll-top)))) (defn scroll-into-view-if-needed! - ([element] - (.scrollIntoViewIfNeeded ^js element false)) - ([element scroll-top] - (.scrollIntoViewIfNeeded ^js element scroll-top))) + ([^js element] + (when (some? element) + (.scrollIntoViewIfNeeded ^js element false))) + + ([^js element scroll-top] + (when (some? element) + (.scrollIntoViewIfNeeded ^js element scroll-top)))) (defn is-in-viewport? - [element] - (let [rect (.getBoundingClientRect element) - height (or (.-innerHeight js/window) - (.. js/document -documentElement -clientHeight)) - width (or (.-innerWidth js/window) - (.. js/document -documentElement -clientWidth))] - (and (>= (.-top rect) 0) - (>= (.-left rect) 0) - (<= (.-bottom rect) height) - (<= (.-right rect) width)))) + [^js element] + (when (some? element) + (let [rect (.getBoundingClientRect element) + height (or (.-innerHeight js/window) + (.. js/document -documentElement -clientHeight)) + width (or (.-innerWidth js/window) + (.. js/document -documentElement -clientWidth))] + (and (>= (.-top rect) 0) + (>= (.-left rect) 0) + (<= (.-bottom rect) height) + (<= (.-right rect) width))))) (defn trigger-download-uri [filename mtype uri] From 8e5793296647cb30dc55e5d8b6df26770389796a Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 4 Jan 2022 14:34:12 +0100 Subject: [PATCH 05/10] :bug: Fix problem with multiple exports --- CHANGES.md | 1 + exporter/src/app/http/export.cljs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 9c621028cd..59b183f616 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -36,6 +36,7 @@ - Fix line-height/letter-spacing inputs behaviour [Taiga #2331](https://tree.taiga.io/project/penpot/issue/2331) - Fix dotted style in strokes [Taiga #2312](https://tree.taiga.io/project/penpot/issue/2312) - Fix problem when resizing texts inside groups [Taiga #2310](https://tree.taiga.io/project/penpot/issue/2310) +- Fix problem with multiple exports [Taiga #2468](https://tree.taiga.io/project/penpot/issue/2468) ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) diff --git a/exporter/src/app/http/export.cljs b/exporter/src/app/http/export.cljs index e1551c52c6..493f53908b 100644 --- a/exporter/src/app/http/export.cljs +++ b/exporter/src/app/http/export.cljs @@ -80,9 +80,11 @@ (p/then (fn [results] (reduce #(zip/add! %1 (:filename %2) (:content %2)) (zip/create) results))) (p/then (fn [fzip] + (.generateAsync ^js fzip #js {:type "uint8array"}))) + (p/then (fn [data] {:status 200 :headers {"content-type" "application/zip"} - :body (.generateNodeStream ^js fzip)}))))) + :body data}))))) (defn- perform-export [params] From e9fa04dd1b39d67b033bc26ff8f95b60faec4e98 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 4 Jan 2022 15:19:05 +0100 Subject: [PATCH 06/10] :bug: Fix problem with styles in the viewer --- CHANGES.md | 1 + frontend/resources/styles/main/partials/viewer.scss | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 59b183f616..cb8d554f4c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -139,6 +139,7 @@ - Add placeholder to create shareable link - Fix project files count not refreshing correctly after import [Taiga #2216](https://tree.taiga.io/project/penpot/issue/2216) - Remove button after import process finish [Taiga #2215](https://tree.taiga.io/project/penpot/issue/2215) +- Fix problem with styles in the viewer [Taiga #2467](https://tree.taiga.io/project/penpot/issue/2467) ### :heart: Community contributions by (Thank you!) diff --git a/frontend/resources/styles/main/partials/viewer.scss b/frontend/resources/styles/main/partials/viewer.scss index c5fc1b30b3..0c496877b4 100644 --- a/frontend/resources/styles/main/partials/viewer.scss +++ b/frontend/resources/styles/main/partials/viewer.scss @@ -25,7 +25,6 @@ grid-template-columns: 1fr; justify-items: center; align-items: center; - overflow: hidden; .empty-state { justify-content: center; From d246788a359690003202b6865645787493cd4917 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 4 Jan 2022 15:23:17 +0100 Subject: [PATCH 07/10] :bug: Fix default state in viewer --- CHANGES.md | 1 + frontend/src/app/main/ui/workspace/header.cljs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index cb8d554f4c..a8204ecd88 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -140,6 +140,7 @@ - Fix project files count not refreshing correctly after import [Taiga #2216](https://tree.taiga.io/project/penpot/issue/2216) - Remove button after import process finish [Taiga #2215](https://tree.taiga.io/project/penpot/issue/2215) - Fix problem with styles in the viewer [Taiga #2467](https://tree.taiga.io/project/penpot/issue/2467) +- Fix default state in viewer [Taiga #2465](https://tree.taiga.io/project/penpot/issue/2465) ### :heart: Community contributions by (Thank you!) diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index 6e0a00eeb1..85de2ea77c 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -299,7 +299,7 @@ [{:keys [file layout project page-id] :as props}] (let [team-id (:team-id project) zoom (mf/deref refs/selected-zoom) - params {:page-id page-id :file-id (:id file)} + params {:page-id page-id :file-id (:id file) :section "interactions"} go-back (mf/use-callback From 1d575ece0624a82a395a7cdb13096ebcb662a9f0 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 5 Jan 2022 14:53:32 +0100 Subject: [PATCH 08/10] :bug: Allow import to continue from recoverable failures --- CHANGES.md | 1 + common/src/app/common/file_builder.cljc | 101 ++++++++++-------- common/src/app/common/spec.cljc | 5 + .../resources/styles/main/partials/modal.scss | 11 ++ .../src/app/main/ui/dashboard/import.cljs | 40 ++++--- frontend/src/app/worker/import.cljs | 31 ++++-- frontend/translations/en.po | 3 + frontend/translations/es.po | 3 + 8 files changed, 123 insertions(+), 72 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a8204ecd88..1f9447cd31 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -37,6 +37,7 @@ - Fix dotted style in strokes [Taiga #2312](https://tree.taiga.io/project/penpot/issue/2312) - Fix problem when resizing texts inside groups [Taiga #2310](https://tree.taiga.io/project/penpot/issue/2310) - Fix problem with multiple exports [Taiga #2468](https://tree.taiga.io/project/penpot/issue/2468) +- Allow import to continue from recoverable failures [#1412](https://github.com/penpot/penpot/issues/1412) ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 6fac15a102..0d6c96ff4a 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -12,7 +12,6 @@ [app.common.geom.shapes :as gsh] [app.common.pages.changes :as ch] [app.common.pages.init :as init] - [app.common.pages.spec :as spec] [app.common.spec :as us] [app.common.uuid :as uuid] [cuerdas.core :as str])) @@ -21,15 +20,14 @@ (def conjv (fnil conj [])) (def conjs (fnil conj #{})) -;; This flag controls if we should execute spec validation after every commit -(def verify-on-commit? true) - (defn- commit-change ([file change] (commit-change file change nil)) - ([file change {:keys [add-container?] - :or {add-container? false}}] + ([file change {:keys [add-container? + fail-on-spec?] + :or {add-container? false + fail-on-spec? false}}] (let [component-id (:current-component-id file) change (cond-> change (and add-container? (some? component-id)) @@ -39,11 +37,20 @@ (assoc :page-id (:current-page-id file) :frame-id (:current-frame-id file)))] - (when verify-on-commit? - (us/assert ::spec/change change)) - (-> file - (update :changes conjv change) - (update :data ch/process-changes [change] verify-on-commit?))))) + (when fail-on-spec? + (us/verify :app.common.pages.spec/change change)) + + (let [valid? (us/valid? :app.common.pages.spec/change change)] + #?(:cljs + (when-not valid? (.warn js/console "Invalid shape" (clj->js change)))) + + (cond-> file + valid? + (-> (update :changes conjv change) + (update :data ch/process-changes [change] false)) + + (not valid?) + (update :errors conjv change)))))) (defn- lookup-objects ([file] @@ -56,15 +63,16 @@ (get shape-id))) (defn- commit-shape [file obj] - (let [parent-id (-> file :parent-stack peek)] - (-> file - (commit-change - {:type :add-obj - :id (:id obj) - :obj obj - :parent-id parent-id} + (let [parent-id (-> file :parent-stack peek) + change {:type :add-obj + :id (:id obj) + :obj obj + :parent-id parent-id} - {:add-container? true})))) + fail-on-spec? (or (= :group (:type obj)) + (= :frame (:type obj)))] + + (commit-change file change {:add-container? true :fail-on-spec? fail-on-spec?}))) (defn setup-rect-selrect [obj] (let [rect (select-keys obj [:x :y :width :height]) @@ -421,35 +429,36 @@ (defn add-interaction [file from-id interaction-src] - (assert (some? (lookup-shape file from-id)) (str "Cannot locate shape with id " from-id)) + (let [shape (lookup-shape file from-id)] + (if (nil? shape) + file + (let [{:keys [event-type action-type]} (read-classifier interaction-src) + {:keys [delay]} (read-event-opts interaction-src) + {:keys [destination overlay-pos-type overlay-position url + close-click-outside background-overlay preserve-scroll]} + (read-action-opts interaction-src) - (let [{:keys [event-type action-type]} (read-classifier interaction-src) - {:keys [delay]} (read-event-opts interaction-src) - {:keys [destination overlay-pos-type overlay-position url - close-click-outside background-overlay preserve-scroll]} - (read-action-opts interaction-src) + interactions (-> shape + :interactions + (conjv + (d/without-nils {:event-type event-type + :action-type action-type + :delay delay + :destination destination + :overlay-pos-type overlay-pos-type + :overlay-position overlay-position + :url url + :close-click-outside close-click-outside + :background-overlay background-overlay + :preserve-scroll preserve-scroll})))] + (commit-change + file + {:type :mod-obj + :page-id (:current-page-id file) + :id from-id - interactions (-> (lookup-shape file from-id) - :interactions - (conjv - (d/without-nils {:event-type event-type - :action-type action-type - :delay delay - :destination destination - :overlay-pos-type overlay-pos-type - :overlay-position overlay-position - :url url - :close-click-outside close-click-outside - :background-overlay background-overlay - :preserve-scroll preserve-scroll})))] - (commit-change - file - {:type :mod-obj - :page-id (:current-page-id file) - :id from-id - - :operations - [{:type :set :attr :interactions :val interactions}]}))) + :operations + [{:type :set :attr :interactions :val interactions}]}))))) (defn generate-changes [file] diff --git a/common/src/app/common/spec.cljc b/common/src/app/common/spec.cljc index 19ef41255d..d568d2d9f6 100644 --- a/common/src/app/common/spec.cljc +++ b/common/src/app/common/spec.cljc @@ -31,6 +31,8 @@ (def max-safe-int (int 1e6)) (def min-safe-int (int -1e6)) +(def valid? s/valid?) + ;; --- Conformers (defn uuid-conformer @@ -216,8 +218,11 @@ :code :spec-validation :hint hint :ctx ctx + :value val ::s/problems (::s/problems data))))) + + (defmacro assert "Development only assertion macro." [spec x] diff --git a/frontend/resources/styles/main/partials/modal.scss b/frontend/resources/styles/main/partials/modal.scss index 67042f012a..e767a2c211 100644 --- a/frontend/resources/styles/main/partials/modal.scss +++ b/frontend/resources/styles/main/partials/modal.scss @@ -322,6 +322,10 @@ fill: $color-success; } + .icon-msg-warning { + fill: $color-warning; + } + .icon-close { transform: rotate(45deg); fill: $color-danger; @@ -392,6 +396,13 @@ fill: $color-white; } } + + &.warning { + background: $color-warning-lighter; + .icon { + background: $color-warning; + } + } } .error-message { diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index a076763648..014b0b7d38 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -98,7 +98,7 @@ (filter #(= :ready (:status %))) (mapv #(assoc % :status :importing)))) -(defn update-status [files file-id status progress] +(defn update-status [files file-id status progress errors] (->> files (mapv (fn [file] (cond-> file @@ -106,7 +106,10 @@ (assoc :status status) (and (= file-id (:file-id file)) (= status :import-progress)) - (assoc :progress progress)))))) + (assoc :progress progress) + + (= file-id (:file-id file)) + (assoc :errors errors)))))) (defn parse-progress-message [message] @@ -139,9 +142,10 @@ (let [loading? (or (= :analyzing (:status file)) (= :importing (:status file))) - load-success? (= :import-success (:status file)) analyze-error? (= :analyze-error (:status file)) + import-finish? (= :import-finish (:status file)) import-error? (= :import-error (:status file)) + import-warn? (d/not-empty? (:errors file)) ready? (= :ready (:status file)) is-shared? (:shared file) progress (:progress file) @@ -177,7 +181,8 @@ [:div.file-entry {:class (dom/classnames :loading loading? - :success load-success? + :success (and import-finish? (not import-warn?) (not import-error?)) + :warning (and import-finish? import-warn? (not import-error?)) :error (or import-error? analyze-error?) :editable (and ready? (not editing?)))} @@ -185,8 +190,9 @@ [:div.file-icon (cond loading? i/loader-pencil ready? i/logo-icon - load-success? i/tick + import-warn? i/msg-warning import-error? i/close + import-finish? i/tick analyze-error? i/close)] (if editing? @@ -212,7 +218,7 @@ [:div.error-message (tr "dashboard.import.import-error")] - (and (not load-success?) (some? progress)) + (and (not import-finish?) (some? progress)) [:div.progress-message (parse-progress-message progress)]) [:div.linked-libraries @@ -258,9 +264,8 @@ :project-id project-id :files files}) (rx/subs - (fn [{:keys [file-id status message] :as msg}] - (log/debug :msg msg) - (swap! state update :files update-status file-id status message)))))) + (fn [{:keys [file-id status message errors] :as msg}] + (swap! state update :files update-status file-id status message errors)))))) handle-cancel (mf/use-callback @@ -291,7 +296,8 @@ (st/emit! (modal/hide)) (when on-finish-import (on-finish-import)))) - success-files (->> @state :files (filter #(= (:status %) :import-success)) count) + warning-files (->> @state :files (filter #(and (= (:status %) :import-finish) (d/not-empty? (:errors %)))) count) + success-files (->> @state :files (filter #(and (= (:status %) :import-finish) (empty? (:errors %)))) count) pending-analysis? (> (->> @state :files (filter #(= (:status %) :analyzing)) count) 0) pending-import? (> (->> @state :files (filter #(= (:status %) :importing)) count) 0)] @@ -316,11 +322,15 @@ {:on-click handle-cancel} i/close]] [:div.modal-content - (when (and (= :importing (:status @state)) - (not pending-import?)) - [:div.feedback-banner - [:div.icon i/checkbox-checked] - [:div.message (tr "dashboard.import.import-message" success-files)]]) + (when (and (= :importing (:status @state)) (not pending-import?)) + (if (> warning-files 0) + [:div.feedback-banner.warning + [:div.icon i/msg-warning] + [:div.message (tr "dashboard.import.import-warning" warning-files success-files)]] + + [:div.feedback-banner + [:div.icon i/checkbox-checked] + [:div.message (tr "dashboard.import.import-message" success-files)]])) (for [file (->> (:files @state) (filterv (comp not :deleted?)))] (let [editing? (and (some? (:file-id file)) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 5849ca22ca..11473c0af6 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -176,7 +176,9 @@ (rx/tap #(reset! revn (:revn %))) (rx/ignore)) - (rp/mutation :persist-temp-file {:id file-id})))) + (->> (rp/mutation :persist-temp-file {:id file-id}) + ;; We use merge to keep some information not stored in back-end + (rx/map #(merge file %)))))) (defn upload-media-files "Upload a image to the backend and returns its id" @@ -457,8 +459,7 @@ (let [progress-str (rx/subject) context (assoc context :progress progress-str)] - (rx/merge - progress-str + [progress-str (->> (rx/of file) (rx/flat-map (partial process-pages context)) (rx/tap #(progress! context :process-colors)) @@ -470,7 +471,7 @@ (rx/tap #(progress! context :process-components)) (rx/flat-map (partial process-library-components context)) (rx/flat-map (partial send-changes context)) - (rx/tap #(rx/end! progress-str)))))) + (rx/tap #(rx/end! progress-str)))])) (defn create-files [context files] @@ -482,7 +483,6 @@ (rx/flat-map (fn [context] (->> (create-file context) - (rx/tap #(.log js/console "create-file" (clj->js %))) (rx/map #(vector % (first (get data (:file-id context))))))))) (->> (rx/from files) @@ -509,20 +509,29 @@ (let [context {:project-id project-id :resolve (resolve-factory)}] (->> (create-files context files) - (rx/catch #(.error js/console "IMPORT ERROR" %)) + (rx/catch #(.error js/console "IMPORT ERROR" (clj->js %))) (rx/flat-map (fn [[file data]] (->> (rx/concat (->> (uz/load-from-url (:uri data)) (rx/map #(-> context (assoc :zip %) (merge data))) - (rx/flat-map #(process-file % file))) - (rx/of - {:status :import-success - :file-id (:file-id data)})) + (rx/flat-map + (fn [context] + ;; process file retrieves a stream that will emit progress notifications + ;; and other that will emit the files once imported + (let [[progress-stream file-stream] (process-file context file)] + (rx/merge + progress-stream + (->> file-stream + (rx/map + (fn [file] + {:status :import-finish + :errors (:errors file) + :file-id (:file-id data)}))))))))) (rx/catch (fn [err] - (.error js/console "ERROR" (:file-id data) err) + (.error js/console "ERROR" (str (:file-id data)) (clj->js err) (clj->js (.-data err))) (rx/of {:status :import-error :file-id (:file-id data) :error (.-message err) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index bd1bdcc96f..4f2e5c615c 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -3322,3 +3322,6 @@ msgstr "Insufficient members to leave team, you probably want to delete it." msgid "errors.auth.unable-to-login" msgstr "Looks like you are not authenticated or session expired." + +msgid "dashboard.import.import-warning" +msgstr "Some files containted invalid objects that have been removed." diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 60f9b7b88d..025d67cbb0 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -3309,3 +3309,6 @@ msgstr "Actualizar" msgid "workspace.viewport.click-to-close-path" msgstr "Pulsar para cerrar la ruta" + +msgid "dashboard.import.import-warning" +msgstr "Algunos ficheros contenĂ­an objetos erroneos que no han sido importados." From 4360c1fe4b4c599f941bf575a82f1d54a38305a3 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 7 Jan 2022 11:31:08 +0100 Subject: [PATCH 09/10] :bug: Improved behaviour on text options when not text is selected --- CHANGES.md | 3 +- .../main/ui/workspace/shapes/text/editor.cljs | 63 ++++++++++--------- frontend/src/app/util/text_editor.cljs | 44 +++++++++++++ frontend/src/app/util/text_editor_impl.js | 41 ++++++++++-- 4 files changed, 117 insertions(+), 34 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 1f9447cd31..169624d0ad 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -38,6 +38,7 @@ - Fix problem when resizing texts inside groups [Taiga #2310](https://tree.taiga.io/project/penpot/issue/2310) - Fix problem with multiple exports [Taiga #2468](https://tree.taiga.io/project/penpot/issue/2468) - Allow import to continue from recoverable failures [#1412](https://github.com/penpot/penpot/issues/1412) +- Improved behaviour on text options when not text is selected [Taiga #2390](https://tree.taiga.io/project/penpot/issue/2390) ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) @@ -81,8 +82,6 @@ ### :arrow_up: Deps updates - Update log4j2 dependency. ->>>>>>> main - # 1.10.2-beta 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 47880bac01..fad6579384 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.workspace.shapes.text.editor (:require ["draft-js" :as draft] - [app.common.data :as d] [app.common.geom.shapes :as gsh] [app.common.text :as txt] [app.main.data.workspace :as dw] @@ -72,20 +71,18 @@ (def empty-editor-state (ted/create-editor-state nil default-decorator)) -(defn get-content-changes - [old-state state] - (let [old-blocks (js->clj (.toJS (.getBlockMap (.getCurrentContent ^js old-state))) - :keywordize-keys false) - new-blocks (js->clj (.toJS (.getBlockMap (.getCurrentContent ^js state))) +(defn get-blocks-to-setup [block-changes] + (->> block-changes + (filter (fn [[_ v]] + (nil? (:old v)))) + (mapv first))) - :keywordize-keys false)] - (->> old-blocks - (d/mapm - (fn [bkey bstate] - {:old (get bstate "text") - :new (get-in new-blocks [bkey "text"])})) - (filter #(contains? new-blocks (first %))) - (into {})))) +(defn get-blocks-to-add-styles + [block-changes] + (->> block-changes + (filter (fn [[_ v]] + (and (not= (:old v) (:new v)) (= (:old v) "")))) + (mapv first))) (mf/defc text-shape-edit-html {::mf/wrap [mf/memo] @@ -143,25 +140,35 @@ (fn [state] (let [old-state (mf/ref-val prev-value)] (if (and (some? state) (some? old-state)) - (let [block-states (get-content-changes old-state state) - - block-to-add-styles - (->> block-states - (filter - (fn [[_ v]] - (and (not= (:old v) (:new v)) - (= (:old v) "")))) - (mapv first))] - (ted/apply-block-styles-to-content state block-to-add-styles)) + (let [block-changes (ted/get-content-changes old-state state) + prev-data (ted/get-editor-current-inline-styles old-state) + block-to-setup (get-blocks-to-setup block-changes) + block-to-add-styles (get-blocks-to-add-styles block-changes)] + (-> state + (ted/setup-block-styles block-to-setup prev-data) + (ted/apply-block-styles-to-content block-to-add-styles))) state)))) on-change (mf/use-callback (fn [val] - (let [val (handle-change val) - val (if (true? @blurred) - (ted/add-editor-blur-selection val) - (ted/remove-editor-blur-selection val))] + (let [prev-val (mf/ref-val prev-value) + styleOverride (ted/get-style-override prev-val) + + ;; If the content and the selection are the same we keep the style override + keep-style? (and (some? styleOverride) + (ted/content-equals prev-val val) + (ted/selection-equals prev-val val)) + + val (cond-> (handle-change val) + @blurred + (ted/add-editor-blur-selection) + + (not @blurred) + (ted/remove-editor-blur-selection) + + keep-style? + (ted/set-style-override styleOverride))] (st/emit! (dwt/update-editor-state shape val))))) on-editor diff --git a/frontend/src/app/util/text_editor.cljs b/frontend/src/app/util/text_editor.cljs index 06738d20ef..f1d0a77354 100644 --- a/frontend/src/app/util/text_editor.cljs +++ b/frontend/src/app/util/text_editor.cljs @@ -111,6 +111,16 @@ [state] (impl/cursorToEnd state)) +(defn setup-block-styles + [state blocks attrs] + (if (empty? blocks) + state + (->> blocks + (reduce + (fn [state block-key] + (impl/updateBlockData state block-key (clj->js attrs))) + state)))) + (defn apply-block-styles-to-content [state blocks] (if (empty? blocks) @@ -130,3 +140,37 @@ (defn insert-text [state text attrs] (let [style (txt/attrs-to-styles attrs)] (impl/insertText state text (clj->js attrs) (clj->js style)))) + +(defn get-style-override [state] + (.getInlineStyleOverride state)) + +(defn set-style-override [state inline-style] + (impl/setInlineStyleOverride state inline-style)) + +(defn content-equals [state other] + (.equals (.getCurrentContent state) (.getCurrentContent other))) + +(defn selection-equals [state other] + (impl/selectionEquals (.getSelection state) (.getSelection other))) + +(defn get-content-changes + [old-state state] + (let [old-blocks (js->clj (.toJS (.getBlockMap (.getCurrentContent ^js old-state))) + :keywordize-keys false) + new-blocks (js->clj (.toJS (.getBlockMap (.getCurrentContent ^js state))) + :keywordize-keys false)] + (merge + (into {} + (comp (filter #(contains? new-blocks (first %))) + (map (fn [[bkey bstate]] + [bkey + {:old (get bstate "text") + :new (get-in new-blocks [bkey "text"])}]))) + old-blocks) + (into {} + (comp (filter #(not (contains? old-blocks (first %)))) + (map (fn [[bkey bstate]] + [bkey + {:old nil + :new (get bstate "text")}]))) + new-blocks)))) diff --git a/frontend/src/app/util/text_editor_impl.js b/frontend/src/app/util/text_editor_impl.js index 92b01533bf..6a8cbc1787 100644 --- a/frontend/src/app/util/text_editor_impl.js +++ b/frontend/src/app/util/text_editor_impl.js @@ -121,15 +121,33 @@ export function updateCurrentBlockData(state, attrs) { return EditorState.push(state, content, "change-block-data"); } +function addStylesToOverride(styles, other) { + let result = styles; + + for (let style of other) { + const [p, k, v] = style.split("$$$"); + const prefix = [p, k, ""].join("$$$"); + + const curValue = result.find((it) => it.startsWith(prefix)) + if (curValue) { + result = result.remove(curValue); + } + result = result.add(style); + } + return result +} + export function applyInlineStyle(state, styles) { const userSelection = state.getSelection(); let selection = userSelection; + let result = state; if (selection.isCollapsed()) { - selection = getSelectAllSelection(state); + const currentOverride = state.getCurrentInlineStyle() || new OrderedSet(); + const styleOverride = addStylesToOverride(currentOverride, styles) + return EditorState.setInlineStyleOverride(state, styleOverride); } - let result = state; let content = null; for (let style of styles) { @@ -300,6 +318,7 @@ export function getBlockData(state, blockKey) { export function updateBlockData(state, blockKey, data) { const userSelection = state.getSelection(); + const inlineStyleOverride = state.getInlineStyleOverride(); const content = state.getCurrentContent(); const block = content.getBlockForKey(blockKey); const newBlock = mergeBlockData(block, data); @@ -312,8 +331,10 @@ export function updateBlockData(state, blockKey, data) { blockData ); - const result = EditorState.push(state, newContent, 'change-block-data'); - return EditorState.acceptSelection(result, userSelection); + let result = EditorState.push(state, newContent, 'change-block-data'); + result = EditorState.acceptSelection(result, userSelection); + result = EditorState.setInlineStyleOverride(result, inlineStyleOverride); + return result; } export function getSelection(state) { @@ -376,3 +397,15 @@ export function insertText(state, text, attrs, inlineStyles) { const resultSelection = SelectionState.createEmpty(selection.getStartKey()); return EditorState.push(state, newContent, 'insert-fragment'); } + +export function setInlineStyleOverride(state, inlineStyles) { + return EditorState.setInlineStyleOverride(state, inlineStyles); +} + +export function selectionEquals(selection, other) { + return selection.getAnchorKey() === other.getAnchorKey() && + selection.getAnchorOffset() === other.getAnchorOffset() && + selection.getFocusKey() === other.getFocusKey() && + selection.getFocusOffset() === other.getFocusOffset() && + selection.getIsBackward() === other.getIsBackward(); +} From 363a82d068c87be0dae6232921f112f050892a9f Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 10 Jan 2022 13:32:15 +0100 Subject: [PATCH 10/10] :bug: Fix decimal numbers in export viewbox --- CHANGES.md | 1 + frontend/src/app/main/exports.cljs | 33 ++++++++++------ .../app/main/ui/components/numeric_input.cljs | 9 +---- .../sidebar/options/menus/typography.cljs | 3 +- frontend/src/app/util/strings.cljs | 38 +++++++++++++++++++ 5 files changed, 64 insertions(+), 20 deletions(-) create mode 100644 frontend/src/app/util/strings.cljs diff --git a/CHANGES.md b/CHANGES.md index 169624d0ad..3f41e504e7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -39,6 +39,7 @@ - Fix problem with multiple exports [Taiga #2468](https://tree.taiga.io/project/penpot/issue/2468) - Allow import to continue from recoverable failures [#1412](https://github.com/penpot/penpot/issues/1412) - Improved behaviour on text options when not text is selected [Taiga #2390](https://tree.taiga.io/project/penpot/issue/2390) +- Fix decimal numbers in export viewbox [Taiga #2290](https://tree.taiga.io/project/penpot/issue/2290) ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index 04da9d9d9f..318643a03d 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -29,11 +29,13 @@ [app.main.ui.shapes.text :as text] [app.main.ui.shapes.text.fontfaces :as ff] [app.util.object :as obj] + [app.util.strings :as ust] [app.util.timers :as ts] [cuerdas.core :as str] [debug :refer [debug?]] [rumext.alpha :as mf])) +(def ^:const viewbox-decimal-precision 3) (def ^:private default-color clr/canvas) (mf/defc background @@ -139,8 +141,13 @@ ;; Don't wrap svg elements inside a otherwise some can break [:> svg-raw-wrapper {:shape shape :frame frame}])))))) -(defn get-viewbox [{:keys [x y width height] :or {x 0 y 0 width 100 height 100}}] - (str/fmt "%s %s %s %s" x y width height)) +(defn format-viewbox + "Format a viewbox given a rectangle" + [{:keys [x y width height] :or {x 0 y 0 width 100 height 100}}] + (str/join + " " + (->> [x y width height] + (map #(ust/format-precision % viewbox-decimal-precision))))) (mf/defc page-svg {::mf/wrap [mf/memo]} @@ -160,7 +167,7 @@ vport (when (and (some? width) (some? height)) {:width width :height height}) dim (calculate-dimensions data vport) - vbox (get-viewbox dim) + vbox (format-viewbox dim) background-color (get-in data [:options :background] default-color) frame-wrapper (mf/use-memo @@ -221,15 +228,15 @@ width (* (:width frame) zoom) height (* (:height frame) zoom) - vbox (str "0 0 " (:width frame 0) - " " (:height frame 0)) + vbox (format-viewbox {:width (:width frame 0) :height (:height frame 0)}) + wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))] [:svg {:view-box vbox - :width width - :height height + :width (ust/format-precision width viewbox-decimal-precision) + :height (ust/format-precision height viewbox-decimal-precision) :version "1.1" :xmlns "http://www.w3.org/2000/svg" :xmlnsXlink "http://www.w3.org/1999/xlink" @@ -255,18 +262,20 @@ group (get objects group-id) + width (* (:width group) zoom) height (* (:height group) zoom) - vbox (str "0 0 " (:width group 0) - " " (:height group 0)) + vbox (format-viewbox {:width (:width group 0) + :height (:height group 0)}) + group-wrapper (mf/use-memo (mf/deps objects) #(group-wrapper-factory objects))] [:svg {:view-box vbox - :width width - :height height + :width (ust/format-precision width viewbox-decimal-precision) + :height (ust/format-precision height viewbox-decimal-precision) :version "1.1" :xmlns "http://www.w3.org/2000/svg" :xmlnsXlink "http://www.w3.org/1999/xlink" @@ -281,7 +290,7 @@ root (get objects id) {:keys [width height]} (:selrect root) - vbox (str "0 0 " width " " height) + vbox (format-viewbox {:width width :height height}) modifier (-> (gpt/point (:x root) (:y root)) (gpt/negate) diff --git a/frontend/src/app/main/ui/components/numeric_input.cljs b/frontend/src/app/main/ui/components/numeric_input.cljs index 04483405fe..f385796daa 100644 --- a/frontend/src/app/main/ui/components/numeric_input.cljs +++ b/frontend/src/app/main/ui/components/numeric_input.cljs @@ -13,6 +13,7 @@ [app.util.keyboard :as kbd] [app.util.object :as obj] [app.util.simple-math :as sm] + [app.util.strings :as ust] [rumext.alpha :as mf])) (defn num? [val] @@ -20,12 +21,6 @@ (not (math/nan? val)) (math/finite? val))) -(defn fixed [value precision] - (try - (.toFixed value precision) - (catch :default _ - (str value)))) - (mf/defc numeric-input {::mf/wrap-props false ::mf/forward-ref true} @@ -102,7 +97,7 @@ (fn [new-value] (let [input-node (mf/ref-val ref)] (dom/set-value! input-node (if (some? precision) - (fixed new-value precision) + (ust/format-precision new-value precision) (str new-value)))))) apply-value 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 d5bf868b5b..c39f715994 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 @@ -23,6 +23,7 @@ [app.util.keyboard :as kbd] [app.util.object :as obj] [app.util.router :as rt] + [app.util.strings :as ust] [app.util.timers :as tm] [cuerdas.core :as str] [goog.events :as events] @@ -31,7 +32,7 @@ (defn- attr->string [value] (if (= value :multiple) "" - (str value))) + (ust/format-precision value 2))) (defn- get-next-font [{:keys [id] :as current} fonts] diff --git a/frontend/src/app/util/strings.cljs b/frontend/src/app/util/strings.cljs new file mode 100644 index 0000000000..e51bbe86df --- /dev/null +++ b/frontend/src/app/util/strings.cljs @@ -0,0 +1,38 @@ +;; 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) UXBOX Labs SL + +(ns app.util.strings + (:require + [cuerdas.core :as str])) + + +(def ^:const trail-zeros-regex-1 #"\.0+$") +(def ^:const trail-zeros-regex-2 #"(\.\d*[^0])0+$") + +(defn format-precision + "Creates a number with predetermined precision and then removes the trailing 0. + Examples: + 12.0123, 0 => 12 + 12.0123, 1 => 12 + 12.0123, 2 => 12.01" + [num precision] + + (try + (if (number? num) + (let [num-str (.toFixed num precision) + + ;; Remove all trailing zeros after the comma 100.00000 + num-str (str/replace num-str trail-zeros-regex-1 "") + + ;; Remove trailing zeros after a decimal number: 0.001|00| + num-str (if-let [m (re-find trail-zeros-regex-2 num-str)] + (str/replace num-str (first m) (second m)) + num-str)] + num-str) + (str num)) + (catch :default _ + (str num)))) +