import download from 'downloadjs'; import type { FileObject, PageInstructionOptions, ReduxAttachments } from './Attachment.types'; const megabyteSize = 1048576; export const isContentBinary = (headers: Record) => { return headers && headers['content-transfer-encoding'] === 'binary'; }; export const isContentBase64 = (headers: Record) => { return headers && headers['content-transfer-encoding'] === 'base64'; }; export const validateMaxSize = (fileObj: Record, maxSizeInMB: string) => { const fileSize = (fileObj['size'] / megabyteSize).toFixed(2); return parseFloat(fileSize) < parseFloat(maxSizeInMB); }; export const validateFileExtension = (fileObj: Record, allowedExtensions: string) => { if (!allowedExtensions) { return true; } const allowedExtensionList = allowedExtensions .toLowerCase() .split(',') .map(item => item.replaceAll('.', '').trim()); const extension = fileObj['name'].split('.').pop()?.toLowerCase() || ''; return allowedExtensionList.includes(extension); }; export const fileDownload = (data: string | Blob, fileName: string, ext: string | null, headers: Record) => { const name = ext ? `${fileName}.${ext}` : fileName; // Temp fix: downloading EMAIl type attachment as html file if (ext === 'html') { download(isContentBase64(headers) ? atob(data as string) : data, name, 'text/html'); } else if (isContentBinary(headers)) { download(data, name); } else { download(atob(data as string), name); } }; export const fileDownloadVar = (content: { data: string; headers: Record }, type: string, name: string, extension: string) => { if (type === 'FILE' || type === undefined) { fileDownload(content.data, name, extension, content.headers); } else if (type === 'URL') { let { data } = content; if (!/^(http|https):\/\//.test(data)) { data = `//${data}`; } window.open(content.data, '_blank'); } else if (type === 'EMAIL') { // Temp Fix: for EMAIL type attachment fileDownload(content.data, name, 'html', content.headers); } }; export const getMappedValue = (value: string): string => { return PCore.getEnvironmentInfo().getKeyMapping(value) ?? value; }; const generateInstructions = ( files: FileObject[], pConn: typeof PConnect, attachmentsInModal: ReduxAttachments[] | Pick[], options: { allowMultiple: boolean; isMultiAttachmentInInlineEditTable: boolean; attachmentCount: number; insertPageInstruction: boolean; deletePageInstruction: boolean; deleteIndex: number; } ) => { const { allowMultiple, isMultiAttachmentInInlineEditTable, attachmentCount, insertPageInstruction, deletePageInstruction, deleteIndex } = options; const transformedAttachments: ReduxAttachments[] = []; let valueRef = pConn.getStateProps().value; valueRef = valueRef?.indexOf('.') === 0 ? valueRef.substring(1) : valueRef; const uniqueKey = getMappedValue('pzInsKey'); files.forEach((file, index) => { const filename = file.value?.filename || file.props?.name || ''; const payload = { [uniqueKey]: file.value?.ID || file.props?.id, FileName: filename, Category: '', // MimeType: getMimeTypeFromFile(filename), FileExtension: filename.split('.').pop() ?? filename, error: file.props?.error || null, localAttachment: true, thumbnail: file.value?.thumbnail }; transformedAttachments.push(payload); if (payload.error) { return; // Don't process page instructions for error files, skip current iteration } if (allowMultiple) { if (isMultiAttachmentInInlineEditTable) { if (insertPageInstruction) { attachmentsInModal.push({ ...payload, instruction: 'insert' } as any); } else if (deletePageInstruction) { (attachmentsInModal as Pick[]).push({ instruction: 'delete', fileIndex: deleteIndex }); } } else if (insertPageInstruction) { pConn.getListActions().insert({ ID: payload[uniqueKey] }, attachmentCount + index, undefined, { skipStateUpdate: true }); } else if (deletePageInstruction) { pConn.getListActions().deleteEntry(deleteIndex, undefined, { skipStateUpdate: true }); } } else if (insertPageInstruction) { pConn.getListActions().replacePage(`.${valueRef}`, { ID: payload[uniqueKey] }, { skipStateUpdate: true }); } else if (deletePageInstruction) { pConn.getListActions().deletePage(`.${valueRef}`, { skipStateUpdate: true }); } }); return transformedAttachments; }; export const updateReduxState = ( transformedAttachments: ReduxAttachments[], pConn: typeof PConnect, valueRef: string, options: PageInstructionOptions ) => { const { allowMultiple, isOldAttachment, insertRedux, deleteRedux } = options; let deleteIndex = -1; if (allowMultiple || isOldAttachment) { transformedAttachments.forEach(attachment => { const key = isOldAttachment ? `${valueRef}.pxResults` : valueRef; const existingAttachments: ReduxAttachments[] = PCore.getStoreValue(`.${key}`, pConn.getPageReference(), pConn.getContextName()) || []; if (insertRedux) { const actionPayLoad = { type: 'LIST_ACTION', payload: { instruction: 'INSERT', context: pConn.getContextName(), referenceList: `${pConn.getPageReference()}.${key}`, listIndex: existingAttachments.length, content: attachment } }; PCore.getStore()?.dispatch(actionPayLoad); } else if (deleteRedux) { const uniqueKey = getMappedValue('pzInsKey'); deleteIndex = existingAttachments.findIndex( existingAttachment => existingAttachment[uniqueKey as keyof ReduxAttachments] === transformedAttachments[0][uniqueKey as keyof ReduxAttachments] ); const actionPayLoad = { type: 'LIST_ACTION', payload: { instruction: 'DELETE', context: pConn.getContextName(), referenceList: `${pConn.getPageReference()}.${key}`, listIndex: deleteIndex } }; PCore.getStore()?.dispatch(actionPayLoad); } }); } else if (insertRedux) { const actionPayLoad = { type: 'LIST_ACTION', payload: { instruction: 'REPLACE', context: pConn.getContextName(), referenceList: `${pConn.getPageReference()}.${valueRef}`, content: transformedAttachments[0] } }; PCore.getStore()?.dispatch(actionPayLoad); } else if (deleteRedux) { const actionPayLoad = { type: 'LIST_ACTION', payload: { instruction: 'DELETEPAGE', context: pConn.getContextName(), referenceList: `${pConn.getPageReference()}.${valueRef}` } }; PCore.getStore()?.dispatch(actionPayLoad); } }; export const insertAttachments = ( files: FileObject[], pConn: typeof PConnect, attachmentsInModal: ReduxAttachments[], options: PageInstructionOptions ) => { const { isMultiAttachmentInInlineEditTable } = options; let valueRef = pConn.getStateProps().value; valueRef = valueRef?.indexOf('.') === 0 ? valueRef.substring(1) : valueRef; const transformedAttachments = generateInstructions(files, pConn, attachmentsInModal, { ...options, insertPageInstruction: true }); if (isMultiAttachmentInInlineEditTable) { return; // For attachments within modal, redux update is not necessary yet, as modal isn't submitted at this stage } updateReduxState(transformedAttachments, pConn, valueRef, { ...options, insertRedux: true }); }; export const deleteAttachments = ( files: FileObject[], pConn: typeof PConnect, attachmentsInModal: Pick[], options: PageInstructionOptions ) => { const { isMultiAttachmentInInlineEditTable } = options; let valueRef = pConn.getStateProps().value; valueRef = valueRef?.indexOf('.') === 0 ? valueRef.substring(1) : valueRef; const transformedAttachments = generateInstructions(files, pConn, attachmentsInModal, { ...options, deletePageInstruction: true }); if (isMultiAttachmentInInlineEditTable) { return; // For attachments within modal, redux update is not necessary yet, as modal isn't submitted at this stage } updateReduxState(transformedAttachments, pConn, valueRef, { ...options, deleteRedux: true }); }; export const clearFieldErrorMessages = (pConn: typeof PConnect) => { const fieldName = pConn.getStateProps().value; PCore.getMessageManager().clearMessages({ type: PCore.getConstants().MESSAGES.MESSAGES_TYPE_ERROR, property: fieldName, pageReference: pConn.getPageReference(), context: pConn.getContextName() }); }; export const onFileDownload = (responseProps, context) => { const { ID, name, extension, type, category, responseType } = responseProps; if (category !== 'pxDocument') { ( PCore.getAttachmentUtils().downloadAttachment(ID, context, responseType) as Promise<{ data: string; headers: Record; }> ) .then(content => { fileDownloadVar(content, type, name, extension); }) .catch(console.error); } else { ( PCore.getAttachmentUtils().downloadDocument(ID, context) as Promise<{ data: string; headers: Record; }> ) .then(content => { fileDownloadVar(content, type, name, extension); }) .catch(console.error); } }; // Prepares new structure as per Cosmos component export const transformAttachments = attachments => { const transformedFiles = [...attachments]; let deleteIndex = -1; transformedFiles.forEach(attachment => { attachment.props.id = attachment.responseProps.ID; attachment.props.format = attachment.props.name.split('.').pop(); if (attachment.props.error) { attachment.responseProps.deleteIndex = deleteIndex; } else { deleteIndex += 1; attachment.responseProps.deleteIndex = deleteIndex; } }); return transformedFiles; };