From 26f18ac5f445d376dd22a109569ca82f3af04629 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Wed, 24 Feb 2021 11:38:35 +0100 Subject: [PATCH] MOBILE-3643 forum: Migrate navigation guards --- .../mod/forum/components/index/index.ts | 65 +++++++- src/addons/mod/forum/forum-lazy.module.ts | 13 +- src/addons/mod/forum/forum.module.ts | 10 ++ .../forum/pages/discussion/discussion.html | 2 +- .../pages/discussion/discussion.module.ts | 2 + .../forum/pages/discussion/discussion.page.ts | 154 +++++++++--------- .../new-discussion/new-discussion.module.ts | 42 +++++ ...w-discussion.ts => new-discussion.page.ts} | 48 ++++-- .../mod/forum/services/forum.service.ts | 1 - 9 files changed, 231 insertions(+), 106 deletions(-) create mode 100644 src/addons/mod/forum/pages/new-discussion/new-discussion.module.ts rename src/addons/mod/forum/pages/new-discussion/{new-discussion.ts => new-discussion.page.ts} (95%) diff --git a/src/addons/mod/forum/components/index/index.ts b/src/addons/mod/forum/components/index/index.ts index 39614dcea..d28d61443 100644 --- a/src/addons/mod/forum/components/index/index.ts +++ b/src/addons/mod/forum/components/index/index.ts @@ -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): 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 { @@ -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 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 {} diff --git a/src/addons/mod/forum/forum.module.ts b/src/addons/mod/forum/forum.module.ts index fc3cb6443..ca6a7128b 100644 --- a/src/addons/mod/forum/forum.module.ts +++ b/src/addons/mod/forum/forum.module.ts @@ -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), diff --git a/src/addons/mod/forum/pages/discussion/discussion.html b/src/addons/mod/forum/pages/discussion/discussion.html index 80dbe61fb..976b0e0a6 100644 --- a/src/addons/mod/forum/pages/discussion/discussion.html +++ b/src/addons/mod/forum/pages/discussion/discussion.html @@ -15,7 +15,7 @@ - + diff --git a/src/addons/mod/forum/pages/discussion/discussion.module.ts b/src/addons/mod/forum/pages/discussion/discussion.module.ts index 4e1d182ac..dbfb5f2a1 100644 --- a/src/addons/mod/forum/pages/discussion/discussion.module.ts +++ b/src/addons/mod/forum/pages/discussion/discussion.module.ts @@ -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({ diff --git a/src/addons/mod/forum/pages/discussion/discussion.page.ts b/src/addons/mod/forum/pages/discussion/discussion.page.ts index 39ff0b77e..c874b73ca 100644 --- a/src/addons/mod/forum/pages/discussion/discussion.page.ts +++ b/src/addons/mod/forum/pages/discussion/discussion.page.ts @@ -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 { - try { - const value = await CoreSites.instance.getCurrentSite()!.getLocalSiteConfig('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 { + /** + * Check if we can leave the page or not. + * + * @return Resolved if we can leave it, rejected if not. + */ + async canLeave(): Promise { + 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 { + try { + const value = await CoreSites.instance.getCurrentSite()!.getLocalSiteConfig('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. * diff --git a/src/addons/mod/forum/pages/new-discussion/new-discussion.module.ts b/src/addons/mod/forum/pages/new-discussion/new-discussion.module.ts new file mode 100644 index 000000000..7cbde9b15 --- /dev/null +++ b/src/addons/mod/forum/pages/new-discussion/new-discussion.module.ts @@ -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 {} diff --git a/src/addons/mod/forum/pages/new-discussion/new-discussion.ts b/src/addons/mod/forum/pages/new-discussion/new-discussion.page.ts similarity index 95% rename from src/addons/mod/forum/pages/new-discussion/new-discussion.ts rename to src/addons/mod/forum/pages/new-discussion/new-discussion.page.ts index 65601ee0a..b6341cbc5 100644 --- a/src/addons/mod/forum/pages/new-discussion/new-discussion.ts +++ b/src/addons/mod/forum/pages/new-discussion/new-discussion.page.ts @@ -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 = { - forumId: this.forumId, - cmId: this.cmId, - discussionIds: discussionIds, - discTimecreated: discTimecreated, - }; - CoreEvents.trigger(AddonModForumProvider.NEW_DISCUSSION_EVENT, data, CoreSites.instance.getCurrentSiteId()); + 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, + }, + CoreSites.instance.getCurrentSiteId(), + ); + + 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 { + async canLeave(): Promise { 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; } /** diff --git a/src/addons/mod/forum/services/forum.service.ts b/src/addons/mod/forum/services/forum.service.ts index 1517839bf..6abf71c05 100644 --- a/src/addons/mod/forum/services/forum.service.ts +++ b/src/addons/mod/forum/services/forum.service.ts @@ -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';