UNPKG

@cmk/fe_utils

Version:
319 lines (291 loc) 11 kB
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' } }