commit
97592c431a
|
@ -12,29 +12,60 @@
|
||||||
// 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 { CommonModule } from '@angular/common';
|
||||||
|
import { IonicModule } from '@ionic/angular';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { Route, RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { conditionalRoutes } from '@/app/app-routing.module';
|
||||||
|
import { CoreScreen } from '@services/screen';
|
||||||
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
|
||||||
export const AddonBadgesIssueRoute: Route = {
|
import { AddonBadgesIssuedBadgePage } from './pages/issued-badge/issued-badge';
|
||||||
path: 'issue',
|
import { AddonBadgesUserBadgesPage } from './pages/user-badges/user-badges';
|
||||||
loadChildren: () => import('./pages/issued-badge/issued-badge.module').then( m => m.AddonBadgesIssuedBadgePageModule),
|
|
||||||
};
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const mobileRoutes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
redirectTo: 'user',
|
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
|
component: AddonBadgesUserBadgesPage,
|
||||||
},
|
},
|
||||||
AddonBadgesIssueRoute,
|
|
||||||
{
|
{
|
||||||
path: 'user',
|
path: ':badgeHash',
|
||||||
loadChildren: () => import('./pages/user-badges/user-badges.module').then( m => m.AddonBadgesUserBadgesPageModule),
|
component: AddonBadgesIssuedBadgePage,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const tabletRoutes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: AddonBadgesUserBadgesPage,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: ':badgeHash',
|
||||||
|
component: AddonBadgesIssuedBadgePage,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
...conditionalRoutes(mobileRoutes, () => CoreScreen.instance.isMobile),
|
||||||
|
...conditionalRoutes(tabletRoutes, () => CoreScreen.instance.isTablet),
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forChild(routes)],
|
imports: [
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
CommonModule,
|
||||||
|
IonicModule,
|
||||||
|
TranslateModule.forChild(),
|
||||||
|
CoreSharedModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
AddonBadgesUserBadgesPage,
|
||||||
|
AddonBadgesIssuedBadgePage,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class AddonBadgesLazyModule {}
|
export class AddonBadgesLazyModule {}
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
// (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 { NgModule } from '@angular/core';
|
|
||||||
import { IonicModule } from '@ionic/angular';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
|
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
|
||||||
import { AddonBadgesIssuedBadgePage } from './issued-badge.page';
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: AddonBadgesIssuedBadgePage,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
RouterModule.forChild(routes),
|
|
||||||
CommonModule,
|
|
||||||
IonicModule,
|
|
||||||
TranslateModule.forChild(),
|
|
||||||
CoreSharedModule,
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
AddonBadgesIssuedBadgePage,
|
|
||||||
],
|
|
||||||
exports: [RouterModule],
|
|
||||||
})
|
|
||||||
export class AddonBadgesIssuedBadgePageModule {}
|
|
|
@ -8,17 +8,17 @@
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<core-split-view>
|
<core-split-view>
|
||||||
<ion-refresher slot="fixed" [disabled]="!badgesLoaded" (ionRefresh)="refreshBadges($event)">
|
<ion-refresher slot="fixed" [disabled]="!badges.loaded" (ionRefresh)="refreshBadges($event.target)">
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
<core-loading [hideUntil]="badgesLoaded">
|
<core-loading [hideUntil]="badges.loaded">
|
||||||
<core-empty-box *ngIf="!badges || badges.length == 0" icon="fas-trophy"
|
<core-empty-box *ngIf="badges.empty" icon="fas-trophy"
|
||||||
[message]="'addon.badges.nobadges' | translate">
|
[message]="'addon.badges.nobadges' | translate">
|
||||||
</core-empty-box>
|
</core-empty-box>
|
||||||
|
|
||||||
<ion-list *ngIf="badges && badges.length" class="ion-no-margin">
|
<ion-list *ngIf="!badges.empty" class="ion-no-margin">
|
||||||
<ion-item class="ion-text-wrap" *ngFor="let badge of badges" [title]="badge.name"
|
<ion-item class="ion-text-wrap" *ngFor="let badge of badges.items" [title]="badge.name"
|
||||||
(click)="loadIssuedBadge(badge.uniquehash)" [class.core-selected-item]="badge.uniquehash == badgeHash">
|
(click)="badges.select(badge)" [class.core-selected-item]="badges.isSelected(badge)">
|
||||||
<ion-avatar slot="start">
|
<ion-avatar slot="start">
|
||||||
<img [src]="badge.badgeurl" [alt]="badge.name" core-external-content>
|
<img [src]="badge.badgeurl" [alt]="badge.name" core-external-content>
|
||||||
</ion-avatar>
|
</ion-avatar>
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
// (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 { NgModule } from '@angular/core';
|
|
||||||
import { IonicModule } from '@ionic/angular';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
|
||||||
import { CommonModule } from '@angular/common';
|
|
||||||
import { conditionalRoutes } from '@/app/app-routing.module';
|
|
||||||
import { CoreScreen } from '@services/screen';
|
|
||||||
|
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
|
||||||
import { AddonBadgesUserBadgesPage } from './user-badges.page';
|
|
||||||
import { AddonBadgesIssueRoute } from '@addons/badges/badges-lazy.module';
|
|
||||||
|
|
||||||
const mobileRoutes: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: AddonBadgesUserBadgesPage,
|
|
||||||
},
|
|
||||||
AddonBadgesIssueRoute,
|
|
||||||
];
|
|
||||||
|
|
||||||
const tabletRoutes: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: AddonBadgesUserBadgesPage,
|
|
||||||
children: [
|
|
||||||
AddonBadgesIssueRoute,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
...conditionalRoutes(mobileRoutes, () => CoreScreen.instance.isMobile),
|
|
||||||
...conditionalRoutes(tabletRoutes, () => CoreScreen.instance.isTablet),
|
|
||||||
];
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
RouterModule.forChild(routes),
|
|
||||||
CommonModule,
|
|
||||||
IonicModule,
|
|
||||||
TranslateModule.forChild(),
|
|
||||||
CoreSharedModule,
|
|
||||||
],
|
|
||||||
declarations: [
|
|
||||||
AddonBadgesUserBadgesPage,
|
|
||||||
],
|
|
||||||
exports: [RouterModule],
|
|
||||||
})
|
|
||||||
export class AddonBadgesUserBadgesPageModule {}
|
|
|
@ -1,106 +0,0 @@
|
||||||
// (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 { Component, OnInit } from '@angular/core';
|
|
||||||
import { IonRefresher } from '@ionic/angular';
|
|
||||||
import { AddonBadges, AddonBadgesUserBadge } from '../../services/badges';
|
|
||||||
import { CoreTimeUtils } from '@services/utils/time';
|
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
|
||||||
import { CoreSites } from '@services/sites';
|
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
|
||||||
import { CoreNavigator } from '@services/navigator';
|
|
||||||
import { CoreScreen } from '@services/screen';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Page that displays the list of calendar events.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'page-addon-badges-user-badges',
|
|
||||||
templateUrl: 'user-badges.html',
|
|
||||||
})
|
|
||||||
export class AddonBadgesUserBadgesPage implements OnInit {
|
|
||||||
|
|
||||||
courseId = 0;
|
|
||||||
userId!: number;
|
|
||||||
|
|
||||||
badgesLoaded = false;
|
|
||||||
badges: AddonBadgesUserBadge[] = [];
|
|
||||||
currentTime = 0;
|
|
||||||
badgeHash!: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* View loaded.
|
|
||||||
*/
|
|
||||||
ngOnInit(): void {
|
|
||||||
|
|
||||||
this.courseId = CoreNavigator.instance.getRouteNumberParam('courseId') || this.courseId; // Use 0 for site badges.
|
|
||||||
this.userId = CoreNavigator.instance.getRouteNumberParam('userId') || CoreSites.instance.getCurrentSite()!.getUserId();
|
|
||||||
|
|
||||||
this.fetchBadges().finally(() => {
|
|
||||||
if (!this.badgeHash && CoreScreen.instance.isTablet && this.badges.length > 0) {
|
|
||||||
// Take first and load it.
|
|
||||||
this.loadIssuedBadge(this.badges[0].uniquehash);
|
|
||||||
}
|
|
||||||
this.badgesLoaded = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch all the badges required for the view.
|
|
||||||
*
|
|
||||||
* @return Promise resolved when done.
|
|
||||||
*/
|
|
||||||
async fetchBadges(): Promise<void> {
|
|
||||||
this.currentTime = CoreTimeUtils.instance.timestamp();
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.badges = await AddonBadges.instance.getUserBadges(this.courseId, this.userId);
|
|
||||||
} catch (message) {
|
|
||||||
CoreDomUtils.instance.showErrorModalDefault(message, 'Error getting badges data.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refresh the badges.
|
|
||||||
*
|
|
||||||
* @param refresher Refresher.
|
|
||||||
*/
|
|
||||||
async refreshBadges(refresher?: CustomEvent<IonRefresher>): Promise<void> {
|
|
||||||
await CoreUtils.instance.ignoreErrors(Promise.all([
|
|
||||||
AddonBadges.instance.invalidateUserBadges(this.courseId, this.userId),
|
|
||||||
]));
|
|
||||||
|
|
||||||
await CoreUtils.instance.ignoreErrors(Promise.all([
|
|
||||||
this.fetchBadges(),
|
|
||||||
]));
|
|
||||||
|
|
||||||
refresher?.detail.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigate to a particular badge.
|
|
||||||
*
|
|
||||||
* @param badgeHash Badge to load.
|
|
||||||
*/
|
|
||||||
loadIssuedBadge(badgeHash: string): void {
|
|
||||||
this.badgeHash = badgeHash;
|
|
||||||
const params = { courseId: this.courseId, userId: this.userId, badgeHash: badgeHash };
|
|
||||||
|
|
||||||
const splitViewLoaded = CoreNavigator.instance.isCurrentPathInTablet('**/badges/user/issue');
|
|
||||||
const path = (splitViewLoaded ? '../' : '') + 'issue';
|
|
||||||
|
|
||||||
CoreNavigator.instance.navigate(path, { params });
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,142 @@
|
||||||
|
// (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 { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
||||||
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
import { AddonBadges, AddonBadgesUserBadge } from '../../services/badges';
|
||||||
|
import { CoreTimeUtils } from '@services/utils/time';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||||
|
import { ActivatedRoute, ActivatedRouteSnapshot, Params } from '@angular/router';
|
||||||
|
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||||
|
import { CoreObject } from '@singletons/object';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays the list of calendar events.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'page-addon-badges-user-badges',
|
||||||
|
templateUrl: 'user-badges.html',
|
||||||
|
})
|
||||||
|
export class AddonBadgesUserBadgesPage implements AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
|
currentTime = 0;
|
||||||
|
badges: AddonBadgesUserBadgesManager;
|
||||||
|
|
||||||
|
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||||
|
|
||||||
|
constructor(route: ActivatedRoute) {
|
||||||
|
const courseId = parseInt(route.snapshot.queryParams.courseId ?? 0); // Use 0 for site badges.
|
||||||
|
const userId = parseInt(route.snapshot.queryParams.userId ?? CoreSites.instance.getCurrentSiteUserId());
|
||||||
|
|
||||||
|
this.badges = new AddonBadgesUserBadgesManager(AddonBadgesUserBadgesPage, courseId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async ngAfterViewInit(): Promise<void> {
|
||||||
|
await this.fetchInitialBadges();
|
||||||
|
|
||||||
|
this.badges.watchSplitViewOutlet(this.splitView);
|
||||||
|
this.badges.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.badges.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the badges.
|
||||||
|
*
|
||||||
|
* @param refresher Refresher.
|
||||||
|
*/
|
||||||
|
async refreshBadges(refresher?: IonRefresher): Promise<void> {
|
||||||
|
await CoreUtils.instance.ignoreErrors(AddonBadges.instance.invalidateUserBadges(this.badges.courseId, this.badges.userId));
|
||||||
|
await CoreUtils.instance.ignoreErrors(this.fetchBadges());
|
||||||
|
|
||||||
|
refresher?.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtain the initial list of badges.
|
||||||
|
*/
|
||||||
|
private async fetchInitialBadges(): Promise<void> {
|
||||||
|
this.currentTime = CoreTimeUtils.instance.timestamp();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.fetchBadges();
|
||||||
|
} catch (message) {
|
||||||
|
CoreDomUtils.instance.showErrorModalDefault(message, 'Error loading badges');
|
||||||
|
|
||||||
|
this.badges.setItems([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the list of badges.
|
||||||
|
*/
|
||||||
|
private async fetchBadges(): Promise<void> {
|
||||||
|
const badges = await AddonBadges.instance.getUserBadges(this.badges.courseId, this.badges.userId);
|
||||||
|
|
||||||
|
this.badges.setItems(badges);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to manage badges.
|
||||||
|
*/
|
||||||
|
class AddonBadgesUserBadgesManager extends CorePageItemsListManager<AddonBadgesUserBadge> {
|
||||||
|
|
||||||
|
courseId: number;
|
||||||
|
userId: number;
|
||||||
|
|
||||||
|
constructor(pageComponent: unknown, courseId: number, userId: number) {
|
||||||
|
super(pageComponent);
|
||||||
|
|
||||||
|
this.courseId = courseId;
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected getItemPath(badge: AddonBadgesUserBadge): string {
|
||||||
|
return badge.uniquehash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected getItemQueryParams(): Params {
|
||||||
|
return CoreObject.withoutEmpty({
|
||||||
|
courseId: this.courseId,
|
||||||
|
userId: this.userId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
|
||||||
|
return route.params.badgeHash ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -43,13 +43,7 @@ export class AddonBadgesBadgeLinkHandlerService extends CoreContentLinksHandlerB
|
||||||
|
|
||||||
return [{
|
return [{
|
||||||
action: (siteId: string): void => {
|
action: (siteId: string): void => {
|
||||||
CoreNavigator.instance.navigateToSitePath(
|
CoreNavigator.instance.navigateToSitePath(`/badges/${params.hash}`, { siteId });
|
||||||
'/badges/issue',
|
|
||||||
{
|
|
||||||
siteId,
|
|
||||||
params: { courseId: 0, badgeHash: params.hash },
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ export class AddonBadgesMyBadgesLinkHandlerService extends CoreContentLinksHandl
|
||||||
getActions(): CoreContentLinksAction[] {
|
getActions(): CoreContentLinksAction[] {
|
||||||
return [{
|
return [{
|
||||||
action: (siteId: string): void => {
|
action: (siteId: string): void => {
|
||||||
CoreNavigator.instance.navigateToSitePath('/badges/user', { siteId });
|
CoreNavigator.instance.navigateToSitePath('/badges', { siteId });
|
||||||
},
|
},
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,9 +59,8 @@ export class AddonBadgesPushClickHandlerService implements CorePushNotifications
|
||||||
|
|
||||||
if (data.hash) {
|
if (data.hash) {
|
||||||
// We have the hash, open the badge directly.
|
// We have the hash, open the badge directly.
|
||||||
await CoreNavigator.instance.navigateToSitePath('/badges/issue', {
|
await CoreNavigator.instance.navigateToSitePath(`/badges/${data.hash}`, {
|
||||||
siteId: notification.site,
|
siteId: notification.site,
|
||||||
params: { courseId: 0, badgeHash: data.hash },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -76,7 +75,7 @@ export class AddonBadgesPushClickHandlerService implements CorePushNotifications
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
await CoreNavigator.instance.navigateToSitePath('/badges/user', { siteId: notification.site });
|
await CoreNavigator.instance.navigateToSitePath('/badges', { siteId: notification.site });
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { CoreUserProfile } from '@features/user/services/user';
|
||||||
import { CoreUserDelegateService, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@features/user/services/user-delegate';
|
import { CoreUserDelegateService, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@features/user/services/user-delegate';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { CoreObject } from '@singletons/object';
|
||||||
import { AddonBadges } from '../badges';
|
import { AddonBadges } from '../badges';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,10 +73,9 @@ export class AddonBadgesUserHandlerService implements CoreUserProfileHandler {
|
||||||
action: (event, user, courseId): void => {
|
action: (event, user, courseId): void => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
CoreNavigator.instance.navigateToSitePath(
|
CoreNavigator.instance.navigateToSitePath('/badges', {
|
||||||
'/badges/user',
|
params: CoreObject.withoutEmpty({ courseId, userId: user.id }),
|
||||||
{ params: { courseId: courseId || 0, userId: user.id } },
|
});
|
||||||
);
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
// limitations under the License.
|
||||||
|
|
||||||
import { AfterViewInit, Component, ElementRef, HostBinding, Input, OnDestroy, ViewChild } from '@angular/core';
|
import { AfterViewInit, Component, ElementRef, HostBinding, Input, OnDestroy, ViewChild } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
import { IonRouterOutlet } from '@ionic/angular';
|
import { IonRouterOutlet } from '@ionic/angular';
|
||||||
import { CoreScreen } from '@services/screen';
|
import { CoreScreen } from '@services/screen';
|
||||||
import { Subscription } from 'rxjs';
|
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||||
|
|
||||||
enum CoreSplitViewMode {
|
enum CoreSplitViewMode {
|
||||||
MenuOnly = 'menu-only', // Hides content.
|
MenuOnly = 'menu-only', // Hides content.
|
||||||
|
@ -35,18 +36,33 @@ export class CoreSplitViewComponent implements AfterViewInit, OnDestroy {
|
||||||
@Input() placeholderText = 'core.emptysplit';
|
@Input() placeholderText = 'core.emptysplit';
|
||||||
isNested = false;
|
isNested = false;
|
||||||
|
|
||||||
|
private outletRouteSubject: BehaviorSubject<ActivatedRouteSnapshot | null> = new BehaviorSubject(null);
|
||||||
private subscriptions?: Subscription[];
|
private subscriptions?: Subscription[];
|
||||||
|
|
||||||
constructor(private element: ElementRef<HTMLElement>) {}
|
constructor(private element: ElementRef<HTMLElement>) {}
|
||||||
|
|
||||||
|
get outletRoute(): ActivatedRouteSnapshot | null {
|
||||||
|
return this.outletRouteSubject.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get outletRouteObservable(): Observable<ActivatedRouteSnapshot | null> {
|
||||||
|
return this.outletRouteSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngAfterViewInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.isNested = !!this.element.nativeElement.parentElement?.closest('core-split-view');
|
this.isNested = !!this.element.nativeElement.parentElement?.closest('core-split-view');
|
||||||
this.subscriptions = [
|
this.subscriptions = [
|
||||||
this.outlet.activateEvents.subscribe(() => this.updateClasses()),
|
this.outlet.activateEvents.subscribe(() => {
|
||||||
this.outlet.deactivateEvents.subscribe(() => this.updateClasses()),
|
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()),
|
CoreScreen.instance.layoutObservable.subscribe(() => this.updateClasses()),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -8,18 +8,18 @@
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<core-split-view>
|
<core-split-view>
|
||||||
<ion-refresher slot="fixed" [disabled]="!gradesTableLoaded" (ionRefresh)="refreshGradesTable($event.target)">
|
<ion-refresher slot="fixed" [disabled]="!grades.loaded" (ionRefresh)="refreshGrades($event.target)">
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
<core-loading [hideUntil]="gradesTableLoaded" class="safe-area-page">
|
<core-loading [hideUntil]="grades.loaded" class="safe-area-page">
|
||||||
<core-empty-box *ngIf="!gradesTable" icon="stats" [message]="'core.grades.nogradesreturned' | translate">
|
<core-empty-box *ngIf="grades.empty" icon="stats" [message]="'core.grades.nogradesreturned' | translate">
|
||||||
</core-empty-box>
|
</core-empty-box>
|
||||||
<div *ngIf="gradesTable" class="core-grades-container">
|
<div *ngIf="!grades.empty" class="core-grades-container">
|
||||||
<table cellspacing="0" cellpadding="0" class="core-grades-table">
|
<table cellspacing="0" cellpadding="0" class="core-grades-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th
|
<th
|
||||||
*ngFor="let column of gradesTable.columns"
|
*ngFor="let column of grades.columns"
|
||||||
id="{{column.name}}"
|
id="{{column.name}}"
|
||||||
class="ion-text-start"
|
class="ion-text-start"
|
||||||
[class.ion-hide-md-down]="column.hiddenPhone"
|
[class.ion-hide-md-down]="column.hiddenPhone"
|
||||||
|
@ -31,8 +31,8 @@
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr
|
<tr
|
||||||
*ngFor="let row of gradesTable.rows"
|
*ngFor="let row of grades.rows"
|
||||||
(click)="row.itemtype != 'category' && gotoGrade(row.id)"
|
(click)="row.itemtype != 'category' && grades.select(row)"
|
||||||
[class]="row.rowclass"
|
[class]="row.rowclass"
|
||||||
[ngClass]='{"core-grades-grade-clickable": row.itemtype != "category"}'
|
[ngClass]='{"core-grades-grade-clickable": row.itemtype != "category"}'
|
||||||
>
|
>
|
||||||
|
@ -45,14 +45,14 @@
|
||||||
<th
|
<th
|
||||||
class="core-grades-table-gradeitem ion-text-start"
|
class="core-grades-table-gradeitem ion-text-start"
|
||||||
[class.column-itemname]="row.itemtype == 'category'"
|
[class.column-itemname]="row.itemtype == 'category'"
|
||||||
[class.core-selected-item]="activeGradeId == row.id"
|
[class.core-selected-item]="grades.isSelected(row)"
|
||||||
[attr.colspan]="row.colspan"
|
[attr.colspan]="row.colspan"
|
||||||
>
|
>
|
||||||
<ion-icon *ngIf="row.icon" name="{{row.icon}}" slot="start"></ion-icon>
|
<ion-icon *ngIf="row.icon" name="{{row.icon}}" slot="start"></ion-icon>
|
||||||
<img *ngIf="row.image" [src]="row.image" slot="start" />
|
<img *ngIf="row.image" [src]="row.image" slot="start" />
|
||||||
<span [innerHTML]="row.gradeitem"></span>
|
<span [innerHTML]="row.gradeitem"></span>
|
||||||
</th>
|
</th>
|
||||||
<ng-container *ngFor="let column of gradesTable.columns">
|
<ng-container *ngFor="let column of grades.columns">
|
||||||
<td
|
<td
|
||||||
*ngIf="column.name != 'gradeitem' && row[column.name] != undefined"
|
*ngIf="column.name != 'gradeitem' && row[column.name] != undefined"
|
||||||
[class]="'ion-text-start core-grades-table-' + column.name"
|
[class]="'ion-text-start core-grades-table-' + column.name"
|
||||||
|
|
|
@ -12,20 +12,24 @@
|
||||||
// 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 } from '@angular/router';
|
import { ActivatedRoute, ActivatedRouteSnapshot, Params } from '@angular/router';
|
||||||
import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
|
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreGrades } from '@features/grades/services/grades';
|
import { CoreGrades } from '@features/grades/services/grades';
|
||||||
import { CoreGradesFormattedTable, CoreGradesHelper } from '@features/grades/services/grades-helper';
|
import {
|
||||||
import { CoreNavigator } from '@services/navigator';
|
CoreGradesFormattedTable,
|
||||||
|
CoreGradesFormattedTableColumn,
|
||||||
|
CoreGradesFormattedTableRow,
|
||||||
|
CoreGradesFormattedTableRowFilled,
|
||||||
|
CoreGradesHelper,
|
||||||
|
} from '@features/grades/services/grades-helper';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreScreen } from '@services/screen';
|
|
||||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||||
import { CoreObject } from '@singletons/object';
|
import { CoreObject } from '@singletons/object';
|
||||||
|
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays a course grades.
|
* Page that displays a course grades.
|
||||||
|
@ -35,116 +39,147 @@ import { CoreObject } from '@singletons/object';
|
||||||
templateUrl: 'course.html',
|
templateUrl: 'course.html',
|
||||||
styleUrls: ['course.scss'],
|
styleUrls: ['course.scss'],
|
||||||
})
|
})
|
||||||
export class CoreGradesCoursePage implements OnInit, OnDestroy {
|
export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
courseId: number;
|
grades: CoreGradesCourseManager;
|
||||||
userId: number;
|
|
||||||
gradesTable?: CoreGradesFormattedTable;
|
|
||||||
gradesTableLoaded = false;
|
|
||||||
activeGradeId?: number;
|
|
||||||
layoutSubscription?: Subscription;
|
|
||||||
|
|
||||||
@ViewChild(CoreSplitViewComponent) splitView?: CoreSplitViewComponent;
|
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute) {
|
constructor(route: ActivatedRoute) {
|
||||||
this.courseId = route.snapshot.params.courseId;
|
const courseId = parseInt(route.snapshot.params.courseId);
|
||||||
this.userId = route.snapshot.queryParams.userId ?? CoreSites.instance.getCurrentSiteUserId();
|
const userId = parseInt(route.snapshot.queryParams.userId ?? CoreSites.instance.getCurrentSiteUserId());
|
||||||
|
|
||||||
|
this.grades = new CoreGradesCourseManager(CoreGradesCoursePage, courseId, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async ngOnInit(): Promise<void> {
|
async ngAfterViewInit(): Promise<void> {
|
||||||
this.layoutSubscription = CoreScreen.instance.layoutObservable.subscribe(() => this.updateActiveGrade());
|
await this.fetchInitialGrades();
|
||||||
|
|
||||||
await this.fetchGradesTable();
|
this.grades.watchSplitViewOutlet(this.splitView);
|
||||||
|
this.grades.start();
|
||||||
// Add log in Moodle.
|
|
||||||
await CoreUtils.instance.ignoreErrors(CoreGrades.instance.logCourseGradesView(this.courseId, this.userId));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
ionViewWillEnter(): void {
|
|
||||||
this.updateActiveGrade();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.layoutSubscription?.unsubscribe();
|
this.grades.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch all the data required for the view.
|
* Refresh grades.
|
||||||
*/
|
|
||||||
async fetchGradesTable(): Promise<void> {
|
|
||||||
try {
|
|
||||||
const table = await CoreGrades.instance.getCourseGradesTable(this.courseId, this.userId);
|
|
||||||
|
|
||||||
this.gradesTable = CoreGradesHelper.instance.formatGradesTable(table);
|
|
||||||
} catch (error) {
|
|
||||||
CoreDomUtils.instance.showErrorModalDefault(error, 'Error loading grades');
|
|
||||||
|
|
||||||
this.gradesTable = { rows: [], columns: [] };
|
|
||||||
} finally {
|
|
||||||
this.gradesTableLoaded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refresh data.
|
|
||||||
*
|
*
|
||||||
* @param refresher Refresher.
|
* @param refresher Refresher.
|
||||||
*/
|
*/
|
||||||
async refreshGradesTable(refresher: IonRefresher): Promise<void> {
|
async refreshGrades(refresher: IonRefresher): Promise<void> {
|
||||||
await CoreUtils.instance.ignoreErrors(CoreGrades.instance.invalidateCourseGradesData(this.courseId, this.userId));
|
const { courseId, userId } = this.grades;
|
||||||
await CoreUtils.instance.ignoreErrors(this.fetchGradesTable());
|
|
||||||
|
|
||||||
refresher.complete();
|
await CoreUtils.instance.ignoreErrors(CoreGrades.instance.invalidateCourseGradesData(courseId, userId));
|
||||||
|
await CoreUtils.instance.ignoreErrors(this.fetchGrades());
|
||||||
|
|
||||||
|
refresher?.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigate to the grade of the selected item.
|
* Obtain the initial table of grades.
|
||||||
*
|
|
||||||
* @param gradeId Grade item ID where to navigate.
|
|
||||||
*/
|
*/
|
||||||
async gotoGrade(gradeId: number): Promise<void> {
|
private async fetchInitialGrades(): Promise<void> {
|
||||||
const path = this.activeGradeId ? `../${gradeId}` : gradeId.toString();
|
try {
|
||||||
|
await this.fetchGrades();
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.instance.showErrorModalDefault(error, 'Error loading course');
|
||||||
|
|
||||||
await CoreNavigator.instance.navigate(path, {
|
this.grades.setTable({ columns: [], rows: [] });
|
||||||
params: CoreObject.withoutEmpty({ userId: this.userId }),
|
|
||||||
});
|
|
||||||
|
|
||||||
this.updateActiveGrade(gradeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update active grade.
|
|
||||||
*
|
|
||||||
* @param activeGradeId Active grade id.
|
|
||||||
*/
|
|
||||||
private updateActiveGrade(activeGradeId?: number): void {
|
|
||||||
if (CoreScreen.instance.isMobile || this.splitView?.isNested) {
|
|
||||||
delete this.activeGradeId;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeGradeId = activeGradeId ?? this.guessActiveGrade();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Guess active grade looking at the current route.
|
* Update the table of grades.
|
||||||
*
|
|
||||||
* @return Active grade id.
|
|
||||||
*/
|
*/
|
||||||
private guessActiveGrade(): number | undefined {
|
private async fetchGrades(): Promise<void> {
|
||||||
const gradeId = parseInt(this.route.snapshot?.firstChild?.params.gradeId);
|
const table = await CoreGrades.instance.getCourseGradesTable(this.grades.courseId!, this.grades.userId);
|
||||||
|
const formattedTable = await CoreGradesHelper.instance.formatGradesTable(table);
|
||||||
|
|
||||||
return isNaN(gradeId) ? undefined : gradeId;
|
this.grades.setTable(formattedTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to manage the table of grades.
|
||||||
|
*/
|
||||||
|
class CoreGradesCourseManager extends CorePageItemsListManager<CoreGradesFormattedTableRowFilled> {
|
||||||
|
|
||||||
|
courseId: number;
|
||||||
|
userId: number;
|
||||||
|
columns?: CoreGradesFormattedTableColumn[];
|
||||||
|
rows?: CoreGradesFormattedTableRow[];
|
||||||
|
|
||||||
|
constructor(pageComponent: unknown, courseId: number, userId: number) {
|
||||||
|
super(pageComponent);
|
||||||
|
|
||||||
|
this.courseId = courseId;
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set grades table.
|
||||||
|
*
|
||||||
|
* @param table Grades table.
|
||||||
|
*/
|
||||||
|
setTable(table: CoreGradesFormattedTable): void {
|
||||||
|
this.columns = table.columns;
|
||||||
|
this.rows = table.rows;
|
||||||
|
|
||||||
|
this.setItems(table.rows.filter(this.isFilledRow));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected getDefaultItem(): CoreGradesFormattedTableRowFilled | null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected getItemPath(row: CoreGradesFormattedTableRowFilled): string {
|
||||||
|
return row.id.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected getItemQueryParams(): Params {
|
||||||
|
return CoreObject.withoutEmpty({ userId: this.userId });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
|
||||||
|
return route.params.gradeId ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected async logActivity(): Promise<void> {
|
||||||
|
await CoreGrades.instance.logCourseGradesView(this.courseId!, this.userId!);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the given row is filled or not.
|
||||||
|
*
|
||||||
|
* @param row Grades table row.
|
||||||
|
* @return Whether the given row is filled or not.
|
||||||
|
*/
|
||||||
|
private isFilledRow(row: CoreGradesFormattedTableRow): row is CoreGradesFormattedTableRowFilled {
|
||||||
|
return 'id' in row;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,34 +8,34 @@
|
||||||
</ion-header>
|
</ion-header>
|
||||||
<ion-content>
|
<ion-content>
|
||||||
<core-split-view>
|
<core-split-view>
|
||||||
<ion-refresher slot="fixed" [disabled]="!gradesLoaded" (ionRefresh)="refreshGrades($event.target)">
|
<ion-refresher slot="fixed" [disabled]="!courses.loaded" (ionRefresh)="refreshCourses($event.target)">
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
<core-loading [hideUntil]="gradesLoaded">
|
<core-loading [hideUntil]="courses.loaded">
|
||||||
<core-empty-box
|
<core-empty-box
|
||||||
*ngIf="grades && grades.length == 0"
|
*ngIf="courses.empty"
|
||||||
icon="stats"
|
icon="stats"
|
||||||
[message]="'core.grades.nogradesreturned' | translate"
|
[message]="'core.grades.nogradesreturned' | translate"
|
||||||
></core-empty-box>
|
></core-empty-box>
|
||||||
|
|
||||||
<ion-list *ngIf="grades && grades.length > 0">
|
<ion-list *ngIf="!courses.empty">
|
||||||
<ion-item
|
<ion-item
|
||||||
*ngFor="let grade of grades"
|
*ngFor="let course of courses.items"
|
||||||
[title]="grade.courseFullName"
|
[title]="course.courseFullName"
|
||||||
[class.core-selected-item]="grade.courseid === this.activeCourseId"
|
[class.core-selected-item]="courses.isSelected(course)"
|
||||||
class="ion-text-wrap"
|
class="ion-text-wrap"
|
||||||
button
|
button
|
||||||
detail
|
detail
|
||||||
(click)="openCourse(grade.courseid)"
|
(click)="courses.select(course)"
|
||||||
>
|
>
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<core-format-text
|
<core-format-text
|
||||||
[text]="grade.courseFullName"
|
[text]="course.courseFullName"
|
||||||
[contextInstanceId]="grade.courseid"
|
[contextInstanceId]="course.courseid"
|
||||||
contextLevel="course"
|
contextLevel="course"
|
||||||
></core-format-text>
|
></core-format-text>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
<ion-badge slot="end" color="light">{{grade.grade}}</ion-badge>
|
<ion-badge slot="end" color="light">{{course.grade}}</ion-badge>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ion-list>
|
</ion-list>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
|
|
|
@ -12,17 +12,16 @@
|
||||||
// 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 { Component, OnDestroy, OnInit } from '@angular/core';
|
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
import { Subscription } from 'rxjs';
|
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||||
|
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||||
import { CoreGrades } from '@features/grades/services/grades';
|
import { CoreGrades } from '@features/grades/services/grades';
|
||||||
import { CoreGradesHelper, CoreGradesGradeOverviewWithCourseData } from '@features/grades/services/grades-helper';
|
import { CoreGradesGradeOverviewWithCourseData, CoreGradesHelper } from '@features/grades/services/grades-helper';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
import { CoreScreen } from '@services/screen';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays courses grades (main menu option).
|
* Page that displays courses grades (main menu option).
|
||||||
|
@ -31,113 +30,92 @@ import { ActivatedRoute } from '@angular/router';
|
||||||
selector: 'page-core-grades-courses',
|
selector: 'page-core-grades-courses',
|
||||||
templateUrl: 'courses.html',
|
templateUrl: 'courses.html',
|
||||||
})
|
})
|
||||||
export class CoreGradesCoursesPage implements OnInit, OnDestroy {
|
export class CoreGradesCoursesPage implements OnDestroy, AfterViewInit {
|
||||||
|
|
||||||
grades?: CoreGradesGradeOverviewWithCourseData[];
|
courses: CoreGradesCoursesManager = new CoreGradesCoursesManager(CoreGradesCoursesPage);
|
||||||
gradesLoaded = false;
|
|
||||||
activeCourseId?: number;
|
|
||||||
layoutSubscription?: Subscription;
|
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute) {}
|
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async ngOnInit(): Promise<void> {
|
async ngAfterViewInit(): Promise<void> {
|
||||||
this.layoutSubscription = CoreScreen.instance.layoutObservable.subscribe(() => this.updateActiveCourse());
|
await this.fetchInitialCourses();
|
||||||
this.updateActiveCourse();
|
|
||||||
|
|
||||||
await this.fetchGrades();
|
this.courses.watchSplitViewOutlet(this.splitView);
|
||||||
|
this.courses.start();
|
||||||
if (!CoreScreen.instance.isMobile && !this.activeCourseId && this.grades && this.grades.length > 0) {
|
|
||||||
this.openCourse(this.grades[0].courseid);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add log in Moodle.
|
|
||||||
await CoreUtils.instance.ignoreErrors(CoreGrades.instance.logCoursesGradesView());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
ionViewWillEnter(): void {
|
|
||||||
this.updateActiveCourse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.layoutSubscription?.unsubscribe();
|
this.courses.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch all the data required for the view.
|
* Refresh courses.
|
||||||
*/
|
|
||||||
async fetchGrades(): Promise<void> {
|
|
||||||
try {
|
|
||||||
const grades = await CoreGrades.instance.getCoursesGrades();
|
|
||||||
const gradesWithCourseData = await CoreGradesHelper.instance.getGradesCourseData(grades);
|
|
||||||
|
|
||||||
this.grades = gradesWithCourseData;
|
|
||||||
} catch (error) {
|
|
||||||
CoreDomUtils.instance.showErrorModalDefault(error, 'Error loading grades');
|
|
||||||
|
|
||||||
this.grades = [];
|
|
||||||
} finally {
|
|
||||||
this.gradesLoaded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refresh data.
|
|
||||||
*
|
*
|
||||||
* @param refresher Refresher.
|
* @param refresher Refresher.
|
||||||
*/
|
*/
|
||||||
async refreshGrades(refresher: IonRefresher): Promise<void> {
|
async refreshCourses(refresher: IonRefresher): Promise<void> {
|
||||||
await CoreUtils.instance.ignoreErrors(CoreGrades.instance.invalidateCoursesGradesData());
|
await CoreUtils.instance.ignoreErrors(CoreGrades.instance.invalidateCoursesGradesData());
|
||||||
await CoreUtils.instance.ignoreErrors(this.fetchGrades());
|
await CoreUtils.instance.ignoreErrors(this.fetchCourses());
|
||||||
|
|
||||||
refresher.complete();
|
refresher?.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Navigate to the grades of the selected course.
|
* Obtain the initial list of courses.
|
||||||
*
|
|
||||||
* @param courseId Course Id where to navigate.
|
|
||||||
*/
|
*/
|
||||||
async openCourse(courseId: number): Promise<void> {
|
private async fetchInitialCourses(): Promise<void> {
|
||||||
const path = this.activeCourseId ? `../${courseId}` : courseId.toString();
|
try {
|
||||||
|
await this.fetchCourses();
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.instance.showErrorModalDefault(error, 'Error loading courses');
|
||||||
|
|
||||||
await CoreNavigator.instance.navigate(path);
|
this.courses.setItems([]);
|
||||||
|
|
||||||
this.updateActiveCourse(courseId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update active course.
|
|
||||||
*
|
|
||||||
* @param activeCourseId Active course id.
|
|
||||||
*/
|
|
||||||
private updateActiveCourse(activeCourseId?: number): void {
|
|
||||||
if (CoreScreen.instance.isMobile) {
|
|
||||||
delete this.activeCourseId;
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.activeCourseId = activeCourseId ?? this.guessActiveCourse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Guess active course looking at the current route.
|
* Update the list of courses.
|
||||||
*
|
|
||||||
* @return Active course id.
|
|
||||||
*/
|
*/
|
||||||
private guessActiveCourse(): number | undefined {
|
private async fetchCourses(): Promise<void> {
|
||||||
const courseId = parseInt(this.route.snapshot?.firstChild?.params.courseId);
|
const grades = await CoreGrades.instance.getCoursesGrades();
|
||||||
|
const courses = await CoreGradesHelper.instance.getGradesCourseData(grades);
|
||||||
|
|
||||||
return isNaN(courseId) ? undefined : courseId;
|
this.courses.setItems(courses);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to manage courses.
|
||||||
|
*/
|
||||||
|
class CoreGradesCoursesManager extends CorePageItemsListManager<CoreGradesGradeOverviewWithCourseData> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected getItemPath(courseGrade: CoreGradesGradeOverviewWithCourseData): string {
|
||||||
|
return courseGrade.courseid.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
|
||||||
|
const courseId = parseInt(route?.params.courseId);
|
||||||
|
|
||||||
|
return isNaN(courseId) ? null : courseId.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
protected async logActivity(): Promise<void> {
|
||||||
|
await CoreGrades.instance.logCoursesGradesView();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,9 +38,9 @@ export class CoreGradesGradePage implements OnInit {
|
||||||
gradeLoaded = false;
|
gradeLoaded = false;
|
||||||
|
|
||||||
constructor(route: ActivatedRoute) {
|
constructor(route: ActivatedRoute) {
|
||||||
this.courseId = route.snapshot.params.courseId ?? route.snapshot.parent?.params.courseId;
|
this.courseId = parseInt(route.snapshot.params.courseId ?? route.snapshot.parent?.params.courseId);
|
||||||
this.gradeId = route.snapshot.params.gradeId;
|
this.gradeId = parseInt(route.snapshot.params.gradeId);
|
||||||
this.userId = route.snapshot.queryParams.userId ?? CoreSites.instance.getCurrentSiteUserId();
|
this.userId = parseInt(route.snapshot.queryParams.userId ?? CoreSites.instance.getCurrentSiteUserId());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -144,9 +144,9 @@ export class CoreGradesHelperProvider {
|
||||||
*/
|
*/
|
||||||
formatGradesTable(table: CoreGradesTable): CoreGradesFormattedTable {
|
formatGradesTable(table: CoreGradesTable): CoreGradesFormattedTable {
|
||||||
const maxDepth = table.maxdepth;
|
const maxDepth = table.maxdepth;
|
||||||
const formatted: CoreGradesFormattedTable = {
|
const formatted = {
|
||||||
columns: [],
|
columns: [] as any[],
|
||||||
rows: [],
|
rows: [] as any[],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Columns, in order.
|
// Columns, in order.
|
||||||
|
@ -673,9 +673,21 @@ export class CoreGradesHelper extends makeSingleton(CoreGradesHelperProvider) {}
|
||||||
export type CoreGradesFormattedRow = any;
|
export type CoreGradesFormattedRow = any;
|
||||||
export type CoreGradesFormattedRowForTable = any;
|
export type CoreGradesFormattedRowForTable = any;
|
||||||
export type CoreGradesFormattedItem = any;
|
export type CoreGradesFormattedItem = any;
|
||||||
|
export type CoreGradesFormattedTableColumn = any;
|
||||||
|
export type CoreGradesFormattedTableRow = CoreGradesFormattedTableRowFilled | CoreGradesFormattedTableRowEmpty;
|
||||||
export type CoreGradesFormattedTable = {
|
export type CoreGradesFormattedTable = {
|
||||||
columns: any[];
|
columns: CoreGradesFormattedTableColumn[];
|
||||||
rows: any[];
|
rows: CoreGradesFormattedTableRow[];
|
||||||
|
};
|
||||||
|
export type CoreGradesFormattedTableRowFilled = {
|
||||||
|
// @todo complete types.
|
||||||
|
id: number;
|
||||||
|
itemtype: 'category' | 'leader';
|
||||||
|
grade: unknown;
|
||||||
|
percentage: unknown;
|
||||||
|
};
|
||||||
|
type CoreGradesFormattedTableRowEmpty ={
|
||||||
|
//
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -11,11 +11,11 @@
|
||||||
<core-split-view>
|
<core-split-view>
|
||||||
<ion-list>
|
<ion-list>
|
||||||
<ion-item
|
<ion-item
|
||||||
*ngFor="let section of sections"
|
*ngFor="let section of sections.items"
|
||||||
[class.core-selected-item]="section.name === activeSection"
|
[class.core-selected-item]="sections.isSelected(section)"
|
||||||
button
|
button
|
||||||
detail
|
detail
|
||||||
(click)="openSection(section)"
|
(click)="sections.select(section)"
|
||||||
>
|
>
|
||||||
<ion-icon [name]="section.icon" slot="start"></ion-icon>
|
<ion-icon [name]="section.icon" slot="start"></ion-icon>
|
||||||
<ion-label>{{ 'core.settings.' + section.name | translate }}</ion-label>
|
<ion-label>{{ 'core.settings.' + section.name | translate }}</ion-label>
|
||||||
|
|
|
@ -12,83 +12,57 @@
|
||||||
// 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 { Component, OnDestroy, OnInit } from '@angular/core';
|
import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
|
||||||
import { Subscription } from 'rxjs';
|
|
||||||
|
|
||||||
import { CoreNavigator } from '@services/navigator';
|
|
||||||
import { CoreScreen } from '@services/screen';
|
|
||||||
import { CoreSettingsConstants, CoreSettingsSection } from '@features/settings/constants';
|
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({
|
@Component({
|
||||||
selector: 'page-core-settings-index',
|
selector: 'page-core-settings-index',
|
||||||
templateUrl: 'index.html',
|
templateUrl: 'index.html',
|
||||||
})
|
})
|
||||||
export class CoreSettingsIndexPage implements OnInit, OnDestroy {
|
export class CoreSettingsIndexPage implements AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
sections = CoreSettingsConstants.SECTIONS;
|
sections: CoreSettingsSectionsManager = new CoreSettingsSectionsManager(CoreSettingsIndexPage);
|
||||||
activeSection?: string;
|
|
||||||
layoutSubscription?: Subscription;
|
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngAfterViewInit(): void {
|
||||||
this.layoutSubscription = CoreScreen.instance.layoutObservable.subscribe(() => this.updateActiveSection());
|
this.sections.setItems(CoreSettingsConstants.SECTIONS);
|
||||||
}
|
this.sections.watchSplitViewOutlet(this.splitView);
|
||||||
|
this.sections.start();
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
ionViewWillEnter(): void {
|
|
||||||
this.updateActiveSection();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
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 {
|
class CoreSettingsSectionsManager extends CorePageItemsListManager<CoreSettingsSection> {
|
||||||
const path = this.activeSection ? `../${section.path}` : section.path;
|
|
||||||
|
/**
|
||||||
CoreNavigator.instance.navigate(path);
|
* @inheritdoc
|
||||||
|
*/
|
||||||
this.updateActiveSection(section.name);
|
protected getItemPath(section: CoreSettingsSection): string {
|
||||||
}
|
return section.path;
|
||||||
|
}
|
||||||
/**
|
|
||||||
* Update active section.
|
/**
|
||||||
*
|
* @inheritdoc
|
||||||
* @param activeSection Active section.
|
*/
|
||||||
*/
|
protected getSelectedItemPath(route: ActivatedRouteSnapshot): string | null {
|
||||||
private updateActiveSection(activeSection?: string): void {
|
return route.parent?.routeConfig?.path ?? null;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,14 @@ export type CoreNavigationOptions = {
|
||||||
reset?: boolean;
|
reset?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for CoreNavigatorService#getCurrentRoute method.
|
||||||
|
*/
|
||||||
|
type GetCurrentRouteOptions = Partial<{
|
||||||
|
parentRoute: ActivatedRoute;
|
||||||
|
pageComponent: unknown;
|
||||||
|
}>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to provide some helper functions regarding navigation.
|
* Service to provide some helper functions regarding navigation.
|
||||||
*/
|
*/
|
||||||
|
@ -310,13 +318,26 @@ export class CoreNavigatorService {
|
||||||
/**
|
/**
|
||||||
* Get current activated route.
|
* 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.
|
* @return Current activated route.
|
||||||
*/
|
*/
|
||||||
protected getCurrentRoute(route?: ActivatedRoute): ActivatedRoute {
|
getCurrentRoute(): ActivatedRoute;
|
||||||
route = route ?? Router.instance.routerState.root;
|
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…
Reference in New Issue