dbl-components
Version:
Framework based on bootstrap 5
202 lines (184 loc) • 7.47 kB
JavaScript
import React from "react";
import { NavLink, Link } from "react-router-dom";
import parseReact, { domToReact, attributesToProps } from "html-react-parser";
import { hash, t, formatValue, deepMerge } from "dbl-utils";
import Icons from "./media/icons";
import COMPONENTS from "./components";
const excludeSectionWrapper = ['NavLink', 'Image', 'Link', 'Icons', 'SvgImports', 'Action',
'DropdownButtonContainer', 'ModalButtonContainer', 'DropdownItem'];
export function addExclusions(exclusion) {
excludeSectionWrapper.push(...[exclusion].flat());
};
/**
* Clase utilizada para generar contenido dinámico en React a partir de una estructura de datos JSON.
*
* @class JsonRender
*/
export default class JsonRender {
/**
* Opciones para el análisis del contenido HTML.
* @type {Object}
*/
parseOpts = {
replace: domNode => {
let C7tReplace;
switch (domNode.name) {
case 'navlink':
C7tReplace = NavLink;
break;
case 'a':
if (!domNode.attribs.to && domNode.attribs.href) return;
C7tReplace = Link;
break;
case 'icons':
C7tReplace = Icons;
domNode.attribs.inline = domNode.attribs.inline === 'false' ? false : true;
break;
case 'textarea':
case 'input':
domNode.defaultValue = domNode.value;
domNode.defaultChecked = domNode.checked;
delete domNode.value;
delete domNode.checked;
default:
return;
}
Object.keys(domNode).forEach(k => {
if (k.match(/^on[A-Z]/)) {
domNode[k] = this.props[k];
}
});
return React.createElement(C7tReplace,
{ ...attributesToProps(domNode.attribs) },
domToReact(domNode.children, this.parseOpts)
);
}
}
actualSections = [];
/**
* Crea una instancia de JsonRender.
* @param {Object} props - Las propiedades del componente.
* @param {Object} mutations - Las mutaciones para las secciones.
*/
constructor(props, mutations) {
this.props = props;
this.mutations = mutations;
this.sections = this.sections.bind(this);
this.buildContent = this.buildContent.bind(this);
}
/**
* Construye el contenido basado en la estructura de datos proporcionada.
* @param {any} content - El contenido a construir.
* @param {number} index - El índice del contenido.
* @returns {React.Component|React.Fragment|boolean} - El componente construido.
*/
buildContent(content, index) {
if (!content) return false;
if (typeof content !== 'object') {
const translate = t(content, this.props.context);
const section = this.actualSections[this.actualSections.length - 1];
if (typeof translate === 'number' || typeof translate === 'boolean') {
return formatValue(translate, section);
} else if (typeof translate === 'string') {
let parsed = parseReact(translate, this.parseOpts);
if (typeof parsed === 'string') parsed = formatValue(parsed, section);
return React.createElement(React.Fragment,
{ key: hash(translate) },
parsed
);
}
} else if (React.isValidElement(content)) {
try {
content.key = content.key || content.props.name || index;
} catch (error) {
}
return content;
} else if (Array.isArray(content)) return content.map(this.buildContent);
if (Array.isArray(content.name)) content.name = content.name.join('-');
if (typeof content === 'object' && typeof content.name !== 'string')
return Object.keys(content)
.map((name, i) => this.buildContent(typeof content[name] !== 'object'
? content[name] : { name, ...content[name] }, i)
);
this.actualSections.push(content);
const builded = this.sections(content, index);
this.actualSections.pop();
return builded;
}
/**
* Construye una sección basada en la información proporcionada.
* @param {Object} sectionRaw - Los datos de la sección.
* @param {number} i - El índice de la sección.
* @returns {React.ElementType} - El componente construido.
*/
sections(sr, i) {
const m = (typeof this.mutations === 'function' && this.mutations(sr.name, sr)) || {};
if (m.style && sr.style) m.style = deepMerge({}, sr.style, m.style);
if (m._props && sr._props) m._props = deepMerge({}, sr._props, m._props);
const sectionRaw = Object.assign({}, sr, m || {});
if (sectionRaw.active === false) return false;
const { component: componentName, content, placeholder,
label, message, errorMessage, managerName, wrapperClasses, wrapperStyle = {}, ...section } = sectionRaw;
const { navigate, location, match, childrenIn = this.childrenIn, children } = this.props;
const Component = COMPONENTS[componentName] || (COMPONENTS.Component);
const extraBuilded = [Component.slots].flat().filter(Boolean).reduce((eb, key) => {
const tmp = section[key];
section[key] = null;
delete section[key];
eb[key] = this.buildContent(tmp);
return eb;
}, {});
const componentProps = {
...section,
managerName: managerName || this.props.name,
label: this.buildContent(label),
placeholder: this.buildContent(placeholder),
message: this.buildContent(message),
errorMessage: this.buildContent(errorMessage),
...extraBuilded,
location,
match,
navigate
}
if (Component.dontBuildContent) componentProps.content = content;
const childrenHere = (
(Array.isArray(childrenIn) ? childrenIn.join('-') : childrenIn)
=== (Array.isArray(section.name) ? section.name.join('-') : section.name)
);
if (!Component.dontBuildContent && content && childrenHere) {
componentProps.children = React.createElement(React.Fragment, {},
this.buildContent(content),
children
);
} else if (!Component.dontBuildContent && content) {
componentProps.children = this.buildContent(content);
} else if (childrenHere) {
componentProps.children = children;
}
const cnSection = [componentProps.name + '-section'];
if (this.props.test) cnSection.push('test-section-wrapper');
if (this.props.wrapperClasses) cnSection.push(this.props.wrapperClasses);
if (wrapperClasses) cnSection.push(wrapperClasses);
const exclusionSec = excludeSectionWrapper.includes(componentName);
const Wrapper = (componentProps.wrapper === false || Component.wrapper === false)
? false : componentProps.wrapper || Component.wrapper || 'section';
if (!Wrapper || exclusionSec || componentProps.tag) {
if (this.props.test) {
if (!componentProps.style) componentProps.style = {};
componentProps.style.border = '1px solid yellow';
}
return React.createElement(Component, { key: componentProps.name || i, ...componentProps })
}
return (React.createElement(Wrapper,
{
key: componentProps.name || i,
className: cnSection.flat().join(' '),
style: {
"--component-name": `"${componentProps.name}"`,
...wrapperStyle
}
},
React.createElement(Component, { ...componentProps })
));
}
}