MOBILE-3103 iframe: Add fullscreen button on iframes header toolbar
parent
be8a7b35b7
commit
d8cc67eab1
|
@ -1574,6 +1574,7 @@
|
|||
"core.dftimedate": "local_moodlemobileapp",
|
||||
"core.digitalminor": "moodle",
|
||||
"core.digitalminor_desc": "moodle",
|
||||
"core.disablefullscreen": "h5p",
|
||||
"core.discard": "local_moodlemobileapp",
|
||||
"core.dismiss": "local_moodlemobileapp",
|
||||
"core.displayoptions": "atto_media",
|
||||
|
@ -1657,6 +1658,7 @@
|
|||
"core.forcepasswordchangenotice": "moodle",
|
||||
"core.fulllistofcourses": "moodle",
|
||||
"core.fullnameandsitename": "local_moodlemobileapp",
|
||||
"core.fullscreen": "h5p",
|
||||
"core.grades.aggregatemean": "grades",
|
||||
"core.grades.aggregatesum": "grades",
|
||||
"core.grades.average": "grades",
|
||||
|
|
|
@ -21,7 +21,9 @@
|
|||
<core-loading [hideUntil]="loaded" class="core-loading-fullheight">
|
||||
<core-navigation-bar [previous]="previousSco" [next]="nextSco" (action)="loadSco($event)"></core-navigation-bar>
|
||||
|
||||
<core-iframe *ngIf="loaded && src" [src]="src" [iframeWidth]="scormWidth" [iframeHeight]="scormHeight"></core-iframe>
|
||||
<core-iframe *ngIf="loaded && src" [src]="src" [iframeWidth]="scormWidth" [iframeHeight]="scormHeight"
|
||||
[showFullscreenOnToolbar]="true">
|
||||
</core-iframe>
|
||||
|
||||
<p *ngIf="!src && errorMessage">{{ errorMessage | translate }}</p>
|
||||
</core-loading>
|
||||
|
|
|
@ -66,7 +66,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
|||
protected moduleUrl!: string; // Module URL.
|
||||
protected newAttempt = false; // Whether to start a new attempt.
|
||||
protected organizationId?: string; // Organization ID to load.
|
||||
protected attempt?: number; // The attempt number.
|
||||
protected attempt = 0; // The attempt number.
|
||||
protected offline = false; // Whether it's offline mode.
|
||||
protected userData?: AddonModScormUserDataMap; // User data.
|
||||
protected initialScoId?: number; // Initial SCO ID to load.
|
||||
|
@ -96,7 +96,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
|||
this.newAttempt = !!CoreNavigator.getRouteBooleanParam('newAttempt');
|
||||
this.organizationId = CoreNavigator.getRouteParam('organizationId');
|
||||
this.initialScoId = CoreNavigator.getRouteNumberParam('scoId');
|
||||
this.siteId = CoreSites.getCurrentSiteId();
|
||||
this.siteId = CoreSites.getRequiredCurrentSite().getId();
|
||||
} catch (error) {
|
||||
CoreDomUtils.showErrorModal(error);
|
||||
|
||||
|
@ -150,14 +150,12 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
|||
this.showToc = AddonModScorm.displayTocInPlayer(this.scorm);
|
||||
|
||||
if (this.scorm.popup) {
|
||||
this.mainMenuPage.changeVisibility(false);
|
||||
|
||||
// If we receive a value > 100 we assume it's a fixed pixel size.
|
||||
if (this.scorm.width! > 100) {
|
||||
if (this.scorm.width && this.scorm.width > 100) {
|
||||
this.scormWidth = this.scorm.width;
|
||||
|
||||
// Only get fixed size on height if width is also fixed.
|
||||
if (this.scorm.height! > 100) {
|
||||
if (this.scorm.height && this.scorm.height > 100) {
|
||||
this.scormHeight = this.scorm.height;
|
||||
}
|
||||
}
|
||||
|
@ -198,7 +196,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
|||
// Wait a bit to prevent collisions between this store and SCORM API's store.
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
AddonModScormHelper.convertAttemptToOffline(this.scorm, this.attempt!);
|
||||
AddonModScormHelper.convertAttemptToOffline(this.scorm, this.attempt);
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'core.error', true);
|
||||
}
|
||||
|
@ -292,7 +290,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
|||
await this.determineAttemptAndMode(attemptsData);
|
||||
|
||||
const [data, accessInfo] = await Promise.all([
|
||||
AddonModScorm.getScormUserData(this.scorm.id, this.attempt!, {
|
||||
AddonModScorm.getScormUserData(this.scorm.id, this.attempt, {
|
||||
cmId: this.cmId,
|
||||
offline: this.offline,
|
||||
}),
|
||||
|
@ -319,13 +317,13 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
|||
|
||||
try {
|
||||
// We need to check incomplete again: attempt number or status might have changed.
|
||||
this.incomplete = await AddonModScorm.isAttemptIncomplete(this.scorm.id, this.attempt!, {
|
||||
this.incomplete = await AddonModScorm.isAttemptIncomplete(this.scorm.id, this.attempt, {
|
||||
offline: this.offline,
|
||||
cmId: this.cmId,
|
||||
});
|
||||
|
||||
// Get TOC.
|
||||
this.toc = await AddonModScormHelper.getToc(this.scorm.id, this.attempt!, this.incomplete, {
|
||||
this.toc = await AddonModScormHelper.getToc(this.scorm.id, this.attempt, this.incomplete, {
|
||||
organization: this.organizationId,
|
||||
offline: this.offline,
|
||||
cmId: this.cmId,
|
||||
|
@ -351,7 +349,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
// No SCO defined. Get the first valid one.
|
||||
const sco = await AddonModScormHelper.getFirstSco(this.scorm.id, this.attempt!, {
|
||||
const sco = await AddonModScormHelper.getFirstSco(this.scorm.id, this.attempt, {
|
||||
toc: this.toc,
|
||||
organization: this.organizationId,
|
||||
mode: this.mode,
|
||||
|
@ -383,7 +381,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
|||
this.siteId,
|
||||
this.scorm,
|
||||
sco.id,
|
||||
this.attempt!,
|
||||
this.attempt,
|
||||
this.userData!,
|
||||
this.mode,
|
||||
this.offline,
|
||||
|
@ -446,14 +444,14 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
|||
}];
|
||||
|
||||
try {
|
||||
AddonModScorm.saveTracks(sco.id, this.attempt!, tracks, this.scorm, this.offline);
|
||||
AddonModScorm.saveTracks(sco.id, this.attempt, tracks, this.scorm, this.offline);
|
||||
} catch {
|
||||
// Error saving data. Go offline if needed.
|
||||
if (this.offline) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await AddonModScorm.getScormUserData(this.scorm.id, this.attempt!, {
|
||||
const data = await AddonModScorm.getScormUserData(this.scorm.id, this.attempt, {
|
||||
cmId: this.cmId,
|
||||
});
|
||||
|
||||
|
@ -464,12 +462,12 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
|||
|
||||
try {
|
||||
// Go offline.
|
||||
await AddonModScormHelper.convertAttemptToOffline(this.scorm, this.attempt!);
|
||||
await AddonModScormHelper.convertAttemptToOffline(this.scorm, this.attempt);
|
||||
|
||||
this.offline = true;
|
||||
this.dataModel?.setOffline(true);
|
||||
|
||||
await AddonModScorm.saveTracks(sco.id, this.attempt!, tracks, this.scorm, true);
|
||||
await AddonModScorm.saveTracks(sco.id, this.attempt, tracks, this.scorm, true);
|
||||
} catch (error) {
|
||||
CoreDomUtils.instance.showErrorModalDefault(error, 'core.error', true);
|
||||
}
|
||||
|
@ -528,7 +526,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
|||
value: String(CoreTimeUtils.timestamp()),
|
||||
}];
|
||||
|
||||
await AddonModScorm.saveTracks(scoId, this.attempt!, tracks, this.scorm, this.offline);
|
||||
await AddonModScorm.saveTracks(scoId, this.attempt, tracks, this.scorm, this.offline);
|
||||
|
||||
if (this.offline) {
|
||||
return;
|
||||
|
@ -541,22 +539,6 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy {
|
|||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ionViewDidEnter(): void {
|
||||
if (this.scorm && this.scorm.popup) {
|
||||
this.mainMenuPage.changeVisibility(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ionViewWillLeave(): void {
|
||||
this.mainMenuPage.changeVisibility(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component being destroyed.
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
<div [class.core-loading-container]="loading || !safeUrl" [ngStyle]="{'width': iframeWidth, 'height': iframeHeight}">
|
||||
|
||||
<core-navbar-buttons slot="end" append *ngIf="initialized && showFullscreenOnToolbar">
|
||||
<ion-button fill="clear" (click)="toggleFullscreen()"
|
||||
[attr.aria-label]="(fullscreen ? 'core.disablefullscreen' : 'core.fullscreen') | translate" >
|
||||
<ion-icon *ngIf="!fullscreen" name="fas-expand" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||
<ion-icon *ngIf="fullscreen" name="fas-compress" slot="icon-only" aria-hidden="true"></ion-icon>
|
||||
</ion-button>
|
||||
</core-navbar-buttons>
|
||||
|
||||
<!-- Don't add the iframe until safeUrl is set, adding an iframe with null as src causes the iframe to load the whole app. -->
|
||||
<iframe #iframe *ngIf="safeUrl" [hidden]="loading" class="core-iframe"
|
||||
[ngStyle]="{'width': iframeWidth, 'height': iframeHeight}" [src]="safeUrl"
|
||||
|
|
|
@ -30,3 +30,14 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
:host-context(.core-iframe-fullscreen) {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import {
|
||||
Component, Input, Output, ViewChild, ElementRef, EventEmitter, OnChanges, SimpleChange,
|
||||
Component, Input, Output, ViewChild, ElementRef, EventEmitter, OnChanges, SimpleChange, OnDestroy,
|
||||
} from '@angular/core';
|
||||
import { SafeResourceUrl } from '@angular/platform-browser';
|
||||
|
||||
|
@ -22,7 +22,6 @@ import { CoreDomUtils } from '@services/utils/dom';
|
|||
import { CoreUrlUtils } from '@services/utils/url';
|
||||
import { CoreIframeUtils } from '@services/utils/iframe';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { DomSanitizer } from '@singletons';
|
||||
|
||||
@Component({
|
||||
|
@ -30,7 +29,7 @@ import { DomSanitizer } from '@singletons';
|
|||
templateUrl: 'core-iframe.html',
|
||||
styleUrls: ['iframe.scss'],
|
||||
})
|
||||
export class CoreIframeComponent implements OnChanges {
|
||||
export class CoreIframeComponent implements OnChanges, OnDestroy {
|
||||
|
||||
static loadingTimeout = 15000;
|
||||
|
||||
|
@ -39,17 +38,19 @@ export class CoreIframeComponent implements OnChanges {
|
|||
@Input() iframeWidth?: string;
|
||||
@Input() iframeHeight?: string;
|
||||
@Input() allowFullscreen?: boolean | string;
|
||||
@Input() showFullscreenOnToolbar?: boolean | string;
|
||||
@Output() loaded: EventEmitter<HTMLIFrameElement> = new EventEmitter<HTMLIFrameElement>();
|
||||
|
||||
loading?: boolean;
|
||||
safeUrl?: SafeResourceUrl;
|
||||
displayHelp = false;
|
||||
fullscreen = false;
|
||||
|
||||
protected logger: CoreLogger;
|
||||
protected initialized = false;
|
||||
initialized = false;
|
||||
|
||||
protected style?: HTMLStyleElement;
|
||||
|
||||
constructor() {
|
||||
this.logger = CoreLogger.getInstance('CoreIframe');
|
||||
this.loaded = new EventEmitter<HTMLIFrameElement>();
|
||||
}
|
||||
|
||||
|
@ -71,6 +72,16 @@ export class CoreIframeComponent implements OnChanges {
|
|||
this.iframeWidth = (this.iframeWidth && CoreDomUtils.formatPixelsSize(this.iframeWidth)) || '100%';
|
||||
this.iframeHeight = (this.iframeHeight && CoreDomUtils.formatPixelsSize(this.iframeHeight)) || '100%';
|
||||
this.allowFullscreen = CoreUtils.isTrueOrOne(this.allowFullscreen);
|
||||
this.showFullscreenOnToolbar = CoreUtils.isTrueOrOne(this.showFullscreenOnToolbar);
|
||||
|
||||
if (this.showFullscreenOnToolbar) {
|
||||
const shadow =
|
||||
iframe.closest('.ion-page')?.querySelector('ion-header ion-toolbar')?.shadowRoot;
|
||||
if (shadow) {
|
||||
this.style = document.createElement('style');
|
||||
shadow.appendChild(this.style);
|
||||
}
|
||||
}
|
||||
|
||||
// Show loading only with external URLs.
|
||||
this.loading = !this.src || !CoreUrlUtils.isLocalFileUrl(this.src);
|
||||
|
@ -120,4 +131,32 @@ export class CoreIframeComponent implements OnChanges {
|
|||
CoreIframeUtils.openIframeHelpModal();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.toggleFullscreen(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle fullscreen mode.
|
||||
*/
|
||||
toggleFullscreen(enable?: boolean): void {
|
||||
if (enable !== undefined) {
|
||||
this.fullscreen = enable;
|
||||
} else {
|
||||
this.fullscreen = !this.fullscreen;
|
||||
}
|
||||
|
||||
if (this.style) {
|
||||
// Done this way because of the shadow DOM.
|
||||
this.style.textContent = this.fullscreen
|
||||
? '@media screen and (orientation: landscape) {\
|
||||
.toolbar-container { flex-direction: column-reverse !important; height: 100%; } }'
|
||||
: '';
|
||||
}
|
||||
|
||||
document.body.classList.toggle('core-iframe-fullscreen', this.fullscreen);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -82,6 +82,7 @@
|
|||
"dftimedate": "h[:]mm A",
|
||||
"digitalminor": "Digital minor",
|
||||
"digitalminor_desc": "Please ask your parent/guardian to contact:",
|
||||
"disablefullscreen": "Disable fullscreen",
|
||||
"discard": "Discard",
|
||||
"dismiss": "Dismiss",
|
||||
"displayoptions": "Display options",
|
||||
|
@ -122,6 +123,7 @@
|
|||
"forcepasswordchangenotice": "You must change your password to proceed.",
|
||||
"fulllistofcourses": "All courses",
|
||||
"fullnameandsitename": "{{fullname}} ({{sitename}})",
|
||||
"fullscreen": "Fullscreen",
|
||||
"group": "Group",
|
||||
"groupsseparate": "Separate groups",
|
||||
"groupsvisible": "Visible groups",
|
||||
|
|
|
@ -384,6 +384,46 @@ ion-toolbar {
|
|||
}
|
||||
}
|
||||
|
||||
// Iframe fullscreen manage.
|
||||
// Using router outlet to avoid changing styles on modals.
|
||||
body.core-iframe-fullscreen ion-router-outlet {
|
||||
|
||||
ion-tab-bar.mainmenu-tabs {
|
||||
display: none;
|
||||
}
|
||||
|
||||
--core-header-toolbar-height: 48px;
|
||||
--core-header-toolbar-color: white;
|
||||
--core-header-toolbar-background: black;
|
||||
--core-header-toolbar-border-width: 0;
|
||||
|
||||
ion-header ion-toolbar {
|
||||
h1, ion-back-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (orientation: landscape) {
|
||||
// Place ion-header on the side and hide text
|
||||
.ion-page {
|
||||
flex-direction: row-reverse;
|
||||
ion-header {
|
||||
width: var(--core-header-toolbar-height);
|
||||
|
||||
ion-toolbar {
|
||||
height: 100%;
|
||||
--padding-start: 0;
|
||||
--padding-end: 0;
|
||||
}
|
||||
|
||||
ion-buttons {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Modals.
|
||||
.core-modal-fullscreen .modal-wrapper {
|
||||
position: absolute;
|
||||
|
|
|
@ -236,6 +236,11 @@
|
|||
--color: var(--subdued-text-color);
|
||||
}
|
||||
|
||||
ion-back-button {
|
||||
--min-height: var(--a11y-min-target-size);
|
||||
--min-width: var(--a11y-min-target-size);
|
||||
}
|
||||
|
||||
--core-combobox-background: var(--ion-item-background);
|
||||
--core-combobox-color: var(--black);
|
||||
--core-combobox-border-color: var(--primary);
|
||||
|
|
Loading…
Reference in New Issue