mtt-simple
Version:
Biblioteca de componentes y helpers para desarrollo de formularios en SIMPLE digital
226 lines (204 loc) • 7.64 kB
JavaScript
;
require('./components.typedefs')
const $ = require('jquery')
const { S3_FILE_UPLOADER_DEFAULTS } = require('./s3-input-file-uploader')
const utils = require('../utils')
// TODO: extraer lógica de carga del documento de modo de usarla en otros componentes
// sin replicar código
const S3_URL_FILE_UPLOADER_DEFAULTS = {
metodo: 'GET'
}
/**
* Componente que permite efectuar una carga de un documento generado en SIMPLE usando el idCampo asociado
* y llevarlo al repositorio de SIMPLE de modo de dejarlo disponible para descarga.
*/
class S3UrlFileUploader {
/**
* @param {number|string} refCampo - asociado al campo Documento agregado aal formulario desde donde se extraerá la url
* @param {} options
*/
constructor(refCampo, options) {
this.cfg = Object.assign({}, S3_FILE_UPLOADER_DEFAULTS, S3_URL_FILE_UPLOADER_DEFAULTS, options || {})
this.refCampo = refCampo
if (typeof refCampo === 'string') {
this._$btn = $(`a.btn:contains(${refCampo})`)
} else {
this.idCampo = refCampo
this._$btn = $(`[data-id=${refCampo}] a.btn`)
}
if (!this._$btn) {
throw new Error(`Campo ${refCampo} no fue en encontrado dentro del formulario`)
}
if (typeof refCampo === 'string') {
this.idCampo = parseInt(this._$btn.closest('.campo.control-group').attr('data-id'), 10)
}
}
/**
* Descargar documento y agregarlo al repositorio de SIMPLE
* @param {string} nombreArchivo - nombre opcional para renombrar documento descargado al momento de
* cargarlo al repositorio de SIMPLE
*/
async addFileToRepo(nombreArchivo) {
this.url = this._$btn.attr('href')
if (!this.url) {
throw new Error(`El campo ${this.refCampo} (${typeof this.refCampo}) no posee una url válida para efectuar la descarga del docuemnto.`)
}
const file = await this.downloadFileContent(this.url)
file.name = nombreArchivo || file.name
return this._uploadFile(file, nombreArchivo)
}
/**
* Descargar desde la url el contenido del archivo
* @param {string} url
* @returns {Promise<BlobFile>}
*/
async downloadFileContent(url) {
if (!url) throw new Error(`S3UrlFileUploader: debe especificar una url para la descarga del documento original`)
if (!utils.esUrlValida(url)) throw new Error(`S3UrlFileUploader: La url '${url}' no es válida`)
console.log('downloadFileContent', url)
return new Promise((res, _rej) => {
var oReq = new XMLHttpRequest();
oReq.open(this.cfg.metodo, url, true);
oReq.responseType = "blob";
oReq.onload = function (oEvent, oReqRef) {
var blob = oReq.response;
const regex = /filename="(.*)"/g
if (blob) {
blob.name = regex.exec(oReq.getResponseHeader('content-disposition'))[1]
blob.url = url
res(blob)
}
};
oReq.send();
})
}
/**
* envio de documento a repositorio S3 usando SIMPLE
* @param {BlobFile} file - un objeto Blobl con información del archivo a cargar
* @returns {Promise<S3UploadedDocumentTypedef>}
*/
async _uploadFile(file) {
const totalSize = file.size
let totalSegments = 1
if (totalSize > this.cfg.maxSize) {
throw new Error(`Tamaño del archivo supera el máximo permitido`)
}
if (totalSize > this.cfg.chunkSize) {
const rest = totalSize % this.cfg.chunkSize
totalSegments = ((totalSize - rest) / this.cfg.chunkSize) + (rest > 0 ? 1 : 0)
}
let urlSIMPLE = ''
for (let segmentNumber = 1; segmentNumber <= totalSegments; segmentNumber++) {
const chunk = await this._getFileSegment(file, segmentNumber, totalSegments, this.cfg.chunkSize)
const loaded = await this._uploadSegment(chunk)
urlSIMPLE = loaded.url
}
return {
name: file.name,
urlOrig: file.url,
url: urlSIMPLE,
size: file.size,
contentType: file.type
}
}
/**
* @param {S3ChunkTypedef} chunk
*/
_getSegmentUploadUrl(chunk) {
const { totalSegments, segmentNumber } = chunk
const { baseUrl, idEtapa } = this.cfg
const urlParts = [baseUrl + '/uploader/datos_s3', this.idCampo, idEtapa]
urlParts.push(totalSegments > 1 ? 'multi' : 'single')
urlParts.push(...[segmentNumber, totalSegments])
return urlParts.join('/')
}
/**
* @param {BlobFile} file
* @param {number} segmentNumber
* @param {number} chunkSize
* @return {Promise<S3ChunkTypedef>}
*/
async _getFileSegment(file, segmentNumber, totalSegments, chunkSize) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
const initialPos = chunkSize * (segmentNumber - 1)
const content = file.slice(initialPos, initialPos + chunkSize)
const chunk = {
totalSize: file.size,
size: content.size,
segmentNumber,
filename: file.name,
totalSegments,
contentType: file.type
}
reader.onload = (evt) => {
if (evt.target.error) {
reject(evt.target.error)
} else {
chunk.content = evt.target.result
resolve(chunk)
}
}
reader.readAsArrayBuffer(content);
})
}
_getUploadProgress(segmentNumber, chunkSize, previousUploadedSize, totalSize) {
const uploaded = (segmentNumber - 1) * chunkSize + previousUploadedSize
return Math.round((uploaded * 100) / totalSize)
}
/**
* @param {S3ChunkTypedef} chunk
*/
async _uploadSegment(chunk) {
const self = this
return new Promise((resolve, reject) => {
var uploadUrl = this._getSegmentUploadUrl(chunk)
var xhr = new XMLHttpRequest();
var content = new Uint8Array(chunk.content);
xhr.addEventListener('load', function (chunkNumber, totalSegments, filename, totalSize) {
return function (evt) {
try {
if (evt.target.status === 200) {
const uploadStatus = JSON.parse(evt.target.response);
if (!uploadStatus.success) {
console.error(`Servidor informa error segmento ${chunkNumber}`, uploadStatus.error);
} else {
const uploadedFile = {
segmentNumber: chunkNumber,
totalSegments,
name: filename,
url: `${self.cfg.baseUrl}${uploadStatus.URL}`,
contentType: chunk.contentType,
totalSize
}
resolve(uploadedFile)
}
} else {
throw new Error('Error en la carga servidor informa STATUS=' + evt.target.status)
}
} catch (e) {
reject(e)
return;
}
}
}(chunk.segmentNumber, chunk.totalSegments, chunk.filename, chunk.totalSize));
xhr.addEventListener("error", function (evt) {
console.error('Error al enviar', evt);
reject(evt)
});
xhr.addEventListener("abort", function (evt) {
console.error('Abortado al enviar', evt)
resolve(null)
});
xhr.open("POST", uploadUrl, true);
xhr.setRequestHeader('X-CSRF-TOKEN', this.cfg.token);
xhr.setRequestHeader('filename', encodeURI(chunk.filename));
xhr.setRequestHeader('Content-Type', chunk.contentType);
xhr.send(content);
})
}
}
module.exports = {
S3_URL_FILE_UPLOADER_DEFAULTS,
S3UrlFileUploader
}