forked from EVOgeek/Vmeda.Online
		
	Extract list items management from settings
This commit is contained in:
		
							parent
							
								
									6f54e0eb06
								
							
						
					
					
						commit
						98910cf465
					
				
							
								
								
									
										202
									
								
								src/core/classes/page-items-list-manager.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								src/core/classes/page-items-list-manager.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,202 @@ | ||||
| // (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 { ActivatedRouteSnapshot, Params } from '@angular/router'; | ||||
| import { Subscription } from 'rxjs'; | ||||
| 
 | ||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||
| import { CoreNavigator } from '@services/navigator'; | ||||
| import { CoreScreen } from '@services/screen'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| 
 | ||||
| /** | ||||
|  * Helper class to manage the state and routing of a list of items in a page, for example on pages using a split view. | ||||
|  */ | ||||
| export abstract class CorePageItemsListManager<Item> { | ||||
| 
 | ||||
|     protected itemsList: Item[] | null = null; | ||||
|     protected itemsMap: Record<string, Item> | null = null; | ||||
|     protected selectedItem: Item | null = null; | ||||
|     protected pageComponent: unknown; | ||||
|     protected splitView?: CoreSplitViewComponent; | ||||
|     protected splitViewOutletSubscription?: Subscription; | ||||
| 
 | ||||
|     constructor(pageComponent: unknown) { | ||||
|         this.pageComponent = pageComponent; | ||||
|     } | ||||
| 
 | ||||
|     get items(): Item[] { | ||||
|         return this.itemsList || []; | ||||
|     } | ||||
| 
 | ||||
|     get loaded(): boolean { | ||||
|         return this.itemsMap !== null; | ||||
|     } | ||||
| 
 | ||||
|     get empty(): boolean { | ||||
|         return this.itemsList === null || this.itemsList.length === 0; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Process page started operations. | ||||
|      */ | ||||
|     async start(): Promise<void> { | ||||
|         // Calculate current selected item.
 | ||||
|         const route = CoreNavigator.instance.getCurrentRoute({ pageComponent: this.pageComponent }); | ||||
|         if (route !== null && route.firstChild) { | ||||
|             this.updateSelectedItem(route.firstChild.snapshot); | ||||
|         } | ||||
| 
 | ||||
|         // Select default item if none is selected on a non-mobile layout.
 | ||||
|         if (!CoreScreen.instance.isMobile && this.selectedItem === null) { | ||||
|             const defaultItem = this.getDefaultItem(); | ||||
| 
 | ||||
|             if (defaultItem) { | ||||
|                 this.select(defaultItem); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Log activity.
 | ||||
|         await CoreUtils.instance.ignoreErrors(this.logActivity()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Process page destroyed operations. | ||||
|      */ | ||||
|     destroy(): void { | ||||
|         this.splitViewOutletSubscription?.unsubscribe(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Watch a split view outlet to keep track of the selected item. | ||||
|      * | ||||
|      * @param splitView Split view component. | ||||
|      */ | ||||
|     watchSplitViewOutlet(splitView: CoreSplitViewComponent): void { | ||||
|         this.splitView = splitView; | ||||
|         this.splitViewOutletSubscription = splitView.outletRouteObservable.subscribe(route => this.updateSelectedItem(route)); | ||||
| 
 | ||||
|         this.updateSelectedItem(splitView.outletRoute); | ||||
|     } | ||||
| 
 | ||||
|     // @todo Implement watchResize.
 | ||||
| 
 | ||||
|     /** | ||||
|      * Check whether the given item is selected or not. | ||||
|      * | ||||
|      * @param item Item. | ||||
|      * @return Whether the given item is selected. | ||||
|      */ | ||||
|     isSelected(item: Item): boolean { | ||||
|         return this.selectedItem === item; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Select an item. | ||||
|      * | ||||
|      * @param item Item. | ||||
|      */ | ||||
|     async select(item: Item): Promise<void> { | ||||
|         // Get current route in the page.
 | ||||
|         const route = CoreNavigator.instance.getCurrentRoute({ pageComponent: this.pageComponent }); | ||||
| 
 | ||||
|         if (route === null) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // If this item is already selected, do nothing.
 | ||||
|         const itemPath = this.getItemPath(item); | ||||
| 
 | ||||
|         if (route.firstChild?.routeConfig?.path === itemPath) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Navigate to item.
 | ||||
|         const path = route.firstChild ? `../${itemPath}` : itemPath; | ||||
|         const params = this.getItemQueryParams(item); | ||||
| 
 | ||||
|         await CoreNavigator.instance.navigate(path, { params }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the list of items. | ||||
|      * | ||||
|      * @param items Items. | ||||
|      */ | ||||
|     setItems(items: Item[]): void { | ||||
|         this.itemsList = items.slice(0); | ||||
|         this.itemsMap = items.reduce((map, item) => { | ||||
|             map[this.getItemPath(item)] = item; | ||||
| 
 | ||||
|             return map; | ||||
|         }, {}); | ||||
| 
 | ||||
|         this.updateSelectedItem(this.splitView?.outletRoute); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Log activity when the page starts. | ||||
|      */ | ||||
|     protected async logActivity(): Promise<void> { | ||||
|         //
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Update the selected item given the current route. | ||||
|      * | ||||
|      * @param route Current route. | ||||
|      */ | ||||
|     protected updateSelectedItem(route?: ActivatedRouteSnapshot | null): void { | ||||
|         const selectedItemPath = route ? this.getSelectedItemPath(route) : null; | ||||
| 
 | ||||
|         this.selectedItem = selectedItemPath | ||||
|             ? this.itemsMap?.[selectedItemPath] ?? null | ||||
|             : null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the item that should be selected by default. | ||||
|      */ | ||||
|     protected getDefaultItem(): Item | null { | ||||
|         return this.itemsList?.[0] || null; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the query parameters to use when navigating to an item page. | ||||
|      * | ||||
|      * @param item Item. | ||||
|      * @return Query parameters to use when navigating to the item page. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     protected getItemQueryParams(item: Item): Params { | ||||
|         return {}; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the path to use when navigating to an item page. | ||||
|      * | ||||
|      * @param item Item. | ||||
|      * @return Path to use when navigating to the item page. | ||||
|      */ | ||||
|     protected abstract getItemPath(item: Item): string; | ||||
| 
 | ||||
|     /** | ||||
|      * Get the path of the selected item given the current route. | ||||
|      * | ||||
|      * @param route Current route. | ||||
|      * @return Path of the selected item in the given route. | ||||
|      */ | ||||
|     protected abstract getSelectedItemPath(route: ActivatedRouteSnapshot): string | null; | ||||
| 
 | ||||
| } | ||||
| @ -13,9 +13,10 @@ | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { AfterViewInit, Component, ElementRef, HostBinding, Input, OnDestroy, ViewChild } from '@angular/core'; | ||||
| import { ActivatedRouteSnapshot } from '@angular/router'; | ||||
| import { IonRouterOutlet } from '@ionic/angular'; | ||||
| import { CoreScreen } from '@services/screen'; | ||||
| import { Subscription } from 'rxjs'; | ||||
| import { BehaviorSubject, Observable, Subscription } from 'rxjs'; | ||||
| 
 | ||||
| enum CoreSplitViewMode { | ||||
|     MenuOnly = 'menu-only', // Hides content.
 | ||||
| @ -35,18 +36,33 @@ export class CoreSplitViewComponent implements AfterViewInit, OnDestroy { | ||||
|     @Input() placeholderText = 'core.emptysplit'; | ||||
|     isNested = false; | ||||
| 
 | ||||
|     private outletRouteSubject: BehaviorSubject<ActivatedRouteSnapshot | null> = new BehaviorSubject(null); | ||||
|     private subscriptions?: Subscription[]; | ||||
| 
 | ||||
|     constructor(private element: ElementRef<HTMLElement>) {} | ||||
| 
 | ||||
|     get outletRoute(): ActivatedRouteSnapshot | null { | ||||
|         return this.outletRouteSubject.value; | ||||
|     } | ||||
| 
 | ||||
|     get outletRouteObservable(): Observable<ActivatedRouteSnapshot | null> { | ||||
|         return this.outletRouteSubject.asObservable(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     ngAfterViewInit(): void { | ||||
|         this.isNested = !!this.element.nativeElement.parentElement?.closest('core-split-view'); | ||||
|         this.subscriptions = [ | ||||
|             this.outlet.activateEvents.subscribe(() => this.updateClasses()), | ||||
|             this.outlet.deactivateEvents.subscribe(() => this.updateClasses()), | ||||
|             this.outlet.activateEvents.subscribe(() => { | ||||
|                 this.updateClasses(); | ||||
|                 this.outletRouteSubject.next(this.outlet.activatedRoute.snapshot); | ||||
|             }), | ||||
|             this.outlet.deactivateEvents.subscribe(() => { | ||||
|                 this.updateClasses(); | ||||
|                 this.outletRouteSubject.next(null); | ||||
|             }), | ||||
|             CoreScreen.instance.layoutObservable.subscribe(() => this.updateClasses()), | ||||
|         ]; | ||||
| 
 | ||||
|  | ||||
| @ -11,11 +11,11 @@ | ||||
|     <core-split-view> | ||||
|         <ion-list> | ||||
|             <ion-item | ||||
|                 *ngFor="let section of sections" | ||||
|                 [class.core-selected-item]="section.name === activeSection" | ||||
|                 *ngFor="let section of sections.items" | ||||
|                 [class.core-selected-item]="sections.isSelected(section)" | ||||
|                 button | ||||
|                 detail | ||||
|                 (click)="openSection(section)" | ||||
|                 (click)="sections.select(section)" | ||||
|             > | ||||
|                 <ion-icon [name]="section.icon" slot="start"></ion-icon> | ||||
|                 <ion-label>{{ 'core.settings.' + section.name | translate }}</ion-label> | ||||
|  | ||||
| @ -12,83 +12,57 @@ | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Component, OnDestroy, OnInit } from '@angular/core'; | ||||
| import { Subscription } from 'rxjs'; | ||||
| 
 | ||||
| import { CoreNavigator } from '@services/navigator'; | ||||
| import { CoreScreen } from '@services/screen'; | ||||
| import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core'; | ||||
| import { CoreSettingsConstants, CoreSettingsSection } from '@features/settings/constants'; | ||||
| import { CorePageItemsListManager } from '@classes/page-items-list-manager'; | ||||
| import { ActivatedRouteSnapshot } from '@angular/router'; | ||||
| import { CoreSplitViewComponent } from '@components/split-view/split-view'; | ||||
| 
 | ||||
| @Component({ | ||||
|     selector: 'page-core-settings-index', | ||||
|     templateUrl: 'index.html', | ||||
| }) | ||||
| export class CoreSettingsIndexPage implements OnInit, OnDestroy { | ||||
| export class CoreSettingsIndexPage implements AfterViewInit, OnDestroy { | ||||
| 
 | ||||
|     sections = CoreSettingsConstants.SECTIONS; | ||||
|     activeSection?: string; | ||||
|     layoutSubscription?: Subscription; | ||||
|     sections: CoreSettingsSectionsManager = new CoreSettingsSectionsManager(CoreSettingsIndexPage); | ||||
| 
 | ||||
|     @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     ngOnInit(): void { | ||||
|         this.layoutSubscription = CoreScreen.instance.layoutObservable.subscribe(() => this.updateActiveSection()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     ionViewWillEnter(): void { | ||||
|         this.updateActiveSection(); | ||||
|     ngAfterViewInit(): void { | ||||
|         this.sections.setItems(CoreSettingsConstants.SECTIONS); | ||||
|         this.sections.watchSplitViewOutlet(this.splitView); | ||||
|         this.sections.start(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     ngOnDestroy(): void { | ||||
|         this.layoutSubscription?.unsubscribe(); | ||||
|         this.sections.destroy(); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|      * Open a section page. | ||||
|      * | ||||
|      * @param section Section to open. | ||||
|  * Helper class to manage sections. | ||||
|  */ | ||||
|     openSection(section: CoreSettingsSection): void { | ||||
|         const path = this.activeSection ? `../${section.path}` : section.path; | ||||
| class CoreSettingsSectionsManager extends CorePageItemsListManager<CoreSettingsSection> { | ||||
| 
 | ||||
|         CoreNavigator.instance.navigate(path); | ||||
| 
 | ||||
|         this.updateActiveSection(section.name); | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     protected getItemPath(section: CoreSettingsSection): string { | ||||
|         return section.path; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Update active section. | ||||
|      * | ||||
|      * @param activeSection Active section. | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     private updateActiveSection(activeSection?: string): void { | ||||
|         if (CoreScreen.instance.isMobile) { | ||||
|             delete this.activeSection; | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.activeSection = activeSection ?? this.guessActiveSection(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Guess active section looking at the current route. | ||||
|      * | ||||
|      * @return Active section. | ||||
|      */ | ||||
|     private guessActiveSection(): string | undefined { | ||||
|         const activeSection = this.sections.find( | ||||
|             section => CoreNavigator.instance.isCurrent(`**/settings/${section.path}`), | ||||
|         ); | ||||
| 
 | ||||
|         return activeSection?.name; | ||||
|     protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null { | ||||
|         return route.parent?.routeConfig?.path ?? null; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -50,6 +50,14 @@ export type CoreNavigationOptions = { | ||||
|     reset?: boolean; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Options for CoreNavigatorService#getCurrentRoute method. | ||||
|  */ | ||||
| type GetCurrentRouteOptions = Partial<{ | ||||
|     parentRoute: ActivatedRoute; | ||||
|     pageComponent: unknown; | ||||
| }>; | ||||
| 
 | ||||
| /** | ||||
|  * Service to provide some helper functions regarding navigation. | ||||
|  */ | ||||
| @ -310,13 +318,26 @@ export class CoreNavigatorService { | ||||
|     /** | ||||
|      * Get current activated route. | ||||
|      * | ||||
|      * @param route Parent route. | ||||
|      * @param options | ||||
|      *     - parent: Parent route, if this isn't provided the current active route will be used. | ||||
|      *     - pageComponent: Page component of the route to find, if this isn't provided the deepest route in the hierarchy | ||||
|      *                      will be returned. | ||||
|      * @return Current activated route. | ||||
|      */ | ||||
|     protected getCurrentRoute(route?: ActivatedRoute): ActivatedRoute { | ||||
|         route = route ?? Router.instance.routerState.root; | ||||
|     getCurrentRoute(): ActivatedRoute; | ||||
|     getCurrentRoute(options: GetCurrentRouteOptions): ActivatedRoute | null; | ||||
|     getCurrentRoute({ parentRoute, pageComponent }: GetCurrentRouteOptions = {}): ActivatedRoute | null { | ||||
|         parentRoute = parentRoute ?? Router.instance.routerState.root; | ||||
| 
 | ||||
|         return route.firstChild ? this.getCurrentRoute(route.firstChild) : route; | ||||
|         if (pageComponent && parentRoute.component === pageComponent) { | ||||
|             return parentRoute; | ||||
|         } | ||||
| 
 | ||||
|         if (parentRoute.firstChild) { | ||||
|             return this.getCurrentRoute({ parentRoute: parentRoute.firstChild, pageComponent }); | ||||
|         } | ||||
| 
 | ||||
|         return pageComponent ? null : parentRoute; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user