Merge pull request #1565 from dpalou/MOBILE-2653
MOBILE-2653 iframe: Display warning in offline if needed
This commit is contained in:
		
						commit
						47408c9602
					
				| @ -1403,6 +1403,7 @@ | |||||||
|   "core.more": "moodle", |   "core.more": "moodle", | ||||||
|   "core.mygroups": "group", |   "core.mygroups": "group", | ||||||
|   "core.name": "moodle", |   "core.name": "moodle", | ||||||
|  |   "core.networkerroriframemsg": "local_moodlemobileapp", | ||||||
|   "core.networkerrormsg": "local_moodlemobileapp", |   "core.networkerrormsg": "local_moodlemobileapp", | ||||||
|   "core.never": "moodle", |   "core.never": "moodle", | ||||||
|   "core.next": "moodle", |   "core.next": "moodle", | ||||||
|  | |||||||
| @ -941,6 +941,10 @@ ion-app.app-root { | |||||||
|   &.disable-scroll ion-modal .ion-page { |   &.disable-scroll ion-modal .ion-page { | ||||||
|     pointer-events: initial; |     pointer-events: initial; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   .core-iframe-offline-disabled { | ||||||
|  |     display: none !important; | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @each $color-name, $color-base, $color-contrast in get-colors($colors) { | @each $color-name, $color-base, $color-contrast in get-colors($colors) { | ||||||
|  | |||||||
| @ -66,7 +66,20 @@ export class CoreIconComponent implements OnInit { | |||||||
| 
 | 
 | ||||||
|         const attrs = this.element.attributes; |         const attrs = this.element.attributes; | ||||||
|         for (let i = attrs.length - 1; i >= 0; i--) { |         for (let i = attrs.length - 1; i >= 0; i--) { | ||||||
|             newElement.setAttribute(attrs[i].name, attrs[i].value); |             if (attrs[i].name == 'class') { | ||||||
|  |                 // We don't want to override the classes we already added. Add them one by one.
 | ||||||
|  |                 if (attrs[i].value) { | ||||||
|  |                     const classes = attrs[i].value.split(' '); | ||||||
|  |                     for (let j = 0; j < classes.length; j++) { | ||||||
|  |                         if (classes[j]) { | ||||||
|  |                             newElement.classList.add(classes[j]); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |             } else { | ||||||
|  |                 newElement.setAttribute(attrs[i].name, attrs[i].value); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.element.parentElement.replaceChild(newElement, this.element); |         this.element.parentElement.replaceChild(newElement, this.element); | ||||||
| @ -79,12 +92,12 @@ export class CoreIconComponent implements OnInit { | |||||||
|      * @return {boolean}     If has a value equivalent to true. |      * @return {boolean}     If has a value equivalent to true. | ||||||
|      */ |      */ | ||||||
|     isTrueProperty(val: any): boolean { |     isTrueProperty(val: any): boolean { | ||||||
|       if (typeof val === 'string') { |         if (typeof val === 'string') { | ||||||
|           val = val.toLowerCase().trim(); |             val = val.toLowerCase().trim(); | ||||||
| 
 | 
 | ||||||
|           return (val === 'true' || val === 'on' || val === ''); |             return (val === 'true' || val === 'on' || val === ''); | ||||||
|       } |         } | ||||||
| 
 | 
 | ||||||
|       return !!val; |         return !!val; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -153,6 +153,7 @@ | |||||||
|     "mygroups": "My groups", |     "mygroups": "My groups", | ||||||
|     "name": "Name", |     "name": "Name", | ||||||
|     "nograde": "No grade", |     "nograde": "No grade", | ||||||
|  |     "networkerroriframemsg": "This content is not available offline. Please connect to the internet and try again.", | ||||||
|     "networkerrormsg": "There was a problem connecting to the site. Please check your connection and try again.", |     "networkerrormsg": "There was a problem connecting to the site. Please check your connection and try again.", | ||||||
|     "never": "Never", |     "never": "Never", | ||||||
|     "next": "Next", |     "next": "Next", | ||||||
|  | |||||||
| @ -429,6 +429,18 @@ export class CoreDomUtilsProvider { | |||||||
|         return parseInt(style[measure], 10) || 0; |         return parseInt(style[measure], 10) || 0; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the HTML code to render a connection warning icon. | ||||||
|  |      * | ||||||
|  |      * @return {string} HTML Code. | ||||||
|  |      */ | ||||||
|  |     getConnectionWarningIconHtml(): string { | ||||||
|  |         return '<div text-center><span class="core-icon-with-badge">' + | ||||||
|  |                 '<ion-icon role="img" class="icon fa fa-wifi" aria-label="wifi"></ion-icon>' + | ||||||
|  |                 '<ion-icon class="icon fa fa-exclamation-triangle core-icon-badge"></ion-icon>' + | ||||||
|  |             '</span></div>'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Returns width of an element. |      * Returns width of an element. | ||||||
|      * |      * | ||||||
| @ -500,12 +512,9 @@ export class CoreDomUtilsProvider { | |||||||
|      */ |      */ | ||||||
|     private getErrorTitle(message: string): any { |     private getErrorTitle(message: string): any { | ||||||
|         if (message == this.translate.instant('core.networkerrormsg') || |         if (message == this.translate.instant('core.networkerrormsg') || | ||||||
|             message == this.translate.instant('core.fileuploader.errormustbeonlinetoupload')) { |                 message == this.translate.instant('core.fileuploader.errormustbeonlinetoupload')) { | ||||||
|             return this.sanitizer.bypassSecurityTrustHtml('<div text-center><span class="core-icon-with-badge">' + |  | ||||||
|                     '<ion-icon role="img" class="icon fa fa-wifi" aria-label="wifi"></ion-icon>' + |  | ||||||
|                     '<ion-icon class="icon fa fa-exclamation-triangle core-icon-badge"></ion-icon>' + |  | ||||||
|                 '</span></div>'); |  | ||||||
| 
 | 
 | ||||||
|  |             return this.sanitizer.bypassSecurityTrustHtml(this.getConnectionWarningIconHtml()); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return this.textUtils.decodeHTML(this.translate.instant('core.error')); |         return this.textUtils.decodeHTML(this.translate.instant('core.error')); | ||||||
|  | |||||||
| @ -12,8 +12,11 @@ | |||||||
| // See the License for the specific language governing permissions and
 | // See the License for the specific language governing permissions and
 | ||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
| import { Injectable } from '@angular/core'; | import { Injectable, NgZone } from '@angular/core'; | ||||||
| import { Platform } from 'ionic-angular'; | import { Platform } from 'ionic-angular'; | ||||||
|  | import { TranslateService } from '@ngx-translate/core'; | ||||||
|  | import { Network } from '@ionic-native/network'; | ||||||
|  | import { CoreAppProvider } from '../app'; | ||||||
| import { CoreFileProvider } from '../file'; | import { CoreFileProvider } from '../file'; | ||||||
| import { CoreLoggerProvider } from '../logger'; | import { CoreLoggerProvider } from '../logger'; | ||||||
| import { CoreSitesProvider } from '../sites'; | import { CoreSitesProvider } from '../sites'; | ||||||
| @ -33,10 +36,75 @@ export class CoreIframeUtilsProvider { | |||||||
| 
 | 
 | ||||||
|     constructor(logger: CoreLoggerProvider, private fileProvider: CoreFileProvider, private sitesProvider: CoreSitesProvider, |     constructor(logger: CoreLoggerProvider, private fileProvider: CoreFileProvider, private sitesProvider: CoreSitesProvider, | ||||||
|             private urlUtils: CoreUrlUtilsProvider, private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider, |             private urlUtils: CoreUrlUtilsProvider, private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider, | ||||||
|             private domUtils: CoreDomUtilsProvider, private platform: Platform) { |             private domUtils: CoreDomUtilsProvider, private platform: Platform, private appProvider: CoreAppProvider, | ||||||
|  |             private translate: TranslateService, private network: Network, private zone: NgZone) { | ||||||
|         this.logger = logger.getInstance('CoreUtilsProvider'); |         this.logger = logger.getInstance('CoreUtilsProvider'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Check if a frame uses an online URL but the app is offline. If it does, the iframe is hidden and a warning is shown. | ||||||
|  |      * | ||||||
|  |      * @param {any} element The frame to check (iframe, embed, ...). | ||||||
|  |      * @param {boolean} [isSubframe] Whether it's a frame inside another frame. | ||||||
|  |      * @return {boolean} True if frame is online and the app is offline, false otherwise. | ||||||
|  |      */ | ||||||
|  |     checkOnlineFrameInOffline(element: any, isSubframe?: boolean): boolean { | ||||||
|  |         const src = element.src || element.data; | ||||||
|  | 
 | ||||||
|  |         if (src && src.match(/^https?:\/\//i) && !this.appProvider.isOnline()) { | ||||||
|  |             if (element.classList.contains('core-iframe-offline-disabled')) { | ||||||
|  |                 // Iframe already hidden, stop.
 | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // The frame has an online URL but the app is offline. Show a warning.
 | ||||||
|  |             const div = document.createElement('div'); | ||||||
|  | 
 | ||||||
|  |             div.setAttribute('text-center', ''); | ||||||
|  |             div.setAttribute('padding', ''); | ||||||
|  |             div.classList.add('core-iframe-offline-warning'); | ||||||
|  |             div.innerHTML = (isSubframe ?  '' : this.domUtils.getConnectionWarningIconHtml()) + | ||||||
|  |                     '<p>' + this.translate.instant('core.networkerroriframemsg') + '</p>'; | ||||||
|  | 
 | ||||||
|  |             element.parentElement.insertBefore(div, element); | ||||||
|  | 
 | ||||||
|  |             // Add a class to specify that the iframe is hidden.
 | ||||||
|  |             element.classList.add('core-iframe-offline-disabled'); | ||||||
|  | 
 | ||||||
|  |             if (isSubframe) { | ||||||
|  |                 // We cannot apply CSS styles in subframes, just hide the iframe.
 | ||||||
|  |                 element.style.display = 'none'; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // If the network changes, check it again.
 | ||||||
|  |             const subscription = this.network.onConnect().subscribe(() => { | ||||||
|  |                 // Execute the callback in the Angular zone, so change detection doesn't stop working.
 | ||||||
|  |                 this.zone.run(() => { | ||||||
|  |                     if (!this.checkOnlineFrameInOffline(element, isSubframe)) { | ||||||
|  |                         // Now the app is online, no need to check connection again.
 | ||||||
|  |                         subscription.unsubscribe(); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             return true; | ||||||
|  |         } else if (element.classList.contains('core-iframe-offline-disabled')) { | ||||||
|  |             // Reload the frame.
 | ||||||
|  |             element.src = element.src; | ||||||
|  |             element.data = element.data; | ||||||
|  | 
 | ||||||
|  |             // Remove the warning and show the iframe
 | ||||||
|  |             this.domUtils.removeElement(element.parentElement, 'div.core-iframe-offline-warning'); | ||||||
|  |             element.classList.remove('core-iframe-offline-disabled'); | ||||||
|  | 
 | ||||||
|  |             if (isSubframe) { | ||||||
|  |                 element.style.display = ''; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Given an element, return the content window and document. |      * Given an element, return the content window and document. | ||||||
|      * Please notice that the element should be an iframe, embed or similar. |      * Please notice that the element should be an iframe, embed or similar. | ||||||
| @ -136,7 +204,7 @@ export class CoreIframeUtilsProvider { | |||||||
|             CoreIframeUtilsProvider.FRAME_TAGS.forEach((tag) => { |             CoreIframeUtilsProvider.FRAME_TAGS.forEach((tag) => { | ||||||
|                 const elements = Array.from(contentDocument.querySelectorAll(tag)); |                 const elements = Array.from(contentDocument.querySelectorAll(tag)); | ||||||
|                 elements.forEach((subElement) => { |                 elements.forEach((subElement) => { | ||||||
|                     this.treatFrame(subElement); |                     this.treatFrame(subElement, true); | ||||||
|                 }); |                 }); | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
| @ -147,9 +215,12 @@ export class CoreIframeUtilsProvider { | |||||||
|      * Search links (<a>) and open them in browser or InAppBrowser if needed. |      * Search links (<a>) and open them in browser or InAppBrowser if needed. | ||||||
|      * |      * | ||||||
|      * @param {any} element Element to treat (iframe, embed, ...). |      * @param {any} element Element to treat (iframe, embed, ...). | ||||||
|  |      * @param {boolean} [isSubframe] Whether it's a frame inside another frame. | ||||||
|      */ |      */ | ||||||
|     treatFrame(element: any): void { |     treatFrame(element: any, isSubframe?: boolean): void { | ||||||
|         if (element) { |         if (element) { | ||||||
|  |             this.checkOnlineFrameInOffline(element, isSubframe); | ||||||
|  | 
 | ||||||
|             let winAndDoc = this.getContentWindowAndDocument(element); |             let winAndDoc = this.getContentWindowAndDocument(element); | ||||||
|             // Redefine window.open in this element and sub frames, it might have been loaded already.
 |             // Redefine window.open in this element and sub frames, it might have been loaded already.
 | ||||||
|             this.redefineWindowOpen(element, winAndDoc.window, winAndDoc.document); |             this.redefineWindowOpen(element, winAndDoc.window, winAndDoc.document); | ||||||
| @ -157,6 +228,8 @@ export class CoreIframeUtilsProvider { | |||||||
|             this.treatFrameLinks(element, winAndDoc.document); |             this.treatFrameLinks(element, winAndDoc.document); | ||||||
| 
 | 
 | ||||||
|             element.addEventListener('load', () => { |             element.addEventListener('load', () => { | ||||||
|  |                 this.checkOnlineFrameInOffline(element, isSubframe); | ||||||
|  | 
 | ||||||
|                 // Element loaded, redefine window.open and treat links again.
 |                 // Element loaded, redefine window.open and treat links again.
 | ||||||
|                 winAndDoc = this.getContentWindowAndDocument(element); |                 winAndDoc = this.getContentWindowAndDocument(element); | ||||||
|                 this.redefineWindowOpen(element, winAndDoc.window, winAndDoc.document); |                 this.redefineWindowOpen(element, winAndDoc.window, winAndDoc.document); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user