@@ -29,6 +29,10 @@ import {
2929} from "./videoRecordingSupport" ;
3030import LangData from "./langData" ;
3131
32+ // Used to force the app bar (part of the controls) to be displayed,
33+ // e.g. when we need to force the back button to appear when there is an error loading a book.
34+ export const ForceShowAppBar = new LiteEvent < void > ( ) ;
35+
3236// See related comments in controlBar.tsx
3337import IconButton from "@material-ui/core/IconButton" ;
3438import ArrowBack from "@material-ui/icons/ArrowBackIosRounded" ;
@@ -422,7 +426,12 @@ export class BloomPlayerCore extends React.Component<IProps, IPlayerState> {
422426 }
423427 // called by bloom-player-controls
424428 public HandleBackButtonClickedIfHavePlayerHistory ( ) : boolean {
425- const backLocation = tryPopPlayerHistory ( this . bookInfo . bookInstanceId ) ;
429+ // When a linked book fails to load, bookInfo.bookInstanceId may still be the previous book.
430+ // If our current URL is a /book/{id}/index.htm-style URL, prefer that for navigation history.
431+ const currentBookIdForHistory =
432+ this . getBookIdFromBookUrlForHistory ( this . state . bookUrl ) ||
433+ this . bookInfo . bookInstanceId ;
434+ const backLocation = tryPopPlayerHistory ( currentBookIdForHistory ) ;
426435 if ( backLocation ) {
427436 this . navigate ( backLocation . bookUrl , backLocation . pageId ) ;
428437 return true ;
@@ -460,6 +469,22 @@ export class BloomPlayerCore extends React.Component<IProps, IPlayerState> {
460469 }
461470 }
462471
472+ private getBookIdFromBookUrlForHistory (
473+ bookUrl : string | undefined ,
474+ ) : string | undefined {
475+ if ( ! bookUrl ) {
476+ return undefined ;
477+ }
478+ try {
479+ const pathname = new URL ( bookUrl , window . location . href ) . pathname ;
480+ // Expected format from navigation.ts: /book/{guid}/index.htm
481+ const match = pathname . match ( / ^ \/ b o o k \/ ( [ ^ / ] + ) \/ i n d e x \. h t m $ / i) ;
482+ return match ? match [ 1 ] : undefined ;
483+ } catch {
484+ return undefined ;
485+ }
486+ }
487+
463488 private handleWindowFocus ( ) {
464489 const readDuration = localStorage . getItem ( kLocalStorageDurationKey ) ;
465490 const savedBookUrl = localStorage . getItem ( kLocalStorageBookUrlKey ) ;
@@ -585,8 +610,13 @@ export class BloomPlayerCore extends React.Component<IProps, IPlayerState> {
585610 // This happens in Bloom Editor preview, where a render of some outer component does not
586611 // yet have the URL. There's nothing useful we can do.
587612 if ( ! this . props . url ) return ;
588- if ( this . state . loadFailed ) {
589- return ; // otherwise we'll just be stuck in here forever trying to load
613+ // If a load failed, we normally stop trying to reload (otherwise we can loop forever).
614+ // But we still want to allow navigation (e.g. Back) to a different URL to recover.
615+ if (
616+ this . state . loadFailed &&
617+ prevState . bookUrl === this . state . bookUrl
618+ ) {
619+ return ;
590620 }
591621 // also one-time setup; only the first time through
592622 this . initializeMedia ( ) ;
@@ -888,31 +918,48 @@ export class BloomPlayerCore extends React.Component<IProps, IPlayerState> {
888918
889919 private HandleLoadingError ( axiosError : any ) {
890920 const errorMessage = axiosError . message as string ;
891- // Note: intentionally no bothering to add this to the l10n load, at this time.
921+ // Note: intentionally not bothering to add this to the l10n load, at this time.
892922 let msg = `<p>There was a problem displaying this book: ${ errorMessage } <p>` ; // just show the raw thing
893- // If it's a file:/// url, we're probably on an Android, and something
894- // went wrong with unpacking it, or it is corrupt. This typically results in an error
895- // message that is NOT a 404 but IS reported, unhelpfully, as a "Network Error" (BL-7813).
896- // A file:/// url can't possibly be a network error, so the "part of the book is missing"
897- // error is at least a little more helpful, maybe to the user, and certainly to a developer
898- // trying to fix the problem, especially if the user reports the problem URL.
899- const localFileMissing =
900- axiosError . config &&
901- axiosError . config . url &&
902- axiosError . config . url . startsWith ( "file://" ) ;
903- if ( localFileMissing || axiosError . message . indexOf ( "404" ) >= 0 ) {
904- msg = "<p>This book (or some part of it) was not found.<p>" ;
905- if ( axiosError . config && axiosError . config . url ) {
906- msg += `<p class='errorDetails'>${ htmlEncode (
907- axiosError . config . url ,
908- ) } </p>`;
923+
924+ const isBookUrlLoadFailure =
925+ ! ! axiosError . config ?. url &&
926+ new URL (
927+ axiosError . config . url ,
928+ window . location . href ,
929+ ) . pathname . startsWith ( "/book/" ) ;
930+
931+ if ( isBookUrlLoadFailure ) {
932+ msg = "<p>We could not find that book.</p>" ;
933+ } else {
934+ // If it's a file:/// url, we're probably on an Android, and something
935+ // went wrong with unpacking it, or it is corrupt. This typically results in an error
936+ // message that is NOT a 404 but IS reported, unhelpfully, as a "Network Error" (BL-7813).
937+ // A file:/// url can't possibly be a network error, so the "part of the book is missing"
938+ // error is at least a little more helpful, maybe to the user, and certainly to a developer
939+ // trying to fix the problem, especially if the user reports the problem URL.
940+ const localFileMissing =
941+ axiosError . config &&
942+ axiosError . config . url &&
943+ axiosError . config . url . startsWith ( "file://" ) ;
944+ if ( localFileMissing || axiosError . message . indexOf ( "404" ) >= 0 ) {
945+ msg = "<p>This book (or some part of it) was not found.<p>" ;
946+ if ( axiosError . config && axiosError . config . url ) {
947+ msg += `<p class='errorDetails'>${ htmlEncode (
948+ axiosError . config . url ,
949+ ) } </p>`;
950+ }
909951 }
910952 }
953+
911954 this . setState ( {
912955 isLoading : false ,
913956 loadFailed : true ,
914957 loadErrorHtml : msg ,
915958 } ) ;
959+
960+ if ( isBookUrlLoadFailure ) {
961+ ForceShowAppBar . raise ( ) ;
962+ }
916963 }
917964
918965 private finishUpCalled = false ;
0 commit comments