Add MagicWandIcon component and integrate template formatting functionality
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Introduced MagicWandIcon component using SVG for enhanced visual representation in the TemplateEditor. - Updated TemplateEditor to include a button for formatting template content, utilizing the new fetchTemplateFormat function from ApiServerContext. - Enhanced DocumentTemplateDesign to pass necessary props to TemplateEditor for improved functionality. - Refactored CodeBlockEditor to support an additional value prop for better code handling.
This commit is contained in:
parent
ec0e6245b3
commit
6d0e9a7abc
8
assets/icons/magicwandicon.svg
Normal file
8
assets/icons/magicwandicon.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||||
|
<g transform="matrix(0.885551,0,0,0.885551,3,2.046843)">
|
||||||
|
<path d="M50.547,29.572C51.295,29.572 51.877,29.045 51.995,28.338C53.37,19.038 54.594,17.459 64.024,16.253C64.81,16.143 65.361,15.53 65.361,14.782C65.361,14.043 64.81,13.453 64.024,13.367C54.904,12.161 53.559,10.64 51.995,1.321C51.829,0.503 51.295,0 50.547,0C49.808,0 49.226,0.503 49.132,1.314C47.805,10.551 46.39,12.113 37.086,13.367C36.316,13.453 35.765,14.043 35.765,14.782C35.765,15.53 36.316,16.112 37.086,16.253C46.127,17.794 47.513,18.895 49.132,28.33C49.242,29.045 49.817,29.572 50.547,29.572ZM9.329,55.436C9.964,55.436 10.42,54.98 10.507,54.376C11.325,48.334 11.42,48.159 17.652,47.278C18.28,47.168 18.712,46.696 18.712,46.108C18.712,45.496 18.304,45.023 17.652,44.937C11.349,43.794 11.349,43.667 10.507,37.815C10.42,37.187 9.941,36.731 9.329,36.731C8.724,36.731 8.268,37.187 8.158,37.839C7.364,43.714 7.292,43.937 1.108,44.937C0.384,45.023 0,45.448 0,46.108C0,46.696 0.384,47.144 1.013,47.278C7.34,48.398 7.388,48.501 8.158,54.376C8.268,54.98 8.724,55.436 9.329,55.436ZM20.339,65.06C20.762,65.06 21.06,64.755 21.13,64.332C21.788,60.84 21.727,60.803 25.481,60.013C25.903,59.919 26.177,59.661 26.177,59.246C26.177,58.831 25.927,58.518 25.505,58.432C21.734,57.665 21.802,57.643 21.13,54.081C21.06,53.665 20.731,53.36 20.339,53.36C19.916,53.36 19.595,53.665 19.525,54.096C18.87,57.643 18.968,57.688 15.205,58.432C14.727,58.542 14.453,58.823 14.453,59.246C14.453,59.661 14.727,59.919 15.15,60.013C18.968,60.803 18.87,60.84 19.525,64.309C19.595,64.755 19.916,65.06 20.339,65.06Z" style="fill-rule:nonzero;"/>
|
||||||
|
<path d="M55.838,66.006C57.977,68.2 61.69,68.193 63.862,66.006C66.042,63.788 66.042,60.206 63.855,57.981L20.002,13.818C17.839,11.624 14.149,11.624 11.954,13.818C9.774,16.06 9.774,19.641 11.961,21.859L55.838,66.006ZM28.346,32.791L15.017,19.432C14.341,18.732 14.317,17.622 15.017,16.922C15.71,16.205 16.851,16.198 17.575,16.922L30.815,30.241L28.346,32.791Z" style="fill-rule:nonzero;"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
@ -135,7 +135,7 @@ const DocumentTemplateDesign = () => {
|
|||||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ loading, isEditing, objectData }) => {
|
{({ loading, isEditing, objectData, form, setObjectData }) => {
|
||||||
return (
|
return (
|
||||||
<TemplateEditor
|
<TemplateEditor
|
||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
@ -143,6 +143,8 @@ const DocumentTemplateDesign = () => {
|
|||||||
style={{ height: '72vh' }}
|
style={{ height: '72vh' }}
|
||||||
collapseState={collapseState}
|
collapseState={collapseState}
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
|
form={form}
|
||||||
|
setObjectData={setObjectData}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -82,6 +82,7 @@ export function getCodeLanguageExtension(
|
|||||||
|
|
||||||
export default function CodeBlockEditor({
|
export default function CodeBlockEditor({
|
||||||
code = '',
|
code = '',
|
||||||
|
value,
|
||||||
language = 'javascript',
|
language = 'javascript',
|
||||||
style = {},
|
style = {},
|
||||||
className = '',
|
className = '',
|
||||||
@ -95,7 +96,8 @@ export default function CodeBlockEditor({
|
|||||||
}) {
|
}) {
|
||||||
const { isDarkMode } = useThemeContext()
|
const { isDarkMode } = useThemeContext()
|
||||||
const [codeMirrorOpen, setCodeMirrorOpen] = useState(false)
|
const [codeMirrorOpen, setCodeMirrorOpen] = useState(false)
|
||||||
const editorCode = toEditorCode(code, language)
|
const sourceCode = value !== undefined ? value : code
|
||||||
|
const editorCode = toEditorCode(sourceCode, language)
|
||||||
|
|
||||||
const languageExtension = useMemo(
|
const languageExtension = useMemo(
|
||||||
() => getCodeLanguageExtension(language, autoCompleteObject),
|
() => getCodeLanguageExtension(language, autoCompleteObject),
|
||||||
@ -192,7 +194,8 @@ export default function CodeBlockEditor({
|
|||||||
}
|
}
|
||||||
|
|
||||||
CodeBlockEditor.propTypes = {
|
CodeBlockEditor.propTypes = {
|
||||||
code: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
|
code: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
|
value: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
language: PropTypes.oneOfType([
|
language: PropTypes.oneOfType([
|
||||||
PropTypes.string,
|
PropTypes.string,
|
||||||
PropTypes.arrayOf(PropTypes.string)
|
PropTypes.arrayOf(PropTypes.string)
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useState, useCallback } from 'react'
|
import { useState, useCallback, useContext } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import {
|
import {
|
||||||
Flex,
|
Flex,
|
||||||
@ -14,9 +14,11 @@ import {
|
|||||||
import { LoadingOutlined, CaretDownOutlined } from '@ant-design/icons'
|
import { LoadingOutlined, CaretDownOutlined } from '@ant-design/icons'
|
||||||
import ExclamationOctagonIcon from '../../Icons/ExclamationOctagonIcon.jsx'
|
import ExclamationOctagonIcon from '../../Icons/ExclamationOctagonIcon.jsx'
|
||||||
import CheckCircleIcon from '../../Icons/CheckCircleIcon.jsx'
|
import CheckCircleIcon from '../../Icons/CheckCircleIcon.jsx'
|
||||||
|
import MagicWandIcon from '../../Icons/MagicWandIcon.jsx'
|
||||||
import ObjectProperty from '../common/ObjectProperty.jsx'
|
import ObjectProperty from '../common/ObjectProperty.jsx'
|
||||||
import TemplatePreview from './TemplatePreview.jsx'
|
import TemplatePreview from './TemplatePreview.jsx'
|
||||||
import DataTree from './DataTree.jsx'
|
import DataTree from './DataTree.jsx'
|
||||||
|
import { ApiServerContext } from '../context/ApiServerContext.jsx'
|
||||||
//import { useMediaQuery } from 'react-responsive'
|
//import { useMediaQuery } from 'react-responsive'
|
||||||
|
|
||||||
const TemplateEditor = ({
|
const TemplateEditor = ({
|
||||||
@ -24,12 +26,16 @@ const TemplateEditor = ({
|
|||||||
loading,
|
loading,
|
||||||
collapseState,
|
collapseState,
|
||||||
isEditing,
|
isEditing,
|
||||||
style
|
style,
|
||||||
|
form,
|
||||||
|
setObjectData
|
||||||
}) => {
|
}) => {
|
||||||
|
const { fetchTemplateFormat } = useContext(ApiServerContext)
|
||||||
const [testObjectOpen, setTestObjectOpen] = useState(false)
|
const [testObjectOpen, setTestObjectOpen] = useState(false)
|
||||||
const [testObjectViewMode, setTestObjectViewMode] = useState('Tree')
|
const [testObjectViewMode, setTestObjectViewMode] = useState('Tree')
|
||||||
const [previewMessage, setPreviewMessage] = useState('No issues found.')
|
const [previewMessage, setPreviewMessage] = useState('No issues found.')
|
||||||
const [previewError, setPreviewError] = useState(false)
|
const [previewError, setPreviewError] = useState(false)
|
||||||
|
const [formatLoading, setFormatLoading] = useState(false)
|
||||||
//const isMobile = useMediaQuery({ maxWidth: 768 })
|
//const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||||
|
|
||||||
const handlePreviewMessage = useCallback((message, isError) => {
|
const handlePreviewMessage = useCallback((message, isError) => {
|
||||||
@ -67,6 +73,40 @@ const TemplateEditor = ({
|
|||||||
setPreviewError(isError)
|
setPreviewError(isError)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const handleFormatCode = useCallback(async () => {
|
||||||
|
const content = form?.getFieldValue?.('content') ?? objectData?.content
|
||||||
|
if (content == null || typeof content !== 'string') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setFormatLoading(true)
|
||||||
|
try {
|
||||||
|
const result = await fetchTemplateFormat(content)
|
||||||
|
if (result?.error) {
|
||||||
|
handlePreviewMessage(result.error, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result?.content == null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
form?.setFieldValue?.('content', result.content)
|
||||||
|
setObjectData?.((prev) => ({
|
||||||
|
...prev,
|
||||||
|
content: result.content
|
||||||
|
}))
|
||||||
|
} finally {
|
||||||
|
setFormatLoading(false)
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
form,
|
||||||
|
objectData?.content,
|
||||||
|
fetchTemplateFormat,
|
||||||
|
handlePreviewMessage,
|
||||||
|
setObjectData
|
||||||
|
])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Splitter className={'farmcontrol-splitter'} vertical={true}>
|
<Splitter className={'farmcontrol-splitter'} vertical={true}>
|
||||||
@ -95,19 +135,33 @@ const TemplateEditor = ({
|
|||||||
<Spin spinning={loading} indicator={<LoadingOutlined />}>
|
<Spin spinning={loading} indicator={<LoadingOutlined />}>
|
||||||
<Card style={style} styles={{ body: { height: '100%' } }}>
|
<Card style={style} styles={{ body: { height: '100%' } }}>
|
||||||
<Flex vertical gap={'middle'} style={{ height: '100%' }}>
|
<Flex vertical gap={'middle'} style={{ height: '100%' }}>
|
||||||
<Alert
|
<Flex gap={'small'} align={'stretch'}>
|
||||||
message={previewMessage}
|
<Alert
|
||||||
showIcon
|
message={previewMessage}
|
||||||
style={{ padding: '4px 5px 4px 8px' }}
|
showIcon
|
||||||
icon={
|
style={{
|
||||||
previewError ? (
|
padding: '4px 5px 4px 8px',
|
||||||
<ExclamationOctagonIcon />
|
flex: '1 1 auto',
|
||||||
) : (
|
minWidth: 0
|
||||||
<CheckCircleIcon />
|
}}
|
||||||
)
|
icon={
|
||||||
}
|
previewError ? (
|
||||||
type={previewError ? 'error' : 'success'}
|
<ExclamationOctagonIcon />
|
||||||
/>
|
) : (
|
||||||
|
<CheckCircleIcon />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
type={previewError ? 'error' : 'success'}
|
||||||
|
/>
|
||||||
|
{isEditing && (
|
||||||
|
<Button
|
||||||
|
icon={<MagicWandIcon />}
|
||||||
|
loading={formatLoading}
|
||||||
|
disabled={loading || formatLoading}
|
||||||
|
onClick={handleFormatCode}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -190,7 +244,9 @@ TemplateEditor.propTypes = {
|
|||||||
objectData: PropTypes.object,
|
objectData: PropTypes.object,
|
||||||
collapseState: PropTypes.object,
|
collapseState: PropTypes.object,
|
||||||
isEditing: PropTypes.bool,
|
isEditing: PropTypes.bool,
|
||||||
style: PropTypes.object
|
style: PropTypes.object,
|
||||||
|
form: PropTypes.object,
|
||||||
|
setObjectData: PropTypes.func
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TemplateEditor
|
export default TemplateEditor
|
||||||
|
|||||||
@ -1774,6 +1774,33 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
URL.revokeObjectURL(fileUrl)
|
URL.revokeObjectURL(fileUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fetchTemplateFormat = async (content, callback) => {
|
||||||
|
logger.debug('Formatting template content...')
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
`${config.backendUrl}/documenttemplates/format`,
|
||||||
|
{ content },
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback(response.data)
|
||||||
|
}
|
||||||
|
return response.data
|
||||||
|
} catch (err) {
|
||||||
|
const error = getTemplateErrorFromResponse(err)
|
||||||
|
logger.error('Error formatting template content:', error)
|
||||||
|
if (typeof callback === 'function') {
|
||||||
|
callback({ error })
|
||||||
|
}
|
||||||
|
return { error }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const fetchTemplatePreview = async (
|
const fetchTemplatePreview = async (
|
||||||
id,
|
id,
|
||||||
content,
|
content,
|
||||||
@ -2346,6 +2373,7 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
exportToExcel,
|
exportToExcel,
|
||||||
exportToCsv,
|
exportToCsv,
|
||||||
fetchTemplatePreview,
|
fetchTemplatePreview,
|
||||||
|
fetchTemplateFormat,
|
||||||
fetchTemplatePDF,
|
fetchTemplatePDF,
|
||||||
fetchNotes,
|
fetchNotes,
|
||||||
downloadTemplate,
|
downloadTemplate,
|
||||||
|
|||||||
6
src/components/Icons/MagicWandIcon.jsx
Normal file
6
src/components/Icons/MagicWandIcon.jsx
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import Icon from '@ant-design/icons'
|
||||||
|
import CustomIconSvg from '../../../assets/icons/magicwandicon.svg?react'
|
||||||
|
|
||||||
|
const MagicWandIcon = (props) => <Icon component={CustomIconSvg} {...props} />
|
||||||
|
|
||||||
|
export default MagicWandIcon
|
||||||
Loading…
x
Reference in New Issue
Block a user