Source: input/UploadFile.js

import { useEffect, useMemo, useState } from "react";
import { booleanString, processOptions, useVariables } from "../../_core";
import { Template } from "../templates";
import Resizer from "react-image-file-resizer";

const objectPath = require("object-path");
const defaultValue = (multiple_files, content) => {
  if(booleanString(multiple_files)){
    return !!content ? processOptions(content) : [];
  }else{
    return {name: null, content, type: null};
  }
}

/**
 * Componente que renderiza un input para cargar una imagen
 * @name Reactor.Components.Input.UploadFile
 * @param {string} name 
 *  name del control
 * @param {BooleanString} [use_change = "false"] 
 *  establece si envia el evento change al cambiar el archivo
 * @param {string} [label = "Seleccione un archivo..."] 
 *  texto del boton (placeholder)
 * @param {string} accept 
 *  determina los tipos de archivos que aceptara el control file
 * @param {BooleanString} [multiple_files = "false"] 
 *  establece si se puede subir mas de un archivo con el mismo componente (por el momento solo funciona con uno)
 * @param {string} preview_file_template
 *  nombre del template que renderiza una lista de templates de previews (solo si es multiple)
 * @param {string} preview_container_template
 *  nombre del template que renderiza un preview
 * @param {JsonString} delete_options
 *  opciones para el elemento descartar
 * @param {JsonString} label_options
 *  opciones para el label (placeholder)
 * @param {JsonString} resizer_options 
 *  opciones para redimencionar la imagen
 *     resizer_options='{
              "maxWidth": 300,
              "maxHeight": 400,
              "compressFormat": "JPEG",
              "quality": 75
            }'>
 * @param {string?} storage_name El nombre que usara para guardar el dato en el storage, si no se pasa este parĂ¡metro se usara el name del input
 * @param {string?} change_var Variable que tomarĂ¡ el valor del control cuando se modifique
 * @param {string} [context = "global"] Contexto donde se encuentra la variable
 * @param {string|JsonString} content
 *  valor previo del control, 
 *  si es multiple_files enviar un array de objetos con esta estructura {name: null, content, type: null}
 *  si no lo es enviar un string DataUrl
 * @class 
 * @example 

<upload name="archivo" use_change="false" label="Seleccione la imagen"  accept="image/png, image/jpeg" multiple_files="false" preview_file_template="TemplateParaMostrarFoto" delete_options='{}' label_options='{}'></upload>

 */

export const UploadFile = ({name, use_change = "false", label = "Seleccione un archivo...", accept, multiple_files = "false", preview_file_template, preview_container_template, delete_options, label_options, resizer_options, content, change_var, context = "global", storage_name, ...props}) => {

  //console.log({label})
  const variables = useVariables({variable: change_var, context, storage: storage_name});
  const labelProps = useMemo(()=>processOptions(label_options), [label_options]);
  const deleteProps = useMemo(()=>processOptions(delete_options), [delete_options]);
  //{content: null, name: null, type: null}
  const [fileData, setFileData] = useState(() => defaultValue(multiple_files, content));
  const [hasData, setHasData] = useState(() => !content);

  useEffect(() => {
    setFileData(defaultValue(multiple_files, content));
    setHasData(!!content);
    if(!!content && change_var){
      variables.setData(content);
    }
  }, [content])


  // On file select (from the pop up) 
  const onFileChange = (event) => { 
    // Update the state 
    if(event.target.files.length > 0) {
      var reader  = new FileReader();
      const files = Array.from(event.target.files);
      reader.onloadend = function () {
        if(resizer_options){
          resizeImage(reader.result).then(result => {
            setFileData({name: files[0].name, content: result, type: files[0].type});
            if(change_var){
              variables.setData(result);
            }
          })
        }else{
          if(booleanString(multiple_files)){
            setFileData(files.map(file => ({name: file.name, content: reader.result, type: file.type})));
            if(change_var){
              variables.setData(files.map(file => reader.result));
            }
          }else{
            if(change_var){
              setFileData({name: files[0].name, content: reader.result, type: files[0].type});
            }
            variables.setData(reader.result);
          }
        }
        setHasData(true);
      }
      files.map(file => reader.readAsDataURL(file));
      event.target.value = [ ];
    }
  }; 


  const resizeImage = (dataUrl) => {
    return new Promise((resolve, reject) => {
      const options = {
        maxWidth: 2000, // Is the maxWidth of the resized new image.
        maxHeight: 2000, // Is the maxHeight of the resized new image.
        compressFormat: "JPEG", // Is the compressFormat of the resized new image.
        quality: 75, // Is the quality of the resized new image.
        rotation: 0, // Is the degree of clockwise rotation to apply to uploaded image.
        outputType: "base64", // Is the output type of the resized new image.
        minWidth: null, // Is the minWidth of the resized new image.
        minHeight: null, // Is the minHeight of the resized new image
        ...processOptions(resizer_options)
      };
      fetch(dataUrl)
        .then(res => res.blob())
        .then(blob => {
          const file = new File([blob], "Image")
          try{
            Resizer.imageFileResizer(
              file,
              options.maxWidth, // Is the maxWidth of the resized new image.
              options.maxHeight, // Is the maxHeight of the resized new image.
              options.compressFormat, // Is the compressFormat of the resized new image.
              options.quality, // Is the quality of the resized new image.
              options.rotation, // Is the degree of clockwise rotation to apply to uploaded image.
              (uri) => {
                resolve(uri);
              },
              options.outputType, // Is the output type of the resized new image.
              options.minWidth, // Is the minWidth of the resized new image.
              options.minHeight // Is the minHeight of the resized new image.*/
            );
          }catch(e){
            console.log(e);
            reject(e);
          }
    
        })
    })
  }

  const handleFileCancel = (event) => {
    setFileData(defaultValue(multiple_files));
    setHasData(false);
    variables.setData("");
  }

  //console.log({name, ...(fileData ? fileData : []), multiple_files, multiple_filesB: booleanString(multiple_files)})

  return (
      <div className="control-upload-file">
        {booleanString(multiple_files) &&
          fileData.map((file, index) => 
            <>
              <input type="hidden" name={`${name}.${index}.content`} value={file.content} use_change={use_change}/>
              <input type="hidden" name={`${name}.${index}.name`} value={file.name} use_change={use_change}/>
              <input type="hidden" name={`${name}.${index}.type`} value={file.type} use_change={use_change}/>
            </>
          )
        }
        {!booleanString(multiple_files) &&
          <>
              <input type="hidden" name={`${name}.content`} value={fileData.content} use_change={use_change}/>
              <input type="hidden" name={`${name}.name`} value={fileData.name} use_change={use_change}/>
              <input type="hidden" name={`${name}.type`} value={fileData.type} use_change={use_change}/>
          </>
        }
        {!hasData &&
          <>
            <input 
              type="file"
              id={name}
              name={name}
              accept={accept}
              style={{display: "none"}} 
              onChange={onFileChange} 
              multiple_files={booleanString(multiple_files)}
              use_change={use_change}
            /> 
            <label {...labelProps} for={name} >{label}</label>
          </>
        }
        {hasData &&
          <>
            {booleanString(multiple_files) && preview_file_template && preview_container_template &&
              <Template content={{template: preview_container_template}} >
                {fileData.map((file, index) => {
                  return <Template key={`file${index}`} content={{template: preview_file_template, ...file}} />
                })}
              </Template>
            }
            {!booleanString(multiple_files) && preview_file_template &&
              <Template content={{template: preview_file_template, ...fileData}} />
            }
            <a {...deleteProps} onClick={handleFileCancel}>Descartar</a>
          </>
        }
      </div>
  )
}