MOBILE-4470 icon: Revert load SVG directly and use img and filters
This commit is contained in:
		
							parent
							
								
									4704b63142
								
							
						
					
					
						commit
						dfbf591e2a
					
				@ -1,6 +1,3 @@
 | 
				
			|||||||
<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>
 | 
					 | 
				
			||||||
<div *ngIf="svgIcon()" [innerHTML]="svgIcon()"></div>
 | 
					 | 
				
			||||||
 | 
				
			|||||||
@ -23,13 +23,13 @@
 | 
				
			|||||||
    background-color: transparent;
 | 
					    background-color: transparent;
 | 
				
			||||||
    line-height: var(--size);
 | 
					    line-height: var(--size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    --color: var(--activity-base-icon-color);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    &.colorize {
 | 
					    &.colorize {
 | 
				
			||||||
        &.version_current {
 | 
					        &.version_current {
 | 
				
			||||||
            @each $type, $value in $activity-icon-colors {
 | 
					            @each $type, $value in $activity-icon-color-filters {
 | 
				
			||||||
                &.#{$type} {
 | 
					                &.#{$type}:not(.branded) {
 | 
				
			||||||
                    --color: var(--activity#{$type});
 | 
					                    img {
 | 
				
			||||||
 | 
					                        filter: var(--activity#{$type});
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -40,7 +40,7 @@
 | 
				
			|||||||
            @each $type, $value in $activity-icon-background-colors {
 | 
					            @each $type, $value in $activity-icon-background-colors {
 | 
				
			||||||
                &.#{$type}:not(.branded) {
 | 
					                &.#{$type}:not(.branded) {
 | 
				
			||||||
                    background-color: var(--activity-40-#{$type});
 | 
					                    background-color: var(--activity-40-#{$type});
 | 
				
			||||||
                    ::ng-deep svg, img {
 | 
					                    img {
 | 
				
			||||||
                        filter: brightness(0) invert(1);
 | 
					                        filter: brightness(0) invert(1);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -57,15 +57,7 @@
 | 
				
			|||||||
        --padding-bottom: var(--module-legacy-icon-padding, 8px);
 | 
					        --padding-bottom: var(--module-legacy-icon-padding, 8px);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    &.colorize.version_current:not(.branded) {
 | 
					    img {
 | 
				
			||||||
        ::ng-deep svg,
 | 
					 | 
				
			||||||
        ::ng-deep svg * {
 | 
					 | 
				
			||||||
            fill: var(--color) !important;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    img,
 | 
					 | 
				
			||||||
    ::ng-deep svg {
 | 
					 | 
				
			||||||
        width: var(--size);
 | 
					        width: var(--size);
 | 
				
			||||||
        height: var(--size);
 | 
					        height: var(--size);
 | 
				
			||||||
        max-width: var(--size);
 | 
					        max-width: var(--size);
 | 
				
			||||||
 | 
				
			|||||||
@ -24,17 +24,11 @@ import {
 | 
				
			|||||||
    SimpleChange,
 | 
					    SimpleChange,
 | 
				
			||||||
    signal,
 | 
					    signal,
 | 
				
			||||||
} from '@angular/core';
 | 
					} from '@angular/core';
 | 
				
			||||||
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';
 | 
				
			||||||
import { CoreFile } from '@services/file';
 | 
					 | 
				
			||||||
import { CoreFileHelper } from '@services/file-helper';
 | 
					 | 
				
			||||||
import { CoreSites } from '@services/sites';
 | 
					import { CoreSites } from '@services/sites';
 | 
				
			||||||
import { CoreTextUtils } from '@services/utils/text';
 | 
					import { CoreTextUtils } from '@services/utils/text';
 | 
				
			||||||
import { CoreUrlUtils } from '@services/utils/url';
 | 
					import { CoreUrlUtils } from '@services/utils/url';
 | 
				
			||||||
import { CoreUtils } from '@services/utils/utils';
 | 
					 | 
				
			||||||
import { DomSanitizer, Http } from '@singletons';
 | 
					 | 
				
			||||||
import { firstValueFrom } from 'rxjs';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const assetsPath = 'assets/img/';
 | 
					const assetsPath = 'assets/img/';
 | 
				
			||||||
const fallbackModName = 'external-tool';
 | 
					const fallbackModName = 'external-tool';
 | 
				
			||||||
@ -80,9 +74,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
 | 
				
			|||||||
    iconUrl = signal('');
 | 
					    iconUrl = signal('');
 | 
				
			||||||
    modNameTranslated = signal('');
 | 
					    modNameTranslated = signal('');
 | 
				
			||||||
    isLocalUrl = signal(false);
 | 
					    isLocalUrl = signal(false);
 | 
				
			||||||
    svgIcon = signal<SafeHtml>('');
 | 
					 | 
				
			||||||
    linkIconWithComponent = signal(false);
 | 
					    linkIconWithComponent = signal(false);
 | 
				
			||||||
    loaded = signal(false);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected iconVersion: IconVersion = IconVersion.LEGACY_VERSION;
 | 
					    protected iconVersion: IconVersion = IconVersion.LEGACY_VERSION;
 | 
				
			||||||
    protected purposeClass = '';
 | 
					    protected purposeClass = '';
 | 
				
			||||||
@ -208,8 +200,6 @@ export class CoreModIconComponent implements OnInit, OnChanges {
 | 
				
			|||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.setBrandedClass();
 | 
					        this.setBrandedClass();
 | 
				
			||||||
 | 
					 | 
				
			||||||
        await this.setSVGIcon();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -230,8 +220,6 @@ export class CoreModIconComponent implements OnInit, OnChanges {
 | 
				
			|||||||
        const path = CoreCourse.getModuleIconsPath();
 | 
					        const path = CoreCourse.getModuleIconsPath();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.iconUrl.set(path + moduleName + '.svg');
 | 
					        this.iconUrl.set(path + moduleName + '.svg');
 | 
				
			||||||
 | 
					 | 
				
			||||||
        await this.setSVGIcon();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -306,141 +294,4 @@ export class CoreModIconComponent implements OnInit, OnChanges {
 | 
				
			|||||||
        return IconVersion.CURRENT_VERSION;
 | 
					        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<void> {
 | 
					 | 
				
			||||||
        if (this.iconVersion === IconVersion.LEGACY_VERSION) {
 | 
					 | 
				
			||||||
            this.loaded.set(true);
 | 
					 | 
				
			||||||
            this.svgIcon.set('');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.loaded.set(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) {
 | 
					 | 
				
			||||||
                this.svgIcon.set('');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                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') {
 | 
					 | 
				
			||||||
                this.svgIcon.set('');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                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]);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Has own styles, do not apply colors.
 | 
					 | 
				
			||||||
            if (doc.documentElement.getElementsByTagName('style').length > 0) {
 | 
					 | 
				
			||||||
                this.brandedClass = true;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // 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);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Prefix id's on svg DOM to avoid conflicts.
 | 
					 | 
				
			||||||
            const uniqueId = 'modicon' + CoreUtils.getUniqueId('modicon') + '_';
 | 
					 | 
				
			||||||
            const styleTags = Array.from(doc.documentElement.getElementsByTagName('style'));
 | 
					 | 
				
			||||||
            const styleAttrs = Array.from(doc.documentElement.querySelectorAll('[style]'));
 | 
					 | 
				
			||||||
            const idTags = Array.from(doc.documentElement.querySelectorAll('[id]'));
 | 
					 | 
				
			||||||
            idTags.forEach((element) => {
 | 
					 | 
				
			||||||
                if (!element.id) {
 | 
					 | 
				
			||||||
                    return;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                const newId = uniqueId + element.id;
 | 
					 | 
				
			||||||
                // Regexp to replace all ocurrences of the id with workd bondaries.
 | 
					 | 
				
			||||||
                const oldIdFinder = new RegExp(`#${element.id}\\b`, 'g');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                element.id = newId;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Prefix the elementId on style Tags.
 | 
					 | 
				
			||||||
                styleTags.forEach((style) => {
 | 
					 | 
				
			||||||
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 | 
					 | 
				
			||||||
                    style.textContent = style.textContent!.replace(oldIdFinder, `#${newId}`);
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Also change ids on style attributes.
 | 
					 | 
				
			||||||
                styleAttrs.forEach((attr) => {
 | 
					 | 
				
			||||||
                    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 | 
					 | 
				
			||||||
                    attr.setAttribute('style', attr.getAttribute('style')!.replace(oldIdFinder, `#${newId}`));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            this.svgIcon.set(DomSanitizer.bypassSecurityTrustHtml(doc.documentElement.outerHTML));
 | 
					 | 
				
			||||||
        } catch {
 | 
					 | 
				
			||||||
            this.svgIcon.set('');
 | 
					 | 
				
			||||||
        } finally {
 | 
					 | 
				
			||||||
            this.loaded.set(true);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -136,6 +136,15 @@ $activity-icon-colors: (
 | 
				
			|||||||
    interactivecontent: #8d3d1b
 | 
					    interactivecontent: #8d3d1b
 | 
				
			||||||
) !default;
 | 
					) !default;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$activity-icon-color-filters: (
 | 
				
			||||||
 | 
					    administration: brightness(0%) invert(45%) sepia(46%) saturate(3819%) hue-rotate(260deg) brightness(101%) contrast(87%),
 | 
				
			||||||
 | 
					    assessment: brightness(0%) invert(36%) sepia(98%) saturate(6969%) hue-rotate(315deg) brightness(90%) contrast(119%),
 | 
				
			||||||
 | 
					    collaboration: brightness(0%) invert(25%) sepia(54%) saturate(6226%) hue-rotate(245deg) brightness(100%) contrast(102%),
 | 
				
			||||||
 | 
					    communication: brightness(0%) invert(48%) sepia(74%) saturate(4887%) hue-rotate(11deg) brightness(102%) contrast(101%),
 | 
				
			||||||
 | 
					    content: brightness(0%) invert(49%) sepia(52%) saturate(4675%) hue-rotate(156deg) brightness(89%) contrast(102%),
 | 
				
			||||||
 | 
					    interactivecontent: brightness(0%) 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;
 | 
				
			||||||
 | 
				
			|||||||
@ -172,5 +172,4 @@ html.dark {
 | 
				
			|||||||
    --core-table-even-cell-background: var(--gray-900);
 | 
					    --core-table-even-cell-background: var(--gray-900);
 | 
				
			||||||
    --core-table-even-cell-hover: var(--gray-700);
 | 
					    --core-table-even-cell-hover: var(--gray-700);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    --activity-base-icon-color: var(--white);
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -201,9 +201,8 @@ html {
 | 
				
			|||||||
        --core-dd-question-color-#{$i + 1}-contrast: #{get_contrast_color(nth($core-dd-question-colors, $i + 1))};
 | 
					        --core-dd-question-color-#{$i + 1}-contrast: #{get_contrast_color(nth($core-dd-question-colors, $i + 1))};
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    --activity-base-icon-color: var(--gray-900);
 | 
					 | 
				
			||||||
    // 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};
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user