Enhance TemplatePreview Component with Scaling and Layout Improvements
Some checks failed
farmcontrol/farmcontrol-ui/pipeline/head There was a failure building this commit

- Introduced scaleCssSize function to dynamically adjust CSS sizes based on scaling factor.
- Added responsive width control for the preview document, improving layout adaptability.
- Implemented useLayoutEffect to apply iframe content size based on preview type and width.
- Refactored reloadPreview function to simplify parameters and ensure consistent width handling.
- Enhanced iframe rendering with scaling transformations for better visual representation.
This commit is contained in:
Tom Butcher 2026-09-12 23:47:57 +01:00
parent a590650d80
commit f01f978ec6

View File

@ -21,6 +21,17 @@ import ProgressDisplay from './ProgressDisplay.jsx'
import { CaretDownFilled } from '@ant-design/icons'
const noop = () => {}
const scaleCssSize = (size, scale) => {
if (scale === 1 || typeof size !== 'string' || !size.endsWith('px')) {
return size
}
const value = parseFloat(size)
if (!Number.isFinite(value)) {
return size
}
return `${Math.ceil(value * scale)}px`
}
const TemplatePreview = ({
objectData,
documentTemplate,
@ -57,6 +68,8 @@ const TemplatePreview = ({
const [previewScale, setPreviewScale] = useState(1)
const [previewType, setPreviewType] = useState('HTML')
const [previewWidth, setPreviewWidth] = useState(defaultPreviewWidth)
const previewWidthRef = useRef(previewWidth)
previewWidthRef.current = previewWidth
const [panMode, setPanMode] = useState(false)
const [isPanning, setIsPanning] = useState(false)
const panStartRef = useRef({ x: 0, y: 0, scrollLeft: 0, scrollTop: 0 })
@ -93,12 +106,43 @@ const TemplatePreview = ({
return
}
if (showWidthControl && !doc.querySelector('.previewDocument') && doc.body) {
const container = doc.createElement('div')
container.className = 'previewContainer'
const pages = doc.createElement('div')
pages.className = 'previewPages'
pages.id = 'pages'
const wrapper = doc.createElement('div')
wrapper.className = 'previewWrapper'
const page = doc.createElement('div')
page.className = 'previewDocument documentPage'
page.id = 'content'
while (doc.body.firstChild) {
page.appendChild(doc.body.firstChild)
}
wrapper.appendChild(page)
pages.appendChild(wrapper)
container.appendChild(pages)
doc.body.appendChild(container)
if (!doc.head?.querySelector('style[data-preview-chrome]')) {
const styleEl = doc.createElement('style')
styleEl.setAttribute('data-preview-chrome', 'true')
styleEl.textContent =
'.previewContainer{display:flex;justify-content:center;align-items:flex-start;padding:60px;box-sizing:border-box;width:fit-content}.previewPages{display:flex;flex-direction:row;align-items:flex-start;width:fit-content}.previewWrapper{position:relative;flex-shrink:0}.previewDocument{background:#fff;border:1px solid #000;box-shadow:0 0 5px rgba(0,0,0,.2);transform-origin:top left;position:relative;overflow:visible;box-sizing:content-box}'
doc.head?.appendChild(styleEl)
}
}
const wrappers = doc.querySelectorAll('.previewWrapper')
wrappers.forEach((wrapper) => {
const page = wrapper.querySelector('.previewDocument')
if (!page) {
return
}
if (showWidthControl) {
page.style.width = `${previewWidth}px`
page.style.maxWidth = `${previewWidth}px`
}
const pageVisual = page.getBoundingClientRect()
if (pageVisual.width <= 0 && pageVisual.height <= 0) {
return
@ -130,9 +174,7 @@ const TemplatePreview = ({
parseFloat(containerStyle.paddingBottom)
}
const contentWidth = showWidthControl
? previewWidth
: Math.ceil(visual.width + padX)
const contentWidth = Math.ceil(visual.width + padX)
const contentHeight = Math.ceil(
Math.max(visual.height + padY, doc.documentElement.scrollHeight)
)
@ -192,6 +234,13 @@ const TemplatePreview = ({
restoreScrollPosition()
}, [iframeSize, restoreScrollPosition])
useLayoutEffect(() => {
if (previewType != 'HTML') {
return
}
applyIframeContentSize()
}, [applyIframeContentSize, previewType, previewWidth])
const updatePreviewContentHTML = (html) => {
const scrollEl = scrollElementRef.current
if (scrollEl) {
@ -370,7 +419,7 @@ const TemplatePreview = ({
)
const reloadPreview = useCallback(
(content, testObject = {}, scale = 1) => {
(content, testObject = {}) => {
const templateId = currentTemplate?._id
if (!templateId) {
return
@ -382,7 +431,7 @@ const TemplatePreview = ({
templateId,
content,
testObject,
scale,
1,
(result) => {
if (requestId !== previewRequestIdRef.current) {
return
@ -396,14 +445,13 @@ const TemplatePreview = ({
}
},
templateType,
showWidthControl ? { width: previewWidth } : {}
showWidthControl ? { width: previewWidthRef.current } : {}
)
},
[
currentTemplate?._id,
fetchTemplatePreview,
onPreviewMessage,
previewWidth,
showWidthControl,
templateType
]
@ -424,7 +472,7 @@ const TemplatePreview = ({
const timeoutId = window.setTimeout(() => {
if (content == null) {
reloadPreview(templateContent, objectData, previewScale)
reloadPreview(templateContent, objectData)
}
}, 300)
@ -435,8 +483,6 @@ const TemplatePreview = ({
objectData,
templateContent,
templateId,
previewScale,
previewWidth,
previewType,
reloadPreview,
content
@ -534,6 +580,8 @@ const TemplatePreview = ({
}, [isPanning])
const showProgressOverlay = previewType != 'HTML' && downloadProgress != null
const scaledIframeWidth = scaleCssSize(iframeSize.width, previewScale)
const scaledIframeHeight = scaleCssSize(iframeSize.height, previewScale)
const toolbarContent = (
<>
@ -707,14 +755,14 @@ const TemplatePreview = ({
align='center'
style={{
minWidth: '100%',
width: iframeSize.width,
height: iframeSize.height
width: scaledIframeWidth,
height: scaledIframeHeight
}}
>
<div
style={{
width: iframeSize.width,
height: iframeSize.height,
width: scaledIframeWidth,
height: scaledIframeHeight,
cursor: panMode
? isPanning
? 'grabbing'
@ -729,11 +777,14 @@ const TemplatePreview = ({
scrolling='no'
frameBorder='0'
style={{
width: '100%',
height: iframeSize.height === 'auto' ? 150 : '100%',
width:
iframeSize.width === '100%' ? '100%' : iframeSize.width,
height: iframeSize.height === 'auto' ? 150 : iframeSize.height,
display: 'block',
border: 0,
overflow: 'hidden',
transform: `scale(${previewScale})`,
transformOrigin: 'top left',
pointerEvents: panMode ? 'none' : undefined
}}
/>