diff --git a/src/engine/lib/core/loaders/MRIFileLoader.ts b/src/engine/lib/core/loaders/MRIFileLoader.ts index 7738988e..72c4b517 100644 --- a/src/engine/lib/core/loaders/MRIFileLoader.ts +++ b/src/engine/lib/core/loaders/MRIFileLoader.ts @@ -24,7 +24,7 @@ export class MRIFileLoader { * @param {string} url - The URL of the file to load. * @returns {Promise<File[]>} A promise that resolves to an array of Files or rejects with an error. */ - async load(url: string): Promise<File[]> { + async load(url: string): Promise<File[] | null> { this.filesLoaded = 0; this.filesProgressByLength = false; this.store.setLoadingProgress(0); @@ -52,15 +52,16 @@ export class MRIFileLoader { * @param {string} url - The URL from which to fetch the file. * @returns {Promise<File[]>} A promise that resolves to an array containing the fetched file as a File object. */ - async fetchSingleFile(url: string): Promise<File[]> { + async fetchSingleFile(url: string): Promise<File[] | null> { const response = await this.fetchWithProgress(url, this.callbackLoadProgress); if (!response.ok) { this.handleVolumeLoadFailed(`Failed to fetch file from URL: ${url}`); - return []; + return null; } const blob = await response.blob(); + const fileName = getFileNameFromUrl(url); const file = new File([blob], fileName, { type: blob.type, @@ -91,14 +92,15 @@ export class MRIFileLoader { * @param {string} txtUrl - The URL of the .txt file containing the list of file URLs. * @returns {Promise<File[]>} A promise that resolves to an array of File objects. */ - async fetchTxtFile(txtUrl: string): Promise<File[]> { + async fetchTxtFile(txtUrl: string): Promise<File[] | null> { this.filesProgressByLength = true; const base = txtUrl.substring(0, txtUrl.lastIndexOf('/') + 1); const fileNames = await this.fetchTxtContent(txtUrl); this.filesLength = fileNames.length; const filePromises = fileNames.map((filename: string) => this.fetchSingleFile(base + filename)); const files = await Promise.all(filePromises); - return files.flat(); + const validFalies = files.filter(Boolean) as Array<File[]>; + return validFalies.flat(); } /** @@ -131,7 +133,9 @@ export class MRIFileLoader { const files = await Promise.all(filePromises); - return files.flat(); + const validFalies = files.filter(Boolean) as Array<File[]>; + + return validFalies.flat(); } /** @@ -154,6 +158,10 @@ export class MRIFileLoader { }; xhr.onload = () => { + if (xhr.status === 403) { + return this.handleVolumeLoadFailed(`Error 403 Forbiden, failed to fetch file from URL: ${url}`); + } + if (this.filesProgressByLength) { this.filesLoaded = this.filesLoaded + 1; const percentComplete = this.filesLoaded / this.filesLength; @@ -166,7 +174,7 @@ export class MRIFileLoader { xhr.onerror = () => { this.handleVolumeLoadFailed(`Failed to fetch file from URL: ${url}`); - reject(new Error()); + reject(); }; xhr.responseType = 'blob'; @@ -189,6 +197,6 @@ export class MRIFileLoader { */ handleVolumeLoadFailed(error: string) { this.events.emit(MriEvents.FILE_READ_ERROR, { error }); - this.store.setVolumeLoadFailed(this.fileName, [error]); + this.store.setVolumeLoadFailed(this.fileName); } } diff --git a/src/engine/lib/core/readers/MRIReader.ts b/src/engine/lib/core/readers/MRIReader.ts index f4e12778..7a3edae0 100644 --- a/src/engine/lib/core/readers/MRIReader.ts +++ b/src/engine/lib/core/readers/MRIReader.ts @@ -10,8 +10,11 @@ export class MRIReader { if (data && data.length && data[0] instanceof File) { this.fileReader.read(data as File[]); } else if (isValidUrl(data as string)) { - const files: File[] = await this.fileLoader.load(data as string); - this.fileReader.read(files); + const files: File[] | null = await this.fileLoader.load(data as string); + + if (files) { + this.fileReader.read(files); + } } else { throw new Error('Invalid input. Expected a File or URL.'); } diff --git a/src/engine/lib/core/readers/abstract-file-reader/AbstractFileReader.ts b/src/engine/lib/core/readers/abstract-file-reader/AbstractFileReader.ts index bbeb9a6a..ce12de81 100644 --- a/src/engine/lib/core/readers/abstract-file-reader/AbstractFileReader.ts +++ b/src/engine/lib/core/readers/abstract-file-reader/AbstractFileReader.ts @@ -75,7 +75,7 @@ export abstract class AbstractFileReader { */ public handleVolumeReadFailed(error: string) { this.events.emit(MriEvents.FILE_READ_ERROR, { error }); - this.store.setVolumeLoadFailed(this.fileName, [error]); + this.store.setVolumeLoadFailed(this.fileName); } /** diff --git a/src/engine/lib/services/StoreService.ts b/src/engine/lib/services/StoreService.ts index e58797ab..cc82fa95 100644 --- a/src/engine/lib/services/StoreService.ts +++ b/src/engine/lib/services/StoreService.ts @@ -82,11 +82,10 @@ export class MRIStoreService { this.dispatchActions(actions); } - public setVolumeLoadFailed(fileName: string, errors: string[]): void { + public setVolumeLoadFailed(fileName: string): void { const actions = [ - { type: StoreActionType.SET_ERR_ARRAY, errors }, { type: StoreActionType.SET_VOLUME_SET, volume: null }, - { type: StoreActionType.SET_FILENAME, fileName: fileName }, + { type: StoreActionType.SET_FILENAME, fileName }, { type: StoreActionType.SET_PROGRESS, progress: 0 }, { type: StoreActionType.SET_SPINNER, spinner: false }, { type: StoreActionType.SET_IS_LOADED, isLoaded: false }, diff --git a/src/ui/Main.jsx b/src/ui/Main.jsx index 00f8774d..c4ac5ca0 100644 --- a/src/ui/Main.jsx +++ b/src/ui/Main.jsx @@ -8,7 +8,6 @@ import StoreActionType from '../store/ActionTypes'; import FullScreenToggle from './Toolbars/FullScreen'; import UiModalText from './Modals/UiModalText'; import UiModalAlert from './Modals/ModalAlert'; -import UiErrConsole from './UiErrConsole'; import ModeView from '../store/ViewMode'; import Graphics2d from '../engine/Graphics2d'; import BrowserDetector from '../engine/utils/BrowserDetector'; @@ -28,6 +27,9 @@ import { TopToolbar } from './TopToolbar/TopToolbar'; import { UiAbout } from './Header/UiAbout'; import { MobileSettings } from './MobileSettings/MobileSettings'; import StartScreen from './StartScreen/StartScreen'; +import MriViwer from '../engine/lib/MRIViewer'; +import { MriEvents } from '../engine/lib/enums'; + import css from './Main.module.css'; import cx from 'classnames'; import '../nouislider-custom.css'; @@ -38,8 +40,9 @@ import UiModalConfirmation from './Modals/UiModalConfirmation'; export const Main = () => { const dispatch = useDispatch(); - const { arrErrors, isLoaded, progress, spinner, viewMode, showModalText, showModalAlert, showModalWindowCW, showModalConfirmation } = - useSelector((state) => state); + const { isLoaded, progress, spinner, viewMode, showModalText, showModalAlert, showModalWindowCW, showModalConfirmation } = useSelector( + (state) => state + ); const [m_fileNameOnLoad, setM_fileNameOnLoad] = useState(false); const [isWebGl20supported, setIsWebGl20supported] = useState(true); @@ -48,6 +51,7 @@ export const Main = () => { const [isFullMode, setIsFullMode] = useState(false); const [isMobile, setIsMobile] = useState(window.innerWidth <= 768); const appRef = useRef(); + const mriViwer = useRef(MriViwer).current; useEffect(() => { function handleResize() { @@ -60,6 +64,23 @@ export const Main = () => { window.removeEventListener('resize', handleResize); }; }, []); + + useEffect(() => { + const handleFileReadError = (eventData) => { + setStrAlertTitle('File Read Error'); + setStrAlertText(eventData.error); + onShowModalAlert(); + }; + + // Subscribe to the FILE_READ_ERROR event + mriViwer.events.on(MriEvents.FILE_READ_ERROR, handleFileReadError); + + // Clean up + return () => { + mriViwer.events.off(MriEvents.FILE_READ_ERROR, handleFileReadError); + }; + }, []); + const [, drop] = useDrop( () => ({ accept: DnDItemTypes.SETTINGS, @@ -215,7 +236,6 @@ export const Main = () => { <MobileSettings /> </div> )} - {arrErrors.length > 0 && <UiErrConsole />} {showModalText && ( <UiModalText stateVis={showModalText} onHide={onHideModalText.bind(this)} onShow={onShowModalText.bind(this)} /> )}