From e450659697da70ab898d7454337d48d649cedf27 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Tue, 23 Feb 2021 10:02:37 +0100 Subject: [PATCH] MOBILE-3643 forum: Migrate single activity view --- .../mod/forum/components/index/index.html | 229 +++++++++--------- .../mod/forum/components/index/index.ts | 128 +++++++--- src/addons/mod/forum/forum-lazy.module.ts | 8 +- src/addons/mod/forum/forum.module.ts | 27 ++- .../pages/discussion/discussion.module.ts | 38 +++ .../{discussion.ts => discussion.page.ts} | 0 src/core/classes/page-items-list-manager.ts | 4 +- src/core/components/split-view/split-view.ts | 5 +- .../pages/contents/contents-routing.module.ts | 33 +++ .../course/pages/contents/contents.module.ts | 33 ++- 10 files changed, 338 insertions(+), 167 deletions(-) create mode 100644 src/addons/mod/forum/pages/discussion/discussion.module.ts rename src/addons/mod/forum/pages/discussion/{discussion.ts => discussion.page.ts} (100%) create mode 100644 src/core/features/course/pages/contents/contents-routing.module.ts diff --git a/src/addons/mod/forum/components/index/index.html b/src/addons/mod/forum/components/index/index.html index b5504a53a..761c17ed4 100644 --- a/src/addons/mod/forum/components/index/index.html +++ b/src/addons/mod/forum/components/index/index.html @@ -1,127 +1,124 @@ - - - - - + + + + - - - + + + - - - - - {{ 'core.hasdatatosync' | translate:{$a: moduleName} }} - - + + + + + {{ 'core.hasdatatosync' | translate:{$a: moduleName} }} + + - - - - - {{ availabilityMessage }} - - + + + + + {{ availabilityMessage }} + + - - - + + + -
- - {{ selectedSortOrder.label | translate }} -
-
-
+
+ + {{ selectedSortOrder.label | translate }} +
+
+
- - -
-

- -

+ + +
+

+ +

+
+
+ + +
+

{{discussion.userfullname}}

+

{{ discussion.groupname }}

+

{{ 'core.notsent' | translate }}

-
- - -
-

{{discussion.userfullname}}

-

{{ discussion.groupname }}

-

{{ 'core.notsent' | translate }}

-
-
- - +
+
+
- - -
-

- - - - - -

- - - - + + +
+

+ + + + + +

+ + + + +
+
+ + +
+

{{discussion.userfullname}}

+

{{ discussion.groupname }}

+

{{discussion.created * 1000 | coreFormatDate: "strftimerecentfull"}}

-
- - -
-

{{discussion.userfullname}}

-

{{ discussion.groupname }}

-

{{discussion.created * 1000 | coreFormatDate: "strftimerecentfull"}}

-
-
- - - - {{ 'addon.mod_forum.lastpost' | translate }} - {{discussion.timemodified | coreTimeAgo}} - {{discussion.created | coreTimeAgo}} - - - - - {{ 'addon.mod_forum.numreplies' | translate:{numreplies: discussion.numreplies} }} - - {{ discussion.numunread }} - - - - - - +
+ + + + {{ 'addon.mod_forum.lastpost' | translate }} + {{discussion.timemodified | coreTimeAgo}} + {{discussion.created | coreTimeAgo}} + + + + + {{ 'addon.mod_forum.numreplies' | translate:{numreplies: discussion.numreplies} }} + + {{ discussion.numunread }} + + + + +
+
+ + - - - - - - - - - - + + + + + + diff --git a/src/addons/mod/forum/components/index/index.ts b/src/addons/mod/forum/components/index/index.ts index 702f1b44c..df4d71950 100644 --- a/src/addons/mod/forum/components/index/index.ts +++ b/src/addons/mod/forum/components/index/index.ts @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Optional, OnInit, OnDestroy } from '@angular/core'; +import { Component, Optional, OnInit, OnDestroy, ViewChild, AfterViewInit } from '@angular/core'; +import { ActivatedRoute, ActivatedRouteSnapshot, Params } from '@angular/router'; import { IonContent } from '@ionic/angular'; import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component'; import { @@ -34,6 +35,8 @@ import { CoreUser } from '@features/user/services/user'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreUtils } from '@services/utils/utils'; import { CoreCourse } from '@features/course/services/course'; +import { CorePageItemsListManager } from '@classes/page-items-list-manager'; +import { CoreSplitViewComponent } from '@components/split-view/split-view'; /** * Component that displays a forum entry page. @@ -43,32 +46,32 @@ import { CoreCourse } from '@features/course/services/course'; templateUrl: 'index.html', styleUrls: ['index.scss'], }) -export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit, OnDestroy { +export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit, AfterViewInit, OnDestroy { + + @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; component = AddonModForumProvider.COMPONENT; moduleName = 'forum'; - descriptionNote?: string; forum?: AddonModForumData; canLoadMore = false; loadMoreError = false; - discussions: AddonModForumDiscussion[] = []; + discussions: AddonModForumDiscussionsManager; offlineDiscussions: AddonModForumOfflineDiscussion[] = []; selectedDiscussion = 0; // Disucssion ID or negative timecreated if it's an offline discussion. canAddDiscussion = false; addDiscussionText!: string; availabilityMessage: string | null = null; - sortingAvailable!: boolean; sortOrders: AddonModForumSortOrder[] = []; selectedSortOrder: AddonModForumSortOrder | null = null; sortOrderSelectorExpanded = false; + canPin = false; protected syncEventName = AddonModForumSyncProvider.AUTO_SYNCED; protected page = 0; - protected trackPosts = false; + trackPosts = false; protected usesGroups = false; - protected canPin = false; protected syncManualObserver: any; // It will observe the sync manual event. protected replyObserver: any; protected newDiscObserver: any; @@ -80,10 +83,17 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom protected ratingSyncObserver: any; constructor( + route: ActivatedRoute, @Optional() protected content?: IonContent, @Optional() courseContentsPage?: CoreCourseContentsPage, ) { super('AddonModForumIndexComponent', content, courseContentsPage); + + this.discussions = new AddonModForumDiscussionsManager( + route.component, + this, + courseContentsPage ? 'mod_forum/' : '', + ); } /** @@ -95,6 +105,9 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom this.sortOrders = AddonModForum.instance.getAvailableSortOrders(); await super.ngOnInit(); + } + + async ngAfterViewInit(): Promise { await this.loadContent(false, true); if (!this.forum) { @@ -110,6 +123,8 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom return; }), ); + + this.discussions.start(this.splitView); } /** @@ -389,10 +404,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom } } - this.discussions = this.page === 0 - ? discussions - : this.discussions.concat(discussions); - + this.discussions.setItems(this.page === 0 ? discussions : this.discussions.items.concat(discussions)); this.canLoadMore = response.canLoadMore; this.page++; @@ -486,24 +498,6 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom return result.updated; } - /** - * Opens a discussion. - * - * @param discussion Discussion object. - */ - openDiscussion(discussion: AddonModForumDiscussion): void { - alert(`Open discussion ${discussion.id}: Not implemented!`); - - // @todo - // const params = { - // courseId: this.courseId, - // cmId: this.module.id, - // forumId: this.forum.id, - // discussion: discussion, - // trackPosts: this.trackPosts, - // }; - // this.splitviewCtrl.push('AddonModForumDiscussionPage', params); - } /** * Opens the new discussion form. @@ -560,4 +554,80 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom // this.sortOrderSelectorExpanded = true; } + /** + * Show the context menu. + * + * @param e Click Event. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + showOptionsMenu(e: Event, discussion: any): void { + alert('Show options menu not implemented'); + + // @todo + // e.preventDefault(); + // e.stopPropagation(); + + // const popover = this.popoverCtrl.create(AddonForumDiscussionOptionsMenuComponent, { + // discussion: discussion, + // forumId: this.forum.id, + // cmId: this.module.id, + // }); + // popover.onDidDismiss((data) => { + // if (data && data.action) { + // switch (data.action) { + // case 'lock': + // discussion.locked = data.value; + // break; + // case 'pin': + // discussion.pinned = data.value; + // break; + // case 'star': + // discussion.starred = data.value; + // break; + // default: + // break; + // } + // } + // }); + // popover.present({ + // ev: e, + // }); + } + +} + +class AddonModForumDiscussionsManager extends CorePageItemsListManager { + + private discussionsPathPrefix: string; + private component: AddonModForumIndexComponent; + + constructor(pageComponent: unknown, component: AddonModForumIndexComponent, discussionsPathPrefix: string) { + super(pageComponent); + + this.component = component; + this.discussionsPathPrefix = discussionsPathPrefix; + } + + getItemQueryParams(discussion: AddonModForumDiscussion): Params { + return { + discussion, + courseId: this.component.courseId, + cmId: this.component.module!.id, + forumId: this.component.forum!.id, + trackPosts: this.component.trackPosts, + }; + } + + protected getItemPath(discussion: AddonModForumDiscussion): string { + const discussionId = discussion.id; + + return this.discussionsPathPrefix + discussionId; + } + + protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null { + const discussionId = route.params.discussionId; + + return discussionId ? this.discussionsPathPrefix + discussionId : null; + } + } diff --git a/src/addons/mod/forum/forum-lazy.module.ts b/src/addons/mod/forum/forum-lazy.module.ts index 827da9d7a..d46b5c4f7 100644 --- a/src/addons/mod/forum/forum-lazy.module.ts +++ b/src/addons/mod/forum/forum-lazy.module.ts @@ -19,7 +19,6 @@ import { CoreSharedModule } from '@/core/shared.module'; import { AddonModForumComponentsModule } from './components/components.module'; import { AddonModForumIndexPage } from './pages/index'; -import { AddonModForumDiscussionPage } from './pages/discussion/discussion'; import { conditionalRoutes } from '@/app/app-routing.module'; import { CoreScreen } from '@services/screen'; @@ -30,7 +29,8 @@ const mobileRoutes: Routes = [ }, { path: ':courseId/:cmId/:discussionId', - component: AddonModForumDiscussionPage, + loadChildren: () => import('./pages/discussion/discussion.module').then(m => m.AddonForumDiscussionPageModule), + }, ]; @@ -41,7 +41,8 @@ const tabletRoutes: Routes = [ children: [ { path: ':discussionId', - component: AddonModForumDiscussionPage, + loadChildren: () => import('./pages/discussion/discussion.module').then(m => m.AddonForumDiscussionPageModule), + }, ], }, @@ -60,7 +61,6 @@ const routes: Routes = [ ], declarations: [ AddonModForumIndexPage, - AddonModForumDiscussionPage, ], }) export class AddonModForumLazyModule {} diff --git a/src/addons/mod/forum/forum.module.ts b/src/addons/mod/forum/forum.module.ts index 56acdf148..fc3cb6443 100644 --- a/src/addons/mod/forum/forum.module.ts +++ b/src/addons/mod/forum/forum.module.ts @@ -15,24 +15,47 @@ import { APP_INITIALIZER, NgModule } from '@angular/core'; import { Routes } from '@angular/router'; +import { conditionalRoutes } from '@/app/app-routing.module'; import { CORE_SITE_SCHEMAS } from '@services/sites'; +import { CoreCourseContentsRoutingModule } from '@features/course/pages/contents/contents-routing.module'; import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module'; +import { CoreScreen } from '@services/screen'; import { AddonModForumComponentsModule } from './components/components.module'; import { AddonModForumModuleHandler, AddonModForumModuleHandlerService } from './services/handlers/module'; import { SITE_SCHEMA } from './services/offline-db'; -const routes: Routes = [ +const mainMenuRoutes: Routes = [ { path: AddonModForumModuleHandlerService.PAGE_NAME, loadChildren: () => import('./forum-lazy.module').then(m => m.AddonModForumLazyModule), }, + ...conditionalRoutes( + [ + { + path: 'course/index/contents/mod_forum/:discussionId', + loadChildren: () => import('./pages/discussion/discussion.module').then(m => m.AddonForumDiscussionPageModule), + }, + ], + () => CoreScreen.instance.isMobile, + ), ]; +const courseContentsRoutes: Routes = conditionalRoutes( + [ + { + path: 'mod_forum/:discussionId', + loadChildren: () => import('./pages/discussion/discussion.module').then(m => m.AddonForumDiscussionPageModule), + }, + ], + () => CoreScreen.instance.isTablet, +); + @NgModule({ imports: [ - CoreMainMenuTabRoutingModule.forChild(routes), + CoreMainMenuTabRoutingModule.forChild(mainMenuRoutes), + CoreCourseContentsRoutingModule.forChild({ children: courseContentsRoutes }), AddonModForumComponentsModule, ], providers: [ diff --git a/src/addons/mod/forum/pages/discussion/discussion.module.ts b/src/addons/mod/forum/pages/discussion/discussion.module.ts new file mode 100644 index 000000000..4e1d182ac --- /dev/null +++ b/src/addons/mod/forum/pages/discussion/discussion.module.ts @@ -0,0 +1,38 @@ +// (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 { CoreSharedModule } from '@/core/shared.module'; + +import { AddonModForumDiscussionPage } from './discussion.page'; + +const routes: Routes = [{ + path: '', + component: AddonModForumDiscussionPage, +}]; + +@NgModule({ + imports: [ + RouterModule.forChild(routes), + CoreSharedModule, + AddonModForumComponentsModule, + ], + declarations: [ + AddonModForumDiscussionPage, + ], +}) +export class AddonForumDiscussionPageModule {} diff --git a/src/addons/mod/forum/pages/discussion/discussion.ts b/src/addons/mod/forum/pages/discussion/discussion.page.ts similarity index 100% rename from src/addons/mod/forum/pages/discussion/discussion.ts rename to src/addons/mod/forum/pages/discussion/discussion.page.ts diff --git a/src/core/classes/page-items-list-manager.ts b/src/core/classes/page-items-list-manager.ts index 0810ede23..38c6d6c79 100644 --- a/src/core/classes/page-items-list-manager.ts +++ b/src/core/classes/page-items-list-manager.ts @@ -142,10 +142,10 @@ export abstract class CorePageItemsListManager { } // Navigate to item. - const path = route.firstChild ? `../${itemPath}` : itemPath; const params = this.getItemQueryParams(item); + const pathPrefix = route.firstChild ? itemPath.split('/').fill('../').join('') : ''; - await CoreNavigator.instance.navigate(path, { params }); + await CoreNavigator.instance.navigate(pathPrefix + itemPath, { params }); } /** diff --git a/src/core/components/split-view/split-view.ts b/src/core/components/split-view/split-view.ts index 870db7056..153817f65 100644 --- a/src/core/components/split-view/split-view.ts +++ b/src/core/components/split-view/split-view.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { AfterViewInit, Component, ElementRef, HostBinding, Input, OnDestroy, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, Input, OnDestroy, ViewChild } from '@angular/core'; import { ActivatedRouteSnapshot } from '@angular/router'; import { IonContent, IonRouterOutlet } from '@ionic/angular'; import { CoreScreen } from '@services/screen'; @@ -33,7 +33,6 @@ export class CoreSplitViewComponent implements AfterViewInit, OnDestroy { @ViewChild(IonContent) menuContent!: IonContent; @ViewChild(IonRouterOutlet) contentOutlet!: IonRouterOutlet; - @HostBinding('class') classes = ''; @Input() placeholderText = 'core.emptysplit'; @Input() mode?: CoreSplitViewMode; isNested = false; @@ -92,7 +91,7 @@ export class CoreSplitViewComponent implements AfterViewInit, OnDestroy { classes.push('nested'); } - this.classes = classes.join(' '); + this.element.nativeElement.setAttribute('class', classes.join(' ')); } /** diff --git a/src/core/features/course/pages/contents/contents-routing.module.ts b/src/core/features/course/pages/contents/contents-routing.module.ts new file mode 100644 index 000000000..2a5dcf26b --- /dev/null +++ b/src/core/features/course/pages/contents/contents-routing.module.ts @@ -0,0 +1,33 @@ +// (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 { InjectionToken, ModuleWithProviders, NgModule } from '@angular/core'; + +import { ModuleRoutesConfig } from '@/app/app-routing.module'; + +export const COURSE_CONTENTS_ROUTES = new InjectionToken('COURSE_CONTENTS_ROUTES'); + +@NgModule() +export class CoreCourseContentsRoutingModule { + + static forChild(routes: ModuleRoutesConfig): ModuleWithProviders { + return { + ngModule: CoreCourseContentsRoutingModule, + providers: [ + { provide: COURSE_CONTENTS_ROUTES, multi: true, useValue: routes }, + ], + }; + } + +} diff --git a/src/core/features/course/pages/contents/contents.module.ts b/src/core/features/course/pages/contents/contents.module.ts index 698eaeca7..54b6f07fb 100644 --- a/src/core/features/course/pages/contents/contents.module.ts +++ b/src/core/features/course/pages/contents/contents.module.ts @@ -12,23 +12,34 @@ // 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 { Injector, NgModule } from '@angular/core'; +import { RouterModule, ROUTES, Routes } from '@angular/router'; -import { CoreSharedModule } from '@/core/shared.module'; -import { CoreCourseContentsPage } from './contents'; import { CoreCourseComponentsModule } from '@features/course/components/components.module'; +import { CoreSharedModule } from '@/core/shared.module'; +import { resolveModuleRoutes } from '@/app/app-routing.module'; -const routes: Routes = [ - { - path: '', - component: CoreCourseContentsPage, - }, -]; +import { CoreCourseContentsPage } from './contents'; +import { COURSE_CONTENTS_ROUTES } from './contents-routing.module'; + +function buildRoutes(injector: Injector): Routes { + const routes = resolveModuleRoutes(injector, COURSE_CONTENTS_ROUTES); + + return [ + { + path: '', + component: CoreCourseContentsPage, + children: routes.children, + }, + ...routes.siblings, + ]; +} @NgModule({ + providers: [ + { provide: ROUTES, multi: true, useFactory: buildRoutes, deps: [Injector] }, + ], imports: [ - RouterModule.forChild(routes), CoreSharedModule, CoreCourseComponentsModule, ],