Enhance TemplatePreview Component with Scroll and Iframe Resizing Features
- Refactored the TemplatePreview component to improve iframe content size handling and scrolling behavior. - Introduced a new ScrollBox component to manage scroll position and enhance user experience. - Implemented a ResizeObserver to dynamically adjust iframe size based on content, ensuring better display of HTML previews. - Added cleanup logic for the observer to prevent memory leaks during component unmounting.
This commit is contained in:
parent
699e178fa1
commit
fede8af6aa
@ -1,10 +1,18 @@
|
||||
import { useState, useContext, useEffect, useRef, useCallback } from 'react'
|
||||
import {
|
||||
useState,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useCallback
|
||||
} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Flex, Button, Input, Select } from 'antd'
|
||||
import PlusIcon from '../../Icons/PlusIcon.jsx'
|
||||
import MinusIcon from '../../Icons/MinusIcon.jsx'
|
||||
import InfoCircleIcon from '../../Icons/InfoCircleIcon.jsx'
|
||||
import ObjectProperty from '../common/ObjectProperty.jsx'
|
||||
import ScrollBox from './ScrollBox.jsx'
|
||||
import { ApiServerContext } from '../context/ApiServerContext.jsx'
|
||||
|
||||
const noop = () => {}
|
||||
@ -20,6 +28,9 @@ const TemplatePreview = ({
|
||||
showPreviewSwitch = true
|
||||
}) => {
|
||||
const iframeRef = useRef(null)
|
||||
const scrollElementRef = useRef(null)
|
||||
const savedScrollRef = useRef({ top: 0, left: 0 })
|
||||
const iframeSizeObserverRef = useRef(null)
|
||||
const previewRequestIdRef = useRef(0)
|
||||
const { fetchTemplatePreview, fetchTemplatePDF } =
|
||||
useContext(ApiServerContext)
|
||||
@ -28,23 +39,112 @@ const TemplatePreview = ({
|
||||
const [reloadLoading, setReloadLoading] = useState(false)
|
||||
const [previewScale, setPreviewScale] = useState(1)
|
||||
const [previewType, setPreviewType] = useState('HTML')
|
||||
const [iframeSize, setIframeSize] = useState({
|
||||
width: '100%',
|
||||
height: 'auto'
|
||||
})
|
||||
|
||||
const applyIframeContentSize = useCallback(() => {
|
||||
const iframe = iframeRef.current
|
||||
const doc = iframe?.contentDocument
|
||||
if (!iframe || !doc?.documentElement) {
|
||||
return
|
||||
}
|
||||
|
||||
const documentEl =
|
||||
doc.querySelector('.previewDocument') ||
|
||||
doc.querySelector('.previewWrapper') ||
|
||||
doc.body
|
||||
const container = doc.querySelector('.previewContainer')
|
||||
const wrapper = doc.querySelector('.previewWrapper')
|
||||
const visual = documentEl.getBoundingClientRect()
|
||||
if (visual.width <= 0 && visual.height <= 0) {
|
||||
return
|
||||
}
|
||||
|
||||
if (wrapper) {
|
||||
wrapper.style.width = `${visual.width}px`
|
||||
wrapper.style.height = `${visual.height}px`
|
||||
}
|
||||
|
||||
let padX = 0
|
||||
let padY = 0
|
||||
if (container) {
|
||||
const containerStyle = doc.defaultView.getComputedStyle(container)
|
||||
padX =
|
||||
parseFloat(containerStyle.paddingLeft) +
|
||||
parseFloat(containerStyle.paddingRight)
|
||||
padY =
|
||||
parseFloat(containerStyle.paddingTop) +
|
||||
parseFloat(containerStyle.paddingBottom)
|
||||
}
|
||||
|
||||
const contentWidth = Math.ceil(visual.width + padX)
|
||||
const contentHeight = Math.ceil(visual.height + padY)
|
||||
|
||||
if (!doc.head?.querySelector('style[data-preview-outer-scroll]')) {
|
||||
const styleEl = doc.createElement('style')
|
||||
styleEl.setAttribute('data-preview-outer-scroll', 'true')
|
||||
styleEl.textContent =
|
||||
'html, body { overflow: hidden !important; height: auto !important; }'
|
||||
doc.head?.appendChild(styleEl)
|
||||
}
|
||||
|
||||
setIframeSize((prev) => {
|
||||
const width = `${contentWidth}px`
|
||||
const height = `${contentHeight}px`
|
||||
if (prev.width === width && prev.height === height) {
|
||||
return prev
|
||||
}
|
||||
return { width, height }
|
||||
})
|
||||
}, [])
|
||||
|
||||
const restoreScrollPosition = useCallback(() => {
|
||||
const scrollEl = scrollElementRef.current
|
||||
if (!scrollEl) {
|
||||
return
|
||||
}
|
||||
scrollEl.scrollTo(savedScrollRef.current.left, savedScrollRef.current.top)
|
||||
}, [])
|
||||
|
||||
const handleIframeLoad = useCallback(() => {
|
||||
iframeSizeObserverRef.current?.disconnect()
|
||||
iframeSizeObserverRef.current = null
|
||||
|
||||
if (previewType != 'HTML') {
|
||||
return
|
||||
}
|
||||
|
||||
applyIframeContentSize()
|
||||
|
||||
const doc = iframeRef.current?.contentDocument
|
||||
const observed =
|
||||
doc?.querySelector('.previewDocument') ||
|
||||
doc?.querySelector('.previewContainer') ||
|
||||
doc?.body
|
||||
if (observed && typeof ResizeObserver === 'function') {
|
||||
const observer = new ResizeObserver(() => {
|
||||
applyIframeContentSize()
|
||||
})
|
||||
observer.observe(observed)
|
||||
iframeSizeObserverRef.current = observer
|
||||
}
|
||||
}, [applyIframeContentSize, previewType])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
restoreScrollPosition()
|
||||
}, [iframeSize, restoreScrollPosition])
|
||||
|
||||
const updatePreviewContentHTML = (html) => {
|
||||
if (iframeRef.current) {
|
||||
// Save current scroll position
|
||||
const scrollY = iframeRef.current.contentWindow.scrollY
|
||||
const scrollX = iframeRef.current.contentWindow.scrollX
|
||||
|
||||
// Update srcDoc
|
||||
const scrollEl = scrollElementRef.current
|
||||
if (scrollEl) {
|
||||
savedScrollRef.current = {
|
||||
top: scrollEl.scrollTop,
|
||||
left: scrollEl.scrollLeft
|
||||
}
|
||||
}
|
||||
setPreviewContentHTML(html)
|
||||
|
||||
// Restore scroll position after iframe loads new content
|
||||
const handleLoad = () => {
|
||||
iframeRef.current.contentWindow.scrollTo(scrollX, scrollY)
|
||||
iframeRef.current.removeEventListener('load', handleLoad)
|
||||
}
|
||||
iframeRef.current.addEventListener('load', handleLoad)
|
||||
}
|
||||
}
|
||||
|
||||
const reloadPreviewPDF = useCallback(
|
||||
@ -84,12 +184,7 @@ const TemplatePreview = ({
|
||||
|
||||
const requestId = ++previewRequestIdRef.current
|
||||
setReloadLoading(true)
|
||||
fetchTemplatePreview(
|
||||
templateId,
|
||||
content,
|
||||
testObject,
|
||||
scale,
|
||||
(result) => {
|
||||
fetchTemplatePreview(templateId, content, testObject, scale, (result) => {
|
||||
if (requestId !== previewRequestIdRef.current) {
|
||||
return
|
||||
}
|
||||
@ -100,8 +195,7 @@ const TemplatePreview = ({
|
||||
updatePreviewContentHTML(result.html)
|
||||
onPreviewMessage('No issues found.', false)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
},
|
||||
[fetchTemplatePreview, onPreviewMessage, documentTemplate?._id]
|
||||
)
|
||||
@ -136,6 +230,12 @@ const TemplatePreview = ({
|
||||
reloadPreviewPDF
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
iframeSizeObserverRef.current?.disconnect()
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Flex vertical gap={'middle'} style={{ height: '100%' }}>
|
||||
<Flex gap={'small'}>
|
||||
@ -149,6 +249,7 @@ const TemplatePreview = ({
|
||||
objectData={objectData}
|
||||
disabled={!isEditing || objectData?.global}
|
||||
type={'object'}
|
||||
style={{ minWidth: 0 }}
|
||||
/>
|
||||
) : (
|
||||
<div style={{ flexGrow: 1 }}>
|
||||
@ -205,18 +306,50 @@ const TemplatePreview = ({
|
||||
) : null}
|
||||
</Flex>
|
||||
|
||||
<div
|
||||
style={{
|
||||
flex: '1 1 auto',
|
||||
minHeight: 0,
|
||||
border: '1px solid #85858541'
|
||||
}}
|
||||
>
|
||||
{previewType == 'HTML' ? (
|
||||
<ScrollBox scrollableNodeProps={{ ref: scrollElementRef }}>
|
||||
<div
|
||||
style={{
|
||||
width: iframeSize.width,
|
||||
height: iframeSize.height
|
||||
}}
|
||||
>
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
srcDoc={previewType == 'HTML' ? previewContentHTML : undefined}
|
||||
src={previewType == 'PDF' ? pdfBlob : undefined}
|
||||
srcDoc={previewContentHTML}
|
||||
onLoad={handleIframeLoad}
|
||||
scrolling='no'
|
||||
frameBorder='0'
|
||||
style={{
|
||||
width: '100%',
|
||||
flexGrow: 1,
|
||||
border: '1px solid #85858541',
|
||||
overflow: 'auto'
|
||||
height: iframeSize.height === 'auto' ? 150 : '100%',
|
||||
display: 'block',
|
||||
border: 0,
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ScrollBox>
|
||||
) : (
|
||||
<iframe
|
||||
ref={iframeRef}
|
||||
src={pdfBlob}
|
||||
frameBorder='0'
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
border: 0
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user