Source: navigation/ReactorLink.js

import React, {useCallback, useEffect, useImperativeHandle, useRef } from "react";
import { useSelector } from "react-redux";
import { useParams } from "react-router";
import { getStyleObjectFromString, processIncludeVariables } from "../..";
import { booleanString, readHotkeyProp, useHotkeys, useReactorNavigation, useWebAuthn, uuid } from "../../_core";
import { getFormData } from "../data/FormExt";
import isEqual from "react-fast-compare";

/**
 * @typedef {"menu" | "modal" | "navigate" | "redirect" | "frame" | "operation" | "variable" | "operation_on_dialog"} Reactor.Components.Navigation.TargetType Establece el lugar donde se va a renderizar el resultado de una operación
 **/

/**
 * Componente que renderiza html link que al ser clickeado ejecuta una acción definida en las propiedades del mismo
 * @name Reactor.Components.Navigation.ReactorLink
 * @param {string} href Nombre de la operación que va a llamar para recibir el objeto de construccion, o nombre de la variable (cuando target fuera "variable")
 * @param {string} href_external Link externo que se va a llamar en una nueva ventana/pestaña del explorador
 * @param {string} hotkeys tecla o combinacion que ejecutara el evento click de este componente, se pueden usar varias entradas separando por espacio, las combinaciones se pueden hacer con las teclas ctrl, shift, alt y meta, por ej: hotkeys="ctrl+shift+k" (registra evento control + shift + K) hostkeys="ctrl+d ctrl+h" (registra eventos control + D o control + H)
 * @param {string} data Json string con la data que se envía a la operación
 * @param {string} include_variables Lista de variables que se incluyen en data (separadas por espacio)
 * @param {string} context Contexto contenedor de las variables que se van a incluir, es requerido si se incluyen variables
 * @param {Reactor.Components.Navigation.TargetType} target Determina el lugar donde se va a mostrar el resultado de la operación establecida en href
 * @param {Reactor.Components.Surfaces.DirectionType} direction Determina la dirección en la que se muestra el menú (solo cuando es target es menú) , "bottom" por defecto
 * @param {string} offcanvas_class Establece la clase o clases que va a usar el menú (solo cuando target es menú)
 * @param {string} style Estilos para el link
 * @param {BooleanString} include_form Incluye la data del form que contiene al componente
 * @param {BooleanString} close_menu Establece si se cierra el menu cuando el servidor redirige el target a un modal
 * @param {BooleanString} clear_token Establece si se elimina el token antes de llamar a la operacion. Default "false"
 * @param {BooleanString} [disabled = "false"] Establece si el componente responde al evento click
 * @param {BooleanString} [webauthn_check = "false"] Establece si debe requerir autenticacion por medio de webauthn para ejecutar el evento click
 * @class
 * @example
<a href="NombreOperacion" data='{"param": "value"}'>Link que dispara un menu</a>
<a href="NombreOperacion" data='{"param": "value"}' target="modal">Link que dispara un modal</a>
<a href="NombreOperacion" href_external="http://www.google.com.ar" data='{"param": "value"}' target="modal">Link que abre Google en una ventana, y ejecuta la operación NombreOperacion que se renderiza ene un modal</a>
 */
export const ReactorLink = React.forwardRef(({variable, disabled = "false", href = "", href_external = null, target = "menu", data = false, navOptions, children, direction = "bottom", className = "", offcanvas_class, style, include_form = "false", include_variables, context, class_name, close_menu, clear_token, webauthn_check = "false", hotkeys, ...attribs}, refReactorLink) => {

  const page = useParams().page;
  const isActive = page === href;
  const ref = useRef();
  const variablesState = useSelector(state => context === "_" ? state.app.variables : state.app.variables[context]) || null;
  const { execute } = useReactorNavigation({data, navOptions, target, to: href, direction, offcanvasClass: offcanvas_class, useClearToken: clear_token === "true", variable, context});
  const { assertionCheck } = useWebAuthn();

  const { register, unregister } = useHotkeys();
  

  const execClick = useCallback((event) => {
    if(booleanString(disabled) || href === "_"){
      return false;
    }
    const formData = booleanString(include_form) ? {form: getFormData(ref.current.closest("form"))} : {};
    //console.log({include_form, formData, form: ref.current.closest("form")})
    const variablesData = context && processIncludeVariables({variablesState, include_variables}); 
    execute({...formData, ...variablesData});
  }, [disabled, href, include_form, context, include_variables, execute]);



  const handleClick = useCallback((event) => {
    event?.preventDefault && event?.preventDefault();
    event?.stopPropagation && event?.stopPropagation();
    if(booleanString(webauthn_check)){
      assertionCheck()
        .then(()=>{
          execClick(event);
        })
        .catch(()=>{});
    }else{
      execClick(event);
    }
  }, [execClick, assertionCheck, webauthn_check])

  const handleClickExternal = (event) => {
    event?.preventDefault && event?.preventDefault();
    event?.stopPropagation && event?.stopPropagation();
    if(booleanString(disabled) || !href){
      return;
    }
    execute();
  }

  useImperativeHandle(refReactorLink, () => {
    return {
      click: (e) => {
        handleClick(e);
      }
    };
  }, [handleClick]);

  useEffect(()=>{
    if(hotkeys){
      const eventos = readHotkeyProp(hotkeys);
      const guid = uuid();
      register({callback: handleClick, events: eventos, guid});
      return () => {
        unregister(guid);
      }
    }
  }, [hotkeys]);

  return (
    <>
      {href_external
          ? <a {...attribs} style={style ? getStyleObjectFromString(style) : {}} href={href_external} className={className || class_name} target="_blank"  onClick={handleClickExternal} >{ children }</a>
          : <a ref={ref} {...attribs} style={style ? getStyleObjectFromString(style) : {}} className={isActive ? (className || class_name) + " active-nav" : (className || class_name)} href="#" onClick={handleClick} >{ children }</a>
      }
    </>
  )
})