MOBILE-4456 course: Load icon svg as inline and style them properly
| @ -1,7 +1,7 @@ | |||||||
| :host { | :host { | ||||||
|     core-mod-icon { |     core-mod-icon { | ||||||
|         background: transparent; |  | ||||||
|         margin: 0; |         margin: 0; | ||||||
|         --filter: var(--module-icon-filter); |         --padding-start: 0px; | ||||||
|  |         --module-icon-size: 24px; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ | |||||||
| </ion-item-divider> | </ion-item-divider> | ||||||
| <core-loading [hideUntil]="loaded"> | <core-loading [hideUntil]="loaded"> | ||||||
|     <ion-item class="ion-text-wrap" *ngFor="let entry of entries" [detail]="true" button (click)="gotoCoureListModType(entry)"> |     <ion-item class="ion-text-wrap" *ngFor="let entry of entries" [detail]="true" button (click)="gotoCoureListModType(entry)"> | ||||||
|         <core-mod-icon slot="start" [modicon]="entry.icon" [modname]="entry.iconModName" [showAlt]="false" /> |         <core-mod-icon slot="start" [modicon]="entry.icon" [modname]="entry.iconModName" [showAlt]="false" [colorize]="false" /> | ||||||
|         <ion-label>{{ entry.name }}</ion-label> |         <ion-label>{{ entry.name }}</ion-label> | ||||||
|     </ion-item> |     </ion-item> | ||||||
| </core-loading> | </core-loading> | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ | |||||||
|                 <ion-card> |                 <ion-card> | ||||||
|                     <ion-item class="core-course-module-handler ion-text-wrap" [detail]="false" (click)="action($event, item)" button> |                     <ion-item class="core-course-module-handler ion-text-wrap" [detail]="false" (click)="action($event, item)" button> | ||||||
|                         <core-mod-icon slot="start" *ngIf="item.iconUrl" [modicon]="item.iconUrl" [modname]="item.modname" |                         <core-mod-icon slot="start" *ngIf="item.iconUrl" [modicon]="item.iconUrl" [modname]="item.modname" | ||||||
|                             [componentId]="item.cmid" [showAlt]="false" [purpose]="item.purpose" /> |                             [componentId]="item.cmid" [showAlt]="false" [purpose]="item.purpose" [isBranded]="item.branded" /> | ||||||
|                         <ion-label> |                         <ion-label> | ||||||
|                             <!-- Add the icon title so accessibility tools read it. --> |                             <!-- Add the icon title so accessibility tools read it. --> | ||||||
|                             <span class="sr-only" *ngIf="item.iconTitle">{{ item.iconTitle }}</span> |                             <span class="sr-only" *ngIf="item.iconTitle">{{ item.iconTitle }}</span> | ||||||
|  | |||||||
| @ -22,7 +22,7 @@ | |||||||
|                             <ion-col class="addon-block-timeline-activity-time ion-no-padding ion-text-nowrap"> |                             <ion-col class="addon-block-timeline-activity-time ion-no-padding ion-text-nowrap"> | ||||||
|                                 <small>{{event.timesort * 1000 | coreFormatDate:"strftimetime24" }}</small> |                                 <small>{{event.timesort * 1000 | coreFormatDate:"strftimetime24" }}</small> | ||||||
|                                 <core-mod-icon *ngIf="event.iconUrl" [modicon]="event.iconUrl" [componentId]="event.instance" |                                 <core-mod-icon *ngIf="event.iconUrl" [modicon]="event.iconUrl" [componentId]="event.instance" | ||||||
|                                     [modname]="event.modulename" [purpose]="event.purpose" /> |                                     [modname]="event.modulename" [purpose]="event.purpose" [isBranded]="event.branded" /> | ||||||
|                             </ion-col> |                             </ion-col> | ||||||
|                             <ion-col class="addon-block-timeline-activity-name ion-no-padding"> |                             <ion-col class="addon-block-timeline-activity-name ion-no-padding"> | ||||||
|                                 <p class="item-heading"> |                                 <p class="item-heading"> | ||||||
|  | |||||||
| @ -23,7 +23,7 @@ h4.core-bold { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     core-mod-icon { |     core-mod-icon { | ||||||
|         --padding: 8px; |         --module-legacy-icon-padding: 8px; | ||||||
|         --margin-end: 0.5rem; |         --margin-end: 0.5rem; | ||||||
|         --margin-vertical: 0; |         --margin-vertical: 0; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ | |||||||
|                 <ion-item class="ion-text-wrap addon-calendar-event" [attr.aria-label]="event.name" (click)="eventClicked(event)" button |                 <ion-item class="ion-text-wrap addon-calendar-event" [attr.aria-label]="event.name" (click)="eventClicked(event)" button | ||||||
|                     [ngClass]="['addon-calendar-eventtype-'+event.eventtype]" [detail]="false"> |                     [ngClass]="['addon-calendar-eventtype-'+event.eventtype]" [detail]="false"> | ||||||
|                     <core-mod-icon *ngIf="event.moduleIcon" [modicon]="event.moduleIcon" slot="start" [modname]="event.modulename" |                     <core-mod-icon *ngIf="event.moduleIcon" [modicon]="event.moduleIcon" slot="start" [modname]="event.modulename" | ||||||
|                         [componentId]="event.instance" [showAlt]="false" [purpose]="event.purpose" /> |                         [componentId]="event.instance" [showAlt]="false" [purpose]="event.purpose" [isBranded]="event.branded" /> | ||||||
|                     <ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start" aria-hidden="true" /> |                     <ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start" aria-hidden="true" /> | ||||||
|                     <ion-label> |                     <ion-label> | ||||||
|                         <!-- Add the icon title so accessibility tools read it. --> |                         <!-- Add the icon title so accessibility tools read it. --> | ||||||
|  | |||||||
| @ -67,7 +67,8 @@ | |||||||
|                                         (click)="gotoEvent(event.id, day)" [class.item-dimmed]="event.ispast" |                                         (click)="gotoEvent(event.id, day)" [class.item-dimmed]="event.ispast" | ||||||
|                                         [ngClass]="['addon-calendar-eventtype-'+event.eventtype]" button [detail]="false"> |                                         [ngClass]="['addon-calendar-eventtype-'+event.eventtype]" button [detail]="false"> | ||||||
|                                         <core-mod-icon *ngIf="event.moduleIcon" [modicon]="event.moduleIcon" slot="start" [showAlt]="false" |                                         <core-mod-icon *ngIf="event.moduleIcon" [modicon]="event.moduleIcon" slot="start" [showAlt]="false" | ||||||
|                                             [modname]="event.modulename" [componentId]="event.instance" [purpose]="event.purpose" /> |                                             [modname]="event.modulename" [componentId]="event.instance" [purpose]="event.purpose" | ||||||
|  |                                             [isBranded]="event.branded" /> | ||||||
|                                         <ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start" |                                         <ion-icon *ngIf="event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" slot="start" | ||||||
|                                             aria-hidden="true" /> |                                             aria-hidden="true" /> | ||||||
|                                         <ion-label> |                                         <ion-label> | ||||||
|  | |||||||
| @ -32,6 +32,7 @@ | |||||||
|             <ion-item class="ion-text-wrap addon-calendar-event" collapsible [ngClass]="['addon-calendar-eventtype-'+event.eventtype]"> |             <ion-item class="ion-text-wrap addon-calendar-event" collapsible [ngClass]="['addon-calendar-eventtype-'+event.eventtype]"> | ||||||
|                 <core-mod-icon *ngIf="event.moduleIcon" [modicon]="event.moduleIcon" [showAlt]="false" [modname]="event.modulename" |                 <core-mod-icon *ngIf="event.moduleIcon" [modicon]="event.moduleIcon" [showAlt]="false" [modname]="event.modulename" | ||||||
|                     [componentId]="event.instance" slot="start" [purpose]="event.purpose" /> |                     [componentId]="event.instance" slot="start" [purpose]="event.purpose" /> | ||||||
|  |                 <!-- TODO MOBILE-4530 Add isBranded when available --> | ||||||
|                 <ion-icon *ngIf=" event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" aria-hidden="true" slot="start" /> |                 <ion-icon *ngIf=" event.eventIcon && !event.moduleIcon" [name]="event.eventIcon" aria-hidden="true" slot="start" /> | ||||||
|                 <ion-label> |                 <ion-label> | ||||||
|                     <!-- Add the icon title so accessibility tools read it. --> |                     <!-- Add the icon title so accessibility tools read it. --> | ||||||
|  | |||||||
| @ -84,7 +84,8 @@ | |||||||
|                     </p> |                     </p> | ||||||
|                     <ion-item class="ion-text-wrap" *ngFor="let activity of coursemodules" [href]="activity.url" |                     <ion-item class="ion-text-wrap" *ngFor="let activity of coursemodules" [href]="activity.url" | ||||||
|                         [attr.aria-label]="activity.name" core-link capture="true"> |                         [attr.aria-label]="activity.name" core-link capture="true"> | ||||||
|                         <core-mod-icon slot="start" [modicon]="activity.iconurl" [showAlt]="false" *ngIf="activity.iconurl" /> |                         <core-mod-icon slot="start" [modicon]="activity.iconurl" [showAlt]="false" *ngIf="activity.iconurl" | ||||||
|  |                             [colorize]="false" /> | ||||||
|                         <ion-label> |                         <ion-label> | ||||||
|                             <core-format-text [text]="activity.name" contextLevel="module" [contextInstanceId]="activity.id" |                             <core-format-text [text]="activity.name" contextLevel="module" [contextInstanceId]="activity.id" | ||||||
|                                 [courseId]="courseId" /> |                                 [courseId]="courseId" /> | ||||||
|  | |||||||
| @ -119,7 +119,8 @@ | |||||||
|                             </p> |                             </p> | ||||||
|                             <ion-item class="ion-text-wrap core-course-module-handler" [attr.aria-label]="activity.name" core-link |                             <ion-item class="ion-text-wrap core-course-module-handler" [attr.aria-label]="activity.name" core-link | ||||||
|                                 *ngFor="let activity of competency.coursemodules" [href]="activity.url" capture="true"> |                                 *ngFor="let activity of competency.coursemodules" [href]="activity.url" capture="true"> | ||||||
|                                 <core-mod-icon slot="start" [modicon]="activity.iconurl" [showAlt]="false" *ngIf="activity.iconurl" /> |                                 <core-mod-icon slot="start" [modicon]="activity.iconurl" [showAlt]="false" *ngIf="activity.iconurl" | ||||||
|  |                                     [colorize]="false" /> | ||||||
|                                 <ion-label> |                                 <ion-label> | ||||||
|                                     <core-format-text [text]="activity.name" contextLevel="module" [contextInstanceId]="activity.id" |                                     <core-format-text [text]="activity.name" contextLevel="module" [contextInstanceId]="activity.id" | ||||||
|                                         [courseId]="courseId" /> |                                         [courseId]="courseId" /> | ||||||
|  | |||||||
| Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 46 KiB | 
| Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB | 
| @ -1,17 +1,17 @@ | |||||||
| @use "theme/globals" as *; | @use "theme/globals" as *; | ||||||
| 
 | 
 | ||||||
| :host { | :host { | ||||||
|     --extra-icon-size: 16px; |     --icon-size: 32px; | ||||||
|     --icon-size: 24px; |     --core-avatar-size: var(--icon-size); | ||||||
|  |     --extra-icon-size: 12px; | ||||||
| 
 | 
 | ||||||
|     ::ng-deep core-user-avatar { |     ::ng-deep core-user-avatar { | ||||||
|         .core-avatar-extra-img, |         .core-avatar-extra-img { | ||||||
|         core-mod-icon { |  | ||||||
|             margin: 0 !important; |             margin: 0 !important; | ||||||
|             position: absolute; |             position: absolute; | ||||||
|             right: -4px; |             right: -4px; | ||||||
|             bottom: -4px; |             bottom: -4px; | ||||||
|             --padding: 0.2rem; |             --module-icon-padding: 0.2rem; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         .core-avatar-extra-img { |         .core-avatar-extra-img { | ||||||
| @ -20,37 +20,34 @@ | |||||||
|             img { |             img { | ||||||
|                 max-width: var(--extra-icon-size); |                 max-width: var(--extra-icon-size); | ||||||
|                 max-height: var(--extra-icon-size); |                 max-height: var(--extra-icon-size); | ||||||
|  |                 width: var(--extra-icon-size); | ||||||
|  |                 height: var(--extra-icon-size); | ||||||
|                 display: block; |                 display: block; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         core-mod-icon  { |  | ||||||
|             --size: var(--extra-icon-size); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     div.core-notification-icon { |     div.core-notification-icon { | ||||||
|  |         max-width: var(--icon-size); | ||||||
|  |         max-height:  var(--icon-size); | ||||||
|         img { |         img { | ||||||
|             width: var(--icon-size); |             width: var(--icon-size); | ||||||
|             height: var(--icon-size); |             height:  var(--icon-size); | ||||||
|         } |         } | ||||||
|         ion-icon { |         ion-icon { | ||||||
|             font-size: var(--icon-size); |             font-size: var(--icon-size); | ||||||
|         } |         } | ||||||
|         padding: 0.7rem; |         padding: 0px; | ||||||
|         background: var(--background-color); |         background: var(--background-color); | ||||||
|         border-radius: var(--radius-xs); |         border-radius: var(--radius-xs); | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     .core-notification-icon { |  | ||||||
|         --module-icon-size: var(--icon-size); |  | ||||||
|         @include margin(6px, 8px, 6px, 0px); |         @include margin(6px, 8px, 6px, 0px); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     .core-notification-img { |     .core-notification-img { | ||||||
|         @include margin(6px, 8px, 6px, 0px); |         @include margin(6px, 8px, 6px, 0px); | ||||||
|         width: var(--core-avatar-size); |         width: var(--icon-size); | ||||||
|         height: var(--core-avatar-size); |         height: var(--icon-size); | ||||||
|         object-fit: cover; |         object-fit: cover; | ||||||
|  |         border-radius: var(--core-avatar-radius); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -41,7 +41,7 @@ | |||||||
|                     [profileUrl]="notification.profileimageurlfrom" [fullname]="notification.userfromfullname" |                     [profileUrl]="notification.profileimageurlfrom" [fullname]="notification.userfromfullname" | ||||||
|                     [userId]="notification.useridfrom"> |                     [userId]="notification.useridfrom"> | ||||||
|                     <div class="core-avatar-extra-img" *ngIf="notification.iconurl"> |                     <div class="core-avatar-extra-img" *ngIf="notification.iconurl"> | ||||||
|                         <img [src]="notification.iconurl" alt="" role="presentation"> |                         <img [src]="notification.iconurl" alt="" role="presentation" core-external-content> | ||||||
|                     </div> |                     </div> | ||||||
|                 </core-user-avatar> |                 </core-user-avatar> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| @use "theme/globals" as *; | @use "theme/globals" as *; | ||||||
| 
 | 
 | ||||||
| ion-item.addon-notification-item { | ion-item.addon-notification-item { | ||||||
|  | 
 | ||||||
|     ion-label { |     ion-label { | ||||||
|         margin-top: 8px; |         margin-top: 8px; | ||||||
|         margin-bottom: 8px; |         margin-bottom: 8px; | ||||||
| @ -32,17 +33,6 @@ ion-item.addon-notification-item { | |||||||
|         align-self: start; |         align-self: start; | ||||||
|         margin-top: 16px; |         margin-top: 16px; | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     --icon-size: 16px; |  | ||||||
|     --extra-icon-size: 12px; |  | ||||||
|     --core-avatar-size: 32px; |  | ||||||
| 
 |  | ||||||
|     div.core-notification-icon, |  | ||||||
|     core-mod-icon.core-notification-icon { |  | ||||||
|         --padding: 8px; |  | ||||||
|         max-width: var(--core-avatar-size); |  | ||||||
|         max-height:  var(--core-avatar-size); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .mark-all-as-read { | .mark-all-as-read { | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ | |||||||
|                 <core-user-avatar *ngIf="notification.useridfrom > 0" slot="start" [userId]="notification.useridfrom" |                 <core-user-avatar *ngIf="notification.useridfrom > 0" slot="start" [userId]="notification.useridfrom" | ||||||
|                     [profileUrl]="notification.profileimageurlfrom" [fullname]="notification.userfromfullname"> |                     [profileUrl]="notification.profileimageurlfrom" [fullname]="notification.userfromfullname"> | ||||||
|                     <div class="core-avatar-extra-img" *ngIf="notification.iconurl"> |                     <div class="core-avatar-extra-img" *ngIf="notification.iconurl"> | ||||||
|                         <img [src]="notification.iconurl" alt="" role="presentation"> |                         <img [src]="notification.iconurl" alt="" role="presentation" core-external-content> | ||||||
|                     </div> |                     </div> | ||||||
|                 </core-user-avatar> |                 </core-user-avatar> | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,6 +1,8 @@ | |||||||
| @use "theme/globals" as *; | @use "theme/globals" as *; | ||||||
| 
 | 
 | ||||||
| :host { | :host { | ||||||
|  |     --icon-size: 44px; | ||||||
|  |     --extra-icon-size: 16px; | ||||||
| 
 | 
 | ||||||
|     .core-notification-title { |     .core-notification-title { | ||||||
|         [slot=start] { |         [slot=start] { | ||||||
|  | |||||||
| @ -100,7 +100,8 @@ | |||||||
|                             <ion-item class="core-course-storage-activity" |                             <ion-item class="core-course-storage-activity" | ||||||
|                                 *ngIf="downloadEnabled || (!module.calculatingSize && module.totalSize > 0)"> |                                 *ngIf="downloadEnabled || (!module.calculatingSize && module.totalSize > 0)"> | ||||||
|                                 <core-mod-icon slot="start" *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon" |                                 <core-mod-icon slot="start" *ngIf="module.handlerData.icon" [modicon]="module.handlerData.icon" | ||||||
|                                     [modname]="module.modname" [componentId]="module.instance" [fallbackTranslation]="module.modplural" /> |                                     [modname]="module.modname" [componentId]="module.instance" [fallbackTranslation]="module.modplural" | ||||||
|  |                                     [isBranded]="module.branded" /> | ||||||
|                                 <ion-label class="ion-text-wrap"> |                                 <ion-label class="ion-text-wrap"> | ||||||
|                                     <p class="item-heading {{module.handlerData!.class}} addon-storagemanager-module-size"> |                                     <p class="item-heading {{module.handlerData!.class}} addon-storagemanager-module-size"> | ||||||
|                                         <core-format-text [text]="module.handlerData.title" [courseId]="module.course" contextLevel="module" |                                         <core-format-text [text]="module.handlerData.title" [courseId]="module.course" contextLevel="module" | ||||||
|  | |||||||
| @ -50,8 +50,9 @@ ion-badge { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ion-item core-mod-icon { | ion-item core-mod-icon { | ||||||
|     --size: 18px; |     --module-icon-size: 24px; | ||||||
|     --padding: 9px; |     --module-legacy-icon-size: 16px; | ||||||
|  | 
 | ||||||
|     --margin-vertical: 8px; |     --margin-vertical: 8px; | ||||||
|     --margin-end: 8px; |     --margin-end: 8px; | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,7 +1,5 @@ | |||||||
| <img *ngIf="!isLocalUrl && !iconSVG" [src]="icon" [alt]="showAlt ? modNameTranslated : ''" [attr.role]="!showAlt ? 'presentation' : null" | <ng-container *ngIf="loaded"> | ||||||
|     class="core-module-icon" core-external-content [component]="linkIconWithComponent ? modname : null" |     <img *ngIf="!isLocalUrl" [src]="iconUrl" core-external-content alt="" [component]="linkIconWithComponent ? modname : null" | ||||||
|     [componentId]="linkIconWithComponent ? componentId : null" (error)="loadFallbackIcon()"> |         [componentId]="linkIconWithComponent ? componentId : null" (error)="loadFallbackIcon()"> | ||||||
| <img *ngIf="isLocalUrl && !iconSVG" [src]="icon" [alt]="showAlt ? modNameTranslated : ''" [attr.role]="!showAlt ? 'presentation' : null" |     <img *ngIf="isLocalUrl" [src]="iconUrl" (error)="loadFallbackIcon()" alt=""> | ||||||
|     class="core-module-icon" (error)="loadFallbackIcon()"> | </ng-container> | ||||||
| <div *ngIf="iconSVG" class="core-module-icon" [innerHTML]="iconSVG" [attr.aria-label]="showAlt ? modNameTranslated : ''" |  | ||||||
|     [attr.role]="!showAlt ? 'presentation' : null"></div> |  | ||||||
|  | |||||||
| @ -3,70 +3,75 @@ | |||||||
| :host { | :host { | ||||||
|     display: inline-block; |     display: inline-block; | ||||||
|     --size: var(--module-icon-size, 32px); |     --size: var(--module-icon-size, 32px); | ||||||
|     --padding: var(--module-icon-padding, 8px); |     --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)); |     --icon-radius: var(--module-icon-radius, var(--radius-xs)); | ||||||
|     --margin-end: 0px; |     --margin-end: 0px; | ||||||
|     --margin-vertical: 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-top: var(--margin-vertical); | ||||||
|     margin-bottom: var(--margin-vertical); |     margin-bottom: var(--margin-vertical); | ||||||
|     @include margin-horizontal(null, var(--margin-end)); |     @include margin-horizontal(null, var(--margin-end)); | ||||||
| 
 | 
 | ||||||
|     border-radius: var(--icon-radius); |     border-radius: var(--icon-radius); | ||||||
|     padding: var(--padding); |     @include padding(var(--padding-top), var(--padding-end), var(--padding-bottom), var(--padding-start)); | ||||||
|     background-color: transparent; |     background-color: transparent; | ||||||
|     line-height: var(--size); |     line-height: var(--size); | ||||||
| 
 | 
 | ||||||
|     &.version_current { |     --color: var(--text-color); | ||||||
|         @each $type, $value in $activity-icon-color-filters { | 
 | ||||||
|             &.#{$type} { |     &.colorize { | ||||||
|                 --filter: var(--activity#{$type}); |         &.version_current { | ||||||
|  |             @each $type, $value in $activity-icon-colors { | ||||||
|  |                 &.#{$type} { | ||||||
|  |                     --color: var(--activity#{$type}); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         img { |         &.version_40 { | ||||||
|             filter: var(--filter, brightness(0) invert(1)); |             background-color: var(--gray-100); | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     &.version_40 { |             --color: white; | ||||||
|         --size: var(--module-icon-size, 24px); |  | ||||||
|         background-color: $gray-100; |  | ||||||
| 
 | 
 | ||||||
|         @each $type, $value in $activity-icon-background-colors { |             @each $type, $value in $activity-icon-background-colors { | ||||||
|             &.#{$type} { |                 &.#{$type} { | ||||||
|                 background-color: var(--activity-40-#{$type}); |                     background-color: var(--activity-40-#{$type}); | ||||||
|                 img { |  | ||||||
|                     filter: var(--filter, brightness(0) invert(1)); |  | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     &.version_40, | ||||||
|     &.version_legacy { |     &.version_legacy { | ||||||
|         --size: var(--module-icon-size, 24px); |         --size: var(--module-legacy-icon-size, 24px); | ||||||
|         --filter: none; |         --padding-start: var(--module-legacy-icon-padding, 8px); | ||||||
|         background-color: $gray-100; |         --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 { |     &:not(.branded) { | ||||||
|         --filter: none !important; |         ::ng-deep svg, | ||||||
|  |         ::ng-deep svg * { | ||||||
|  |             fill: var(--color); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| img { |     img, | ||||||
|     width: var(--size); |     ::ng-deep svg { | ||||||
|     height: var(--size); |         width: var(--size); | ||||||
|     max-width: var(--size); |         height: var(--size); | ||||||
|     max-height: var(--size); |         max-width: var(--size); | ||||||
|     min-width: var(--size); |         max-height: var(--size); | ||||||
|     min-height: var(--size); |         min-width: var(--size); | ||||||
|     vertical-align: top; |         min-height: var(--size); | ||||||
| 
 |         vertical-align: top; | ||||||
|     &[alt] { |  | ||||||
|         text-indent: -999999px; |  | ||||||
|         white-space: nowrap; |  | ||||||
|         overflow: hidden; |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -16,9 +16,14 @@ import { CoreConstants, ModPurpose } from '@/core/constants'; | |||||||
| import { Component, ElementRef, HostBinding, 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 { 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 { Http } from '@singletons'; | ||||||
|  | import { firstValueFrom } from 'rxjs'; | ||||||
| 
 | 
 | ||||||
| const assetsPath = 'assets/img/'; | const assetsPath = 'assets/img/'; | ||||||
| const fallbackModName = 'external-tool'; | const fallbackModName = 'external-tool'; | ||||||
| @ -39,30 +44,46 @@ const enum IconVersion { | |||||||
| }) | }) | ||||||
| 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.
 | ||||||
|     @Input() modicon?: string; // Module icon url or local url.
 |     @Input() modicon?: string; // Module icon url or local url.
 | ||||||
|     @Input() showAlt = true; // Show alt otherwise it's only presentation icon.
 |     @Input() showAlt = true; // Show alt otherwise it's only presentation icon.
 | ||||||
|     @Input() purpose: ModPurpose = ModPurpose.MOD_PURPOSE_OTHER; // Purpose of the module.
 |     @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.
 | ||||||
| 
 | 
 | ||||||
|     icon = ''; |     @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 = ''; | ||||||
| 
 | 
 | ||||||
|     modNameTranslated = ''; |     modNameTranslated = ''; | ||||||
|     isLocalUrl = true; |     isLocalUrl = false; | ||||||
|     linkIconWithComponent = false; |     linkIconWithComponent = false; | ||||||
|  |     loaded = false; | ||||||
| 
 | 
 | ||||||
|     @HostBinding('class') iconVersion = 'legacy'; |     protected iconVersion: IconVersion = IconVersion.LEGACY_VERSION; | ||||||
|  |     protected purposeClass = ''; | ||||||
|  |     protected element: HTMLElement; | ||||||
| 
 | 
 | ||||||
|     constructor(protected el: ElementRef) { } |     constructor(element: ElementRef) { | ||||||
|  |         this.element = element.nativeElement; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * @inheritdoc |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|     async ngOnInit(): Promise<void> { |     async ngOnInit(): Promise<void> { | ||||||
|         this.iconVersion = this.getIconVersion(); |         this.iconVersion = this.getIconVersion(); | ||||||
|  |         this.element.classList.add(this.iconVersion); | ||||||
| 
 | 
 | ||||||
|         if (!this.modname && this.modicon) { |         if (!this.modname && this.modicon) { | ||||||
|             // Guess module from the icon url.
 |             // Guess module from the icon url.
 | ||||||
| @ -70,32 +91,9 @@ export class CoreModIconComponent implements OnInit, OnChanges { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.modNameTranslated = CoreCourse.translateModuleName(this.modname, this.fallbackTranslation); |         this.modNameTranslated = CoreCourse.translateModuleName(this.modname, this.fallbackTranslation); | ||||||
|         if (this.iconVersion !== IconVersion.LEGACY_VERSION) { |  | ||||||
| 
 | 
 | ||||||
|             let purposeClass = |         this.setIsBranded(); | ||||||
|                 CoreCourseModuleDelegate.supportsFeature<ModPurpose>( |         this.setPurposeClass(); | ||||||
|                     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); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         await this.setIcon(); |         await this.setIcon(); | ||||||
|     } |     } | ||||||
| @ -109,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<void> { | ||||||
|  |         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. |      * Set icon. | ||||||
|      */ |      */ | ||||||
|     async setIcon(): Promise<void> { |     async setIcon(): Promise<void> { | ||||||
|         this.icon = this.modicon || this.icon; |         this.iconUrl = this.modicon || this.iconUrl; | ||||||
|         this.isLocalUrl = this.icon.startsWith(assetsPath); | 
 | ||||||
|  |         if (!this.iconUrl) { | ||||||
|  |             this.loadFallbackIcon(); | ||||||
|  | 
 | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.isLocalUrl = 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.
 | ||||||
| @ -123,62 +179,31 @@ export class CoreModIconComponent implements OnInit, OnChanges { | |||||||
|             !!this.modname && |             !!this.modname && | ||||||
|             !!this.componentId && |             !!this.componentId && | ||||||
|             !this.isLocalUrl && |             !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. |      * Icon to load on error. | ||||||
|      */ |      */ | ||||||
|     async loadFallbackIcon(): Promise<void> { |     async loadFallbackIcon(): Promise<void> { | ||||||
|  |         if (this.isLocalUrl) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         this.isLocalUrl = true; |         this.isLocalUrl = true; | ||||||
|  |         this.linkIconWithComponent = false; | ||||||
|  | 
 | ||||||
|         const moduleName = !this.modname || CoreCourse.CORE_MODULES.indexOf(this.modname) < 0 |         const moduleName = !this.modname || CoreCourse.CORE_MODULES.indexOf(this.modname) < 0 | ||||||
|             ? fallbackModName |             ? fallbackModName | ||||||
|             : this.modname; |             : this.modname; | ||||||
| 
 | 
 | ||||||
|         const path = CoreCourse.getModuleIconsPath(); |         const path = CoreCourse.getModuleIconsPath(); | ||||||
| 
 | 
 | ||||||
|         this.icon = path + moduleName + '.svg'; |         this.iconUrl = path + moduleName + '.svg'; | ||||||
|         this.noFilter = await this.getIconNoFilter(); |  | ||||||
|     } |  | ||||||
| 
 | 
 | ||||||
|     /** |         await this.setSVGIcon(); | ||||||
|      * Returns if the icon does not need to be filtered. |  | ||||||
|      * |  | ||||||
|      * @returns wether the icon does not need to be filtered. |  | ||||||
|      */ |  | ||||||
|     protected async getIconNoFilter(): Promise<boolean> { |  | ||||||
|         // Earlier 4.0, icons were never filtered.
 |  | ||||||
|         if (this.iconVersion === IconVersion.LEGACY_VERSION) { |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         // No icon or local icon (not legacy), filter it.
 |  | ||||||
|         if (!this.icon || this.isLocalUrl) { |  | ||||||
|             return await CoreCourseModuleDelegate.moduleIconIsBranded(this.modname); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         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; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -188,7 +213,7 @@ export class CoreModIconComponent implements OnInit, OnChanges { | |||||||
|      * @returns Guessed modname. |      * @returns Guessed modname. | ||||||
|      */ |      */ | ||||||
|     protected getComponentNameFromIconUrl(iconUrl: string): string { |     protected getComponentNameFromIconUrl(iconUrl: string): string { | ||||||
|         if (!CoreUrlUtils.isThemeImageUrl(this.icon)) { |         if (!CoreUrlUtils.isThemeImageUrl(this.iconUrl)) { | ||||||
|             // Cannot be guessed.
 |             // Cannot be guessed.
 | ||||||
|             return ''; |             return ''; | ||||||
|         } |         } | ||||||
| @ -213,6 +238,38 @@ export class CoreModIconComponent implements OnInit, OnChanges { | |||||||
|         return component; |         return component; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Set the purpose class. | ||||||
|  |      */ | ||||||
|  |     protected setPurposeClass(): void { | ||||||
|  |         if (this.iconVersion === IconVersion.LEGACY_VERSION) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.purposeClass = | ||||||
|  |             CoreCourseModuleDelegate.supportsFeature<ModPurpose>( | ||||||
|  |                 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. |      * Get the icon version depending on site version. | ||||||
|      * |      * | ||||||
| @ -232,4 +289,103 @@ 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 = 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; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| <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" [purpose]="module.purpose" /> |     <core-mod-icon slot="start" [modicon]="modicon" [modname]="module.modname" [componentId]="module.instance" [purpose]="module.purpose" | ||||||
|     <ion-label> |         [isBranded]="module.branded" /> | ||||||
|  |     <ion-label class="core-module-info-activity-title"> | ||||||
|         <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" | ||||||
|                 [contextInstanceId]="module.id" [courseId]="courseId" /> |                 [contextInstanceId]="module.id" [courseId]="courseId" /> | ||||||
|  | |||||||
| @ -10,8 +10,16 @@ | |||||||
| 
 | 
 | ||||||
|     core-mod-icon { |     core-mod-icon { | ||||||
|         align-self: flex-start; |         align-self: flex-start; | ||||||
|         --padding: 4px; |  | ||||||
|         @include margin-horizontal(null, 8px); |         @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 { |     h1 ion-icon { | ||||||
|  | |||||||
| @ -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" [purpose]="module.purpose" /> |                         [fallbackTranslation]="module.modplural" [purpose]="module.purpose" [isBranded]="module.branded" /> | ||||||
|                     {{moduleNameTranslated}} |                     {{moduleNameTranslated}} | ||||||
|                 </p> |                 </p> | ||||||
|                 <h1> |                 <h1> | ||||||
|  | |||||||
| @ -10,16 +10,20 @@ h1 { | |||||||
| 
 | 
 | ||||||
| .core-modulename { | .core-modulename { | ||||||
|     text-transform: uppercase; |     text-transform: uppercase; | ||||||
|  | 
 | ||||||
|     core-mod-icon { |     core-mod-icon { | ||||||
|         --padding: 3px; |         --module-icon-padding: 0px; | ||||||
|         --size: 10px; |         --module-legacy-icon-padding: 2px; | ||||||
|         margin: 0; |         --module-icon-size: 16px; | ||||||
|  |         --module-legacy-icon-size: 12px; | ||||||
|  | 
 | ||||||
|  |         margin: 0px; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ion-item ion-label ion-icon { | ion-item ion-label ion-icon { | ||||||
|     @include margin-horizontal(0, 4px); |     @include margin-horizontal(0px, 4px); | ||||||
|     vertical-align: text-top; |     vertical-align: text-top; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -8,7 +8,8 @@ | |||||||
|             <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" [purpose]="module.purpose" /> |                         [componentId]="module.instance" [fallbackTranslation]="module.modplural" [purpose]="module.purpose" | ||||||
|  |                         [isBranded]="module.branded" /> | ||||||
|                     <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" | ||||||
|  | |||||||
| @ -33,7 +33,8 @@ | |||||||
|         core-mod-icon { |         core-mod-icon { | ||||||
|             margin-top: 0px; |             margin-top: 0px; | ||||||
|             margin-bottom: 0px; |             margin-bottom: 0px; | ||||||
|             --module-icon-padding: 4px; |             --module-icon-padding: 0px; | ||||||
|  |             --module-legacy-icon-padding: 4px; | ||||||
|             --module-icon-radius: var(--radius-xs); |             --module-icon-radius: var(--radius-xs); | ||||||
| 
 | 
 | ||||||
|             @include margin-horizontal(null, 8px); |             @include margin-horizontal(null, 8px); | ||||||
|  | |||||||
| @ -1751,6 +1751,7 @@ export type CoreCourseGetContentsWSModule = { | |||||||
|     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.
 |     purpose?: string; // @since 4.4 The module purpose.
 | ||||||
|  |     branded?: boolean; // @since 4.4 Whether the module is branded or not.
 | ||||||
|     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.
 | ||||||
|  | |||||||
| Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB | 
| Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB | 
| @ -45,7 +45,7 @@ | |||||||
|                                     <img *ngIf="row.image && !row.itemmodule" [src]="row.image" slot="start" class="core-module-icon" |                                     <img *ngIf="row.image && !row.itemmodule" [src]="row.image" slot="start" class="core-module-icon" | ||||||
|                                         [alt]="row.iconAlt" /> |                                         [alt]="row.iconAlt" /> | ||||||
|                                     <core-mod-icon *ngIf="row.image && row.itemmodule" [modicon]="row.image" slot="start" |                                     <core-mod-icon *ngIf="row.image && row.itemmodule" [modicon]="row.image" slot="start" | ||||||
|                                         [modname]="row.itemmodule" /> |                                         [modname]="row.itemmodule" [colorize]="false" /> | ||||||
|                                     <span [innerHTML]="row.gradeitem"></span> |                                     <span [innerHTML]="row.gradeitem"></span> | ||||||
|                                 </th> |                                 </th> | ||||||
|                                 <ng-container *ngIf="row.itemtype !== 'category'"> |                                 <ng-container *ngIf="row.itemtype !== 'category'"> | ||||||
|  | |||||||
| @ -43,10 +43,10 @@ table.core-table.core-grades-table { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         core-mod-icon { |         core-mod-icon { | ||||||
|             --padding: 0px; |             --module-icon-padding: 0px; | ||||||
|             --size: 16px; |             --module-legacy-icon-padding: 0px; | ||||||
|             background: transparent; |             --module-icon-size: 16px; | ||||||
|             --filter: var(--module-icon-filter); |             --module-legacy-icon-size: 16px; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         ion-icon { |         ion-icon { | ||||||
|  | |||||||
| @ -4,8 +4,8 @@ | |||||||
|     <ion-label> |     <ion-label> | ||||||
|         <h3 *ngIf="result.title"> |         <h3 *ngIf="result.title"> | ||||||
|             <ion-icon *ngIf="renderedIcon" [name]="renderedIcon" aria-hidden="true" /> |             <ion-icon *ngIf="renderedIcon" [name]="renderedIcon" aria-hidden="true" /> | ||||||
|             <core-mod-icon *ngIf="!renderedIcon && result.module" [modicon]="result.module.iconurl" |             <core-mod-icon *ngIf="!renderedIcon && result.module" [modicon]="result.module.iconurl" [modname]="result.module.name" | ||||||
|                 [modname]="result.module.name" /> |                 [colorize]="false" /> | ||||||
|             <img *ngIf="!renderedIcon && !result.module && result.component" [src]="result.component.iconurl" alt="" class="result-icon" |             <img *ngIf="!renderedIcon && !result.module && result.component" [src]="result.component.iconurl" alt="" class="result-icon" | ||||||
|                 core-external-content [component]="result.component.name"> |                 core-external-content [component]="result.component.name"> | ||||||
|             <core-format-text [text]="result.title" /> |             <core-format-text [text]="result.title" /> | ||||||
|  | |||||||
| @ -12,14 +12,14 @@ | |||||||
|         color: var(--core-global-search-result-title-color); |         color: var(--core-global-search-result-title-color); | ||||||
| 
 | 
 | ||||||
|         core-mod-icon { |         core-mod-icon { | ||||||
|             --size: var(--core-global-search-result-icon-size); |             --module-icon-padding: 0px; | ||||||
|             --filter: var(--module-icon-filter); |             --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-inline-end: var(--spacing-2); | ||||||
|             margin-top: 0px; |             margin-top: 0px; | ||||||
|             margin-bottom: 0px; |             margin-bottom: 0px; | ||||||
|             --padding: 0px; |  | ||||||
|             background: transparent; |  | ||||||
|             flex-shrink: 0; |             flex-shrink: 0; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
|     <ion-item class="ion-text-wrap" [href]="item.url" core-link [capture]="true"> |     <ion-item class="ion-text-wrap" [href]="item.url" core-link [capture]="true"> | ||||||
|         <core-user-avatar *ngIf="item.avatarUrl" [profileUrl]="item.avatarUrl" slot="start" [linkProfile]="false" /> |         <core-user-avatar *ngIf="item.avatarUrl" [profileUrl]="item.avatarUrl" slot="start" [linkProfile]="false" /> | ||||||
| 
 | 
 | ||||||
|         <core-mod-icon *ngIf="item.iconUrl" [modicon]="item.iconUrl" slot="start" [showAlt]="false" /> |         <core-mod-icon *ngIf="item.iconUrl" [modicon]="item.iconUrl" slot="start" [showAlt]="false" [isBranded]="item.branded" /> | ||||||
|         <ion-label> |         <ion-label> | ||||||
|             <p class="item-heading">{{ item.heading }}</p> |             <p class="item-heading">{{ item.heading }}</p> | ||||||
|             <p *ngFor="let text of item.details">{{ text }}</p> |             <p *ngFor="let text of item.details">{{ text }}</p> | ||||||
|  | |||||||
| Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 16 KiB | 
| @ -135,15 +135,6 @@ $activity-icon-colors: ( | |||||||
|     interactivecontent: #8d3d1b |     interactivecontent: #8d3d1b | ||||||
| ) !default; | ) !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; | ||||||
|  | |||||||
| @ -130,8 +130,6 @@ html.dark { | |||||||
|     --core-login-input-background: var(--core-login-background); |     --core-login-input-background: var(--core-login-background); | ||||||
|     --core-login-input-color: var(--core-login-text-color); |     --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: var(--success-tint); | ||||||
|     --core-question-correct-color-bg: var(--success-shade); |     --core-question-correct-color-bg: var(--success-shade); | ||||||
|     --core-question-incorrect-color: var(--danger); |     --core-question-incorrect-color: var(--danger); | ||||||
|  | |||||||
| @ -354,8 +354,6 @@ html { | |||||||
|     --core-messages-discussion-badge: var(--primary); |     --core-messages-discussion-badge: var(--primary); | ||||||
|     --core-messages-discussion-badge-text: var(--white); |     --core-messages-discussion-badge-text: var(--white); | ||||||
| 
 | 
 | ||||||
|     --module-icon-filter: brightness(0); |  | ||||||
| 
 |  | ||||||
|     --addon-forum-avatar-size: var(--core-avatar-size); |     --addon-forum-avatar-size: var(--core-avatar-size); | ||||||
|     --addon-forum-border-color: var(--stroke); |     --addon-forum-border-color: var(--stroke); | ||||||
|     --addon-forum-highlight-color: var(--light); |     --addon-forum-highlight-color: var(--light); | ||||||
| @ -404,7 +402,7 @@ html { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Make activtity colours available for custom modules. |     // Make activtity colours available for custom modules. | ||||||
|     @each $type, $value in $activity-icon-color-filters { |     @each $type, $value in $activity-icon-colors { | ||||||
|         --activity#{$type}: #{$value}; |         --activity#{$type}: #{$value}; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||