forked from CIT/Vmeda.Online
		
	MOBILE-3320 course: Support nested navigation
This commit is contained in:
		
							parent
							
								
									1e82b7796c
								
							
						
					
					
						commit
						c39d6cc8a5
					
				@ -97,6 +97,33 @@ function buildConditionalUrlMatcher(pathOrMatcher: string | UrlMatcher, conditio
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function buildRegExpUrlMatcher(regexp: RegExp): UrlMatcher {
 | 
			
		||||
    return (segments: UrlSegment[]): UrlMatchResult | null => {
 | 
			
		||||
        // Ignore empty paths.
 | 
			
		||||
        if (segments.length === 0) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const path = segments.map(segment => segment.path).join('/');
 | 
			
		||||
        const match = regexp.exec(path)?.[0];
 | 
			
		||||
 | 
			
		||||
        // Ignore paths that don't match the start of the url.
 | 
			
		||||
        if (!match || !path.startsWith(match)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Consume segments that match.
 | 
			
		||||
        const [consumed] = segments.slice(1).reduce(([consumed, path], segment) => path === match
 | 
			
		||||
            ? [consumed, path]
 | 
			
		||||
            :[
 | 
			
		||||
                consumed.concat(segment),
 | 
			
		||||
                `${path}/${segment.path}`,
 | 
			
		||||
            ], [[segments[0]] as UrlSegment[], segments[0].path]);
 | 
			
		||||
 | 
			
		||||
        return { consumed };
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type ModuleRoutes = { children: Routes; siblings: Routes };
 | 
			
		||||
export type ModuleRoutesConfig = Routes | Partial<ModuleRoutes>;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										43
									
								
								src/app/tests/app-routing.module.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/app/tests/app-routing.module.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,43 @@
 | 
			
		||||
// (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 { Route } from '@angular/compiler/src/core';
 | 
			
		||||
import { UrlSegment, UrlSegmentGroup } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
import { mock } from '@/testing/utils';
 | 
			
		||||
 | 
			
		||||
import { buildRegExpUrlMatcher } from '../app-routing.module';
 | 
			
		||||
 | 
			
		||||
describe('Routing utils', () => {
 | 
			
		||||
 | 
			
		||||
    it('matches paths using a RegExp', () => {
 | 
			
		||||
        const matcher = buildRegExpUrlMatcher(/foo(\/bar)*/);
 | 
			
		||||
        const route = mock<Route>();
 | 
			
		||||
        const segmentGroup = mock<UrlSegmentGroup>();
 | 
			
		||||
        const toUrlSegment = (path: string) => new UrlSegment(path, {});
 | 
			
		||||
        const testMatcher = (path: string, consumedParts: string[] | null) =>
 | 
			
		||||
            expect(matcher(path.split('/').map(toUrlSegment), segmentGroup, route))
 | 
			
		||||
                .toEqual(
 | 
			
		||||
                    consumedParts
 | 
			
		||||
                        ? { consumed: consumedParts.map(toUrlSegment) }
 | 
			
		||||
                        : null,
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
        testMatcher('baz/foo/bar', null);
 | 
			
		||||
        testMatcher('foo', ['foo']);
 | 
			
		||||
        testMatcher('foo/baz', ['foo']);
 | 
			
		||||
        testMatcher('foo/bar/bar/baz', ['foo', 'bar', 'bar']);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
});
 | 
			
		||||
@ -40,6 +40,7 @@ import { CoreCourseOptionsDelegateService } from './services/course-options-dele
 | 
			
		||||
import { CoreCourseOfflineProvider } from './services/course-offline';
 | 
			
		||||
import { CoreCourseSyncProvider } from './services/sync';
 | 
			
		||||
import { COURSE_INDEX_PATH } from '@features/course/course-lazy.module';
 | 
			
		||||
import { buildRegExpUrlMatcher } from '@/app/app-routing.module';
 | 
			
		||||
 | 
			
		||||
export const CORE_COURSE_SERVICES: Type<unknown>[] = [
 | 
			
		||||
    CoreCourseProvider,
 | 
			
		||||
@ -59,7 +60,7 @@ export const COURSE_CONTENTS_PATH = `${COURSE_PAGE_NAME}/${COURSE_INDEX_PATH}/${
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: COURSE_PAGE_NAME,
 | 
			
		||||
        matcher: buildRegExpUrlMatcher(new RegExp(`^${COURSE_PAGE_NAME}(/deep)*`)),
 | 
			
		||||
        loadChildren: () => import('@features/course/course-lazy.module').then(m => m.CoreCourseLazyModule),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Component, ViewChild, OnDestroy, OnInit } from '@angular/core';
 | 
			
		||||
import { Params } from '@angular/router';
 | 
			
		||||
import { ActivatedRoute, Params } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
import { CoreTabsOutletTab, CoreTabsOutletComponent } from '@components/tabs-outlet/tabs-outlet';
 | 
			
		||||
import { CoreCourseFormatDelegate } from '../../services/format-delegate';
 | 
			
		||||
@ -54,7 +54,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
 | 
			
		||||
        pageParams: {},
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
    constructor(private route: ActivatedRoute) {
 | 
			
		||||
        this.selectTabObserver = CoreEvents.on(CoreEvents.SELECT_COURSE_TAB, (data) => {
 | 
			
		||||
            if (!data.name) {
 | 
			
		||||
                // If needed, set sectionId and sectionNumber. They'll only be used if the content tabs hasn't been loaded yet.
 | 
			
		||||
@ -81,6 +81,11 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        // Increase route depth.
 | 
			
		||||
        const path = CoreNavigator.getRouteFullPath(this.route.snapshot);
 | 
			
		||||
 | 
			
		||||
        CoreNavigator.increaseRouteDepth(path.replace(/(\/deep)+/, ''));
 | 
			
		||||
 | 
			
		||||
        // Get params.
 | 
			
		||||
        this.course = CoreNavigator.getRouteParam('course');
 | 
			
		||||
        this.firstTabName = CoreNavigator.getRouteParam('selectedTab');
 | 
			
		||||
@ -180,6 +185,9 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
 | 
			
		||||
     * Page destroyed.
 | 
			
		||||
     */
 | 
			
		||||
    ngOnDestroy(): void {
 | 
			
		||||
        const path = CoreNavigator.getRouteFullPath(this.route.snapshot);
 | 
			
		||||
 | 
			
		||||
        CoreNavigator.decreaseRouteDepth(path.replace(/(\/deep)+/, ''));
 | 
			
		||||
        this.selectTabObserver?.off();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -177,7 +177,11 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
 | 
			
		||||
        Object.assign(params, { course: course });
 | 
			
		||||
 | 
			
		||||
        // Don't return the .push promise, we don't want to display a loading modal during the page transition.
 | 
			
		||||
        CoreNavigator.navigateToSitePath(`course/${course.id}`, { params });
 | 
			
		||||
        const currentTab = CoreNavigator.getCurrentMainMenuTab();
 | 
			
		||||
        const routeDepth = CoreNavigator.getRouteDepth(`/main/${currentTab}/course/${course.id}`);
 | 
			
		||||
        const deepPath = '/deep'.repeat(routeDepth);
 | 
			
		||||
 | 
			
		||||
        CoreNavigator.navigateToSitePath(`course${deepPath}/${course.id}`, { params });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { ActivatedRoute, Params } from '@angular/router';
 | 
			
		||||
import { ActivatedRoute, ActivatedRouteSnapshot, Params } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
import { NavigationOptions } from '@ionic/angular/providers/nav-controller';
 | 
			
		||||
 | 
			
		||||
@ -71,6 +71,7 @@ export type CoreNavigatorCurrentRouteOptions = Partial<{
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class CoreNavigatorService {
 | 
			
		||||
 | 
			
		||||
    protected routesDepth: Record<string, number> = {};
 | 
			
		||||
    protected storedParams: Record<number, unknown> = {};
 | 
			
		||||
    protected lastParamId = 0;
 | 
			
		||||
 | 
			
		||||
@ -397,6 +398,38 @@ export class CoreNavigatorService {
 | 
			
		||||
        return pageComponent || routeData ? null : route;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Increase the number of times a route is repeated on the navigation stack.
 | 
			
		||||
     *
 | 
			
		||||
     * @param path Absolute route path.
 | 
			
		||||
     */
 | 
			
		||||
    increaseRouteDepth(path: string): void {
 | 
			
		||||
        this.routesDepth[path] = this.getRouteDepth(path) + 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Decrease the number of times a route is repeated on the navigation stack.
 | 
			
		||||
     *
 | 
			
		||||
     * @param path Absolute route path.
 | 
			
		||||
     */
 | 
			
		||||
    decreaseRouteDepth(path: string): void {
 | 
			
		||||
        if (this.getRouteDepth(path) <= 1) {
 | 
			
		||||
            delete this.routesDepth[path];
 | 
			
		||||
        } else {
 | 
			
		||||
            this.routesDepth[path]--;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the number of times a route is repeated on the navigation stack.
 | 
			
		||||
     *
 | 
			
		||||
     * @param path Absolute route path.
 | 
			
		||||
     * @return Route depth.
 | 
			
		||||
     */
 | 
			
		||||
    getRouteDepth(path: string): number {
 | 
			
		||||
        return this.routesDepth[path] ?? 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Navigate to a path within the main menu.
 | 
			
		||||
     * If the path belongs to a visible tab, that tab will be selected.
 | 
			
		||||
@ -493,16 +526,16 @@ export class CoreNavigatorService {
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the full path of a certain route, including parent routes paths.
 | 
			
		||||
     *
 | 
			
		||||
     * @param route Route.
 | 
			
		||||
     * @param route Route snapshot.
 | 
			
		||||
     * @return Path.
 | 
			
		||||
     */
 | 
			
		||||
    getRouteFullPath(route: ActivatedRoute | null): string {
 | 
			
		||||
    getRouteFullPath(route: ActivatedRouteSnapshot | null): string {
 | 
			
		||||
        if (!route) {
 | 
			
		||||
            return '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const parentPath = this.getRouteFullPath(route.parent);
 | 
			
		||||
        const routePath = route.snapshot.url.join('/');
 | 
			
		||||
        const routePath = route.url.join('/');
 | 
			
		||||
 | 
			
		||||
        if (!parentPath && !routePath) {
 | 
			
		||||
            return '';
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user