@@ -6,40 +6,63 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
66import { cn , isInternalHref , noReturnDebounce } from "@/lib/utils" ;
77import { CSSProperties , RefObject , useCallback , useEffect , useRef , useState } from "react" ;
88
9- const IFRAME_DEFAULTS = {
10- WIDTH : 1440 , // Magic! Experiment with this for different iframed websites
11- HEIGHT : 600 ,
9+ const IFRAME_SIZE = {
10+ /**
11+ * For Wix, mobile UA serves:
12+ * <div id="SITE_CONTAINER">
13+ * body.device-mobile-optimized:not(.responsive) #SITE_CONTAINER
14+ * width: 320px;
15+ * Hence MOBILE_WIDTH = 320.
16+ */
17+ MOBILE_WIDTH : 320 ,
18+ DESKTOP_WIDTH : 1440 ,
19+ /** DEFAULT_WIDTH = DESKTOP_WIDTH */
20+ DEFAULT_WIDTH : 1440 ,
21+ /** Non-fullscreen view iframe height in /resource page */
22+ HEIGHT : 400 ,
1223} as const ;
1324
14- function calculateResponsiveScale ( containerWidth : number , sourceWidth : number ) : number {
15- if ( ! containerWidth || ! sourceWidth ) return 1 ;
16- return Math . min ( containerWidth / sourceWidth , 1 ) ;
17- }
18-
19- function createScaleTransformStyle ( scale : number , hideTopPx : number = 0 ) {
20- const computedTopOffset = Math . max ( 0 , hideTopPx ) * scale ;
21- const height = hideTopPx > 0 ? `calc( ${ 100 / scale } % + ${ hideTopPx } px)` : ` ${ 100 / scale } %` ;
25+ /** Prefer UA detection when available; fallback to viewport width <= 640px. */
26+ function getIsMobileNow ( ) : boolean {
27+ if ( typeof window === "undefined" || typeof navigator === "undefined" ) return false ;
28+ // Wix uses UserAgent to detect mobile, so its better for us to stay consistent.
29+ const nav = navigator as Navigator & { userAgentData ?: { mobile ?: boolean } ; maxTouchPoints ?: number } ;
30+ if ( typeof nav . userAgentData ?. mobile === "boolean" ) {
31+ if ( nav . userAgentData . mobile ) return true ;
32+ }
2233
23- return {
24- transform : `scale(${ scale } )` ,
25- transformOrigin : "top left" ,
26- width : `${ 100 / scale } %` ,
27- height,
28- position : "relative" as const ,
29- top : hideTopPx > 0 ? `-${ computedTopOffset } px` : undefined ,
30- } ;
34+ const ua = navigator . userAgent || "" ;
35+ const isIpadOs =
36+ / i P a d / i. test ( ua ) && ( navigator as Navigator & { maxTouchPoints ?: number } ) . maxTouchPoints ! > 1 ;
37+ const isMobileUa = / A n d r o i d | w e b O S | i P h o n e | i P o d | B l a c k B e r r y | I E M o b i l e | O p e r a M i n i | M o b i / i. test ( ua ) && ! isIpadOs ;
38+ if ( isMobileUa ) return true ;
39+ return window . innerWidth <= 640 ;
3140}
3241
3342/**
34- * Resize the iframe zoom level to fit the container width to make it look normal
43+ * Determine whether the client is mobile and update on user rotating/resize their screen.
3544 */
45+ function useIsMobile ( ) : boolean {
46+ const [ isMobile , setIsMobile ] = useState < boolean > ( ( ) => getIsMobileNow ( ) ) ;
47+
48+ useEffect ( ( ) => {
49+ const recompute = noReturnDebounce ( ( ) => {
50+ setIsMobile ( getIsMobileNow ( ) ) ;
51+ } ) ;
52+ window . addEventListener ( "resize" , recompute ) ;
53+ return ( ) => window . removeEventListener ( "resize" , recompute ) ;
54+ } , [ ] ) ;
55+
56+ return isMobile ;
57+ }
58+
59+ /** Resize the iframe zoom level to fit the container width to make it look normal */
3660function useResponsiveScale ( targetRef : RefObject < HTMLElement | null > , sourceWidth : number ) : number {
3761 const [ scale , setScale ] = useState ( 1 ) ;
3862
3963 const updateScale = useCallback ( ( ) => {
4064 if ( ! targetRef . current ) return ;
41- const newScale = calculateResponsiveScale ( targetRef . current . clientWidth , sourceWidth ) ;
42- setScale ( newScale ) ;
65+ setScale ( targetRef . current . clientWidth && sourceWidth ? targetRef . current . clientWidth / sourceWidth : 1 ) ;
4366 } , [ targetRef , sourceWidth ] ) ;
4467
4568 useEffect ( ( ) => {
@@ -74,8 +97,8 @@ export function ResourceIframe({
7497 title,
7598 className,
7699 scale : forcedScale ,
77- desktopWidth = IFRAME_DEFAULTS . WIDTH ,
78- containerHeight = IFRAME_DEFAULTS . HEIGHT ,
100+ desktopWidth = IFRAME_SIZE . DEFAULT_WIDTH ,
101+ containerHeight = IFRAME_SIZE . HEIGHT ,
79102 hideTopPx = 0 ,
80103 fullPageHref,
81104 hideHeader = false ,
@@ -84,10 +107,11 @@ export function ResourceIframe({
84107} : ResourceIframeProps ) {
85108 const [ isLoading , setIsLoading ] = useState ( true ) ;
86109 const containerRef = useRef < HTMLDivElement > ( null ) ;
87- const calculatedScale = useResponsiveScale ( containerRef , desktopWidth ) ;
110+ const isMobileClient = useIsMobile ( ) ;
111+ const effectiveSourceWidth = isMobileClient ? IFRAME_SIZE . MOBILE_WIDTH : desktopWidth ;
112+ const calculatedScale = useResponsiveScale ( containerRef , effectiveSourceWidth ) ;
88113
89114 const finalScale = forcedScale ?? calculatedScale ;
90- const scaleWrapperStyle = createScaleTransformStyle ( finalScale , hideTopPx ) ;
91115 const linkHref = fullPageHref ?? websiteUrl ;
92116 const isInternalLink = isInternalHref ( linkHref ) ;
93117 const buttonLabel = isInternalLink ? "Open Full Page →" : "Open Original Site →" ;
@@ -131,19 +155,22 @@ export function ResourceIframe({
131155 < p className = "text-muted-foreground" > Loading website...</ p >
132156 </ div >
133157 ) }
134- < div
135- className = "h-full w-full"
136- style = { scaleWrapperStyle }
137- >
138- < iframe
139- src = { websiteUrl }
140- title = { title }
141- allowFullScreen
142- loading = "lazy"
143- className = "h-full w-full border-0"
144- onLoad = { ( ) => setIsLoading ( false ) }
145- />
146- </ div >
158+ < iframe
159+ src = { websiteUrl }
160+ title = { title }
161+ allowFullScreen
162+ loading = "lazy"
163+ className = "border-0"
164+ style = { {
165+ transform : `scale(${ finalScale } )` ,
166+ transformOrigin : "top left" ,
167+ width : `${ 100 / finalScale } %` ,
168+ height : hideTopPx > 0 ? `calc(${ 100 / finalScale } % + ${ hideTopPx } px)` : `${ 100 / finalScale } %` ,
169+ position : "relative" as const ,
170+ top : hideTopPx > 0 ? `-${ Math . max ( 0 , hideTopPx ) * finalScale } px` : undefined ,
171+ } }
172+ onLoad = { ( ) => setIsLoading ( false ) }
173+ />
147174 </ div >
148175 </ CardContent >
149176 </ Card >
0 commit comments