MOBILE-4639 badges: Support links to badges/badgeclass.php?id=X
parent
38ae12fe35
commit
5c6744ac48
|
@ -0,0 +1,36 @@
|
||||||
|
// (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 { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { AddonBadgesBadgeClassPage } from './pages/badge-class/badge-class';
|
||||||
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: ':badgeId',
|
||||||
|
component: AddonBadgesBadgeClassPage,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
CoreSharedModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
AddonBadgesBadgeClassPage,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonBadgeClassLazyModule {}
|
|
@ -17,6 +17,7 @@ import { Routes } from '@angular/router';
|
||||||
|
|
||||||
import { AddonBadgesMyBadgesLinkHandler } from './services/handlers/mybadges-link';
|
import { AddonBadgesMyBadgesLinkHandler } from './services/handlers/mybadges-link';
|
||||||
import { AddonBadgesBadgeLinkHandler } from './services/handlers/badge-link';
|
import { AddonBadgesBadgeLinkHandler } from './services/handlers/badge-link';
|
||||||
|
import { AddonBadgesBadgeClassLinkHandler } from './services/handlers/badgeclass-link';
|
||||||
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
||||||
import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
||||||
import { AddonBadgesUserHandler } from './services/handlers/user';
|
import { AddonBadgesUserHandler } from './services/handlers/user';
|
||||||
|
@ -48,6 +49,10 @@ const mainMenuRoutes: Routes = [
|
||||||
path: 'badges',
|
path: 'badges',
|
||||||
loadChildren: () => import('./badges-lazy.module').then(m => m.AddonBadgesLazyModule),
|
loadChildren: () => import('./badges-lazy.module').then(m => m.AddonBadgesLazyModule),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'badgeclass',
|
||||||
|
loadChildren: () => import('./badgeclass-lazy.module').then(m => m.AddonBadgeClassLazyModule),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -61,6 +66,7 @@ const mainMenuRoutes: Routes = [
|
||||||
useValue: () => {
|
useValue: () => {
|
||||||
CoreContentLinksDelegate.registerHandler(AddonBadgesMyBadgesLinkHandler.instance);
|
CoreContentLinksDelegate.registerHandler(AddonBadgesMyBadgesLinkHandler.instance);
|
||||||
CoreContentLinksDelegate.registerHandler(AddonBadgesBadgeLinkHandler.instance);
|
CoreContentLinksDelegate.registerHandler(AddonBadgesBadgeLinkHandler.instance);
|
||||||
|
CoreContentLinksDelegate.registerHandler(AddonBadgesBadgeClassLinkHandler.instance);
|
||||||
CoreUserDelegate.registerHandler(AddonBadgesUserHandler.instance);
|
CoreUserDelegate.registerHandler(AddonBadgesUserHandler.instance);
|
||||||
CorePushNotificationsDelegate.registerClickHandler(AddonBadgesPushClickHandler.instance);
|
CorePushNotificationsDelegate.registerClickHandler(AddonBadgesPushClickHandler.instance);
|
||||||
CoreTagAreaDelegate.registerHandler(AddonBadgesTagAreaHandler.instance);
|
CoreTagAreaDelegate.registerHandler(AddonBadgesTagAreaHandler.instance);
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [text]="'core.back' | translate" />
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>
|
||||||
|
<h1 *ngIf="badge">{{ badge.name }}</h1>
|
||||||
|
<h1 *ngIf="!badge">{{ 'addon.badges.badgedetails' | translate }}</h1>
|
||||||
|
</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content class="limited-width">
|
||||||
|
<ion-refresher slot="fixed" [disabled]="!badgeLoaded" (ionRefresh)="refreshBadgeClass($event.target)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}" />
|
||||||
|
</ion-refresher>
|
||||||
|
<core-loading [hideUntil]="badgeLoaded">
|
||||||
|
<ng-container *ngIf="badge">
|
||||||
|
<ion-item-group>
|
||||||
|
<ion-item class="ion-text-wrap ion-text-center">
|
||||||
|
<ion-label>
|
||||||
|
<img *ngIf="badge.image" class="large-avatar" [url]="badge.image" core-external-content [alt]="badge.name" />
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item class="ion-text-wrap" *ngIf="badge.name">
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading">{{ 'core.name' | translate}}</p>
|
||||||
|
<p>{{ badge.name }}</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item class="ion-text-wrap" *ngIf="badge.issuer">
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading">{{ 'addon.badges.issuername' | translate}}</p>
|
||||||
|
<p>{{ badge.issuer }}</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item class="ion-text-wrap" *ngIf="badge.coursefullname">
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading">{{ 'core.course' | translate}}</p>
|
||||||
|
<p>
|
||||||
|
<core-format-text [text]="badge.coursefullname" contextLevel="course" [contextInstanceId]="badge.courseid" />
|
||||||
|
</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item class="ion-text-wrap" *ngIf="badge.description">
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading">{{ 'core.description' | translate}}</p>
|
||||||
|
<p>{{ badge.description }}</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-item-group>
|
||||||
|
|
||||||
|
<!-- Competencies alignment -->
|
||||||
|
<ion-item-group *ngIf="badge.alignment?.length">
|
||||||
|
<ion-item-divider>
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{ 'addon.badges.alignment' | translate}}</h2>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item-divider>
|
||||||
|
<ion-item class="ion-text-wrap" *ngFor="let alignment of badge.alignment" [href]="alignment.targetUrl" core-link
|
||||||
|
[autoLogin]="false">
|
||||||
|
<ion-label>
|
||||||
|
<p class="item-heading">{{ alignment.targetName }}</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-item-group>
|
||||||
|
</ng-container>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,91 @@
|
||||||
|
// (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 { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
|
||||||
|
import { CoreTime } from '@singletons/time';
|
||||||
|
import { AddonBadges, AddonBadgesBadgeClass } from '../../services/badges';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays a badge class.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'page-addon-badges-badge-class',
|
||||||
|
templateUrl: 'badge-class.html',
|
||||||
|
})
|
||||||
|
export class AddonBadgesBadgeClassPage implements OnInit {
|
||||||
|
|
||||||
|
protected badgeId = 0;
|
||||||
|
protected logView: (badge: AddonBadgesBadgeClass) => void;
|
||||||
|
|
||||||
|
badge?: AddonBadgesBadgeClass;
|
||||||
|
badgeLoaded = false;
|
||||||
|
currentTime = 0;
|
||||||
|
|
||||||
|
constructor(protected route: ActivatedRoute) {
|
||||||
|
this.badgeId = CoreNavigator.getRequiredRouteNumberParam('badgeId');
|
||||||
|
|
||||||
|
this.logView = CoreTime.once((badge) => {
|
||||||
|
CoreAnalytics.logEvent({
|
||||||
|
type: CoreAnalyticsEventType.VIEW_ITEM,
|
||||||
|
ws: 'core_badges_get_badge',
|
||||||
|
name: badge.name,
|
||||||
|
data: { id: this.badgeId, category: 'badges' },
|
||||||
|
url: `/badges/badgeclass.php?id=${this.badgeId}`,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View loaded.
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.fetchBadgeClass().finally(() => {
|
||||||
|
this.badgeLoaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the badge class required for the view.
|
||||||
|
*
|
||||||
|
* @returns Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async fetchBadgeClass(): Promise<void> {
|
||||||
|
try {
|
||||||
|
this.badge = await AddonBadges.getBadgeClass(this.badgeId);
|
||||||
|
|
||||||
|
this.logView(this.badge);
|
||||||
|
} catch (message) {
|
||||||
|
CoreDomUtils.showErrorModalDefault(message, 'Error getting badge data.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the badge class.
|
||||||
|
*
|
||||||
|
* @param refresher Refresher.
|
||||||
|
*/
|
||||||
|
async refreshBadgeClass(refresher?: HTMLIonRefresherElement): Promise<void> {
|
||||||
|
await CoreUtils.ignoreErrors(AddonBadges.invalidateBadgeClass(this.badgeId));
|
||||||
|
|
||||||
|
await this.fetchBadgeClass();
|
||||||
|
|
||||||
|
refresher?.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -165,6 +165,76 @@ export class AddonBadgesProvider {
|
||||||
await site.invalidateWsCacheForKey(this.getUserBadgeByHashCacheKey(hash));
|
await site.invalidateWsCacheForKey(this.getUserBadgeByHashCacheKey(hash));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the cache key for the get badge class WS call.
|
||||||
|
*
|
||||||
|
* @param id Badge ID.
|
||||||
|
* @returns Cache key.
|
||||||
|
*/
|
||||||
|
protected getBadgeClassCacheKey(id: number): string {
|
||||||
|
return ROOT_CACHE_KEY + 'badgeclass:' + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get badge class.
|
||||||
|
*
|
||||||
|
* @param id Badge ID.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @returns Promise to be resolved when the badge is retrieved.
|
||||||
|
* @since 4.5
|
||||||
|
*/
|
||||||
|
async getBadgeClass(id: number, siteId?: string): Promise<AddonBadgesBadgeClass> {
|
||||||
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
const data: AddonBadgesGetBadgeClassWSParams = {
|
||||||
|
id,
|
||||||
|
};
|
||||||
|
const preSets = {
|
||||||
|
cacheKey: this.getBadgeClassCacheKey(id),
|
||||||
|
updateFrequency: CoreSite.FREQUENCY_RARELY,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await site.read<AddonBadgesGetBadgeClassWSResponse>(
|
||||||
|
'core_badges_get_badge',
|
||||||
|
data,
|
||||||
|
preSets,
|
||||||
|
);
|
||||||
|
const badge = response?.badge;
|
||||||
|
if (!badge) {
|
||||||
|
throw new CoreError('Invalid badge response');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exclude alignments without targetName, we can't display them.
|
||||||
|
badge.alignment = badge.alignment?.filter((alignment) => alignment.targetName);
|
||||||
|
|
||||||
|
return badge;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidate get badge class WS call.
|
||||||
|
*
|
||||||
|
* @param id Badge ID.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @returns Promise resolved when data is invalidated.ç
|
||||||
|
* @since 4.5
|
||||||
|
*/
|
||||||
|
async invalidateBadgeClass(id: number, siteId?: string): Promise<void> {
|
||||||
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
|
await site.invalidateWsCacheForKey(this.getBadgeClassCacheKey(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether get badge class WS is available.
|
||||||
|
*
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @returns If WS is available.
|
||||||
|
*/
|
||||||
|
async isGetBadgeClassAvailable(siteId?: string): Promise<boolean> {
|
||||||
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
|
return site.wsAvailable('core_badges_get_badge');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddonBadges = makeSingleton(AddonBadgesProvider);
|
export const AddonBadges = makeSingleton(AddonBadgesProvider);
|
||||||
|
@ -288,3 +358,44 @@ type AddonBadgesGetUserBadgeByHashWSResponse = {
|
||||||
badge: AddonBadgesUserBadge[];
|
badge: AddonBadgesUserBadge[];
|
||||||
warnings?: CoreWSExternalWarning[];
|
warnings?: CoreWSExternalWarning[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params of core_badges_get_badge WS.
|
||||||
|
*/
|
||||||
|
type AddonBadgesGetBadgeClassWSParams = {
|
||||||
|
id: number; // Badge ID.
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data returned by core_badges_get_badge WS.
|
||||||
|
*/
|
||||||
|
type AddonBadgesGetBadgeClassWSResponse = {
|
||||||
|
badge: AddonBadgesBadgeClass;
|
||||||
|
warnings?: CoreWSExternalWarning[];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Badge data returned by core_badges_get_badge WS.
|
||||||
|
*/
|
||||||
|
export type AddonBadgesBadgeClass = {
|
||||||
|
type: string; // BadgeClass.
|
||||||
|
id: string; // Unique identifier for this badgeclass (URL).
|
||||||
|
issuer?: string; // Issuer for this badgeclass.
|
||||||
|
name: string; // Name of the badgeclass.
|
||||||
|
image: string; // URL to the image.
|
||||||
|
description: string; // Description of the badge class.
|
||||||
|
hostedUrl?: string; // Identifier of the open badge for this assertion.
|
||||||
|
courseid?: number; // Course ID.
|
||||||
|
coursefullname?: string; // Full name of the course.
|
||||||
|
alignment?: { // Badge alignments.
|
||||||
|
id?: number; // Alignment id.
|
||||||
|
badgeid?: number; // Badge id.
|
||||||
|
targetName?: string; // Target name.
|
||||||
|
targetUrl?: string; // Target URL.
|
||||||
|
targetDescription?: string; // Target description.
|
||||||
|
targetFramework?: string; // Target framework.
|
||||||
|
targetCode?: string; // Target code.
|
||||||
|
}[];
|
||||||
|
criteriaUrl?: string; // Criteria URL.
|
||||||
|
criteriaNarrative?: string; // Criteria narrative.
|
||||||
|
};
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { makeSingleton } from '@singletons';
|
||||||
import { AddonBadgesHelper } from '../badges-helper';
|
import { AddonBadgesHelper } from '../badges-helper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler to treat links to user participants page.
|
* Handler to treat links to issued badges.
|
||||||
*/
|
*/
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class AddonBadgesBadgeLinkHandlerService extends CoreContentLinksHandlerBase {
|
export class AddonBadgesBadgeLinkHandlerService extends CoreContentLinksHandlerBase {
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
|
||||||
|
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonBadges } from '../badges';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to treat links to badge classes.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AddonBadgesBadgeClassLinkHandlerService extends CoreContentLinksHandlerBase {
|
||||||
|
|
||||||
|
name = 'AddonBadgesBadgeClassLinkHandler';
|
||||||
|
pattern = /\/badges\/badgeclass\.php.*([?&]id=)/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
|
||||||
|
|
||||||
|
return [{
|
||||||
|
action: async (siteId: string): Promise<void> => {
|
||||||
|
await CoreNavigator.navigateToSitePath(`/badgeclass/${params.id}`, { siteId });
|
||||||
|
},
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabled(siteId: string): Promise<boolean> {
|
||||||
|
const pluginEnabled = await AddonBadges.isPluginEnabled(siteId);
|
||||||
|
const wsAvailable = await AddonBadges.isGetBadgeClassAvailable(siteId);
|
||||||
|
|
||||||
|
return pluginEnabled && wsAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AddonBadgesBadgeClassLinkHandler = makeSingleton(AddonBadgesBadgeClassLinkHandlerService);
|
Loading…
Reference in New Issue