From a5b09520a6723c604f2c9365071ae228f7841d06 Mon Sep 17 00:00:00 2001 From: Dominic Monroe Date: Thu, 11 Jan 2024 22:09:28 +0000 Subject: [PATCH 1/2] Fix react render under advanced optimizations --- src/portfolio/react_utils.cljs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/portfolio/react_utils.cljs b/src/portfolio/react_utils.cljs index 6846760..d9f51e6 100644 --- a/src/portfolio/react_utils.cljs +++ b/src/portfolio/react_utils.cljs @@ -1,5 +1,6 @@ (ns portfolio.react-utils (:require [goog] + [goog.object :as o] ["react" :as react] [portfolio.ui.actions :as actions] [portfolio.adapter :as adapter])) @@ -12,7 +13,7 @@ (adapter/prepare-scene impl))) (defn get-scene [this] - (.. this -props -scene)) + (o/getValueByKeys this "props" "scene")) (defn create-safe-wrapper [] (let [ctor (fn [])] @@ -32,7 +33,7 @@ (render [this] (.createElement react "div" #js {} - (if (some-> this .-state .-error) + (if (o/getValueByKeys this "state" "error") "" (:component (get-scene this)))))) ctor)) From c9f4673de893e0206bb417e0de17657e8dede0a2 Mon Sep 17 00:00:00 2001 From: Christian Johansen Date: Mon, 8 Jan 2024 12:33:46 +0100 Subject: [PATCH 2/2] Add render-mode iframe --- src/portfolio/ui.cljs | 66 +++++++------- src/portfolio/ui/client.cljs | 45 +++++++++- src/portfolio/ui/components/canvas.cljs | 111 ++++++++++++++++++------ src/portfolio/ui/css.cljs | 5 ++ 4 files changed, 168 insertions(+), 59 deletions(-) diff --git a/src/portfolio/ui.cljs b/src/portfolio/ui.cljs index 5c3fb5b..87bd8e4 100644 --- a/src/portfolio/ui.cljs +++ b/src/portfolio/ui.cljs @@ -70,42 +70,42 @@ (defn start! [& [{:keys [on-render config canvas-tools extra-canvas-tools index get-indexable-data] :as opt}]] (let [->diffable (partial search/get-diffables (or get-indexable-data search/get-indexable-data))] (swap! app merge (create-app config canvas-tools extra-canvas-tools) {:index index}) - (when-not (client/started? app) (add-watch data/scenes ::app - (fn [_ _ old-scenes scenes] - (let [collections (get-collections scenes (:collections @app)) - old-collections (get-collections old-scenes (:collections @app))] - (swap! app (fn [state] - (-> state - (assoc :scenes scenes) - (assoc :collections collections)))) - (when (:reindex? opt true) - (index-content - app - {:ids (concat - (search/get-diff-keys (->diffable scenes) (->diffable old-scenes)) - (search/get-diff-keys (->diffable collections) (->diffable old-collections)))}))) - (eventually-execute app [:go-to-current-location]))) + (fn [_ _ old-scenes scenes] + (let [collections (get-collections scenes (:collections @app)) + old-collections (get-collections old-scenes (:collections @app))] + (swap! app (fn [state] + (-> state + (assoc :scenes scenes) + (assoc :collections collections)))) + (when (:reindex? opt true) + (index-content + app + {:ids (concat + (search/get-diff-keys (->diffable scenes) (->diffable old-scenes)) + (search/get-diff-keys (->diffable collections) (->diffable old-collections)))}))) + (eventually-execute app [:go-to-current-location]))) (add-watch data/collections ::app - (fn [_ _ _ collections] - (let [old-collections (:collections @app) - collections (get-collections (:scenes @app) collections)] - (swap! app assoc :collections collections) - (when (:reindex? opt true) - (index-content app {:ids (search/get-diff-keys (->diffable collections) (->diffable old-collections))}))))) - - (add-tap render-scene) - - (js/window.addEventListener - "message" - (fn [e] - (when (.. e -data -action) - (when-let [action (actions/get-action (.-data e))] - (actions/execute-action! app action))))))) + (fn [_ _ _ collections] + (let [old-collections (:collections @app) + collections (get-collections (:scenes @app) collections)] + (swap! app assoc :collections collections) + (when (:reindex? opt true) + (index-content app {:ids (search/get-diff-keys (->diffable collections) (->diffable old-collections))})))))) - (when-not (client/started? app) - (index-content app)) + (if (.get (new js/URLSearchParams js/window.location.search) "portfolio.embed") + (client/start-embed-app app) + (do + (when-not (client/started? app) + (add-tap render-scene) + (js/window.addEventListener + "message" + (fn [e] + (when (.. e -data -action) + (when-let [action (actions/get-action (.-data e))] + (actions/execute-action! app action))))) + (index-content app)) - (client/start-app app {:on-render on-render})) + (client/start-app app {:on-render on-render}))))) diff --git a/src/portfolio/ui/client.cljs b/src/portfolio/ui/client.cljs index 5ce0c07..5e2c931 100644 --- a/src/portfolio/ui/client.cljs +++ b/src/portfolio/ui/client.cljs @@ -1,6 +1,9 @@ (ns portfolio.ui.client "Bootstrap and render a Portfolio UI app instance" - (:require [dumdom.core :as d] + (:require [clojure.edn :as edn] + [dumdom.core :as d] + [goog.object :as o] + [portfolio.adapter :as adapter] [portfolio.homeless :as h] [portfolio.ui.actions :as actions] [portfolio.ui.collection :as collection] @@ -108,3 +111,43 @@ [:go-to-current-location]))) (swap! app assoc ::started? true))))) app) + +(defn mark-embed-ready! + [app] + (set! (.-portfolioReady js/window) true) + (swap! app assoc ::started? true) + (js/window.parent.postMessage #js {:portfolio_ready true} "*")) + +(defn track-host-location + [app] + (js/window.addEventListener + "message" + (fn [e] + (let [message (.-data e)] + (when-let [scene-id (o/get message "set_scene")] + (swap! app assoc ::opt (some-> message (o/get "opt") edn/read-string)) + (actions/execute-action! app [:go-to-location {:query-params {:id scene-id + :portfolio.embed true}}])))))) + +(defn render-component [app {:keys [on-render]}] + (let [state @app] + (when (ifn? on-render) + (on-render + ;; TODO: add page-data parameter + #_page-data)) + (if-let [el (js/document.getElementById "portfolio")] + (when-let [{:keys [target]} (collection/get-selection state (routes/get-id (:location state)))] + (adapter/render-component (assoc target :component ((:component-fn target) nil (::opt state))) el) + (js/window.parent.postMessage #js {:portfolio_render (routes/get-id (:location state))})) + (js/console.error "Unable to render portfolio: no element with id \"portfolio\"")))) + +(defn start-embed-app [app & [{:keys [on-render]}]] + (css/load-css-files-direct (:css-paths @app)) + (if (started? app) + (render-component app {:on-render on-render}) + (do + (ensure-element!) + (track-host-location app) + (add-watch app ::render (fn [_ _ _ _] (render-component app {:on-render on-render}))) + (mark-embed-ready! app))) + app) diff --git a/src/portfolio/ui/components/canvas.cljs b/src/portfolio/ui/components/canvas.cljs index d1df919..24bf69e 100644 --- a/src/portfolio/ui/components/canvas.cljs +++ b/src/portfolio/ui/components/canvas.cljs @@ -1,6 +1,7 @@ (ns portfolio.ui.components.canvas (:require [clojure.string :as str] [dumdom.core :as d] + [goog.object :as o] [portfolio.adapter :as adapter] [portfolio.ui.actions :as actions] [portfolio.ui.canvas.protocols :as canvas] @@ -33,7 +34,23 @@ (defn render-scene [el {:keys [scene tools opt]}] (let [iframe (get-iframe el) canvas (some-> iframe .-contentDocument (.getElementById "canvas")) - error (.querySelector el ".error-container")] + error (.querySelector el ".error-container") + scene-id-str (->> [(namespace (:id scene)) + (name (:id scene))] + (remove empty?) + (str/join "/")) + finalize-fn (fn [] + (doseq [tool tools] + (try + (canvas/finalize-canvas tool el opt) + (catch :default e + (-> (str "Failed to finalize canvas with " (:id tool)) + (report-error e scene))))) + (when-let [win (.-contentWindow iframe)] + (.postMessage + win + (clj->js {:event "scene-rendered" + :scene-id scene-id-str}) "*")))] (when error (.removeChild (.-parentNode error) error) (set! (.. iframe -style -display) "block")) @@ -44,24 +61,32 @@ (-> (str "Failed to prepare canvas with " (:id tool)) (report-error e scene))))) (try - (adapter/render-component (assoc scene :component ((:component-fn scene))) canvas) - (js/setTimeout - (fn [] - (doseq [tool tools] - (try - (canvas/finalize-canvas tool el opt) - (catch :default e - (-> (str "Failed to finalize canvas with " (:id tool)) - (report-error e scene))))) - (when-let [win (.-contentWindow iframe)] - (.postMessage - win - (clj->js {:event "scene-rendered" - :scene-id (->> [(namespace (:id scene)) - (name (:id scene))] - (remove empty?) - (str/join "/"))}) "*"))) - 50) + (case (:render-mode scene :mount) + :mount + (do + (adapter/render-component (assoc scene :component ((:component-fn scene))) canvas) + (js/setTimeout finalize-fn 50)) + + :iframe + (let [set-scene! #(some-> iframe .-contentWindow + (.postMessage #js {:set_scene scene-id-str + :opt (pr-str opt)}))] + (js/window.addEventListener + "message" + (fn finalize [e] + (when (and (identical? (.-contentWindow iframe) (.-source e)) + (o/getValueByKeys e "data" "portfolio_render")) + (js/window.removeEventListener "message" finalize) + (js/setTimeout finalize-fn 50)))) + (if (-> iframe .-contentWindow (o/get "portfolioReady")) + (set-scene!) + (js/window.addEventListener + "message" + (fn set-the-scene [e] + (when (and (identical? (.-contentWindow iframe) (.-source e)) + (o/getValueByKeys e "data" "portfolio_ready")) + (js/window.removeEventListener "message" set-the-scene) + (set-scene!))))))) (catch :default e (-> (str "Failed to render " (str "'" (:title scene) "'")) (report-error e scene)))))) @@ -80,6 +105,13 @@ (.appendChild (.-body doc) el))) (f)))))) +(defn- pad-canvas [data document] + (let [[t r b l] (:viewport/padding (:opt data))] + (when t (set! (.. document -body -style -paddingTop) (str t "px"))) + (when r (set! (.. document -body -style -paddingBottom) (str r "px"))) + (when b (set! (.. document -documentElement -style -paddingLeft) (str b "px"))) + (when l (set! (.. document -documentElement -style -paddingRight) (str l "px"))))) + (defn init-canvas [el data f] (let [iframe (get-iframe el) document (get-iframe-document el) @@ -122,11 +154,7 @@ (.appendChild head link))) ;; Set padding properties - (let [[t r b l] (:viewport/padding (:opt data))] - (when t (set! (.. document -body -style -paddingTop) (str t "px"))) - (when r (set! (.. document -body -style -paddingBottom) (str r "px"))) - (when b (set! (.. document -documentElement -style -paddingLeft) (str b "px"))) - (when l (set! (.. document -documentElement -style -paddingRight) (str l "px")))))) + (pad-canvas data document))) (defn get-rendered-data [{:keys [scene opt]}] {:rendered (:rendered-data scene) @@ -181,6 +209,35 @@ :height (when (number? (:viewport/height (:opt data))) (:viewport/height (:opt data)))}}]]) +(d/defcomponent IframeCanvas + :on-mount (fn [el data] + (enqueue-render-data el data) + (on-mounted + (get-iframe el) + (fn [] + (pad-canvas data (get-iframe-document el)) + (set! (.-renderFromQueue el) true) + (process-render-queue el)))) + :on-update (fn [el data] + (enqueue-render-data el data)) + [data] + [:div {:style {:background (or (:background/background-color (:opt data)) + "var(--canvas-bg)") + :display "flex" + :transition "width 0.25s, height 0.25s"}} + [:iframe.canvas + {:src + (str (doto (new js/URL js/window.location) + (set! -search (new js/URLSearchParams #js {"portfolio.embed" true})))) + :title "Component scene" + :style {:border "none" + :flex-grow "1" + :width (or (when (number? (:viewport/width (:opt data))) + (:viewport/width (:opt data))) + "100%") + :height (when (number? (:viewport/height (:opt data))) + (:viewport/height (:opt data)))}}]]) + (d/defcomponent Toolbar [{:keys [buttons]}] [:nav {:style {:background "var(--bg)" :color "var(--fg)" @@ -242,7 +299,11 @@ (if (or (not (:component-fn (:scene data))) (:error (:scene data))) (Error (:error (:scene data))) - (Canvas data)))] + (case (:render-mode (:scene data) :mount) + :mount + (Canvas data) + :iframe + (IframeCanvas data))))] (remove nil?))]) (def direction diff --git a/src/portfolio/ui/css.cljs b/src/portfolio/ui/css.cljs index a25a051..b489f95 100644 --- a/src/portfolio/ui/css.cljs +++ b/src/portfolio/ui/css.cljs @@ -39,6 +39,11 @@ (when-not (find-link-by-href js/document.head path) (.appendChild js/document.head (create-css-link path {:media "portfolio"}))))) +(defn load-css-files-direct [paths] + (doseq [path paths] + (when-not (find-link-by-href js/document.head path) + (.appendChild js/document.head (create-css-link path))))) + (defn on-head-mutation [mutations paths] (let [paths (set paths)] (doseq [path (->> mutations