MOBILE-4456 course: Adapt purpose of the icons to new version

main
Pau Ferrer Ocaña 2024-01-26 13:15:10 +01:00
parent b69341ecb2
commit c3c414a1da
18 changed files with 136 additions and 38 deletions

View File

@ -45,7 +45,7 @@ export class AddonModBBBModuleHandlerService extends CoreModuleHandlerBase imple
[CoreConstants.FEATURE_GRADE_OUTCOMES]: true,
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
[CoreConstants.FEATURE_SHOW_DESCRIPTION]: true,
[CoreConstants.FEATURE_MOD_PURPOSE]: ModPurpose.MOD_PURPOSE_OTHER,
[CoreConstants.FEATURE_MOD_PURPOSE]: ModPurpose.MOD_PURPOSE_COMMUNICATION,
};
/**

View File

@ -42,7 +42,7 @@ export class AddonModH5PActivityModuleHandlerService extends CoreModuleHandlerBa
[CoreConstants.FEATURE_GRADE_HAS_GRADE]: true,
[CoreConstants.FEATURE_GRADE_OUTCOMES]: true,
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
[CoreConstants.FEATURE_MOD_PURPOSE]: ModPurpose.MOD_PURPOSE_CONTENT,
[CoreConstants.FEATURE_MOD_PURPOSE]: ModPurpose.MOD_PURPOSE_INTERACTIVECONTENT,
};
/**

View File

@ -42,7 +42,7 @@ export class AddonModImscpModuleHandlerService extends CoreModuleHandlerBase imp
[CoreConstants.FEATURE_GRADE_OUTCOMES]: false,
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
[CoreConstants.FEATURE_SHOW_DESCRIPTION]: true,
[CoreConstants.FEATURE_MOD_PURPOSE]: ModPurpose.MOD_PURPOSE_CONTENT,
[CoreConstants.FEATURE_MOD_PURPOSE]: ModPurpose.MOD_PURPOSE_INTERACTIVECONTENT,
};
/**

View File

@ -42,7 +42,7 @@ export class AddonModLessonModuleHandlerService extends CoreModuleHandlerBase im
[CoreConstants.FEATURE_GRADE_OUTCOMES]: true,
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
[CoreConstants.FEATURE_SHOW_DESCRIPTION]: true,
[CoreConstants.FEATURE_MOD_PURPOSE]: ModPurpose.MOD_PURPOSE_CONTENT,
[CoreConstants.FEATURE_MOD_PURPOSE]: ModPurpose.MOD_PURPOSE_INTERACTIVECONTENT,
};
/**

View File

@ -44,7 +44,7 @@ export class AddonModLtiModuleHandlerService extends CoreModuleHandlerBase imple
[CoreConstants.FEATURE_GRADE_OUTCOMES]: true,
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
[CoreConstants.FEATURE_SHOW_DESCRIPTION]: true,
[CoreConstants.FEATURE_MOD_PURPOSE]: ModPurpose.MOD_PURPOSE_CONTENT,
[CoreConstants.FEATURE_MOD_PURPOSE]: ModPurpose.MOD_PURPOSE_OTHER,
};
/**

View File

@ -41,7 +41,7 @@ export class AddonModScormModuleHandlerService extends CoreModuleHandlerBase imp
[CoreConstants.FEATURE_GRADE_OUTCOMES]: true,
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
[CoreConstants.FEATURE_SHOW_DESCRIPTION]: true,
[CoreConstants.FEATURE_MOD_PURPOSE]: ModPurpose.MOD_PURPOSE_CONTENT,
[CoreConstants.FEATURE_MOD_PURPOSE]: ModPurpose.MOD_PURPOSE_INTERACTIVECONTENT,
};
/**

View File

@ -1431,7 +1431,7 @@ export class CoreAuthenticatedSite extends CoreUnauthenticatedSite {
}
}
}
} else if (typeof versions == 'string') {
} else if (typeof versions === 'string') {
// Compare with this version.
return siteVersion >= this.getVersionNumber(versions);
}

View File

@ -1,5 +1,7 @@
<img *ngIf="!isLocalUrl" [src]="icon" [alt]="showAlt ? modNameTranslated : ''" [attr.role]="!showAlt ? 'presentation' : null"
class="core-module-icon" [class.no-filter]="noFilter" core-external-content [component]="linkIconWithComponent ? modname : null"
<img *ngIf="!isLocalUrl && !iconSVG" [src]="icon" [alt]="showAlt ? modNameTranslated : ''" [attr.role]="!showAlt ? 'presentation' : null"
class="core-module-icon" core-external-content [component]="linkIconWithComponent ? modname : null"
[componentId]="linkIconWithComponent ? componentId : null" (error)="loadFallbackIcon()">
<img *ngIf="isLocalUrl" [src]="icon" [alt]="showAlt ? modNameTranslated : ''" [attr.role]="!showAlt ? 'presentation' : null"
class="core-module-icon" [class.no-filter]="noFilter" (error)="loadFallbackIcon()">
<img *ngIf="isLocalUrl && !iconSVG" [src]="icon" [alt]="showAlt ? modNameTranslated : ''" [attr.role]="!showAlt ? 'presentation' : null"
class="core-module-icon" (error)="loadFallbackIcon()">
<div *ngIf="iconSVG" class="core-module-icon" [innerHTML]="iconSVG" [attr.aria-label]="showAlt ? modNameTranslated : ''"
[attr.role]="!showAlt ? 'presentation' : null"></div>

View File

@ -2,7 +2,7 @@
:host {
display: inline-block;
--size: var(--module-icon-size, 24px);
--size: var(--module-icon-size, 32px);
--padding: var(--module-icon-padding, 8px);
--icon-radius: var(--module-icon-radius, var(--radius-xs));
--margin-end: 0px;
@ -14,16 +14,43 @@
border-radius: var(--icon-radius);
padding: var(--padding);
background-color: $gray-100;
background-color: transparent;
line-height: var(--size);
@each $type, $value in $activity-icon-colors {
&.version_current {
@each $type, $value in $activity-icon-color-filters {
&.#{$type} {
background-color: var(--activity#{$type});
--filter: var(--activity#{$type});
}
}
img {
filter: var(--filter, brightness(0) invert(1));
}
}
&.version_40 {
--size: var(--module-icon-size, 24px);
background-color: $gray-100;
@each $type, $value in $activity-icon-background-colors {
&.#{$type} {
background-color: var(--activity-40-#{$type});
img {
filter: var(--filter, brightness(0) invert(1));
}
}
}
}
&.version_legacy {
--size: var(--module-icon-size, 24px);
--filter: none;
background-color: $gray-100;
}
&.no-filter {
--filter: none !important;
}
}
@ -41,11 +68,6 @@ img {
white-space: nowrap;
overflow: hidden;
}
&.no-filter {
--filter: none;
}
}
:host-context(ion-item) {

View File

@ -13,7 +13,7 @@
// limitations under the License.
import { CoreConstants, ModPurpose } from '@/core/constants';
import { Component, ElementRef, Input, OnChanges, OnInit, SimpleChange } from '@angular/core';
import { Component, ElementRef, HostBinding, Input, OnChanges, OnInit, SimpleChange } from '@angular/core';
import { CoreCourse } from '@features/course/services/course';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
import { CoreSites } from '@services/sites';
@ -23,6 +23,12 @@ import { CoreUrlUtils } from '@services/utils/url';
const assetsPath = 'assets/img/';
const fallbackModName = 'external-tool';
const enum IconVersion {
LEGACY_VERSION = 'version_legacy',
VERSION_4_0 = 'version_40',
CURRENT_VERSION = 'version_current',
}
/**
* Component to handle a module icon.
*/
@ -33,6 +39,8 @@ const fallbackModName = 'external-tool';
})
export class CoreModIconComponent implements OnInit, OnChanges {
@HostBinding('class.no-filter') noFilter = false;
@Input() modname = ''; // The module name. Used also as component if set.
@Input() fallbackTranslation = ''; // Fallback translation string if cannot auto translate.
@Input() componentId?: number; // Component Id for external icons.
@ -41,12 +49,12 @@ export class CoreModIconComponent implements OnInit, OnChanges {
@Input() purpose: ModPurpose = ModPurpose.MOD_PURPOSE_OTHER; // Purpose of the module.
icon = '';
noFilter = false;
modNameTranslated = '';
isLocalUrl = true;
linkIconWithComponent = false;
protected legacyIcon = true; // @deprecatedonmoodle since 3.11.
@HostBinding('class') iconVersion = 'legacy';
constructor(protected el: ElementRef) { }
@ -54,22 +62,35 @@ export class CoreModIconComponent implements OnInit, OnChanges {
* @inheritdoc
*/
async ngOnInit(): Promise<void> {
this.iconVersion = this.getIconVersion();
if (!this.modname && this.modicon) {
// Guess module from the icon url.
this.modname = this.getComponentNameFromIconUrl(this.modicon);
}
this.modNameTranslated = CoreCourse.translateModuleName(this.modname, this.fallbackTranslation);
if (CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('4.0')) {
this.legacyIcon = false;
if (this.iconVersion !== IconVersion.LEGACY_VERSION) {
const purposeClass =
let purposeClass =
CoreCourseModuleDelegate.supportsFeature<ModPurpose>(
this.modname || '',
CoreConstants.FEATURE_MOD_PURPOSE,
this.purpose,
);
if (this.iconVersion === IconVersion.VERSION_4_0) {
if (purposeClass === ModPurpose.MOD_PURPOSE_INTERACTIVECONTENT) {
// Interactive content was introduced on 4.4, on previous versions CONTENT is used instead.
purposeClass = ModPurpose.MOD_PURPOSE_CONTENT;
}
if (this.modname === 'lti') {
// LTI had content purpose with 4.0 icons.
purposeClass = ModPurpose.MOD_PURPOSE_CONTENT;
}
}
if (purposeClass) {
const element: HTMLElement = this.el.nativeElement;
element.classList.add(purposeClass);
@ -129,13 +150,13 @@ export class CoreModIconComponent implements OnInit, OnChanges {
*/
protected async getIconNoFilter(): Promise<boolean> {
// Earlier 4.0, icons were never filtered.
if (this.legacyIcon) {
if (this.iconVersion === IconVersion.LEGACY_VERSION) {
return true;
}
// No icon or local icon (not legacy), filter it.
if (!this.icon || this.isLocalUrl) {
return false;
return await CoreCourseModuleDelegate.moduleIconIsBranded(this.modname);
}
this.icon = CoreTextUtils.decodeHTMLEntities(this.icon);
@ -192,4 +213,23 @@ export class CoreModIconComponent implements OnInit, OnChanges {
return component;
}
/**
* Get the icon version depending on site version.
*
* @returns Icon version.
*/
protected getIconVersion(): IconVersion {
if (!CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('4.0')) {
// @deprecatedonmoodle since 3.11.
return IconVersion.LEGACY_VERSION;
}
if (!CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('4.4')) {
// @deprecatedonmoodle since 4.3.
return IconVersion.VERSION_4_0;
}
return IconVersion.CURRENT_VERSION;
}
}

View File

@ -41,7 +41,8 @@ export const enum ModPurpose {
MOD_PURPOSE_COLLABORATION = 'collaboration',
MOD_PURPOSE_CONTENT = 'content',
MOD_PURPOSE_ADMINISTRATION = 'administration',
MOD_PURPOSE_INTERFACE = 'interface',
MOD_PURPOSE_INTERFACE = 'interface', // @deprecatedonmoodle since 4.4.
MOD_PURPOSE_INTERACTIVECONTENT = 'interactivecontent',
MOD_PURPOSE_OTHER = 'other',
}

View File

@ -1,5 +1,5 @@
<ion-item class="ion-text-wrap" collapsible>
<core-mod-icon slot="start" [modicon]="modicon" [modname]="module.modname" [componentId]="module.instance" />
<core-mod-icon slot="start" [modicon]="modicon" [modname]="module.modname" [componentId]="module.instance" [purpose]="module.purpose" />
<ion-label>
<h1>
<core-format-text [text]="module.name" contextLevel="module" [component]="component" [componentId]="componentId"

View File

@ -15,7 +15,7 @@
<ion-label>
<p *ngIf="moduleNameTranslated" class="core-modulename">
<core-mod-icon slot="start" [modicon]="modicon" [modname]="module.modname" [componentId]="module.instance"
[fallbackTranslation]="module.modplural" />
[fallbackTranslation]="module.modplural" [purpose]="module.purpose" />
{{moduleNameTranslated}}
</p>
<h1>

View File

@ -8,7 +8,7 @@
<ion-label>
<div class="activity-main">
<core-mod-icon *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon" [modname]="module.modname"
[componentId]="module.instance" [fallbackTranslation]="module.modplural" />
[componentId]="module.instance" [fallbackTranslation]="module.modplural" [purpose]="module.purpose" />
<div class="activity-title">
<p class="item-heading">
<core-format-text [text]="module.handlerData.title" contextLevel="module" [contextInstanceId]="module.id"

View File

@ -1750,6 +1750,7 @@ export type CoreCourseGetContentsWSModule = {
visibleoncoursepage: number; // Is the module visible on course page. Cannot be undefined.
modicon: string; // Activity icon url.
modname: string; // Activity module type.
purpose?: string; // @since 4.4 The module purpose.
modplural: string; // Activity module plural name.
availability?: string; // Module availability settings.
indent: number; // Number of identation in the site.

View File

@ -123,6 +123,14 @@ export interface CoreCourseModuleHandler extends CoreDelegateHandler {
* @returns Promise resolved when done.
*/
openActivityPage(module: CoreCourseModuleData, courseId: number, options?: CoreNavigationOptions): Promise<void>;
/**
* Whether the activity is branded.
* This information is used, for instance, to decide if a filter should be applied to the icon or not.
*
* @returns bool True if the activity is branded, false otherwise.
*/
isBranded?(): Promise<boolean>;
}
/**

View File

@ -115,8 +115,8 @@ $core-user-hide-siteinfo: $core-more-hide-siteinfo !default;
$core-user-hide-sitename: $core-more-hide-sitename !default;
$core-user-hide-siteurl: $core-more-hide-siteurl !default;
// Activity icon background colors.
$activity-icon-colors: (
// Activity icon background colors. Only used on 4.0-4.3. @deprecatedonmoodle since 4.4.
$activity-icon-background-colors: (
administration: #5d63f6,
assessment: #eb66a2,
collaboration: #f7634d,
@ -125,6 +125,25 @@ $activity-icon-colors: (
interface: #a378ff
) !default;
// Moodle 4.4 onwards.
$activity-icon-colors: (
administration: #da58ef,
assessment: #f90086,
collaboration: #5b40ff,
communication: #eb6200,
content: #0099ad,
interactivecontent: #8d3d1b
) !default;
$activity-icon-color-filters: (
administration: invert(45%) sepia(46%) saturate(3819%) hue-rotate(260deg) brightness(101%) contrast(87%),
assessment: invert(36%) sepia(98%) saturate(6969%) hue-rotate(315deg) brightness(90%) contrast(119%),
collaboration: invert(25%) sepia(54%) saturate(6226%) hue-rotate(245deg) brightness(100%) contrast(102%),
communication: invert(48%) sepia(74%) saturate(4887%) hue-rotate(11deg) brightness(102%) contrast(101%),
content: invert(49%) sepia(52%) saturate(4675%) hue-rotate(156deg) brightness(89%) contrast(102%),
interactivecontent: invert(25%) sepia(63%) saturate(1152%) hue-rotate(344deg) brightness(94%) contrast(91%)
) !default;
$calendar-event-category-category: #8e24aa !default;
$calendar-event-category-course: $red !default;
$calendar-event-category-group: $yellow !default;

View File

@ -404,10 +404,15 @@ html {
}
// Make activtity colours available for custom modules.
@each $type, $value in $activity-icon-colors {
@each $type, $value in $activity-icon-color-filters {
--activity#{$type}: #{$value};
}
// Make activtity colours available for custom modules.
@each $type, $value in $activity-icon-background-colors {
--activity-40-#{$type}: #{$value};
}
--core-mainpage-sitename-display: none;
--core-mainpage-headerlogo-display: none;
--core-mainpage-headerlogo-maxheight: calc(var(--core-header-toolbar-height) - 16px);