Merge pull request #2738 from dpalou/MOBILE-3742

Mobile 3742
main
Pau Ferrer Ocaña 2021-05-03 12:21:38 +02:00 committed by GitHub
commit 8a1497ed39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 433 additions and 372 deletions

View File

@ -51,16 +51,12 @@ export class AddonBadgesIssuedBadgePage implements OnInit {
* View loaded. * View loaded.
*/ */
ngOnInit(): void { ngOnInit(): void {
this.route.queryParams.subscribe(() => { this.courseId = CoreNavigator.getRouteNumberParam('courseId') || this.courseId; // Use 0 for site badges.
this.badgeLoaded = false; this.userId = CoreNavigator.getRouteNumberParam('userId') || CoreSites.getCurrentSite()!.getUserId();
this.badgeHash = CoreNavigator.getRouteParam('badgeHash') || '';
this.courseId = CoreNavigator.getRouteNumberParam('courseId') || this.courseId; // Use 0 for site badges. this.fetchIssuedBadge().finally(() => {
this.userId = CoreNavigator.getRouteNumberParam('userId') || CoreSites.getCurrentSite()!.getUserId(); this.badgeLoaded = true;
this.badgeHash = CoreNavigator.getRouteParam('badgeHash') || '';
this.fetchIssuedBadge().finally(() => {
this.badgeLoaded = true;
});
}); });
} }

View File

@ -24,7 +24,7 @@ export const AddonCalendarEditRoute: Route = {
}; };
export const AddonCalendarEventRoute: Route ={ export const AddonCalendarEventRoute: Route ={
path: 'event', path: 'event/:id',
loadChildren: () => import('@/addons/calendar/pages/event/event.module').then(m => m.AddonCalendarEventPageModule), loadChildren: () => import('@/addons/calendar/pages/event/event.module').then(m => m.AddonCalendarEventPageModule),
}; };

View File

@ -525,7 +525,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
// It's an offline event, go to the edit page. // It's an offline event, go to the edit page.
this.openEdit(eventId); this.openEdit(eventId);
} else { } else {
CoreNavigator.navigateToSitePath('/calendar/event', { params: { id: eventId } }); CoreNavigator.navigateToSitePath(`/calendar/event/${eventId}`);
} }
} }

View File

@ -155,22 +155,10 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
* View loaded. * View loaded.
*/ */
ngOnInit(): void { ngOnInit(): void {
this.route.queryParams.subscribe(() => { this.eventId = CoreNavigator.getRouteNumberParam('id')!;
this.eventLoaded = false; this.syncIcon = CoreConstants.ICON_LOADING;
const eventId = CoreNavigator.getRouteNumberParam('id'); this.fetchEvent();
if (!eventId) {
CoreDomUtils.showErrorModal('Event ID not supplied.');
CoreNavigator.back();
return;
}
this.eventId = eventId;
this.syncIcon = CoreConstants.ICON_LOADING;
this.fetchEvent();
});
} }
/** /**

View File

@ -46,7 +46,6 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
@ViewChild(AddonCalendarCalendarComponent) calendarComponent?: AddonCalendarCalendarComponent; @ViewChild(AddonCalendarCalendarComponent) calendarComponent?: AddonCalendarCalendarComponent;
@ViewChild(AddonCalendarUpcomingEventsComponent) upcomingEventsComponent?: AddonCalendarUpcomingEventsComponent; @ViewChild(AddonCalendarUpcomingEventsComponent) upcomingEventsComponent?: AddonCalendarUpcomingEventsComponent;
protected eventId?: number;
protected currentSiteId: string; protected currentSiteId: string;
// Observers. // Observers.
@ -167,8 +166,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
ngOnInit(): void { ngOnInit(): void {
this.notificationsEnabled = CoreLocalNotifications.isAvailable(); this.notificationsEnabled = CoreLocalNotifications.isAvailable();
this.route.queryParams.subscribe(() => { this.route.queryParams.subscribe(async () => {
this.eventId = CoreNavigator.getRouteNumberParam('eventId');
this.filter.courseId = CoreNavigator.getRouteNumberParam('courseId'); this.filter.courseId = CoreNavigator.getRouteNumberParam('courseId');
this.year = CoreNavigator.getRouteNumberParam('year'); this.year = CoreNavigator.getRouteNumberParam('year');
this.month = CoreNavigator.getRouteNumberParam('month'); this.month = CoreNavigator.getRouteNumberParam('month');
@ -176,11 +174,6 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
this.showCalendar = !this.loadUpcoming; this.showCalendar = !this.loadUpcoming;
this.filter.filtered = !!this.filter.courseId; this.filter.filtered = !!this.filter.courseId;
if (this.eventId) {
// There is an event to load, open the event in a new state.
this.gotoEvent(this.eventId);
}
this.fetchData(true, false); this.fetchData(true, false);
}); });
} }
@ -311,7 +304,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy {
// It's an offline event, go to the edit page. // It's an offline event, go to the edit page.
this.openEdit(eventId); this.openEdit(eventId);
} else { } else {
CoreNavigator.navigateToSitePath('/calendar/event', { params: { id: eventId } }); CoreNavigator.navigateToSitePath(`/calendar/event/${eventId}`);
} }
} }

View File

@ -244,14 +244,7 @@ export class AddonCalendarListPage implements OnInit, OnDestroy {
* View loaded. * View loaded.
*/ */
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
this.eventId = CoreNavigator.getRouteNumberParam('eventId');
this.filter.courseId = CoreNavigator.getRouteNumberParam('courseId') || -1; this.filter.courseId = CoreNavigator.getRouteNumberParam('courseId') || -1;
if (this.eventId) {
// There is an event to load, open the event in a new state.
this.gotoEvent(this.eventId);
}
this.syncIcon = CoreConstants.ICON_LOADING; this.syncIcon = CoreConstants.ICON_LOADING;
await this.fetchData(false, true, false); await this.fetchData(false, true, false);

View File

@ -373,7 +373,14 @@ export class AddonCalendarProvider {
CoreNavigator.navigateToSitePath( CoreNavigator.navigateToSitePath(
pageName, pageName,
{ params: { eventId: notification.eventId }, siteId: notification.siteId }, {
siteId: notification.siteId,
preferCurrentTab: false,
nextNavigation: {
path: `calendar/event/${notification.eventId}`,
isSitePath: true,
},
},
); );
} }
}, },

View File

@ -58,8 +58,11 @@ export class AddonCalendarViewLinkHandlerService extends CoreContentLinksHandler
stateParams.year = date.getFullYear(); stateParams.year = date.getFullYear();
stateParams.month = date.getMonth() + 1; stateParams.month = date.getMonth() + 1;
// @todo: Add checkMenu param. CoreNavigator.navigateToSitePath('/calendar/index', {
CoreNavigator.navigateToSitePath('/calendar/index', { params: stateParams, siteId }); params: stateParams,
siteId,
preferCurrentTab: false,
});
} else if (params.view == 'day') { } else if (params.view == 'day') {
// Daily view, open the page. // Daily view, open the page.
@ -82,8 +85,11 @@ export class AddonCalendarViewLinkHandlerService extends CoreContentLinksHandler
upcoming: true, upcoming: true,
}; };
// @todo: Add checkMenu param. CoreNavigator.navigateToSitePath('/calendar/index', {
CoreNavigator.navigateToSitePath('/calendar/index', { params: stateParams, siteId }); params: stateParams,
siteId,
preferCurrentTab: false,
});
} }
}, },

View File

@ -12,7 +12,7 @@
// 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 { Component, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { IonRefresher } from '@ionic/angular'; import { IonRefresher } from '@ionic/angular';
import { import {
AddonMessagesConversationFormatted, AddonMessagesConversationFormatted,
@ -22,7 +22,6 @@ import {
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { ModalController } from '@singletons'; import { ModalController } from '@singletons';
import { CoreNavigator } from '@services/navigator';
/** /**
* Component that displays the list of conversations, including group conversations. * Component that displays the list of conversations, including group conversations.
@ -33,14 +32,14 @@ import { CoreNavigator } from '@services/navigator';
}) })
export class AddonMessagesConversationInfoComponent implements OnInit { export class AddonMessagesConversationInfoComponent implements OnInit {
@Input() conversationId = 0;
loaded = false; loaded = false;
conversation?: AddonMessagesConversationFormatted; conversation?: AddonMessagesConversationFormatted;
members: AddonMessagesConversationMember[] = []; members: AddonMessagesConversationMember[] = [];
canLoadMore = false; canLoadMore = false;
loadMoreError = false; loadMoreError = false;
protected conversationId!: number;
constructor( constructor(
protected route: ActivatedRoute, protected route: ActivatedRoute,
) { ) {
@ -50,13 +49,8 @@ export class AddonMessagesConversationInfoComponent implements OnInit {
* Component loaded. * Component loaded.
*/ */
ngOnInit(): void { ngOnInit(): void {
this.route.queryParams.subscribe(async () => { this.fetchData().finally(() => {
this.conversationId = CoreNavigator.getRouteNumberParam('conversationId') || 0; this.loaded = true;
this.loaded = false;
this.fetchData().finally(() => {
this.loaded = true;
});
}); });
} }

View File

@ -84,42 +84,40 @@ export class AddonMessagesContacts35Page implements OnInit, OnDestroy {
/** /**
* Component loaded. * Component loaded.
*/ */
ngOnInit(): void { async ngOnInit(): Promise<void> {
this.route.queryParams.subscribe(async () => { const discussionUserId = CoreNavigator.getRouteNumberParam('discussionUserId') ||
const discussionUserId = CoreNavigator.getRouteNumberParam('discussionUserId') || CoreNavigator.getRouteNumberParam('userId') || undefined;
CoreNavigator.getRouteNumberParam('userId') || undefined;
if (this.loaded && this.discussionUserId == discussionUserId) { if (this.loaded && this.discussionUserId == discussionUserId) {
return; return;
} }
this.discussionUserId = discussionUserId; this.discussionUserId = discussionUserId;
if (this.discussionUserId) { if (this.discussionUserId) {
// There is a discussion to load, open the discussion in a new state. // There is a discussion to load, open the discussion in a new state.
this.gotoDiscussion(this.discussionUserId); this.gotoDiscussion(this.discussionUserId);
} }
try { try {
await this.fetchData(); await this.fetchData();
if (!this.discussionUserId && this.hasContacts && CoreScreen.isTablet) { if (!this.discussionUserId && this.hasContacts && CoreScreen.isTablet) {
let contact: AddonMessagesGetContactsContact | undefined; let contact: AddonMessagesGetContactsContact | undefined;
for (const x in this.contacts) { for (const x in this.contacts) {
if (this.contacts[x].length > 0) { if (this.contacts[x].length > 0) {
contact = this.contacts[x][0]; contact = this.contacts[x][0];
break; break;
}
}
if (contact) {
// Take first and load it.
this.gotoDiscussion(contact.id);
} }
} }
} finally {
this.loaded = true; if (contact) {
// Take first and load it.
this.gotoDiscussion(contact.id);
}
} }
}); } finally {
this.loaded = true;
}
} }
/** /**

View File

@ -136,29 +136,18 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
/** /**
* Component loaded. * Component loaded.
*/ */
ngOnInit(): void { async ngOnInit(): Promise<void> {
this.route.queryParams.subscribe(async (params) => { this.route.queryParams.subscribe(async (params) => {
const discussionUserId = CoreNavigator.getRouteNumberParam('discussionUserId', { params }) || // When a child page loads this callback is triggered too.
CoreNavigator.getRouteNumberParam('userId', { params }) || undefined; this.discussionUserId = CoreNavigator.getRouteNumberParam('userId', { params }) ?? this.discussionUserId;
if (this.loaded && this.discussionUserId == discussionUserId) {
return;
}
this.discussionUserId = discussionUserId;
if (this.discussionUserId) {
// There is a discussion to load, open the discussion in a new state.
this.gotoDiscussion(this.discussionUserId);
}
await this.fetchData();
if (!this.discussionUserId && this.discussions.length > 0 && CoreScreen.isTablet) {
// Take first and load it.
this.gotoDiscussion(this.discussions[0].message!.user);
}
}); });
await this.fetchData();
if (!this.discussionUserId && this.discussions.length > 0 && CoreScreen.isTablet) {
// Take first and load it.
this.gotoDiscussion(this.discussions[0].message!.user);
}
} }
/** /**

View File

@ -87,8 +87,6 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
protected siteId: string; protected siteId: string;
protected currentUserId: number; protected currentUserId: number;
protected conversationId?: number;
protected discussionUserId?: number;
protected newMessagesObserver: CoreEventObserver; protected newMessagesObserver: CoreEventObserver;
protected pushObserver: Subscription; protected pushObserver: Subscription;
protected appResumeSubscription: Subscription; protected appResumeSubscription: Subscription;
@ -270,34 +268,30 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
/** /**
* Component loaded. * Component loaded.
*/ */
ngOnInit(): void { async ngOnInit(): Promise<void> {
this.route.queryParams.subscribe(async (params) => { this.route.queryParams.subscribe(async (params) => {
// Conversation to load. // When a child page loads this callback is triggered too.
this.conversationId = CoreNavigator.getRouteNumberParam('conversationId', { params }) || undefined; this.selectedConversationId =
if (!this.conversationId) { CoreNavigator.getRouteNumberParam('conversationId', { params }) ?? this.selectedConversationId;
this.discussionUserId = CoreNavigator.getRouteNumberParam('discussionUserId', { params }) || undefined; this.selectedUserId =
} CoreNavigator.getRouteNumberParam('userId', { params }) ?? this.selectedUserId;
});
if (this.conversationId || this.discussionUserId) { await this.fetchData();
// There is a discussion to load, open the discussion in a new state.
this.gotoConversation(this.conversationId, this.discussionUserId);
}
await this.fetchData(); if (!this.selectedConversationId && !this.selectedUserId && CoreScreen.isTablet) {
if (!this.conversationId && !this.discussionUserId && CoreScreen.isTablet) { // Load the first conversation.
// Load the first conversation. let conversation: AddonMessagesConversationForList;
let conversation: AddonMessagesConversationForList; const expandedOption = this.getExpandedOption();
const expandedOption = this.getExpandedOption();
if (expandedOption && expandedOption.conversations.length) { if (expandedOption && expandedOption.conversations.length) {
conversation = expandedOption.conversations[0]; conversation = expandedOption.conversations[0];
if (conversation) { if (conversation) {
this.gotoConversation(conversation.id); this.gotoConversation(conversation.id);
}
} }
} }
}); }
} }
/** /**
@ -322,7 +316,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
await Promise.all(promises); await Promise.all(promises);
// The expanded status hasn't been initialized. Do it now. // The expanded status hasn't been initialized. Do it now.
if (typeof this.favourites.expanded == 'undefined' && this.conversationId || this.discussionUserId) { if (typeof this.favourites.expanded == 'undefined' && (this.selectedConversationId || this.selectedUserId)) {
// A certain conversation should be opened. // A certain conversation should be opened.
// We don't know which option it belongs to, so we need to fetch the data for all of them. // We don't know which option it belongs to, so we need to fetch the data for all of them.
const promises: Promise<void>[] = []; const promises: Promise<void>[] = [];
@ -333,7 +327,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
await Promise.all(promises); await Promise.all(promises);
// All conversations have been loaded, find the one we need to load and expand its option. // All conversations have been loaded, find the one we need to load and expand its option.
const conversation = this.findConversation(this.conversationId, this.discussionUserId); const conversation = this.findConversation(this.selectedConversationId, this.selectedUserId);
if (conversation) { if (conversation) {
const option = this.getConversationOption(conversation); const option = this.getConversationOption(conversation);

View File

@ -39,7 +39,10 @@ export class AddonMessagesIndexLinkHandlerService extends CoreContentLinksHandle
action: async (siteId): Promise<void> => { action: async (siteId): Promise<void> => {
const pageName = await AddonMessages.getMainMessagesPagePathInSite(siteId); const pageName = await AddonMessages.getMainMessagesPagePathInSite(siteId);
CoreNavigator.navigateToSitePath(pageName, { siteId }); CoreNavigator.navigateToSitePath(pageName, {
siteId,
preferCurrentTab: false,
});
}, },
}]; }];
} }

View File

@ -63,16 +63,29 @@ export class AddonMessagesPushClickHandlerService implements CorePushNotificatio
const enabled = await AddonMessages.isGroupMessagingEnabledInSite(notification.site); const enabled = await AddonMessages.isGroupMessagingEnabledInSite(notification.site);
const pageName = await AddonMessages.getMainMessagesPagePathInSite(notification.site); const pageName = await AddonMessages.getMainMessagesPagePathInSite(notification.site);
const pageParams: Params = {}; let nextPageParams: Params | undefined;
// Check if we have enough information to open the conversation. // Check if we have enough information to open the conversation.
if (notification.convid && enabled) { if (notification.convid && enabled) {
pageParams.conversationId = Number(notification.convid); nextPageParams = {
conversationId: Number(notification.convid),
};
} else if (notification.userfromid) { } else if (notification.userfromid) {
pageParams.discussionUserId = Number(notification.userfromid); nextPageParams = {
userId: Number(notification.userfromid),
};
} }
await CoreNavigator.navigateToSitePath(pageName, { params: pageParams, siteId: notification.site }); await CoreNavigator.navigateToSitePath(pageName, {
siteId: notification.site,
preferCurrentTab: false,
nextNavigation: nextPageParams ?
{
path: 'discussion',
options: { params: nextPageParams },
} :
undefined,
});
} }
} }

View File

@ -125,7 +125,10 @@ export class AddonNotificationsPushClickHandlerService implements CorePushNotifi
await CoreNavigator.navigateToSitePath( await CoreNavigator.navigateToSitePath(
AddonNotificationsMainMenuHandlerService.PAGE_NAME, AddonNotificationsMainMenuHandlerService.PAGE_NAME,
{ siteId: notification.site }, {
siteId: notification.site,
preferCurrentTab: false,
},
); );
} }

View File

@ -26,7 +26,6 @@ import {
} from '@angular/router'; } from '@angular/router';
import { CoreArray } from '@singletons/array'; import { CoreArray } from '@singletons/array';
import { CoreRedirectGuard } from '@guards/redirect';
/** /**
* Build app routes. * Build app routes.
@ -35,16 +34,7 @@ import { CoreRedirectGuard } from '@guards/redirect';
* @return App routes. * @return App routes.
*/ */
function buildAppRoutes(injector: Injector): Routes { function buildAppRoutes(injector: Injector): Routes {
const appRoutes = CoreArray.flatten(injector.get<Routes[]>(APP_ROUTES, [])); return CoreArray.flatten(injector.get<Routes[]>(APP_ROUTES, []));
return appRoutes.map(route => {
route.canLoad = route.canLoad ?? [];
route.canActivate = route.canActivate ?? [];
route.canLoad.push(CoreRedirectGuard);
route.canActivate.push(CoreRedirectGuard);
return route;
});
} }
/** /**

View File

@ -15,6 +15,7 @@
import { BehaviorSubject, Subject } from 'rxjs'; import { BehaviorSubject, Subject } from 'rxjs';
import { CoreEvents } from '@singletons/events'; import { CoreEvents } from '@singletons/events';
import { CoreDelegate, CoreDelegateDisplayHandler, CoreDelegateToDisplay } from './delegate'; import { CoreDelegate, CoreDelegateDisplayHandler, CoreDelegateToDisplay } from './delegate';
import { CoreUtils } from '@services/utils/utils';
/** /**
* Superclass to help creating sorted delegates. * Superclass to help creating sorted delegates.
@ -76,6 +77,30 @@ export class CoreSortedDelegate<
return this.sortedHandlersRxJs; return this.sortedHandlersRxJs;
} }
/**
* Get the handlers for the current site once they're loaded.
*
* @return Promise resolved with the handlers.
*/
async getHandlersWhenLoaded(): Promise<DisplayType[]> {
if (this.loaded) {
return this.sortedHandlers;
}
const deferred = CoreUtils.promiseDefer<DisplayType[]>();
const subscription = this.getHandlersObservable().subscribe((handlers) => {
if (this.loaded) {
subscription?.unsubscribe();
// Return main handlers.
deferred.resolve(handlers);
}
});
return deferred.promise;
}
/** /**
* Update handlers Data. * Update handlers Data.
*/ */

View File

@ -4,6 +4,11 @@
<ion-back-button [text]="'core.back' | translate"></ion-back-button> <ion-back-button [text]="'core.back' | translate"></ion-back-button>
</ion-buttons> </ion-buttons>
<ion-title>{{ 'core.contentlinks.chooseaccount' | translate }}</ion-title> <ion-title>{{ 'core.contentlinks.chooseaccount' | translate }}</ion-title>
<ion-buttons slot="end">
<ion-button (click)="closeModal()" [attr.aria-label]="'core.close' | translate">
<ion-icon slot="icon-only" name="fas-times" aria-hidden="true"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content> <ion-content>
@ -27,11 +32,6 @@
<p>{{site.siteUrl}}</p> <p>{{site.siteUrl}}</p>
</ion-label> </ion-label>
</ion-item> </ion-item>
<ion-item>
<ion-label>
<ion-button expand="block" (click)="cancel()">{{ 'core.login.cancel' | translate }}</ion-button>
</ion-label>
</ion-item>
</ion-list> </ion-list>
</core-loading> </core-loading>
</ion-content> </ion-content>

View File

@ -12,10 +12,10 @@
// 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 { Component, OnInit } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { CoreSiteBasicInfo, CoreSites } from '@services/sites'; import { CoreSiteBasicInfo, CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { Translate } from '@singletons'; import { ModalController, Translate } from '@singletons';
import { CoreContentLinksAction } from '../../services/contentlinks-delegate'; import { CoreContentLinksAction } from '../../services/contentlinks-delegate';
import { CoreContentLinksHelper } from '../../services/contentlinks-helper'; import { CoreContentLinksHelper } from '../../services/contentlinks-helper';
import { CoreError } from '@classes/errors/error'; import { CoreError } from '@classes/errors/error';
@ -27,12 +27,13 @@ import { CoreNavigator } from '@services/navigator';
* @todo Include routing and testing. * @todo Include routing and testing.
*/ */
@Component({ @Component({
selector: 'page-core-content-links-choose-site', selector: 'core-content-links-choose-site-modal',
templateUrl: 'choose-site.html', templateUrl: 'choose-site-modal.html',
}) })
export class CoreContentLinksChooseSitePage implements OnInit { export class CoreContentLinksChooseSiteModalComponent implements OnInit {
@Input() url!: string;
url!: string;
sites: CoreSiteBasicInfo[] = []; sites: CoreSiteBasicInfo[] = [];
loaded = false; loaded = false;
protected action?: CoreContentLinksAction; protected action?: CoreContentLinksAction;
@ -42,12 +43,10 @@ export class CoreContentLinksChooseSitePage implements OnInit {
* Component being initialized. * Component being initialized.
*/ */
async ngOnInit(): Promise<void> { async ngOnInit(): Promise<void> {
const url = CoreNavigator.getRouteParam<string>('url'); if (!this.url) {
if (!url) { return this.closeModal();
return this.leaveView();
} }
this.url = url;
let siteIds: string[] | undefined = []; let siteIds: string[] | undefined = [];
try { try {
@ -75,25 +74,20 @@ export class CoreContentLinksChooseSitePage implements OnInit {
this.sites = await CoreSites.getSites(siteIds); this.sites = await CoreSites.getSites(siteIds);
} catch (error) { } catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'core.contentlinks.errornosites', true); CoreDomUtils.showErrorModalDefault(error, 'core.contentlinks.errornosites', true);
this.leaveView(); this.closeModal();
} }
this.loaded = true; this.loaded = true;
} }
/**
* Cancel.
*/
cancel(): void {
this.leaveView();
}
/** /**
* Perform the action on a certain site. * Perform the action on a certain site.
* *
* @param siteId Site ID. * @param siteId Site ID.
*/ */
siteClicked(siteId: string): void { async siteClicked(siteId: string): Promise<void> {
await ModalController.dismiss();
if (this.isRootURL) { if (this.isRootURL) {
CoreNavigator.navigateToSiteHome({ siteId }); CoreNavigator.navigateToSiteHome({ siteId });
} else if (this.action) { } else if (this.action) {
@ -102,14 +96,10 @@ export class CoreContentLinksChooseSitePage implements OnInit {
} }
/** /**
* Cancel and leave the view. * Close the modal.
*/ */
protected async leaveView(): Promise<void> { closeModal(): void {
try { ModalController.dismiss();
await CoreSites.logout();
} finally {
await CoreNavigator.navigate('/login/sites', { reset: true });
}
} }
} }

View File

@ -12,26 +12,19 @@
// 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 { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CoreSharedModule } from '@/core/shared.module'; import { CoreSharedModule } from '@/core/shared.module';
import { CoreContentLinksChooseSitePage } from './choose-site'; import { NgModule } from '@angular/core';
import { CoreContentLinksChooseSiteModalComponent } from './choose-site-modal/choose-site-modal';
const routes: Routes = [
{
path: '',
component: CoreContentLinksChooseSitePage,
},
];
@NgModule({ @NgModule({
declarations: [ declarations: [
CoreContentLinksChooseSitePage, CoreContentLinksChooseSiteModalComponent,
], ],
imports: [ imports: [
RouterModule.forChild(routes),
CoreSharedModule, CoreSharedModule,
], ],
exports: [
CoreContentLinksChooseSiteModalComponent,
],
}) })
export class CoreContentLinksChooseSitePageModule {} export class CoreContentLinksComponentsModule {}

View File

@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
import { NgModule, Type } from '@angular/core'; import { NgModule, Type } from '@angular/core';
import { CoreContentLinksComponentsModule } from './components/components.module';
import { CoreContentLinksDelegateService } from './services/contentlinks-delegate'; import { CoreContentLinksDelegateService } from './services/contentlinks-delegate';
import { CoreContentLinksHelperProvider } from './services/contentlinks-helper'; import { CoreContentLinksHelperProvider } from './services/contentlinks-helper';
@ -22,5 +22,9 @@ export const CORE_CONTENTLINKS_SERVICES: Type<unknown>[] = [
CoreContentLinksHelperProvider, CoreContentLinksHelperProvider,
]; ];
@NgModule({}) @NgModule({
imports: [
CoreContentLinksComponentsModule,
],
})
export class CoreContentLinksModule {} export class CoreContentLinksModule {}

View File

@ -17,9 +17,10 @@ import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { CoreContentLinksDelegate, CoreContentLinksAction } from './contentlinks-delegate'; import { CoreContentLinksDelegate, CoreContentLinksAction } from './contentlinks-delegate';
import { CoreSite } from '@classes/site'; import { CoreSite } from '@classes/site';
import { makeSingleton, Translate } from '@singletons'; import { makeSingleton, ModalController, Translate } from '@singletons';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigator } from '@services/navigator';
import { Params } from '@angular/router'; import { Params } from '@angular/router';
import { CoreContentLinksChooseSiteModalComponent } from '../components/choose-site-modal/choose-site-modal';
/** /**
* Service that provides some features regarding content links. * Service that provides some features regarding content links.
@ -56,7 +57,7 @@ export class CoreContentLinksHelperProvider {
} }
/** /**
* Get the first valid action in the list of possible actions to do for a URL. * Get the first valid action for a URL.
* *
* @param url URL to handle. * @param url URL to handle.
* @param courseId Course ID related to the URL. Optional but recommended. * @param courseId Course ID related to the URL. Optional but recommended.
@ -75,6 +76,16 @@ export class CoreContentLinksHelperProvider {
return; return;
} }
return this.getFirstValidAction(actions);
}
/**
* Get the first valid action in a list of possible actions.
*
* @param actions Actions.
* @return First valid action if any.
*/
getFirstValidAction(actions: CoreContentLinksAction[]): CoreContentLinksAction | undefined {
return actions.find((action) => action && action.sites && action.sites.length); return actions.find((action) => action && action.sites && action.sites.length);
} }
@ -101,7 +112,15 @@ export class CoreContentLinksHelperProvider {
* @todo set correct root. * @todo set correct root.
*/ */
async goToChooseSite(url: string): Promise<void> { async goToChooseSite(url: string): Promise<void> {
await CoreNavigator.navigate('CoreContentLinksChooseSitePage @todo', { params: { url }, reset: true }); const modal = await ModalController.create({
component: CoreContentLinksChooseSiteModalComponent,
componentProps: {
url: url,
},
cssClass: 'core-modal-fullscreen',
});
await modal.present();
} }
/** /**

View File

@ -311,11 +311,8 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy {
this.sectionChanged(sections[0]); this.sectionChanged(sections[0]);
} else if (this.initialSectionId || this.initialSectionNumber) { } else if (this.initialSectionId || this.initialSectionNumber) {
// We have an input indicating the section ID to load. Search the section. // We have an input indicating the section ID to load. Search the section.
const section = sections.find((section) => { const section = sections.find((section) =>
if (section.id != this.initialSectionId && (!section.section || section.section != this.initialSectionNumber)) { section.id == this.initialSectionId || (section.section && section.section == this.initialSectionNumber));
return false;
}
});
// Don't load the section if it cannot be viewed by the user. // Don't load the section if it cannot be viewed by the user.
if (section && this.canViewSection(section)) { if (section && this.canViewSection(section)) {

View File

@ -11,5 +11,5 @@
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content> <ion-content>
<core-tabs-outlet [tabs]="tabs" [hideUntil]="loaded"></core-tabs-outlet> <core-tabs-outlet [tabs]="tabs" [hideUntil]="loaded" (ionChange)="tabSelected()"></core-tabs-outlet>
</ion-content> </ion-content>

View File

@ -46,6 +46,8 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
protected currentPagePath = ''; protected currentPagePath = '';
protected selectTabObserver: CoreEventObserver; protected selectTabObserver: CoreEventObserver;
protected firstTabName?: string; protected firstTabName?: string;
protected module?: CoreCourseWSModule;
protected modParams?: Params;
protected contentsTab: CoreTabsOutletTab = { protected contentsTab: CoreTabsOutletTab = {
page: CONTENTS_PAGE_NAME, page: CONTENTS_PAGE_NAME,
title: 'core.course.contents', title: 'core.course.contents',
@ -82,8 +84,8 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
// Get params. // Get params.
this.course = CoreNavigator.getRouteParam('course'); this.course = CoreNavigator.getRouteParam('course');
this.firstTabName = CoreNavigator.getRouteParam('selectedTab'); this.firstTabName = CoreNavigator.getRouteParam('selectedTab');
const module = CoreNavigator.getRouteParam<CoreCourseWSModule>('module'); this.module = CoreNavigator.getRouteParam<CoreCourseWSModule>('module');
const modParams = CoreNavigator.getRouteParam<Params>('modParams'); this.modParams = CoreNavigator.getRouteParam<Params>('modParams');
this.currentPagePath = CoreNavigator.getCurrentPath(); this.currentPagePath = CoreNavigator.getCurrentPath();
this.contentsTab.page = CoreTextUtils.concatenatePaths(this.currentPagePath, this.contentsTab.page); this.contentsTab.page = CoreTextUtils.concatenatePaths(this.currentPagePath, this.contentsTab.page);
@ -93,9 +95,8 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
sectionNumber: CoreNavigator.getRouteNumberParam('sectionNumber'), sectionNumber: CoreNavigator.getRouteNumberParam('sectionNumber'),
}; };
if (module) { if (this.module) {
this.contentsTab.pageParams!.moduleId = module.id; this.contentsTab.pageParams!.moduleId = this.module.id;
CoreCourseHelper.openModule(module, this.course!.id, this.contentsTab.pageParams!.sectionId, modParams);
} }
this.tabs.push(this.contentsTab); this.tabs.push(this.contentsTab);
@ -107,6 +108,18 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
]); ]);
} }
/**
* A tab was selected.
*/
tabSelected(): void {
if (this.module) {
// Now that the first tab has been selected we can load the module.
CoreCourseHelper.openModule(this.module, this.course!.id, this.contentsTab.pageParams!.sectionId, this.modParams);
delete this.module;
}
}
/** /**
* Load course option handlers. * Load course option handlers.
* *

View File

@ -36,7 +36,10 @@ export class CoreGradesOverviewLinkHandlerService extends CoreContentLinksHandle
getActions(): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> { getActions(): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> {
return [{ return [{
action: siteId => { action: siteId => {
CoreNavigator.navigateToSitePath('/grades', { siteId }); CoreNavigator.navigateToSitePath('/grades', {
siteId,
preferCurrentTab: false,
});
}, },
}]; }];
} }

View File

@ -17,6 +17,7 @@ import { Routes } from '@angular/router';
import { AppRoutingModule } from '@/app/app-routing.module'; import { AppRoutingModule } from '@/app/app-routing.module';
import { CoreLoginHelperProvider } from './services/login-helper'; import { CoreLoginHelperProvider } from './services/login-helper';
import { CoreRedirectGuard } from '@guards/redirect';
export const CORE_LOGIN_SERVICES = [ export const CORE_LOGIN_SERVICES = [
CoreLoginHelperProvider, CoreLoginHelperProvider,
@ -26,6 +27,8 @@ const appRoutes: Routes = [
{ {
path: 'login', path: 'login',
loadChildren: () => import('./login-lazy.module').then(m => m.CoreLoginLazyModule), loadChildren: () => import('./login-lazy.module').then(m => m.CoreLoginLazyModule),
canActivate: [CoreRedirectGuard],
canLoad: [CoreRedirectGuard],
}, },
]; ];

View File

@ -13,7 +13,6 @@
// limitations under the License. // limitations under the License.
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
import { Params } from '@angular/router';
import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { CoreApp } from '@services/app'; import { CoreApp } from '@services/app';
@ -24,7 +23,7 @@ import { CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/site'; import { CoreSiteIdentityProvider, CoreSitePublicConfigResponse } from '@classes/site';
import { CoreEvents } from '@singletons/events'; import { CoreEvents } from '@singletons/events';
import { CoreError } from '@classes/errors/error'; import { CoreError } from '@classes/errors/error';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigationOptions, CoreNavigator, CoreRedirectPayload } from '@services/navigator';
import { CoreForms } from '@singletons/form'; import { CoreForms } from '@singletons/form';
/** /**
@ -55,7 +54,7 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
showScanQR = false; showScanQR = false;
protected page?: string; protected page?: string;
protected pageParams?: Params; protected pageOptions?: CoreNavigationOptions;
protected siteConfig?: CoreSitePublicConfigResponse; protected siteConfig?: CoreSitePublicConfigResponse;
protected viewLeft = false; protected viewLeft = false;
protected eventThrown = false; protected eventThrown = false;
@ -83,7 +82,7 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
this.siteUrl = siteId; this.siteUrl = siteId;
this.page = CoreNavigator.getRouteParam('pageName'); this.page = CoreNavigator.getRouteParam('pageName');
this.pageParams = CoreNavigator.getRouteParam('pageParams'); this.pageOptions = CoreNavigator.getRouteParam('pageOptions');
this.showScanQR = CoreLoginHelper.displayQRInSiteScreen() || CoreLoginHelper.displayQRInCredentialsScreen(); this.showScanQR = CoreLoginHelper.displayQRInSiteScreen() || CoreLoginHelper.displayQRInCredentialsScreen();
try { try {
@ -214,8 +213,8 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
await CoreNavigator.navigateToSiteHome({ await CoreNavigator.navigateToSiteHome({
params: { params: {
redirectPath: this.page, redirectPath: this.page,
redirectParams: this.pageParams, redirectOptions: this.pageOptions,
}, } as CoreRedirectPayload,
}); });
} catch (error) { } catch (error) {
CoreLoginHelper.treatUserTokenError(this.siteUrl, error, this.username, password); CoreLoginHelper.treatUserTokenError(this.siteUrl, error, this.username, password);
@ -244,7 +243,15 @@ export class CoreLoginReconnectPage implements OnInit, OnDestroy {
* @param provider The provider that was clicked. * @param provider The provider that was clicked.
*/ */
oauthClicked(provider: CoreSiteIdentityProvider): void { oauthClicked(provider: CoreSiteIdentityProvider): void {
if (!CoreLoginHelper.openBrowserForOAuthLogin(this.siteUrl, provider, this.siteConfig?.launchurl)) { const result = CoreLoginHelper.openBrowserForOAuthLogin(
this.siteUrl,
provider,
this.siteConfig?.launchurl,
this.page,
this.pageOptions,
);
if (!result) {
CoreDomUtils.showErrorModal('Invalid data.'); CoreDomUtils.showErrorModal('Invalid data.');
} }
} }

View File

@ -32,7 +32,7 @@ import { CoreWSError } from '@classes/errors/wserror';
import { makeSingleton, Translate } from '@singletons'; import { makeSingleton, Translate } from '@singletons';
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
import { CoreUrl } from '@singletons/url'; import { CoreUrl } from '@singletons/url';
import { CoreNavigator } from '@services/navigator'; import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
import { CoreCanceledError } from '@classes/errors/cancelederror'; import { CoreCanceledError } from '@classes/errors/cancelederror';
import { CoreCustomURLSchemes } from '@services/urlschemes'; import { CoreCustomURLSchemes } from '@services/urlschemes';
@ -461,7 +461,7 @@ export class CoreLoginHelperProvider {
...options, ...options,
params: { params: {
redirectPath: page, redirectPath: page,
redirectParams: params, redirectOptions: { params },
urlToOpen: url, urlToOpen: url,
}, },
}); });
@ -549,10 +549,10 @@ export class CoreLoginHelperProvider {
* Check if current site is logged out, triggering mmCoreEventSessionExpired if it is. * Check if current site is logged out, triggering mmCoreEventSessionExpired if it is.
* *
* @param pageName Name of the page to go once authenticated if logged out. If not defined, site initial page. * @param pageName Name of the page to go once authenticated if logged out. If not defined, site initial page.
* @param params Params of the page to go once authenticated if logged out. * @param options Options of the page to go once authenticated if logged out.
* @return True if user is logged out, false otherwise. * @return True if user is logged out, false otherwise.
*/ */
isSiteLoggedOut(pageName?: string, params?: Params): boolean { isSiteLoggedOut(pageName?: string, options?: CoreNavigationOptions): boolean {
const site = CoreSites.getCurrentSite(); const site = CoreSites.getCurrentSite();
if (!site) { if (!site) {
return false; return false;
@ -561,7 +561,7 @@ export class CoreLoginHelperProvider {
if (site.isLoggedOut()) { if (site.isLoggedOut()) {
CoreEvents.trigger(CoreEvents.SESSION_EXPIRED, { CoreEvents.trigger(CoreEvents.SESSION_EXPIRED, {
pageName, pageName,
params, options,
}, site.getId()); }, site.getId());
return true; return true;
@ -633,7 +633,7 @@ export class CoreLoginHelperProvider {
* @param provider The identity provider. * @param provider The identity provider.
* @param launchUrl The URL to open for SSO. If not defined, tool/mobile launch URL will be used. * @param launchUrl The URL to open for SSO. If not defined, tool/mobile launch URL will be used.
* @param pageName Name of the page to go once authenticated. If not defined, site initial page. * @param pageName Name of the page to go once authenticated. If not defined, site initial page.
* @param pageParams Params of the state to go once authenticated. * @param pageOptions Options of the page to go once authenticated.
* @return True if success, false if error. * @return True if success, false if error.
*/ */
openBrowserForOAuthLogin( openBrowserForOAuthLogin(
@ -641,7 +641,7 @@ export class CoreLoginHelperProvider {
provider: CoreSiteIdentityProvider, provider: CoreSiteIdentityProvider,
launchUrl?: string, launchUrl?: string,
pageName?: string, pageName?: string,
pageParams?: Params, pageOptions?: CoreNavigationOptions,
): boolean { ): boolean {
launchUrl = launchUrl || siteUrl + '/admin/tool/mobile/launch.php'; launchUrl = launchUrl || siteUrl + '/admin/tool/mobile/launch.php';
if (!provider || !provider.url) { if (!provider || !provider.url) {
@ -655,7 +655,7 @@ export class CoreLoginHelperProvider {
} }
const service = CoreSites.determineService(siteUrl); const service = CoreSites.determineService(siteUrl);
const loginUrl = this.prepareForSSOLogin(siteUrl, service, launchUrl, pageName, pageParams, { const loginUrl = this.prepareForSSOLogin(siteUrl, service, launchUrl, pageName, pageOptions, {
oauthsso: params.id, oauthsso: params.id,
}); });
@ -676,7 +676,7 @@ export class CoreLoginHelperProvider {
* @param service The service to use. If not defined, external service will be used. * @param service The service to use. If not defined, external service will be used.
* @param launchUrl The URL to open for SSO. If not defined, local_mobile launch URL will be used. * @param launchUrl The URL to open for SSO. If not defined, local_mobile launch URL will be used.
* @param pageName Name of the page to go once authenticated. If not defined, site initial page. * @param pageName Name of the page to go once authenticated. If not defined, site initial page.
* @param pageParams Params of the state to go once authenticated. * @param pageOptions Options of the state to go once authenticated.
*/ */
openBrowserForSSOLogin( openBrowserForSSOLogin(
siteUrl: string, siteUrl: string,
@ -684,9 +684,9 @@ export class CoreLoginHelperProvider {
service?: string, service?: string,
launchUrl?: string, launchUrl?: string,
pageName?: string, pageName?: string,
pageParams?: Params, pageOptions?: CoreNavigationOptions,
): void { ): void {
const loginUrl = this.prepareForSSOLogin(siteUrl, service, launchUrl, pageName, pageParams); const loginUrl = this.prepareForSSOLogin(siteUrl, service, launchUrl, pageName, pageOptions);
if (this.isSSOEmbeddedBrowser(typeOfLogin)) { if (this.isSSOEmbeddedBrowser(typeOfLogin)) {
CoreUtils.openInApp(loginUrl, { CoreUtils.openInApp(loginUrl, {
@ -797,7 +797,7 @@ export class CoreLoginHelperProvider {
* @param service The service to use. If not defined, external service will be used. * @param service The service to use. If not defined, external service will be used.
* @param launchUrl The URL to open for SSO. If not defined, local_mobile launch URL will be used. * @param launchUrl The URL to open for SSO. If not defined, local_mobile launch URL will be used.
* @param pageName Name of the page to go once authenticated. If not defined, site initial page. * @param pageName Name of the page to go once authenticated. If not defined, site initial page.
* @param pageParams Params of the state to go once authenticated. * @param pageOptions Options of the page to go once authenticated.
* @param urlParams Other params to add to the URL. * @param urlParams Other params to add to the URL.
* @return Login Url. * @return Login Url.
*/ */
@ -806,7 +806,7 @@ export class CoreLoginHelperProvider {
service?: string, service?: string,
launchUrl?: string, launchUrl?: string,
pageName?: string, pageName?: string,
pageParams?: Params, pageOptions?: CoreNavigationOptions,
urlParams?: CoreUrlParams, urlParams?: CoreUrlParams,
): string { ): string {
@ -829,7 +829,7 @@ export class CoreLoginHelperProvider {
siteUrl: siteUrl, siteUrl: siteUrl,
passport: passport, passport: passport,
pageName: pageName || '', pageName: pageName || '',
pageParams: pageParams || {}, pageOptions: pageOptions || {},
ssoUrlParams: urlParams || {}, ssoUrlParams: urlParams || {},
})); }));
@ -917,7 +917,7 @@ export class CoreLoginHelperProvider {
result.service, result.service,
result.config?.launchurl, result.config?.launchurl,
data.pageName, data.pageName,
data.params, data.options,
); );
} catch (error) { } catch (error) {
// User cancelled, logout him. // User cancelled, logout him.
@ -955,7 +955,7 @@ export class CoreLoginHelperProvider {
providerToUse, providerToUse,
result.config?.launchurl, result.config?.launchurl,
data.pageName, data.pageName,
data.params, data.options,
); );
} catch (error) { } catch (error) {
// User cancelled, logout him. // User cancelled, logout him.
@ -982,7 +982,7 @@ export class CoreLoginHelperProvider {
params: { params: {
siteId, siteId,
pageName: data.pageName, pageName: data.pageName,
pageParams: data.params, pageOptions: data.options,
}, },
reset: true, reset: true,
})); }));
@ -1213,7 +1213,7 @@ export class CoreLoginHelperProvider {
token: params[1], token: params[1],
privateToken: params[2], privateToken: params[2],
pageName: data.pageName, pageName: data.pageName,
pageParams: data.pageParams, pageOptions: data.pageOptions,
ssoUrlParams: data.ssoUrlParams, ssoUrlParams: data.ssoUrlParams,
}; };
} else { } else {
@ -1357,9 +1357,9 @@ export interface CoreLoginSSOData {
pageName?: string; pageName?: string;
/** /**
* Params to page to the page. * Options of the navigation to the page.
*/ */
pageParams?: Params; pageOptions?: CoreNavigationOptions;
/** /**
* Other params added to the login url. * Other params added to the login url.
@ -1450,6 +1450,6 @@ type StoredLoginLaunchData = {
siteUrl: string; siteUrl: string;
passport: number; passport: number;
pageName: string; pageName: string;
pageParams: Params; pageOptions: CoreNavigationOptions;
ssoUrlParams: CoreUrlParams; ssoUrlParams: CoreUrlParams;
}; };

View File

@ -14,7 +14,8 @@
</ion-header> </ion-header>
<ion-content> <ion-content>
<core-loading [hideUntil]="loaded"> <core-loading [hideUntil]="loaded">
<core-tabs-outlet *ngIf="tabs.length > 0" [selectedIndex]="selectedTab" [hideUntil]="loaded" [tabs]="tabs"> <core-tabs-outlet *ngIf="tabs.length > 0" [selectedIndex]="selectedTab" [hideUntil]="loaded" [tabs]="tabs"
(ionChange)="tabSelected()">
</core-tabs-outlet> </core-tabs-outlet>
<ng-container *ngIf="tabs.length == 0"> <ng-container *ngIf="tabs.length == 0">
<core-empty-box icon="fas-home" [message]="'core.courses.nocourses' | translate"></core-empty-box> <core-empty-box icon="fas-home" [message]="'core.courses.nocourses' | translate"></core-empty-box>

View File

@ -20,6 +20,12 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreTabsOutletComponent, CoreTabsOutletTab } from '@components/tabs-outlet/tabs-outlet'; import { CoreTabsOutletComponent, CoreTabsOutletTab } from '@components/tabs-outlet/tabs-outlet';
import { CoreMainMenuHomeDelegate, CoreMainMenuHomeHandlerToDisplay } from '../../services/home-delegate'; import { CoreMainMenuHomeDelegate, CoreMainMenuHomeHandlerToDisplay } from '../../services/home-delegate';
import { CoreUtils } from '@services/utils/utils'; import { CoreUtils } from '@services/utils/utils';
import { ActivatedRoute } from '@angular/router';
import { CoreNavigator, CoreRedirectPayload } from '@services/navigator';
import { CoreCourseHelper } from '@features/course/services/course-helper';
import { CoreCourse } from '@features/course/services/course';
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
/** /**
* Page that displays the Home. * Page that displays the Home.
@ -40,11 +46,29 @@ export class CoreMainMenuHomePage implements OnInit {
protected subscription?: Subscription; protected subscription?: Subscription;
protected updateSiteObserver?: CoreEventObserver; protected updateSiteObserver?: CoreEventObserver;
protected pendingRedirect?: CoreRedirectPayload;
protected urlToOpen?: string;
constructor(
protected route: ActivatedRoute,
) {
}
/** /**
* Initialize the component. * Initialize the component.
*/ */
ngOnInit(): void { ngOnInit(): void {
this.route.queryParams.subscribe((params: Partial<CoreRedirectPayload> & { urlToOpen?: string }) => {
this.urlToOpen = params.urlToOpen;
if (params.redirectPath) {
this.pendingRedirect = {
redirectPath: params.redirectPath,
redirectOptions: params.redirectOptions,
};
}
});
this.loadSiteName(); this.loadSiteName();
this.subscription = CoreMainMenuHomeDelegate.getHandlersObservable().subscribe((handlers) => { this.subscription = CoreMainMenuHomeDelegate.getHandlersObservable().subscribe((handlers) => {
@ -111,6 +135,57 @@ export class CoreMainMenuHomePage implements OnInit {
this.siteName = CoreSites.getCurrentSite()!.getSiteName(); this.siteName = CoreSites.getCurrentSite()!.getSiteName();
} }
/**
* Handle a redirect.
*
* @param data Data received.
*/
protected handleRedirect(data: CoreRedirectPayload): void {
const params = data.redirectOptions?.params;
const coursePathMatches = data.redirectPath.match(/^course\/(\d+)\/?$/);
if (coursePathMatches) {
if (!params?.course) {
CoreCourseHelper.getAndOpenCourse(Number(coursePathMatches[1]), params);
} else {
CoreCourse.openCourse(params.course, params);
}
} else {
CoreNavigator.navigateToSitePath(data.redirectPath, {
...data.redirectOptions,
preferCurrentTab: false,
});
}
}
/**
* Handle a URL to open.
*
* @param url URL to open.
*/
protected async handleUrlToOpen(url: string): Promise<void> {
const actions = await CoreContentLinksDelegate.getActionsFor(url, undefined);
const action = CoreContentLinksHelper.getFirstValidAction(actions);
if (action) {
action.action(action.sites![0]);
}
}
/**
* Tab was selected.
*/
tabSelected(): void {
if (this.pendingRedirect) {
this.handleRedirect(this.pendingRedirect);
} else if (this.urlToOpen) {
this.handleUrlToOpen(this.urlToOpen);
}
delete this.pendingRedirect;
delete this.urlToOpen;
}
/** /**
* User entered the page. * User entered the page.
*/ */

View File

@ -19,14 +19,12 @@ import { BackButtonEvent } from '@ionic/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { CoreApp } from '@services/app'; import { CoreApp } from '@services/app';
import { CoreSites } from '@services/sites';
import { CoreTextUtils } from '@services/utils/text'; import { CoreTextUtils } from '@services/utils/text';
import { CoreEvents, CoreEventObserver } from '@singletons/events'; import { CoreEvents, CoreEventObserver } from '@singletons/events';
import { CoreMainMenu, CoreMainMenuProvider } from '../../services/mainmenu'; import { CoreMainMenu, CoreMainMenuProvider } from '../../services/mainmenu';
import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../services/mainmenu-delegate'; import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../services/mainmenu-delegate';
import { CoreDomUtils } from '@services/utils/dom'; import { CoreDomUtils } from '@services/utils/dom';
import { Translate } from '@singletons'; import { Translate } from '@singletons';
import { CoreNavigator, CoreRedirectPayload } from '@services/navigator';
/** /**
* Page that displays the main menu of the app. * Page that displays the main menu of the app.
@ -47,9 +45,6 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
morePageName = CoreMainMenuProvider.MORE_PAGE_NAME; morePageName = CoreMainMenuProvider.MORE_PAGE_NAME;
protected subscription?: Subscription; protected subscription?: Subscription;
protected redirectObs?: CoreEventObserver;
protected pendingRedirect?: CoreRedirectPayload;
protected urlToOpen?: string;
protected keyboardObserver?: CoreEventObserver; protected keyboardObserver?: CoreEventObserver;
protected resizeFunction: () => void; protected resizeFunction: () => void;
protected backButtonFunction: (event: BackButtonEvent) => void; protected backButtonFunction: (event: BackButtonEvent) => void;
@ -72,24 +67,6 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
* Initialize the component. * Initialize the component.
*/ */
ngOnInit(): void { ngOnInit(): void {
// @TODO this should be handled by route guards and can be removed
if (!CoreSites.isLoggedIn()) {
CoreNavigator.navigate('/login/init', { reset: true });
return;
}
this.route.queryParams.subscribe((params: Partial<CoreRedirectPayload> & { urlToOpen?: string }) => {
if (params.redirectPath) {
this.pendingRedirect = {
redirectPath: params.redirectPath,
redirectParams: params.redirectParams,
};
}
this.urlToOpen = params.urlToOpen;
});
this.showTabs = true; this.showTabs = true;
this.subscription = CoreMainMenuDelegate.getHandlersObservable().subscribe((handlers) => { this.subscription = CoreMainMenuDelegate.getHandlersObservable().subscribe((handlers) => {
@ -97,16 +74,6 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
this.allHandlers = handlers.filter((handler) => !handler.onlyInMore); this.allHandlers = handlers.filter((handler) => !handler.onlyInMore);
this.initHandlers(); this.initHandlers();
if (this.loaded && this.pendingRedirect) {
// Wait for tabs to be initialized and then handle the redirect.
setTimeout(() => {
if (this.pendingRedirect) {
this.handleRedirect(this.pendingRedirect);
delete this.pendingRedirect;
}
});
}
}); });
window.addEventListener('resize', this.resizeFunction); window.addEventListener('resize', this.resizeFunction);
@ -158,18 +125,6 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
this.tabs.sort((a, b) => (b.priority || 0) - (a.priority || 0)); this.tabs.sort((a, b) => (b.priority || 0) - (a.priority || 0));
this.loaded = CoreMainMenuDelegate.areHandlersLoaded(); this.loaded = CoreMainMenuDelegate.areHandlersLoaded();
if (this.loaded && this.mainTabs && !this.mainTabs.getSelected()) {
// Select the first tab.
setTimeout(() => {
this.mainTabs!.select(this.tabs[0]?.page || this.morePageName);
});
}
}
if (this.urlToOpen) {
// There's a content link to open.
// @todo: Treat URL.
} }
} }
@ -189,36 +144,11 @@ export class CoreMainMenuPage implements OnInit, OnDestroy {
} }
} }
/**
* Handle a redirect.
*
* @param data Data received.
*/
protected handleRedirect(data: CoreRedirectPayload): void {
// Check if the redirect page is the root page of any of the tabs.
const i = this.tabs.findIndex((tab) => tab.page == data.redirectPath);
if (i >= 0) {
// Tab found. Open it with the params.
CoreNavigator.navigate(data.redirectPath, {
params: data.redirectParams,
animated: false,
});
} else {
// Tab not found, use a phantom tab.
// @todo
}
// Force change detection, otherwise sometimes the tab was selected before the params were applied.
this.changeDetector.detectChanges();
}
/** /**
* Page destroyed. * Page destroyed.
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.subscription?.unsubscribe(); this.subscription?.unsubscribe();
this.redirectObs?.off();
window.removeEventListener('resize', this.resizeFunction); window.removeEventListener('resize', this.resizeFunction);
document.removeEventListener('ionBackButton', this.backButtonFunction); document.removeEventListener('ionBackButton', this.backButtonFunction);
this.keyboardObserver?.off(); this.keyboardObserver?.off();

View File

@ -42,10 +42,10 @@ export class CoreMainMenuProvider {
* *
* @return Promise resolved with the current main menu handlers. * @return Promise resolved with the current main menu handlers.
*/ */
getCurrentMainMenuHandlers(): CoreMainMenuHandlerToDisplay[] { async getCurrentMainMenuHandlers(): Promise<CoreMainMenuHandlerToDisplay[]> {
return CoreMainMenuDelegate.getHandlers() const handlers = await CoreMainMenuDelegate.getHandlersWhenLoaded();
.filter(handler => !handler.onlyInMore)
.slice(0, this.getNumItems()); return handlers.filter(handler => !handler.onlyInMore).slice(0, this.getNumItems());
} }
/** /**
@ -215,7 +215,11 @@ export class CoreMainMenuProvider {
async isCurrentMainMenuHandler(pageName: string): Promise<boolean> { async isCurrentMainMenuHandler(pageName: string): Promise<boolean> {
const handlers = await this.getCurrentMainMenuHandlers(); const handlers = await this.getCurrentMainMenuHandlers();
const handler = handlers.find((handler) => handler.page == pageName); const handler = handlers.find((handler) => {
const tabRoot = /^[^/]+/.exec(handler.page)?.[0] ?? handler.page;
return tabRoot == pageName;
});
return !!handler; return !!handler;
} }

View File

@ -15,6 +15,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CanActivate, CanLoad, UrlTree } from '@angular/router'; import { CanActivate, CanLoad, UrlTree } from '@angular/router';
import { CoreApp } from '@services/app'; import { CoreApp } from '@services/app';
import { CoreRedirectPayload } from '@services/navigator';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
import { Router } from '@singletons'; import { Router } from '@singletons';
import { CoreConstants } from '../constants'; import { CoreConstants } from '../constants';
@ -40,52 +41,48 @@ export class CoreRedirectGuard implements CanLoad, CanActivate {
* Check if there is a pending redirect and trigger it. * Check if there is a pending redirect and trigger it.
*/ */
private async guard(): Promise<true | UrlTree> { private async guard(): Promise<true | UrlTree> {
const redirect = CoreApp.getRedirect(); const redirect = CoreApp.consumeMemoryRedirect();
if (!redirect) { if (!redirect) {
return true; return true;
} }
try { // Only accept the redirect if it was stored less than 20 seconds ago.
// Only accept the redirect if it was stored less than 20 seconds ago. if (!redirect.timemodified || Date.now() - redirect.timemodified > 20000) {
if (!redirect.timemodified || Date.now() - redirect.timemodified < 20000) { return true;
return true; }
}
// Redirect to site path. // Redirect to site path.
if (redirect.siteId && redirect.siteId !== CoreConstants.NO_SITE_ID) { if (redirect.siteId && redirect.siteId !== CoreConstants.NO_SITE_ID) {
const loggedIn = await CoreSites.loadSite( const loggedIn = await CoreSites.loadSite(
redirect.siteId, redirect.siteId,
redirect.page, redirect.page,
redirect.params, redirect.options,
); );
const route = Router.parseUrl('/main'); const route = Router.parseUrl('/main/home');
route.queryParams = {
redirectPath: redirect.page,
redirectParams: redirect.params,
};
return loggedIn ? route : true;
}
// Abort redirect.
if (!redirect.page) {
return true;
}
// Redirect to non-site path.
const route = Router.parseUrl(redirect.page);
route.queryParams = { route.queryParams = {
redirectPath: redirect.page, redirectPath: redirect.page,
redirectParams: redirect.params, redirectOptions: redirect.options,
}; } as CoreRedirectPayload;
return route; return loggedIn ? route : true;
} finally {
CoreApp.forgetRedirect();
} }
// Abort redirect.
if (!redirect.page) {
return true;
}
// Redirect to non-site path.
const route = Router.parseUrl(redirect.page);
route.queryParams = {
redirectPath: redirect.page,
redirectOptions: redirect.options,
} as CoreRedirectPayload;
return route;
} }
} }

View File

@ -13,7 +13,6 @@
// limitations under the License. // limitations under the License.
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Params } from '@angular/router';
import { CoreDB } from '@services/db'; import { CoreDB } from '@services/db';
import { CoreEvents } from '@singletons/events'; import { CoreEvents } from '@singletons/events';
@ -25,6 +24,7 @@ import { CoreLogger } from '@singletons/logger';
import { CoreColors } from '@singletons/colors'; import { CoreColors } from '@singletons/colors';
import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/database/app'; import { DBNAME, SCHEMA_VERSIONS_TABLE_NAME, SCHEMA_VERSIONS_TABLE_SCHEMA, SchemaVersionsDBEntry } from '@services/database/app';
import { CoreObject } from '@singletons/object'; import { CoreObject } from '@singletons/object';
import { CoreNavigationOptions } from './navigator';
/** /**
* Object responsible of managing schema versions. * Object responsible of managing schema versions.
@ -549,6 +549,19 @@ export class CoreAppProvider {
} }
} }
/**
* Retrieve and forget redirect data.
*
* @return Redirect data if any.
*/
consumeMemoryRedirect(): CoreRedirectData | null {
const redirect = this.getRedirect();
this.forgetRedirect();
return redirect;
}
/** /**
* Forget redirect data. * Forget redirect data.
*/ */
@ -559,7 +572,7 @@ export class CoreAppProvider {
/** /**
* Retrieve redirect data. * Retrieve redirect data.
* *
* @return Object with siteid, state, params and timemodified. * @return Redirect data if any.
*/ */
getRedirect(): CoreRedirectData | null { getRedirect(): CoreRedirectData | null {
return this.redirect || null; return this.redirect || null;
@ -570,14 +583,14 @@ export class CoreAppProvider {
* *
* @param siteId Site ID. * @param siteId Site ID.
* @param page Page to go. * @param page Page to go.
* @param params Page params. * @param options Navigation options.
*/ */
storeRedirect(siteId: string, page: string, params: Params): void { storeRedirect(siteId: string, page: string, options: CoreNavigationOptions): void {
try { try {
const redirect: CoreRedirectData = { const redirect: CoreRedirectData = {
siteId, siteId,
page, page,
params, options,
timemodified: Date.now(), timemodified: Date.now(),
}; };
@ -658,14 +671,14 @@ export type CoreRedirectData = {
siteId?: string; siteId?: string;
/** /**
* Name of the page to redirect to. * Path of the page to redirect to.
*/ */
page?: string; page?: string;
/** /**
* Params to pass to the page. * Options of the navigation.
*/ */
params?: Params; options?: CoreNavigationOptions;
/** /**
* Timestamp when this redirect was last modified. * Timestamp when this redirect was last modified.

View File

@ -30,6 +30,7 @@ import { makeSingleton, NavController, Router } from '@singletons';
import { CoreScreen } from './screen'; import { CoreScreen } from './screen';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { CoreApp } from './app'; import { CoreApp } from './app';
import { CoreSitePlugins } from '@features/siteplugins/services/siteplugins';
const DEFAULT_MAIN_MENU_TAB = CoreMainMenuHomeHandlerService.PAGE_NAME; const DEFAULT_MAIN_MENU_TAB = CoreMainMenuHomeHandlerService.PAGE_NAME;
@ -38,7 +39,7 @@ const DEFAULT_MAIN_MENU_TAB = CoreMainMenuHomeHandlerService.PAGE_NAME;
*/ */
export type CoreRedirectPayload = { export type CoreRedirectPayload = {
redirectPath: string; redirectPath: string;
redirectParams?: Params; redirectOptions?: CoreNavigationOptions;
}; };
/** /**
@ -49,6 +50,11 @@ export type CoreNavigationOptions = {
params?: Params; params?: Params;
reset?: boolean; reset?: boolean;
preferCurrentTab?: boolean; // Default true. preferCurrentTab?: boolean; // Default true.
nextNavigation?: {
path: string;
isSitePath?: boolean;
options?: CoreNavigationOptions;
};
}; };
/** /**
@ -148,6 +154,14 @@ export class CoreNavigatorService {
? await NavController.navigateRoot(url, navigationOptions) ? await NavController.navigateRoot(url, navigationOptions)
: await NavController.navigateForward(url, navigationOptions); : await NavController.navigateForward(url, navigationOptions);
if (options.nextNavigation?.path && navigationResult !== false) {
if (options.nextNavigation.isSitePath) {
return this.navigateToSitePath(options.nextNavigation.path, options.nextNavigation.options);
}
return this.navigate(options.nextNavigation.path, options.nextNavigation.options);
}
return navigationResult !== false; return navigationResult !== false;
} }
@ -208,7 +222,14 @@ export class CoreNavigatorService {
// If we are logged into a different site, log out first. // If we are logged into a different site, log out first.
if (CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() !== siteId) { if (CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() !== siteId) {
// @todo: Check site plugins and store redirect. if (CoreSitePlugins.hasSitePluginsLoaded) {
// The site has site plugins so the app will be restarted. Store the data and logout.
CoreApp.storeRedirect(siteId, path, options || {});
await CoreSites.logout();
return true;
}
await CoreSites.logout(); await CoreSites.logout();
} }
@ -442,7 +463,7 @@ export class CoreNavigatorService {
...options, ...options,
params: { params: {
redirectPath: `/main/${DEFAULT_MAIN_MENU_TAB}/${path}`, redirectPath: `/main/${DEFAULT_MAIN_MENU_TAB}/${path}`,
redirectParams: options.params, redirectOptions: options.params || options.nextNavigation ? options : undefined,
} as CoreRedirectPayload, } as CoreRedirectPayload,
}); });
} }

View File

@ -50,6 +50,7 @@ import {
} from '@services/database/sites'; } from '@services/database/sites';
import { CoreArray } from '../singletons/array'; import { CoreArray } from '../singletons/array';
import { CoreNetworkError } from '@classes/errors/network-error'; import { CoreNetworkError } from '@classes/errors/network-error';
import { CoreNavigationOptions } from './navigator';
export const CORE_SITE_SCHEMAS = new InjectionToken<CoreSiteSchema[]>('CORE_SITE_SCHEMAS'); export const CORE_SITE_SCHEMAS = new InjectionToken<CoreSiteSchema[]>('CORE_SITE_SCHEMAS');
@ -795,10 +796,10 @@ export class CoreSitesProvider {
* *
* @param siteId ID of the site to load. * @param siteId ID of the site to load.
* @param pageName Name of the page to go once authenticated if logged out. If not defined, site initial page. * @param pageName Name of the page to go once authenticated if logged out. If not defined, site initial page.
* @param params Params of the page to go once authenticated if logged out. * @param pageOptions Options of the navigation to pageName.
* @return Promise resolved with true if site is loaded, resolved with false if cannot login. * @return Promise resolved with true if site is loaded, resolved with false if cannot login.
*/ */
async loadSite(siteId: string, pageName?: string, params?: Record<string, unknown>): Promise<boolean> { async loadSite(siteId: string, pageName?: string, pageOptions?: CoreNavigationOptions): Promise<boolean> {
this.logger.debug(`Load site ${siteId}`); this.logger.debug(`Load site ${siteId}`);
const site = await this.getSite(siteId); const site = await this.getSite(siteId);
@ -809,7 +810,7 @@ export class CoreSitesProvider {
// Logged out, trigger session expired event and stop. // Logged out, trigger session expired event and stop.
CoreEvents.trigger(CoreEvents.SESSION_EXPIRED, { CoreEvents.trigger(CoreEvents.SESSION_EXPIRED, {
pageName, pageName,
params, options: pageOptions,
}, site.getId()); }, site.getId());
return false; return false;
@ -822,7 +823,7 @@ export class CoreSitesProvider {
// Local mobile was added. Throw invalid session to force reconnect and create a new token. // Local mobile was added. Throw invalid session to force reconnect and create a new token.
CoreEvents.trigger(CoreEvents.SESSION_EXPIRED, { CoreEvents.trigger(CoreEvents.SESSION_EXPIRED, {
pageName, pageName,
params, options: pageOptions,
}, siteId); }, siteId);
return false; return false;

View File

@ -153,9 +153,7 @@ export class CoreCustomURLSchemesProvider {
// Site created and authenticated, open the page to go. // Site created and authenticated, open the page to go.
if (data.pageName) { if (data.pageName) {
// Page defined, go to that page instead of site initial page. // Page defined, go to that page instead of site initial page.
CoreNavigator.navigateToSitePath(data.pageName, { CoreNavigator.navigateToSitePath(data.pageName, data.pageOptions);
params: data.pageParams,
});
} else { } else {
CoreNavigator.navigateToSiteHome(); CoreNavigator.navigateToSiteHome();
} }
@ -408,7 +406,7 @@ export class CoreCustomURLSchemesProvider {
hasSitePluginsLoaded = CoreSitePlugins.hasSitePluginsLoaded; hasSitePluginsLoaded = CoreSitePlugins.hasSitePluginsLoaded;
if (hasSitePluginsLoaded) { if (hasSitePluginsLoaded) {
// Store the redirect since logout will restart the app. // Store the redirect since logout will restart the app.
CoreApp.storeRedirect(CoreConstants.NO_SITE_ID, '/login/credentials', pageParams); CoreApp.storeRedirect(CoreConstants.NO_SITE_ID, '/login/credentials', { params: pageParams });
} }
await CoreSites.logout(); await CoreSites.logout();

View File

@ -12,12 +12,12 @@
// 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 { Params } from '@angular/router';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { CoreLogger } from '@singletons/logger'; import { CoreLogger } from '@singletons/logger';
import { CoreSite, CoreSiteInfoResponse, CoreSitePublicConfigResponse } from '@classes/site'; import { CoreSite, CoreSiteInfoResponse, CoreSitePublicConfigResponse } from '@classes/site';
import { CoreFilepoolComponentFileEventData } from '@services/filepool'; import { CoreFilepoolComponentFileEventData } from '@services/filepool';
import { CoreNavigationOptions } from '@services/navigator';
/** /**
* Observer instance to stop listening to an event. * Observer instance to stop listening to an event.
@ -260,7 +260,7 @@ export type CoreEventSiteAddedData = CoreSiteInfoResponse;
*/ */
export type CoreEventSessionExpiredData = { export type CoreEventSessionExpiredData = {
pageName?: string; pageName?: string;
params?: Params; options?: CoreNavigationOptions;
}; };
/** /**