MOBILE-3643 forum: Migrate navigation guards

main
Noel De Martin 2021-02-24 11:38:35 +01:00
parent d4bc77b386
commit 26f18ac5f4
9 changed files with 231 additions and 106 deletions

View File

@ -39,6 +39,7 @@ import { CorePageItemsListManager } from '@classes/page-items-list-manager';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { AddonModForumDiscussionOptionsMenuComponent } from '../discussion-options-menu/discussion-options-menu';
import { AddonModForumSortOrderSelectorComponent } from '../sort-order-selector/sort-order-selector';
import { CoreScreen } from '@services/screen';
/**
* Type to use for selecting new discussion form in the discussions manager.
@ -48,6 +49,16 @@ type NewDiscussionForm = {
timeCreated: number;
};
/**
* Type guard to infer NewDiscussionForm objects.
*
* @param discussion Object to check.
* @return Whether the object is a new discussion form.
*/
function isNewDiscussionForm(discussion: Record<string, unknown>): discussion is NewDiscussionForm {
return 'newDiscussion' in discussion;
}
/**
* Component that displays a forum entry page.
*/
@ -115,6 +126,21 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
this.sortOrders = AddonModForum.instance.getAvailableSortOrders();
await super.ngOnInit();
// Refresh data if this forum discussion is synchronized from discussions list.
this.syncManualObserver = CoreEvents.on(AddonModForumSyncProvider.MANUAL_SYNCED, (data) => {
this.autoSyncEventReceived(data);
}, this.siteId);
// Listen for discussions added. When a discussion is added, we reload the data.
this.newDiscObserver = CoreEvents.on(
AddonModForumProvider.NEW_DISCUSSION_EVENT,
this.eventReceived.bind(this, true),
);
this.replyObserver = CoreEvents.on(
AddonModForumProvider.REPLY_DISCUSSION_EVENT,
this.eventReceived.bind(this, false),
);
}
async ngAfterViewInit(): Promise<void> {
@ -503,6 +529,37 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
return result.updated;
}
/**
* Function called when we receive an event of new discussion or reply to discussion.
*
* @param isNewDiscussion Whether it's a new discussion event.
* @param data Event data.
*/
protected eventReceived(isNewDiscussion: boolean, data: any): void {
if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module?.id) {
this.showLoadingAndRefresh(false).finally(() => {
// If it's a new discussion in tablet mode, try to open it.
if (isNewDiscussion && CoreScreen.instance.isTablet) {
if (data.discussionIds) {
// Discussion sent to server, search it in the list of discussions.
const discussion = this.discussions.items.find(
(disc) =>
!isNewDiscussionForm(disc) &&
data.discussionIds.indexOf(disc.discussion) >= 0,
);
this.discussions.select(discussion ?? this.discussions.items[0]);
} else if (data.discTimecreated) {
// It's an offline discussion, open it.
this.openNewDiscussion(data.discTimecreated);
}
}
});
// Check completion since it could be configured to complete once the user adds a new discussion or replies.
CoreCourse.instance.checkModuleCompletion(this.courseId!, this.module!.completiondata);
}
}
/**
* Opens the new discussion form.
@ -611,7 +668,7 @@ class AddonModForumDiscussionsManager extends CorePageItemsListManager<AddonModF
cmId: this.component.module!.id,
forumId: this.component.forum!.id,
...(
this.isNewDiscussionForm(discussion)
isNewDiscussionForm(discussion)
? { timeCreated: discussion.timeCreated }
: { discussion, trackPosts: this.component.trackPosts }
),
@ -619,7 +676,7 @@ class AddonModForumDiscussionsManager extends CorePageItemsListManager<AddonModF
}
protected getItemPath(discussion: AddonModForumDiscussion | NewDiscussionForm): string {
const discussionId = this.isNewDiscussionForm(discussion) ? 'new' : discussion.id;
const discussionId = isNewDiscussionForm(discussion) ? 'new' : discussion.id;
return this.discussionsPathPrefix + discussionId;
}
@ -630,8 +687,4 @@ class AddonModForumDiscussionsManager extends CorePageItemsListManager<AddonModF
return discussionId ? this.discussionsPathPrefix + discussionId : null;
}
private isNewDiscussionForm(discussion: AddonModForumDiscussion | NewDiscussionForm): discussion is NewDiscussionForm {
return 'newDiscussion' in discussion;
}
}

View File

@ -15,14 +15,12 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { conditionalRoutes } from '@/app/app-routing.module';
import { CoreScreen } from '@services/screen';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreEditorComponentsModule } from '@features/editor/components/components.module';
import { AddonModForumComponentsModule } from './components/components.module';
import { AddonModForumIndexPage } from './pages/index';
import { conditionalRoutes } from '@/app/app-routing.module';
import { CoreScreen } from '@services/screen';
import { AddonModForumNewDiscussionPage } from './pages/new-discussion/new-discussion';
const mobileRoutes: Routes = [
{
@ -31,7 +29,7 @@ const mobileRoutes: Routes = [
},
{
path: ':courseId/:cmId/new',
component: AddonModForumNewDiscussionPage,
loadChildren: () => import('./pages/new-discussion/new-discussion.module').then(m => m.AddonForumNewDiscussionPageModule),
},
{
path: ':courseId/:cmId/:discussionId',
@ -46,7 +44,8 @@ const tabletRoutes: Routes = [
children: [
{
path: 'new',
component: AddonModForumNewDiscussionPage,
loadChildren: () => import('./pages/new-discussion/new-discussion.module')
.then(m => m.AddonForumNewDiscussionPageModule),
},
{
path: ':discussionId',
@ -67,11 +66,9 @@ const routes: Routes = [
RouterModule.forChild(routes),
CoreSharedModule,
AddonModForumComponentsModule,
CoreEditorComponentsModule,
],
declarations: [
AddonModForumIndexPage,
AddonModForumNewDiscussionPage,
],
})
export class AddonModForumLazyModule {}

View File

@ -33,6 +33,11 @@ const mainMenuRoutes: Routes = [
},
...conditionalRoutes(
[
{
path: 'course/index/contents/mod_forum/new',
loadChildren: () => import('./pages/new-discussion/new-discussion.module')
.then(m => m.AddonForumNewDiscussionPageModule),
},
{
path: 'course/index/contents/mod_forum/:discussionId',
loadChildren: () => import('./pages/discussion/discussion.module').then(m => m.AddonForumDiscussionPageModule),
@ -44,6 +49,11 @@ const mainMenuRoutes: Routes = [
const courseContentsRoutes: Routes = conditionalRoutes(
[
{
path: 'mod_forum/new',
loadChildren: () => import('./pages/new-discussion/new-discussion.module')
.then(m => m.AddonForumNewDiscussionPageModule),
},
{
path: 'mod_forum/:discussionId',
loadChildren: () => import('./pages/discussion/discussion.module').then(m => m.AddonForumDiscussionPageModule),

View File

@ -15,7 +15,7 @@
<core-navbar-buttons slot="end">
<core-context-menu>
<core-context-menu-item [priority]="650" *ngIf="discussionLoaded && !postHasOffline && isOnline" [content]="'addon.mod_forum.refreshposts' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false"></core-context-menu-item>
<core-context-menu-item [priority]="550" *ngIf="discussionLoaded && !isSplitViewOn && postHasOffline && isOnline" [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item>
<core-context-menu-item [priority]="550" *ngIf="discussionLoaded && isMobile && postHasOffline && isOnline" [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item>
<core-context-menu-item [hidden]="sort == 'flat-oldest'" [priority]="500" [content]="'addon.mod_forum.modeflatoldestfirst' | translate" (action)="changeSort('flat-oldest')" iconAction="arrow-round-down"></core-context-menu-item>
<core-context-menu-item [hidden]="sort == 'flat-newest'" [priority]="450" [content]="'addon.mod_forum.modeflatnewestfirst' | translate" (action)="changeSort('flat-newest')" iconAction="arrow-round-up"></core-context-menu-item>
<core-context-menu-item [hidden]="sort == 'nested'" [priority]="400" [content]="'addon.mod_forum.modenested' | translate" (action)="changeSort('nested')" iconAction="swap"></core-context-menu-item>

View File

@ -16,6 +16,7 @@ import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AddonModForumComponentsModule } from '@addons/mod/forum/components/components.module';
import { CanLeaveGuard } from '@guards/can-leave';
import { CoreSharedModule } from '@/core/shared.module';
import { AddonModForumDiscussionPage } from './discussion.page';
@ -23,6 +24,7 @@ import { AddonModForumDiscussionPage } from './discussion.page';
const routes: Routes = [{
path: '',
component: AddonModForumDiscussionPage,
canDeactivate: [CanLeaveGuard],
}];
@NgModule({

View File

@ -13,10 +13,13 @@
// limitations under the License.
import { Component, OnDestroy, ViewChild, OnInit, AfterViewInit, ElementRef } from '@angular/core';
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
import { CoreUser } from '@features/user/services/user';
import { CanLeave } from '@guards/can-leave';
import { IonContent } from '@ionic/angular';
import { CoreApp } from '@services/app';
import { CoreNavigator } from '@services/navigator';
import { CoreScreen } from '@services/screen';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
@ -48,7 +51,7 @@ type Post = AddonModForumPost & { children?: Post[] };
templateUrl: 'discussion.html',
styleUrls: ['discussion.scss'],
})
export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDestroy {
export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDestroy, CanLeave {
@ViewChild(IonContent) content!: IonContent;
@ -105,6 +108,10 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
constructor(protected elementRef: ElementRef) {}
get isMobile(): boolean {
return CoreScreen.instance.isMobile;
}
ngOnInit(): void {
this.courseId = CoreNavigator.instance.getRouteNumberParam('courseId')!;
this.cmId = CoreNavigator.instance.getRouteNumberParam('cmId')!;
@ -153,40 +160,6 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
}
}
/**
* Get sort type configured by the current user.
*
* @return Promise resolved with the sort type.
*/
protected async getUserSort(): Promise<SortType> {
try {
const value = await CoreSites.instance.getCurrentSite()!.getLocalSiteConfig<SortType>('AddonModForumDiscussionSort');
return value;
} catch (error) {
try {
const value = await CoreUser.instance.getUserPreference('forum_displaymode');
switch (Number(value)) {
case 1:
return 'flat-oldest';
case -1:
return 'flat-newest';
case 3:
return 'nested';
case 2: // Threaded not implemented.
default:
// Not set, use default sort.
// @TODO add fallback to $CFG->forum_displaymode.
}
} catch (error) {
// Ignore errors.
}
}
return 'flat-oldest';
}
/**
* User entered the page that contains the component.
*/
@ -216,11 +189,10 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
}
}, CoreSites.instance.getCurrentSiteId());
// Trigger view event, to highlight the current opened discussion in the split view.
CoreEvents.trigger(AddonModForumProvider.VIEW_DISCUSSION_EVENT, {
forumId: this.forumId,
discussion: this.discussionId,
}, CoreSites.instance.getCurrentSiteId());
// Invalidate discussion list if it was not read.
if (this.discussion.numunread > 0) {
AddonModForum.instance.invalidateDiscussionsList(this.forumId);
}
this.changeDiscObserver = CoreEvents.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, (data: any) => {
if ((this.forumId && this.forumId === data.forumId) || data.cmId === this.cmId) {
@ -253,24 +225,77 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
});
}
// @todo
// /**
// * Check if we can leave the page or not.
// *
// * @return Resolved if we can leave it, rejected if not.
// */
// async ionViewCanLeave(): Promise<void> {
/**
* Check if we can leave the page or not.
*
* @return Resolved if we can leave it, rejected if not.
*/
async canLeave(): Promise<boolean> {
if (AddonModForumHelper.instance.hasPostDataChanged(this.replyData, this.originalData)) {
// Show confirmation if some data has been modified.
await CoreDomUtils.instance.showConfirm(Translate.instant('core.confirmcanceledit'));
}
// if (AddonModForumHelper.instance.hasPostDataChanged(this.replyData, this.originalData)) {
// // Show confirmation if some data has been modified.
// await CoreDomUtils.instance.showConfirm(this.translate.instant('core.confirmcanceledit'));
// }
// Delete the local files from the tmp folder.
CoreFileUploader.instance.clearTmpFiles(this.replyData.files);
// // Delete the local files from the tmp folder.
// this.uploaderProvider.clearTmpFiles(this.replyData.files);
this.leavingPage = true;
// this.leavingPage = true;
// }
return true;
}
/**
* Runs when the page is about to leave and no longer be the active page.
*/
ionViewWillLeave(): void {
this.syncObserver && this.syncObserver.off();
this.syncManualObserver && this.syncManualObserver.off();
this.ratingOfflineObserver && this.ratingOfflineObserver.off();
this.ratingSyncObserver && this.ratingSyncObserver.off();
this.changeDiscObserver && this.changeDiscObserver.off();
delete this.syncObserver;
}
/**
* Page destroyed.
*/
ngOnDestroy(): void {
this.onlineObserver && this.onlineObserver.unsubscribe();
}
/**
* Get sort type configured by the current user.
*
* @return Promise resolved with the sort type.
*/
protected async getUserSort(): Promise<SortType> {
try {
const value = await CoreSites.instance.getCurrentSite()!.getLocalSiteConfig<SortType>('AddonModForumDiscussionSort');
return value;
} catch (error) {
try {
const value = await CoreUser.instance.getUserPreference('forum_displaymode');
switch (Number(value)) {
case 1:
return 'flat-oldest';
case -1:
return 'flat-newest';
case 3:
return 'nested';
case 2: // Threaded not implemented.
default:
// Not set, use default sort.
// @TODO add fallback to $CFG->forum_displaymode.
}
} catch (error) {
// Ignore errors.
}
}
return 'flat-oldest';
}
/**
* Convenience function to get the forum.
@ -710,25 +735,6 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
});
}
/**
* Runs when the page is about to leave and no longer be the active page.
*/
ionViewWillLeave(): void {
this.syncObserver && this.syncObserver.off();
this.syncManualObserver && this.syncManualObserver.off();
this.ratingOfflineObserver && this.ratingOfflineObserver.off();
this.ratingSyncObserver && this.ratingSyncObserver.off();
this.changeDiscObserver && this.changeDiscObserver.off();
delete this.syncObserver;
}
/**
* Page destroyed.
*/
ngOnDestroy(): void {
this.onlineObserver && this.onlineObserver.unsubscribe();
}
/**
* Get all the posts contained in the discussion.
*

View File

@ -0,0 +1,42 @@
// (C) Copyright 2015 Moodle Pty Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AddonModForumComponentsModule } from '@addons/mod/forum/components/components.module';
import { CanLeaveGuard } from '@guards/can-leave';
import { CoreEditorComponentsModule } from '@features/editor/components/components.module';
import { CoreSharedModule } from '@/core/shared.module';
import { AddonModForumNewDiscussionPage } from './new-discussion.page';
const routes: Routes = [{
path: '',
component: AddonModForumNewDiscussionPage,
canDeactivate: [CanLeaveGuard],
}];
@NgModule({
imports: [
RouterModule.forChild(routes),
CoreSharedModule,
AddonModForumComponentsModule,
CoreEditorComponentsModule,
],
declarations: [
AddonModForumNewDiscussionPage,
],
})
export class AddonForumNewDiscussionPageModule {}

View File

@ -37,6 +37,8 @@ import { AddonModForumHelper } from '@addons/mod/forum/services/helper.service';
import { IonRefresher } from '@ionic/angular';
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
import { CoreTextUtils } from '@services/utils/text';
import { CanLeave } from '@guards/can-leave';
import { CoreScreen } from '@services/screen';
type NewDiscussionData = {
subject: string;
@ -55,7 +57,7 @@ type NewDiscussionData = {
selector: 'page-addon-mod-forum-new-discussion',
templateUrl: 'new-discussion.html',
})
export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy {
export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLeave {
@ViewChild('newDiscFormEl') formElement!: ElementRef;
@ViewChild(CoreEditorRichTextEditorComponent) messageEditor!: CoreEditorRichTextEditorComponent;
@ -124,12 +126,6 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy {
this.returnToDiscussions();
}
}, CoreSites.instance.getCurrentSiteId());
// Trigger view event, to highlight the current opened discussion in the split view.
CoreEvents.trigger(AddonModForumProvider.VIEW_DISCUSSION_EVENT, {
forumId: this.forumId,
discussion: -this.timeCreated,
}, CoreSites.instance.getCurrentSiteId());
}
/**
@ -421,16 +417,34 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy {
* @param discTimecreated The time created of the discussion (if offline).
*/
protected returnToDiscussions(discussionIds?: number[] | null, discTimecreated?: number): void {
const data: any = {
this.forceLeave = true;
// Delete the local files from the tmp folder.
CoreFileUploader.instance.clearTmpFiles(this.newDiscussion.files);
CoreEvents.trigger(
AddonModForumProvider.NEW_DISCUSSION_EVENT,
{
forumId: this.forumId,
cmId: this.cmId,
discussionIds: discussionIds,
discTimecreated: discTimecreated,
};
CoreEvents.trigger(AddonModForumProvider.NEW_DISCUSSION_EVENT, data, CoreSites.instance.getCurrentSiteId());
},
CoreSites.instance.getCurrentSiteId(),
);
// Delete the local files from the tmp folder.
CoreFileUploader.instance.clearTmpFiles(this.newDiscussion.files);
if (CoreScreen.instance.isMobile) {
CoreNavigator.instance.back();
} else {
// Empty form.
this.hasOffline = false;
this.newDiscussion.subject = '';
this.newDiscussion.message = null;
this.newDiscussion.files = [];
this.newDiscussion.postToAllGroups = false;
this.messageEditor.clearText();
this.originalData = CoreUtils.instance.clone(this.newDiscussion);
}
}
/**
@ -555,9 +569,9 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy {
*
* @return Resolved if we can leave it, rejected if not.
*/
async ionViewCanLeave(): Promise<void> {
async canLeave(): Promise<boolean> {
if (this.forceLeave) {
return;
return true;
}
if (AddonModForumHelper.instance.hasPostDataChanged(this.newDiscussion, this.originalData)) {
@ -571,6 +585,8 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy {
if (this.formElement) {
CoreDomUtils.instance.triggerFormCancelledEvent(this.formElement, CoreSites.instance.getCurrentSiteId());
}
return true;
}
/**

View File

@ -42,7 +42,6 @@ export class AddonModForumProvider {
static readonly DISCUSSIONS_PER_PAGE = 10; // Max of discussions per page.
static readonly NEW_DISCUSSION_EVENT = 'addon_mod_forum_new_discussion';
static readonly REPLY_DISCUSSION_EVENT = 'addon_mod_forum_reply_discussion';
static readonly VIEW_DISCUSSION_EVENT = 'addon_mod_forum_view_discussion';
static readonly CHANGE_DISCUSSION_EVENT = 'addon_mod_forum_change_discussion_status';
static readonly MARK_READ_EVENT = 'addon_mod_forum_mark_read';
static readonly LEAVING_POSTS_PAGE = 'addon_mod_forum_leaving_posts_page';