MOBILE-3320 navigation: Simplify items manager
parent
1e3863e949
commit
df0059e74d
|
@ -20,7 +20,7 @@ import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||||
import { ActivatedRouteSnapshot, Params } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
|
||||||
|
@ -131,11 +131,4 @@ class AddonBadgesUserBadgesManager extends CorePageItemsListManager<AddonBadgesU
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
|
|
||||||
return route.params.badgeHash ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||||
import {
|
import {
|
||||||
AddonCompetencyDataForPlanPageCompetency, AddonCompetencyDataForCourseCompetenciesPageCompetency, AddonCompetency,
|
AddonCompetencyDataForPlanPageCompetency, AddonCompetencyDataForCourseCompetenciesPageCompetency, AddonCompetency,
|
||||||
} from '../../services/competency';
|
} from '../../services/competency';
|
||||||
import { Params, ActivatedRouteSnapshot, ActivatedRoute } from '@angular/router';
|
import { Params, ActivatedRoute } from '@angular/router';
|
||||||
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
@ -162,11 +162,4 @@ class AddonCompetencyListManager extends CorePageItemsListManager<AddonCompetenc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
|
|
||||||
return route.params.competencyId ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import { AddonCompetencyProvider, AddonCompetencyPlan, AddonCompetency } from '.
|
||||||
import { AddonCompetencyHelper } from '../../services/competency-helper';
|
import { AddonCompetencyHelper } from '../../services/competency-helper';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||||
import { ActivatedRouteSnapshot } from '@angular/router';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the list of learning plans.
|
* Page that displays the list of learning plans.
|
||||||
|
@ -130,11 +129,4 @@ class AddonCompetencyPlanListManager extends CorePageItemsListManager<AddonCompe
|
||||||
return String(plan.id);
|
return String(plan.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
|
|
||||||
return route.params.planId ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, OnDestroy, AfterViewInit, ViewChild } from '@angular/core';
|
import { Component, OnDestroy, AfterViewInit, ViewChild } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot, Params } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
@ -368,13 +368,6 @@ class AddonModAssignSubmissionListManager extends CorePageItemsListManager<Addon
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
|
|
||||||
return route.params.submitId ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, Optional, OnInit, OnDestroy, ViewChild, AfterViewInit } from '@angular/core';
|
import { Component, Optional, OnInit, OnDestroy, ViewChild, AfterViewInit } from '@angular/core';
|
||||||
import { ActivatedRoute, ActivatedRouteSnapshot, Params } from '@angular/router';
|
import { ActivatedRoute, Params } from '@angular/router';
|
||||||
import { IonContent } from '@ionic/angular';
|
import { IonContent } from '@ionic/angular';
|
||||||
import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component';
|
import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component';
|
||||||
import {
|
import {
|
||||||
|
@ -820,19 +820,4 @@ class AddonModForumDiscussionsManager extends CorePageItemsListManager<Discussio
|
||||||
return this.discussionsPathPrefix + getRelativePath();
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { ActivatedRoute, ActivatedRouteSnapshot, Params } from '@angular/router';
|
import { ActivatedRouteSnapshot, Params, UrlSegment } 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';
|
||||||
|
@ -63,9 +63,7 @@ export abstract class CorePageItemsListManager<Item> {
|
||||||
|
|
||||||
// Calculate current selected item.
|
// Calculate current selected item.
|
||||||
const route = CoreNavigator.getCurrentRoute({ pageComponent: this.pageComponent });
|
const route = CoreNavigator.getCurrentRoute({ pageComponent: this.pageComponent });
|
||||||
if (route !== null && route.firstChild) {
|
this.updateSelectedItem(route?.snapshot ?? null);
|
||||||
this.updateSelectedItem(route.firstChild.snapshot);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select default item if none is selected on a non-mobile layout.
|
// Select default item if none is selected on a non-mobile layout.
|
||||||
if (!CoreScreen.isMobile && this.selectedItem === null) {
|
if (!CoreScreen.isMobile && this.selectedItem === null) {
|
||||||
|
@ -94,9 +92,11 @@ export abstract class CorePageItemsListManager<Item> {
|
||||||
*/
|
*/
|
||||||
watchSplitViewOutlet(splitView: CoreSplitViewComponent): void {
|
watchSplitViewOutlet(splitView: CoreSplitViewComponent): void {
|
||||||
this.splitView = splitView;
|
this.splitView = splitView;
|
||||||
this.splitViewOutletSubscription = splitView.outletRouteObservable.subscribe(route => this.updateSelectedItem(route));
|
this.splitViewOutletSubscription = splitView.outletRouteObservable.subscribe(
|
||||||
|
route => this.updateSelectedItem(this.getPageRouteFromSplitViewOutlet(route)),
|
||||||
|
);
|
||||||
|
|
||||||
this.updateSelectedItem(splitView.outletRoute);
|
this.updateSelectedItem(this.getPageRouteFromSplitViewOutlet(splitView.outletRoute) ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -135,9 +135,8 @@ 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?.snapshot ? this.getSelectedItemPath(itemRoute.snapshot) : null;
|
const selectedItemPath = this.getSelectedItemPath(route.snapshot);
|
||||||
|
|
||||||
if (selectedItemPath === itemPath) {
|
if (selectedItemPath === itemPath) {
|
||||||
return;
|
return;
|
||||||
|
@ -168,7 +167,7 @@ export abstract class CorePageItemsListManager<Item> {
|
||||||
return map;
|
return map;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
this.updateSelectedItem(this.splitView?.outletRoute);
|
this.updateSelectedItem(this.getPageRouteFromSplitViewOutlet(this.splitView?.outletRoute ?? null));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -183,8 +182,8 @@ export abstract class CorePageItemsListManager<Item> {
|
||||||
*
|
*
|
||||||
* @param route Current route.
|
* @param route Current route.
|
||||||
*/
|
*/
|
||||||
protected updateSelectedItem(route?: ActivatedRouteSnapshot | null): void {
|
protected updateSelectedItem(route: ActivatedRouteSnapshot | null): void {
|
||||||
const selectedItemPath = route ? this.getSelectedItemPath(route) : null;
|
const selectedItemPath = this.getSelectedItemPath(route);
|
||||||
|
|
||||||
this.selectedItem = selectedItemPath
|
this.selectedItem = selectedItemPath
|
||||||
? this.itemsMap?.[selectedItemPath] ?? null
|
? this.itemsMap?.[selectedItemPath] ?? null
|
||||||
|
@ -220,25 +219,31 @@ export abstract class CorePageItemsListManager<Item> {
|
||||||
/**
|
/**
|
||||||
* Get the path of the selected item given the current route.
|
* Get the path of the selected item given the current route.
|
||||||
*
|
*
|
||||||
* @param route Current route.
|
* @param route Page route.
|
||||||
* @return Path of the selected item in the given route.
|
* @return Path of the selected item in the given route.
|
||||||
*/
|
*/
|
||||||
protected abstract getSelectedItemPath(route: ActivatedRouteSnapshot): string | null;
|
protected getSelectedItemPath(route?: ActivatedRouteSnapshot | null): string | null {
|
||||||
|
const segments: UrlSegment[] = [];
|
||||||
|
|
||||||
/**
|
while ((route = route?.firstChild)) {
|
||||||
* Get the active item route, if any.
|
segments.push(...route.url);
|
||||||
*
|
|
||||||
* @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;
|
return segments.map(segment => segment.path).join('/').replace(/\/+/, '/').trim() || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the page route given a child route on the splitview outlet.
|
||||||
|
*
|
||||||
|
* @param route Child route.
|
||||||
|
* @return Page route.
|
||||||
|
*/
|
||||||
|
private getPageRouteFromSplitViewOutlet(route: ActivatedRouteSnapshot | null): ActivatedRouteSnapshot | null {
|
||||||
|
while (route && route.component !== this.pageComponent) {
|
||||||
|
route = route.parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 { ActivatedRoute, ActivatedRouteSnapshot, Params } from '@angular/router';
|
import { ActivatedRoute, Params } from '@angular/router';
|
||||||
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
|
||||||
|
@ -176,13 +176,6 @@ class CoreGradesCourseManager extends CorePageItemsListManager<CoreGradesFormatt
|
||||||
return { userId: this.userId };
|
return { userId: this.userId };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
|
|
||||||
return route.params.gradeId ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -13,7 +13,6 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot } from '@angular/router';
|
|
||||||
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||||
|
|
||||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||||
|
@ -101,15 +100,6 @@ class CoreGradesCoursesManager extends CorePageItemsListManager<CoreGradesGradeO
|
||||||
return courseGrade.courseid.toString();
|
return courseGrade.courseid.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
|
|
||||||
const courseId = parseInt(route?.params.courseId);
|
|
||||||
|
|
||||||
return isNaN(courseId) ? null : courseId.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -13,11 +13,10 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot, Params } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
|
|
||||||
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||||
import { CoreSettingsHelper } from '@features/settings/services/settings-helper';
|
|
||||||
import { CoreConstants } from '@/core/constants';
|
import { CoreConstants } from '@/core/constants';
|
||||||
import { SHAREDFILES_PAGE_NAME } from '@features/sharedfiles/sharedfiles.module';
|
import { SHAREDFILES_PAGE_NAME } from '@features/sharedfiles/sharedfiles.module';
|
||||||
import { CoreApp } from '@services/app';
|
import { CoreApp } from '@services/app';
|
||||||
|
@ -110,13 +109,6 @@ class CoreSettingsSectionsManager extends CorePageItemsListManager<CoreSettingsS
|
||||||
return section.params || {};
|
return section.params || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
|
|
||||||
return CoreSettingsHelper.getSelectedItemPath(route);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
||||||
import { ActivatedRouteSnapshot, Params } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
|
||||||
import { CoreSettingsDelegate, CoreSettingsHandlerToDisplay } from '../../services/settings-delegate';
|
import { CoreSettingsDelegate, CoreSettingsHandlerToDisplay } from '../../services/settings-delegate';
|
||||||
|
@ -200,11 +200,4 @@ class CoreSettingsSitePreferencesManager extends CorePageItemsListManager<CoreSe
|
||||||
return handler.params || {};
|
return handler.params || {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
|
|
||||||
return CoreSettingsHelper.getSelectedItemPath(route);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,9 +27,6 @@ import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreCourse } from '@features/course/services/course';
|
import { CoreCourse } from '@features/course/services/course';
|
||||||
import { makeSingleton, Translate } from '@singletons';
|
import { makeSingleton, Translate } from '@singletons';
|
||||||
import { CoreError } from '@classes/errors/error';
|
import { CoreError } from '@classes/errors/error';
|
||||||
import { ActivatedRouteSnapshot } from '@angular/router';
|
|
||||||
import { CoreNavigator } from '@services/navigator';
|
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object with space usage and cache entries that can be erased.
|
* Object with space usage and cache entries that can be erased.
|
||||||
|
@ -478,29 +475,6 @@ export class CoreSettingsHelperProvider {
|
||||||
document.body.classList.toggle('dark', enable);
|
document.body.classList.toggle('dark', enable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation of getSelectedItemPath for settings items managers.
|
|
||||||
*
|
|
||||||
* @param route Current route.
|
|
||||||
* @return Path of the selected item in the given route.
|
|
||||||
*/
|
|
||||||
getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
|
|
||||||
// @todo: routeConfig doesn't have a path after refreshing the app.
|
|
||||||
// route.component is null too, and route.parent.url is empty.
|
|
||||||
let routePath = route.routeConfig?.path;
|
|
||||||
const parentPath = route.parent?.routeConfig?.path;
|
|
||||||
|
|
||||||
if (!routePath && !parentPath) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (routePath) {
|
|
||||||
routePath = CoreNavigator.replaceRoutePathParams(routePath, route.params);
|
|
||||||
}
|
|
||||||
|
|
||||||
return CoreTextUtils.concatenatePaths(parentPath || '', routePath || '');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CoreSettingsHelper = makeSingleton(CoreSettingsHelperProvider);
|
export const CoreSettingsHelper = makeSingleton(CoreSettingsHelperProvider);
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
// 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 } from '@angular/router';
|
|
||||||
import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
|
||||||
|
@ -227,13 +226,6 @@ class CoreUserParticipantsManager extends CorePageItemsListManager<CoreUserParti
|
||||||
return participant.id.toString();
|
return participant.id.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
|
|
||||||
return route.params.userId ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue