-
Notifications
You must be signed in to change notification settings - Fork 0
Persistent iframe #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: melange-v3
Are you sure you want to change the base?
Changes from all commits
42e897e
8595ef5
627bb67
10ef9d2
a86b20f
f29b23b
1d75b0e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -157,8 +157,6 @@ module TopPanel = { | |
| </div>; | ||
| }; | ||
|
|
||
| let rightSidebarId = "rightSidebar"; | ||
|
|
||
| module Link = { | ||
| [@react.component] | ||
| let make = (~href, ~text: React.element, ~style=?, ~activeStyle=?) => { | ||
|
|
@@ -611,9 +609,9 @@ module DemoUnitSidebar = { | |
| <PropBox key=propName propName> | ||
| <input | ||
| type_="number" | ||
| min=string_of_int(min) | ||
| max=string_of_int(max) | ||
| value=string_of_int(value) | ||
| min={string_of_int(min)} | ||
| max={string_of_int(max)} | ||
| value={string_of_int(value)} | ||
| style=Styles.textInput | ||
| onChange={event => | ||
| onIntChange( | ||
|
|
@@ -633,9 +631,9 @@ module DemoUnitSidebar = { | |
| <PropBox key=propName propName> | ||
| <input | ||
| type_="number" | ||
| min=string_of_float(min) | ||
| max=string_of_float(max) | ||
| value=string_of_float(value) | ||
| min={string_of_float(min)} | ||
| max={string_of_float(max)} | ||
| value={string_of_float(value)} | ||
| style=Styles.textInput | ||
| onChange={event => | ||
| onFloatChange( | ||
|
|
@@ -712,38 +710,9 @@ module DemoUnit = { | |
| ->(ReactDOM.Style.unsafeAddProp("WebkitOverflowScrolling", "touch")); | ||
| }; | ||
|
|
||
| let getRightSidebarElement = (): option(Dom.element) => | ||
| Window.window##parent##document##getElementById(rightSidebarId) | ||
| ->Js.Nullable.toOption; | ||
|
|
||
| [@react.component] | ||
| let make = (~demoUnit: Configs.demoUnitProps => React.element) => { | ||
| let (parentWindowRightSidebarElem, setParentWindowRightSidebarElem) = | ||
| React.useState(() => None); | ||
|
|
||
| React.useEffect0(() => { | ||
| switch (getRightSidebarElement()) { | ||
| | Some(elem) => setParentWindowRightSidebarElem(_ => Some(elem)) | ||
| | None => () | ||
| }; | ||
| None; | ||
| }); | ||
| React.useEffect0(() => { | ||
| Window.addMessageListener(event => | ||
| if (Window.window##parent === event##source) { | ||
| let message: string = event##data; | ||
| switch (message->Window.Message.fromStringOpt) { | ||
| | Some(RightSidebarDisplayed) => | ||
| switch (getRightSidebarElement()) { | ||
| | Some(elem) => setParentWindowRightSidebarElem(_ => Some(elem)) | ||
| | None => () | ||
| } | ||
| | None => Js.Console.error("Unexpected message received") | ||
| }; | ||
| } | ||
| ); | ||
| None; | ||
| }); | ||
| let make = | ||
| (~demoUnit: Configs.demoUnitProps => React.element, ~sidebarElem=?) => { | ||
| let (state, dispatch) = | ||
| React.useReducer( | ||
| (state, action) => | ||
|
|
@@ -867,7 +836,7 @@ module DemoUnit = { | |
|
|
||
| <div name="DemoUnit" style=Styles.container> | ||
| <div style=Styles.contents> {demoUnit(props)} </div> | ||
| {switch (parentWindowRightSidebarElem) { | ||
| {switch (sidebarElem) { | ||
| | None => React.null | ||
| | Some(element) => | ||
| ReactDOM.createPortal( | ||
|
|
@@ -909,22 +878,12 @@ module DemoUnitFrame = { | |
| (), | ||
| ); | ||
|
|
||
| let useFullframeUrl: bool = [%mel.raw | ||
| {js|typeof USE_FULL_IFRAME_URL === "boolean" ? USE_FULL_IFRAME_URL : false|js} | ||
| ]; | ||
|
Comment on lines
-912
to
-914
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this feature removed?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. my bad. I was not sure what it was doing and how to debug it. Will check |
||
|
|
||
| [@react.component] | ||
| let make = | ||
| (~queryString: string, ~responsiveMode, ~onLoad: Js.t('a) => unit) => { | ||
| let iframePath = if (useFullframeUrl) {"demo/index.html"} else {"demo"}; | ||
| let make = (~responsiveMode, ~onLoad: Js.t('a) => unit, ~children) => { | ||
| let (body, setBody) = React.useState(_ => None); | ||
|
|
||
| <div name="DemoUnitFrame" style={container(responsiveMode)}> | ||
| <iframe | ||
| onLoad={event => { | ||
| let iframe = event->React.Event.Synthetic.target; | ||
| let window = iframe##contentWindow; | ||
| onLoad(window); | ||
| }} | ||
| src={(iframePath ++ {js|?iframe=true&|js}) ++ queryString} | ||
| style={ReactDOM.Style.make( | ||
| ~height= | ||
| switch (responsiveMode) { | ||
|
|
@@ -939,7 +898,18 @@ module DemoUnitFrame = { | |
| ~border="none", | ||
| (), | ||
| )} | ||
| /> | ||
| onLoad={event => { | ||
| let iframe = event->React.Event.Synthetic.target; | ||
| let body = | ||
| iframe##contentWindow##document->Option.flatMap(d => d##body); | ||
| setBody(_ => body); | ||
| onLoad(iframe##contentWindow); | ||
| }}> | ||
| {switch (body) { | ||
| | None => React.null | ||
| | Some(body) => ReactDOM.createPortal(children, body) | ||
| }} | ||
| </iframe> | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the core change. Instead of referencing iframe with demo, we just render demo using portal in persistent iframe
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you thought about how this change might affect stateful changes that the iframe keeps from one demo to another? E.g. if some iframe loads some styles in the page
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, I didn't. I assumed this is the responsibility of the consumer: do a cleanup on unmount. |
||
| </div>; | ||
| }; | ||
| }; | ||
|
|
@@ -1017,29 +987,20 @@ module App = { | |
| let make = (~demos: Demos.t) => { | ||
| let url = ReasonReactRouter.useUrl(); | ||
| let urlSearchParams = url.search->URLSearchParams.make; | ||
| let (sidebarElem, setSidebarElem) = React.useState(_ => None); | ||
| let route = | ||
| switch ( | ||
| urlSearchParams->(URLSearchParams.get("iframe")), | ||
| urlSearchParams->(URLSearchParams.get("demo")), | ||
| ) { | ||
| | (Some("true"), Some(demoName)) => Unit(urlSearchParams, demoName) | ||
| | (_, Some(_)) => Demo(url.search) | ||
| | (_, Some(demoName)) => Demo(demoName) | ||
| | _ => Home | ||
| }; | ||
|
|
||
| let (loadedIframeWindow: option(Js.t('a)), setLoadedIframeWindow) = | ||
| React.useState(() => None); | ||
|
|
||
| let (iframeKey, setIframeKey) = | ||
| React.useState(() => Js.Date.now()->Float.toString); | ||
|
|
||
| React.useEffect1( | ||
| () => { | ||
| setIframeKey(_ => Js.Date.now()->Float.toString); | ||
| None; | ||
| }, | ||
| [|url|], | ||
| ); | ||
| let (showRightSidebar, toggleShowRightSidebar) = | ||
| React.useState(() => | ||
| LocalStorage.localStorage | ||
|
|
@@ -1089,72 +1050,70 @@ module App = { | |
| }; | ||
|
|
||
| <div name="App" style=Styles.app> | ||
| {switch (route) { | ||
| | Unit(_, _) => React.null | ||
| | Demo(_) | ||
| | Home => | ||
| <DemoListSidebar | ||
| demos | ||
| urlSearchParams | ||
| isCategoriesCollapsedByDefault | ||
| onToggleCollapsedCategoriesByDefault | ||
| /> | ||
| }} | ||
| {switch (route) { | ||
| | Unit(urlSearchParams, demoName) => | ||
| let demoUnit = Demos.findDemo(urlSearchParams, demoName, demos); | ||
| <div style=Styles.main> | ||
| {demoUnit | ||
| ->(Option.map(demoUnit => <DemoUnit demoUnit />)) | ||
| ->(Option.getWithDefault("Demo not found"->React.string))} | ||
| ->Option.map(demoUnit => <DemoUnit demoUnit />) | ||
| ->Option.getWithDefault("Demo not found"->React.string)} | ||
| </div>; | ||
| | Demo(queryString) => | ||
| <> | ||
| <DemoListSidebar | ||
| demos | ||
| urlSearchParams | ||
| isCategoriesCollapsedByDefault | ||
| onToggleCollapsedCategoriesByDefault | ||
| | Demo(demoName) => | ||
| let demoUnit = | ||
| Demos.findDemo(urlSearchParams, demoName, demos) | ||
| ->Option.map(demoUnit => | ||
| <DemoUnit demoUnit key={url.search} ?sidebarElem /> | ||
| ) | ||
| ->Option.getWithDefault("Demo not found"->React.string); | ||
| <div name="Content" style=Styles.right> | ||
| <TopPanel | ||
| isSidebarHidden={!showRightSidebar} | ||
| responsiveMode | ||
| onRightSidebarToggle={() => { | ||
| toggleShowRightSidebar(_ => !showRightSidebar); | ||
| switch (loadedIframeWindow) { | ||
| | Some(window) when !showRightSidebar => | ||
| Window.postMessage(window, RightSidebarDisplayed) | ||
|
Comment on lines
+1086
to
+1087
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure why it's done via "signalling", but decided to keep it this way for now to limit the scope of this PR |
||
| | None | ||
| | _ => () | ||
| }; | ||
| }} | ||
| onSetResponsiveMode | ||
| /> | ||
| <div name="Content" style=Styles.right> | ||
| <TopPanel | ||
| isSidebarHidden={!showRightSidebar} | ||
| responsiveMode | ||
| onRightSidebarToggle={() => { | ||
| toggleShowRightSidebar(_ => !showRightSidebar); | ||
| switch (loadedIframeWindow) { | ||
| | Some(window) when !showRightSidebar => | ||
| Window.postMessage(window, RightSidebarDisplayed) | ||
| | None | ||
| | _ => () | ||
| }; | ||
| }} | ||
| onSetResponsiveMode | ||
| /> | ||
| <div name="Demo" style=Styles.demo> | ||
| <div style=Styles.demoContents> | ||
| <DemoUnitFrame | ||
| key={"DemoUnitFrame" ++ iframeKey} | ||
| queryString | ||
| responsiveMode | ||
| onLoad={iframeWindow => | ||
| setLoadedIframeWindow(_ => Some(iframeWindow)) | ||
| } | ||
| /> | ||
| </div> | ||
| {if (showRightSidebar) { | ||
| <Sidebar | ||
| key={"Sidebar" ++ iframeKey} | ||
| innerContainerId=rightSidebarId | ||
| />; | ||
| } else { | ||
| React.null; | ||
| }} | ||
| <div name="Demo" style=Styles.demo> | ||
| <div style=Styles.demoContents> | ||
| <DemoUnitFrame | ||
| responsiveMode | ||
| onLoad={iframeWindow => { | ||
| setLoadedIframeWindow(_ => Some(iframeWindow)) | ||
| }}> | ||
| demoUnit | ||
| </DemoUnitFrame> | ||
| </div> | ||
| {showRightSidebar | ||
| ? <Sidebar | ||
| domRef={ReactDOM.Ref.callbackDomRef(node => | ||
| setSidebarElem(_ => node->Js.Nullable.toOption) | ||
| )} | ||
| /> | ||
| : React.null} | ||
| </div> | ||
| </> | ||
|
|
||
| </div>; | ||
| | Home => | ||
| <> | ||
| <DemoListSidebar | ||
| demos | ||
| urlSearchParams | ||
| isCategoriesCollapsedByDefault | ||
| onToggleCollapsedCategoriesByDefault | ||
| /> | ||
| <div style=Styles.empty> | ||
| <div style=Styles.emptyText> "Pick a demo"->React.string </div> | ||
| </div> | ||
| </> | ||
| <div style=Styles.empty> | ||
| <div style=Styles.emptyText> "Pick a demo"->React.string </div> | ||
| </div> | ||
| }} | ||
| </div>; | ||
| }; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also refactored the sidebar to use reference instead of getting elements by id, which seems much more fragile and caused runtime exceptions for me.