@cmk/fe_utils
Version:
frontend utility library
319 lines (291 loc) • 11 kB
text/typescript
import { getDeepPropertyByPath } from '../../../utils/object'
import { EditorRendererControllerType } from '../../types/editorRendererController'
import { EditorStateType } from '../../types'
export const REGEX_DATA_PLACEHOLDER = /{_data\.[^}]*}/g
export const REGEX_FORMDATA_PLACEHOLDER = /{formData\.[^}]*}/g
export const REGEX_TREEVIEW_PLACEHOLDER = /{treeviews\.[^}]*}/g
export const REGEX_PROPS_PLACEHOLDER = /{props\.[^}]*}/g
export const REGEX_BUTTON_STATES_PLACEHOLDER = /{buttonStates\.[^}]*}/g
export const REGEX_MDIICON_PLACEHOLDER = /{mdi\w*}/g
export const REGEX_RESOLUTION_FAILED =
/(({_data)|({formData)|({treeviews)|({props)|({buttonStates))\.[^}]*'[^}]*'[^}]*/g
export const REGEX_TEXT_HAS_BRACKETS = /({|}|\[|\])/g
export const REGEX_PLACEHOLDERS = [
REGEX_DATA_PLACEHOLDER,
REGEX_TREEVIEW_PLACEHOLDER,
REGEX_PROPS_PLACEHOLDER,
REGEX_BUTTON_STATES_PLACEHOLDER,
]
export const checkForPlaceholders = (text: string) => {
return !!REGEX_PLACEHOLDERS.map((regex) => text.match(regex)).filter(
(match) => match
)?.length
}
export const checkForParsableJson = (text: string) => {
return text.match(REGEX_TEXT_HAS_BRACKETS)
}
/** replaces the placeholders AND EVALs the string if it contains any placeholders, static calculations are skipped!
@returns the evaluated string -> can be any type !!!
*/
export const replacePlaceholdersInString = (
text: string,
appState: EditorRendererControllerType['appController']['state'],
componentPropertyDefinitions: EditorStateType['composite_component_props'],
properties: EditorStateType['properties'],
currentElement: EditorRendererControllerType['selectedElement'],
rootCompositeElementId?: string,
forceEval?: boolean,
icons?: Record<string, string>,
isTransformer?: boolean,
formData?: Record<string, unknown>
) => {
const getTemplates = (text: string) => {
let templatesOut: {
placeholder: string
value: string | number | boolean | object
type: string
placeholderRaw: string
placeholderCutted: string
isValueUndefined: boolean
}[] = []
const buttonStatesMatches = text.match(REGEX_BUTTON_STATES_PLACEHOLDER)
const buttonStatesTemplates =
buttonStatesMatches?.map((match) => {
const keyRaw = match //.replace('{buttonStates.', '').replace('}', '')
const key = keyRaw.split('.').slice(1).join('.')?.replaceAll('}', '')
return {
type: 'buttonStates',
placeholder: match,
placeholderRaw: match,
placeholderCutted: match.split('.').slice(1).join('.'),
value: appState?.buttonStates?.[key] ?? '',
isValueUndefined: appState?.buttonStates?.[key] === undefined,
}
}) || []
const regex = REGEX_DATA_PLACEHOLDER
const dataMatches = text.match(regex)
const dataTemplates =
dataMatches?.map((match) => {
const keyRaw = match.replace('{_data.', '').replace('}', '')
const key = keyRaw.replace(/\..*$/gm, '')
return {
type: 'data',
placeholder: key,
placeholderRaw: keyRaw,
placeholderCutted: keyRaw.replace(key, ''),
value: appState?._data?.[key] ?? '',
isValueUndefined: appState?._data?.[key] === undefined,
}
}) || []
const regexFormData = REGEX_FORMDATA_PLACEHOLDER
const formDataMatches = text.match(regexFormData)
const formDataTemplates = !formData
? []
: formDataMatches?.map((match) => {
const keyRaw = match.replace('{formData.', '').replace('}', '')
const key = keyRaw.replace(/\..*$/gm, '')
return {
type: 'formData',
placeholder: key,
placeholderRaw: keyRaw,
placeholderCutted: keyRaw.replace(key, ''),
value: formData?.[key] ?? '',
isValueUndefined: formData?.[key] === undefined,
}
}) || []
const regexTreeViews = REGEX_TREEVIEW_PLACEHOLDER
const treeViewMatches = text.match(regexTreeViews)
const treeViewTemplates =
treeViewMatches?.map((match) => {
const keyRaw = match.replace('{treeviews.', '').replace('}', '')
const key = keyRaw.replace(/\..*$/gm, '')
return {
type: 'treeviews',
placeholder: key,
placeholderRaw: keyRaw,
placeholderCutted: keyRaw.replace(key, ''),
value:
key in appState.treeviews
? (appState.treeviews?.[key as keyof typeof appState.treeviews] ??
'')
: '',
isValueUndefined:
appState?.treeviews?.[key as keyof typeof appState.treeviews] ===
undefined,
}
}) || []
const propsMatches = text.match(REGEX_PROPS_PLACEHOLDER)
const propsTemplates =
propsMatches?.map((match) => {
const keyRaw = match.replace('{props.', '').replace('}', '')
const key = keyRaw.replace(/\..*$/gm, '')
const propDef = componentPropertyDefinitions.find(
(def) =>
def.property_name === key &&
def.component_id === currentElement?.component_id
)
// currentElement is the element in the component that actually uses the template
const instanceValueProp = properties?.find(
(prop) =>
prop.component_id === propDef?.component_id &&
prop.prop_name === key &&
prop.element_id === currentElement?.element_id // HERE IS THE BUG SOMEWHERE
)
// const instanceValueAttribute = attributes.find(
// (attr) =>
// attr.component_id === propDef?.component_id &&
// attr.attr_name === key &&
// (attr.element_id === currentElement?.element_id ||
// attr.element_id === elementId)
// )
// const styleAttributeInstanceValue = attributes.find(
// (attr) =>
// (attr.element_id === currentElement?.element_id ||
// attr.element_id === elementId) &&
// attr.attr_name === 'style'
// )?.attr_value?.[key]
const rootCompositeElementPropValue = properties?.find(
(prop) =>
prop.component_id === propDef?.component_id &&
prop.prop_name === key &&
prop.element_id === rootCompositeElementId
)?.prop_value
return {
type: 'props',
placeholder: key,
placeholderRaw: keyRaw,
placeholderCutted: keyRaw.replace(key, ''),
value:
instanceValueProp?.prop_value ??
// styleAttributeInstanceValue ??
rootCompositeElementPropValue ??
propDef?.property_default_value ??
'',
isValueUndefined: !propDef?.property_default_value,
debugInstanceValueProp: instanceValueProp,
debugRootCompositeElementPropValue: rootCompositeElementPropValue,
debugPropDef: propDef,
}
}) || []
const regexMdiIcon = REGEX_MDIICON_PLACEHOLDER
const mdiIconMatches = text.match(regexMdiIcon)
const mdiIconTemplates =
!icons || !Object.keys(icons)?.length
? []
: mdiIconMatches
?.map((match) => {
const cuttedPlaceholderName = match
.replaceAll('{', '')
.replaceAll('}', '')
return {
type: 'mdi',
placeholder: match,
placeholderRaw: match,
placeholderCutted: cuttedPlaceholderName,
value: icons?.[cuttedPlaceholderName] ?? '<ICON%/>',
isValueUndefined: false,
}
})
.filter((match) => !!match.value) || []
templatesOut = [
...templatesOut,
...dataTemplates,
...propsTemplates,
...treeViewTemplates,
...mdiIconTemplates,
...buttonStatesTemplates,
...formDataTemplates,
]
return templatesOut
}
let newText: string | object = text
const templates = typeof text === 'string' ? getTemplates(text) : []
const undefinedPlaceholders = []
for (const template of templates) {
if (['string', 'number', 'boolean'].includes(typeof template.value)) {
newText = isTransformer
? newText.replaceAll(template.placeholder, template.value.toString())
: newText
.replaceAll(
template.placeholder,
typeof template.value === 'string'
? "'" + template.value.toString() + "'"
: template.value.toString()
)
?.replaceAll?.('{formData.', '')
} else {
if (template.isValueUndefined) {
undefinedPlaceholders.push(template.placeholder)
continue
}
if (
typeof template.value === 'object' &&
template.placeholderCutted.startsWith('.')
) {
const path = template.placeholderCutted?.slice(1).split('.')
const value = getDeepPropertyByPath(template.value, path)
console.debug('PATH', template, path, value)
if (value && typeof value === 'object') {
newText = value
break
}
newText = newText
.replaceAll(
template.placeholderRaw,
value
? typeof value === 'string'
? '"' + value + '"'
: (value as string)
: ''
)
.replaceAll('{_data.', '')
.replaceAll('{formData.', '')
.replaceAll('{treeviews.', '')
.replaceAll('}', '')
continue
}
newText = newText.replaceAll(template.placeholder, '"XXX"')
console.warn('Template value is not a string', template)
}
}
if (typeof newText === 'string' && templates.length && !isTransformer) {
newText = newText.replaceAll('{props.', '').replaceAll('}', '')
}
try {
console.debug(
'BEFORE EVAL -',
newText,
'-',
typeof newText,
newText === text,
text,
'templates',
templates,
'formData',
formData,
'if true no eval',
(newText === text || typeof newText !== 'string') && !forceEval,
forceEval
)
if (
typeof newText === 'string' &&
newText.match?.(REGEX_RESOLUTION_FAILED)
) {
console.debug('Resolution failed', newText, text, templates)
return newText
}
// this will though prevent calculations without placeholders
const evalText =
((newText === text || typeof newText !== 'string') && !forceEval) ||
typeof newText === 'object'
? newText
: eval(newText)
console.debug('AFTER EVAL', evalText)
return evalText === 'true' ? true : evalText === 'false' ? false : evalText
} catch (e) {
console.error('Error in eval', e, newText)
return undefinedPlaceholders.length
? 'Placeholder could not be resolved: ' + undefinedPlaceholders.join(', ')
: 'Error in eval'
}
}