Skip to content

Commit

Permalink
Merge pull request #1842 from France-ioi/fix-group-nav
Browse files Browse the repository at this point in the history
Fix group navigation when going to the mine/managed pages
  • Loading branch information
smadbe authored Nov 28, 2024
2 parents c1a57ed + 051d703 commit 8a7d693
Show file tree
Hide file tree
Showing 5 changed files with 65 additions and 30 deletions.
9 changes: 5 additions & 4 deletions src/app/groups/group.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,19 @@ import { GroupByIdComponent } from './group-by-id.component';
import { ManageGroupsComponent } from 'src/app/groups/containers/manage-groups/manage-groups.component';
import { PageNotFoundComponent } from '../containers/page-not-found/page-not-found.component';
import { DefaultLayoutInitService } from '../services/layout.service';
import { groupPathRouterSubPrefix, managedGroupsPage, myGroupsPage, userPathRouterSubPrefix } from '../models/routing/group-route';

const routes: Routes = [
{
path: 'mine',
path: myGroupsPage,
component: MyGroupsComponent,
},
{
path: 'manage',
path: managedGroupsPage,
component: ManageGroupsComponent,
},
{
path: 'users/:id',
path: userPathRouterSubPrefix + '/:id',
component: UserComponent,
children: [ /* if you change routes here, update `isUserPage` as well! */
{
Expand All @@ -31,7 +32,7 @@ const routes: Routes = [
]
},
{
path: 'by-id/:id',
path: groupPathRouterSubPrefix + '/:id',
component: GroupByIdComponent,
canDeactivate: [ PendingChangesGuard ],
children: [ /* if you change routes here, update `isGroupPage` as well! */
Expand Down
5 changes: 4 additions & 1 deletion src/app/groups/models/group-types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { z } from 'zod';

export type GroupTypeCategory = 'group'|'user';
export const groupGroupTypeCategory = 'group';
export const userGroupTypeCategory = 'user';

export type GroupTypeCategory = typeof groupGroupTypeCategory | typeof userGroupTypeCategory;

export function isGroupTypeVisible(type: string): boolean {
return type !== 'Base' && type !== 'ContestParticipants';
Expand Down
24 changes: 18 additions & 6 deletions src/app/groups/store/group-content/group-content.selectors.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MemoizedSelector, Selector, createSelector } from '@ngrx/store';
import { GroupRoute, RawGroupRoute, contentTypeOfPath, isGroupRoute, isUser } from 'src/app/models/routing/group-route';
import { GroupRoute, RawGroupRoute, isGroupRoute, isUser, parseRouterPath } from 'src/app/models/routing/group-route';
import { GroupRouteError, groupRouteFromParams, isGroupRouteError } from '../../utils/group-route-validation';
import { ObservationInfo } from 'src/app/store/observation';
import { fromRouter } from 'src/app/store/router';
Expand All @@ -8,6 +8,7 @@ import { fetchingState } from 'src/app/utils/state';
import { formatUser } from 'src/app/groups/models/user';
import { RootState } from 'src/app/utils/store/root_state';
import { selectIdParameter, selectPathParameter } from 'src/app/models/routing/content-route-selectors';
import { groupGroupTypeCategory, userGroupTypeCategory } from '../../models/group-types';

interface UserContentSelectors<T extends RootState> {
selectIsGroupContentActive: MemoizedSelector<T, boolean>,
Expand Down Expand Up @@ -43,22 +44,33 @@ interface UserContentSelectors<T extends RootState> {

export function selectors<T extends RootState>(selectState: Selector<T, State>): UserContentSelectors<T> {

const selectIsGroupContentActive = createSelector(
const selectRouterPathParsingResult = createSelector(
fromRouter.selectPath,
path => !!path && !!contentTypeOfPath(path)
path => (path ? parseRouterPath(path) : null)
);

const selectIsUserContentActive = createSelector(
fromRouter.selectPath,
path => !!path && contentTypeOfPath(path) === 'user'
selectRouterPathParsingResult,
pathParsingResult => pathParsingResult === userGroupTypeCategory
);

const selectIsNonUserGroupContentActive = createSelector(
selectRouterPathParsingResult,
pathParsingResult => pathParsingResult === groupGroupTypeCategory
);

const selectIsGroupContentActive = createSelector(
selectIsUserContentActive,
selectIsNonUserGroupContentActive,
(isUser, isNonUserGroup) => isUser || isNonUserGroup
);

const selectActiveContentRouteParsingResult = createSelector(
selectIsGroupContentActive,
selectIsUserContentActive,
selectIdParameter,
selectPathParameter,
(isActive, isUser, id, path) => (isActive ? groupRouteFromParams(id, path, isUser) : null)
(isGroupContent, isUser, id, path) => (isGroupContent ? groupRouteFromParams(id, path, isUser) : null)
);

const selectActiveContentRouteError = createSelector(
Expand Down
43 changes: 29 additions & 14 deletions src/app/models/routing/group-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ import { Group } from 'src/app/groups/data-access/get-group-by-id.service';
import { User } from 'src/app/groups/models/user';
import { UrlCommand } from '../../utils/url';
import { ContentRoute, pathAsParameter, pathFromRouterParameters } from './content-route';
import { GroupTypeCategory } from '../../groups/models/group-types';
import { groupGroupTypeCategory, GroupTypeCategory, userGroupTypeCategory } from '../../groups/models/group-types';

export const myGroupsPage = 'mine';
export const managedGroupsPage = 'manage';
export type GroupPage = typeof myGroupsPage | typeof managedGroupsPage;

export const groupPathRouterPrefix = 'groups';
export const userPathRouterSubPrefix = 'users';
export const groupPathRouterSubPrefix = 'by-id';

export interface GroupRoute extends ContentRoute {
contentType: GroupTypeCategory,
Expand All @@ -24,12 +32,17 @@ export function isUser(group: GroupLike): boolean {
}

function contentType(group: GroupLike): GroupTypeCategory {
return isUser(group) ? 'user' : 'group';
return isUser(group) ? userGroupTypeCategory : groupGroupTypeCategory;
}

export function contentTypeOfPath(path: string[]): GroupTypeCategory | undefined {
if (path[0] !== 'groups') return undefined;
return path[1] === 'users' ? 'user' : 'group';
export function parseRouterPath(path: string[]): GroupTypeCategory | GroupPage | null {
if (path[0] !== groupPathRouterPrefix) return null;
const subPrefix = path[1];
if (subPrefix === userPathRouterSubPrefix) return userGroupTypeCategory;
if (subPrefix === groupPathRouterSubPrefix) return groupGroupTypeCategory;
if (subPrefix === myGroupsPage) return myGroupsPage;
if (subPrefix === managedGroupsPage) return managedGroupsPage;
return null; // means the path is "/groups/something-unrecognized"
}

export function rawGroupRoute(group: GroupLike): RawGroupRoute {
Expand All @@ -42,25 +55,27 @@ export function groupRoute(group: GroupLike, path: string[]): GroupRoute {
}

export function isGroupRoute(route: ContentRoute | RawGroupRoute): route is GroupRoute {
return (route.contentType === 'group' || route.contentType === 'user') && route.path !== undefined;
return (route.contentType === groupGroupTypeCategory || route.contentType === userGroupTypeCategory) && route.path !== undefined;
}

export function isRawGroupRoute(route?: unknown): route is RawGroupRoute {
if (typeof route !== 'object') return false;
const contentType = (route as Record<string, unknown> | null)?.contentType;
return contentType === 'group' || contentType === 'user';
return contentType === groupGroupTypeCategory || contentType === userGroupTypeCategory;
}

/**
* Return a url array (`commands` array) to the given group, on the given page.
*/
export function urlArrayForGroupRoute(
route: RawGroupRoute,
page?: string[],
): UrlCommand {
const path = route.path ? pathAsParameter(route.path) : {};
const actualPage = page && ((isUser(route) && isUserPage(page)) || (!isUser(route) && isGroupPage(page))) ? page : [];
return [ '/', 'groups', isUser(route) ? 'users' : 'by-id', route.id, path, ...actualPage ];
export function urlArrayForGroupRoute(route: RawGroupRoute|GroupPage, page?: string[]): UrlCommand {
if (route === myGroupsPage || route === managedGroupsPage) {
return [ '/', groupPathRouterPrefix, route ];
} else {
const path = route.path ? pathAsParameter(route.path) : {};
const actualPage = page && ((isUser(route) && isUserPage(page)) || (!isUser(route) && isGroupPage(page))) ? page : [];
const subPrefix = isUser(route) ? userPathRouterSubPrefix : groupPathRouterSubPrefix;
return [ '/', groupPathRouterPrefix, subPrefix, route.id, path, ...actualPage ];
}
}

function isUserPage(page: string[]): boolean {
Expand Down
14 changes: 9 additions & 5 deletions src/app/models/routing/group-router.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { NavigationExtras, Router, UrlTree } from '@angular/router';
import { UrlCommand } from '../../utils/url';
import { RawGroupRoute, urlArrayForGroupRoute } from './group-route';
import { GroupPage, groupPathRouterPrefix, groupPathRouterSubPrefix, RawGroupRoute, urlArrayForGroupRoute } from './group-route';

@Injectable({
providedIn: 'root'
Expand All @@ -16,7 +16,7 @@ export class GroupRouter {
* Navigate to given group, on the path page.
* If page is not given and we are currently on a group page, use the same page. Otherwise, default to '/'.
*/
navigateTo(route: RawGroupRoute, options?: { page?: string[], navExtras?: NavigationExtras }): void {
navigateTo(route: RawGroupRoute|GroupPage, options?: { page?: string[], navExtras?: NavigationExtras }): void {
void this.router.navigateByUrl(this.url(route, options?.page), options?.navExtras);
}

Expand All @@ -34,15 +34,15 @@ export class GroupRouter {
* Return a url to the given group, on the `path` page.
* If page is not given and we are currently on a group page, use the same page. Otherwise, default to '/'.
*/
url(route: RawGroupRoute, page?: string[]): UrlTree {
url(route: RawGroupRoute|GroupPage, page?: string[]): UrlTree {
return this.router.createUrlTree(this.urlArray(route, page));
}

/**
* Return a url array (`commands` array) to the given group, on the `path` page.
* If page is not given and we are currently on a group page, use the same page. Otherwise, default to '/'.
*/
urlArray(route: RawGroupRoute, page?: string[]): UrlCommand {
urlArray(route: RawGroupRoute|GroupPage, page?: string[]): UrlCommand {
return urlArrayForGroupRoute(route, page ?? this.currentGroupPage());
}

Expand All @@ -61,7 +61,11 @@ export class GroupRouter {
const { primary } = this.router.parseUrl(this.router.url).root.children;
if (!primary) return undefined;
const { segments } = primary;
if (segments.length < 3 || segments[0]?.path !== 'groups' || segments[1]?.path !== 'by-id') return undefined;
if (
segments.length < 3 ||
segments[0]?.path !== groupPathRouterPrefix ||
segments[1]?.path !== groupPathRouterSubPrefix
) return undefined;
return segments.map(segment => segment.path);
}

Expand Down

0 comments on commit 8a7d693

Please sign in to comment.