From d5ab0eea1a0c528446df180d39fb6cda4b3a7269 Mon Sep 17 00:00:00 2001
From: "alonso.torres" <alonso.torres@kaleidos.net>
Date: Tue, 3 Jan 2023 16:57:01 +0100
Subject: [PATCH 1/5] :zap: Removed reflow in viewport

---
 .../src/app/main/ui/workspace/viewport.cljs   | 16 ++---
 .../main/ui/workspace/viewport/actions.cljs   | 43 +++++-------
 .../app/main/ui/workspace/viewport/hooks.cljs | 20 ++++--
 .../ui/workspace/viewport/scroll_bars.cljs    | 14 ++--
 .../app/main/ui/workspace/viewport/utils.cljs | 15 +----
 .../ui/workspace/viewport/viewport_ref.cljs   | 65 +++++++++++++++++++
 6 files changed, 111 insertions(+), 62 deletions(-)
 create mode 100644 frontend/src/app/main/ui/workspace/viewport/viewport_ref.cljs

diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs
index a44943058..81b2ecec9 100644
--- a/frontend/src/app/main/ui/workspace/viewport.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport.cljs
@@ -40,6 +40,7 @@
    [app.main.ui.workspace.viewport.snap-distances :as snap-distances]
    [app.main.ui.workspace.viewport.snap-points :as snap-points]
    [app.main.ui.workspace.viewport.utils :as utils]
+   [app.main.ui.workspace.viewport.viewport-ref :refer [create-viewport-ref]]
    [app.main.ui.workspace.viewport.widgets :as widgets]
    [beicon.core :as rx]
    [debug :refer [debug?]]
@@ -98,7 +99,8 @@
         active-frames     (mf/use-state #{})
 
         ;; REFS
-        viewport-ref      (mf/use-ref nil)
+        [viewport-ref
+         on-viewport-ref] (create-viewport-ref)
 
         ;; VARS
         disable-paste     (mf/use-var false)
@@ -140,15 +142,14 @@
         on-double-click   (actions/on-double-click hover hover-ids drawing-path? base-objects edition workspace-read-only?)
         on-drag-enter     (actions/on-drag-enter)
         on-drag-over      (actions/on-drag-over)
-        on-drop           (actions/on-drop file viewport-ref zoom)
+        on-drop           (actions/on-drop file)
         on-mouse-down     (actions/on-mouse-down @hover selected edition drawing-tool text-editing? node-editing?
-                                                 drawing-path? create-comment? space? viewport-ref zoom panning
-                                                 workspace-read-only?)
+                                                 drawing-path? create-comment? space? panning workspace-read-only?)
         on-mouse-up       (actions/on-mouse-up disable-paste)
         on-pointer-down   (actions/on-pointer-down)
         on-pointer-enter  (actions/on-pointer-enter in-viewport?)
         on-pointer-leave  (actions/on-pointer-leave in-viewport?)
-        on-pointer-move   (actions/on-pointer-move viewport-ref zoom move-stream)
+        on-pointer-move   (actions/on-pointer-move move-stream)
         on-pointer-up     (actions/on-pointer-up)
         on-move-selected  (actions/on-move-selected hover hover-ids selected space? workspace-read-only?)
         on-menu-selected  (actions/on-menu-selected hover hover-ids selected workspace-read-only?)
@@ -269,7 +270,7 @@
        :preserveAspectRatio "xMidYMid meet"
        :key (str "viewport" page-id)
        :view-box (utils/format-viewbox vbox)
-       :ref viewport-ref
+       :ref on-viewport-ref
        :class (when drawing-tool "drawing")
        :style {:cursor @cursor}
        :fill "none"
@@ -423,8 +424,7 @@
        [:& scroll-bars/viewport-scrollbars
         {:objects base-objects
          :zoom zoom
-         :vbox vbox
-         :viewport-ref viewport-ref}]
+         :vbox vbox}]
 
        (when show-rules?
          [:& rules/rules
diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs
index 0bf2505fd..1a85587f2 100644
--- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs
@@ -19,7 +19,7 @@
    [app.main.refs :as refs]
    [app.main.store :as st]
    [app.main.streams :as ms]
-   [app.main.ui.workspace.viewport.utils :as utils]
+   [app.main.ui.workspace.viewport.viewport-ref :as uwvv]
    [app.util.dom :as dom]
    [app.util.dom.dnd :as dnd]
    [app.util.dom.normalize-wheel :as nw]
@@ -34,11 +34,11 @@
 
 (defn on-mouse-down
   [{:keys [id blocked hidden type]} selected edition drawing-tool text-editing?
-   node-editing? drawing-path? create-comment? space? viewport-ref zoom panning
+   node-editing? drawing-path? create-comment? space? panning
    workspace-read-only?]
   (mf/use-callback
    (mf/deps id blocked hidden type selected edition drawing-tool text-editing?
-            node-editing? drawing-path? create-comment? @space? viewport-ref zoom
+            node-editing? drawing-path? create-comment? @space?
             panning workspace-read-only?)
    (fn [bevent]
      (when (or (dom/class? (dom/get-target bevent) "viewport-controls")
@@ -61,8 +61,7 @@
              (dom/prevent-default bevent)
              (if mod?
                (let [raw-pt   (dom/get-client-position event)
-                     viewport (mf/ref-val viewport-ref)
-                     pt       (utils/translate-point-to-viewport viewport zoom raw-pt)]
+                     pt       (uwvv/point->viewport raw-pt)]
                  (st/emit! (dw/start-zooming pt)))
                (st/emit! (dw/start-panning))))
 
@@ -322,15 +321,12 @@
                         (= "TEXTAREA" (obj/get target "tagName")))]
        (st/emit! (ms/->KeyboardEvent :up key shift? ctrl? alt? meta? editing?))))))
 
-(defn on-mouse-move [viewport-ref zoom]
+(defn on-mouse-move []
   (let [last-position (mf/use-var nil)]
     (mf/use-callback
-     (mf/deps zoom)
      (fn [event]
-       (let [event    (.getBrowserEvent ^js event)
-             raw-pt   (dom/get-client-position event)
-             viewport (mf/ref-val viewport-ref)
-             pt       (utils/translate-point-to-viewport viewport zoom raw-pt)
+       (let [raw-pt   (dom/get-client-position event)
+             pt       (uwvv/point->viewport raw-pt)
 
              ;; We calculate the delta because Safari's MouseEvent.movementX/Y drop
              ;; events
@@ -350,30 +346,27 @@
                                       (kbd/alt? event)
                                       (kbd/meta? event))))))))
 
-(defn on-pointer-move [viewport-ref zoom move-stream]
+(defn on-pointer-move [move-stream]
   (mf/use-callback
-   (mf/deps zoom move-stream)
+   (mf/deps move-stream)
    (fn [event]
      (let [raw-pt (dom/get-client-position event)
-           viewport (mf/ref-val viewport-ref)
-           pt     (utils/translate-point-to-viewport viewport zoom raw-pt)]
+           pt     (uwvv/point->viewport raw-pt)]
        (rx/push! move-stream pt)))))
 
-(defn on-mouse-wheel [viewport-ref zoom]
+(defn on-mouse-wheel [zoom]
   (mf/use-callback
    (mf/deps zoom)
    (fn [event]
-     (let [viewport (mf/ref-val viewport-ref)
-           event  (.getBrowserEvent ^js event)
+     (let [event  (.getBrowserEvent ^js event)
            target (dom/get-target event)
            mod? (kbd/mod? event)]
 
-       (when (dom/is-child? viewport target)
+       (when (uwvv/inside-viewport? target)
          (dom/prevent-default event)
          (dom/stop-propagation event)
-         (let [pt     (->> (dom/get-client-position event)
-                           (utils/translate-point-to-viewport viewport zoom))
-
+         (let [raw-pt (dom/get-client-position event)
+               pt     (uwvv/point->viewport raw-pt)
                norm-event ^js (nw/normalize-wheel event)
                ctrl?  (kbd/ctrl? event)
                delta-y (.-pixelY norm-event)
@@ -413,14 +406,12 @@
        (dom/prevent-default e)))))
 
 (defn on-drop
-  [file viewport-ref zoom]
+  [file]
   (mf/use-fn
-   (mf/deps zoom)
    (fn [event]
      (dom/prevent-default event)
      (let [point (gpt/point (.-clientX event) (.-clientY event))
-           viewport (mf/ref-val viewport-ref)
-           viewport-coord (utils/translate-point-to-viewport viewport zoom point)
+           viewport-coord (uwvv/point->viewport point)
            asset-id     (-> (dnd/get-data event "text/asset-id") uuid/uuid)
            asset-name   (dnd/get-data event "text/asset-name")
            asset-type   (dnd/get-data event "text/asset-type")]
diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs
index fb20a66fb..5076cb86f 100644
--- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs
@@ -35,21 +35,29 @@
 (defn setup-dom-events [viewport-ref zoom disable-paste in-viewport? workspace-read-only?]
   (let [on-key-down       (actions/on-key-down)
         on-key-up         (actions/on-key-up)
-        on-mouse-move     (actions/on-mouse-move viewport-ref zoom)
-        on-mouse-wheel    (actions/on-mouse-wheel viewport-ref zoom)
+        on-mouse-move     (actions/on-mouse-move)
+        on-mouse-wheel    (actions/on-mouse-wheel zoom)
         on-paste          (actions/on-paste disable-paste in-viewport? workspace-read-only?)]
+
+    ;; We use the DOM listener because the goog.closure one forces reflow to generate its internal
+    ;; structure. As we don't need currently nothing from BrowserEvent we optimize by using the basic event
+    (mf/use-layout-effect
+     (mf/deps on-mouse-move)
+     (fn []
+       (let [node (mf/ref-val viewport-ref)]
+         (.addEventListener node "mousemove" on-mouse-move)
+         (fn []
+           (.removeEventListener node "mousemove" on-mouse-move)))))
+
     (mf/use-layout-effect
      (mf/deps on-key-down on-key-up on-mouse-move on-mouse-wheel on-paste workspace-read-only?)
      (fn []
-       (let [node (mf/ref-val viewport-ref)
-             keys [(events/listen js/document EventType.KEYDOWN on-key-down)
+       (let [keys [(events/listen js/document EventType.KEYDOWN on-key-down)
                    (events/listen js/document EventType.KEYUP on-key-up)
-                   (events/listen node EventType.MOUSEMOVE on-mouse-move)
                    ;; bind with passive=false to allow the event to be cancelled
                    ;; https://stackoverflow.com/a/57582286/3219895
                    (events/listen js/window EventType.WHEEL on-mouse-wheel #js {:passive false})
                    (events/listen js/window EventType.PASTE on-paste)]]
-
          (fn []
            (doseq [key keys]
              (events/unlistenByKey key))))))))
diff --git a/frontend/src/app/main/ui/workspace/viewport/scroll_bars.cljs b/frontend/src/app/main/ui/workspace/viewport/scroll_bars.cljs
index 9e6a823b7..75166092a 100644
--- a/frontend/src/app/main/ui/workspace/viewport/scroll_bars.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/scroll_bars.cljs
@@ -11,7 +11,7 @@
    [app.common.pages.helpers :as cph]
    [app.main.data.workspace :as dw]
    [app.main.store :as st]
-   [app.main.ui.workspace.viewport.utils :as utils]
+   [app.main.ui.workspace.viewport.viewport-ref :refer [point->viewport]]
    [app.util.dom :as dom]
    [rumext.v2 :as mf]))
 
@@ -27,7 +27,7 @@
 
 (mf/defc viewport-scrollbars
   {::mf/wrap [mf/memo]}
-  [{:keys [objects viewport-ref zoom vbox]}]
+  [{:keys [objects zoom vbox]}]
 
   (let [v-scrolling?              (mf/use-state false)
         h-scrolling?              (mf/use-state false)
@@ -126,10 +126,9 @@
         on-mouse-move
         (fn [event axis]
           (when-let [_ (or @v-scrolling? @h-scrolling?)]
-            (let [viewport            (mf/ref-val viewport-ref)
-                  start-pt            (mf/ref-val start-ref)
+            (let [start-pt            (mf/ref-val start-ref)
                   current-pt          (dom/get-client-position event)
-                  current-pt-viewport (utils/translate-point-to-viewport viewport zoom current-pt)
+                  current-pt-viewport (point->viewport current-pt)
                   y-delta               (/ (* (mf/ref-val height-factor-ref) (- (:y current-pt) (:y start-pt))) zoom)
                   x-delta               (/ (* (mf/ref-val width-factor-ref) (- (:x current-pt) (:x start-pt))) zoom)
                   new-v-scrollbar-y   (-> current-pt-viewport
@@ -150,9 +149,8 @@
 
         on-mouse-down
         (fn [event axis]
-          (let [viewport              (mf/ref-val viewport-ref)
-                start-pt              (dom/get-client-position event)
-                viewport-point        (utils/translate-point-to-viewport viewport zoom start-pt)
+          (let [start-pt              (dom/get-client-position event)
+                viewport-point        (point->viewport start-pt)
                 new-h-scrollbar-x     (:x viewport-point)
                 new-v-scrollbar-y     (:y viewport-point)
                 v-scrollbar-y-padding (- v-scrollbar-y new-v-scrollbar-y)
diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs
index 40b54f43c..59c411b4a 100644
--- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs
@@ -6,13 +6,11 @@
 
 (ns app.main.ui.workspace.viewport.utils
   (:require
-   [app.common.data :as d]
    [app.common.data.macros :as dm]
    [app.common.geom.point :as gpt]
    [app.common.geom.shapes :as gsh]
    [app.main.ui.cursors :as cur]
-   [app.main.ui.formats :refer [format-number]]
-   [app.util.dom :as dom]))
+   [app.main.ui.formats :refer [format-number]]))
 
 (defn format-viewbox [vbox]
   (dm/str (format-number(:x vbox 0)) " "
@@ -20,17 +18,6 @@
           (format-number (:width vbox 0)) " "
           (format-number (:height vbox 0))))
 
-(defn translate-point-to-viewport [viewport zoom pt]
-  (let [vbox     (.. ^js viewport -viewBox -baseVal)
-        brect    (dom/get-bounding-rect viewport)
-        brect    (gpt/point (d/parse-integer (:left brect))
-                            (d/parse-integer (:top brect)))
-        box      (gpt/point (.-x vbox) (.-y vbox))
-        zoom     (gpt/point zoom)]
-    (-> (gpt/subtract pt brect)
-        (gpt/divide zoom)
-        (gpt/add box))))
-
 (defn get-cursor [cursor]
   (case cursor
     :hand cur/hand
diff --git a/frontend/src/app/main/ui/workspace/viewport/viewport_ref.cljs b/frontend/src/app/main/ui/workspace/viewport/viewport_ref.cljs
new file mode 100644
index 000000000..aa293f191
--- /dev/null
+++ b/frontend/src/app/main/ui/workspace/viewport/viewport_ref.cljs
@@ -0,0 +1,65 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) KALEIDOS INC
+
+(ns app.main.ui.workspace.viewport.viewport-ref
+  (:require
+   [app.common.data :as d]
+   [app.common.data.macros :as dm]
+   [app.common.geom.point :as gpt]
+   [app.main.store :as st]
+   [app.util.dom :as dom]
+   [rumext.v2 :as mf]))
+
+(defonce viewport-ref (atom nil))
+(defonce current-observer (atom nil))
+(defonce viewport-brect (atom nil))
+
+(defn init-observer
+  [node on-change-bounds]
+
+  (let [observer (js/ResizeObserver. on-change-bounds)]
+    (when (some? @current-observer)
+      (.disconnect @current-observer))
+
+    (reset! current-observer observer)
+
+    (when (some? node)
+      (.observe observer node))))
+
+(defn on-change-bounds
+  [_]
+  (when @viewport-ref
+    (let [brect (dom/get-bounding-rect @viewport-ref)
+          brect (gpt/point (d/parse-integer (:left brect))
+                           (d/parse-integer (:top brect)))]
+      (reset! viewport-brect brect))))
+
+(defn create-viewport-ref
+  []
+  
+  (let [ref (mf/use-ref nil)]
+    [ref
+     (mf/use-memo
+      #(fn [node]
+         (mf/set-ref-val! ref node)
+         (reset! viewport-ref node)
+         (init-observer node on-change-bounds)))]))
+
+(defn point->viewport
+  [pt]
+  (let [zoom (dm/get-in @st/state [:workspace-local :zoom])]
+    (when (some? @viewport-ref)
+      (let [vbox     (.. ^js @viewport-ref -viewBox -baseVal)
+            brect    @viewport-brect
+            box      (gpt/point (.-x vbox) (.-y vbox))
+            zoom     (gpt/point zoom)]
+        (-> (gpt/subtract pt brect)
+            (gpt/divide zoom)
+            (gpt/add box))))))
+
+(defn inside-viewport?
+  [target]
+  (dom/is-child? @viewport-ref target))

From 98698cf2db43f56302323b3e5bb43a9a3e960abd Mon Sep 17 00:00:00 2001
From: "alonso.torres" <alonso.torres@kaleidos.net>
Date: Tue, 3 Jan 2023 16:58:10 +0100
Subject: [PATCH 2/5] :zap: Improved modifiers lens

---
 common/src/app/common/types/shape/layout.cljc | 12 +++---
 .../app/main/data/workspace/drawing/box.cljs  | 22 ++++++-----
 .../app/main/data/workspace/transforms.cljs   |  1 +
 frontend/src/app/main/refs.cljs               | 39 +++++++++++--------
 .../src/app/main/ui/workspace/shapes.cljs     |  4 +-
 5 files changed, 41 insertions(+), 37 deletions(-)

diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc
index 5a9ffcf97..534174444 100644
--- a/common/src/app/common/types/shape/layout.cljc
+++ b/common/src/app/common/types/shape/layout.cljc
@@ -100,15 +100,13 @@
    (and (= :frame (:type shape)) (= :flex (:layout shape)))))
 
 (defn layout-child? [objects shape]
-  (let [parent-id (:parent-id shape)
-        parent (get objects parent-id)]
-    (layout? parent)))
+  (let [frame-id (:frame-id shape)
+        frame (get objects frame-id)]
+    (layout? frame)))
 
 (defn layout-child-id? [objects id]
-  (let [shape (get objects id)
-        parent-id (:parent-id shape)
-        parent (get objects parent-id)]
-    (layout? parent)))
+  (let [shape (get objects id)]
+    (layout-child? objects shape)))
 
 (defn inside-layout?
   "Check if the shape is inside a layout"
diff --git a/frontend/src/app/main/data/workspace/drawing/box.cljs b/frontend/src/app/main/data/workspace/drawing/box.cljs
index 4c4c3346e..39971af7c 100644
--- a/frontend/src/app/main/data/workspace/drawing/box.cljs
+++ b/frontend/src/app/main/data/workspace/drawing/box.cljs
@@ -37,19 +37,21 @@
       (assoc :x (- (:x point) (* sx (- dy dx)))))))
 
 (defn resize-shape [{:keys [x y width height] :as shape} initial point lock?]
-  (let [draw-rect (gsh/make-rect initial (cond-> point lock? (adjust-ratio initial)))
-        shape-rect (gsh/make-rect x y width height)
+  (if (and (some? x) (some? y) (some? width) (some? height))
+    (let [draw-rect (gsh/make-rect initial (cond-> point lock? (adjust-ratio initial)))
+          shape-rect (gsh/make-rect x y width height)
 
-        scalev (gpt/point (/ (:width draw-rect) (:width shape-rect))
-                          (/ (:height draw-rect) (:height shape-rect)))
+          scalev (gpt/point (/ (:width draw-rect) (:width shape-rect))
+                            (/ (:height draw-rect) (:height shape-rect)))
 
-        movev (gpt/to-vec (gpt/point shape-rect) (gpt/point draw-rect))]
+          movev (gpt/to-vec (gpt/point shape-rect) (gpt/point draw-rect))]
 
-    (-> shape
-        (assoc :click-draw? false)
-        (gsh/transform-shape (-> (ctm/empty)
-                                 (ctm/resize scalev (gpt/point x y))
-                                 (ctm/move movev))))))
+      (-> shape
+          (assoc :click-draw? false)
+          (gsh/transform-shape (-> (ctm/empty)
+                                   (ctm/resize scalev (gpt/point x y))
+                                   (ctm/move movev)))))
+    shape))
 
 (defn update-drawing [state initial point lock?]
   (update-in state [:workspace-drawing :object] resize-shape initial point lock?))
diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs
index 848379901..2512826dc 100644
--- a/frontend/src/app/main/data/workspace/transforms.cljs
+++ b/frontend/src/app/main/data/workspace/transforms.cljs
@@ -219,6 +219,7 @@
 
           (rx/concat
            (->> ms/mouse-position
+                (rx/filter some?)
                 (rx/with-latest-from ms/mouse-position-shift ms/mouse-position-alt)
                 (rx/map normalize-proportion-lock)
                 (rx/switch-map (fn [[point _ _ :as current]]
diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs
index 858f8fb57..51a0becaa 100644
--- a/frontend/src/app/main/refs.cljs
+++ b/frontend/src/app/main/refs.cljs
@@ -324,11 +324,7 @@
   (l/derived :workspace-editor-state st/state))
 
 (def workspace-modifiers
-  (l/derived :workspace-modifiers st/state))
-
-(defn workspace-modifiers-by-id
-  [ids]
-  (l/derived #(select-keys % ids) workspace-modifiers))
+  (l/derived :workspace-modifiers st/state =))
 
 (def workspace-modifiers-with-objects
   (l/derived
@@ -340,20 +336,29 @@
      (and (= (:modifiers a) (:modifiers b))
           (identical? (:objects a) (:objects b))))))
 
-(defn workspace-modifiers-by-frame-id
-  [frame-id]
+(def workspace-frame-modifiers
   (l/derived
    (fn [{:keys [modifiers objects]}]
-     (let [keys (->> modifiers
-                     (keys)
-                     (filter (fn [id]
-                               (let [shape (get objects id)]
-                                 (or (= frame-id id)
-                                     (and (= frame-id (:frame-id shape))
-                                          (not (= :frame (:type shape)))))))))]
-       (select-keys modifiers keys)))
-   workspace-modifiers-with-objects
-   =))
+     (->> modifiers
+          (reduce
+           (fn [result [id modifiers]]
+             (let [shape (get objects id)
+                   frame-id (:frame-id shape)]
+               (cond
+                 (cph/frame-shape? shape)
+                 (assoc-in result [id id] modifiers)
+
+                 (some? frame-id)
+                 (assoc-in result [frame-id id] modifiers)
+
+                 :else
+                 result)))
+           {})))
+   workspace-modifiers-with-objects))
+
+(defn workspace-modifiers-by-frame-id
+  [frame-id]
+  (l/derived #(get % frame-id) workspace-frame-modifiers =))
 
 (defn select-bool-children [id]
   (l/derived (partial wsh/select-bool-children id) st/state =))
diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs
index 0b92d475e..35148dc16 100644
--- a/frontend/src/app/main/ui/workspace/shapes.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes.cljs
@@ -110,9 +110,7 @@
          :circle  [:> circle-wrapper opts]
          :svg-raw [:> svg-raw-wrapper opts]
          :bool    [:> bool-wrapper opts]
-
-         ;; Only used when drawing a new frame.
-         :frame [:> nested-frame-wrapper opts]
+         :frame   [:> nested-frame-wrapper opts]
 
          nil)])))
 

From 837b52aea114c91ca399bf132e2197e12ff51076 Mon Sep 17 00:00:00 2001
From: "alonso.torres" <alonso.torres@kaleidos.net>
Date: Tue, 3 Jan 2023 16:59:18 +0100
Subject: [PATCH 3/5] :zap: Improved performand for hug content in layout

---
 .../src/app/common/geom/shapes/modifiers.cljc | 110 ++++++++++++++----
 1 file changed, 86 insertions(+), 24 deletions(-)

diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc
index b1761aac5..4b11007a3 100644
--- a/common/src/app/common/geom/shapes/modifiers.cljc
+++ b/common/src/app/common/geom/shapes/modifiers.cljc
@@ -28,6 +28,16 @@
 ;;                           [(get-in objects [k :name]) v]))
 ;;                    modif-tree))))
 
+(defn children-sequence
+  "Given an id returns a sequence of its children"
+  [id objects]
+
+  (->> (tree-seq
+        #(d/not-empty? (dm/get-in objects [% :shapes]))
+        #(dm/get-in objects [% :shapes])
+        id)
+       (map #(get objects %))))
+
 (defn resolve-tree-sequence
   "Given the ids that have changed search for layout roots to recalculate"
   [ids objects]
@@ -75,20 +85,12 @@
 
                 (cond-> result
                   (not contains-parent?)
-                  (conj root)))))
-
-          (generate-tree ;; Generate a tree sequence from a given root id
-            [id]
-            (->> (tree-seq
-                  #(d/not-empty? (dm/get-in objects [% :shapes]))
-                  #(dm/get-in objects [% :shapes])
-                  id)
-                 (map #(get objects %))))]
+                  (conj root)))))]
 
     (let [roots (->> ids (reduce calculate-common-roots #{}))]
       (concat
        (when (contains? ids uuid/zero) [(get objects uuid/zero)])
-       (mapcat generate-tree roots)))))
+       (mapcat #(children-sequence % objects) roots)))))
 
 (defn- set-children-modifiers
   "Propagates the modifiers from a parent too its children applying constraints if necesary"
@@ -296,28 +298,88 @@
              result (assoc result (:id shape) new-bounds)]
          (recur result (rest shapes)))))))
 
+(defn reflow-layout
+  [objects old-modif-tree bounds ignore-constraints id]
+
+  (let [tree-seq (children-sequence id objects)
+
+        [modif-tree _]
+        (reduce
+         #(propagate-modifiers-layout objects bounds ignore-constraints %1 %2) [{} #{}]
+         tree-seq)
+
+        bounds (transform-bounds bounds objects modif-tree tree-seq)
+
+        modif-tree (merge-modif-tree old-modif-tree modif-tree)]
+    [modif-tree bounds]))
+
 (defn sizing-auto-modifiers
   "Recalculates the layouts to adjust the sizing: auto new sizes"
   [modif-tree sizing-auto-layouts objects bounds ignore-constraints]
-  (loop [modif-tree modif-tree
-         bounds bounds
-         sizing-auto-layouts (reverse sizing-auto-layouts)]
-    (if-let [current (first sizing-auto-layouts)]
-      (let [parent-base (get objects current)
+  (let [;; Step-1 resize the auto-width/height. Reflow the parents if they are also auto-width/height
+        [modif-tree bounds to-reflow]
+        (loop [modif-tree modif-tree
+               bounds bounds
+               sizing-auto-layouts (reverse sizing-auto-layouts)
+               to-reflow #{}]
+          (if-let [current (first sizing-auto-layouts)]
+            (let [parent-base (get objects current)
 
-            resize-modif-tree
-            {current {:modifiers (calc-auto-modifiers objects bounds parent-base)}}
+                  [modif-tree bounds]
+                  (if (contains? to-reflow current)
+                    (reflow-layout objects modif-tree bounds ignore-constraints current)
+                    [modif-tree bounds])
 
-            tree-seq (resolve-tree-sequence #{current} objects)
+                  auto-resize-modifiers
+                  (calc-auto-modifiers objects bounds parent-base)
 
-            [resize-modif-tree _]
-            (reduce #(propagate-modifiers-layout objects bounds ignore-constraints %1 %2) [resize-modif-tree #{}] tree-seq)
+                  to-reflow
+                  (cond-> to-reflow
+                    (contains? to-reflow current)
+                    (disj current))]
 
-            bounds (transform-bounds bounds objects resize-modif-tree tree-seq)
+              (if (ctm/empty? auto-resize-modifiers)
+                (recur modif-tree
+                       bounds
+                       (rest sizing-auto-layouts)
+                       to-reflow)
 
-            modif-tree (merge-modif-tree modif-tree resize-modif-tree)]
-        (recur modif-tree bounds (rest sizing-auto-layouts)))
-      modif-tree)))
+                (let [resize-modif-tree {current {:modifiers auto-resize-modifiers}}
+
+                      tree-seq (children-sequence current objects)
+
+                      [resize-modif-tree _]
+                      (reduce
+                       #(propagate-modifiers-layout objects bounds ignore-constraints %1 %2) [resize-modif-tree #{}]
+                       tree-seq)
+
+                      bounds (transform-bounds bounds objects resize-modif-tree tree-seq)
+
+                      modif-tree (merge-modif-tree modif-tree resize-modif-tree)
+
+                      to-reflow
+                      (cond-> to-reflow
+                        (and (ctl/layout-child-id? objects current)
+                             (not= uuid/zero (:frame-id parent-base)))
+                        (conj (:frame-id parent-base)))]
+                  (recur modif-tree
+                         bounds
+                         (rest sizing-auto-layouts)
+                         to-reflow))))
+            [modif-tree bounds to-reflow]))
+
+        ;; Step-2: After resizing we still need to reflow the layout parents that are not auto-width/height
+
+        tree-seq (resolve-tree-sequence to-reflow objects)
+
+        [reflow-modif-tree _]
+        (reduce
+         #(propagate-modifiers-layout objects bounds ignore-constraints %1 %2) [{} #{}]
+         tree-seq)
+
+        result (merge-modif-tree modif-tree reflow-modif-tree)]
+
+    result))
 
 (defn set-objects-modifiers
   ([modif-tree objects ignore-constraints snap-pixel?]

From 84e9f6921351323c8a15533493b9c47f633ecd45 Mon Sep 17 00:00:00 2001
From: "alonso.torres" <alonso.torres@kaleidos.net>
Date: Wed, 4 Jan 2023 16:12:00 +0100
Subject: [PATCH 4/5] :zap: Improved text rendering performance

---
 .../src/app/main/data/workspace/texts.cljs    |  12 +-
 .../app/main/data/workspace/transforms.cljs   |  14 +-
 frontend/src/app/main/streams.cljs            |  10 ++
 .../shapes/text/viewport_texts_html.cljs      | 127 ++++++++++--------
 .../workspace/sidebar/options/menus/blur.cljs |  38 ++++--
 .../src/app/main/ui/workspace/viewport.cljs   |  29 +++-
 frontend/src/app/util/text_svg_position.cljs  |   3 +-
 7 files changed, 148 insertions(+), 85 deletions(-)

diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs
index 1d4c0098f..3355bfc3f 100644
--- a/frontend/src/app/main/data/workspace/texts.cljs
+++ b/frontend/src/app/main/data/workspace/texts.cljs
@@ -415,10 +415,14 @@
   [id]
   (ptk/reify ::clean-text-modifier
     ptk/WatchEvent
-    (watch [_ _ _]
-      (->> (rx/of #(update % :workspace-text-modifier dissoc id))
-           ;; We delay a bit the change so there is no weird transition to the user
-           (rx/delay 50)))))
+    (watch [_ state _]
+      (let [current-value (dm/get-in state [:workspace-text-modifier id])]
+        ;; We only dissocc the value when hasn't change after a time
+        (->> (rx/of (fn [state]
+                      (cond-> state
+                        (identical? (dm/get-in state [:workspace-text-modifier id]) current-value)
+                        (update :workspace-text-modifier dissoc id))))
+             (rx/delay 100))))))
 
 (defn remove-text-modifier
   [id]
diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs
index 2512826dc..b61f63749 100644
--- a/frontend/src/app/main/data/workspace/transforms.cljs
+++ b/frontend/src/app/main/data/workspace/transforms.cljs
@@ -601,9 +601,17 @@
                 move-events (->> stream
                                  (rx/filter (ptk/type? ::nudge-selected-shapes))
                                  (rx/filter #(= direction (deref %))))
-                stopper (->> move-events
-                             (rx/debounce 100)
-                             (rx/take 1))
+
+                stopper
+                (->> move-events
+                     ;; We stop when there's been 1s without movement or after 250ms after a key-up
+                     (rx/switch-map #(rx/merge
+                                      (rx/timer 1000)
+                                      (->> stream
+                                           (rx/filter ms/key-up?)
+                                           (rx/delay 250))))
+                     (rx/take 1))
+
                 scale (if shift? (gpt/point (or (:big nudge) 10)) (gpt/point (or (:small nudge) 1)))
                 mov-vec (gpt/multiply (get-displacement direction) scale)]
 
diff --git a/frontend/src/app/main/streams.cljs b/frontend/src/app/main/streams.cljs
index 1a4cb1069..a586077a9 100644
--- a/frontend/src/app/main/streams.cljs
+++ b/frontend/src/app/main/streams.cljs
@@ -21,6 +21,16 @@
   [v]
   (instance? KeyboardEvent v))
 
+(defn key-up?
+  [v]
+  (and (keyboard-event? v)
+       (= :up (:type v))))
+
+(defn key-down?
+  [v]
+  (and (keyboard-event? v)
+       (= :down (:type v))))
+
 (defrecord MouseEvent [type ctrl shift alt meta])
 
 (defn mouse-event?
diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs
index 7658de673..bfd704d7d 100644
--- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs
@@ -30,29 +30,16 @@
    [promesa.core :as p]
    [rumext.v2 :as mf]))
 
-(defn strip-position-data [shape]
-  (-> shape
-      (cond-> (some? (meta (:position-data shape)))
-        (with-meta (meta (:position-data shape))))
-      (dissoc :position-data)))
-
-(defn fix-position [shape modifier]
-  (let [shape' (gsh/transform-shape shape modifier)
+(defn fix-position [shape]
+  (let [modifiers (:modifiers shape)
+        shape' (gsh/transform-shape shape modifiers)
         ;; We need to remove the movement because the dynamic modifiers will have move it
         deltav (gpt/to-vec (gpt/point (:selrect shape'))
                            (gpt/point (:selrect shape)))]
     (-> shape
-        (gsh/transform-shape (ctm/move modifier deltav))
-        (mdwm/update-grow-type shape))))
-
-(defn process-shape [modifiers {:keys [id] :as shape}]
-  (let [modifier (dm/get-in modifiers [id :modifiers])]
-    (-> shape
-        (cond-> (and (some? modifier) (not (ctm/only-move? modifier)))
-          (fix-position modifier))
-        (cond-> (nil? (:position-data shape))
-          (assoc :migrate true))
-        strip-position-data)))
+        (gsh/transform-shape (ctm/move modifiers deltav))
+        (mdwm/update-grow-type shape)
+        (dissoc :modifiers))))
 
 (defn- update-with-editor-state
   "Updates the shape with the current state in the editor"
@@ -87,6 +74,7 @@
                    (not (mth/almost-zero? height))
                    (not migrate))
           (st/emit! (dwt/resize-text id width height)))))
+
     (st/emit! (dwt/clean-text-modifier id))))
 
 (defn- update-text-modifier
@@ -136,8 +124,8 @@
   (or (identical? shape other)
       (and
        ;; Check if both shapes are equivalent removing their geometry data
-       (= (dissoc shape :migrate :points :selrect :height :width :x :y)
-          (dissoc other :migrate :points :selrect :height :width :x :y))
+       (= (dissoc shape :migrate :points :selrect :height :width :x :y :position-data :modifiers)
+          (dissoc other :migrate :points :selrect :height :width :x :y :position-data :modifiers))
 
        ;; Check if the position and size is close. If any of these changes the shape has changed
        ;; and if not there is no geometry relevant change
@@ -146,55 +134,69 @@
        (mth/close? (:width shape) (:width other))
        (mth/close? (:height shape) (:height other)))))
 
-(mf/defc viewport-texts-wrapper
-  {::mf/wrap-props false
-   ::mf/wrap [mf/memo #(mf/deferred % ts/idle-then-raf)]}
+(mf/defc text-changes-renderer
+  {::mf/wrap-props false}
   [props]
   (let [text-shapes (obj/get props "text-shapes")
-        modifiers (obj/get props "modifiers")
-        prev-modifiers (hooks/use-previous modifiers)
         prev-text-shapes (hooks/use-previous text-shapes)
 
-        ;; A change in position-data won't be a "real" change
         text-change?
         (fn [id]
           (let [new-shape (get text-shapes id)
                 old-shape (get prev-text-shapes id)
-                old-modifiers (ctm/select-geometry (get prev-modifiers id))
-                new-modifiers (ctm/select-geometry (get modifiers id))
-
                 remote? (some? (-> new-shape meta :session-id))]
-            (or (and (not remote?)
+
+            (or (and (not remote?) ;; changes caused by a remote peer are not re-calculated
                      (not (text-properties-equal? old-shape new-shape)))
-
-                (and (not= new-modifiers old-modifiers)
-                     (or (ctm/empty? new-modifiers)
-                         (ctm/empty? old-modifiers)))
-
-                (and (not= new-modifiers old-modifiers)
-                     (or (not (ctm/only-move? new-modifiers))
-                         (not (ctm/only-move? old-modifiers))))
-
                 ;; When the position data is nil we force to recalculate
-                (:migrate new-shape))))
+                (nil? (:position-data new-shape)))))
 
         changed-texts
         (mf/use-memo
-         (mf/deps text-shapes modifiers)
+         (mf/deps text-shapes)
          #(->> (keys text-shapes)
                (filter text-change?)
                (map (d/getf text-shapes))))
 
-        handle-update-modifier (mf/use-callback update-text-modifier)
         handle-update-shape (mf/use-callback update-text-shape)]
 
-    [:*
+    [:.text-changes-renderer
      (for [{:keys [id] :as shape} changed-texts]
-       [:& text-container {:shape shape
-                           :on-update (if (some? (get modifiers (:id shape)))
-                                        handle-update-modifier
-                                        handle-update-shape)
-                           :key (str (dm/str "text-container-" id))}])]))
+       [:& text-container {:key (str (dm/str "text-container-" id))
+                           :shape shape
+                           :on-update handle-update-shape}])]))
+
+(mf/defc text-modifiers-renderer
+  {::mf/wrap-props false}
+  [props]
+  (let [text-shapes (-> (obj/get props "text-shapes")
+                        (update-vals fix-position))
+
+        prev-text-shapes (hooks/use-previous text-shapes)
+
+        text-change?
+        (fn [id]
+          (let [new-shape (get text-shapes id)
+                old-shape (get prev-text-shapes id)]
+            (and
+             (some? new-shape)
+             (some? old-shape)
+             (not (text-properties-equal? old-shape new-shape)))))
+
+        changed-texts
+        (mf/use-memo
+         (mf/deps text-shapes)
+         #(->> (keys text-shapes)
+               (filter text-change?)
+               (map (d/getf text-shapes))))
+
+        handle-update-shape (mf/use-callback update-text-modifier)]
+
+    [:.text-changes-renderer
+     (for [{:keys [id] :as shape} changed-texts]
+       [:& text-container {:key (str (dm/str "text-container-" id))
+                           :shape shape
+                           :on-update handle-update-shape}])]))
 
 (mf/defc viewport-text-editing
   {::mf/wrap-props false}
@@ -256,21 +258,30 @@
         text-shapes
         (mf/use-memo
          (mf/deps objects)
-         #(into {} (filter (comp cph/text-shape? second)) objects))
+         (fn []
+           (into {} (filter (comp cph/text-shape? second)) objects)))
 
         text-shapes
-        (mf/use-memo
-         (mf/deps text-shapes modifiers)
-         #(update-vals text-shapes (partial process-shape modifiers)))
+        (hooks/use-equal-memo text-shapes)
 
         editing-shape (get text-shapes edition)
 
-        ;; This memo is necessary so the viewport-text-wrapper memoize its props correctly
-        text-shapes-wrapper
+        text-shapes-changes
         (mf/use-memo
          (mf/deps text-shapes edition)
          (fn []
-           (dissoc text-shapes edition)))]
+           (-> text-shapes
+               (dissoc edition))))
+
+        text-shapes-modifiers
+        (mf/use-memo
+         (mf/deps modifiers text-shapes)
+         (fn []
+           (into {}
+                 (keep (fn [[id modifiers]]
+                         (when-let [shape (get text-shapes id)]
+                           (vector id (merge shape modifiers)))))
+                 modifiers)))]
 
     ;; We only need the effect to run on "mount" because the next fonts will be changed when the texts are
     ;; edited
@@ -284,5 +295,5 @@
      (when editing-shape
        [:& viewport-text-editing {:shape editing-shape}])
 
-     [:& viewport-texts-wrapper {:text-shapes text-shapes-wrapper
-                                 :modifiers modifiers}]]))
+     [:& text-modifiers-renderer {:text-shapes text-shapes-modifiers}]
+     [:& text-changes-renderer {:text-shapes text-shapes-changes}]]))
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs
index 64a25b603..8b6eb7895 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs
@@ -28,28 +28,40 @@
         has-value? (not (nil? blur))
         multiple? (= blur :multiple)
 
-        change! (fn [update-fn] (st/emit! (dch/update-shapes ids update-fn)))
+        change!
+        (mf/use-callback
+         (mf/deps ids)
+         (fn [update-fn]
+           (st/emit! (dch/update-shapes ids update-fn))))
 
         handle-add
-        (fn []
-          (change! #(assoc % :blur (create-blur))))
+        (mf/use-callback
+         (mf/deps change!)
+         (fn []
+           (change! #(assoc % :blur (create-blur)))))
 
         handle-delete
-        (fn []
-          (change! #(dissoc % :blur)))
+        (mf/use-callback
+         (mf/deps change!)
+         (fn []
+           (change! #(dissoc % :blur))))
 
         handle-change
-        (fn [value]
-          (change! #(cond-> %
-                      (not (contains? % :blur))
-                      (assoc :blur (create-blur))
+        (mf/use-callback
+         (mf/deps change!)
+         (fn [value]
+           (change! #(cond-> %
+                       (not (contains? % :blur))
+                       (assoc :blur (create-blur))
 
-                      :always
-                      (assoc-in [:blur :value] value))))
+                       :always
+                       (assoc-in [:blur :value] value)))))
 
         handle-toggle-visibility
-        (fn []
-          (change! #(update-in % [:blur :hidden] not)))]
+        (mf/use-callback
+         (mf/deps change!)
+         (fn []
+           (change! #(update-in % [:blur :hidden] not))))]
 
     [:div.element-set
      [:div.element-set-title
diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs
index 81b2ecec9..da5d13c89 100644
--- a/frontend/src/app/main/ui/workspace/viewport.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport.cljs
@@ -12,6 +12,7 @@
    [app.common.geom.shapes :as gsh]
    [app.common.pages.helpers :as cph]
    [app.common.types.shape.layout :as ctl]
+   [app.main.data.workspace.modifiers :as dwm]
    [app.main.refs :as refs]
    [app.main.ui.context :as ctx]
    [app.main.ui.hooks :as ui-hooks]
@@ -48,6 +49,20 @@
 
 ;; --- Viewport
 
+(defn apply-modifiers-to-selected
+  [selected objects text-modifiers modifiers]
+  (into []
+        (comp
+         (keep (d/getf objects))
+         (map (fn [{:keys [id] :as shape}]
+                (cond-> shape
+                  (and (cph/text-shape? shape) (contains? text-modifiers id))
+                  (dwm/apply-text-modifier (get text-modifiers id))
+
+                  (contains? modifiers id)
+                  (gsh/transform-shape (dm/get-in modifiers [id :modifiers]))))))
+        selected))
+
 (mf/defc viewport
   [{:keys [wlocal wglobal selected layout file] :as props}]
   (let [;; When adding data from workspace-local revisit `app.main.ui.workspace` to check
@@ -80,6 +95,7 @@
         base-objects      (-> objects (ui-hooks/with-focus-objects focus))
 
         modifiers         (mf/deref refs/workspace-modifiers)
+        text-modifiers    (mf/deref refs/workspace-text-modifier)
 
         objects-modified  (mf/with-memo [base-objects modifiers]
                             (gsh/apply-objects-modifiers base-objects modifiers selected))
@@ -120,7 +136,8 @@
         drawing-tool      (:tool drawing)
         drawing-obj       (:object drawing)
 
-        selected-shapes   (into [] (keep (d/getf objects-modified)) selected)
+        selected-shapes   (apply-modifiers-to-selected selected base-objects text-modifiers modifiers)
+
         selected-frames   (into #{} (map :frame-id) selected-shapes)
 
         ;; Only when we have all the selected shapes in one frame
@@ -303,7 +320,7 @@
                outlined-frame (get objects outlined-frame-id)]
            [:*
             [:& outline/shape-outlines
-             {:objects objects-modified
+             {:objects base-objects
               :hover #{outlined-frame-id}
               :zoom zoom
               :modifiers modifiers}]
@@ -443,25 +460,25 @@
        ;; DEBUG LAYOUT DROP-ZONES
        (when (debug? :layout-drop-zones)
          [:& wvd/debug-drop-zones {:selected-shapes selected-shapes
-                                   :objects objects-modified
+                                   :objects base-objects
                                    :hover-top-frame-id @hover-top-frame-id
                                    :zoom zoom}])
 
        (when (debug? :layout-content-bounds)
          [:& wvd/debug-content-bounds {:selected-shapes selected-shapes
-                                       :objects objects-modified
+                                       :objects base-objects
                                        :hover-top-frame-id @hover-top-frame-id
                                        :zoom zoom}])
 
        (when (debug? :layout-lines)
          [:& wvd/debug-layout-lines {:selected-shapes selected-shapes
-                                     :objects objects-modified
+                                     :objects base-objects
                                      :hover-top-frame-id @hover-top-frame-id
                                      :zoom zoom}])
 
        (when (debug? :parent-bounds)
          [:& wvd/debug-parent-bounds {:selected-shapes selected-shapes
-                                      :objects objects-modified
+                                      :objects base-objects
                                       :hover-top-frame-id @hover-top-frame-id
                                       :zoom zoom}])
 
diff --git a/frontend/src/app/util/text_svg_position.cljs b/frontend/src/app/util/text_svg_position.cljs
index ec0f9e003..a99a57c6c 100644
--- a/frontend/src/app/util/text_svg_position.cljs
+++ b/frontend/src/app/util/text_svg_position.cljs
@@ -63,7 +63,8 @@
   [shape-id]
 
   (when (some? shape-id)
-    (let [text-nodes (dom/query-all (dm/str "#html-text-node-" shape-id " .text-node"))
+    (let [text-nodes (-> (dom/query (dm/fmt "#html-text-node-%" shape-id))
+                         (dom/query-all ".text-node"))
           load-fonts (->> text-nodes (map resolve-font))
 
           process-text-node

From 10439934d496cae64f727b2cadf2580645e4d9a6 Mon Sep 17 00:00:00 2001
From: "alonso.torres" <alonso.torres@kaleidos.net>
Date: Wed, 4 Jan 2023 16:18:12 +0100
Subject: [PATCH 5/5] :zap: Use the function `hypot` for distances

---
 common/src/app/common/geom/point.cljc       | 6 ++----
 common/src/app/common/geom/shapes/path.cljc | 2 +-
 common/src/app/common/math.cljc             | 9 ++++++++-
 frontend/src/app/util/svg.cljs              | 3 +--
 4 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/common/src/app/common/geom/point.cljc b/common/src/app/common/geom/point.cljc
index bfc55dc6d..5dd41501e 100644
--- a/common/src/app/common/geom/point.cljc
+++ b/common/src/app/common/geom/point.cljc
@@ -170,8 +170,7 @@
               (dm/get-prop p2 :x))
         dy (- (dm/get-prop p1 :y)
               (dm/get-prop p2 :y))]
-    (mth/sqrt (+ (mth/pow dx 2)
-                 (mth/pow dy 2)))))
+    (mth/hypot dx dy)))
 
 (defn distance-vector
   "Calculate the distance, separated x and y."
@@ -191,8 +190,7 @@
   (assert (point? pt) "point instance expected")
   (let [x (dm/get-prop pt :x)
         y (dm/get-prop pt :y)]
-    (mth/sqrt (+ (mth/pow x 2)
-                 (mth/pow y 2)))))
+    (mth/hypot x y)))
 
 (defn angle
   "Returns the smaller angle between two vectors.
diff --git a/common/src/app/common/geom/shapes/path.cljc b/common/src/app/common/geom/shapes/path.cljc
index 9178f2774..830e98e76 100644
--- a/common/src/app/common/geom/shapes/path.cljc
+++ b/common/src/app/common/geom/shapes/path.cljc
@@ -117,7 +117,7 @@
         [x y] (->> coords (mapv solve-derivative))
 
         ;; normalize value
-        d (mth/sqrt (+ (* x x) (* y y)))]
+        d (mth/hypot x y)]
 
     (if (mth/almost-zero? d)
       (gpt/point 0 0)
diff --git a/common/src/app/common/math.cljc b/common/src/app/common/math.cljc
index eee928bd9..03525d4d3 100644
--- a/common/src/app/common/math.cljc
+++ b/common/src/app/common/math.cljc
@@ -139,12 +139,18 @@
   #?(:cljs (math/toDegrees radians)
      :clj (Math/toDegrees radians)))
 
+(defn hypot
+  "Square root of the squares addition"
+  [a b]
+  #?(:cljs (js/Math.hypot a b)
+     :clj (Math/hypot a b)))
+
 (defn distance
   "Calculate the distance between two points."
   [[x1 y1] [x2 y2]]
   (let [dx (- x1 x2)
         dy (- y1 y2)]
-    (-> (sqrt (+ (pow dx 2) (pow dy 2)))
+    (-> (hypot dx dy)
         (precision 2))))
 
 (defn log10
@@ -182,3 +188,4 @@
   "Get the sign (+1 / -1) for the number"
   [n]
   (if (neg? n) -1 1))
+
diff --git a/frontend/src/app/util/svg.cljs b/frontend/src/app/util/svg.cljs
index 8056ddd33..04b23ebf0 100644
--- a/frontend/src/app/util/svg.cljs
+++ b/frontend/src/app/util/svg.cljs
@@ -890,8 +890,7 @@
 (defn calculate-ratio
   ;;  sqrt((actual-width)**2 + (actual-height)**2)/sqrt(2).
   [width height]
-  (/ (mth/sqrt (+ (mth/pow width 2)
-                  (mth/pow height 2)))
+  (/ (mth/hypot width height)
      (mth/sqrt 2)))
 
 (defn fix-percents