Compare commits

..

No commits in common. "8dd55a4257512c24b7c8bfd7cc7fdd5676eee5ce" and "869a5309fb55c8b1f55babf8b6044cdcc10b1065" have entirely different histories.

16 changed files with 84 additions and 369 deletions

View File

@ -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)
]) ])

View File

@ -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>

View File

@ -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}

View File

@ -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 />}

View File

@ -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 }}
/> />

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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}

View File

@ -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':

View File

@ -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)

View File

@ -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',

View File

@ -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,

View File

@ -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',

View File

@ -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',