diff --git a/web/src/App.js b/web/src/App.js index ba4197e..5f4e4db 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -66,6 +66,14 @@ export class App extends React.Component // This also has the nice side-effect of removing CORS issues :-/ this._flaskBaseUrl = "/api" ; } + + // NOTE: Managing publisher/publication/article images is a bit tricky, since they are accessed via a URL + // such as "/articles/images/123", so if the user uploads a new image, the browser has no way of knowing + // that it can't use what's in its cache and must get a new one. We can add something to the URL to force + // a reload (e.g. "?foo=" + Math.random()), but this forces the image to be reloaded *every* time, which is + // pretty inefficient. + // Instead, we track a unique cache-busting value for each image URL, and change it when necessary. + this._flaskImageUrlVersions = {} ; } render() { @@ -465,21 +473,38 @@ export class App extends React.Component } return url ; } - makeFlaskImageUrl( type, imageId, force ) { + makeExternalDocUrl( url ) { + // generate a URL for an external document + if ( url.substr( 0, 2 ) === "$/" ) + url = url.substr( 2 ) ; + return this.makeFlaskUrl( "/docs/" + encodeURIComponent(url) ) ; + } + + makeFlaskImageUrl( type, imageId ) { // generate an image URL for the Flask backend server if ( ! imageId ) return null ; let url = this.makeFlaskUrl( "/images/" + type + "/" + imageId ) ; - if ( force ) - url += "?foo=" + Math.random() ; // FUDGE! To bypass the cache :-/ + const key = this._makeFlaskImageKey( type, imageId ) ; + if ( ! this._flaskImageUrlVersions[ key ] ) { + // NOTE: It would be nice to only add this if necessary (i.e. the user has changed + // the image, thus requiring us to fetch the new image), but not doing so causes problems + // in a dev environment, since we are constantly changing things in the database + // outside the app (e.g. in tests) and the browser cache will get out of sync. + this.forceFlaskImageReload( type, imageId ) ; + } + url += "?v=" + this._flaskImageUrlVersions[key] ; return url ; } - makeExternalDocUrl( url ) { - // generate a URL for an external document - if ( url.substr( 0, 2 ) === "$/" ) - url = url.substr( 2 ) ; - return this.makeFlaskUrl( "/docs/" + encodeURIComponent(url) ) ; + forceFlaskImageReload( type, imageId ) { + // bump the image's version#, which will force a new URL the next time makeFlaskImageUrl() is called + const key = this._makeFlaskImageKey( type, imageId ) ; + const version = this._flaskImageUrlVersions[ key ] ; + // NOTE: It would be nice to start at 1, but this causes problems in a dev environment, since + // we are constantly changing things in the database, and the browser cache will get out of sync. + this._flaskImageUrlVersions[ key ] = version ? version+1 : Math.floor(Date.now()/1000) ; } + _makeFlaskImageKey( type, imageId ) { return type + ":" + imageId ; } _onStartupTask( taskId ) { // flag that the specified startup task has completed diff --git a/web/src/ArticleSearchResult.js b/web/src/ArticleSearchResult.js index 15a521e..3aa2adc 100644 --- a/web/src/ArticleSearchResult.js +++ b/web/src/ArticleSearchResult.js @@ -22,7 +22,7 @@ export class ArticleSearchResult extends React.Component const display_subtitle = this.props.data[ "article_subtitle!" ] || this.props.data.article_subtitle ; const display_snippet = this.props.data[ "article_snippet!" ] || this.props.data.article_snippet ; const pub = gAppRef.caches.publications[ this.props.data.pub_id ] ; - const image_url = gAppRef.makeFlaskImageUrl( "article", this.props.data.article_image_id, true ) ; + const image_url = gAppRef.makeFlaskImageUrl( "article", this.props.data.article_image_id ) ; // prepare the article's URL let article_url = this.props.data.article_url ; @@ -202,6 +202,8 @@ export class ArticleSearchResult extends React.Component // update the UI with the new details applyUpdatedVals( this.props.data, newVals, resp.data.updated, refs ) ; removeSpecialFields( this.props.data ) ; + if ( newVals.imageData ) + gAppRef.forceFlaskImageReload( "article", newVals.article_id ) ; this.forceUpdate() ; if ( resp.data.warnings ) gAppRef.showWarnings( "The article was updated OK.", resp.data.warnings ) ; diff --git a/web/src/ArticleSearchResult2.js b/web/src/ArticleSearchResult2.js index 71e3a99..cb8ac72 100644 --- a/web/src/ArticleSearchResult2.js +++ b/web/src/ArticleSearchResult2.js @@ -28,8 +28,7 @@ export class ArticleSearchResult2 // initialize the image let imageFilename=null, imageData=null ; let imageRef=null, uploadImageRef=null, removeImageRef=null ; - let imageUrl = gAppRef.makeFlaskUrl( "/images/article/" + vals.article_id ) ; - imageUrl += "?foo=" + Math.random() ; // FUDGE! To bypass the cache :-/ + let imageUrl = gAppRef.makeFlaskImageUrl( "article", vals.article_id ) || "/force-404" ; function onImageLoaded() { onReady() ; } function onMissingImage() { imageRef.src = "/images/placeholder.png" ; diff --git a/web/src/PublicationSearchResult.js b/web/src/PublicationSearchResult.js index 55c365a..172bad6 100644 --- a/web/src/PublicationSearchResult.js +++ b/web/src/PublicationSearchResult.js @@ -160,6 +160,8 @@ export class PublicationSearchResult extends React.Component // update the UI with the new details applyUpdatedVals( this.props.data, newVals, resp.data.updated, refs ) ; removeSpecialFields( this.props.data ) ; + if ( newVals.imageData ) + gAppRef.forceFlaskImageReload( "publication", newVals.pub_id ) ; this.forceUpdate() ; if ( resp.data.warnings ) gAppRef.showWarnings( "The publication was updated OK.", resp.data.warnings ) ; @@ -244,7 +246,7 @@ export class PublicationSearchResult extends React.Component _makeDisplayName( allowAlternateContent ) { return PublicationSearchResult.makeDisplayName( this.props.data, allowAlternateContent ) ; } static makeImageUrl( vals ) { - let image_url = gAppRef.makeFlaskImageUrl( "publication", vals.pub_image_id, true ) ; + let image_url = gAppRef.makeFlaskImageUrl( "publication", vals.pub_image_id ) ; if ( ! image_url ) { // check if the parent publisher has an image if ( vals.publ_id ) { diff --git a/web/src/PublicationSearchResult2.js b/web/src/PublicationSearchResult2.js index c6a7969..3a2a380 100644 --- a/web/src/PublicationSearchResult2.js +++ b/web/src/PublicationSearchResult2.js @@ -33,10 +33,8 @@ export class PublicationSearchResult2 let imageUrl ; if ( initialVals ) imageUrl = imageRef.src ; // nb: leave whatever's there already - else { - imageUrl = gAppRef.makeFlaskUrl( "/images/publication/" + vals.pub_id ) ; - imageUrl += "?foo=" + Math.random() ; // FUDGE! To bypass the cache :-/ - } + else + imageUrl = gAppRef.makeFlaskImageUrl( "publication", vals.pub_id ) || "/force-404" ; function onImageLoaded() { onReady() ; } function onMissingImage() { imageRef.src = "/images/placeholder.png" ; diff --git a/web/src/PublisherSearchResult.js b/web/src/PublisherSearchResult.js index 970cfcb..0b07d60 100644 --- a/web/src/PublisherSearchResult.js +++ b/web/src/PublisherSearchResult.js @@ -20,7 +20,7 @@ export class PublisherSearchResult extends React.Component // prepare the basic details const display_name = this.props.data[ "publ_name!" ] || this.props.data.publ_name ; const display_description = this.props.data[ "publ_description!" ] || this.props.data.publ_description ; - const image_url = gAppRef.makeFlaskImageUrl( "publisher", this.props.data.publ_image_id, true ) ; + const image_url = gAppRef.makeFlaskImageUrl( "publisher", this.props.data.publ_image_id ) ; // prepare the publications let pubs = [] ; @@ -112,6 +112,8 @@ export class PublisherSearchResult extends React.Component // update the UI with the new details applyUpdatedVals( this.props.data, newVals, resp.data.updated, refs ) ; removeSpecialFields( this.props.data ) ; + if ( newVals.imageData ) + gAppRef.forceFlaskImageReload( "publisher", newVals.publ_id ) ; this.forceUpdate() ; if ( resp.data.warnings ) gAppRef.showWarnings( "The publisher was updated OK.", resp.data.warnings ) ; diff --git a/web/src/PublisherSearchResult2.js b/web/src/PublisherSearchResult2.js index 1b94ef8..3835247 100644 --- a/web/src/PublisherSearchResult2.js +++ b/web/src/PublisherSearchResult2.js @@ -23,8 +23,7 @@ export class PublisherSearchResult2 // initialize the image let imageFilename=null, imageData=null ; let imageRef=null, uploadImageRef=null, removeImageRef=null ; - let imageUrl = gAppRef.makeFlaskUrl( "/images/publisher/" + vals.publ_id ) ; - imageUrl += "?foo=" + Math.random() ; // FUDGE! To bypass the cache :-/ + let imageUrl = gAppRef.makeFlaskImageUrl( "publisher", vals.publ_id ) || "/force-404" ; function onImageLoaded() { onReady() ; } function onMissingImage() { imageRef.src = "/images/placeholder.png" ;