Merge pull request #3130 from crazyserver/MOBILE-3814

Mobile 3814
main
Dani Palou 2022-02-21 14:47:00 +01:00 committed by GitHub
commit 71f5caa95c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 232 additions and 141 deletions

View File

@ -1517,10 +1517,8 @@
"core.course": "moodle",
"core.course.activitydisabled": "local_moodlemobileapp",
"core.course.activitynotyetviewableremoteaddon": "local_moodlemobileapp",
"core.course.activitynotyetviewablesiteupgradeneeded": "local_moodlemobileapp",
"core.course.allsections": "local_moodlemobileapp",
"core.course.aria:sectionprogress": "local_moodlemobileapp",
"core.course.askadmintosupport": "local_moodlemobileapp",
"core.course.availablespace": "local_moodlemobileapp",
"core.course.cannotdeletewhiledownloading": "local_moodlemobileapp",
"core.course.completion_automatic:done": "course",
@ -2357,7 +2355,6 @@
"core.whatisyourage": "moodle",
"core.wheredoyoulive": "moodle",
"core.whoissiteadmin": "local_moodlemobileapp",
"core.whoops": "local_moodlemobileapp",
"core.whyisthishappening": "local_moodlemobileapp",
"core.whyisthisrequired": "moodle",
"core.wsfunctionnotavailable": "local_moodlemobileapp",

View File

@ -133,20 +133,10 @@
margin-right: 1px;
margin-left: 1px;
&.calendar_event_category {
background-color: var(--addon-calendar-event-category-color);
}
&.calendar_event_course {
background-color: var(--addon-calendar-event-course-color);
}
&.calendar_event_group {
background-color: var(--addon-calendar-event-group-color);
}
&.calendar_event_user {
background-color: var(--addon-calendar-event-user-color);
}
&.calendar_event_site {
background-color: var(--addon-calendar-event-site-color);
@each $category, $value in $calendar-event-category-colors {
&.calendar_event_#{$category} {
background-color: $value;
}
}
}

View File

@ -48,7 +48,7 @@ export class AddonStorageManagerCourseMenuHandlerService implements CoreCourseOp
course: CoreCourseAnyCourseDataWithOptions,
): CoreCourseOptionsMenuHandlerData {
return {
icon: 'cloud-download',
icon: 'fas-cloud-download-alt',
title: 'addon.storagemanager.coursedownloads',
page: 'storage/' + course.id,
pageParams: {

View File

@ -0,0 +1 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><!-- Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M20.4 10C20.4 10.4575 20.3137 10.8962 20.16 11.2975C22.35 11.74 24 13.6787 24 16C24 18.6512 21.8513 20.8 19.2 20.8H5.4C2.41875 20.8 0 18.3812 0 15.4C0 13.045 1.5075 11.0425 3.6075 10.3038C3.60375 10.2025 3.6 10.1012 3.6 10C3.6 6.685 6.285 4 9.6 4C11.8238 4 13.7625 5.2075 14.8013 7.0075C15.3713 6.625 16.0613 6.4 16.8 6.4C18.7875 6.4 20.4 8.0125 20.4 10ZM4.98276 14.5878L9.03876 18.6438C9.28244 18.8875 9.67753 18.8875 9.92124 18.6439L17.0972 11.4678C17.3409 11.2241 17.3409 10.829 17.0972 10.5853L16.2148 9.70288C15.9711 9.4592 15.576 9.4592 15.3323 9.70288L9.48 15.5552L6.74768 12.8229C6.504 12.5792 6.10888 12.5792 5.86521 12.8229L4.98276 13.7054C4.73908 13.9491 4.73908 14.3441 4.98276 14.5878Z"/></svg>

After

Width:  |  Height:  |  Size: 957 B

View File

@ -0,0 +1 @@
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><!-- Font Awesome Free 5.15.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M20.4 10C20.4 10.4575 20.3137 10.8962 20.16 11.2975C22.35 11.74 24 13.6787 24 16C24 18.6512 21.8513 20.8 19.2 20.8H5.4C2.41875 20.8 0 18.3812 0 15.4C0 13.045 1.5075 11.0425 3.6075 10.3038C3.60375 10.2025 3.6 10.1012 3.6 10C3.6 6.685 6.285 4 9.6 4C11.8238 4 13.7625 5.2075 14.8013 7.0075C15.3713 6.625 16.0613 6.4 16.8 6.4C18.7875 6.4 20.4 8.0125 20.4 10ZM14.6873 10.0224C13.7416 9.11858 12.4606 8.5626 11.0497 8.56007C8.13698 8.55485 5.75998 10.9274 5.76 13.8401C5.76003 16.7561 8.12395 19.1201 11.04 19.1201C12.401 19.1201 13.6417 18.6051 14.578 17.7594C14.6868 17.6611 14.692 17.492 14.5882 17.3883L13.7438 16.5439C13.6488 16.4489 13.4963 16.4435 13.3953 16.5321C12.7432 17.1043 11.9156 17.4168 11.04 17.4168C9.04263 17.4168 7.4795 15.799 7.46336 13.8709C7.44635 11.8384 9.10454 10.2464 11.0717 10.2634C11.9751 10.2712 12.8253 10.612 13.4824 11.2273L12.5936 12.1162C12.2717 12.4381 12.4996 12.9885 12.9549 12.9885H15.809C16.0912 12.9885 16.32 12.7597 16.32 12.4775V9.62333C16.32 9.1681 15.7696 8.94012 15.4477 9.26201L14.6873 10.0224Z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.9 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M2.3 4.8 6.7 4c.8 1.2 1.4 2.6 1.7 4.1.8-1.1 1.4-2 1.9-2.5.6-.7 1-1.1 1.4-1.3.4-.2.8-.3 1.2-.3.5 0 .8.1 1.1.4.2.3.4.6.4 1s-.1.7-.4 1c-.2.3-.6.4-.9.4-.3 0-.6 0-.9-.1-.3-.1-.6-.1-.7-.1-.4 0-.7.1-1 .4-.6.3-1.2 1-1.8 2.1.7 2.5 1.3 4 1.7 4.6.3.3.5.5.8.5.2 0 .4-.1.6-.2.2-.2.6-.6 1.1-1.3l.5.3c-.7 1.2-1.5 2-2.2 2.5-.5.4-1.1.6-1.6.6s-1-.1-1.3-.4c-.4-.2-.7-.6-.9-1.2-.3-.5-.6-1.4-1-2.6-1 1.2-1.7 2.2-2.3 2.7-.6.6-1 .9-1.4 1.1s-.9.3-1.3.3-.8-.1-1-.4-.4-.5-.4-.9.1-.8.4-1.1c.3-.3.6-.4 1.1-.4.2 0 .5.1.8.2.4.2.7.3.9.3.2 0 .5-.1.7-.2.3-.1.6-.4 1-.8.1-.2.5-.7 1.1-1.6-.8-3-1.4-4.8-1.9-5.3-.2-.4-.6-.6-1-.6-.2 0-.5 0-.8.1v-.5zM6.5 0h7.3L13 2.6H5.7L6.5 0z"/></svg>

After

Width:  |  Height:  |  Size: 777 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1.9 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M9.2 12.4c-.7.3-1.4.5-2.1.6H2.7l5.2-5.9L5.4 2H7c1.1 0 2 .1 2.6.3.5.2 1 .5 1.3 1 .2.4.3.7.3 1.7h.4l1.3-5H2v2l2.8 5.6L0 13v3h10.4l2.5-6h-.5c-1 1-2 1.8-3.2 2.4z"/></svg>

After

Width:  |  Height:  |  Size: 295 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-0.9 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M2.3 4.8 6.7 4c.8 1.2 1.4 2.6 1.7 4.1.8-1.1 1.4-2 1.9-2.5.6-.7 1-1.1 1.4-1.3.4-.2.8-.3 1.2-.3.5 0 .8.1 1.1.4.2.3.4.6.4 1s-.1.7-.4 1c-.2.3-.6.4-.9.4-.3 0-.6 0-.9-.1-.3-.1-.6-.1-.7-.1-.4 0-.7.1-1 .4-.6.3-1.2 1-1.8 2.1.7 2.5 1.3 4 1.7 4.6.3.3.5.5.8.5.2 0 .4-.1.6-.2.2-.2.6-.6 1.1-1.3l.5.3c-.7 1.2-1.5 2-2.2 2.5-.5.4-1.1.6-1.6.6s-1-.1-1.3-.4c-.4-.2-.7-.6-.9-1.2-.3-.5-.6-1.4-1-2.6-1 1.2-1.7 2.2-2.3 2.7-.6.6-1 .9-1.4 1.1s-.9.3-1.3.3-.8-.1-1-.4-.4-.5-.4-.9.1-.8.4-1.1c.3-.3.6-.4 1.1-.4.2 0 .5.1.8.2.4.2.7.3.9.3.2 0 .5-.1.7-.2.3-.1.6-.4 1-.8.1-.2.5-.7 1.1-1.6-.8-3-1.4-4.8-1.9-5.3-.2-.4-.6-.6-1-.6-.2 0-.5 0-.8.1v-.5zM6.5 0h7.3L13 2.6H5.7L6.5 0z" fill="#999"/></svg>

Before

Width:  |  Height:  |  Size: 812 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="-1.9 0 16 16" preserveAspectRatio="xMinYMid meet" overflow="visible"><path d="M9.2 12.4c-.7.3-1.4.5-2.1.6H2.7l5.2-5.9L5.4 2H7c1.1 0 2 .1 2.6.3.5.2 1 .5 1.3 1 .2.4.3.7.3 1.7h.4l1.3-5H2v2l2.8 5.6L0 13v3h10.4l2.5-6h-.5c-1 1-2 1.8-3.2 2.4z" fill="#999"/></svg>

Before

Width:  |  Height:  |  Size: 330 B

View File

@ -2,19 +2,19 @@
<!-- Download button. -->
<ion-button *ngIf="status == statusNotDownloaded" fill="clear" (click)="download($event, false)" color="dark" @coreShowHideAnimation
[attr.aria-label]="(statusTranslatable || 'core.download') | translate">
<ion-icon slot="icon-only" name="cloud-download" aria-hidden="true"></ion-icon>
<ion-icon slot="icon-only" name="fas-cloud-download-alt" aria-hidden="true"></ion-icon>
</ion-button>
<!-- Refresh button. -->
<ion-button *ngIf="status == statusOutdated || (status == statusDownloaded && !canTrustDownload)" fill="clear"
(click)="download($event, true)" color="dark" @coreShowHideAnimation
(click)="download($event, true)" color="primary" @coreShowHideAnimation
[attr.aria-label]="(statusTranslatable || 'core.refresh') | translate">
<ion-icon slot="icon-only" name="fas-redo-alt" aria-hidden="true"></ion-icon>
<ion-icon slot="icon-only" name="fam-cloud-refresh" aria-hidden="true"></ion-icon>
</ion-button>
<!-- Downloaded status icon. -->
<ion-icon *ngIf="status == statusDownloaded && canTrustDownload" class="core-icon-downloaded ion-padding-horizontal" color="success"
name="cloud-done" [attr.aria-label]="(statusTranslatable || 'core.downloaded') | translate" role="status"></ion-icon>
name="fam-cloud-done" [attr.aria-label]="(statusTranslatable || 'core.downloaded') | translate" role="status"></ion-icon>
<ion-spinner *ngIf="status === statusDownloading" @coreShowHideAnimation
[attr.aria-label]="(statusTranslatable || 'core.downloading') | translate"></ion-spinner>

View File

@ -6,6 +6,7 @@
--margin-end: 0px;
--margin-vertical: 0px;
--icon-radius: var(--small-radius);
--filter: brightness(0) invert(1);
margin-top: var(--margin-vertical);
margin-bottom: var(--margin-vertical);
@ -20,7 +21,7 @@
&.#{$type} {
background-color: var(--activity#{$type});
img {
filter: brightness(0) invert(1);
filter: var(--filter);
}
}
}

View File

@ -93,10 +93,10 @@ export class CoreConstants {
static readonly NOT_DOWNLOADABLE = 'notdownloadable';
// Download / prefetch status icon.
static readonly ICON_DOWNLOADED = 'cloud-done';
static readonly ICON_DOWNLOADED = 'fam-cloud-done';
static readonly ICON_DOWNLOADING = 'spinner';
static readonly ICON_NOT_DOWNLOADED = 'cloud-download';
static readonly ICON_OUTDATED = 'fas-redo-alt';
static readonly ICON_NOT_DOWNLOADED = 'fas-cloud-download-alt';
static readonly ICON_OUTDATED = 'fam-cloud-refresh';
static readonly ICON_NOT_DOWNLOADABLE = '';
// General download and sync icons.

View File

@ -45,47 +45,58 @@ export class CoreFaIconDirective implements AfterViewInit, OnChanges {
* Detect icon name and use svg.
*/
async setIcon(): Promise<void> {
let library = 'ionic';
let library = '';
let iconName = this.name;
let font = 'ionicons';
const parts = iconName.split('-', 2);
if (parts.length == 2) {
switch (parts[0]) {
case 'far':
library = 'regular';
iconName = iconName.substring(4);
font = 'font-awesome';
break;
case 'fa':
case 'fas':
library = 'solid';
iconName = iconName.substring(parts[0].length + 1);
font = 'font-awesome';
break;
case 'fab':
library = 'brands';
iconName = iconName.substring(4);
font = 'font-awesome';
break;
case 'moodle':
library = 'moodle';
font = 'moodle';
break;
case 'fam':
library = 'font-awesome';
font = 'moodle';
break;
default:
break;
}
}
if (library != 'ionic') {
const src = `assets/fonts/font-awesome/${library}/${iconName}.svg`;
this.element.setAttribute('src', src);
this.element.classList.add('faicon');
if (CoreConstants.BUILD.isDevelopment || CoreConstants.BUILD.isTesting) {
try {
await Http.get(src, { responseType: 'text' }).toPromise();
} catch (error) {
this.logger.error(`Icon ${this.name} not found`);
}
}
} else {
if (font == 'ionicons') {
this.element.removeAttribute('src');
this.logger.warn(`Ionic icon ${this.name} detected`);
return;
}
return;
iconName = iconName.substring(parts[0].length + 1);
const src = `assets/fonts/${font}/${library}/${iconName}.svg`;
this.element.setAttribute('src', src);
this.element.classList.add('faicon');
if (CoreConstants.BUILD.isDevelopment || CoreConstants.BUILD.isTesting) {
try {
await Http.get(src, { responseType: 'text' }).toPromise();
} catch (error) {
this.logger.error(`Icon ${this.name} not found`);
}
}
}
/**

View File

@ -7,6 +7,8 @@
<core-format-text [text]="module.name" contextLevel="module" [component]="component" [componentId]="componentId"
[contextInstanceId]="module.id" [courseId]="courseId">
</core-format-text>
<ion-icon name="fas-lock" *ngIf="module.visible === 0 || module.uservisible === false"
[attr.aria-label]="'core.restricted' | translate"></ion-icon>
</h1>
<ng-content select="[title]"></ng-content>
</ion-label>
@ -20,22 +22,33 @@
</ion-item>
<ng-content select="[description]"></ng-content>
<ion-item class="ion-text-wrap" *ngIf="showCompletion && (module.dates?.length ||
(module.completiondata && module.completiondata.isautomatic && module.uservisible))">
<!-- Module completion. -->
<ion-item class="ion-text-wrap" *ngIf="showCompletion && module.completiondata && module.completiondata.isautomatic">
<ion-label>
<!-- Activity dates. -->
<div *ngIf="module.dates?.length" class="core-module-dates">
<p *ngFor="let date of module.dates">
<ion-icon name="fas-calendar" aria-hidden="true"></ion-icon><strong>{{ date.label }}</strong> {{ date.timestamp * 1000 |
coreFormatDate:'strftimedatetime' }}
</p>
</div>
<!-- Module completion. -->
<core-course-module-completion [completion]="module.completiondata" [moduleName]="module.name" [moduleId]="module.id"
[showCompletionConditions]="true">
</core-course-module-completion>
</ion-label>
</ion-item>
<div class="core-module-dates-availabilityinfo"
*ngIf="(module.dates && module.dates.length) || (showAvailabilityInfo && module.availabilityinfo)">
<!-- Activity dates. -->
<div *ngIf="module.dates && module.dates.length" class="core-module-dates">
<p *ngFor="let date of module.dates">
<ion-icon name="fas-calendar" aria-hidden="true"></ion-icon><strong>{{ date.label }}</strong> {{ date.timestamp
*
1000 | coreFormatDate:'strftimedatetime' }}
</p>
</div>
<!-- Availability info space. -->
<div class="core-module-availabilityinfo" *ngIf="showAvailabilityInfo">
<ion-icon name="fas-lock" [attr.aria-label]="'core.restricted' | translate"></ion-icon>
<core-format-text [text]="module.availabilityinfo" contextLevel="module" [contextInstanceId]="module.id" [courseId]="module.course">
</core-format-text>
</div>
</div>
<ng-content></ng-content>
<!-- Activity has something offline. -->

View File

@ -16,16 +16,38 @@
align-self: flex-start;
}
.core-module-dates {
h1 ion-icon {
color: var(--medium);
@include margin-horizontal(8px, null);
font-size: 80%;
}
.core-module-dates-availabilityinfo {
background: var(--light);
border-radius: var(--small-radius);
padding: 8px;
margin: 8px;
font-size: 90%;
ion-icon {
position: static;
@include margin-horizontal(null, 8px);
}
p,
ul {
margin-top: 4px;
margin-bottom: 4px;
}
}
.core-module-dates + .core-module-availabilityinfo {
border-top: 1px solid var(--stroke);
padding-top: 8px;
}
}
:host-context(.core-iframe-fullscreen) {

View File

@ -44,6 +44,8 @@ export class CoreCourseModuleInfoComponent implements OnInit {
@Input() description?: string | false; // The description to display. If false, no description will be shown.
@Input() expandDescription = false; // If the description should be expanded by default.
@Input() showAvailabilityInfo = false; // If show availability info on the box.
@Input() hasDataToSync = false; // If the activity has any data to be synced.
modicon = '';

View File

@ -58,7 +58,7 @@
<ion-item lines="full" class="ion-text-wrap">
<ion-label>
<h2>
<ion-icon name="cloud-done" aria-hidden="true"></ion-icon>
<ion-icon name="fam-cloud-done" aria-hidden="true"></ion-icon>
{{ 'addon.storagemanager.downloads' | translate }}
</h2>
</ion-label>
@ -81,7 +81,7 @@
</ion-item>
<ion-button fill="outline" expand="block" *ngIf="canPrefetch && displayOptions.displayPrefetch" class="ion-text-wrap"
(click)="prefetch()" color="primary" [disabled]="prefetchDisabled">
<ion-icon *ngIf="!prefetchLoading" name="cloud-done" slot="start" aria-hidden="true"></ion-icon>
<ion-icon *ngIf="!prefetchLoading" name="fam-cloud-done" slot="start" aria-hidden="true"></ion-icon>
<ion-spinner *ngIf="prefetchLoading" slot="start" aria-hidden="true"></ion-spinner>
<ion-label>
{{ 'core.download' | translate }}

View File

@ -17,6 +17,8 @@
</core-format-text>
<ion-icon name="fas-lock" *ngIf="module.visible === 0 || module.uservisible === false"
[attr.aria-label]="'core.restricted' | translate"></ion-icon>
<ion-icon [name]="prefetchStatusIcon" *ngIf="prefetchStatusIcon" color="success"
[attr.aria-label]="prefetchStatusText | translate"></ion-icon>
</p>
<div class="core-module-additional-info">

View File

@ -13,6 +13,7 @@
.core-module-title .item-heading ion-icon {
@include margin-horizontal(8px, null);
vertical-align: middle;
}
.core-module-buttons,

View File

@ -22,6 +22,12 @@ import {
} from '@features/course/services/course-helper';
import { CoreCourse } from '@features/course/services/course';
import { CoreCourseModuleDelegate, CoreCourseModuleHandlerButton } from '@features/course/services/module-delegate';
import {
CoreCourseModulePrefetchDelegate,
CoreCourseModulePrefetchHandler,
} from '@features/course/services/module-prefetch-delegate';
import { CoreConstants } from '@/core/constants';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
/**
* Component to display a module entry in a list of modules.
@ -47,11 +53,16 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
hasInfo = false;
showLegacyCompletion = false; // Whether to show module completion in the old format.
showManualCompletion = false; // Whether to show manual completion when completion conditions are disabled.
prefetchStatusIcon = ''; // Module prefetch status icon.
prefetchStatusText = ''; // Module prefetch status text.
protected prefetchHandler?: CoreCourseModulePrefetchHandler;
protected moduleStatusObserver?: CoreEventObserver;
/**
* Component being initialized.
* @inheritdoc
*/
ngOnInit(): void {
async ngOnInit(): Promise<void> {
this.modNameTranslated = CoreCourse.translateModuleName(this.module.modname) || '';
this.showLegacyCompletion = !CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.11');
this.checkShowManualCompletion();
@ -70,6 +81,60 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
(this.module.visible !== 0 && this.module.isStealth) ||
(this.module.availabilityinfo)
);
if (this.module.handlerData?.showDownloadButton) {
const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(this.module, this.module.course);
this.updateModuleStatus(status);
// Listen for changes on this module status, even if download isn't enabled.
this.prefetchHandler = CoreCourseModulePrefetchDelegate.getPrefetchHandlerFor(this.module.modname);
if (!this.prefetchHandler) {
return;
}
this.moduleStatusObserver = CoreEvents.on(CoreEvents.PACKAGE_STATUS_CHANGED, (data) => {
if (this.module.id != data.componentId || data.component != this.prefetchHandler?.component) {
return;
}
let status = data.status;
if (this.prefetchHandler.determineStatus) {
// Call determineStatus to get the right status to display.
status = this.prefetchHandler.determineStatus(this.module, status, true);
}
// Update the status.
this.updateModuleStatus(status);
}, CoreSites.getCurrentSiteId());
}
}
/**
* Show module status.
*
* @param prefetchstatus Module status.
*/
protected updateModuleStatus(prefetchstatus: string): void {
if (!prefetchstatus) {
return;
}
switch (prefetchstatus) {
case CoreConstants.OUTDATED:
this.prefetchStatusIcon = CoreConstants.ICON_OUTDATED;
this.prefetchStatusText = 'core.outdated';
break;
case CoreConstants.DOWNLOADED:
this.prefetchStatusIcon = CoreConstants.ICON_DOWNLOADED;
this.prefetchStatusText = 'core.downloaded';
break;
default:
this.prefetchStatusIcon = '';
this.prefetchStatusText = '';
break;
}
this.module.handlerData?.updateStatus?.(prefetchstatus);
}
/**
@ -109,10 +174,11 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy {
}
/**
* Component destroyed.
* @inheritdoc
*/
ngOnDestroy(): void {
this.module.handlerData?.onDestroy?.();
this.moduleStatusObserver?.off();
}
}

View File

@ -1,21 +1,22 @@
<div class="ion-padding">
<h2 *ngIf="!isDisabledInSite && isSupportedByTheApp">{{ 'core.whoops' | translate }}</h2>
<h2 *ngIf="isDisabledInSite || !isSupportedByTheApp">{{ 'core.uhoh' | translate }}</h2>
<p class="core-big" *ngIf="isDisabledInSite">{{ 'core.course.activitydisabled' | translate }}</p>
<p class="core-big" *ngIf="!isDisabledInSite && isSupportedByTheApp">
{{ 'core.course.activitynotyetviewablesiteupgradeneeded' | translate }}
</p>
<p class="core-big" *ngIf="!isDisabledInSite && !isSupportedByTheApp">
{{ 'core.course.activitynotyetviewableremoteaddon' | translate }}
</p>
<p *ngIf="isDisabledInSite || !isSupportedByTheApp"><strong>{{ 'core.course.askadmintosupport' | translate }}</strong></p>
<div *ngIf="module && module.url">
<p><strong>{{ 'core.course.useactivityonbrowser' | translate }}</strong></p>
<ion-button expand="block" [href]="module.url" core-link [showBrowserWarning]="false">
<ion-card class="core-danger-card">
<ion-item>
<ion-icon name="fas-exclamation-triangle" slot="start" aria-hidden="true"></ion-icon>
<ion-label>
<p><strong>{{ 'core.uhoh' | translate }} </strong>
<ng-container *ngIf="isDisabledInSite">{{ 'core.course.activitydisabled' | translate }}</ng-container>
<ng-container *ngIf="!isDisabledInSite">
{{ 'core.course.activitynotyetviewableremoteaddon' | translate }}
</ng-container>
</p>
</ion-label>
</ion-item>
</ion-card>
<ion-item lines="none" class="ion-text-wrap" *ngIf="module?.url && module?.uservisible">
<ion-label>
<p>{{ 'core.course.useactivityonbrowser' | translate }}</p>
<ion-button expand="block" [href]="module?.url" core-link [showBrowserWarning]="false">
{{ 'core.openinbrowser' | translate }}
<ion-icon name="fas-external-link-alt" slot="end" aria-hidden="true"></ion-icon>
</ion-button>
</div>
</div>
</ion-label>
</ion-item>

View File

@ -14,7 +14,6 @@
import { Component, Input, OnInit } from '@angular/core';
import { CoreCourse } from '@features/course/services/course';
import { CoreCourseModuleData } from '@features/course/services/course-helper';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
@ -27,12 +26,10 @@ import { CoreCourseModuleDelegate } from '@features/course/services/module-deleg
})
export class CoreCourseUnsupportedModuleComponent implements OnInit {
@Input() courseId?: number; // The course to module belongs to.
@Input() courseId?: number; // The course to module belongs to (unused).
@Input() module?: CoreCourseModuleData; // The module to render.
isDisabledInSite?: boolean;
isSupportedByTheApp?: boolean;
moduleName?: string;
isDisabledInSite = false; // It is implicit than if not disabled it will be unsupported.
/**
* Component being initialized.
@ -43,8 +40,6 @@ export class CoreCourseUnsupportedModuleComponent implements OnInit {
}
this.isDisabledInSite = CoreCourseModuleDelegate.isModuleDisabledInSite(this.module.modname);
this.isSupportedByTheApp = CoreCourseModuleDelegate.hasHandler(this.module.modname);
this.moduleName = CoreCourse.translateModuleName(this.module.modname);
}
}

View File

@ -1,10 +1,8 @@
{
"activitydisabled": "Your organisation has disabled this activity in the mobile app.",
"activitynotyetviewableremoteaddon": "Your organisation installed a plugin that is not yet supported.",
"activitynotyetviewablesiteupgradeneeded": "Your organisation's Moodle installation needs to be updated.",
"allsections": "All sections",
"aria:sectionprogress": "Section progress:",
"askadmintosupport": "Contact the site administrator and tell them you want to use this activity with the Moodle Mobile app.",
"availablespace": " You currently have about {{available}} free space.",
"cannotdeletewhiledownloading": "Files cannot be deleted while the activity is being downloaded. Please wait for the download to finish.",
"completion_automatic:done": "Done:",

View File

@ -11,8 +11,8 @@
</ion-title>
<ion-buttons slot="end">
<ion-button fill="clear" *ngIf="module.url" [href]="module.url" core-link [showBrowserWarning]="false" color="dark"
[attr.aria-label]="'core.openinbrowser' | translate">
<ion-button fill="clear" *ngIf="module.url && module.uservisible && !unsupported" [href]="module.url" core-link
[showBrowserWarning]="false" color="dark" [attr.aria-label]="'core.openinbrowser' | translate">
<ion-icon name="fas-external-link-alt" slot="icon-only" aria-hidden="true"></ion-icon>
</ion-button>
</ion-buttons>
@ -24,44 +24,24 @@
</ion-refresher>
<core-loading [hideUntil]="loaded">
<core-course-module-info [module]="module" [courseId]="courseId" [description]="module.description" [component]="module.modname"
[componentId]="module.id" [expandDescription]="true">
[componentId]="module.id" [expandDescription]="true" [showAvailabilityInfo]="true">
<div class="core-module-additional-info" title>
<ion-chip *ngIf="module.handlerData?.extraBadge" [color]="module.handlerData?.extraBadgeColor"
class="ion-text-wrap ion-text-start" [outline]="true">
<ion-label><span [innerHTML]="module.handlerData?.extraBadge"></span></ion-label>
</ion-chip>
<ion-item class="ion-text-wrap" *ngIf="module.handlerData?.extraBadge ||
(module.visible === 0 && (!section || section.visible)) ||
(module.visible !== 0 && module.isStealth) ||
module.availabilityinfo">
<ion-label>
<div class="ion-padding" *ngIf="module.handlerData?.extraBadge">
<ion-chip *ngIf="module.handlerData?.extraBadge" [color]="module.handlerData?.extraBadgeColor"
class="ion-text-wrap ion-text-start" [outline]="true">
<ion-label><span [innerHTML]="module.handlerData?.extraBadge"></span></ion-label>
</ion-chip>
</div>
<!-- Hidden badges -->
<div *ngIf="module.visible === 0 && (!section || section.visible)">
<ion-badge color="warning">
{{ 'core.course.hiddenfromstudents' | translate }}
</ion-badge>
</div>
<div *ngIf="module.visible !== 0 && module.isStealth">
<ion-badge color="warning">
{{ 'core.course.hiddenoncoursepage' | translate }}
</ion-badge>
</div>
<!-- Availability info -->
<div *ngIf="module.availabilityinfo" class="core-module-availabilityinfo">
<ion-icon name="fas-lock" [attr.aria-label]="'core.restricted' | translate"></ion-icon>
<core-format-text [text]="module.availabilityinfo" contextLevel="module" [contextInstanceId]="module.id"
[courseId]="module.course">
</core-format-text>
</div>
</ion-label>
</ion-item>
<core-course-unsupported-module *ngIf="unsupported" [module]="module" [courseId]="courseId"></core-course-unsupported-module>
<!-- Hidden badges -->
<ion-badge color="warning" *ngIf="module.visible === 0">
{{ 'core.course.hiddenfromstudents' | translate }}
</ion-badge>
<ion-badge color="warning" *ngIf="module.visible !== 0 && module.isStealth">
{{ 'core.course.hiddenoncoursepage' | translate }}
</ion-badge>
</div>
</core-course-module-info>
<core-course-unsupported-module *ngIf="unsupported" [module]="module"></core-course-unsupported-module>
</core-loading>
<core-course-module-navigation [hidden]="!loaded" [courseId]="courseId" [currentModule]="module"

View File

@ -37,6 +37,7 @@ export class CoreCourseModulePreviewPage implements OnInit {
courseId!: number;
loaded = false;
unsupported = false;
isDisabledInSite = false;
showManualCompletion = false;
protected debouncedUpdateModule?: () => void; // Update the module after a certain time.
@ -80,6 +81,8 @@ export class CoreCourseModulePreviewPage implements OnInit {
if (!this.unsupported) {
this.module.handlerData =
await CoreCourseModuleDelegate.getModuleDataFor(this.module.modname, this.module, this.courseId);
} else {
this.isDisabledInSite = CoreCourseModuleDelegate.isModuleDisabledInSite(this.module.modname);
}
this.title = this.module.name;

View File

@ -62,8 +62,8 @@
</ion-icon>
</span>
<ion-icon *ngIf="prefetchCourseData.downloadSucceeded" class="core-icon-downloaded" name="cloud-done" color="success"
role="status" [attr.aria-label]="'core.downloaded' | translate"></ion-icon>
<ion-icon *ngIf="prefetchCourseData.downloadSucceeded" class="core-icon-downloaded" name="fam-cloud-done"
color="success" role="status" [attr.aria-label]="'core.downloaded' | translate"></ion-icon>
</p>
<ion-chip color="brand" *ngIf="course.categoryname"

View File

@ -44,7 +44,7 @@
<!-- Downloaded icon. -->
<ion-icon *ngIf="downloadCourseEnabled && prefetchCourseData.downloadSucceeded && !showSpinner" class="core-icon-downloaded"
name="cloud-done" color="success" role="status" [attr.aria-label]="'core.downloaded' | translate"></ion-icon>
name="fam-cloud-done" color="success" role="status" [attr.aria-label]="'core.downloaded' | translate"></ion-icon>
<!-- Options menu. -->
<ion-button fill="clear" color="dark" (click)="showCourseOptionsMenu($event)" *ngIf="!showSpinner"

View File

@ -3,11 +3,12 @@
:host {
--header-background: var(--white);
--odd-cell-background: var(--light);
--even-cell-background: var(--white);
--odd-cell-hover: var(--gray-200);
--even-cell-background: var(--white);
--even-cell-hover: var(--light);
--icon-color: var(--gray-500);
--border-color: var(--stroke);
--mod-icon-filter: brightness(0);
.odd {
--cell-background: var(--odd-cell-background);
@ -23,11 +24,12 @@
:host-context(body.dark) {
--header-background: var(--gray-900);
--odd-cell-background: var(--medium);
--odd-cell-background: var(--gray-800);
--odd-cell-hover: var(--gray-600);
--even-cell-background: var(--gray-900);
--odd-cell-hover: var(--gray-500);
--even-cell-hover: var(--medium);
--even-cell-hover: var(--gray-700);
--icon-color: var(--gray-200);
--mod-icon-filter: brightness(0) invert(1);
}
.core-grades-table {
@ -80,8 +82,10 @@
}
core-mod-icon {
padding: 3px;
--size: 10px;
padding: 0px;
--size: 16px;
background: transparent;
--filter: var(--mod-icon-filter);
}

View File

@ -593,11 +593,11 @@ export class CoreGradesHelperProvider {
text = text.replace('%2F', '/').replace('%2f', '/');
if (text.indexOf('/agg_mean') > -1) {
row.itemtype = 'agg_mean';
row.image = 'assets/img/grades/agg_mean.svg';
row.icon = 'moodle-agg_mean';
row.iconAlt = Translate.instant('core.grades.aggregatemean');
} else if (text.indexOf('/agg_sum') > -1) {
row.itemtype = 'agg_sum';
row.image = 'assets/img/grades/agg_sum.svg';
row.icon = 'moodle-agg_sum';
row.iconAlt = Translate.instant('core.grades.aggregatesum');
} else if (text.indexOf('/outcomes') > -1 || text.indexOf('fa-tasks') > -1) {
row.itemtype = 'outcome';

View File

@ -5,6 +5,10 @@
</ion-button>
</core-navbar-buttons>
<!-- Activity info. -->
<core-course-module-info [module]="module" [courseId]="courseId" [component]="component" [componentId]="module.id">
</core-course-module-info>
<core-site-plugins-plugin-content *ngIf="component && method" [component]="component" [method]="method" [args]="args"
[initResult]="initResult" [data]="jsData" [pageTitle]="pageTitle" [preSets]="preSets" (onContentLoaded)="contentLoaded($event)"
(onLoadingContent)="contentLoading()">

View File

@ -1,4 +1,4 @@
<ion-header>
<ion-header collapsible>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button [text]="'core.back' | translate"></ion-back-button>

View File

@ -342,7 +342,6 @@
"whatisyourage": "What is your age?",
"wheredoyoulive": "In which country do you live?",
"whoissiteadmin": "\"Site Administrators\" are the people who manage the Moodle at your school/university/company or learning organisation. If you don't know how to contact them, please contact your teachers/trainers.",
"whoops": "Oops!",
"whyisthishappening": "Why is this happening?",
"whyisthisrequired": "Why is this required?",
"wsfunctionnotavailable": "The web service function is not available.",