230 lines
7.3 KiB
JavaScript
230 lines
7.3 KiB
JavaScript
import { useState, useContext, useEffect, useRef } from 'react'
|
|
import PropTypes from 'prop-types'
|
|
import { Flex, Alert, Card, Spin, Splitter, Button, Modal, Input } 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'
|
|
|
|
const TemplateEditor = ({
|
|
objectData,
|
|
loading,
|
|
collapseState,
|
|
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)
|
|
}
|
|
iframeRef.current.addEventListener('load', handleLoad)
|
|
}
|
|
}
|
|
|
|
function reloadPreview(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.')
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
// 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])
|
|
|
|
return (
|
|
<>
|
|
<Splitter className={'farmcontrol-splitter'}>
|
|
{collapseState.preview == true && (
|
|
<Splitter.Panel style={{ height: '100%' }}>
|
|
<Spin
|
|
spinning={loading || reloadLoading}
|
|
indicator={<LoadingOutlined />}
|
|
>
|
|
<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'}
|
|
/>
|
|
) : (
|
|
<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 && (
|
|
<Splitter.Panel>
|
|
<Spin spinning={loading} indicator={<LoadingOutlined />}>
|
|
<Card style={style} styles={{ body: { height: '100%' } }}>
|
|
<Flex vertical gap={'middle'} style={{ height: '100%' }}>
|
|
<Alert
|
|
message={previewMessage}
|
|
showIcon
|
|
style={{ padding: '4px 8px' }}
|
|
icon={
|
|
previewError ? (
|
|
<ExclamationOctagonIcon />
|
|
) : (
|
|
<CheckCircleIcon />
|
|
)
|
|
}
|
|
type={previewError ? 'error' : 'success'}
|
|
/>
|
|
|
|
<div
|
|
style={{
|
|
overflowY: 'scroll',
|
|
height: '100%',
|
|
flex: '1 1 auto'
|
|
}}
|
|
>
|
|
<ObjectProperty
|
|
name={'content'}
|
|
height='100%'
|
|
type='codeBlock'
|
|
language={'xml'}
|
|
objectData={objectData}
|
|
isEditing={isEditing}
|
|
/>
|
|
</div>
|
|
</Flex>
|
|
</Card>
|
|
</Spin>
|
|
</Splitter.Panel>
|
|
)}
|
|
</Splitter>
|
|
<Modal
|
|
open={testObjectOpen}
|
|
closeIcon={null}
|
|
width={800}
|
|
footer={
|
|
<Button
|
|
onClick={() => {
|
|
setTestObjectOpen(false)
|
|
}}
|
|
>
|
|
Close
|
|
</Button>
|
|
}
|
|
>
|
|
<div
|
|
style={{
|
|
maxHeight: 'calc(var(--unit-100vh) - 280px)',
|
|
overflowY: 'scroll'
|
|
}}
|
|
>
|
|
<ObjectProperty
|
|
type={'codeBlock'}
|
|
name='testObject'
|
|
language='json'
|
|
objectData={objectData}
|
|
isEditing={true}
|
|
/>
|
|
</div>
|
|
</Modal>
|
|
</>
|
|
)
|
|
}
|
|
|
|
TemplateEditor.propTypes = {
|
|
loading: PropTypes.bool,
|
|
objectData: PropTypes.object,
|
|
collapseState: PropTypes.object,
|
|
isEditing: PropTypes.bool,
|
|
style: PropTypes.object
|
|
}
|
|
|
|
export default TemplateEditor
|