+
diff --git a/src/assets/img/mod/bigbluebuttonbn.svg b/src/assets/img/mod/bigbluebuttonbn.svg
index e5e0e91a6..13cf8581b 100644
--- a/src/assets/img/mod/bigbluebuttonbn.svg
+++ b/src/assets/img/mod/bigbluebuttonbn.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/book.svg b/src/assets/img/mod/book.svg
index 3c43cad61..e99a62f47 100644
--- a/src/assets/img/mod/book.svg
+++ b/src/assets/img/mod/book.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/chat.svg b/src/assets/img/mod/chat.svg
index f6ec94d25..54087a7b9 100644
--- a/src/assets/img/mod/chat.svg
+++ b/src/assets/img/mod/chat.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/choice.svg b/src/assets/img/mod/choice.svg
index e81b8f5c5..3506f7a46 100644
--- a/src/assets/img/mod/choice.svg
+++ b/src/assets/img/mod/choice.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/data.svg b/src/assets/img/mod/data.svg
index 66f3a996b..677bdfef0 100644
--- a/src/assets/img/mod/data.svg
+++ b/src/assets/img/mod/data.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/external-tool.svg b/src/assets/img/mod/external-tool.svg
index ff362e431..8a0148259 100644
--- a/src/assets/img/mod/external-tool.svg
+++ b/src/assets/img/mod/external-tool.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/feedback.svg b/src/assets/img/mod/feedback.svg
index 7fa5f02c5..0d212e239 100644
--- a/src/assets/img/mod/feedback.svg
+++ b/src/assets/img/mod/feedback.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/folder.svg b/src/assets/img/mod/folder.svg
index 9ad8f814f..a77d266bd 100644
--- a/src/assets/img/mod/folder.svg
+++ b/src/assets/img/mod/folder.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/forum.svg b/src/assets/img/mod/forum.svg
index c1dbbb7ee..e40826c65 100644
--- a/src/assets/img/mod/forum.svg
+++ b/src/assets/img/mod/forum.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/glossary.svg b/src/assets/img/mod/glossary.svg
index e77fa85ad..543d9e9bb 100644
--- a/src/assets/img/mod/glossary.svg
+++ b/src/assets/img/mod/glossary.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/h5pactivity.svg b/src/assets/img/mod/h5pactivity.svg
index 0dddac960..efdbf3376 100644
--- a/src/assets/img/mod/h5pactivity.svg
+++ b/src/assets/img/mod/h5pactivity.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/imscp.svg b/src/assets/img/mod/imscp.svg
index 87f31fd32..148b2a22d 100644
--- a/src/assets/img/mod/imscp.svg
+++ b/src/assets/img/mod/imscp.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/label.svg b/src/assets/img/mod/label.svg
index 49db2f821..90954192c 100644
--- a/src/assets/img/mod/label.svg
+++ b/src/assets/img/mod/label.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/lesson.svg b/src/assets/img/mod/lesson.svg
index 12d1824e7..301d7efe3 100644
--- a/src/assets/img/mod/lesson.svg
+++ b/src/assets/img/mod/lesson.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/lti.svg b/src/assets/img/mod/lti.svg
index ff362e431..8a0148259 100644
--- a/src/assets/img/mod/lti.svg
+++ b/src/assets/img/mod/lti.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/page.svg b/src/assets/img/mod/page.svg
index 0a202805e..742fc783d 100644
--- a/src/assets/img/mod/page.svg
+++ b/src/assets/img/mod/page.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/quiz.svg b/src/assets/img/mod/quiz.svg
index 04aaadfc8..3cf45e83f 100644
--- a/src/assets/img/mod/quiz.svg
+++ b/src/assets/img/mod/quiz.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/resource.svg b/src/assets/img/mod/resource.svg
index e123e7470..a67145fe6 100644
--- a/src/assets/img/mod/resource.svg
+++ b/src/assets/img/mod/resource.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/scorm.svg b/src/assets/img/mod/scorm.svg
index 95d97e0dd..517cc23ef 100644
--- a/src/assets/img/mod/scorm.svg
+++ b/src/assets/img/mod/scorm.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/survey.svg b/src/assets/img/mod/survey.svg
index a99aa548f..0ea8bf840 100644
--- a/src/assets/img/mod/survey.svg
+++ b/src/assets/img/mod/survey.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/url.svg b/src/assets/img/mod/url.svg
index fe096966e..6fec97bfd 100644
--- a/src/assets/img/mod/url.svg
+++ b/src/assets/img/mod/url.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/wiki.svg b/src/assets/img/mod/wiki.svg
index 9c4ff6281..27c277461 100644
--- a/src/assets/img/mod/wiki.svg
+++ b/src/assets/img/mod/wiki.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod/workshop.svg b/src/assets/img/mod/workshop.svg
index ed7652eb3..5c3c86745 100644
--- a/src/assets/img/mod/workshop.svg
+++ b/src/assets/img/mod/workshop.svg
@@ -1 +1 @@
-
+
diff --git a/src/assets/img/mod_40/assign.svg b/src/assets/img/mod_40/assign.svg
new file mode 100644
index 000000000..7c70fce84
--- /dev/null
+++ b/src/assets/img/mod_40/assign.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/bigbluebuttonbn.svg b/src/assets/img/mod_40/bigbluebuttonbn.svg
new file mode 100644
index 000000000..41db39941
--- /dev/null
+++ b/src/assets/img/mod_40/bigbluebuttonbn.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/book.svg b/src/assets/img/mod_40/book.svg
new file mode 100644
index 000000000..8c60b140f
--- /dev/null
+++ b/src/assets/img/mod_40/book.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/chat.svg b/src/assets/img/mod_40/chat.svg
new file mode 100644
index 000000000..cd4657b6c
--- /dev/null
+++ b/src/assets/img/mod_40/chat.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/choice.svg b/src/assets/img/mod_40/choice.svg
new file mode 100644
index 000000000..005b62d13
--- /dev/null
+++ b/src/assets/img/mod_40/choice.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/data.svg b/src/assets/img/mod_40/data.svg
new file mode 100644
index 000000000..b3b0d1f4d
--- /dev/null
+++ b/src/assets/img/mod_40/data.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/external-tool.svg b/src/assets/img/mod_40/external-tool.svg
new file mode 100644
index 000000000..18e5751d1
--- /dev/null
+++ b/src/assets/img/mod_40/external-tool.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/feedback.svg b/src/assets/img/mod_40/feedback.svg
new file mode 100644
index 000000000..0a44345fe
--- /dev/null
+++ b/src/assets/img/mod_40/feedback.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/folder.svg b/src/assets/img/mod_40/folder.svg
new file mode 100644
index 000000000..ab0942c5e
--- /dev/null
+++ b/src/assets/img/mod_40/folder.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/forum.svg b/src/assets/img/mod_40/forum.svg
new file mode 100644
index 000000000..7373ed187
--- /dev/null
+++ b/src/assets/img/mod_40/forum.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/glossary.svg b/src/assets/img/mod_40/glossary.svg
new file mode 100644
index 000000000..ef05d9cb7
--- /dev/null
+++ b/src/assets/img/mod_40/glossary.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/h5pactivity.svg b/src/assets/img/mod_40/h5pactivity.svg
new file mode 100644
index 000000000..c6616d073
--- /dev/null
+++ b/src/assets/img/mod_40/h5pactivity.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/imscp.svg b/src/assets/img/mod_40/imscp.svg
new file mode 100644
index 000000000..4bc715bc9
--- /dev/null
+++ b/src/assets/img/mod_40/imscp.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/label.svg b/src/assets/img/mod_40/label.svg
new file mode 100644
index 000000000..fdaf2b314
--- /dev/null
+++ b/src/assets/img/mod_40/label.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/lesson.svg b/src/assets/img/mod_40/lesson.svg
new file mode 100644
index 000000000..3a384db60
--- /dev/null
+++ b/src/assets/img/mod_40/lesson.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/lti.svg b/src/assets/img/mod_40/lti.svg
new file mode 100644
index 000000000..18e5751d1
--- /dev/null
+++ b/src/assets/img/mod_40/lti.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/page.svg b/src/assets/img/mod_40/page.svg
new file mode 100644
index 000000000..1cda47d15
--- /dev/null
+++ b/src/assets/img/mod_40/page.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/quiz.svg b/src/assets/img/mod_40/quiz.svg
new file mode 100644
index 000000000..ac4220539
--- /dev/null
+++ b/src/assets/img/mod_40/quiz.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/resource.svg b/src/assets/img/mod_40/resource.svg
new file mode 100644
index 000000000..eeb98b598
--- /dev/null
+++ b/src/assets/img/mod_40/resource.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/scorm.svg b/src/assets/img/mod_40/scorm.svg
new file mode 100644
index 000000000..4a98b13a7
--- /dev/null
+++ b/src/assets/img/mod_40/scorm.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/survey.svg b/src/assets/img/mod_40/survey.svg
new file mode 100644
index 000000000..a2eeb5b5f
--- /dev/null
+++ b/src/assets/img/mod_40/survey.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/url.svg b/src/assets/img/mod_40/url.svg
new file mode 100644
index 000000000..c029667d0
--- /dev/null
+++ b/src/assets/img/mod_40/url.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/wiki.svg b/src/assets/img/mod_40/wiki.svg
new file mode 100644
index 000000000..ff2435cb2
--- /dev/null
+++ b/src/assets/img/mod_40/wiki.svg
@@ -0,0 +1 @@
+
diff --git a/src/assets/img/mod_40/workshop.svg b/src/assets/img/mod_40/workshop.svg
new file mode 100644
index 000000000..25a60d834
--- /dev/null
+++ b/src/assets/img/mod_40/workshop.svg
@@ -0,0 +1 @@
+
diff --git a/src/core/classes/sites/authenticated-site.ts b/src/core/classes/sites/authenticated-site.ts
index d013a5a80..ed6f6b43d 100644
--- a/src/core/classes/sites/authenticated-site.ts
+++ b/src/core/classes/sites/authenticated-site.ts
@@ -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);
}
diff --git a/src/core/components/mod-icon/mod-icon.html b/src/core/components/mod-icon/mod-icon.html
index 46777347a..b3342d2dc 100644
--- a/src/core/components/mod-icon/mod-icon.html
+++ b/src/core/components/mod-icon/mod-icon.html
@@ -1,5 +1,5 @@
-
-
+
+
+
+
diff --git a/src/core/components/mod-icon/mod-icon.scss b/src/core/components/mod-icon/mod-icon.scss
index 37913a25e..8fe00d913 100644
--- a/src/core/components/mod-icon/mod-icon.scss
+++ b/src/core/components/mod-icon/mod-icon.scss
@@ -2,50 +2,77 @@
:host {
display: inline-block;
- --size: var(--module-icon-size, 24px);
- --padding: var(--module-icon-padding, 8px);
+ --size: var(--module-icon-size, 32px);
+ --padding-start: var(--module-icon-padding, 4px);
+ --padding-top: var(--module-icon-padding, 4px);
+ --padding-end: var(--module-icon-padding, 4px);
+ --padding-bottom: var(--module-icon-padding, 4px);
--icon-radius: var(--module-icon-radius, var(--radius-xs));
--margin-end: 0px;
--margin-vertical: 0px;
+ min-width: calc(var(--size) + var(--padding-start) + var(--padding-end));
+ min-height: calc(var(--size) + var(--padding-top) + var(--padding-bottom));
+
margin-top: var(--margin-vertical);
margin-bottom: var(--margin-vertical);
@include margin-horizontal(null, var(--margin-end));
border-radius: var(--icon-radius);
- padding: var(--padding);
- background-color: $gray-100;
+ @include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start));
+ background-color: transparent;
line-height: var(--size);
- @each $type, $value in $activity-icon-colors {
- &.#{$type} {
- background-color: var(--activity#{$type});
- img {
- filter: var(--filter, brightness(0) invert(1));
+ --color: var(--text-color);
+
+ &.colorize {
+ &.version_current {
+ @each $type, $value in $activity-icon-colors {
+ &.#{$type} {
+ --color: var(--activity#{$type});
+ }
+ }
+ }
+
+ &.version_40 {
+ background-color: var(--gray-100);
+
+ --color: white;
+
+ @each $type, $value in $activity-icon-background-colors {
+ &.#{$type} {
+ background-color: var(--activity-40-#{$type});
+ }
}
}
}
-}
-img {
- width: var(--size);
- height: var(--size);
- max-width: var(--size);
- max-height: var(--size);
- min-width: var(--size);
- min-height: var(--size);
- vertical-align: top;
-
- &[alt] {
- text-indent: -999999px;
- white-space: nowrap;
- overflow: hidden;
+ &.version_40,
+ &.version_legacy {
+ --size: var(--module-legacy-icon-size, 24px);
+ --padding-start: var(--module-legacy-icon-padding, 8px);
+ --padding-top: var(--module-legacy-icon-padding, 8px);
+ --padding-end: var(--module-legacy-icon-padding, 8px);
+ --padding-bottom: var(--module-legacy-icon-padding, 8px);
}
- &.no-filter {
- --filter: none;
+ &:not(.branded) {
+ ::ng-deep svg,
+ ::ng-deep svg * {
+ fill: var(--color);
+ }
}
+ img,
+ ::ng-deep svg {
+ width: var(--size);
+ height: var(--size);
+ max-width: var(--size);
+ max-height: var(--size);
+ min-width: var(--size);
+ min-height: var(--size);
+ vertical-align: top;
+ }
}
:host-context(ion-item) {
diff --git a/src/core/components/mod-icon/mod-icon.ts b/src/core/components/mod-icon/mod-icon.ts
index d35294ff7..8ce84db68 100644
--- a/src/core/components/mod-icon/mod-icon.ts
+++ b/src/core/components/mod-icon/mod-icon.ts
@@ -13,16 +13,27 @@
// 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 { CoreFile } from '@services/file';
+import { CoreFileHelper } from '@services/file-helper';
import { CoreSites } from '@services/sites';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url';
+import { CoreUtils } from '@services/utils/utils';
+import { Http } from '@singletons';
+import { firstValueFrom } from 'rxjs';
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.
*/
@@ -39,42 +50,50 @@ export class CoreModIconComponent implements OnInit, OnChanges {
@Input() modicon?: string; // Module icon url or local url.
@Input() showAlt = true; // Show alt otherwise it's only presentation icon.
@Input() purpose: ModPurpose = ModPurpose.MOD_PURPOSE_OTHER; // Purpose of the module.
+ @Input() @HostBinding('class.colorize') colorize = true; // Colorize the icon. Only applies on 4.0 onwards.
+ @Input() @HostBinding('class.branded') isBranded?: boolean; // If icon is branded and no colorize will be applied.
+
+ @HostBinding('attr.role')
+ get getRole(): string | null {
+ return !this.showAlt ? 'presentation' : null;
+ }
+
+ @HostBinding('attr.aria-label')
+ get getAriaLabel(): string {
+ return this.showAlt ? this.modNameTranslated : '';
+ }
+
+ iconUrl = '';
- icon = '';
- noFilter = false;
modNameTranslated = '';
- isLocalUrl = true;
+ isLocalUrl = false;
linkIconWithComponent = false;
+ loaded = false;
- protected legacyIcon = true; // @deprecatedonmoodle since 3.11.
+ protected iconVersion: IconVersion = IconVersion.LEGACY_VERSION;
+ protected purposeClass = '';
+ protected element: HTMLElement;
- constructor(protected el: ElementRef) { }
+ constructor(element: ElementRef) {
+ this.element = element.nativeElement;
+ }
/**
* @inheritdoc
*/
async ngOnInit(): Promise {
+ this.iconVersion = this.getIconVersion();
+ this.element.classList.add(this.iconVersion);
+
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;
- const purposeClass =
- CoreCourseModuleDelegate.supportsFeature(
- this.modname || '',
- CoreConstants.FEATURE_MOD_PURPOSE,
- this.purpose,
- );
-
- if (purposeClass) {
- const element: HTMLElement = this.el.nativeElement;
- element.classList.add(purposeClass);
- }
- }
+ this.setIsBranded();
+ this.setPurposeClass();
await this.setIcon();
}
@@ -88,12 +107,70 @@ export class CoreModIconComponent implements OnInit, OnChanges {
}
}
+ /**
+ * Sets the isBranded property when undefined.
+ *
+ * @returns wether the icon does not need to be filtered.
+ */
+ protected async setIsBranded(): Promise {
+ if (!this.colorize || this.isBranded !== undefined) {
+ // It doesn't matter.
+ return;
+ }
+
+ // Earlier 4.0, icons were never colorized.
+ if (this.iconVersion === IconVersion.LEGACY_VERSION) {
+ this.colorize = false;
+
+ return;
+ }
+
+ // No icon or local icon (not legacy), colorize it.
+ if (!this.iconUrl || this.isLocalUrl) {
+ this.isBranded = false;
+
+ return;
+ }
+
+ this.iconUrl = CoreTextUtils.decodeHTMLEntities(this.iconUrl);
+
+ // If it's an Moodle Theme icon, check if filtericon is set and use it.
+ if (this.iconUrl && CoreUrlUtils.isThemeImageUrl(this.iconUrl)) {
+ const iconParams = CoreUrlUtils.extractUrlParams(this.iconUrl);
+ if (iconParams['filtericon'] === '1') {
+ this.isBranded = false;
+
+ return;
+ }
+
+ // filtericon was introduced in 4.2 and backported to 4.1.3 and 4.0.8.
+ if (this.modname && !CoreSites.getCurrentSite()?.isVersionGreaterEqualThan(['4.0.8', '4.1.3', '4.2'])) {
+ // If version is prior to that, check if the url is a module icon and filter it.
+ if (this.getComponentNameFromIconUrl(this.iconUrl) === this.modname) {
+ this.isBranded = false;
+
+ return;
+ }
+ }
+ }
+
+ // External icons, or non monologo, do not filter.
+ this.isBranded = true;
+ }
+
/**
* Set icon.
*/
async setIcon(): Promise {
- this.icon = this.modicon || this.icon;
- this.isLocalUrl = this.icon.startsWith(assetsPath);
+ this.iconUrl = this.modicon || this.iconUrl;
+
+ if (!this.iconUrl) {
+ this.loadFallbackIcon();
+
+ return;
+ }
+
+ this.isLocalUrl = this.iconUrl.startsWith(assetsPath);
// Cache icon if the url is not the theme generic one.
// If modname is not set icon won't be cached.
@@ -102,66 +179,31 @@ export class CoreModIconComponent implements OnInit, OnChanges {
!!this.modname &&
!!this.componentId &&
!this.isLocalUrl &&
- this.getComponentNameFromIconUrl(this.icon) != this.modname;
+ this.getComponentNameFromIconUrl(this.iconUrl) != this.modname;
- this.noFilter = await this.getIconNoFilter();
+ await this.setSVGIcon();
}
/**
* Icon to load on error.
*/
async loadFallbackIcon(): Promise {
+ if (this.isLocalUrl) {
+ return;
+ }
+
this.isLocalUrl = true;
+ this.linkIconWithComponent = false;
+
const moduleName = !this.modname || CoreCourse.CORE_MODULES.indexOf(this.modname) < 0
? fallbackModName
: this.modname;
- let path = assetsPath + 'mod/';
- if (this.legacyIcon) {
- // @deprecatedonmoodle since 3.11.
- path = assetsPath + 'mod_legacy/';
- }
+ const path = CoreCourse.getModuleIconsPath();
- this.icon = path + moduleName + '.svg';
- this.noFilter = await this.getIconNoFilter();
- }
+ this.iconUrl = path + moduleName + '.svg';
- /**
- * Returns if the icon does not need to be filtered.
- *
- * @returns wether the icon does not need to be filtered.
- */
- protected async getIconNoFilter(): Promise {
- // Earlier 4.0, icons were never filtered.
- if (this.legacyIcon) {
- return true;
- }
-
- // No icon or local icon (not legacy), filter it.
- if (!this.icon || this.isLocalUrl) {
- return false;
- }
-
- this.icon = CoreTextUtils.decodeHTMLEntities(this.icon);
-
- // If it's an Moodle Theme icon, check if filtericon is set and use it.
- if (this.icon && CoreUrlUtils.isThemeImageUrl(this.icon)) {
- const iconParams = CoreUrlUtils.extractUrlParams(this.icon);
- if (iconParams['filtericon'] === '1') {
- return false;
- }
-
- // filtericon was introduced in 4.2 and backported to 4.1.3 and 4.0.8.
- if (this.modname && !CoreSites.getCurrentSite()?.isVersionGreaterEqualThan(['4.0.8', '4.1.3', '4.2'])) {
- // If version is prior to that, check if the url is a module icon and filter it.
- if (this.getComponentNameFromIconUrl(this.icon) === this.modname) {
- return false;
- }
- }
- }
-
- // External icons, or non monologo, do not filter.
- return true;
+ await this.setSVGIcon();
}
/**
@@ -171,7 +213,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
* @returns Guessed modname.
*/
protected getComponentNameFromIconUrl(iconUrl: string): string {
- if (!CoreUrlUtils.isThemeImageUrl(this.icon)) {
+ if (!CoreUrlUtils.isThemeImageUrl(this.iconUrl)) {
// Cannot be guessed.
return '';
}
@@ -196,4 +238,154 @@ export class CoreModIconComponent implements OnInit, OnChanges {
return component;
}
+ /**
+ * Set the purpose class.
+ */
+ protected setPurposeClass(): void {
+ if (this.iconVersion === IconVersion.LEGACY_VERSION) {
+ return;
+ }
+
+ this.purposeClass =
+ CoreCourseModuleDelegate.supportsFeature(
+ this.modname || '',
+ CoreConstants.FEATURE_MOD_PURPOSE,
+ this.purpose,
+ );
+
+ if (this.iconVersion === IconVersion.VERSION_4_0) {
+ if (this.purposeClass === ModPurpose.MOD_PURPOSE_INTERACTIVECONTENT) {
+ // Interactive content was introduced on 4.4, on previous versions CONTENT is used instead.
+ this.purposeClass = ModPurpose.MOD_PURPOSE_CONTENT;
+ }
+
+ if (this.modname === 'lti') {
+ // LTI had content purpose with 4.0 icons.
+ this.purposeClass = ModPurpose.MOD_PURPOSE_CONTENT;
+ }
+ }
+
+ if (this.purposeClass) {
+ this.element.classList.add(this.purposeClass);
+ }
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Sets SVG markup for the icon (if the URL is an SVG).
+ *
+ * @returns Promise resolved when done.
+ */
+ protected async setSVGIcon(): Promise {
+ if (this.iconVersion === IconVersion.LEGACY_VERSION) {
+ this.loaded = true;
+
+ return;
+ }
+
+ this.loaded = false;
+
+ let mimetype = '';
+ let fileContents = '';
+
+ // Download the icon if it's not local to cache it.
+ if (!this.isLocalUrl) {
+ try {
+ const iconUrl = await CoreFileHelper.downloadFile(
+ this.iconUrl,
+ this.linkIconWithComponent ? this.modname : undefined,
+ this.linkIconWithComponent ? this.componentId : undefined,
+ );
+ if (iconUrl) {
+ mimetype = await CoreUtils.getMimeTypeFromUrl(iconUrl);
+ fileContents = await CoreFile.readFile(iconUrl);
+ }
+ } catch {
+ // Ignore errors.
+ }
+ }
+
+ try {
+
+ if (!fileContents) {
+ // Try to download the icon directly (also for local files).
+ const response = await firstValueFrom(Http.get(
+ this.iconUrl,
+ {
+ observe: 'response',
+ responseType: 'text',
+ },
+ ));
+ mimetype = response.headers.get('content-type') || mimetype;
+ fileContents = response.body || '';
+ }
+
+ if (mimetype !== 'image/svg+xml' || !fileContents) {
+ return;
+ }
+
+ // Clean the DOM to avoid security issues.
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(fileContents, 'image/svg+xml');
+
+ // Safety check.
+ if (doc.documentElement.nodeName !== 'svg') {
+ return;
+ }
+
+ // Remove scripts tags.
+ const scripts = doc.documentElement.getElementsByTagName('script');
+ for (let i = scripts.length - 1; i >= 0; i--) {
+ scripts[i].parentNode?.removeChild(scripts[i]);
+ }
+
+ // Recursively remove attributes starting with on.
+ const removeAttributes = (element: Element): void => {
+ Array.from(element.attributes).forEach((attr) => {
+ if (attr.name.startsWith('on')) {
+ element.removeAttribute(attr.name);
+ }
+ });
+
+ Array.from(element.children).forEach((child) => {
+ removeAttributes(child);
+ });
+ };
+ removeAttributes(doc.documentElement);
+
+ // Add viewBox to avoid scaling issues.
+ if (!doc.documentElement.getAttribute('viewBox')) {
+ const width = doc.documentElement.getAttribute('width');
+ const height = doc.documentElement.getAttribute('height');
+ if (width && height) {
+ doc.documentElement.setAttribute('viewBox', '0 0 '+ width + ' ' + height);
+ }
+ }
+
+ this.element.replaceChildren(doc.documentElement);
+ } catch {
+ // Ignore errors.
+ } finally {
+ this.loaded = true;
+ }
+ }
+
}
diff --git a/src/core/constants.ts b/src/core/constants.ts
index e3a6ce330..25504259b 100644
--- a/src/core/constants.ts
+++ b/src/core/constants.ts
@@ -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',
}
diff --git a/src/core/features/course/components/module-info/core-course-module-info.html b/src/core/features/course/components/module-info/core-course-module-info.html
index 244e2d406..eb5ff2400 100644
--- a/src/core/features/course/components/module-info/core-course-module-info.html
+++ b/src/core/features/course/components/module-info/core-course-module-info.html
@@ -1,6 +1,7 @@
-
-
+
+
diff --git a/src/core/features/course/components/module-info/course-module-info.scss b/src/core/features/course/components/module-info/course-module-info.scss
index 013704204..246932f5c 100644
--- a/src/core/features/course/components/module-info/course-module-info.scss
+++ b/src/core/features/course/components/module-info/course-module-info.scss
@@ -10,8 +10,16 @@
core-mod-icon {
align-self: flex-start;
- --padding: 4px;
@include margin-horizontal(null, 8px);
+
+ --module-icon-padding: 0px;
+ --module-legacy-icon-padding: 4px;
+ }
+
+ ion-label.core-module-info-activity-title {
+ margin-top: 12px;
+ margin-bottom: 12px;
+ line-height: 32px;
}
h1 ion-icon {
diff --git a/src/core/features/course/components/module-summary/module-summary.html b/src/core/features/course/components/module-summary/module-summary.html
index 9d862340d..f1e8cdc70 100644
--- a/src/core/features/course/components/module-summary/module-summary.html
+++ b/src/core/features/course/components/module-summary/module-summary.html
@@ -15,7 +15,7 @@
+ [fallbackTranslation]="module.modplural" [purpose]="module.purpose" [isBranded]="module.branded" />
{{moduleNameTranslated}}
diff --git a/src/core/features/course/components/module-summary/module-summary.scss b/src/core/features/course/components/module-summary/module-summary.scss
index 3594fb7eb..6b2c74d6b 100644
--- a/src/core/features/course/components/module-summary/module-summary.scss
+++ b/src/core/features/course/components/module-summary/module-summary.scss
@@ -10,16 +10,20 @@ h1 {
.core-modulename {
text-transform: uppercase;
+
core-mod-icon {
- --padding: 3px;
- --size: 10px;
- margin: 0;
+ --module-icon-padding: 0px;
+ --module-legacy-icon-padding: 2px;
+ --module-icon-size: 16px;
+ --module-legacy-icon-size: 12px;
+
+ margin: 0px;
}
}
ion-item ion-label ion-icon {
- @include margin-horizontal(0, 4px);
+ @include margin-horizontal(0px, 4px);
vertical-align: text-top;
}
diff --git a/src/core/features/course/components/module/core-course-module.html b/src/core/features/course/components/module/core-course-module.html
index d4f3da3c6..1a296ac1b 100644
--- a/src/core/features/course/components/module/core-course-module.html
+++ b/src/core/features/course/components/module/core-course-module.html
@@ -8,7 +8,8 @@
+ [componentId]="module.instance" [fallbackTranslation]="module.modplural" [purpose]="module.purpose"
+ [isBranded]="module.branded" />
;
+
+ /**
+ * 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;
}
/**
diff --git a/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_46.png b/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_46.png
index 317db3b95..5e167d91b 100644
Binary files a/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_46.png and b/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_46.png differ
diff --git a/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_50.png b/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_50.png
index ddc6ce75b..c1dc1267f 100644
Binary files a/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_50.png and b/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_50.png differ
diff --git a/src/core/features/grades/pages/course/course.html b/src/core/features/grades/pages/course/course.html
index ea8aa5cbd..1627cddd7 100644
--- a/src/core/features/grades/pages/course/course.html
+++ b/src/core/features/grades/pages/course/course.html
@@ -45,7 +45,7 @@
+ [modname]="row.itemmodule" [colorize]="false" />
diff --git a/src/core/features/grades/pages/course/course.scss b/src/core/features/grades/pages/course/course.scss
index a767571c3..ef6808d2b 100644
--- a/src/core/features/grades/pages/course/course.scss
+++ b/src/core/features/grades/pages/course/course.scss
@@ -43,10 +43,10 @@ table.core-table.core-grades-table {
}
core-mod-icon {
- --padding: 0px;
- --size: 16px;
- background: transparent;
- --filter: var(--module-icon-filter);
+ --module-icon-padding: 0px;
+ --module-legacy-icon-padding: 0px;
+ --module-icon-size: 16px;
+ --module-legacy-icon-size: 16px;
}
ion-icon {
diff --git a/src/core/features/search/components/global-search-result/global-search-result.html b/src/core/features/search/components/global-search-result/global-search-result.html
index 88238eec1..b1cc66ea8 100644
--- a/src/core/features/search/components/global-search-result/global-search-result.html
+++ b/src/core/features/search/components/global-search-result/global-search-result.html
@@ -4,8 +4,8 @@
-
+
diff --git a/src/core/features/search/components/global-search-result/global-search-result.scss b/src/core/features/search/components/global-search-result/global-search-result.scss
index ccfbe4d05..8e25e101c 100644
--- a/src/core/features/search/components/global-search-result/global-search-result.scss
+++ b/src/core/features/search/components/global-search-result/global-search-result.scss
@@ -12,14 +12,14 @@
color: var(--core-global-search-result-title-color);
core-mod-icon {
- --size: var(--core-global-search-result-icon-size);
- --filter: var(--module-icon-filter);
+ --module-icon-padding: 0px;
+ --module-legacy-icon-padding: 0px;
+ --module-icon-size: var(--core-global-search-result-icon-size);
+ --module-legacy-icon-size: var(--core-global-search-result-icon-size);
margin-inline-end: var(--spacing-2);
margin-top: 0px;
margin-bottom: 0px;
- --padding: 0px;
- background: transparent;
flex-shrink: 0;
}
diff --git a/src/core/features/tag/components/feed/core-tag-feed.html b/src/core/features/tag/components/feed/core-tag-feed.html
index b2dbea3c2..a9715910f 100644
--- a/src/core/features/tag/components/feed/core-tag-feed.html
+++ b/src/core/features/tag/components/feed/core-tag-feed.html
@@ -2,7 +2,7 @@
-
+
{{ item.heading }}
{{ text }}
diff --git a/src/core/tests/behat/snapshots/fontawesome-icons-are-correctly-shown-in-the-app-view-fontawesome-icons-in-the-app_7.png b/src/core/tests/behat/snapshots/fontawesome-icons-are-correctly-shown-in-the-app-view-fontawesome-icons-in-the-app_7.png
index cb5a0fbb2..ce2463119 100644
Binary files a/src/core/tests/behat/snapshots/fontawesome-icons-are-correctly-shown-in-the-app-view-fontawesome-icons-in-the-app_7.png and b/src/core/tests/behat/snapshots/fontawesome-icons-are-correctly-shown-in-the-app-view-fontawesome-icons-in-the-app_7.png differ
diff --git a/src/theme/globals.variables.scss b/src/theme/globals.variables.scss
index ba409da53..7641f6701 100644
--- a/src/theme/globals.variables.scss
+++ b/src/theme/globals.variables.scss
@@ -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,16 @@ $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;
+
$calendar-event-category-category: #8e24aa !default;
$calendar-event-category-course: $red !default;
$calendar-event-category-group: $yellow !default;
diff --git a/src/theme/theme.dark.scss b/src/theme/theme.dark.scss
index 559faf1aa..58877fed0 100644
--- a/src/theme/theme.dark.scss
+++ b/src/theme/theme.dark.scss
@@ -130,8 +130,6 @@ html.dark {
--core-login-input-background: var(--core-login-background);
--core-login-input-color: var(--core-login-text-color);
- --module-icon-filter: brightness(0) invert(1);
-
--core-question-correct-color: var(--success-tint);
--core-question-correct-color-bg: var(--success-shade);
--core-question-incorrect-color: var(--danger);
diff --git a/src/theme/theme.light.scss b/src/theme/theme.light.scss
index 79cb58582..34607d160 100644
--- a/src/theme/theme.light.scss
+++ b/src/theme/theme.light.scss
@@ -354,8 +354,6 @@ html {
--core-messages-discussion-badge: var(--primary);
--core-messages-discussion-badge-text: var(--white);
- --module-icon-filter: brightness(0);
-
--addon-forum-avatar-size: var(--core-avatar-size);
--addon-forum-border-color: var(--stroke);
--addon-forum-highlight-color: var(--light);
@@ -408,6 +406,11 @@ html {
--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);