Refactor TemplateEditor component to utilize TemplatePreview for improved preview functionality; remove unnecessary state and effects, enhancing code clarity and maintainability.

This commit is contained in:
Tom Butcher 2025-09-05 23:19:20 +01:00
parent b6cd1bac0b
commit c7ddcd36e3
2 changed files with 179 additions and 122 deletions

View File

@ -1,14 +1,11 @@
import { useState, useContext, useEffect, useRef, useCallback } from 'react'
import { useState } from 'react'
import PropTypes from 'prop-types'
import { Flex, Alert, Card, Spin, Splitter, Button, Modal, Input } from 'antd'
import { Flex, Alert, Card, Spin, Splitter, Button, Modal } from 'antd'
import { LoadingOutlined } from '@ant-design/icons'
import PlusIcon from '../../Icons/PlusIcon.jsx'
import MinusIcon from '../../Icons/MinusIcon.jsx'
import ExclamationOctagonIcon from '../../Icons/ExclamationOctagonIcon.jsx'
import CheckCircleIcon from '../../Icons/CheckCircleIcon.jsx'
import ObjectProperty from '../common/ObjectProperty.jsx'
import { ApiServerContext } from '../context/ApiServerContext.jsx'
import InfoCircleIcon from '../../Icons/InfoCircleIcon.jsx'
import TemplatePreview from './TemplatePreview.jsx'
const TemplateEditor = ({
objectData,
@ -17,134 +14,36 @@ const TemplateEditor = ({
isEditing,
style
}) => {
const iframeRef = useRef(null)
const { fetchTemplatePreview } = useContext(ApiServerContext)
const [testObjectOpen, setTestObjectOpen] = useState(false)
const [previewMessage, setPreviewMessage] = useState('No issues found.')
const [previewError, setPreviewError] = useState(false)
const [previewContent, setPreviewContent] = useState('')
const [reloadLoading, setReloadLoading] = useState(false)
const [previewScale, setPreviewScale] = useState(1)
const updatePreviewContent = (html) => {
if (iframeRef.current) {
// Save current scroll position
const scrollY = iframeRef.current.contentWindow.scrollY
const scrollX = iframeRef.current.contentWindow.scrollX
// Update srcDoc
setPreviewContent(html)
// Restore scroll position after iframe loads new content
const handleLoad = () => {
iframeRef.current.contentWindow.scrollTo(scrollX, scrollY)
iframeRef.current.removeEventListener('load', handleLoad)
const handlePreviewMessage = (message, isError) => {
setPreviewMessage(message)
setPreviewError(isError)
}
iframeRef.current.addEventListener('load', handleLoad)
}
}
const reloadPreview = useCallback(
(content, testObject = {}, scale = 1) => {
fetchTemplatePreview(
objectData._id,
content,
testObject,
scale,
(result) => {
setReloadLoading(false)
if (result?.error) {
setPreviewError(true)
setPreviewMessage(result.error)
} else {
setPreviewError(false)
updatePreviewContent(result.html)
setPreviewMessage('No issues found.')
}
}
)
},
[fetchTemplatePreview, objectData?._id]
)
// Move useEffect to component level and use state to track objectData changes
useEffect(() => {
if (objectData) {
console.log('PreviewScale', previewScale)
reloadPreview(objectData.content, objectData.testObject, previewScale)
}
}, [objectData, previewScale, reloadPreview])
return (
<>
<Splitter className={'farmcontrol-splitter'}>
{collapseState.preview == true && (
<Splitter.Panel style={{ height: '100%' }}>
<Spin
spinning={loading || reloadLoading}
indicator={<LoadingOutlined />}
<Card
spinning={loading}
style={style}
styles={{ body: { height: '100%' } }}
>
<Card style={style} styles={{ body: { height: '100%' } }}>
<Flex vertical gap={'middle'} style={{ height: '100%' }}>
<Flex gap={'small'}>
{objectData?.objectType ? (
<ObjectProperty
objectType={objectData?.objectType}
name={'testObject'}
isEditing={true}
objectData={objectData}
disabled={!isEditing || objectData?.global}
type={'object'}
<TemplatePreview
objectData={objectData?.testObject}
documentTemplate={objectData}
loading={loading}
isEditing={isEditing}
style={style}
onTestObjectOpen={() => setTestObjectOpen(true)}
onPreviewMessage={handlePreviewMessage}
showTestObject={true}
/>
) : (
<div style={{ flexGrow: 1 }}>
<Input disabled={true} />
</div>
)}
<Button
icon={<InfoCircleIcon />}
disabled={objectData?.global}
onClick={() => {
setTestObjectOpen(true)
}}
/>
<Button
icon={<PlusIcon />}
onClick={() => {
setPreviewScale((prev) => prev + 0.05)
}}
/>
<Button
icon={<MinusIcon />}
onClick={() => {
setPreviewScale((prev) => prev - 0.05)
}}
/>
<Button
readOnly={true}
style={{ width: '65px' }}
onClick={() => {
setPreviewScale(1)
}}
>
{previewScale.toFixed(2)}x
</Button>
</Flex>
<iframe
ref={iframeRef}
srcDoc={previewContent}
frameBorder='0'
style={{
width: '100%',
flexGrow: 1,
border: '1px solid #85858541',
overflow: 'auto'
}}
/>
</Flex>
</Card>
</Spin>
</Splitter.Panel>
)}
{collapseState.editor == true && (

View File

@ -0,0 +1,158 @@
import { useState, useContext, useEffect, useRef, useCallback } from 'react'
import PropTypes from 'prop-types'
import { Flex, Button, Input } 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 { ApiServerContext } from '../context/ApiServerContext.jsx'
const TemplatePreview = ({
objectData,
documentTemplate,
loading,
isEditing,
onTestObjectOpen,
onPreviewMessage,
showTestObject = false
}) => {
const iframeRef = useRef(null)
const { fetchTemplatePreview } = useContext(ApiServerContext)
const [previewContent, setPreviewContent] = useState('')
const [reloadLoading, setReloadLoading] = useState(false)
const [previewScale, setPreviewScale] = useState(1)
const updatePreviewContent = (html) => {
if (iframeRef.current) {
// Save current scroll position
const scrollY = iframeRef.current.contentWindow.scrollY
const scrollX = iframeRef.current.contentWindow.scrollX
// Update srcDoc
setPreviewContent(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 reloadPreview = useCallback(
(content, testObject = {}, scale = 1) => {
if (!objectData?._id) {
onPreviewMessage('No object data available for preview.', true)
return
}
setReloadLoading(true)
fetchTemplatePreview(
documentTemplate._id,
content,
testObject,
scale,
(result) => {
setReloadLoading(false)
if (result?.error) {
// Handle error through parent component
onPreviewMessage(result.error, true)
} else {
updatePreviewContent(result.html)
onPreviewMessage('No issues found.', false)
}
}
)
},
[fetchTemplatePreview, objectData?._id, onPreviewMessage]
)
// Move useEffect to component level and use state to track objectData changes
useEffect(() => {
if (objectData && documentTemplate?.content) {
console.log('PreviewScale', previewScale)
reloadPreview(documentTemplate.content, objectData, previewScale)
}
}, [objectData, documentTemplate, previewScale, reloadPreview])
return (
<Flex vertical gap={'middle'} style={{ height: '100%' }}>
<Flex gap={'small'}>
{showTestObject == true ? (
<>
{documentTemplate?.objectType ? (
<ObjectProperty
objectType={documentTemplate?.objectType}
name={'testObject'}
isEditing={true}
objectData={objectData}
disabled={!isEditing || objectData?.global}
type={'object'}
/>
) : (
<div style={{ flexGrow: 1 }}>
<Input disabled={true} />
</div>
)}
<Button
icon={<InfoCircleIcon />}
disabled={objectData?.global}
onClick={() => {
onTestObjectOpen()
}}
/>
</>
) : null}
<Button
icon={<PlusIcon />}
onClick={() => {
setPreviewScale((prev) => prev + 0.05)
}}
/>
<Button
icon={<MinusIcon />}
onClick={() => {
setPreviewScale((prev) => prev - 0.05)
}}
/>
<Button
readOnly={true}
style={{ width: '65px' }}
loading={loading || reloadLoading}
disabled={loading || reloadLoading}
onClick={() => {
setPreviewScale(1)
}}
>
{previewScale.toFixed(2)}x
</Button>
</Flex>
<iframe
ref={iframeRef}
srcDoc={previewContent}
frameBorder='0'
style={{
width: '100%',
flexGrow: 1,
border: '1px solid #85858541',
overflow: 'auto'
}}
/>
</Flex>
)
}
TemplatePreview.propTypes = {
loading: PropTypes.bool,
objectData: PropTypes.object,
documentTemplate: PropTypes.object,
isEditing: PropTypes.bool,
style: PropTypes.object,
showTestObject: PropTypes.bool,
onTestObjectOpen: PropTypes.func.isRequired,
onPreviewMessage: PropTypes.func.isRequired
}
export default TemplatePreview