import React, { useCallback, useMemo } from "react";
import { LiveError, LivePreview, LiveProvider } from "react-live";
import { components } from "./Template.helpers";
import { GenericContext, booleanString, emptyObject, processOptions, useReactorNavigation, useVars, emptyArray, processFn } from "../../_core";
import { useDispatch, useSelector } from "react-redux";
import { appActions } from "../../redux";
import { _ } from "../../_core/hooks/useLodash";
import objectPath from "object-path";
import { getStyleObjectFromString } from "../../utils";
const immutable = require("object-path-immutable");
const merge = require('deepmerge');
const functions = {
lodash: () => _,
objectPath: () => objectPath,
immutable: () => immutable,
}
/**
* Componente que transforma un texto en un componente React dándole un contexto limitado para su funcionamiento.
* @name Reactor.Components.Templates.Live
* @param {string} template Un código react que tendra acceso a un contexto limitado (componentes, React y useVariables)
* @param {JsonString} [div_options = "{}"] Propiedades que se envian al componente div que envuelve el live
* @param {JsonString} [params = "{}"] Parametros que se envian al componente live (solo cuando se llama a un componente live desde el parametro template)
* @class
* @example
<live template='
() => {
const [count, setCount] = React.useState(0);
React.useEffect(()=>{
var interval = setInterval(() => {
setCount(count => count + 1)
}, 1000);
return ()=>clearInterval(interval);
}, [])
return (
<center>
<h3>
{count}
</h3>
</center>
)
}
'>
</live>
**************************
codigo de template live
() => {
//params:
//variable: string, nombre de la variable que se va a modificar
//context: string, contexto al que pertenece la variable
//defaultValue: number, el valor por defecto
//count: cantidad de botones
//max: number, el valor maximo posible
//inicializacion de parametros
const {
variable = "botoneta",
context = "live",
defaultValue,
count = 5,
max = Number.MAX_SAFE_INTEGER,
} = params;
//variables de contexto
const [value, setValue] = useVars({variable, context, defaultValue});
const [editing, setEditing] = useVars({variable: `${variable}_editing`, context, defaultValue: false});
const [disabled, setDisabled] = useVars({variable: `${variable}_disabled`, context, defaultValue: false});
//referencias interna
const inputRef = React.useRef();
const botoncitos = Array(count).fill(0).map((n, index) => index + 1);
//cuando hace click en el boton mas disminuye en uno el valor (si no es menor a cero)
const handleMinusClick = (event) => {
if(disabled) return;
event.preventDefault();
const currentValue = value || 0;
setValue(currentValue - 1 < 0 ? 0 : currentValue - 1);
}
//cuando hace click en el boton mas aumenta en uno el valor (si no excede params.max)
const handlePlusClick = (event) => {
if(disabled) return;
event.preventDefault();
const currentValue = value || 0;
setValue(currentValue + 1 > max ? currentValue : currentValue + 1);
}
//cuando hace click en el boton del numero establece ese numero como valor
const handleNumberClick = (number, event) => {
if(disabled) return;
event.preventDefault();
setValue(number);
}
//cuando hace click en el numero inicializa la edicion a mano, hace foco en el input y deshabilita el control
const handleValueClick = (event) => {
if(disabled) return;
event.preventDefault();
setEditing(true);
setTimeout(()=>inputRef.current.focus(), 1);
}
//cuando se hace foco en el input se selecciona todo el texto
const handleEditingFocus = () => {
inputRef.current.select();
};
//cuando se pierde el foco del input se sale de la edicion
const handleEditingBlur = () => {
setEditing(false);
};
//cuando esta editando y se cambia el valor lo guarda temporalmente (siempre que sea valido)
const handleEditingChangeValue = (event) => {
const validate = inputRef.current.validity.valid &&
( event.target.value === "" ||
(
parseInt(event.target.value) >= 0 &&
parseInt(event.target.value) <= max
)
);
if(validate){
setValue(event.target.value);
}
}
return (
<div className="botoneta wrapper">
{botoncitos.map(n => <a href="#" className={`botoneta button ${(n === value ? "active" : "")}`} onClick={(event) => handleNumberClick(n, event)}>{n}</a>)}
<a href="#" className="botoneta button minus" onClick={handleMinusClick}>-</a>
{editing &&
<input className="botoneta input" ref={inputRef} type="number" value={value} onChange={handleEditingChangeValue} onFocus={handleEditingFocus} onBlur={handleEditingBlur} pattern="^\d+$"/>
}
{!editing &&
<div className="botoneta value" onClick={handleValueClick}>{value || 0}</div>
}
<a href="#" className="botoneta button plus" onClick={handlePlusClick}>+</a>
{!value &&
children
}
</div>
)
}
**************************
para llamarlo desde otro template:
<live
template="LiveBotoneta"
params='{"count": 8, "variable": "noches", "context": "test", "max": 10, "defaultValue": 2}'
show_errors="true"
>
<h1>hola</h1>
</live>
*/
const regexp = /{{((\w+)(.\w+|\[\d+\])+?)}}/g
const resolveMatches = (templateBody, templates) => {
if(!templateBody) return "";
const matches = templateBody.match(regexp);
return !matches ? templateBody : matches.reduce((prevValue, match) => {
const matchText = match.replace(/{{|}}/g, "");
const nameTemplate = matchText.toLowerCase();
const template = templates[nameTemplate];
if(!template) return prevValue;
const content = resolveMatches(template?.body || "", templates);
return prevValue.replace(match, content);
}, templateBody);
}
const Live = ({template, params, children, show_errors = "false", no_div}) => {
const templates = useSelector(state => state.app.templates);
const dispatch = useDispatch();
const code = useMemo(() => {
const templateBody = templates[template?.toLowerCase()]?.body;
if(!!templateBody){
return resolveMatches(templateBody, templates);
}else{
return template;
}
}, [template, templates]);
const scope = useMemo(() => ({
useVars,
useReactorNavigation,
getComponent: (name) => components[name](),
getAsyncContent: (params) => {
const {operation, data, token} = params;
return dispatch(appActions.getAsyncContent(operation, token, data));
},
getAsyncContentUntoken: (params) => {
const {operation, data, token} = params;
return dispatch(appActions.getAsyncContentUntoken(operation, data, token));
},
LiveContext: GenericContext,
children,
params: processOptions(params, emptyObject, emptyObject),
emptyObject,
emptyArray,
merge,
getFunction: (name) => functions[name](),
getStyleObjectFromString,
processFn,
}), [children, params]);
const component = useMemo(() => booleanString(no_div) ? NoDivComponent : "div", [no_div]);
return (
<>
{scope &&
<LiveProvider disabled={true} code={code} noInline={false} scope={scope}>
<LivePreview Component={component}/>
{booleanString(show_errors) && <LiveError />}
</LiveProvider>
}
</>
);
}
const NoDivComponent = ({children}) =>{
return (
<>{children}</>
)
}
export default Live;