apps/chat/src/store/files/files.epics.ts (273 lines of code) (raw):

import { EMPTY, catchError, concat, filter, ignoreElements, iif, map, mergeMap, of, switchMap, takeUntil, tap, } from 'rxjs'; import { combineEpics } from 'redux-observable'; import { FileService } from '@/src/utils/app/data/file-service'; import { getDownloadPath, triggerDownload } from '@/src/utils/app/file'; import { translate } from '@/src/utils/app/translation'; import { ApiUtils } from '@/src/utils/server/api'; import { FeatureType } from '@/src/types/common'; import { AppEpic } from '@/src/types/store'; import { PublicationActions } from '../publication/publication.reducers'; import { UIActions, UISelectors } from '../ui/ui.reducers'; import { FilesActions, FilesSelectors } from './files.reducers'; import { UploadStatus } from '@epam/ai-dial-shared'; const initEpic: AppEpic = (action$, state$) => action$.pipe( filter( (action) => FilesActions.init.match(action) && !FilesSelectors.selectInitialized(state$.value), ), switchMap(() => concat( of( PublicationActions.uploadPublishedWithMeItems({ featureType: FeatureType.File, }), ), of(FilesActions.initFinish()), ), ), ); const uploadFileEpic: AppEpic = (action$) => action$.pipe( filter(FilesActions.uploadFile.match), mergeMap(({ payload }) => { const formData = new FormData(); formData.append('attachment', payload.fileContent, payload.name); return FileService.sendFile( formData, payload.relativePath, payload.name, ).pipe( filter( ({ percent, result }) => typeof percent !== 'undefined' || typeof result !== 'undefined', ), map(({ percent, result }) => { if (result) { return FilesActions.uploadFileSuccess({ apiResult: result, }); } return FilesActions.uploadFileTick({ id: payload.id, percent: percent!, }); }), takeUntil( action$.pipe( filter(FilesActions.uploadFileCancel.match), filter((action) => action.payload.id === payload.id), ), ), catchError(() => { return of(FilesActions.uploadFileFail({ id: payload.id })); }), ); }), ); const reuploadFileEpic: AppEpic = (action$, state$) => action$.pipe( filter(FilesActions.reuploadFile.match), switchMap(({ payload }) => { const file = FilesSelectors.selectFiles(state$.value).find( (file) => file.id === payload.fileId, ); if (!file || !file.fileContent) { return of(FilesActions.uploadFileFail({ id: payload.fileId })); } return of( FilesActions.uploadFile({ fileContent: file.fileContent, id: payload.fileId, relativePath: file.relativePath, name: file.name, }), ); }), ); const getFilesEpic: AppEpic = (action$) => action$.pipe( filter(FilesActions.getFiles.match), switchMap(({ payload }) => FileService.getFiles(payload.id).pipe( map((files) => FilesActions.getFilesSuccess({ files, }), ), catchError(() => of(FilesActions.getFilesFail())), ), ), ); const getFileFoldersEpic: AppEpic = (action$) => action$.pipe( filter(FilesActions.getFolders.match), mergeMap(({ payload }) => FileService.getFileFolders(payload?.id).pipe( map((folders) => FilesActions.getFoldersSuccess({ folderId: payload.id, folders, }), ), catchError(() => of(FilesActions.getFoldersFail({ folderId: payload.id })), ), ), ), ); const getFilesWithFoldersEpic: AppEpic = (action$) => action$.pipe( filter(FilesActions.getFilesWithFolders.match), switchMap(({ payload }) => { return concat( of(FilesActions.getFolders(payload)), of(FilesActions.getFiles(payload)), ); }), ); const getFoldersListEpic: AppEpic = (action$) => action$.pipe( filter(FilesActions.getFoldersList.match), switchMap(({ payload }) => { return concat( ...(payload.paths ? payload.paths.map((path) => of(FilesActions.getFolders({ id: path })), ) : [of(FilesActions.getFolders({}))]), ); }), ); const deleteFileEpic: AppEpic = (action$, state$) => action$.pipe( filter(FilesActions.deleteFile.match), mergeMap(({ payload }) => { const file = FilesSelectors.selectFiles(state$.value).find( (file) => file.id === payload.fileId, ); if (!file?.serverSynced) { return concat( of( FilesActions.uploadFileCancel({ id: payload.fileId, }), ), of( FilesActions.deleteFileSuccess({ fileId: payload.fileId, }), ), ); } return FileService.deleteFile(payload.fileId).pipe( mergeMap(() => { const customLogo = UISelectors.selectCustomLogo(state$.value); return concat( iif( () => !!customLogo && customLogo === payload.fileId, of(UIActions.deleteCustomLogo()), EMPTY, ), of( FilesActions.deleteFileSuccess({ fileId: payload.fileId, }), ), ); }), catchError(() => { return of( FilesActions.deleteFileFail({ fileName: file.name, }), ); }), ); }), ); const deleteFileFailEpic: AppEpic = (action$) => action$.pipe( filter(FilesActions.deleteFileFail.match), map(({ payload }) => { return UIActions.showToast({ message: translate( 'Deleting file {{fileName}} failed. Please try again later', { ns: 'file', fileName: payload.fileName, }, ), }); }), ignoreElements(), ); const deleteMultipleFilesEpic: AppEpic = (action$) => action$.pipe( filter(FilesActions.deleteFilesList.match), switchMap(({ payload }) => { return concat( ...payload.fileIds.map((fileId) => of(FilesActions.deleteFile({ fileId })), ), ); }), ); const unselectFilesEpic: AppEpic = (action$, state$) => action$.pipe( filter(FilesActions.unselectFiles.match), switchMap(({ payload }) => { const files = FilesSelectors.selectFilesByIds(state$.value, payload.ids); const cancelFileActions = files .filter( (file) => !file.serverSynced && file.status === UploadStatus.LOADING, ) .map((file) => of(FilesActions.uploadFileCancel({ id: file.id }))); return cancelFileActions.length ? concat(...cancelFileActions) : EMPTY; }), ); const downloadFilesListEpic: AppEpic = (action$, state$) => action$.pipe( filter(FilesActions.downloadFilesList.match), map(({ payload }) => ({ files: FilesSelectors.selectFilesByIds(state$.value, payload.fileIds), })), tap(({ files }) => { files.forEach((file) => { const filePath = getDownloadPath(file); return triggerDownload( `/api/${ApiUtils.encodeApiUrl(filePath)}`, file.name, ); }); }), ignoreElements(), ); export const FilesEpics = combineEpics( initEpic, uploadFileEpic, getFileFoldersEpic, getFilesEpic, reuploadFileEpic, getFilesWithFoldersEpic, deleteFileEpic, getFoldersListEpic, deleteMultipleFilesEpic, downloadFilesListEpic, deleteFileFailEpic, unselectFilesEpic, );