MOBILE-2653 iframe: Display warning in offline if needed
parent
85c3d0fc7c
commit
8f76caad92
|
@ -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…
Reference in New Issue