import Presentations from '/imports/api/presentations'; import React, { useState } from 'react'; import { useTracker } from 'meteor/react-meteor-data'; import Icon from '/imports/ui/components/common/icon/component'; import { makeCall } from '/imports/ui/services/api'; import Styled from '/imports/ui/components/presentation/presentation-uploader/styles'; import { toast } from 'react-toastify'; import { defineMessages } from 'react-intl'; import _ from 'lodash'; import { UploadingPresentations } from '/imports/api/presentations'; const TIMEOUT_CLOSE_TOAST = 1; //second const intlMessages = defineMessages({ item: { id: 'app.presentationUploder.item', description: 'single item label', }, itemPlural: { id: 'app.presentationUploder.itemPlural', description: 'plural item label', }, uploading: { id: 'app.presentationUploder.uploading', description: 'uploading label for toast notification', }, uploadStatus: { id: 'app.presentationUploder.uploadStatus', description: 'upload status for toast notification', }, completed: { id: 'app.presentationUploder.completed', description: 'uploads complete label for toast notification', }, GENERATING_THUMBNAIL: { id: 'app.presentationUploder.conversion.generatingThumbnail', description: 'indicatess that it is generating thumbnails', }, GENERATING_SVGIMAGES: { id: 'app.presentationUploder.conversion.generatingSvg', description: 'warns that it is generating svg images', }, GENERATED_SLIDE: { id: 'app.presentationUploder.conversion.generatedSlides', description: 'warns that were slides generated', }, 413: { id: 'app.presentationUploder.upload.413', description: 'error that file exceed the size limit', }, CONVERSION_TIMEOUT: { id:'app.presentationUploder.conversion.conversionTimeout', description: 'warns the user that the presentation timed out in the back-end in specific page of the document', }, FILE_TOO_LARGE: { id: 'app.presentationUploder.upload.413', description: 'error that file exceed the size limit', }, PAGE_COUNT_EXCEEDED: { id: 'app.presentationUploder.conversion.pageCountExceeded', description: 'warns the user that the conversion failed because of the page count', }, PDF_HAS_BIG_PAGE: { id: 'app.presentationUploder.conversion.pdfHasBigPage', description: 'warns the user that the conversion failed because of the pdf page siz that exceeds the allowed limit', }, OFFICE_DOC_CONVERSION_INVALID: { id: 'app.presentationUploder.conversion.officeDocConversionInvalid', description: '', }, OFFICE_DOC_CONVERSION_FAILED: { id: 'app.presentationUploder.conversion.officeDocConversionFailed', description: 'warns the user that the conversion failed because of wrong office file', }, UNSUPPORTED_DOCUMENT: { id: 'app.presentationUploder.conversion.unsupportedDocument', description: 'warns the user that the file extension is not supported', }, fileToUpload: { id: 'app.presentationUploder.fileToUpload', description: 'message used in the file selected for upload', }, uploadProcess: { id: 'app.presentationUploder.upload.progress', description: 'message that indicates the percentage of the upload', }, badConnectionError: { id: 'app.presentationUploder.connectionClosedError', description: 'message indicating that the connection was closed', }, conversionProcessingSlides: { id: 'app.presentationUploder.conversion.conversionProcessingSlides', description: 'indicates how many slides were converted', }, genericError: { id: 'app.presentationUploder.genericError', description: 'generic error while uploading/converting', }, genericConversionStatus: { id: 'app.presentationUploder.conversion.genericConversionStatus', description: 'indicates that file is being converted', }, }); function renderPresentationItemStatus(item, intl) { if ((("progress" in item) && item.progress === 0) || (("upload" in item) && item.upload.progress === 0 && !item.upload.error)) { return intl.formatMessage(intlMessages.fileToUpload); } if (("progress" in item) && item.progress < 100 && !("conversion" in item)) { return intl.formatMessage(intlMessages.uploadProcess, { 0: Math.floor(item.progress).toString(), }); } const constraint = {}; if (("upload" in item) && (item.upload.done && item.upload.error)) { if (item.conversion.status === 'FILE_TOO_LARGE' || item.upload.status !== 413) { constraint['0'] = ((item.conversion.maxFileSize) / 1000 / 1000).toFixed(2); } else { if (item.progress < 100) { const errorMessage = intlMessages.badConnectionError; return intl.formatMessage(errorMessage); } } const errorMessage = intlMessages[item.upload.status] || intlMessages.genericError; return intl.formatMessage(errorMessage, constraint); } if (("conversion" in item) && (!item.conversion.done && item.conversion.error)) { const errorMessage = intlMessages[item.conversion.status] || intlMessages.genericConversionStatus; switch (item.conversion.status) { case 'CONVERSION_TIMEOUT': constraint['0'] = item.conversion.numberPageError; constraint['1'] = item.conversion.maxNumberOfAttempts; break; case 'FILE_TOO_LARGE': constraint['0'] = ((item.conversion.maxFileSize) / 1000 / 1000).toFixed(2); break; case 'PAGE_COUNT_EXCEEDED': constraint['0'] = item.conversion.maxNumberPages; break; case 'PDF_HAS_BIG_PAGE': constraint['0'] = (item.conversion.bigPageSize / 1000 / 1000).toFixed(2); break; default: break; } return intl.formatMessage(errorMessage, constraint); } if ((("conversion" in item) && (!item.conversion.done && !item.conversion.error)) || (("progress" in item) && item.progress == 100)) { let conversionStatusMessage if ("conversion" in item) { if (item.conversion.pagesCompleted < item.conversion.numPages) { return intl.formatMessage(intlMessages.conversionProcessingSlides, { 0: item.conversion.pagesCompleted, 1: item.conversion.numPages, }); } conversionStatusMessage = intlMessages[item.conversion.status] || intlMessages.genericConversionStatus; } else { conversionStatusMessage = intlMessages.genericConversionStatus; } return intl.formatMessage(conversionStatusMessage); } return null; } function renderToastItem(item, intl) { const isUploading = ("progress" in item) && item.progress <= 100; const isConverting = ("conversion" in item) && !item.conversion.done; const hasError = ((("conversion" in item) && item.conversion.error) || (("upload" in item) && item.upload.error)); const isProcessing = (isUploading || isConverting) && !hasError; let icon = isProcessing ? 'blank' : 'check'; if (hasError) icon = 'circle_close'; return ( { if (hasError || isProcessing) Session.set('showUploadPresentationView', true); }} > {item.filename || item.name} {renderPresentationItemStatus(item, intl)} ); } const renderToastList = (presentations, intl) => { let converted = 0; let presentationsSorted = presentations .sort((a, b) => a.uploadTimestamp - b.uploadTimestamp) .sort((a, b) => { const presADone = a.conversion ? a.conversion.done : false; const presBDone = b.conversion ? b.conversion.done : false; return presADone - presBDone }); presentationsSorted .forEach((p) => { const presDone = p.conversion ? p.conversion.done : false; if (presDone) converted += 1; return p; }); let toastHeading = ''; const itemLabel = presentationsSorted.length > 1 ? intl.formatMessage(intlMessages.itemPlural) : intl.formatMessage(intlMessages.item); if (converted === 0) { toastHeading = intl.formatMessage(intlMessages.uploading, { 0: presentationsSorted.length, 1: itemLabel, }); } if (converted > 0 && converted !== presentationsSorted.length) { toastHeading = intl.formatMessage(intlMessages.uploadStatus, { 0: converted, 1: presentationsSorted.length, }); } if (converted === presentationsSorted.length) { toastHeading = intl.formatMessage(intlMessages.completed, { 0: converted, }); } return ( {toastHeading} {presentationsSorted.map((item) => renderToastItem(item, intl))} ); } function handleDismissToast(toastId) { return toast.dismiss(toastId); } const alreadyRenderedPresList = [] export const PresentationUploaderToast = ({ intl }) => { useTracker(() => { const presentationsRenderedFalseAndConversionFalse = Presentations.find({ $or: [{renderedInToast: false}, {"conversion.done": false}] }).fetch(); const convertingPresentations = presentationsRenderedFalseAndConversionFalse.filter(p => !p.renderedInToast ) let tmpIdconvertingPresentations = presentationsRenderedFalseAndConversionFalse.filter(p => !p.conversion.done) .map(p => { return {temporaryPresentationId: p.temporaryPresentationId, id: p.id}; }) UploadingPresentations.find({}).fetch().filter(p => tmpIdconvertingPresentations.findIndex(pres => pres.temporaryPresentationId === p.temporaryPresentationId || pres.id === p.id) !== -1) .map(p => { return UploadingPresentations.remove({$or: [{temporaryPresentationId: p.temporaryPresentationId }, {id: p.id}]})}); const uploadingPresentations = UploadingPresentations.find().fetch(); let presentationsToConvert = convertingPresentations.concat(uploadingPresentations); // Updating or populating the "state" presentation list presentationsToConvert.map(p => { return { filename: p.name || p.filename, temporaryPresentationId: p.temporaryPresentationId, presentationId: p.id, hasError: p.conversion?.error || p.upload?.error, lastModifiedUploader: p.lastModifiedUploader, } }).forEach(p => { const docIndexAlreadyInList = alreadyRenderedPresList.findIndex(pres => { return (pres.temporaryPresentationId === p.temporaryPresentationId || pres.presentationId === p.presentationId || (pres.lastModifiedUploader !== undefined && !pres.lastModifiedUploader && pres.filename === p.filename))}); if (docIndexAlreadyInList === -1) { alreadyRenderedPresList.push({ filename: p.filename, temporaryPresentationId: p.temporaryPresentationId, presentationId: p.presentationId, rendered: false, lastModifiedUploader: p.lastModifiedUploader, hasError: p.hasError, }); } else { alreadyRenderedPresList[docIndexAlreadyInList].temporaryPresentationId = p.temporaryPresentationId; alreadyRenderedPresList[docIndexAlreadyInList].presentationId = p.presentationId; alreadyRenderedPresList[docIndexAlreadyInList].lastModifiedUploader = p.lastModifiedUploader; alreadyRenderedPresList[docIndexAlreadyInList].hasError = p.hasError; } }) let activeToast = Session.get("presentationUploaderToastId"); const showToast = presentationsToConvert.length > 0; if (showToast && !activeToast) { activeToast = toast.info(() => renderToastList(presentationsToConvert, intl), { hideProgressBar: true, autoClose: false, newestOnTop: true, closeOnClick: true, className: "presentationUploaderToast toastClass", onClose: () => { presentationsToConvert = []; if (alreadyRenderedPresList.every((pres) => pres.rendered)) { makeCall('setPresentationRenderedInToast').then(() => { Session.set("presentationUploaderToastId", null); }); alreadyRenderedPresList.length = 0; } }, }); Session.set("presentationUploaderToastId", activeToast); } else if (!showToast && activeToast) { handleDismissToast(activeToast); Session.set("presentationUploaderToastId", null); } else { toast.update(activeToast, { render: renderToastList(presentationsToConvert, intl), }); } let temporaryPresentationIdListToSetAsRendered = presentationsToConvert.filter(p => ("conversion" in p && (p.conversion.done || p.conversion.error))) temporaryPresentationIdListToSetAsRendered = temporaryPresentationIdListToSetAsRendered.map(p => { index = alreadyRenderedPresList.findIndex(pres => (pres.temporaryPresentationId === p.temporaryPresentationId || pres.presentationId === p.id)); if (index !== -1) { alreadyRenderedPresList[index].rendered = true; } return p.temporaryPresentationId }); if (alreadyRenderedPresList.every((pres) => pres.rendered && !pres.hasError)) { setTimeout(() => { makeCall('setPresentationRenderedInToast'); alreadyRenderedPresList.length = 0; }, TIMEOUT_CLOSE_TOAST * 1000); } }, []) return null; } export default { handleDismissToast, renderPresentationItemStatus, }