Compare commits
No commits in common. "8dd55a4257512c24b7c8bfd7cc7fdd5676eee5ce" and "869a5309fb55c8b1f55babf8b6044cdcc10b1065" have entirely different histories.
8dd55a4257
...
869a5309fb
@ -4,8 +4,7 @@ import {
|
|||||||
indentNodeProp,
|
indentNodeProp,
|
||||||
delimitedIndent,
|
delimitedIndent,
|
||||||
foldNodeProp,
|
foldNodeProp,
|
||||||
foldInside,
|
foldInside
|
||||||
syntaxTree
|
|
||||||
} from '@codemirror/language'
|
} from '@codemirror/language'
|
||||||
import { styleTags, tags as t } from '@lezer/highlight'
|
import { styleTags, tags as t } from '@lezer/highlight'
|
||||||
import { parseMixed } from '@lezer/common'
|
import { parseMixed } from '@lezer/common'
|
||||||
@ -82,64 +81,13 @@ function buildJsScope(autoCompleteObject, templateType) {
|
|||||||
return typed
|
return typed
|
||||||
}
|
}
|
||||||
|
|
||||||
function isResourceValueContext(state, position) {
|
|
||||||
let node = syntaxTree(state).resolveInner(position, -1)
|
|
||||||
let insideTemplateInterpolation = false
|
|
||||||
|
|
||||||
while (node) {
|
|
||||||
if (node.name === 'Interpolation') {
|
|
||||||
insideTemplateInterpolation = true
|
|
||||||
}
|
|
||||||
if (node.name === 'AttributeValue' || node.name === 'String') {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if (node.name === 'TemplateString') {
|
|
||||||
return !insideTemplateInterpolation
|
|
||||||
}
|
|
||||||
node = node.parent
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
function resourceCompletionSource(resourceNames) {
|
|
||||||
const options = [...new Set(resourceNames)]
|
|
||||||
.filter((name) => typeof name === 'string' && name.length > 0)
|
|
||||||
.map((name) => ({
|
|
||||||
label: `@[${name}]`,
|
|
||||||
apply: `@[${name}]`,
|
|
||||||
type: 'variable',
|
|
||||||
detail: 'Template resource'
|
|
||||||
}))
|
|
||||||
|
|
||||||
return (context) => {
|
|
||||||
if (options.length === 0) return null
|
|
||||||
|
|
||||||
const before = context.state.sliceDoc(0, context.pos)
|
|
||||||
const match = before.match(/@(?:\[[^\]\r\n]*)?$/)
|
|
||||||
if (!match || !isResourceValueContext(context.state, context.pos)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
from: context.pos - match[0].length,
|
|
||||||
options
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Language support for FarmControl EJS+XML document templates.
|
* Language support for FarmControl EJS+XML document templates.
|
||||||
* @param {{ autoCompleteObject?: object|string, templateType?: string, resourceNames?: string[] }} [options]
|
* @param {{ autoCompleteObject?: object|string, templateType?: string }} [options]
|
||||||
*/
|
*/
|
||||||
export function fcTemplateLang(options = {}) {
|
export function fcTemplateLang(options = {}) {
|
||||||
const {
|
const { autoCompleteObject, templateType = 'documentTemplate' } = options
|
||||||
autoCompleteObject,
|
|
||||||
templateType = 'documentTemplate',
|
|
||||||
resourceNames = []
|
|
||||||
} = options
|
|
||||||
const jsScope = buildJsScope(autoCompleteObject, templateType)
|
const jsScope = buildJsScope(autoCompleteObject, templateType)
|
||||||
const completeResources = resourceCompletionSource(resourceNames)
|
|
||||||
|
|
||||||
return new LanguageSupport(fcTemplateLanguage, [
|
return new LanguageSupport(fcTemplateLanguage, [
|
||||||
xmlLanguage.data.of({
|
xmlLanguage.data.of({
|
||||||
@ -148,8 +96,6 @@ export function fcTemplateLang(options = {}) {
|
|||||||
fcTemplateAttributes
|
fcTemplateAttributes
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
xmlLanguage.data.of({ autocomplete: completeResources }),
|
|
||||||
javascriptLanguage.data.of({ autocomplete: completeResources }),
|
|
||||||
autoCloseTags,
|
autoCloseTags,
|
||||||
...javascriptCompletionSupport(jsScope)
|
...javascriptCompletionSupport(jsScope)
|
||||||
])
|
])
|
||||||
|
|||||||
@ -8,12 +8,10 @@ import useCollapseState from '../../hooks/useCollapseState.jsx'
|
|||||||
import NotesPanel from '../../common/NotesPanel.jsx'
|
import NotesPanel from '../../common/NotesPanel.jsx'
|
||||||
import InfoCollapse from '../../common/InfoCollapse.jsx'
|
import InfoCollapse from '../../common/InfoCollapse.jsx'
|
||||||
import ObjectInfo from '../../common/ObjectInfo.jsx'
|
import ObjectInfo from '../../common/ObjectInfo.jsx'
|
||||||
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
|
||||||
import ViewButton from '../../common/ViewButton.jsx'
|
import ViewButton from '../../common/ViewButton.jsx'
|
||||||
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
import InfoCircleIcon from '../../../Icons/InfoCircleIcon.jsx'
|
||||||
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
import NoteIcon from '../../../Icons/NoteIcon.jsx'
|
||||||
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
import AuditLogIcon from '../../../Icons/AuditLogIcon.jsx'
|
||||||
import FileIcon from '../../../Icons/FileIcon.jsx'
|
|
||||||
import ObjectForm from '../../common/ObjectForm.jsx'
|
import ObjectForm from '../../common/ObjectForm.jsx'
|
||||||
import EditButtons from '../../common/EditButtons.jsx'
|
import EditButtons from '../../common/EditButtons.jsx'
|
||||||
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
import ObjectTableNavigationButtons from '../../common/ObjectTableNavigationButtons.jsx'
|
||||||
@ -25,10 +23,7 @@ import ObjectTable from '../../common/ObjectTable.jsx'
|
|||||||
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
import InfoCollapsePlaceholder from '../../common/InfoCollapsePlaceholder.jsx'
|
||||||
import InfoActionButtons from '../../common/InfoActionButtons.jsx'
|
import InfoActionButtons from '../../common/InfoActionButtons.jsx'
|
||||||
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
import UserNotifierToggle from '../../common/UserNotifierToggle.jsx'
|
||||||
import {
|
import { getModelByName } from '../../../../database/ObjectModels.js'
|
||||||
getModelByName,
|
|
||||||
getModelProperty
|
|
||||||
} from '../../../../database/ObjectModels.js'
|
|
||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
|
|
||||||
const log = loglevel.getLogger('DocumentTemplateInfo')
|
const log = loglevel.getLogger('DocumentTemplateInfo')
|
||||||
@ -45,7 +40,6 @@ const DocumentTemplateInfo = () => {
|
|||||||
'DocumentTemplateInfo',
|
'DocumentTemplateInfo',
|
||||||
{
|
{
|
||||||
info: true,
|
info: true,
|
||||||
resources: true,
|
|
||||||
stocks: true,
|
stocks: true,
|
||||||
notes: true,
|
notes: true,
|
||||||
auditLogs: false
|
auditLogs: false
|
||||||
@ -114,7 +108,6 @@ const DocumentTemplateInfo = () => {
|
|||||||
disabled={objectFormState.loading}
|
disabled={objectFormState.loading}
|
||||||
items={[
|
items={[
|
||||||
{ key: 'info', label: 'DocumentTemplate Information' },
|
{ key: 'info', label: 'DocumentTemplate Information' },
|
||||||
{ key: 'resources', label: 'Resources' },
|
|
||||||
{ key: 'notes', label: 'Notes' },
|
{ key: 'notes', label: 'Notes' },
|
||||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||||
]}
|
]}
|
||||||
@ -167,6 +160,13 @@ const DocumentTemplateInfo = () => {
|
|||||||
actions={actions}
|
actions={actions}
|
||||||
loading={objectFormState.loading}
|
loading={objectFormState.loading}
|
||||||
ref={actionHandlerRef}
|
ref={actionHandlerRef}
|
||||||
|
>
|
||||||
|
<InfoCollapse
|
||||||
|
title='Document Template Information'
|
||||||
|
icon={<InfoCircleIcon />}
|
||||||
|
active={collapseState.info}
|
||||||
|
onToggle={(expanded) => updateCollapseState('info', expanded)}
|
||||||
|
collapseKey='info'
|
||||||
>
|
>
|
||||||
<ObjectForm
|
<ObjectForm
|
||||||
id={documentTemplateId}
|
id={documentTemplateId}
|
||||||
@ -178,17 +178,8 @@ const DocumentTemplateInfo = () => {
|
|||||||
setEditFormState((prev) => ({ ...prev, ...state }))
|
setEditFormState((prev) => ({ ...prev, ...state }))
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ loading, isEditing, objectData }) => (
|
{({ loading, isEditing, objectData }) => {
|
||||||
<Flex vertical gap={'large'}>
|
return (
|
||||||
<InfoCollapse
|
|
||||||
title='Document Template Information'
|
|
||||||
icon={<InfoCircleIcon />}
|
|
||||||
active={collapseState.info}
|
|
||||||
onToggle={(expanded) =>
|
|
||||||
updateCollapseState('info', expanded)
|
|
||||||
}
|
|
||||||
collapseKey='info'
|
|
||||||
>
|
|
||||||
<ObjectInfo
|
<ObjectInfo
|
||||||
loading={loading}
|
loading={loading}
|
||||||
indicator={<LoadingOutlined />}
|
indicator={<LoadingOutlined />}
|
||||||
@ -197,32 +188,14 @@ const DocumentTemplateInfo = () => {
|
|||||||
objectData={objectData}
|
objectData={objectData}
|
||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
content: false,
|
content: false,
|
||||||
testObject: false,
|
testObject: false
|
||||||
resources: false
|
|
||||||
}}
|
}}
|
||||||
labelWidth='210px'
|
labelWidth='190px'
|
||||||
/>
|
/>
|
||||||
</InfoCollapse>
|
)
|
||||||
<InfoCollapse
|
}}
|
||||||
title='Resources'
|
|
||||||
icon={<FileIcon />}
|
|
||||||
active={collapseState.resources}
|
|
||||||
onToggle={(expanded) =>
|
|
||||||
updateCollapseState('resources', expanded)
|
|
||||||
}
|
|
||||||
collapseKey='resources'
|
|
||||||
>
|
|
||||||
<ObjectProperty
|
|
||||||
{...getModelProperty('documentTemplate', 'resources')}
|
|
||||||
isEditing={isEditing}
|
|
||||||
objectData={objectData}
|
|
||||||
loading={loading}
|
|
||||||
size='medium'
|
|
||||||
/>
|
|
||||||
</InfoCollapse>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</ObjectForm>
|
</ObjectForm>
|
||||||
|
</InfoCollapse>
|
||||||
</ActionHandler>
|
</ActionHandler>
|
||||||
|
|
||||||
<InfoCollapse
|
<InfoCollapse
|
||||||
@ -237,10 +210,7 @@ const DocumentTemplateInfo = () => {
|
|||||||
indicator={<LoadingOutlined />}
|
indicator={<LoadingOutlined />}
|
||||||
>
|
>
|
||||||
<Card>
|
<Card>
|
||||||
<NotesPanel
|
<NotesPanel _id={documentTemplateId} type='documentTemplate' />
|
||||||
_id={documentTemplateId}
|
|
||||||
type='documentTemplate'
|
|
||||||
/>
|
|
||||||
</Card>
|
</Card>
|
||||||
</Spin>
|
</Spin>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
|
|||||||
@ -158,9 +158,7 @@ const EmailMessageInfo = () => {
|
|||||||
spinning={objectFormState.loading}
|
spinning={objectFormState.loading}
|
||||||
indicator={<LoadingOutlined />}
|
indicator={<LoadingOutlined />}
|
||||||
>
|
>
|
||||||
<Card
|
<Card>
|
||||||
styles={{ body: { height: 'calc(100vh - 218px)' } }}
|
|
||||||
>
|
|
||||||
<TemplatePreview
|
<TemplatePreview
|
||||||
template={objectData?.template}
|
template={objectData?.template}
|
||||||
content={objectData?.content}
|
content={objectData?.content}
|
||||||
|
|||||||
@ -42,7 +42,6 @@ const EmailTemplateInfo = () => {
|
|||||||
'EmailTemplateInfo',
|
'EmailTemplateInfo',
|
||||||
{
|
{
|
||||||
info: true,
|
info: true,
|
||||||
resources: true,
|
|
||||||
attachments: true,
|
attachments: true,
|
||||||
notes: true,
|
notes: true,
|
||||||
auditLogs: false
|
auditLogs: false
|
||||||
@ -108,7 +107,6 @@ const EmailTemplateInfo = () => {
|
|||||||
disabled={objectFormState.loading}
|
disabled={objectFormState.loading}
|
||||||
items={[
|
items={[
|
||||||
{ key: 'info', label: 'Email Template Information' },
|
{ key: 'info', label: 'Email Template Information' },
|
||||||
{ key: 'resources', label: 'Resources' },
|
|
||||||
{ key: 'attachments', label: 'Attachments' },
|
{ key: 'attachments', label: 'Attachments' },
|
||||||
{ key: 'notes', label: 'Notes' },
|
{ key: 'notes', label: 'Notes' },
|
||||||
{ key: 'auditLogs', label: 'Audit Logs' }
|
{ key: 'auditLogs', label: 'Audit Logs' }
|
||||||
@ -193,29 +191,11 @@ const EmailTemplateInfo = () => {
|
|||||||
visibleProperties={{
|
visibleProperties={{
|
||||||
content: false,
|
content: false,
|
||||||
testObject: false,
|
testObject: false,
|
||||||
resources: false,
|
|
||||||
attachments: false
|
attachments: false
|
||||||
}}
|
}}
|
||||||
labelWidth='210px'
|
labelWidth='210px'
|
||||||
/>
|
/>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
<InfoCollapse
|
|
||||||
title='Resources'
|
|
||||||
icon={<FileIcon />}
|
|
||||||
active={collapseState.resources}
|
|
||||||
onToggle={(expanded) =>
|
|
||||||
updateCollapseState('resources', expanded)
|
|
||||||
}
|
|
||||||
collapseKey='resources'
|
|
||||||
>
|
|
||||||
<ObjectProperty
|
|
||||||
{...getModelProperty('emailTemplate', 'resources')}
|
|
||||||
isEditing={isEditing}
|
|
||||||
objectData={objectData}
|
|
||||||
loading={loading}
|
|
||||||
size='medium'
|
|
||||||
/>
|
|
||||||
</InfoCollapse>
|
|
||||||
<InfoCollapse
|
<InfoCollapse
|
||||||
title='Attachments'
|
title='Attachments'
|
||||||
icon={<FileIcon />}
|
icon={<FileIcon />}
|
||||||
|
|||||||
@ -30,10 +30,7 @@ import { ApiServerContext } from '../../context/ApiServerContext.jsx'
|
|||||||
import ScrollBox from '../../common/ScrollBox.jsx'
|
import ScrollBox from '../../common/ScrollBox.jsx'
|
||||||
import JsonObjectIcon from '../../../Icons/JsonObjectIcon.jsx'
|
import JsonObjectIcon from '../../../Icons/JsonObjectIcon.jsx'
|
||||||
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
import ObjectProperty from '../../common/ObjectProperty.jsx'
|
||||||
import {
|
import { getModelProperty, getModelByName } from '../../../../database/ObjectModels.js'
|
||||||
getModelProperty,
|
|
||||||
getModelByName
|
|
||||||
} from '../../../../database/ObjectModels.js'
|
|
||||||
|
|
||||||
const log = loglevel.getLogger('FileInfo')
|
const log = loglevel.getLogger('FileInfo')
|
||||||
log.setLevel(config.logLevel)
|
log.setLevel(config.logLevel)
|
||||||
@ -90,7 +87,7 @@ const FileInfo = () => {
|
|||||||
download: () => {
|
download: () => {
|
||||||
fetchFileContent(objectFormState?.objectData, true)
|
fetchFileContent(objectFormState?.objectData, true)
|
||||||
return true
|
return true
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -206,7 +203,7 @@ const FileInfo = () => {
|
|||||||
collapseKey='preview'
|
collapseKey='preview'
|
||||||
>
|
>
|
||||||
{objectFormState?.objectData?._id ? (
|
{objectFormState?.objectData?._id ? (
|
||||||
<Card styles={{ body: { minHeight: 'calc(100vh - 218px)' } }}>
|
<Card>
|
||||||
<FilePreview
|
<FilePreview
|
||||||
file={objectFormState?.objectData}
|
file={objectFormState?.objectData}
|
||||||
style={{ width: '100%', height: '100%' }}
|
style={{ width: '100%', height: '100%' }}
|
||||||
@ -265,7 +262,10 @@ const FileInfo = () => {
|
|||||||
<ObjectTable
|
<ObjectTable
|
||||||
type='auditLog'
|
type='auditLog'
|
||||||
masterFilter={{
|
masterFilter={{
|
||||||
parent: getModelByName('file').prefix + ':' + fileId
|
parent:
|
||||||
|
getModelByName('file').prefix +
|
||||||
|
':' +
|
||||||
|
fileId
|
||||||
}}
|
}}
|
||||||
visibleColumns={{ _id: false, parent: false }}
|
visibleColumns={{ _id: false, parent: false }}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -166,7 +166,6 @@ export default function CodeBlockEditor({
|
|||||||
disabled = false,
|
disabled = false,
|
||||||
minimal = false,
|
minimal = false,
|
||||||
autoCompleteObject = null,
|
autoCompleteObject = null,
|
||||||
resourceNames = [],
|
|
||||||
errorLines = [],
|
errorLines = [],
|
||||||
templateType,
|
templateType,
|
||||||
onCursorChange = null
|
onCursorChange = null
|
||||||
@ -182,14 +181,8 @@ export default function CodeBlockEditor({
|
|||||||
}, [onCursorChange])
|
}, [onCursorChange])
|
||||||
|
|
||||||
const languageExtension = useMemo(
|
const languageExtension = useMemo(
|
||||||
() =>
|
() => getCodeLanguageExtension(language, autoCompleteObject, templateType),
|
||||||
getCodeLanguageExtension(
|
[language, autoCompleteObject, templateType]
|
||||||
language,
|
|
||||||
autoCompleteObject,
|
|
||||||
templateType,
|
|
||||||
resourceNames
|
|
||||||
),
|
|
||||||
[language, autoCompleteObject, templateType, resourceNames]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const errorLineExtension = useMemo(() => {
|
const errorLineExtension = useMemo(() => {
|
||||||
@ -333,7 +326,6 @@ CodeBlockEditor.propTypes = {
|
|||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
minimal: PropTypes.bool,
|
minimal: PropTypes.bool,
|
||||||
autoCompleteObject: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
autoCompleteObject: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||||
resourceNames: PropTypes.arrayOf(PropTypes.string),
|
|
||||||
errorLines: PropTypes.arrayOf(PropTypes.number),
|
errorLines: PropTypes.arrayOf(PropTypes.number),
|
||||||
templateType: PropTypes.string,
|
templateType: PropTypes.string,
|
||||||
onCursorChange: PropTypes.func
|
onCursorChange: PropTypes.func
|
||||||
|
|||||||
@ -11,14 +11,12 @@ import {
|
|||||||
import LoadingPlaceholder from './LoadingPlaceholder'
|
import LoadingPlaceholder from './LoadingPlaceholder'
|
||||||
import GCodePreview from './GCodePreview'
|
import GCodePreview from './GCodePreview'
|
||||||
import ThreeDPreview from './ThreeDPreview'
|
import ThreeDPreview from './ThreeDPreview'
|
||||||
import PDFPreview from './PDFPreview'
|
|
||||||
import { AuthContext } from '../context/AuthContext'
|
import { AuthContext } from '../context/AuthContext'
|
||||||
|
|
||||||
const hasExplicitPreviewHeight = (height) =>
|
|
||||||
typeof height === 'number' ||
|
|
||||||
(typeof height === 'string' && height !== '100%' && height !== 'auto')
|
|
||||||
|
|
||||||
const FilePreview = ({ file, style = {} }) => {
|
const FilePreview = ({ file, style = {} }) => {
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('FILEPREVIEWFILE', file)
|
||||||
|
}, [file])
|
||||||
const { token } = useContext(AuthContext)
|
const { token } = useContext(AuthContext)
|
||||||
const { fetchFileContent } = useContext(ApiServerContext)
|
const { fetchFileContent } = useContext(ApiServerContext)
|
||||||
|
|
||||||
@ -28,13 +26,6 @@ const FilePreview = ({ file, style = {} }) => {
|
|||||||
|
|
||||||
const currentId = useRef(null)
|
const currentId = useRef(null)
|
||||||
|
|
||||||
const extension = (file?.extension || '').toLowerCase()
|
|
||||||
const isGcode = ['.g', '.gcode'].includes(extension)
|
|
||||||
const is3DModel = ['.stl', '.3mf'].includes(extension)
|
|
||||||
const isImage = file?.type?.startsWith('image/') || false
|
|
||||||
const isPdf =
|
|
||||||
file?.type?.startsWith('application/pdf') || extension === '.pdf'
|
|
||||||
|
|
||||||
const fetchPreview = useCallback(async () => {
|
const fetchPreview = useCallback(async () => {
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
return
|
return
|
||||||
@ -58,25 +49,6 @@ const FilePreview = ({ file, style = {} }) => {
|
|||||||
}
|
}
|
||||||
}, [file._id, file?.type, fetchPreview, token])
|
}, [file._id, file?.type, fetchPreview, token])
|
||||||
|
|
||||||
if (isPdf) {
|
|
||||||
if (error != null) {
|
|
||||||
return <div style={{ color: 'red' }}>{error}</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PDFPreview
|
|
||||||
file={fileObjectUrl}
|
|
||||||
loading={loading}
|
|
||||||
style={{
|
|
||||||
...style,
|
|
||||||
height: hasExplicitPreviewHeight(style.height)
|
|
||||||
? style.height
|
|
||||||
: '72vh'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loading == true || !file?.type) {
|
if (loading == true || !file?.type) {
|
||||||
return <LoadingPlaceholder message={'Loading file preview...'} />
|
return <LoadingPlaceholder message={'Loading file preview...'} />
|
||||||
}
|
}
|
||||||
@ -85,6 +57,14 @@ const FilePreview = ({ file, style = {} }) => {
|
|||||||
return <div style={{ color: 'red' }}>{error}</div>
|
return <div style={{ color: 'red' }}>{error}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isGcode =
|
||||||
|
['.g', '.gcode'].includes((file?.extension || '').toLowerCase()) || false
|
||||||
|
|
||||||
|
const is3DModel =
|
||||||
|
['.stl', '.3mf'].includes((file?.extension || '').toLowerCase()) || false
|
||||||
|
|
||||||
|
const isImage = file?.type.startsWith('image/')
|
||||||
|
|
||||||
if (isGcode && fileObjectUrl) {
|
if (isGcode && fileObjectUrl) {
|
||||||
return (
|
return (
|
||||||
<GCodePreview
|
<GCodePreview
|
||||||
|
|||||||
@ -116,7 +116,6 @@ const ObjectProperty = ({
|
|||||||
height = 'auto',
|
height = 'auto',
|
||||||
minimal = false,
|
minimal = false,
|
||||||
autoCompleteObject = null,
|
autoCompleteObject = null,
|
||||||
resourceNames = [],
|
|
||||||
errorLines = [],
|
errorLines = [],
|
||||||
templateType,
|
templateType,
|
||||||
onCursorChange,
|
onCursorChange,
|
||||||
@ -438,7 +437,6 @@ const ObjectProperty = ({
|
|||||||
readOnly={true}
|
readOnly={true}
|
||||||
minimal={minimal}
|
minimal={minimal}
|
||||||
autoCompleteObject={autoCompleteObject}
|
autoCompleteObject={autoCompleteObject}
|
||||||
resourceNames={resourceNames}
|
|
||||||
errorLines={errorLines}
|
errorLines={errorLines}
|
||||||
templateType={templateType}
|
templateType={templateType}
|
||||||
onCursorChange={onCursorChange}
|
onCursorChange={onCursorChange}
|
||||||
@ -992,7 +990,6 @@ const ObjectProperty = ({
|
|||||||
height={height}
|
height={height}
|
||||||
minimal={minimal}
|
minimal={minimal}
|
||||||
autoCompleteObject={autoCompleteObject}
|
autoCompleteObject={autoCompleteObject}
|
||||||
resourceNames={resourceNames}
|
|
||||||
errorLines={errorLines}
|
errorLines={errorLines}
|
||||||
templateType={templateType}
|
templateType={templateType}
|
||||||
onCursorChange={onCursorChange}
|
onCursorChange={onCursorChange}
|
||||||
@ -1148,7 +1145,6 @@ ObjectProperty.propTypes = {
|
|||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
language: PropTypes.string,
|
language: PropTypes.string,
|
||||||
autoCompleteObject: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
autoCompleteObject: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
|
||||||
resourceNames: PropTypes.arrayOf(PropTypes.string),
|
|
||||||
errorLines: PropTypes.arrayOf(PropTypes.number),
|
errorLines: PropTypes.arrayOf(PropTypes.number),
|
||||||
templateType: PropTypes.string,
|
templateType: PropTypes.string,
|
||||||
onCursorChange: PropTypes.func,
|
onCursorChange: PropTypes.func,
|
||||||
|
|||||||
@ -35,9 +35,16 @@ const PDFPreview = ({ file, loading = false, style }) => {
|
|||||||
}}
|
}}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
icon={<MinusIcon />}
|
||||||
|
onClick={() => {
|
||||||
|
setPreviewScale((prev) => clampPreviewScale(prev - 0.05))
|
||||||
|
}}
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
readOnly={true}
|
readOnly={true}
|
||||||
style={{ minWidth: '70px' }}
|
style={{ width: '65px' }}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setPreviewScale(1)
|
setPreviewScale(1)
|
||||||
@ -45,13 +52,6 @@ const PDFPreview = ({ file, loading = false, style }) => {
|
|||||||
>
|
>
|
||||||
{previewScale.toFixed(2)}x
|
{previewScale.toFixed(2)}x
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
|
||||||
icon={<MinusIcon />}
|
|
||||||
onClick={() => {
|
|
||||||
setPreviewScale((prev) => clampPreviewScale(prev - 0.05))
|
|
||||||
}}
|
|
||||||
disabled={loading}
|
|
||||||
/>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|||||||
@ -94,16 +94,6 @@ const parseEjsErrorLines = (message) => {
|
|||||||
return [...lines]
|
return [...lines]
|
||||||
}
|
}
|
||||||
|
|
||||||
const normalizeResourceNames = (resources) => {
|
|
||||||
if (!Array.isArray(resources)) return []
|
|
||||||
|
|
||||||
return resources
|
|
||||||
.map((resource) =>
|
|
||||||
typeof resource === 'string' ? resource : resource?.name
|
|
||||||
)
|
|
||||||
.filter((name) => typeof name === 'string' && name.length > 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
const TemplateEditor = ({
|
const TemplateEditor = ({
|
||||||
objectData,
|
objectData,
|
||||||
template,
|
template,
|
||||||
@ -128,15 +118,11 @@ const TemplateEditor = ({
|
|||||||
const [errorLines, setErrorLines] = useState([])
|
const [errorLines, setErrorLines] = useState([])
|
||||||
const [formatLoading, setFormatLoading] = useState(false)
|
const [formatLoading, setFormatLoading] = useState(false)
|
||||||
const [intellisense, setIntellisense] = useState(null)
|
const [intellisense, setIntellisense] = useState(null)
|
||||||
const [responseResources, setResponseResources] = useState(null)
|
|
||||||
const intellisenseRequestIdRef = useRef(0)
|
const intellisenseRequestIdRef = useRef(0)
|
||||||
const intellisenseTimerRef = useRef(null)
|
const intellisenseTimerRef = useRef(null)
|
||||||
const resourceNames =
|
|
||||||
responseResources ?? normalizeResourceNames(currentTemplate?.resources)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIntellisense(null)
|
setIntellisense(null)
|
||||||
setResponseResources(null)
|
|
||||||
}, [currentTemplate?._id])
|
}, [currentTemplate?._id])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -182,9 +168,6 @@ const TemplateEditor = ({
|
|||||||
setIntellisense(result.intellisense)
|
setIntellisense(result.intellisense)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Array.isArray(result?.resources)) {
|
|
||||||
setResponseResources(normalizeResourceNames(result.resources))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}, 200)
|
}, 200)
|
||||||
@ -386,7 +369,6 @@ const TemplateEditor = ({
|
|||||||
autoCompleteObject={
|
autoCompleteObject={
|
||||||
intellisense ?? currentTemplate?.testObject
|
intellisense ?? currentTemplate?.testObject
|
||||||
}
|
}
|
||||||
resourceNames={resourceNames}
|
|
||||||
errorLines={errorLines}
|
errorLines={errorLines}
|
||||||
templateType={templateType}
|
templateType={templateType}
|
||||||
onCursorChange={handleCursorChange}
|
onCursorChange={handleCursorChange}
|
||||||
|
|||||||
@ -26,8 +26,7 @@ export function toEditorCode(code, language = 'javascript') {
|
|||||||
export function getCodeLanguageExtension(
|
export function getCodeLanguageExtension(
|
||||||
language = 'javascript',
|
language = 'javascript',
|
||||||
autoCompleteObject = null,
|
autoCompleteObject = null,
|
||||||
templateType,
|
templateType
|
||||||
resourceNames = []
|
|
||||||
) {
|
) {
|
||||||
const lang = Array.isArray(language) ? language[0] : language
|
const lang = Array.isArray(language) ? language[0] : language
|
||||||
|
|
||||||
@ -44,11 +43,7 @@ export function getCodeLanguageExtension(
|
|||||||
return xml()
|
return xml()
|
||||||
case 'fctemplatelang':
|
case 'fctemplatelang':
|
||||||
case 'ejs':
|
case 'ejs':
|
||||||
return fcTemplateLang({
|
return fcTemplateLang({ autoCompleteObject, templateType })
|
||||||
autoCompleteObject,
|
|
||||||
templateType,
|
|
||||||
resourceNames
|
|
||||||
})
|
|
||||||
case 'html':
|
case 'html':
|
||||||
return html()
|
return html()
|
||||||
case 'css':
|
case 'css':
|
||||||
|
|||||||
@ -2240,14 +2240,12 @@ const ApiServerProvider = ({ children }) => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
const error = getTemplateErrorFromResponse(err)
|
const error = getTemplateErrorFromResponse(err)
|
||||||
const intellisense = err?.response?.data?.intellisense
|
const intellisense = err?.response?.data?.intellisense
|
||||||
const resources = err?.response?.data?.resources
|
|
||||||
logger.error('Error fetching template intellisense:', error)
|
logger.error('Error fetching template intellisense:', error)
|
||||||
const payload = {
|
const payload = {
|
||||||
error,
|
error,
|
||||||
...(intellisense != null && typeof intellisense === 'object'
|
...(intellisense != null && typeof intellisense === 'object'
|
||||||
? { intellisense }
|
? { intellisense }
|
||||||
: {}),
|
: {})
|
||||||
...(Array.isArray(resources) ? { resources } : {})
|
|
||||||
}
|
}
|
||||||
if (typeof callback === 'function') {
|
if (typeof callback === 'function') {
|
||||||
callback(payload)
|
callback(payload)
|
||||||
|
|||||||
@ -22,34 +22,6 @@ import ListIcon from '../../components/Icons/ListIcon'
|
|||||||
import DuplicateIcon from '../../components/Icons/DuplicateIcon'
|
import DuplicateIcon from '../../components/Icons/DuplicateIcon'
|
||||||
import BinIcon from '../../components/Icons/BinIcon'
|
import BinIcon from '../../components/Icons/BinIcon'
|
||||||
|
|
||||||
const computeTemplateResources = (resources) => {
|
|
||||||
if (!Array.isArray(resources)) return []
|
|
||||||
|
|
||||||
const usedNames = new Set()
|
|
||||||
const nextSuffix = new Map()
|
|
||||||
|
|
||||||
return resources.map((resource) => {
|
|
||||||
const filename = resource?.file?.filename || resource?.file?.name || ''
|
|
||||||
const baseName =
|
|
||||||
filename
|
|
||||||
.replace(/\.[^.]*$/, '')
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/[^a-z0-9]+/g, '-')
|
|
||||||
.replace(/^-+|-+$/g, '') || 'resource'
|
|
||||||
let name = baseName
|
|
||||||
let suffix = nextSuffix.get(baseName) || 1
|
|
||||||
|
|
||||||
while (usedNames.has(name)) {
|
|
||||||
name = `${baseName}-${suffix}`
|
|
||||||
suffix += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
usedNames.add(name)
|
|
||||||
nextSuffix.set(baseName, suffix)
|
|
||||||
return { ...resource, name }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DocumentTemplate = {
|
export const DocumentTemplate = {
|
||||||
name: 'documentTemplate',
|
name: 'documentTemplate',
|
||||||
label: 'Document Template',
|
label: 'Document Template',
|
||||||
@ -222,13 +194,6 @@ export const DocumentTemplate = {
|
|||||||
readOnly: true,
|
readOnly: true,
|
||||||
columnWidth: 180
|
columnWidth: 180
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'updatedAt',
|
|
||||||
label: 'Updated At',
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true,
|
|
||||||
columnWidth: 200
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'name',
|
name: 'name',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
@ -237,7 +202,13 @@ export const DocumentTemplate = {
|
|||||||
columnWidth: 200,
|
columnWidth: 200,
|
||||||
columnFixed: 'left'
|
columnFixed: 'left'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
label: 'Updated At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true,
|
||||||
|
columnWidth: 200
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'objectType',
|
name: 'objectType',
|
||||||
label: 'Object Type',
|
label: 'Object Type',
|
||||||
@ -317,38 +288,6 @@ export const DocumentTemplate = {
|
|||||||
type: 'codeBlock',
|
type: 'codeBlock',
|
||||||
language: 'fcTemplateLang'
|
language: 'fcTemplateLang'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'resources',
|
|
||||||
label: 'Resources',
|
|
||||||
type: 'objectChildren',
|
|
||||||
required: false,
|
|
||||||
canAddRemove: true,
|
|
||||||
size: 'medium',
|
|
||||||
columns: ['name', 'file'],
|
|
||||||
value: (documentTemplate) =>
|
|
||||||
computeTemplateResources(documentTemplate?.resources),
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
name: 'name',
|
|
||||||
label: 'Name',
|
|
||||||
type: 'text',
|
|
||||||
required: true,
|
|
||||||
readOnly: true,
|
|
||||||
columnWidth: 200
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'file',
|
|
||||||
required: true,
|
|
||||||
type: 'file',
|
|
||||||
label: 'File',
|
|
||||||
showPreview: false,
|
|
||||||
showHyperlink: true,
|
|
||||||
masterFilter: ['.pdf', '.jpeg', '.png', '.svg', '.gif'],
|
|
||||||
thumbnail: true,
|
|
||||||
columnWidth: 200
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'testObject',
|
name: 'testObject',
|
||||||
label: 'Test Object',
|
label: 'Test Object',
|
||||||
|
|||||||
@ -242,7 +242,7 @@ export const EmailMessage = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'recipientEmail',
|
name: 'recipientEmail',
|
||||||
label: 'Recipient Email',
|
label: 'Recipient',
|
||||||
type: 'email',
|
type: 'email',
|
||||||
required: true,
|
required: true,
|
||||||
readOnly: readOnlyAfterCreate,
|
readOnly: readOnlyAfterCreate,
|
||||||
@ -278,7 +278,7 @@ export const EmailMessage = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'recipient',
|
name: 'recipient',
|
||||||
label: 'Recipient',
|
label: 'Recipient Object',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
objectType: (data) => data?.recipientType,
|
objectType: (data) => data?.recipientType,
|
||||||
readOnly: readOnlyAfterCreate,
|
readOnly: readOnlyAfterCreate,
|
||||||
|
|||||||
@ -22,34 +22,6 @@ import EmailTemplateIcon from '../../components/Icons/EmailTemplateIcon'
|
|||||||
import DuplicateIcon from '../../components/Icons/DuplicateIcon'
|
import DuplicateIcon from '../../components/Icons/DuplicateIcon'
|
||||||
import BinIcon from '../../components/Icons/BinIcon'
|
import BinIcon from '../../components/Icons/BinIcon'
|
||||||
|
|
||||||
const computeTemplateResources = (resources) => {
|
|
||||||
if (!Array.isArray(resources)) return []
|
|
||||||
|
|
||||||
const usedNames = new Set()
|
|
||||||
const nextSuffix = new Map()
|
|
||||||
|
|
||||||
return resources.map((resource) => {
|
|
||||||
const filename = resource?.file?.filename || resource?.file?.name || ''
|
|
||||||
const baseName =
|
|
||||||
filename
|
|
||||||
.replace(/\.[^.]*$/, '')
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/[^a-z0-9]+/g, '-')
|
|
||||||
.replace(/^-+|-+$/g, '') || 'resource'
|
|
||||||
let name = baseName
|
|
||||||
let suffix = nextSuffix.get(baseName) || 1
|
|
||||||
|
|
||||||
while (usedNames.has(name)) {
|
|
||||||
name = `${baseName}-${suffix}`
|
|
||||||
suffix += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
usedNames.add(name)
|
|
||||||
nextSuffix.set(baseName, suffix)
|
|
||||||
return { ...resource, name }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const EmailTemplate = {
|
export const EmailTemplate = {
|
||||||
name: 'emailTemplate',
|
name: 'emailTemplate',
|
||||||
label: 'Email Template',
|
label: 'Email Template',
|
||||||
@ -298,38 +270,6 @@ export const EmailTemplate = {
|
|||||||
objectType: (data) => data?.objectType,
|
objectType: (data) => data?.objectType,
|
||||||
showHyperlink: true
|
showHyperlink: true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'resources',
|
|
||||||
label: 'Resources',
|
|
||||||
type: 'objectChildren',
|
|
||||||
required: false,
|
|
||||||
canAddRemove: true,
|
|
||||||
size: 'medium',
|
|
||||||
columns: ['name', 'file'],
|
|
||||||
value: (emailTemplate) =>
|
|
||||||
computeTemplateResources(emailTemplate?.resources),
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
name: 'name',
|
|
||||||
label: 'Name',
|
|
||||||
type: 'text',
|
|
||||||
required: true,
|
|
||||||
readOnly: true,
|
|
||||||
columnWidth: 200
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'file',
|
|
||||||
required: true,
|
|
||||||
label: 'File',
|
|
||||||
type: 'file',
|
|
||||||
showPreview: false,
|
|
||||||
showHyperlink: true,
|
|
||||||
masterFilter: ['.pdf', '.jpeg', '.png', '.svg', '.gif'],
|
|
||||||
thumbnail: true,
|
|
||||||
columnWidth: 200
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'attachments',
|
name: 'attachments',
|
||||||
label: 'Attachments',
|
label: 'Attachments',
|
||||||
|
|||||||
@ -156,13 +156,6 @@ export const File = {
|
|||||||
readOnly: true,
|
readOnly: true,
|
||||||
columnWidth: 180
|
columnWidth: 180
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'updatedAt',
|
|
||||||
label: 'Updated At',
|
|
||||||
type: 'dateTime',
|
|
||||||
readOnly: true,
|
|
||||||
columnWidth: 200
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'name',
|
name: 'name',
|
||||||
label: 'Name',
|
label: 'Name',
|
||||||
@ -171,7 +164,13 @@ export const File = {
|
|||||||
type: 'text',
|
type: 'text',
|
||||||
columnWidth: 200
|
columnWidth: 200
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'updatedAt',
|
||||||
|
label: 'Updated At',
|
||||||
|
type: 'dateTime',
|
||||||
|
readOnly: true,
|
||||||
|
columnWidth: 200
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'type',
|
name: 'type',
|
||||||
label: 'Type',
|
label: 'Type',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user