Merge pull request #4070 from NoelDeMartin/MOBILE-4470

MOBILE-4470 core: Refactor mod-icon to use signals
main
Dani Palou 2024-05-28 13:45:46 +02:00 committed by GitHub
commit 1d135e62d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 55 additions and 44 deletions

View File

@ -1,6 +1,6 @@
<ng-container *ngIf="loaded && !svgIcon"> <ng-container *ngIf="loaded() && !svgIcon()">
<img *ngIf="!isLocalUrl" [url]="iconUrl" core-external-content alt="" [component]="linkIconWithComponent ? modname : null" <img *ngIf="!isLocalUrl()" [url]="iconUrl()" core-external-content alt="" [component]="linkIconWithComponent() ? modname : null"
[componentId]="linkIconWithComponent ? componentId : null" (error)="loadFallbackIcon()"> [componentId]="linkIconWithComponent() ? componentId : null" (error)="loadFallbackIcon()">
<img *ngIf="isLocalUrl" [src]="iconUrl" (error)="loadFallbackIcon()" alt=""> <img *ngIf="isLocalUrl()" [src]="iconUrl()" (error)="loadFallbackIcon()" alt="">
</ng-container> </ng-container>
<div *ngIf="svgIcon" [innerHTML]="svgIcon"></div> <div *ngIf="svgIcon()" [innerHTML]="svgIcon()"></div>

View File

@ -13,7 +13,17 @@
// limitations under the License. // limitations under the License.
import { CoreConstants, ModPurpose } from '@/core/constants'; import { CoreConstants, ModPurpose } from '@/core/constants';
import { Component, ElementRef, HostBinding, Input, OnChanges, OnInit, SimpleChange } from '@angular/core'; import {
ChangeDetectionStrategy,
Component,
ElementRef,
HostBinding,
Input,
OnChanges,
OnInit,
SimpleChange,
signal,
} from '@angular/core';
import { SafeHtml } from '@angular/platform-browser'; import { SafeHtml } from '@angular/platform-browser';
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';
@ -42,6 +52,7 @@ const enum IconVersion {
selector: 'core-mod-icon', selector: 'core-mod-icon',
templateUrl: 'mod-icon.html', templateUrl: 'mod-icon.html',
styleUrls: ['mod-icon.scss'], styleUrls: ['mod-icon.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class CoreModIconComponent implements OnInit, OnChanges { export class CoreModIconComponent implements OnInit, OnChanges {
@ -63,16 +74,15 @@ export class CoreModIconComponent implements OnInit, OnChanges {
@HostBinding('attr.aria-label') @HostBinding('attr.aria-label')
get getAriaLabel(): string { get getAriaLabel(): string {
return this.showAlt ? this.modNameTranslated : ''; return this.showAlt ? this.modNameTranslated() : '';
} }
iconUrl = ''; iconUrl = signal('');
modNameTranslated = signal('');
modNameTranslated = ''; isLocalUrl = signal(false);
isLocalUrl = false; svgIcon = signal<SafeHtml>('');
svgIcon: SafeHtml = ''; linkIconWithComponent = signal(false);
linkIconWithComponent = false; loaded = signal(false);
loaded = false;
protected iconVersion: IconVersion = IconVersion.LEGACY_VERSION; protected iconVersion: IconVersion = IconVersion.LEGACY_VERSION;
protected purposeClass = ''; protected purposeClass = '';
@ -94,7 +104,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
this.modname = this.getComponentNameFromIconUrl(this.modicon); this.modname = this.getComponentNameFromIconUrl(this.modicon);
} }
this.modNameTranslated = CoreCourse.translateModuleName(this.modname, this.fallbackTranslation); this.modNameTranslated.set(CoreCourse.translateModuleName(this.modname, this.fallbackTranslation));
this.setPurposeClass(); this.setPurposeClass();
@ -133,7 +143,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
this.brandedClass = this.isBranded; this.brandedClass = this.isBranded;
// No icon or local icon (not legacy), colorize it. // No icon or local icon (not legacy), colorize it.
if (!this.iconUrl || this.isLocalUrl) { if (!this.iconUrl() || this.isLocalUrl()) {
// Exception for bigbluebuttonbn, it's the only one that has a branded icon. // Exception for bigbluebuttonbn, it's the only one that has a branded icon.
if (this.iconVersion === IconVersion.VERSION_4_0 && this.modname === 'bigbluebuttonbn') { if (this.iconVersion === IconVersion.VERSION_4_0 && this.modname === 'bigbluebuttonbn') {
this.brandedClass = true; this.brandedClass = true;
@ -146,11 +156,11 @@ export class CoreModIconComponent implements OnInit, OnChanges {
return; return;
} }
this.iconUrl = CoreTextUtils.decodeHTMLEntities(this.iconUrl); this.iconUrl.update(value => CoreTextUtils.decodeHTMLEntities(value));
// If it's an Moodle Theme icon, check if filtericon is set and use it. // If it's an Moodle Theme icon, check if filtericon is set and use it.
if (this.iconUrl && CoreUrlUtils.isThemeImageUrl(this.iconUrl)) { if (CoreUrlUtils.isThemeImageUrl(this.iconUrl())) {
const filter = CoreUrlUtils.getThemeImageUrlParam(this.iconUrl, 'filtericon'); const filter = CoreUrlUtils.getThemeImageUrlParam(this.iconUrl(), 'filtericon');
if (filter === '1') { if (filter === '1') {
this.brandedClass = false; this.brandedClass = false;
@ -160,7 +170,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
// filtericon was introduced in 4.2 and backported to 4.1.3 and 4.0.8. // 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 (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 version is prior to that, check if the url is a module icon and filter it.
if (this.getComponentNameFromIconUrl(this.iconUrl) === this.modname) { if (this.getComponentNameFromIconUrl(this.iconUrl()) === this.modname) {
this.brandedClass = false; this.brandedClass = false;
return; return;
@ -176,25 +186,26 @@ export class CoreModIconComponent implements OnInit, OnChanges {
* Set icon. * Set icon.
*/ */
async setIcon(): Promise<void> { async setIcon(): Promise<void> {
this.iconUrl = this.modicon || this.iconUrl; this.iconUrl.update(value => this.modicon || value);
if (!this.iconUrl) { if (!this.iconUrl()) {
this.loadFallbackIcon(); this.loadFallbackIcon();
this.setBrandedClass(); this.setBrandedClass();
return; return;
} }
this.isLocalUrl = this.iconUrl.startsWith(assetsPath); this.isLocalUrl.set(this.iconUrl().startsWith(assetsPath));
// Cache icon if the url is not the theme generic one. // Cache icon if the url is not the theme generic one.
// If modname is not set icon won't be cached. // If modname is not set icon won't be cached.
// Also if the url matches the regexp (the theme will manage the image so it's not cached). // Also if the url matches the regexp (the theme will manage the image so it's not cached).
this.linkIconWithComponent = this.linkIconWithComponent.set(
!!this.modname && !!this.modname &&
!!this.componentId && !!this.componentId &&
!this.isLocalUrl && !this.isLocalUrl() &&
this.getComponentNameFromIconUrl(this.iconUrl) != this.modname; this.getComponentNameFromIconUrl(this.iconUrl()) != this.modname,
);
this.setBrandedClass(); this.setBrandedClass();
@ -205,12 +216,12 @@ export class CoreModIconComponent implements OnInit, OnChanges {
* Icon to load on error. * Icon to load on error.
*/ */
async loadFallbackIcon(): Promise<void> { async loadFallbackIcon(): Promise<void> {
if (this.isLocalUrl) { if (this.isLocalUrl()) {
return; return;
} }
this.isLocalUrl = true; this.isLocalUrl.set(true);
this.linkIconWithComponent = false; this.linkIconWithComponent.set(false);
const moduleName = !this.modname || !CoreCourse.isCoreModule(this.modname) const moduleName = !this.modname || !CoreCourse.isCoreModule(this.modname)
? fallbackModName ? fallbackModName
@ -218,7 +229,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
const path = CoreCourse.getModuleIconsPath(); const path = CoreCourse.getModuleIconsPath();
this.iconUrl = path + moduleName + '.svg'; this.iconUrl.set(path + moduleName + '.svg');
await this.setSVGIcon(); await this.setSVGIcon();
} }
@ -302,24 +313,24 @@ export class CoreModIconComponent implements OnInit, OnChanges {
*/ */
protected async setSVGIcon(): Promise<void> { protected async setSVGIcon(): Promise<void> {
if (this.iconVersion === IconVersion.LEGACY_VERSION) { if (this.iconVersion === IconVersion.LEGACY_VERSION) {
this.loaded = true; this.loaded.set(true);
this.svgIcon = ''; this.svgIcon.set('');
return; return;
} }
this.loaded = false; this.loaded.set(false);
let mimetype = ''; let mimetype = '';
let fileContents = ''; let fileContents = '';
// Download the icon if it's not local to cache it. // Download the icon if it's not local to cache it.
if (!this.isLocalUrl) { if (!this.isLocalUrl()) {
try { try {
const iconUrl = await CoreFileHelper.downloadFile( const iconUrl = await CoreFileHelper.downloadFile(
this.iconUrl, this.iconUrl(),
this.linkIconWithComponent ? this.modname : undefined, this.linkIconWithComponent() ? this.modname : undefined,
this.linkIconWithComponent ? this.componentId : undefined, this.linkIconWithComponent() ? this.componentId : undefined,
); );
if (iconUrl) { if (iconUrl) {
mimetype = await CoreUtils.getMimeTypeFromUrl(iconUrl); mimetype = await CoreUtils.getMimeTypeFromUrl(iconUrl);
@ -335,7 +346,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
if (!fileContents) { if (!fileContents) {
// Try to download the icon directly (also for local files). // Try to download the icon directly (also for local files).
const response = await firstValueFrom(Http.get( const response = await firstValueFrom(Http.get(
this.iconUrl, this.iconUrl(),
{ {
observe: 'response', observe: 'response',
responseType: 'text', responseType: 'text',
@ -346,7 +357,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
} }
if (mimetype !== 'image/svg+xml' || !fileContents) { if (mimetype !== 'image/svg+xml' || !fileContents) {
this.svgIcon = ''; this.svgIcon.set('');
return; return;
} }
@ -357,7 +368,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
// Safety check. // Safety check.
if (doc.documentElement.nodeName !== 'svg') { if (doc.documentElement.nodeName !== 'svg') {
this.svgIcon = ''; this.svgIcon.set('');
return; return;
} }
@ -424,11 +435,11 @@ export class CoreModIconComponent implements OnInit, OnChanges {
}); });
}); });
this.svgIcon = DomSanitizer.bypassSecurityTrustHtml(doc.documentElement.outerHTML); this.svgIcon.set(DomSanitizer.bypassSecurityTrustHtml(doc.documentElement.outerHTML));
} catch { } catch {
this.svgIcon = ''; this.svgIcon.set('');
} finally { } finally {
this.loaded = true; this.loaded.set(true);
} }
} }