1616
1717import React , { useEffect , useRef , useState } from "react" ;
1818import { Container } from "../container/Container" ;
19+ import { Icon } from "../icons/Icon" ;
20+ import { zoom } from "../zoom/Zoom" ;
1921
2022import "./Iframe.css" ;
2123
@@ -29,6 +31,8 @@ interface Props {
2931 wide ?: boolean ;
3032 height ?: number ;
3133 maxHeight ?: number ;
34+ zoomEnabled ?: boolean ;
35+ newTabEnabled ?: boolean ;
3236 // changes on every page regen to force iframe reload
3337 previewMarker ?: string ;
3438}
@@ -44,7 +48,7 @@ export function Iframe(props: Props) {
4448const initialIframeHeight = 14 ;
4549
4650let activeElement : any = null ;
47- export function IframeFit ( { src, title, wide, height, maxHeight, light, dark, previewMarker } : Props ) {
51+ export function IframeFit ( { src, title, wide, height, maxHeight, light, dark, zoomEnabled , newTabEnabled , previewMarker } : Props ) {
4852 const containerRef = useRef < HTMLDivElement > ( null ) ;
4953 const iframeRef = useRef < HTMLIFrameElement > ( null ) ;
5054 const mutationObserverRef = useRef < MutationObserver | null > ( null ) ;
@@ -60,20 +64,9 @@ export function IframeFit({ src, title, wide, height, maxHeight, light, dark, pr
6064 iframeRef ! . current ! . src += "" ;
6165 } , [ previewMarker ] ) ;
6266
63- // handle site theme switching
64- useEffect ( ( ) => {
65- // TODO theme integration via context
66- // @ts -ignore
67- window . znaiTheme . addChangeHandler ( onThemeChange ) ;
68-
69- // @ts -ignore
70- return ( ) => window . znaiTheme . removeChangeHandler ( onThemeChange ) ;
71-
72- function onThemeChange ( ) {
73- injectCssProperties ( iframeRef , dark , light ) ;
74- updateScrollBarToMatch ( containerRef , iframeRef ) ;
75- }
76- } , [ dark , light ] ) ;
67+ const { syncTheme } = useIframeThemeSync ( iframeRef , dark , light , ( ) => {
68+ updateScrollBarToMatch ( containerRef , iframeRef ) ;
69+ } ) ;
7770
7871 useEffect ( ( ) => {
7972 return ( ) => {
@@ -89,8 +82,15 @@ export function IframeFit({ src, title, wide, height, maxHeight, light, dark, pr
8982 activeElement = document . activeElement ;
9083 }
9184
85+ const titleActions = ( zoomEnabled || newTabEnabled ) ? (
86+ < >
87+ { zoomEnabled && < Icon id = "maximize-2" onClick = { zoomIframe } /> }
88+ { newTabEnabled && < Icon id = "external-link" onClick = { openInNewTab } /> }
89+ </ >
90+ ) : undefined ;
91+
9292 return (
93- < Container wide = { wide } title = { title } additionalTitleClassNames = "znai-iframe-title" >
93+ < Container wide = { wide } title = { title } additionalTitleClassNames = "znai-iframe-title" titleActions = { titleActions } >
9494 < div ref = { containerRef } > </ div >
9595 < iframe
9696 title = { title }
@@ -104,6 +104,14 @@ export function IframeFit({ src, title, wide, height, maxHeight, light, dark, pr
104104 </ Container >
105105 ) ;
106106
107+ function zoomIframe ( ) {
108+ zoom . zoom ( < IframeZoomed src = { src } title = { title } light = { light } dark = { dark } /> ) ;
109+ }
110+
111+ function openInNewTab ( ) {
112+ window . open ( src , "_blank" ) ;
113+ }
114+
107115 function onLoad ( ) {
108116 handleSize ( ) ;
109117 updateScrollBarToMatch ( containerRef , iframeRef ) ;
@@ -153,7 +161,7 @@ export function IframeFit({ src, title, wide, height, maxHeight, light, dark, pr
153161 setTimeout ( ( ) => {
154162 const newHeight = measureContentHeight ( ) ;
155163
156- injectCssProperties ( iframeRef , dark , light ) ;
164+ syncTheme ( ) ;
157165 setCalculatedIframeHeight ( newHeight ) ;
158166 setVisible ( true ) ;
159167
@@ -168,6 +176,26 @@ export function IframeFit({ src, title, wide, height, maxHeight, light, dark, pr
168176 }
169177}
170178
179+ function useIframeThemeSync ( iframeRef : React . RefObject < HTMLIFrameElement | null > , dark : any , light : any , onThemeChangeExtra ?: ( ) => void ) {
180+ useEffect ( ( ) => {
181+ // @ts -ignore
182+ window . znaiTheme . addChangeHandler ( onThemeChange ) ;
183+ // @ts -ignore
184+ return ( ) => window . znaiTheme . removeChangeHandler ( onThemeChange ) ;
185+
186+ function onThemeChange ( ) {
187+ syncTheme ( ) ;
188+ onThemeChangeExtra ?.( ) ;
189+ }
190+ } , [ dark , light ] ) ;
191+
192+ function syncTheme ( ) {
193+ injectCssProperties ( iframeRef , dark , light ) ;
194+ }
195+
196+ return { syncTheme } ;
197+ }
198+
171199function updateScrollBarToMatch ( containerRef : any , iframeRef : any ) {
172200 const div = containerRef ! . current ;
173201
@@ -260,6 +288,27 @@ export function calcAspectRatioPaddingTop(aspectRatio: string): string {
260288 return ( ( Number ( height ) / Number ( width ) ) * 100.0 ) . toFixed ( 2 ) + "%" ;
261289}
262290
291+ function IframeZoomed ( { src, title, light, dark } : Pick < Props , "src" | "title" | "light" | "dark" > ) {
292+ const iframeRef = useRef < HTMLIFrameElement > ( null ) ;
293+ const { syncTheme } = useIframeThemeSync ( iframeRef , dark , light ) ;
294+
295+ return (
296+ < div className = "znai-iframe-zoomed" onClick = { ( e ) => e . stopPropagation ( ) } >
297+ < div className = "znai-iframe-zoomed-header" >
298+ < span className = "znai-iframe-zoomed-title" > { title } </ span >
299+ < Icon id = "x" className = "znai-iframe-zoomed-close" onClick = { ( ) => zoom . clearZoom ( ) } />
300+ </ div >
301+ < iframe
302+ title = { title }
303+ src = { src }
304+ className = "znai-iframe-zoomed-content"
305+ onLoad = { syncTheme }
306+ ref = { iframeRef }
307+ />
308+ </ div >
309+ ) ;
310+ }
311+
263312export const presentationIframe = {
264313 component : Iframe ,
265314 numberOfSlides : ( ) => 1 ,
0 commit comments