MOBILE-3643 forum: Migrate offline discussions

main
Noel De Martin 2021-02-24 14:26:38 +01:00
parent 26f18ac5f4
commit d41523d4bb
5 changed files with 217 additions and 98 deletions

View File

@ -1,3 +1,17 @@
<!-- Buttons to add to the header. -->
<core-navbar-buttons slot="end">
<core-context-menu>
<core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate" [href]="externalUrl" iconAction="open"></core-context-menu-item>
<core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate" (action)="expandDescription()" [iconAction]="'arrow-forward'"></core-context-menu-item>
<core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}" [iconAction]="'fa-newspaper-o'" (action)="gotoBlog($event)"></core-context-menu-item>
<core-context-menu-item *ngIf="discussions.loaded && !(hasOffline || hasOfflineRatings) && isOnline" [priority]="700" [content]="'addon.mod_forum.refreshdiscussions' | translate" (action)="doRefresh(null, $event)" [iconAction]="refreshIcon" [closeOnClick]="false"></core-context-menu-item>
<core-context-menu-item *ngIf="discussions.loaded && (hasOffline || hasOfflineRatings) && isOnline" [priority]="600" [content]="'core.settings.synchronizenow' | translate" (action)="doRefresh(null, $event, true)" [iconAction]="syncIcon" [closeOnClick]="false"></core-context-menu-item>
<core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="500" [content]="prefetchText" (action)="prefetch($event)" [iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item>
<core-context-menu-item *ngIf="size" [priority]="400" [content]="'core.clearstoreddata' | translate:{$a: size}" iconDescription="cube" (action)="removeFiles($event)" iconAction="trash" [closeOnClick]="false"></core-context-menu-item>
<core-context-menu-item *ngIf="sortingAvailable" [priority]="300" [content]="'core.sort' | translate" (action)="showSortOrderSelector()" iconAction="fa-sort"></core-context-menu-item>
</core-context-menu>
</core-navbar-buttons>
<!-- Content. --> <!-- Content. -->
<core-split-view> <core-split-view>
<ion-refresher slot="fixed" [disabled]="!discussions.loaded" (ionRefresh)="doRefresh($event)"> <ion-refresher slot="fixed" [disabled]="!discussions.loaded" (ionRefresh)="doRefresh($event)">
@ -27,7 +41,7 @@
</ion-card> </ion-card>
<ng-container *ngIf="forum"> <ng-container *ngIf="forum">
<core-empty-box *ngIf="discussions.empty && offlineDiscussions.length == 0" icon="chatbubbles" [message]="'addon.mod_forum.forumnodiscussionsyet' | translate"> <core-empty-box *ngIf="discussions.empty" icon="chatbubbles" [message]="'addon.mod_forum.forumnodiscussionsyet' | translate">
</core-empty-box> </core-empty-box>
<div *ngIf="!discussions.empty && sortingAvailable && selectedSortOrder" class="ion-text-wrap addon-forum-sorting-select"> <div *ngIf="!discussions.empty && sortingAvailable && selectedSortOrder" class="ion-text-wrap addon-forum-sorting-select">
@ -41,39 +55,15 @@
</ion-button> </ion-button>
</div> </div>
<ion-item *ngFor="let discussion of offlineDiscussions"
class="ion-text-wrap addon-mod-forum-discussion" detail="true"
[attr.lines="none"]="discussion.groupname" [class.core-selected-item]="discussion.timecreated == -selectedDiscussion"
(click)="openNewDiscussion(discussion.timecreated)">
<ion-label>
<div class="addon-mod-forum-discussion-title">
<h2>
<core-format-text [text]="discussion.subject" contextLevel="module" [contextInstanceId]="module && module.id" [courseId]="courseId"></core-format-text>
</h2>
</div>
<div class="addon-mod-forum-discussion-info">
<core-user-avatar [user]="discussion" slot="start" [courseId]="courseId" *ngIf="discussion.userfullname">
</core-user-avatar>
<div class="addon-mod-forum-discussion-author">
<h3 *ngIf="discussion.userfullname">{{discussion.userfullname}}</h3>
<p *ngIf="discussion.groupname"><ion-icon name="people"></ion-icon> {{ discussion.groupname }}</p>
<p><ion-icon name="time"></ion-icon> {{ 'core.notsent' | translate }}</p>
</div>
</div>
</ion-label>
</ion-item>
<ion-item *ngFor="let discussion of discussions.items" <ion-item *ngFor="let discussion of discussions.items"
class="addon-mod-forum-discussion" detail="true" class="addon-mod-forum-discussion" detail="true"
[class.core-selected-item]="discussions.isSelected(discussion)" [lines]="discussion.groupname && 'none'" [class.core-selected-item]="discussions.isSelected(discussion)"
(click)="discussions.select(discussion)"> (click)="discussions.select(discussion)">
<ion-label> <ion-label>
<div class="addon-mod-forum-discussion-title"> <div class="addon-mod-forum-discussion-title">
<h2 class="ion-text-wrap"> <h2 class="ion-text-wrap">
<ion-icon name="fa-map-pin" *ngIf="discussion.pinned"> <ion-icon name="fa-map-pin" *ngIf="discussion.pinned"></ion-icon>
</ion-icon> <ion-icon name="fa-star" class="addon-forum-star" *ngIf="!discussion.pinned && discussion.starred"></ion-icon>
<ion-icon name="fa-star" class="addon-forum-star" *ngIf="!discussion.pinned && discussion.starred">
</ion-icon>
<core-format-text [text]="discussion.subject" contextLevel="module" [contextInstanceId]="module && module.id" [courseId]="courseId"></core-format-text> <core-format-text [text]="discussion.subject" contextLevel="module" [contextInstanceId]="module && module.id" [courseId]="courseId"></core-format-text>
</h2> </h2>
<ion-button *ngIf="canPin || discussion.canlock || discussion.canfavourite" <ion-button *ngIf="canPin || discussion.canlock || discussion.canfavourite"
@ -85,15 +75,15 @@
</ion-button> </ion-button>
</div> </div>
<div class="addon-mod-forum-discussion-info"> <div class="addon-mod-forum-discussion-info">
<core-user-avatar *ngIf="discussion.userfullname" [user]="discussion" slot="start" [courseId]="courseId"> <core-user-avatar *ngIf="discussion.userfullname" [user]="discussion" slot="start" [courseId]="courseId"></core-user-avatar>
</core-user-avatar>
<div class="addon-mod-forum-discussion-author"> <div class="addon-mod-forum-discussion-author">
<h3 *ngIf="discussion.userfullname">{{discussion.userfullname}}</h3> <h3 *ngIf="discussion.userfullname">{{discussion.userfullname}}</h3>
<p *ngIf="discussion.groupname"><ion-icon name="people"></ion-icon> {{ discussion.groupname }}</p> <p *ngIf="discussion.groupname"><ion-icon name="people"></ion-icon> {{ discussion.groupname }}</p>
<p>{{discussion.created * 1000 | coreFormatDate: "strftimerecentfull"}}</p> <p *ngIf="discussions.isOnlineDiscussion(discussion)">{{discussion.created * 1000 | coreFormatDate: "strftimerecentfull"}}</p>
<p *ngIf="discussions.isOfflineDiscussion(discussion)"><ion-icon name="time"></ion-icon> {{ 'core.notsent' | translate }}</p>
</div> </div>
</div> </div>
<ion-row class="ion-text-center addon-mod-forum-discussion-more-info"> <ion-row *ngIf="discussions.isOnlineDiscussion(discussion)" class="ion-text-center addon-mod-forum-discussion-more-info">
<ion-col class="ion-text-start"> <ion-col class="ion-text-start">
<ion-note> <ion-note>
<ion-icon name="time"></ion-icon> {{ 'addon.mod_forum.lastpost' | translate }} <ion-icon name="time"></ion-icon> {{ 'addon.mod_forum.lastpost' | translate }}

View File

@ -28,7 +28,7 @@ import { ModalController, PopoverController, Translate } from '@singletons';
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
import { AddonModForumHelper } from '@addons/mod/forum/services/helper.service'; import { AddonModForumHelper } from '@addons/mod/forum/services/helper.service';
import { CoreGroups, CoreGroupsProvider } from '@services/groups'; import { CoreGroups, CoreGroupsProvider } from '@services/groups';
import { CoreEvents } from '@singletons/events'; import { CoreEvents, CoreEventObserver } from '@singletons/events';
import { AddonModForumSyncProvider } from '@addons/mod/forum/services/sync.service'; import { AddonModForumSyncProvider } from '@addons/mod/forum/services/sync.service';
import { CoreSites } from '@services/sites'; import { CoreSites } from '@services/sites';
import { CoreUser } from '@features/user/services/user'; import { CoreUser } from '@features/user/services/user';
@ -40,24 +40,7 @@ import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { AddonModForumDiscussionOptionsMenuComponent } from '../discussion-options-menu/discussion-options-menu'; import { AddonModForumDiscussionOptionsMenuComponent } from '../discussion-options-menu/discussion-options-menu';
import { AddonModForumSortOrderSelectorComponent } from '../sort-order-selector/sort-order-selector'; import { AddonModForumSortOrderSelectorComponent } from '../sort-order-selector/sort-order-selector';
import { CoreScreen } from '@services/screen'; import { CoreScreen } from '@services/screen';
import { CoreArray } from '@singletons/array';
/**
* Type to use for selecting new discussion form in the discussions manager.
*/
type NewDiscussionForm = {
newDiscussion: true;
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. * Component that displays a forum entry page.
@ -78,8 +61,6 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
canLoadMore = false; canLoadMore = false;
loadMoreError = false; loadMoreError = false;
discussions: AddonModForumDiscussionsManager; discussions: AddonModForumDiscussionsManager;
offlineDiscussions: AddonModForumOfflineDiscussion[] = [];
selectedDiscussion = 0; // Disucssion ID or negative timecreated if it's an offline discussion.
canAddDiscussion = false; canAddDiscussion = false;
addDiscussionText!: string; addDiscussionText!: string;
availabilityMessage: string | null = null; availabilityMessage: string | null = null;
@ -93,11 +74,11 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
protected page = 0; protected page = 0;
trackPosts = false; trackPosts = false;
protected usesGroups = false; protected usesGroups = false;
protected syncManualObserver: any; // It will observe the sync manual event. protected syncManualObserver?: CoreEventObserver; // It will observe the sync manual event.
protected replyObserver: any; protected replyObserver?: CoreEventObserver;
protected newDiscObserver: any; protected newDiscObserver?: CoreEventObserver;
protected viewDiscObserver: any; protected viewDiscObserver?: CoreEventObserver;
protected changeDiscObserver: any; protected changeDiscObserver?: CoreEventObserver;
hasOfflineRatings?: boolean; hasOfflineRatings?: boolean;
protected ratingOfflineObserver: any; protected ratingOfflineObserver: any;
@ -141,6 +122,41 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
AddonModForumProvider.REPLY_DISCUSSION_EVENT, AddonModForumProvider.REPLY_DISCUSSION_EVENT,
this.eventReceived.bind(this, false), this.eventReceived.bind(this, false),
); );
this.changeDiscObserver = CoreEvents.on(AddonModForumProvider.CHANGE_DISCUSSION_EVENT, (data: any) => {
if ((this.forum && this.forum.id === data.forumId) || data.cmId === this.module!.id) {
AddonModForum.instance.invalidateDiscussionsList(this.forum!.id).finally(() => {
if (data.discussionId) {
// Discussion changed, search it in the list of discussions.
const discussion = this.discussions.items.find(
(disc) => this.discussions.isOnlineDiscussion(disc) && data.discussionId == disc.discussion,
) as AddonModForumDiscussion;
if (discussion) {
if (typeof data.locked != 'undefined') {
discussion.locked = data.locked;
}
if (typeof data.pinned != 'undefined') {
discussion.pinned = data.pinned;
}
if (typeof data.starred != 'undefined') {
discussion.starred = data.starred;
}
this.showLoadingAndRefresh(false);
}
}
if (typeof data.deleted != 'undefined' && data.deleted) {
if (data.post.parentid == 0 && CoreScreen.instance.isTablet && !this.discussions.empty) {
// Discussion deleted, clear details page.
this.discussions.select(this.discussions[0]);
}
this.showLoadingAndRefresh(false);
}
});
}
});
} }
async ngAfterViewInit(): Promise<void> { async ngAfterViewInit(): Promise<void> {
@ -356,7 +372,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
this.hasOffline = !!offlineDiscussions.length; this.hasOffline = !!offlineDiscussions.length;
if (!this.hasOffline) { if (!this.hasOffline) {
this.offlineDiscussions = []; this.discussions.setOfflineDiscussions([]);
return; return;
} }
@ -387,7 +403,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
// Sort discussion by time (newer first). // Sort discussion by time (newer first).
offlineDiscussions.sort((a, b) => b.timecreated - a.timecreated); offlineDiscussions.sort((a, b) => b.timecreated - a.timecreated);
this.offlineDiscussions = offlineDiscussions; this.discussions.setOfflineDiscussions(offlineDiscussions);
} }
/** /**
@ -435,7 +451,12 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
} }
} }
this.discussions.setItems(this.page === 0 ? discussions : this.discussions.items.concat(discussions)); if (this.page === 0) {
this.discussions.setOnlineDiscussions(discussions);
} else {
this.discussions.setItems(this.discussions.items.concat(discussions));
}
this.canLoadMore = response.canLoadMore; this.canLoadMore = response.canLoadMore;
this.page++; this.page++;
@ -540,18 +561,20 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
this.showLoadingAndRefresh(false).finally(() => { this.showLoadingAndRefresh(false).finally(() => {
// If it's a new discussion in tablet mode, try to open it. // If it's a new discussion in tablet mode, try to open it.
if (isNewDiscussion && CoreScreen.instance.isTablet) { if (isNewDiscussion && CoreScreen.instance.isTablet) {
if (data.discussionIds) { const discussion = this.discussions.items.find(disc => {
// Discussion sent to server, search it in the list of discussions. if (this.discussions.isOfflineDiscussion(disc)) {
const discussion = this.discussions.items.find( return disc.timecreated === data.discTimecreated;
(disc) => }
!isNewDiscussionForm(disc) &&
data.discussionIds.indexOf(disc.discussion) >= 0,
);
if (this.discussions.isOnlineDiscussion(disc)) {
return CoreArray.contains(data.discussionIds, disc.discussion);
}
return false;
});
if (discussion || !this.discussions.empty) {
this.discussions.select(discussion ?? this.discussions.items[0]); this.discussions.select(discussion ?? this.discussions.items[0]);
} else if (data.discTimecreated) {
// It's an offline discussion, open it.
this.openNewDiscussion(data.discTimecreated);
} }
} }
}); });
@ -566,11 +589,8 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
* *
* @param timeCreated Creation time of the offline discussion. * @param timeCreated Creation time of the offline discussion.
*/ */
openNewDiscussion(timeCreated: number = 0): void { openNewDiscussion(): void {
this.discussions.select({ this.discussions.select({ newDiscussion: true });
newDiscussion: true,
timeCreated,
});
} }
/** /**
@ -650,7 +670,20 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom
} }
class AddonModForumDiscussionsManager extends CorePageItemsListManager<AddonModForumDiscussion | NewDiscussionForm> { /**
* Type to select the new discussion form.
*/
type NewDiscussionForm = { newDiscussion: true };
/**
* Type of items that can be held by the discussions manager.
*/
type DiscussionItem = AddonModForumDiscussion | AddonModForumOfflineDiscussion | NewDiscussionForm;
/**
* Discussions manager.
*/
class AddonModForumDiscussionsManager extends CorePageItemsListManager<DiscussionItem> {
private discussionsPathPrefix: string; private discussionsPathPrefix: string;
private component: AddonModForumIndexComponent; private component: AddonModForumIndexComponent;
@ -662,29 +695,107 @@ class AddonModForumDiscussionsManager extends CorePageItemsListManager<AddonModF
this.discussionsPathPrefix = discussionsPathPrefix; this.discussionsPathPrefix = discussionsPathPrefix;
} }
getItemQueryParams(discussion: AddonModForumDiscussion | NewDiscussionForm): Params { get onlineDiscussions(): AddonModForumDiscussion[] {
return this.items.filter(discussion => this.isOnlineDiscussion(discussion)) as AddonModForumDiscussion[];
}
/**
* @inheritdoc
*/
getItemQueryParams(discussion: DiscussionItem): Params {
return { return {
courseId: this.component.courseId, courseId: this.component.courseId,
cmId: this.component.module!.id, cmId: this.component.module!.id,
forumId: this.component.forum!.id, forumId: this.component.forum!.id,
...( ...(this.isOnlineDiscussion(discussion) ? { discussion, trackPosts: this.component.trackPosts } : {}),
isNewDiscussionForm(discussion)
? { timeCreated: discussion.timeCreated }
: { discussion, trackPosts: this.component.trackPosts }
),
}; };
} }
protected getItemPath(discussion: AddonModForumDiscussion | NewDiscussionForm): string { /**
const discussionId = isNewDiscussionForm(discussion) ? 'new' : discussion.id; * Type guard to infer NewDiscussionForm objects.
*
return this.discussionsPathPrefix + discussionId; * @param discussion Item to check.
* @return Whether the item is a new discussion form.
*/
isNewDiscussionForm(discussion: DiscussionItem): discussion is NewDiscussionForm {
return 'newDiscussion' in discussion;
} }
protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null { /**
const discussionId = route.params.discussionId; * Type guard to infer AddonModForumDiscussion objects.
*
* @param discussion Item to check.
* @return Whether the item is an online discussion.
*/
isOfflineDiscussion(discussion: DiscussionItem): discussion is AddonModForumOfflineDiscussion {
return !this.isNewDiscussionForm(discussion)
&& !this.isOnlineDiscussion(discussion);
}
return discussionId ? this.discussionsPathPrefix + discussionId : null; /**
* Type guard to infer AddonModForumDiscussion objects.
*
* @param discussion Item to check.
* @return Whether the item is an online discussion.
*/
isOnlineDiscussion(discussion: DiscussionItem): discussion is AddonModForumDiscussion {
return 'id' in discussion;
}
/**
* Update online discussion items.
*
* @param onlineDiscussions Online discussions
*/
setOnlineDiscussions(onlineDiscussions: AddonModForumDiscussion[]): void {
const otherDiscussions = this.items.filter(discussion => !this.isOnlineDiscussion(discussion));
this.setItems(otherDiscussions.concat(onlineDiscussions));
}
/**
* Update offline discussion items.
*
* @param offlineDiscussions Offline discussions
*/
setOfflineDiscussions(offlineDiscussions: AddonModForumOfflineDiscussion[]): void {
const otherDiscussions = this.items.filter(discussion => !this.isOfflineDiscussion(discussion));
this.setItems((offlineDiscussions as DiscussionItem[]).concat(otherDiscussions));
}
/**
* @inheritdoc
*/
protected getItemPath(discussion: DiscussionItem): string {
const getRelativePath = () => {
if (this.isOnlineDiscussion(discussion)) {
return discussion.id;
}
if (this.isOfflineDiscussion(discussion)) {
return `new/${discussion.timecreated}`;
}
return 'new/0';
};
return this.discussionsPathPrefix + getRelativePath();
}
/**
* @inheritdoc
*/
protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
if (route.params.discussionId) {
return this.discussionsPathPrefix + route.params.discussionId;
}
if (route.params.timeCreated) {
return this.discussionsPathPrefix + `new/${route.params.timeCreated}`;
}
return null;
} }
} }

View File

@ -28,7 +28,7 @@ const mobileRoutes: Routes = [
component: AddonModForumIndexPage, component: AddonModForumIndexPage,
}, },
{ {
path: ':courseId/:cmId/new', path: ':courseId/:cmId/new/:timeCreated',
loadChildren: () => import('./pages/new-discussion/new-discussion.module').then(m => m.AddonForumNewDiscussionPageModule), loadChildren: () => import('./pages/new-discussion/new-discussion.module').then(m => m.AddonForumNewDiscussionPageModule),
}, },
{ {
@ -43,7 +43,7 @@ const tabletRoutes: Routes = [
component: AddonModForumIndexPage, component: AddonModForumIndexPage,
children: [ children: [
{ {
path: 'new', path: 'new/:timeCreated',
loadChildren: () => import('./pages/new-discussion/new-discussion.module') loadChildren: () => import('./pages/new-discussion/new-discussion.module')
.then(m => m.AddonForumNewDiscussionPageModule), .then(m => m.AddonForumNewDiscussionPageModule),
}, },

View File

@ -34,7 +34,7 @@ const mainMenuRoutes: Routes = [
...conditionalRoutes( ...conditionalRoutes(
[ [
{ {
path: 'course/index/contents/mod_forum/new', path: 'course/index/contents/mod_forum/new/:timeCreated',
loadChildren: () => import('./pages/new-discussion/new-discussion.module') loadChildren: () => import('./pages/new-discussion/new-discussion.module')
.then(m => m.AddonForumNewDiscussionPageModule), .then(m => m.AddonForumNewDiscussionPageModule),
}, },
@ -50,7 +50,7 @@ const mainMenuRoutes: Routes = [
const courseContentsRoutes: Routes = conditionalRoutes( const courseContentsRoutes: Routes = conditionalRoutes(
[ [
{ {
path: 'mod_forum/new', path: 'mod_forum/new/:timeCreated',
loadChildren: () => import('./pages/new-discussion/new-discussion.module') loadChildren: () => import('./pages/new-discussion/new-discussion.module')
.then(m => m.AddonForumNewDiscussionPageModule), .then(m => m.AddonForumNewDiscussionPageModule),
}, },

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 { ActivatedRouteSnapshot, Params } from '@angular/router'; import { ActivatedRoute, ActivatedRouteSnapshot, Params } from '@angular/router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreSplitViewComponent } from '@components/split-view/split-view';
@ -135,17 +135,19 @@ export abstract class CorePageItemsListManager<Item> {
} }
// If this item is already selected, do nothing. // If this item is already selected, do nothing.
const itemRoute = this.getItemRoute(route);
const itemPath = this.getItemPath(item); const itemPath = this.getItemPath(item);
const selectedItemPath = itemRoute ? this.getSelectedItemPath(itemRoute.snapshot) : null;
if (route.firstChild?.routeConfig?.path === itemPath) { if (selectedItemPath === itemPath) {
return; return;
} }
// Navigate to item. // Navigate to item.
const params = this.getItemQueryParams(item); const params = this.getItemQueryParams(item);
const pathPrefix = route.firstChild ? itemPath.split('/').fill('../').join('') : ''; const pathPrefix = selectedItemPath ? selectedItemPath.split('/').fill('../').join('') : '';
await CoreNavigator.instance.navigate(pathPrefix + itemPath, { params }); await CoreNavigator.instance.navigate(pathPrefix + itemPath, { params, reset: true });
} }
/** /**
@ -220,4 +222,20 @@ export abstract class CorePageItemsListManager<Item> {
*/ */
protected abstract getSelectedItemPath(route: ActivatedRouteSnapshot): string | null; protected abstract getSelectedItemPath(route: ActivatedRouteSnapshot): string | null;
/**
* Get the active item route, if any.
*
* @param pageRoute Page route.
* @return Item route.
*/
private getItemRoute(pageRoute: ActivatedRoute): ActivatedRoute | null {
let itemRoute = pageRoute.firstChild;
while (itemRoute && !itemRoute.component) {
itemRoute = itemRoute.firstChild;
}
return itemRoute;
}
} }