diff --git a/frontend/deps.edn b/frontend/deps.edn index 71119fdfa..a64dfdb00 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -13,8 +13,8 @@ funcool/tubax {:mvn/version "2021.05.20-0"} funcool/rumext - {:git/tag "v2.6" - :git/sha "97203a5" + {:git/tag "v2.7" + :git/sha "37fa860" :git/url "https://github.com/funcool/rumext.git" } diff --git a/frontend/package.json b/frontend/package.json index a5a347c33..c5201325b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -64,8 +64,8 @@ "opentype.js": "^1.3.4", "postcss-modules": "^6.0.0", "randomcolor": "^0.6.2", - "react": "~17.0.2", - "react-dom": "~17.0.2", + "react": "^18.2.0", + "react-dom": "^18.2.0", "react-virtualized": "^9.22.3", "rxjs": "~7.8.1", "sax": "^1.2.4", diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index 801856512..61764331c 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -45,10 +45,18 @@ (declare reinit) +(defonce app-root + (let [el (dom/get-element "app")] + (mf/create-root el))) + +(defonce modal-root + (let [el (dom/get-element "modal")] + (mf/create-root el))) + (defn init-ui [] - (mf/mount (mf/element ui/app) (dom/get-element "app")) - (mf/mount (mf/element modal) (dom/get-element "modal"))) + (mf/render! app-root (mf/element ui/app)) + (mf/render! modal-root (mf/element modal))) (defn- initialize-profile "Event used mainly on application bootstrap; it fetches the profile @@ -110,9 +118,15 @@ (defn ^:export reinit [] - #_(mf/unmount (dom/get-element "app")) - #_(mf/unmount (dom/get-element "modal")) - #_(st/emit! (ev/initialize)) + ;; NOTE: in cases of some strange behavior after hot-reload, + ;; uncomment this lines; they make a hard-rerender instead + ;; soft-rerender. + ;; + ;; (mf/unmount! app-root) + ;; (mf/unmount! modal-root) + ;; (set! app-root (mf/create-root (dom/get-element "app"))) + ;; (set! modal-root (mf/create-root (dom/get-element "modal"))) + (st/emit! (ev/initialize)) (init-ui)) (defn ^:dev/after-load after-load diff --git a/frontend/src/app/main/data/workspace/viewport.cljs b/frontend/src/app/main/data/workspace/viewport.cljs index 959dbf48a..767a6fc99 100644 --- a/frontend/src/app/main/data/workspace/viewport.cljs +++ b/frontend/src/app/main/data/workspace/viewport.cljs @@ -43,6 +43,7 @@ shapes (cph/get-immediate-children objects) srect (gsh/shapes->rect shapes) local (assoc local :vport size :zoom 1 :zoom-inverse 1)] + (cond (or (not (d/num? (:width srect))) (not (d/num? (:height srect)))) @@ -52,6 +53,7 @@ (> (:height srect) height)) (let [srect (gal/adjust-to-viewport size srect {:padding 40}) zoom (/ (:width size) (:width srect))] + (-> local (assoc :zoom zoom) (assoc :zoom-inverse (/ 1 zoom)) diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index fe4651d8b..db29a349f 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -229,10 +229,16 @@ edition? (mf/use-state false) on-show-options - (mf/use-fn #(reset! options true)) + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (reset! options true))) on-hide-options - (mf/use-fn #(reset! options false)) + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (reset! options false))) on-edit-clicked (mf/use-fn diff --git a/frontend/src/app/main/ui/components/dropdown_menu.cljs b/frontend/src/app/main/ui/components/dropdown_menu.cljs index e6a1f06ae..05e270f59 100644 --- a/frontend/src/app/main/ui/components/dropdown_menu.cljs +++ b/frontend/src/app/main/ui/components/dropdown_menu.cljs @@ -97,21 +97,17 @@ (dom/focus! (dom/get-element next-id)))) (when (kbd/tab? event) - (on-close)))) + (on-close))))] - on-mount - (fn [] - (let [keys [(events/listen globals/document EventType.CLICK on-click) - (events/listen globals/document EventType.CONTEXTMENU on-click) - (events/listen globals/document EventType.KEYUP on-keyup) - (events/listen globals/document EventType.KEYDOWN on-key-down)]] - #(doseq [key keys] - (events/unlistenByKey key))))] + (mf/with-effect [] + (let [keys [(events/listen globals/document EventType.CLICK on-click) + (events/listen globals/document EventType.CONTEXTMENU on-click) + (events/listen globals/document EventType.KEYUP on-keyup) + (events/listen globals/document EventType.KEYDOWN on-key-down)]] + #(doseq [key keys] + (events/unlistenByKey key)))) - (mf/use-effect on-mount) - [:ul {:class list-class - :role "menu"} - children])) + [:ul {:class list-class :role "menu"} children])) (mf/defc dropdown-menu {::mf/wrap-props false} diff --git a/frontend/src/app/main/ui/components/editable_select.cljs b/frontend/src/app/main/ui/components/editable_select.cljs index 1dfe8715c..1ae7ee75b 100644 --- a/frontend/src/app/main/ui/components/editable_select.cljs +++ b/frontend/src/app/main/ui/components/editable_select.cljs @@ -32,8 +32,14 @@ emit-blur? (mf/use-ref nil) font-size-wrapper-ref (mf/use-ref) - open-dropdown #(swap! state assoc :is-open? true) - close-dropdown #(swap! state assoc :is-open? false) + open-dropdown + (fn [event] + (dom/stop-propagation event) + (swap! state assoc :is-open? true)) + close-dropdown + (fn [event] + (dom/stop-propagation event) + (swap! state assoc :is-open? false)) select-item (fn [value] (fn [_] (swap! state assoc :current-value value) diff --git a/frontend/src/app/main/ui/components/select.cljs b/frontend/src/app/main/ui/components/select.cljs index e00aa2edb..067df24cd 100644 --- a/frontend/src/app/main/ui/components/select.cljs +++ b/frontend/src/app/main/ui/components/select.cljs @@ -39,8 +39,15 @@ current-label (get label-index current-value) is-open? (:is-open? state) - open-dropdown (mf/use-fn #(swap! state* assoc :is-open? true)) - close-dropdown (mf/use-fn #(swap! state* assoc :is-open? false)) + open-dropdown (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (swap! state* assoc :is-open? true))) + + close-dropdown (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (swap! state* assoc :is-open? false))) select-item (mf/use-fn @@ -77,8 +84,9 @@ (mf/with-effect [default-value] (swap! state* assoc :current-value default-value)) (if new-css-system - [:div {:on-click open-dropdown :class (dom/classnames (css class) true - (css :custom-select) true)} + [:div {:on-click open-dropdown + :class (dom/classnames (css class) true + (css :custom-select) true)} [:span {:class (css :current-label)} current-label] [:span {:class (css :dropdown-button)} i/arrow-refactor] [:& dropdown {:show is-open? :on-close close-dropdown} @@ -98,8 +106,8 @@ :on-click select-item} [:span {:class (css :label)} label] [:span {:class (css :check-icon)} i/tick-refactor]])))]]] - - + + [:div.custom-select {:on-click open-dropdown :class class} [:span current-label] diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs index 571b97fca..2e066120f 100644 --- a/frontend/src/app/main/ui/dashboard/sidebar.cljs +++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs @@ -227,6 +227,7 @@ i/search])])) (mf/defc teams-selector-dropdown-items + {::mf/wrap-props false} [{:keys [team profile teams] :as props}] (let [on-create-clicked (mf/use-callback @@ -257,6 +258,7 @@ (team-selected (:id team-item) event))) :id (str "teams-selector-" (:id team-item)) :klass "team-name" + :key (str "teams-selector-" (:id team-item)) :unique-key (dm/str (:id team-item))} [:span.team-icon [:img {:src (cf/resolve-team-photo-url team-item) @@ -472,7 +474,9 @@ [:div.sidebar-team-switch [:div.switch-content [:button.current-team {:tab-index "0" - :on-click #(reset! show-teams-ddwn? true) + :on-click (fn [event] + (dom/stop-propagation event) + (reset! show-teams-ddwn? true)) :on-key-down (fn [event] (when (or (kbd/space? event) (kbd/enter? event)) (dom/prevent-default event) @@ -496,7 +500,9 @@ i/arrow-down]] (when-not (:is-default team) - [:button.switch-options {:on-click #(reset! show-team-opts-ddwn? true) + [:button.switch-options {:on-click (fn [event] + (dom/stop-propagation event) + (reset! show-team-opts-ddwn? true)) :tab-index "0" :on-key-down (fn [event] (when (or (kbd/space? event) (kbd/enter? event)) @@ -674,6 +680,7 @@ (mf/use-callback (fn [section event] (dom/stop-propagation event) + (reset! show false) (if (keyword? section) (st/emit! (rt/nav section)) (st/emit! section)))) @@ -689,7 +696,9 @@ [:div.profile-section [:div.profile {:tab-index "0" - :on-click #(reset! show true) + :on-click (fn [event] + (dom/stop-propagation event) + (reset! show true)) :on-key-down (fn [event] (when (kbd/enter? event) (reset! show true))) @@ -698,7 +707,9 @@ :alt (:fullname profile)}] [:span (:fullname profile)]] - [:& dropdown-menu {:on-close #(reset! show false) + [:& dropdown-menu {:on-close (fn [event] + (dom/stop-propagation event) + (reset! show false)) :show @show} [:ul.dropdown [:li {:tab-index (if show diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs index a960f6f18..d311d71e1 100644 --- a/frontend/src/app/main/ui/hooks.cljs +++ b/frontend/src/app/main/ui/hooks.cljs @@ -226,6 +226,13 @@ (reset! ptr value)) ptr)) +(defn use-update-ref + [value] + (let [ref (mf/use-ref value)] + (mf/with-effect [value] + (mf/set-ref-val! ref value)) + ref)) + (defn use-ref-callback "Returns a stable callback pointer what calls the interned callback. The interned callback will be automatically updated on diff --git a/frontend/src/app/main/ui/modal.cljs b/frontend/src/app/main/ui/modal.cljs index 5f977c85a..15ded2950 100644 --- a/frontend/src/app/main/ui/modal.cljs +++ b/frontend/src/app/main/ui/modal.cljs @@ -82,11 +82,11 @@ :modal-wrapper (not new-css-system))} (mf/element component (:props data))]))) - (def modal-ref (l/derived ::dm/modal st/state)) (mf/defc modal + {::mf/wrap-props false} [] (let [modal (mf/deref modal-ref) new-css-system (features/use-feature :new-css-system)] diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index d1f767ead..26fd703cb 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -115,4 +115,5 @@ [:& filters/filters {:shape shape-without-shadows :filter-id (dm/fmt "filter_blur_%" render-id)}] [:& fills/fills {:shape shape :render-id render-id}] [:& frame/frame-clip-def {:shape shape :render-id render-id}]] + children]])) diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index 34c7e5173..156e21d95 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -42,7 +42,10 @@ on-zoom-fill] :as props}] (let [show-dropdown? (mf/use-state false)] - [:div.zoom-widget {:on-click #(reset! show-dropdown? true)} + [:div.zoom-widget {:on-click + (fn [event] + (dom/stop-propagation event) + (reset! show-dropdown? true))} [:span.label (fmt/format-percent zoom)] [:span.icon i/arrow-down] [:& dropdown {:show @show-dropdown? diff --git a/frontend/src/app/main/ui/viewer/shapes.cljs b/frontend/src/app/main/ui/viewer/shapes.cljs index 0c494781a..93c5cb139 100644 --- a/frontend/src/app/main/ui/viewer/shapes.cljs +++ b/frontend/src/app/main/ui/viewer/shapes.cljs @@ -272,7 +272,6 @@ :transform (gsh/transform-str shape)}]))) - ;; TODO: use-memo use-fn (defn generic-wrapper-factory diff --git a/frontend/src/app/main/ui/workspace/colorpalette.cljs b/frontend/src/app/main/ui/workspace/colorpalette.cljs index cdc29cffd..c0f5beb6c 100644 --- a/frontend/src/app/main/ui/workspace/colorpalette.cljs +++ b/frontend/src/app/main/ui/workspace/colorpalette.cljs @@ -154,7 +154,10 @@ :color color}])]]]] [:div.color-palette-actions - {:on-click #(swap! state assoc :show-menu true)} + {:on-click + (fn [event] + (dom/stop-propagation event) + (swap! state assoc :show-menu true))} [:div.color-palette-actions-button i/actions]] [:span.left-arrow {:on-click on-left-arrow-click} i/arrow-slide] @@ -163,7 +166,7 @@ [:div.color-palette-empty {:style {:position "absolute" :left "50%" :top "50%" - :transform "translate(-50%, -50%)"}} + :transform "translate(-50%, -50%)"}} (tr "workspace.libraries.colors.empty-palette")] [:div.color-palette-inside {:style {:position "relative" :right (str (* 66 offset) "px")}} diff --git a/frontend/src/app/main/ui/workspace/comments.cljs b/frontend/src/app/main/ui/workspace/comments.cljs index dff8689f2..45d5633da 100644 --- a/frontend/src/app/main/ui/workspace/comments.cljs +++ b/frontend/src/app/main/ui/workspace/comments.cljs @@ -92,7 +92,10 @@ [:div.comments-section.comment-threads-section [:div.workspace-comment-threads-sidebar-header [:div.label (tr "labels.comments")] - [:div.options {:on-click #(reset! options? true)} + [:div.options {:on-click + (fn [event] + (dom/stop-propagation event) + (reset! options? true))} [:div.label (case (:mode local) (nil :all) (tr "labels.all") :yours (tr "labels.only-yours"))] diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index ecb76d821..205895abc 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -79,10 +79,16 @@ open? (deref open*) open-dropdown - (mf/use-fn #(reset! open* true)) + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (reset! open* true))) close-dropdown - (mf/use-fn #(reset! open* false)) + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (reset! open* false))) on-increase (mf/use-fn @@ -449,9 +455,23 @@ sub-menu* (mf/use-state false) sub-menu (deref sub-menu*) - open-menu (mf/use-fn #(reset! show-menu* true)) - close-menu (mf/use-fn #(reset! show-menu* false)) - close-sub-menu (mf/use-fn #(reset! sub-menu* nil)) + open-menu + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (reset! show-menu* true))) + + close-menu + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (reset! show-menu* false))) + + close-sub-menu + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (reset! sub-menu* nil))) on-menu-click (mf/use-fn @@ -465,6 +485,7 @@ toggle-flag (mf/use-fn (fn [event] + (dom/stop-propagation event) (let [flag (-> (dom/get-current-target event) (dom/get-data "flag") (keyword))] diff --git a/frontend/src/app/main/ui/workspace/left_header.cljs b/frontend/src/app/main/ui/workspace/left_header.cljs index 46961e74d..5c3a70532 100644 --- a/frontend/src/app/main/ui/workspace/left_header.cljs +++ b/frontend/src/app/main/ui/workspace/left_header.cljs @@ -613,9 +613,23 @@ sub-menu* (mf/use-state false) sub-menu (deref sub-menu*) - open-menu (mf/use-fn #(reset! show-menu* true)) - close-menu (mf/use-fn #(reset! show-menu* false)) - close-sub-menu (mf/use-fn #(reset! sub-menu* nil)) + open-menu + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (reset! show-menu* true))) + + close-menu + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (reset! show-menu* false))) + + close-sub-menu + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (reset! sub-menu* nil))) on-menu-click (mf/use-fn @@ -629,6 +643,7 @@ toggle-flag (mf/use-fn (fn [event] + (dom/stop-propagation event) (let [flag (-> (dom/get-current-target event) (dom/get-data "test") (keyword))] diff --git a/frontend/src/app/main/ui/workspace/right_header.cljs b/frontend/src/app/main/ui/workspace/right_header.cljs index 3028ef6e6..80ff56c66 100644 --- a/frontend/src/app/main/ui/workspace/right_header.cljs +++ b/frontend/src/app/main/ui/workspace/right_header.cljs @@ -72,10 +72,16 @@ open? (deref open*) open-dropdown - (mf/use-fn #(reset! open* true)) + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (reset! open* true))) close-dropdown - (mf/use-fn #(reset! open* false)) + (mf/use-fn + (fn [event] + (dom/stop-propagation event) + (reset! open* false))) on-increase (mf/use-fn diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index a9b23e647..87c216d03 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -57,15 +57,14 @@ {::mf/wrap [#(mf/memo' % check-props)] ::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") frame-id (:id shape) objects (wsh/lookup-page-objects @st/state) - node-ref (mf/use-var nil) + node-ref (mf/use-ref nil) modifiers-ref (mf/use-memo (mf/deps frame-id) #(refs/workspace-modifiers-by-frame-id frame-id)) modifiers (mf/deref modifiers-ref)] - (fdm/use-dynamic-modifiers objects @node-ref modifiers) + (fdm/use-dynamic-modifiers objects (mf/ref-val node-ref) modifiers) (let [shape (unchecked-get props "shape")] [:& frame-shape {:shape shape :ref node-ref}]))))) @@ -86,62 +85,71 @@ objects (wsh/lookup-page-objects @st/state) - node* (mf/use-var nil) + node-ref (mf/use-ref nil) + root-ref (mf/use-ref nil) + force-render* (mf/use-state false) force-render? (deref force-render*) ;; when `true` we've called the mount for the frame - rendered* (mf/use-var false) + rendered-ref (mf/use-ref false) modifiers-ref (mf/with-memo [frame-id] (refs/workspace-modifiers-by-frame-id frame-id)) modifiers (mf/deref modifiers-ref) - fonts (mf/with-memo [shape objects] (ff/shape->fonts shape objects)) fonts (hooks/use-equal-memo fonts) disable-thumbnail? (d/not-empty? (dm/get-in modifiers [frame-id :modifiers])) - [on-load-frame-dom render-frame? thumbnail-renderer] - (ftr/use-render-thumbnail page-id shape node* rendered* disable-thumbnail? force-render?) + [on-load-frame-dom render-frame? children] + (ftr/use-render-thumbnail page-id shape root-ref node-ref rendered-ref disable-thumbnail? force-render?) on-frame-load - (fns/use-node-store thumbnail? node* rendered* render-frame?)] + (fns/use-node-store node-ref rendered-ref thumbnail? render-frame?) + ] - (fdm/use-dynamic-modifiers objects @node* modifiers) + (fdm/use-dynamic-modifiers objects (mf/ref-val node-ref) modifiers) (mf/with-effect [] ;; When a change in the data is received a "force-render" event is emitted ;; that will force the component to be mounted in memory (let [sub (->> (dwt/force-render-stream frame-id) - (rx/take-while #(not @rendered*)) + (rx/take-while #(not (mf/ref-val rendered-ref))) (rx/subs #(reset! force-render* true)))] #(some-> sub rx/dispose!))) (mf/with-effect [shape fonts thumbnail? on-load-frame-dom force-render? render-frame?] - (when (and (some? @node*) - (or @rendered* - (not thumbnail?) - force-render? - render-frame?)) - (let [elem (mf/element frame-shape #js {:ref on-load-frame-dom :shape shape :fonts fonts})] - (mf/mount elem @node*) - (when (not @rendered*) - (reset! rendered* true))))) + (when (and (some? (mf/ref-val node-ref)) + (or (mf/ref-val rendered-ref) + (false? thumbnail?) + (true? force-render?) + (true? render-frame?))) + + (when (false? (mf/ref-val rendered-ref)) + (when-let [node (mf/ref-val node-ref)] + (mf/set-ref-val! root-ref (mf/create-root node)) + (mf/set-ref-val! rendered-ref true))) + + (when-let [root (mf/ref-val root-ref)] + (mf/render! root (mf/element frame-shape #js {:ref on-load-frame-dom :shape shape :fonts fonts}))) + + (constantly nil))) [:& shape-container {:shape shape} - [:g.frame-container {:id (dm/str "frame-container-" frame-id) - :key "frame-container" - :ref on-frame-load - :opacity (when (:hidden shape) 0)} + [:g.frame-container + {:id (dm/str "frame-container-" frame-id) + :key "frame-container" + :ref on-frame-load + :opacity (when (:hidden shape) 0)} [:& ff/fontfaces-style {:fonts fonts}] [:g.frame-thumbnail-wrapper {:id (dm/str "thumbnail-container-" frame-id) ;; Hide the thumbnail when not displaying :opacity (when-not thumbnail? 0)} - thumbnail-renderer]] + children]] ])))) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs index c856ebcc9..d13d7259a 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs @@ -18,7 +18,7 @@ [app.main.ui.hooks :as hooks] [app.main.ui.workspace.viewport.utils :as vwu] [app.util.dom :as dom] - [app.util.globals :as globals] + [app.util.timers :as ts] [debug :refer [debug?]] [rumext.v2 :as mf])) @@ -230,85 +230,87 @@ (defn use-dynamic-modifiers [objects node modifiers] - (let [transforms - (mf/use-memo - (mf/deps modifiers) - (fn [] - (when (some? modifiers) - (d/mapm (fn [id {current-modifiers :modifiers}] - (let [shape (get objects id) - adapt-text? (and (= :text (:type shape)) (not (ctm/only-move? current-modifiers))) + (mf/with-memo [modifiers] + (when (some? modifiers) + (d/mapm (fn [id {current-modifiers :modifiers}] + (let [shape (get objects id) + adapt-text? (and (= :text (:type shape)) (not (ctm/only-move? current-modifiers))) - current-modifiers - (cond-> current-modifiers - adapt-text? - (adapt-text-modifiers shape))] - (ctm/modifiers->transform current-modifiers))) - modifiers)))) + current-modifiers + (cond-> current-modifiers + adapt-text? + (adapt-text-modifiers shape))] + (ctm/modifiers->transform current-modifiers))) + modifiers))) - add-children (mf/use-memo (mf/deps modifiers) #(ctm/added-children-frames modifiers)) - add-children (hooks/use-equal-memo add-children) - add-children-prev (hooks/use-previous add-children) + add-children + (mf/with-memo [modifiers] + (ctm/added-children-frames modifiers)) shapes - (mf/use-memo - (mf/deps transforms) - (fn [] - (->> (keys transforms) - (filter #(some? (get transforms %))) - (mapv (comp (add-masking-child? objects) (d/getf objects)))))) + (mf/with-memo [transforms] + (->> (keys transforms) + (filter #(some? (get transforms %))) + (mapv (comp (add-masking-child? objects) (d/getf objects))))) - prev-shapes (mf/use-var nil) - prev-modifiers (mf/use-var nil) - prev-transforms (mf/use-var nil)] + add-children (hooks/use-equal-memo add-children) + add-children-prev (hooks/use-previous add-children) + prev-shapes (mf/use-var nil) + prev-modifiers (mf/use-var nil) + prev-transforms (mf/use-var nil)] - (mf/use-effect - (mf/deps add-children) - (fn [] - (doseq [{:keys [shape]} add-children-prev] - (let [shape-node (get-shape-node shape) - mirror-node (dom/query (dm/fmt ".mirror-shape[href='#shape-%'" shape))] - (when mirror-node (.remove mirror-node)) - (dom/remove-attribute! (dom/get-parent shape-node) "display"))) + (mf/with-effect [add-children] + (ts/raf + #(doseq [{:keys [shape]} add-children-prev] + (let [shape-node (get-shape-node shape) + mirror-node (dom/query (dm/fmt ".mirror-shape[href='#shape-%'" shape))] + (when mirror-node (.remove mirror-node)) + (dom/remove-attribute! (dom/get-parent shape-node) "display")))) - (doseq [{:keys [frame shape]} add-children] - (let [frame-node (get-shape-node frame) - shape-node (get-shape-node shape) + (ts/raf + #(doseq [{:keys [frame shape]} add-children] + (let [frame-node (get-shape-node frame) + shape-node (get-shape-node shape) - clip-id - (dom/get-attribute (dom/query frame-node ":scope > defs > .frame-clip-def") "id") + clip-id + (-> (dom/query frame-node ":scope > defs > .frame-clip-def") + (dom/get-attribute "id")) - use-node - (.createElementNS globals/document "http://www.w3.org/2000/svg" "use") + use-node + (dom/create-element "http://www.w3.org/2000/svg" "use") - contents-node - (or (dom/query frame-node ".frame-children") frame-node)] + contents-node + (or (dom/query frame-node ".frame-children") frame-node)] - (dom/set-attribute! use-node "href" (dm/fmt "#shape-%" shape)) - (dom/set-attribute! use-node "clip-path" (dm/fmt "url(#%)" clip-id)) - (dom/add-class! use-node "mirror-shape") - (dom/append-child! contents-node use-node) - (dom/set-attribute! (dom/get-parent shape-node) "display" "none"))))) + (dom/set-attribute! use-node "href" (dm/fmt "#shape-%" shape)) + (dom/set-attribute! use-node "clip-path" (dm/fmt "url(#%)" clip-id)) + (dom/add-class! use-node "mirror-shape") + (dom/append-child! contents-node use-node) + (dom/set-attribute! (dom/get-parent shape-node) "display" "none"))))) - (mf/use-layout-effect - (mf/deps transforms) - (fn [] - (let [curr-shapes-set (into #{} (map :id) shapes) - prev-shapes-set (into #{} (map :id) @prev-shapes) + (mf/with-effect [transforms] + (let [curr-shapes-set (into #{} (map :id) shapes) + prev-shapes-set (into #{} (map :id) @prev-shapes) - new-shapes (->> shapes (remove #(contains? prev-shapes-set (:id %)))) - removed-shapes (->> @prev-shapes (remove #(contains? curr-shapes-set (:id %))))] + new-shapes (->> shapes (remove #(contains? prev-shapes-set (:id %)))) + removed-shapes (->> @prev-shapes (remove #(contains? curr-shapes-set (:id %))))] - (when (d/not-empty? new-shapes) - (start-transform! node new-shapes)) + ;; NOTE: we schedule the dom modifications to be executed + ;; asynchronously for avoid component flickering when react18 + ;; is used. - (when (d/not-empty? shapes) - (update-transform! node shapes transforms modifiers)) + (when (d/not-empty? new-shapes) + (ts/raf #(start-transform! node new-shapes))) - (when (d/not-empty? removed-shapes) - (remove-transform! node removed-shapes))) + (when (d/not-empty? shapes) + (ts/raf #(update-transform! node shapes transforms modifiers))) - (reset! prev-modifiers modifiers) - (reset! prev-transforms transforms) - (reset! prev-shapes shapes))))) + (when (d/not-empty? removed-shapes) + (ts/raf #(remove-transform! node removed-shapes)))) + + (reset! prev-modifiers modifiers) + (reset! prev-transforms transforms) + (reset! prev-shapes shapes)) + + )) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/node_store.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/node_store.cljs index d37470c43..c94d6bd93 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/node_store.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/node_store.cljs @@ -7,40 +7,49 @@ (ns app.main.ui.workspace.shapes.frame.node-store (:require [app.util.dom :as dom] - [app.util.globals :as globals] [rumext.v2 :as mf])) (defn use-node-store "Hook responsible of storing the rendered DOM node in memory while not being used" - [thumbnail? node-ref rendered? render-frame?] + [node-ref rendered-ref thumbnail? render-frame?] - (let [;; when `true` the node is in memory - in-memory? (mf/use-state true) - - ;; State just for re-rendering - re-render (mf/use-state 0) - - parent-ref (mf/use-var nil) + (let [re-render* (mf/use-state 0) + parent-ref (mf/use-ref nil) + present-ref (mf/use-ref false) on-frame-load - (mf/use-callback + (mf/use-fn (fn [node] - (when (and (some? node) (nil? @node-ref)) - (let [content (-> (.createElementNS globals/document "http://www.w3.org/2000/svg" "g") + (when (and (some? node) + (nil? (mf/ref-val node-ref))) + (let [content (-> (dom/create-element "http://www.w3.org/2000/svg" "g") (dom/add-class! "frame-content"))] - (reset! node-ref content) - (reset! parent-ref node) - (swap! re-render inc)))))] + (mf/set-ref-val! node-ref content) + (mf/set-ref-val! parent-ref node) + (swap! re-render* inc)))))] - (mf/use-layout-effect - (mf/deps thumbnail? render-frame?) - (fn [] - (when (and (some? @parent-ref) (some? @node-ref) @rendered? (and thumbnail? (not render-frame?))) - (.removeChild @parent-ref @node-ref) - (reset! in-memory? true)) + (mf/with-effect [thumbnail? render-frame?] + (let [rendered? (mf/ref-val rendered-ref) + present? (mf/ref-val present-ref)] - (when (and (some? @node-ref) @in-memory? (or (not thumbnail?) render-frame?)) - (.appendChild @parent-ref @node-ref) - (reset! in-memory? false)))) + (when (and (true? rendered?) + (true? thumbnail?) + (false? render-frame?) + (true? present?)) + (when-let [parent (mf/ref-val parent-ref)] + (when-let [node (mf/ref-val node-ref)] + (dom/remove-child! parent node) + (mf/set-ref-val! present-ref false) + (swap! re-render* inc)))) + + (when (and (false? present?) + (or (false? thumbnail?) + (true? render-frame?))) + (when-let [parent (mf/ref-val parent-ref)] + (when-let [node (mf/ref-val node-ref)] + (when-not (dom/child? parent node) + (dom/append-child! parent node) + (mf/set-ref-val! present-ref true) + (swap! re-render* inc))))))) on-frame-load)) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs index 5c7d535f3..8924b3074 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs @@ -66,18 +66,17 @@ (defn use-render-thumbnail "Hook that will create the thumbnail data" - [page-id {:keys [id] :as shape} node-ref rendered? disable? force-render] + [page-id {:keys [id] :as shape} root-ref node-ref rendered-ref disable? force-render] (let [frame-image-ref (mf/use-ref nil) - disable* (mf/use-var disable?) - regenerate* (mf/use-var false) + disable-ref (mf/use-ref disable?) + regenerate-ref (mf/use-ref false) all-children-ref (mf/with-memo [id] (refs/all-children-objects id)) all-children (mf/deref all-children-ref) - ;; FIXME: performance rect bounds (if (:show-content shape) (gsh/shapes->rect (cons shape all-children)) @@ -89,37 +88,44 @@ height (dm/get-prop bounds :height) svg-uri* (mf/use-state nil) - bitmap-uri* (mf/use-state nil) - observer* (mf/use-var nil) + svg-uri (deref svg-uri*) - bounds* (hooks/use-update-var bounds) + bitmap-uri* (mf/use-state nil) + bitmap-uri (deref bitmap-uri*) + + observer-ref (mf/use-ref nil) + + bounds-ref (hooks/use-update-ref bounds) updates-s (mf/use-memo rx/subject) - thumbnail-uri-ref (mf/with-memo [page-id id] - (refs/thumbnail-frame-data page-id id)) - thumbnail-uri (mf/deref thumbnail-uri-ref) + thumbnail-uri* (mf/with-memo [page-id id] + (refs/thumbnail-frame-data page-id id)) + thumbnail-uri (mf/deref thumbnail-uri*) ;; State to indicate to the parent that should render the frame render-frame* (mf/use-state (not thumbnail-uri)) + render-frame? (deref render-frame*) + debug? (debug? :thumbnails) on-bitmap-load (mf/use-fn + (mf/deps svg-uri) (fn [] ;; We revoke the SVG Blob URI to free memory only when we ;; are sure that it is not used anymore. - (wapi/revoke-uri @svg-uri*) + (some-> svg-uri wapi/revoke-uri) (reset! svg-uri* nil))) on-svg-load - (mf/use-callback + (mf/use-fn + (mf/deps thumbnail-uri) (fn [] (let [image-node (mf/ref-val frame-image-ref)] (dom/set-data! image-node "ready" "true") - ;; If we don't have the thumbnail data saved (normally the first load) we update the data ;; when available - (when (not ^boolean @thumbnail-uri-ref) + (when-not (some? thumbnail-uri) (st/emit! (dwt/update-thumbnail page-id id))) (reset! render-frame* false)))) @@ -131,34 +137,37 @@ (try ;; When starting generating the canvas we mark it as not ready so its not send to back until ;; we have time to update it - (let [node @node-ref] + + (when-let [node (mf/ref-val node-ref)] (if (dom/has-children? node) ;; The frame-content need to have children in order to generate the thumbnail (let [style-node (dom/query (dm/str "#frame-container-" id " style")) - url (create-svg-blob-uri-from @bounds* node style-node)] + bounds (mf/ref-val bounds-ref) + url (create-svg-blob-uri-from bounds node style-node)] + (reset! svg-uri* url)) ;; Node not yet ready, we schedule a new generation (ts/raf generate-thumbnail))) - (catch :default e (.error js/console e))))) on-change-frame (mf/use-fn - (mf/deps id) + (mf/deps id generate-thumbnail) (fn [] - (when (and ^boolean @node-ref - ^boolean @rendered? - ^boolean @regenerate*) - (let [loading-images? (some? (dom/query @node-ref "[data-loading='true']")) + (when (and (some? (mf/ref-val node-ref)) + (some? (mf/ref-val rendered-ref)) + (some? (mf/ref-val regenerate-ref))) + (let [node (mf/ref-val node-ref) + loading-images? (some? (dom/query node "[data-loading='true']")) loading-fonts? (some? (dom/query (dm/str "#frame-container-" id " > style[data-loading='true']")))] (when (and (not loading-images?) (not loading-fonts?)) (reset! svg-uri* nil) (reset! bitmap-uri* nil) (generate-thumbnail) - (reset! regenerate* false)))))) + (mf/set-ref-val! regenerate-ref false)))))) ;; When the frame is updated, it is marked as not ready ;; so that it is not sent to the background until @@ -169,23 +178,27 @@ (let [image-node (mf/ref-val frame-image-ref)] (when (not= "false" (dom/get-data image-node "ready")) (dom/set-data! image-node "ready" "false"))) - (when-not ^boolean @disable* + + (when-not ^boolean (mf/ref-val disable-ref) (reset! svg-uri* nil) (reset! bitmap-uri* nil) (reset! render-frame* true) - (reset! regenerate* true)))) + (mf/set-ref-val! regenerate-ref true)))) on-load-frame-dom (mf/use-fn (fn [node] - (when (and (some? node) - (nil? @observer*)) - (when-not (some? @thumbnail-uri-ref) + (when (and (nil? (mf/ref-val observer-ref)) (some? node)) + (when-not (some? @thumbnail-uri*) (rx/push! updates-s :update)) (let [observer (js/MutationObserver. (partial rx/push! updates-s))] - (.observe observer node #js {:childList true :attributes true :attributeOldValue true :characterData true :subtree true}) - (reset! observer* observer)))))] + (.observe observer node #js {:childList true + :attributes true + :attributeOldValue true + :characterData true + :subtree true}) + (mf/set-ref-val! observer-ref observer)))))] (mf/with-effect [thumbnail-uri] (when (some? thumbnail-uri) @@ -213,25 +226,30 @@ (partial rx/dispose! subid))) (mf/with-effect [disable?] - (when (and ^boolean disable? - (not @disable*)) + (when (and ^boolean disable? (not (mf/ref-val disable-ref))) (rx/push! updates-s :update)) - (reset! disable* disable?) + + (mf/set-ref-val! disable-ref disable?) nil) (mf/with-effect [] (fn [] - (when (and (some? @node-ref) - ^boolean @rendered?) - (mf/unmount @node-ref) - (reset! node-ref nil) - (reset! rendered? false) - (when (some? @observer*) - (.disconnect @observer*) - (reset! observer* nil))))) + (when (and (some? (mf/ref-val node-ref)) + (true? (mf/ref-val rendered-ref))) + (when-let [root (mf/ref-val root-ref)] + ;; NOTE: the unmount should be always scheduled to be + ;; executed asynchronously of the current flow (react + ;; rules). + (ts/schedule #(mf/unmount! ^js root))) - [on-load-frame-dom - @render-frame* + (mf/set-ref-val! node-ref nil) + (mf/set-ref-val! rendered-ref false) + + (when-let [observer (mf/ref-val observer-ref)] + (.disconnect ^js observer) + (mf/set-ref-val! observer-ref nil))))) + + [on-load-frame-dom render-frame? (mf/html [:& frame/frame-container {:bounds bounds :shape shape} @@ -251,18 +269,18 @@ ;; to be rendered on screen. Then we remove the ;; svg and keep the bitmap one. ;; This is the "buffer" that keeps the bitmap image. - (when ^boolean @bitmap-uri* + (when (some? bitmap-uri) [:image.thumbnail-bitmap {:x x :y y :width width :height height - :href @bitmap-uri* + :href bitmap-uri :style {:filter (when ^boolean debug? "sepia(1)")} :on-load on-bitmap-load}]) ;; This is the "buffer" that keeps the SVG image. - (when ^boolean @svg-uri* + (when (some? svg-uri) [:image.thumbnail-canvas {:x x :y y @@ -271,6 +289,6 @@ :width width :height height :ref frame-image-ref - :href @svg-uri* + :href svg-uri :style {:filter (when ^boolean debug? "sepia(0.5)")} :on-load on-svg-load}])])])) diff --git a/frontend/src/app/main/ui/workspace/textpalette.cljs b/frontend/src/app/main/ui/workspace/textpalette.cljs index 9877b9c46..35a349b80 100644 --- a/frontend/src/app/main/ui/workspace/textpalette.cljs +++ b/frontend/src/app/main/ui/workspace/textpalette.cljs @@ -124,7 +124,10 @@ (str/format " (%s)" (count file-typographies)))]]]] [:div.color-palette-actions - {:on-click #(swap! state assoc :show-menu true)} + {:on-click + (fn [event] + (dom/stop-propagation event) + (swap! state assoc :show-menu true))} [:div.color-palette-actions-button i/actions]] [:span.left-arrow {:on-click on-left-arrow-click} i/arrow-slide] @@ -134,7 +137,7 @@ [:div.color-palette-empty {:style {:position "absolute" :left "50%" :top "50%" - :transform "translate(-50%, -50%)"}} + :transform "translate(-50%, -50%)"}} (tr "workspace.libraries.colors.empty-typography-palette")] [:div.color-palette-inside (for [[idx item] (map-indexed vector current-typographies)] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 096415c59..4fc1a0d76 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -69,7 +69,7 @@ selected)) (mf/defc viewport - [{:keys [wlocal wglobal selected layout file] :as props}] + [{:keys [selected wglobal wlocal layout file] :as props}] (let [;; When adding data from workspace-local revisit `app.main.ui.workspace` to check ;; that the new parameter is sent {:keys [edit-path diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs index 5fdc522a6..6d17baf8a 100644 --- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -12,7 +12,9 @@ [app.main.ui.css-cursors :as cur] [app.main.ui.formats :refer [format-number]])) -(defn format-viewbox [vbox] +(defn format-viewbox + "Format a viewbox to a string" + [vbox] (dm/str (format-number(:x vbox 0)) " " (format-number (:y vbox 0)) " " (format-number (:width vbox 0)) " " diff --git a/frontend/src/app/render.cljs b/frontend/src/app/render.cljs index a8324e7ad..009faf319 100644 --- a/frontend/src/app/render.cljs +++ b/frontend/src/app/render.cljs @@ -32,6 +32,10 @@ (l/setup! {:app :info}) +(defonce app-root + (let [el (dom/get-element "app")] + (mf/create-root el))) + (declare ^:private render-single-object) (declare ^:private render-components) (declare ^:private render-objects) @@ -48,7 +52,7 @@ "objects" (render-objects params) "components" (render-components params) nil)] - (mf/mount component (dom/get-element "app"))))) + (mf/render! app-root component)))) (defn ^:export init [] @@ -57,7 +61,7 @@ (defn reinit [] - (mf/unmount (dom/get-element "app")) + (mf/unmount! app-root) (init-ui)) (defn ^:dev/after-load after-load @@ -201,7 +205,7 @@ [:& objects-svg {:file-id file-id :page-id page-id - :share-id share-id + :share-id share-id :object-ids (into #{} object-id) :render-embed? render-embed}])))) diff --git a/frontend/src/app/thumbnail_renderer.cljs b/frontend/src/app/thumbnail_renderer.cljs index d48780edf..ed06f1ec5 100644 --- a/frontend/src/app/thumbnail_renderer.cljs +++ b/frontend/src/app/thumbnail_renderer.cljs @@ -191,6 +191,7 @@ (rx/mapcat #(wapi/create-image-bitmap % #js {:resizeWidth width :resizeQuality "medium"})) (rx/tap #(wapi/revoke-uri uri))))) + (rx/mapcat bitmap->blob)))) (defn- on-message diff --git a/frontend/src/app/worker/thumbnails.cljs b/frontend/src/app/worker/thumbnails.cljs index faba81cc1..7bf6203bb 100644 --- a/frontend/src/app/worker/thumbnails.cljs +++ b/frontend/src/app/worker/thumbnails.cljs @@ -62,18 +62,21 @@ (defn- render-thumbnail [{:keys [page file-id revn] :as params}] - (binding [fonts/loaded-hints (l/atom #{})] - (let [objects (:objects page) - frame (some->> page :thumbnail-frame-id (get objects)) - element (if frame - (mf/element render/frame-svg #js {:objects objects :frame frame :show-thumbnails? true}) - (mf/element render/page-svg #js {:data page :thumbnails? true :render-embed? true})) - data (rds/renderToStaticMarkup element)] - - {:data data - :fonts @fonts/loaded-hints - :file-id file-id - :revn revn}))) + (try + (binding [fonts/loaded-hints (l/atom #{})] + (let [objects (:objects page) + frame (some->> page :thumbnail-frame-id (get objects)) + element (if frame + (mf/element render/frame-svg #js {:objects objects :frame frame :show-thumbnails? true}) + (mf/element render/page-svg #js {:data page :thumbnails? true :render-embed? true})) + data (rds/renderToStaticMarkup element)] + {:data data + :fonts @fonts/loaded-hints + :file-id file-id + :revn revn})) + (catch :default cause + (js/console.error "unexpected erorr on rendering thumbnail" cause) + nil))) (defmethod impl/handler :thumbnails/generate-for-file [{:keys [file-id revn features] :as message} _] @@ -85,9 +88,7 @@ (let [canvas (js/OffscreenCanvas. (.-width ^js ibpm) (.-height ^js ibpm)) ctx (.getContext ^js canvas "bitmaprenderer") tp (ts/tpoint-ms)] - (.transferFromImageBitmap ^js ctx ibpm) - (->> (.convertToBlob ^js canvas #js {:type "image/png"}) (p/fmap (fn [blob] {:result (wapi/create-uri blob)})) diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 29d69bb78..30f943339 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -4406,14 +4406,13 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" -react-dom@~17.0.2: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" - integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== +react-dom@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" - scheduler "^0.20.2" + scheduler "^0.23.0" react-is@^16.13.1: version "16.13.1" @@ -4437,13 +4436,12 @@ react-virtualized@^9.22.3: prop-types "^15.7.2" react-lifecycles-compat "^3.0.4" -react@~17.0.2: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" - integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== +react@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" read-pkg-up@^1.0.1: version "1.0.1" @@ -4757,13 +4755,12 @@ sax@^1.2.4, sax@~1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -scheduler@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" - integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== dependencies: loose-envify "^1.1.0" - object-assign "^4.1.1" semver-greatest-satisfied-range@^1.1.0: version "1.1.0"