commit
155ec962ed
|
@ -1469,6 +1469,7 @@
|
|||
"core.cannotconnecttrouble": "local_moodlemobileapp",
|
||||
"core.cannotconnectverify": "local_moodlemobileapp",
|
||||
"core.cannotdownloadfiles": "local_moodlemobileapp",
|
||||
"core.cannotlogoutpageblocks": "local_moodlemobileapp",
|
||||
"core.cannotopeninapp": "local_moodlemobileapp",
|
||||
"core.cannotopeninappdownload": "local_moodlemobileapp",
|
||||
"core.captureaudio": "local_moodlemobileapp",
|
||||
|
|
|
@ -41,7 +41,7 @@ import { CoreLogger } from '@singletons/logger';
|
|||
import { Translate } from '@singletons';
|
||||
import { CoreIonLoadingElement } from './ion-loading';
|
||||
import { CoreLang } from '@services/lang';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
||||
import { asyncInstance, AsyncInstance } from '../utils/async-instance';
|
||||
import { CoreDatabaseTable } from './database/database-table';
|
||||
import { CoreDatabaseCachingStrategy } from './database/database-table-proxy';
|
||||
|
@ -1389,9 +1389,88 @@ export class CoreSite {
|
|||
/**
|
||||
* Get the public config of this site.
|
||||
*
|
||||
* @param options Options.
|
||||
* @return Promise resolved with public config. Rejected with an object if error, see CoreWSProvider.callAjax.
|
||||
*/
|
||||
async getPublicConfig(): Promise<CoreSitePublicConfigResponse> {
|
||||
async getPublicConfig(options: { readingStrategy?: CoreSitesReadingStrategy } = {}): Promise<CoreSitePublicConfigResponse> {
|
||||
if (!this.db) {
|
||||
return this.requestPublicConfig();
|
||||
}
|
||||
|
||||
const method = 'tool_mobile_get_public_config';
|
||||
const cacheId = this.getCacheId(method, {});
|
||||
const cachePreSets: CoreSiteWSPreSets = {
|
||||
getFromCache: true,
|
||||
saveToCache: true,
|
||||
emergencyCache: true,
|
||||
...CoreSites.getReadingStrategyPreSets(options.readingStrategy),
|
||||
};
|
||||
|
||||
if (this.offlineDisabled) {
|
||||
// Offline is disabled, don't use cache.
|
||||
cachePreSets.getFromCache = false;
|
||||
cachePreSets.saveToCache = false;
|
||||
cachePreSets.emergencyCache = false;
|
||||
}
|
||||
|
||||
// Check for an ongoing identical request if we're not ignoring cache.
|
||||
if (cachePreSets.getFromCache && this.ongoingRequests[cacheId]) {
|
||||
const response = await this.ongoingRequests[cacheId];
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
const promise = this.getFromCache<CoreSitePublicConfigResponse>(method, {}, cachePreSets, false).catch(async () => {
|
||||
if (cachePreSets.forceOffline) {
|
||||
// Don't call the WS, just fail.
|
||||
throw new CoreError(
|
||||
Translate.instant('core.cannotconnect', { $a: CoreSite.MINIMUM_MOODLE_VERSION }),
|
||||
);
|
||||
}
|
||||
|
||||
// Call the WS.
|
||||
try {
|
||||
const config = await this.requestPublicConfig();
|
||||
|
||||
if (cachePreSets.saveToCache) {
|
||||
this.saveToCache(method, {}, config, cachePreSets);
|
||||
}
|
||||
|
||||
return config;
|
||||
} catch (error) {
|
||||
cachePreSets.omitExpires = true;
|
||||
cachePreSets.getFromCache = true;
|
||||
|
||||
try {
|
||||
return await this.getFromCache<CoreSitePublicConfigResponse>(method, {}, cachePreSets, true);
|
||||
} catch {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.ongoingRequests[cacheId] = promise;
|
||||
|
||||
// Clear ongoing request after setting the promise (just in case it's already resolved).
|
||||
try {
|
||||
const response = await promise;
|
||||
|
||||
// We pass back a clone of the original object, this may prevent errors if in the callback the object is modified.
|
||||
return response;
|
||||
} finally {
|
||||
// Make sure we don't clear the promise of a newer request that ignores the cache.
|
||||
if (this.ongoingRequests[cacheId] === promise) {
|
||||
delete this.ongoingRequests[cacheId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a request to the server to get the public config of this site.
|
||||
*
|
||||
* @return Promise resolved with public config.
|
||||
*/
|
||||
protected async requestPublicConfig(): Promise<CoreSitePublicConfigResponse> {
|
||||
const preSets: CoreWSAjaxPreSets = {
|
||||
siteUrl: this.siteUrl,
|
||||
};
|
||||
|
|
|
@ -11,74 +11,79 @@
|
|||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<ion-list id="core-course-section-selector" role="listbox" aria-labelledby="core-course-section-selector-label">
|
||||
<ng-container *ngFor="let section of sectionsToRender">
|
||||
<ion-item *ngIf="allSectionId == section.id" class="divider core-course-index-all"
|
||||
(click)="selectSectionOrModule($event, section.id)" button [class.item-current]="selectedId === section.id" detail="false">
|
||||
<ion-label>
|
||||
<h2>
|
||||
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
|
||||
</core-format-text>
|
||||
</h2>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ng-container *ngIf="allSectionId != section.id">
|
||||
<ion-item class="divider section" (click)="selectSectionOrModule($event, section.id)" button
|
||||
[class.item-current]="selectedId === section.id" [class.item-dimmed]="!section.visible"
|
||||
[class.item-hightlighted]="section.highlighted" detail="false" sticky="true">
|
||||
<ion-icon *ngIf="section.hasVisibleModules" name="fas-chevron-right" flip-rtl slot="start"
|
||||
class="expandable-status-icon" (click)="toggleExpand($event, section)"
|
||||
[attr.aria-label]="(section.expanded ? 'core.collapse' : 'core.expand') | translate"
|
||||
[attr.aria-expanded]="section.expanded" [attr.aria-controls]="'core-course-index-section-' + section.id"
|
||||
[class.expandable-status-icon-expanded]="section.expanded">
|
||||
</ion-icon>
|
||||
<ion-icon *ngIf="!section.hasVisibleModules" name="" slot="start" aria-hidden="true" class="expandable-status-icon">
|
||||
</ion-icon>
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<ion-list *ngIf="loaded" id="core-course-section-selector" role="listbox" aria-labelledby="core-course-section-selector-label">
|
||||
<ng-container *ngFor="let section of sectionsToRender">
|
||||
<ion-item *ngIf="allSectionId == section.id" class="divider core-course-index-all"
|
||||
(click)="selectSectionOrModule($event, section.id)" button [class.item-current]="selectedId === section.id"
|
||||
detail="false">
|
||||
<ion-label>
|
||||
<h2>
|
||||
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
|
||||
</core-format-text>
|
||||
</h2>
|
||||
</ion-label>
|
||||
<ion-badge *ngIf="section.highlighted && highlighted" slot="end">{{highlighted}}</ion-badge>
|
||||
<ion-icon name="fas-lock" *ngIf="section.availabilityinfo" slot="end" class="restricted"
|
||||
[attr.aria-label]="'core.restricted' | translate"></ion-icon>
|
||||
<ion-icon name="fas-eye-slash" *ngIf="!section.visible && !section.uservisible" slot="end" class="restricted"
|
||||
[attr.aria-label]="'core.notavailable' | translate"></ion-icon>
|
||||
<ion-icon name="fas-eye-slash" *ngIf="!section.visible && section.uservisible" slot="end" class="restricted"
|
||||
[attr.aria-label]="'core.course.hiddenfromstudents' | translate"></ion-icon>
|
||||
</ion-item>
|
||||
<div id="core-course-index-section-{{section.id}}">
|
||||
<ng-container *ngIf="section.expanded">
|
||||
<ng-container *ngFor="let module of section.modules">
|
||||
<ion-item class="module" [class.item-dimmed]="!module.visible" [class.item-hightlighted]="section.highlighted"
|
||||
(click)="selectSectionOrModule($event, section.id, module.id)" button>
|
||||
<ion-icon class="completioninfo completion_none" name="" *ngIf="module.completionStatus === undefined"
|
||||
slot="start" aria-hidden="true"></ion-icon>
|
||||
<ion-icon class="completioninfo completion_incomplete" name="far-circle"
|
||||
*ngIf="module.completionStatus === 0" slot="start" [attr.aria-label]="'core.course.todo' | translate">
|
||||
</ion-icon>
|
||||
<ion-icon class="completioninfo completion_complete" name="fas-circle"
|
||||
*ngIf="module.completionStatus === 1 || module.completionStatus === 2" color="success" slot="start"
|
||||
[attr.aria-label]="'core.course.done' | translate">
|
||||
</ion-icon>
|
||||
<ion-icon class="completioninfo completion_fail" name="fas-circle" *ngIf="module.completionStatus === 3"
|
||||
color="danger" slot="start" [attr.aria-label]="'core.course.failed' | translate">
|
||||
</ion-icon>
|
||||
<ion-label>
|
||||
<p class="item-heading">
|
||||
<core-format-text [text]="module.name" contextLevel="module" [contextInstanceId]="module.id"
|
||||
[courseId]="module.course">
|
||||
</core-format-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
<ion-icon name="fas-lock" *ngIf="!module.uservisible" slot="end" class="restricted"
|
||||
[attr.aria-label]="'core.restricted' | translate"></ion-icon>
|
||||
</ion-item>
|
||||
<ng-container *ngIf="allSectionId != section.id">
|
||||
<ion-item class="divider section" (click)="selectSectionOrModule($event, section.id)" button
|
||||
[class.item-current]="selectedId === section.id" [class.item-dimmed]="!section.visible"
|
||||
[class.item-hightlighted]="section.highlighted" detail="false" sticky="true">
|
||||
<ion-icon *ngIf="section.hasVisibleModules" name="fas-chevron-right" flip-rtl slot="start"
|
||||
class="expandable-status-icon" (click)="toggleExpand($event, section)"
|
||||
[attr.aria-label]="(section.expanded ? 'core.collapse' : 'core.expand') | translate"
|
||||
[attr.aria-expanded]="section.expanded" [attr.aria-controls]="'core-course-index-section-' + section.id"
|
||||
[class.expandable-status-icon-expanded]="section.expanded">
|
||||
</ion-icon>
|
||||
<ion-icon *ngIf="!section.hasVisibleModules" name="" slot="start" aria-hidden="true" class="expandable-status-icon">
|
||||
</ion-icon>
|
||||
<ion-label>
|
||||
<h2>
|
||||
<core-format-text [text]="section.name" contextLevel="course" [contextInstanceId]="course?.id">
|
||||
</core-format-text>
|
||||
</h2>
|
||||
</ion-label>
|
||||
<ion-badge *ngIf="section.highlighted && highlighted" slot="end">{{highlighted}}</ion-badge>
|
||||
<ion-icon name="fas-lock" *ngIf="section.availabilityinfo" slot="end" class="restricted"
|
||||
[attr.aria-label]="'core.restricted' | translate"></ion-icon>
|
||||
<ion-icon name="fas-eye-slash" *ngIf="!section.visible && !section.uservisible" slot="end" class="restricted"
|
||||
[attr.aria-label]="'core.notavailable' | translate"></ion-icon>
|
||||
<ion-icon name="fas-eye-slash" *ngIf="!section.visible && section.uservisible" slot="end" class="restricted"
|
||||
[attr.aria-label]="'core.course.hiddenfromstudents' | translate"></ion-icon>
|
||||
</ion-item>
|
||||
<div id="core-course-index-section-{{section.id}}">
|
||||
<ng-container *ngIf="section.expanded">
|
||||
<ng-container *ngFor="let module of section.modules">
|
||||
<ion-item class="module" [class.item-dimmed]="!module.visible"
|
||||
[class.item-hightlighted]="section.highlighted"
|
||||
(click)="selectSectionOrModule($event, section.id, module.id)" button>
|
||||
<ion-icon class="completioninfo completion_none" name="" *ngIf="module.completionStatus === undefined"
|
||||
slot="start" aria-hidden="true"></ion-icon>
|
||||
<ion-icon class="completioninfo completion_incomplete" name="far-circle"
|
||||
*ngIf="module.completionStatus === 0" slot="start"
|
||||
[attr.aria-label]="'core.course.todo' | translate">
|
||||
</ion-icon>
|
||||
<ion-icon class="completioninfo completion_complete" name="fas-circle"
|
||||
*ngIf="module.completionStatus === 1 || module.completionStatus === 2" color="success" slot="start"
|
||||
[attr.aria-label]="'core.course.done' | translate">
|
||||
</ion-icon>
|
||||
<ion-icon class="completioninfo completion_fail" name="fas-circle" *ngIf="module.completionStatus === 3"
|
||||
color="danger" slot="start" [attr.aria-label]="'core.course.failed' | translate">
|
||||
</ion-icon>
|
||||
<ion-label>
|
||||
<p class="item-heading">
|
||||
<core-format-text [text]="module.name" contextLevel="module" [contextInstanceId]="module.id"
|
||||
[courseId]="module.course">
|
||||
</core-format-text>
|
||||
</p>
|
||||
</ion-label>
|
||||
<ion-icon name="fas-lock" *ngIf="!module.uservisible" slot="end" class="restricted"
|
||||
[attr.aria-label]="'core.restricted' | translate"></ion-icon>
|
||||
</ion-item>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ng-container>
|
||||
</ion-list>
|
||||
</ion-list>
|
||||
</core-loading>
|
||||
</ion-content>
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
import { CoreCourseHelper, CoreCourseSection } from '@features/course/services/course-helper';
|
||||
import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate';
|
||||
import { CoreCourseAnyCourseData } from '@features/courses/services/courses';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { ModalController } from '@singletons';
|
||||
import { CoreDom } from '@singletons/dom';
|
||||
|
||||
|
@ -41,6 +42,7 @@ export class CoreCourseCourseIndexComponent implements OnInit {
|
|||
allSectionId = CoreCourseProvider.ALL_SECTIONS_ID;
|
||||
highlighted?: string;
|
||||
sectionsToRender: CourseIndexSection[] = [];
|
||||
loaded = false;
|
||||
|
||||
constructor(
|
||||
protected elementRef: ElementRef,
|
||||
|
@ -109,6 +111,13 @@ export class CoreCourseCourseIndexComponent implements OnInit {
|
|||
|
||||
this.highlighted = CoreCourseFormatDelegate.getSectionHightlightedName(this.course);
|
||||
|
||||
// Wait a bit to render the data, otherwise the modal takes a while to appear in big courses or slow devices.
|
||||
await CoreUtils.wait(400);
|
||||
|
||||
this.loaded = true;
|
||||
|
||||
await CoreUtils.nextTick();
|
||||
|
||||
CoreDom.scrollToElement(
|
||||
this.elementRef.nativeElement,
|
||||
'.item.item-current',
|
||||
|
|
|
@ -130,7 +130,27 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
|
|||
* @return Promise resolved when ready.
|
||||
*/
|
||||
protected async initCordovaMediaPlugin(): Promise<void> {
|
||||
|
||||
try {
|
||||
await this.createFileAndMediaInstance();
|
||||
|
||||
this.readyToCapture = true;
|
||||
this.previewMedia = this.previewAudio?.nativeElement;
|
||||
} catch (error) {
|
||||
this.dismissWithError(-1, error.message || error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a file entry and the cordova media instance.
|
||||
*/
|
||||
protected async createFileAndMediaInstance(): Promise<void> {
|
||||
this.filePath = this.getFilePath();
|
||||
|
||||
// First create the file.
|
||||
this.fileEntry = await CoreFile.createFile(this.filePath);
|
||||
|
||||
// Now create the media instance.
|
||||
let absolutePath = CoreText.concatenatePaths(CoreFile.getBasePathInstant(), this.filePath);
|
||||
|
||||
if (Platform.is('ios')) {
|
||||
|
@ -138,17 +158,21 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
|
|||
absolutePath = absolutePath.replace(/^file:\/\//, '');
|
||||
}
|
||||
|
||||
try {
|
||||
// First create the file.
|
||||
this.fileEntry = await CoreFile.createFile(this.filePath);
|
||||
this.mediaFile = Media.create(absolutePath);
|
||||
}
|
||||
|
||||
// Now create the media instance.
|
||||
this.mediaFile = Media.create(absolutePath);
|
||||
this.readyToCapture = true;
|
||||
this.previewMedia = this.previewAudio?.nativeElement;
|
||||
} catch (error) {
|
||||
this.dismissWithError(-1, error.message || error);
|
||||
/**
|
||||
* Reset the file and the cordova media instance.
|
||||
*/
|
||||
protected async resetCordovaMediaCapture(): Promise<void> {
|
||||
if (this.filePath) {
|
||||
// Remove old file, don't block the user for this.
|
||||
CoreFile.removeFile(this.filePath);
|
||||
}
|
||||
|
||||
this.mediaFile?.release();
|
||||
|
||||
await this.createFileAndMediaInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -205,7 +229,8 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!this.streamVideo) {
|
||||
const streamVideo = this.streamVideo;
|
||||
if (!streamVideo) {
|
||||
throw new CoreError('Video element not found.');
|
||||
}
|
||||
|
||||
|
@ -221,7 +246,7 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
|
|||
}, 10000);
|
||||
|
||||
// Listen for stream ready to display the stream.
|
||||
this.streamVideo.nativeElement.onloadedmetadata = (): void => {
|
||||
streamVideo.nativeElement.onloadedmetadata = (): void => {
|
||||
if (hasLoaded) {
|
||||
// Already loaded or timeout triggered, stop.
|
||||
return;
|
||||
|
@ -230,19 +255,13 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
|
|||
hasLoaded = true;
|
||||
clearTimeout(waitTimeout);
|
||||
this.readyToCapture = true;
|
||||
this.streamVideo!.nativeElement.onloadedmetadata = null;
|
||||
streamVideo.nativeElement.onloadedmetadata = null;
|
||||
// Force change detection. Angular doesn't detect these async operations.
|
||||
this.changeDetectorRef.detectChanges();
|
||||
};
|
||||
|
||||
// Set the stream as the source of the video.
|
||||
if ('srcObject' in this.streamVideo.nativeElement) {
|
||||
this.streamVideo.nativeElement.srcObject = this.localMediaStream;
|
||||
} else {
|
||||
// Fallback for old browsers.
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject#Examples
|
||||
this.streamVideo.nativeElement.src = window.URL.createObjectURL(this.localMediaStream);
|
||||
}
|
||||
streamVideo.nativeElement.srcObject = this.localMediaStream;
|
||||
} catch (error) {
|
||||
this.dismissWithError(-1, error.message || error);
|
||||
}
|
||||
|
@ -375,7 +394,7 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
|
|||
this.imgCanvas.nativeElement.getContext('2d').drawImage(this.streamVideo?.nativeElement, 0, 0, width, height);
|
||||
|
||||
// Convert the image to blob and show it in an image element.
|
||||
this.imgCanvas.nativeElement.toBlob((blob) => {
|
||||
this.imgCanvas.nativeElement.toBlob((blob: Blob) => {
|
||||
loadingModal.dismiss();
|
||||
|
||||
this.mediaBlob = blob;
|
||||
|
@ -410,11 +429,15 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy {
|
|||
/**
|
||||
* Discard the captured media.
|
||||
*/
|
||||
discard(): void {
|
||||
async discard(): Promise<void> {
|
||||
this.previewMedia?.pause();
|
||||
this.streamVideo?.nativeElement.play();
|
||||
this.audioDrawer?.start();
|
||||
|
||||
if (this.isCordovaAudioCapture) {
|
||||
await this.resetCordovaMediaCapture();
|
||||
}
|
||||
|
||||
this.hasCaptured = false;
|
||||
this.isCapturing = false;
|
||||
this.resetChrono = true;
|
||||
|
|
|
@ -550,10 +550,8 @@ export class CoreFileUploaderHelperProvider {
|
|||
media = medias[0]; // We used limit 1, we only want 1 media.
|
||||
} catch (error) {
|
||||
|
||||
if (isAudio && this.isNoAppError(error) && CoreApp.isMobile() &&
|
||||
(!Platform.is('android') || CoreApp.getPlatformMajorVersion() < 10)) {
|
||||
if (isAudio && this.isNoAppError(error) && CoreApp.isMobile()) {
|
||||
// No app to record audio, fallback to capture it ourselves.
|
||||
// In Android it will only be done in Android 9 or lower because there's a bug in the plugin.
|
||||
try {
|
||||
media = await CoreFileUploader.captureAudioInApp();
|
||||
} catch (error) {
|
||||
|
|
|
@ -16,7 +16,7 @@ import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/co
|
|||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreLoginHelper } from '@features/login/services/login-helper';
|
||||
|
@ -132,7 +132,9 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
|
|||
* Get some data (like identity providers) from the site config.
|
||||
*/
|
||||
protected async checkSiteConfig(site: CoreSite): Promise<void> {
|
||||
this.siteConfig = await CoreUtils.ignoreErrors(site.getPublicConfig());
|
||||
this.siteConfig = await CoreUtils.ignoreErrors(site.getPublicConfig({
|
||||
readingStrategy: CoreSitesReadingStrategy.PREFER_NETWORK,
|
||||
}));
|
||||
|
||||
if (!this.siteConfig) {
|
||||
return;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreCronHandler } from '@services/cron';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { makeSingleton } from '@singletons';
|
||||
|
||||
|
@ -39,7 +39,9 @@ export class CoreLoginCronHandlerService implements CoreCronHandler {
|
|||
// Do not check twice in the same 10 minutes.
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const config = await CoreUtils.ignoreErrors(site.getPublicConfig());
|
||||
const config = await CoreUtils.ignoreErrors(site.getPublicConfig({
|
||||
readingStrategy: CoreSitesReadingStrategy.ONLY_NETWORK,
|
||||
}));
|
||||
|
||||
CoreUtils.ignoreErrors(CoreSites.checkApplication(config));
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ export class CoreMainMenuDeepLinkManager {
|
|||
if (!params?.course) {
|
||||
CoreCourseHelper.getAndOpenCourse(Number(coursePathMatches[1]), params);
|
||||
} else {
|
||||
CoreCourse.openCourse(params.course, params);
|
||||
CoreCourse.openCourse(params.course, navOptions);
|
||||
}
|
||||
} else {
|
||||
CoreNavigator.navigateToSitePath(path, {
|
||||
|
|
|
@ -28,7 +28,7 @@ import {
|
|||
import { CoreNavigator } from '@services/navigator';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
import { ModalController } from '@singletons';
|
||||
import { ModalController, Translate } from '@singletons';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
/**
|
||||
|
@ -172,6 +172,12 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
|
|||
* @param event Click event
|
||||
*/
|
||||
async logout(event: Event): Promise<void> {
|
||||
if (CoreNavigator.currentRouteCanBlockLeave()) {
|
||||
await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.removeAccountOnLogout) {
|
||||
// Ask confirm.
|
||||
const siteName = this.siteName ?
|
||||
|
@ -200,6 +206,12 @@ export class CoreMainMenuUserMenuComponent implements OnInit, OnDestroy {
|
|||
* @param event Click event
|
||||
*/
|
||||
async switchAccounts(event: Event): Promise<void> {
|
||||
if (CoreNavigator.currentRouteCanBlockLeave()) {
|
||||
await CoreDomUtils.showAlert(undefined, Translate.instant('core.cannotlogoutpageblocks'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const thisModal = await ModalController.getTop();
|
||||
|
||||
event.preventDefault();
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
"cannotconnecttrouble": "We're having trouble connecting to your site.",
|
||||
"cannotconnectverify": "<strong>Please check the address is correct.</strong>",
|
||||
"cannotdownloadfiles": "File downloading is disabled. Please contact your site administrator.",
|
||||
"cannotlogoutpageblocks": "Please save or discard your changes before continuing.",
|
||||
"cannotopeninapp": "This file may not work as expected on this device. Would you like to open it anyway?",
|
||||
"cannotopeninappdownload": "This file may not work as expected on this device. Would you like to download it anyway?",
|
||||
"captureaudio": "Record audio",
|
||||
|
|
|
@ -658,6 +658,15 @@ export class CoreNavigatorService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current route page can block leaving the route.
|
||||
*
|
||||
* @return Whether the current route page can block leaving the route.
|
||||
*/
|
||||
currentRouteCanBlockLeave(): boolean {
|
||||
return !!this.getCurrentRoute().snapshot.routeConfig?.canDeactivate?.length;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const CoreNavigator = makeSingleton(CoreNavigatorService);
|
||||
|
|
|
@ -897,7 +897,9 @@ export class CoreSitesProvider {
|
|||
*/
|
||||
protected async getPublicConfigAndCheckApplication(site: CoreSite): Promise<void> {
|
||||
try {
|
||||
const config = await site.getPublicConfig();
|
||||
const config = await site.getPublicConfig({
|
||||
readingStrategy: CoreSitesReadingStrategy.ONLY_NETWORK,
|
||||
});
|
||||
|
||||
await this.checkApplication(config);
|
||||
} catch {
|
||||
|
|
Loading…
Reference in New Issue