From 58f788455f7f165691938b8a2f5c776dcecc4260 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 28 Aug 2023 15:36:23 +0200 Subject: [PATCH] :zap: Add experimental equality with exceptions props checking to frames --- common/src/app/common/record.cljc | 50 +++++++++++++++---- .../src/app/main/ui/workspace/shapes.cljs | 46 +++++++++-------- .../app/main/ui/workspace/shapes/frame.cljs | 28 +++++++++-- 3 files changed, 90 insertions(+), 34 deletions(-) diff --git a/common/src/app/common/record.cljc b/common/src/app/common/record.cljc index db808fa87..97c25cc4a 100644 --- a/common/src/app/common/record.cljc +++ b/common/src/app/common/record.cljc @@ -36,12 +36,17 @@ :else `(. ~this-sym ~(property-symbol field)))) fields))) + +(defprotocol ICustomRecordEquiv + (-equiv-with-exceptions [_ other exceptions])) + #?(:clj (defn emit-impl-js [tagname base-fields] (let [fields (conj base-fields '$meta '$extmap (with-meta '$hash {:mutable true})) key-sym (gensym "key-") val-sym (gensym "val-") + othr-sym (with-meta 'other {:tag tagname}) this-sym (with-meta 'this {:tag tagname})] ['cljs.core/IRecord 'cljs.core/ICloneable @@ -58,16 +63,41 @@ (. ~this-sym ~'-$hash))) 'cljs.core/IEquiv - `(~'-equiv [~this-sym ~val-sym] - (and (some? ~val-sym) - (identical? (.-constructor ~this-sym) - (.-constructor ~val-sym)) - ~@(map (fn [field] - `(= (.. ~this-sym ~(property-symbol field)) - (.. ~(with-meta val-sym {:tag tagname}) ~(property-symbol field)))) - base-fields) - (= (. ~this-sym ~'-$extmap) - (. ~(with-meta val-sym {:tag tagname}) ~'-$extmap)))) + `(~'-equiv [~this-sym ~othr-sym] + (or (identical? ~this-sym ~othr-sym) + (and (some? ~othr-sym) + (identical? (.-constructor ~this-sym) + (.-constructor ~othr-sym)) + ~@(map (fn [field] + `(= (.. ~this-sym ~(property-symbol field)) + (.. ~(with-meta othr-sym {:tag tagname}) ~(property-symbol field)))) + base-fields) + + (= (. ~this-sym ~'-$extmap) + (. ~(with-meta othr-sym {:tag tagname}) ~'-$extmap))))) + + `ICustomRecordEquiv + `(~'-equiv-with-exceptions [~this-sym ~othr-sym ~'exceptions] + (or (identical? ~this-sym ~othr-sym) + (and (some? ~othr-sym) + (identical? (.-constructor ~this-sym) + (.-constructor ~othr-sym)) + (and ~@(->> base-fields + (map (fn [field] + `(= (.. ~this-sym ~(property-symbol field)) + (.. ~(with-meta othr-sym {:tag tagname}) ~(property-symbol field)))))) + (== (count (. ~this-sym ~'-$extmap)) + (count (. ~othr-sym ~'-$extmap)))) + + (reduce-kv (fn [~'_ ~'k ~'v] + (if (contains? ~'exceptions ~'k) + true + (if (= (get (. ~this-sym ~'-$extmap) ~'k ::not-exists) ~'v) + true + (reduced false)))) + true + (. ~othr-sym ~'-$extmap))))) + 'cljs.core/IMeta `(~'-meta [~this-sym] (. ~this-sym ~'-$meta)) diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs index 36f02ddc3..2db204d3e 100644 --- a/frontend/src/app/main/ui/workspace/shapes.cljs +++ b/frontend/src/app/main/ui/workspace/shapes.cljs @@ -68,46 +68,52 @@ [:g.frame-children (for [shape shapes] - [:g.ws-shape-wrapper {:key (:id shape)} - (cond - (not (cph/frame-shape? shape)) + [:g.ws-shape-wrapper {:key (dm/str (dm/get-prop shape :id))} + (if (not ^boolean (cph/frame-shape? shape)) [:& shape-wrapper {:shape shape}] + (if ^boolean (cph/is-direct-child-of-root? shape) + [:& root-frame-wrapper + {:shape shape + :objects (get frame-objects (dm/get-prop shape :id)) + :thumbnail? (not (contains? active-frames (dm/get-prop shape :id)))}] + [:& nested-frame-wrapper + {:shape shape + :objects (get frame-objects (dm/get-prop shape :id))}]))])]]])) - (cph/is-direct-child-of-root? shape) - [:& root-frame-wrapper - {:shape shape - :objects (get frame-objects (:id shape)) - :thumbnail? (not (contains? active-frames (:id shape)))}] - - :else - [:& nested-frame-wrapper - {:shape shape - :objects (get frame-objects (:id shape))}])])]]])) +(defn- check-shape-wrapper-props + [np op] + (frame/check-shape (unchecked-get np "shape") + (unchecked-get op "shape"))) (mf/defc shape-wrapper - {::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))] + {::mf/wrap [#(mf/memo' % check-shape-wrapper-props)] ::mf/wrap-props false} [props] - (let [shape (obj/get props "shape") + (let [shape (unchecked-get props "shape") + shape-type (dm/get-prop shape :type) + shape-id (dm/get-prop shape :id) + ;; FIXME: WARN: this breaks react rule of hooks (hooks can't be under conditional) active-frames - (when (cph/is-direct-child-of-root? shape) (mf/use-ctx ctx/active-frames)) + (when (cph/is-direct-child-of-root? shape) + (mf/use-ctx ctx/active-frames)) thumbnail? (and (some? active-frames) - (not (contains? active-frames (:id shape)))) + (not (contains? active-frames shape-id))) opts #js {:shape shape :thumbnail? thumbnail?} [wrapper wrapper-props] - (if (= :svg-raw (:type shape)) + (if (= :svg-raw shape-type) [mf/Fragment nil] ["g" #js {:className "workspace-shape-wrapper"}])] - (when (and (some? shape) (not (:hidden shape))) + (when (and (some? shape) + (not ^boolean (:hidden shape))) [:> wrapper wrapper-props - (case (:type shape) + (case shape-type :path [:> path/path-wrapper opts] :text [:> text/text-wrapper opts] :group [:> group-wrapper opts] diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index 87c216d03..24437dc94 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.pages.helpers :as cph] + [app.common.record :as cr] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.thumbnails :as dwt] [app.main.refs :as refs] @@ -25,17 +26,36 @@ [beicon.core :as rx] [rumext.v2 :as mf])) +(def ^:private excluded-attrs + #{:blocked + :hide-fill-on-export + :collapsed + :remote-synced + :exports}) + +(defn check-shape + [new-shape old-shape] + (cr/-equiv-with-exceptions old-shape new-shape excluded-attrs)) + +(defn check-frame-props + [np op] + (check-shape (unchecked-get np "shape") + (unchecked-get op "shape"))) + (defn frame-shape-factory [shape-wrapper] (let [frame-shape (frame/frame-shape shape-wrapper)] (mf/fnc frame-shape-inner - {::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))] + {::mf/wrap [#(mf/memo' % check-frame-props)] ::mf/wrap-props false ::mf/forward-ref true} [props ref] (let [shape (unchecked-get props "shape") - childs-ref (mf/use-memo (mf/deps (:id shape)) #(refs/children-objects (:id shape))) + shape-id (dm/get-prop shape :id) + + childs-ref (mf/with-memo [shape-id] + (refs/children-objects shape-id)) childs (mf/deref childs-ref)] [:& (mf/provider embed/context) {:value true} @@ -46,8 +66,8 @@ [new-props old-props] (and (= (unchecked-get new-props "thumbnail?") (unchecked-get old-props "thumbnail?")) - (= (unchecked-get new-props "shape") - (unchecked-get old-props "shape")))) + (check-shape (unchecked-get new-props "shape") + (unchecked-get old-props "shape")))) (defn nested-frame-wrapper-factory [shape-wrapper]