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_GRADE_OUTCOMES]: true,
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true, [CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
[CoreConstants.FEATURE_SHOW_DESCRIPTION]: 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_HAS_GRADE]: true,
[CoreConstants.FEATURE_GRADE_OUTCOMES]: true, [CoreConstants.FEATURE_GRADE_OUTCOMES]: true,
[CoreConstants.FEATURE_BACKUP_MOODLE2]: 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_GRADE_OUTCOMES]: false,
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true, [CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
[CoreConstants.FEATURE_SHOW_DESCRIPTION]: 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_GRADE_OUTCOMES]: true,
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true, [CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
[CoreConstants.FEATURE_SHOW_DESCRIPTION]: 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_GRADE_OUTCOMES]: true,
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true, [CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
[CoreConstants.FEATURE_SHOW_DESCRIPTION]: 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_GRADE_OUTCOMES]: true,
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true, [CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
[CoreConstants.FEATURE_SHOW_DESCRIPTION]: 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. // Compare with this version.
return siteVersion >= this.getVersionNumber(versions); return siteVersion >= this.getVersionNumber(versions);
} }

View File

@ -1,5 +1,7 @@
<img *ngIf="!isLocalUrl" [src]="icon" [alt]="showAlt ? modNameTranslated : ''" [attr.role]="!showAlt ? 'presentation' : null" <img *ngIf="!isLocalUrl && !iconSVG" [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" class="core-module-icon" core-external-content [component]="linkIconWithComponent ? modname : null"
[componentId]="linkIconWithComponent ? componentId : null" (error)="loadFallbackIcon()"> [componentId]="linkIconWithComponent ? componentId : null" (error)="loadFallbackIcon()">
<img *ngIf="isLocalUrl" [src]="icon" [alt]="showAlt ? modNameTranslated : ''" [attr.role]="!showAlt ? 'presentation' : null" <img *ngIf="isLocalUrl && !iconSVG" [src]="icon" [alt]="showAlt ? modNameTranslated : ''" [attr.role]="!showAlt ? 'presentation' : null"
class="core-module-icon" [class.no-filter]="noFilter" (error)="loadFallbackIcon()"> 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 { :host {
display: inline-block; display: inline-block;
--size: var(--module-icon-size, 24px); --size: var(--module-icon-size, 32px);
--padding: var(--module-icon-padding, 8px); --padding: var(--module-icon-padding, 8px);
--icon-radius: var(--module-icon-radius, var(--radius-xs)); --icon-radius: var(--module-icon-radius, var(--radius-xs));
--margin-end: 0px; --margin-end: 0px;
@ -14,16 +14,43 @@
border-radius: var(--icon-radius); border-radius: var(--icon-radius);
padding: var(--padding); padding: var(--padding);
background-color: $gray-100; background-color: transparent;
line-height: var(--size); line-height: var(--size);
@each $type, $value in $activity-icon-colors { &.version_current {
@each $type, $value in $activity-icon-color-filters {
&.#{$type} { &.#{$type} {
background-color: var(--activity#{$type}); --filter: var(--activity#{$type});
}
}
img { img {
filter: var(--filter, brightness(0) invert(1)); 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; white-space: nowrap;
overflow: hidden; overflow: hidden;
} }
&.no-filter {
--filter: none;
}
} }
:host-context(ion-item) { :host-context(ion-item) {

View File

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

View File

@ -1,5 +1,5 @@
<ion-item class="ion-text-wrap" collapsible> <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> <ion-label>
<h1> <h1>
<core-format-text [text]="module.name" contextLevel="module" [component]="component" [componentId]="componentId" <core-format-text [text]="module.name" contextLevel="module" [component]="component" [componentId]="componentId"

View File

@ -15,7 +15,7 @@
<ion-label> <ion-label>
<p *ngIf="moduleNameTranslated" class="core-modulename"> <p *ngIf="moduleNameTranslated" class="core-modulename">
<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"
[fallbackTranslation]="module.modplural" /> [fallbackTranslation]="module.modplural" [purpose]="module.purpose" />
{{moduleNameTranslated}} {{moduleNameTranslated}}
</p> </p>
<h1> <h1>

View File

@ -8,7 +8,7 @@
<ion-label> <ion-label>
<div class="activity-main"> <div class="activity-main">
<core-mod-icon *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon" [modname]="module.modname" <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"> <div class="activity-title">
<p class="item-heading"> <p class="item-heading">
<core-format-text [text]="module.handlerData.title" contextLevel="module" [contextInstanceId]="module.id" <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. visibleoncoursepage: number; // Is the module visible on course page. Cannot be undefined.
modicon: string; // Activity icon url. modicon: string; // Activity icon url.
modname: string; // Activity module type. modname: string; // Activity module type.
purpose?: string; // @since 4.4 The module purpose.
modplural: string; // Activity module plural name. modplural: string; // Activity module plural name.
availability?: string; // Module availability settings. availability?: string; // Module availability settings.
indent: number; // Number of identation in the site. indent: number; // Number of identation in the site.

View File

@ -123,6 +123,14 @@ export interface CoreCourseModuleHandler extends CoreDelegateHandler {
* @returns Promise resolved when done. * @returns Promise resolved when done.
*/ */
openActivityPage(module: CoreCourseModuleData, courseId: number, options?: CoreNavigationOptions): Promise<void>; 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-sitename: $core-more-hide-sitename !default;
$core-user-hide-siteurl: $core-more-hide-siteurl !default; $core-user-hide-siteurl: $core-more-hide-siteurl !default;
// Activity icon background colors. // Activity icon background colors. Only used on 4.0-4.3. @deprecatedonmoodle since 4.4.
$activity-icon-colors: ( $activity-icon-background-colors: (
administration: #5d63f6, administration: #5d63f6,
assessment: #eb66a2, assessment: #eb66a2,
collaboration: #f7634d, collaboration: #f7634d,
@ -125,6 +125,25 @@ $activity-icon-colors: (
interface: #a378ff interface: #a378ff
) !default; ) !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-category: #8e24aa !default;
$calendar-event-category-course: $red !default; $calendar-event-category-course: $red !default;
$calendar-event-category-group: $yellow !default; $calendar-event-category-group: $yellow !default;

View File

@ -404,10 +404,15 @@ html {
} }
// Make activtity colours available for custom modules. // 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}; --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-sitename-display: none;
--core-mainpage-headerlogo-display: none; --core-mainpage-headerlogo-display: none;
--core-mainpage-headerlogo-maxheight: calc(var(--core-header-toolbar-height) - 16px); --core-mainpage-headerlogo-maxheight: calc(var(--core-header-toolbar-height) - 16px);