0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-22 14:39:45 -05:00

Update to React 18

This commit is contained in:
Aitor 2023-08-16 14:09:53 +02:00 committed by Andrey Antukh
parent 5ea9a52e69
commit 4b8ee8ef84
30 changed files with 396 additions and 250 deletions

View file

@ -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"
}

View file

@ -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",

View file

@ -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

View file

@ -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))

View file

@ -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

View file

@ -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}

View file

@ -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)

View file

@ -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]

View file

@ -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

View file

@ -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

View file

@ -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)]

View file

@ -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]]))

View file

@ -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?

View file

@ -272,7 +272,6 @@
:transform (gsh/transform-str shape)}])))
;; TODO: use-memo use-fn
(defn generic-wrapper-factory

View file

@ -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")}}

View file

@ -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"))]

View file

@ -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))]

View file

@ -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))]

View file

@ -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

View file

@ -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]]
]))))

View file

@ -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))
))

View file

@ -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))

View file

@ -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}])])]))

View file

@ -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)]

View file

@ -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

View file

@ -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)) " "

View file

@ -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}]))))

View file

@ -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

View file

@ -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)}))

View file

@ -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"