diff --git a/scripts/langindex.json b/scripts/langindex.json index e1beb3757..092fbfb2c 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -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", diff --git a/src/addons/calendar/components/calendar/calendar.scss b/src/addons/calendar/components/calendar/calendar.scss index 35cd3ccbd..057745e3b 100644 --- a/src/addons/calendar/components/calendar/calendar.scss +++ b/src/addons/calendar/components/calendar/calendar.scss @@ -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; + } } } diff --git a/src/addons/storagemanager/services/handlers/course-menu.ts b/src/addons/storagemanager/services/handlers/course-menu.ts index 7aa36c6de..74dc8f409 100644 --- a/src/addons/storagemanager/services/handlers/course-menu.ts +++ b/src/addons/storagemanager/services/handlers/course-menu.ts @@ -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: { diff --git a/src/assets/fonts/moodle/font-awesome/cloud-done.svg b/src/assets/fonts/moodle/font-awesome/cloud-done.svg new file mode 100644 index 000000000..9f80a5392 --- /dev/null +++ b/src/assets/fonts/moodle/font-awesome/cloud-done.svg @@ -0,0 +1 @@ + diff --git a/src/assets/fonts/moodle/font-awesome/cloud-refresh.svg b/src/assets/fonts/moodle/font-awesome/cloud-refresh.svg new file mode 100644 index 000000000..2e336c793 --- /dev/null +++ b/src/assets/fonts/moodle/font-awesome/cloud-refresh.svg @@ -0,0 +1 @@ + diff --git a/src/assets/fonts/moodle/moodle/agg_mean.svg b/src/assets/fonts/moodle/moodle/agg_mean.svg new file mode 100644 index 000000000..c10adf743 --- /dev/null +++ b/src/assets/fonts/moodle/moodle/agg_mean.svg @@ -0,0 +1 @@ + diff --git a/src/assets/fonts/moodle/moodle/agg_sum.svg b/src/assets/fonts/moodle/moodle/agg_sum.svg new file mode 100644 index 000000000..6108dc6df --- /dev/null +++ b/src/assets/fonts/moodle/moodle/agg_sum.svg @@ -0,0 +1 @@ + diff --git a/src/assets/img/grades/agg_mean.svg b/src/assets/img/grades/agg_mean.svg deleted file mode 100644 index ac4368d15..000000000 --- a/src/assets/img/grades/agg_mean.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/assets/img/grades/agg_sum.svg b/src/assets/img/grades/agg_sum.svg deleted file mode 100644 index 9ebd5c23d..000000000 --- a/src/assets/img/grades/agg_sum.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/core/components/download-refresh/core-download-refresh.html b/src/core/components/download-refresh/core-download-refresh.html index f9580727f..21d380920 100644 --- a/src/core/components/download-refresh/core-download-refresh.html +++ b/src/core/components/download-refresh/core-download-refresh.html @@ -2,19 +2,19 @@ - + - + + name="fam-cloud-done" [attr.aria-label]="(statusTranslatable || 'core.downloaded') | translate" role="status"> diff --git a/src/core/components/mod-icon/mod-icon.scss b/src/core/components/mod-icon/mod-icon.scss index 45b236cb6..f5f7dbdcb 100644 --- a/src/core/components/mod-icon/mod-icon.scss +++ b/src/core/components/mod-icon/mod-icon.scss @@ -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); } } } diff --git a/src/core/constants.ts b/src/core/constants.ts index 7113598cc..0fcd0ddd3 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -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. diff --git a/src/core/directives/fa-icon.ts b/src/core/directives/fa-icon.ts index 8a262dd45..9de2de27a 100644 --- a/src/core/directives/fa-icon.ts +++ b/src/core/directives/fa-icon.ts @@ -45,47 +45,58 @@ export class CoreFaIconDirective implements AfterViewInit, OnChanges { * Detect icon name and use svg. */ async setIcon(): Promise { - 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`); + } + } } /** diff --git a/src/core/features/course/components/module-info/core-course-module-info.html b/src/core/features/course/components/module-info/core-course-module-info.html index 256f2c789..f6f50dd35 100644 --- a/src/core/features/course/components/module-info/core-course-module-info.html +++ b/src/core/features/course/components/module-info/core-course-module-info.html @@ -7,6 +7,8 @@ + @@ -20,22 +22,33 @@ - + + - -
-

- {{ date.label }} {{ date.timestamp * 1000 | - coreFormatDate:'strftimedatetime' }} -

-
-
+ +
+ +
+

+ {{ date.label }} {{ date.timestamp + * + 1000 | coreFormatDate:'strftimedatetime' }} +

+
+ +
+ + + +
+
+ diff --git a/src/core/features/course/components/module-info/course-module-info.scss b/src/core/features/course/components/module-info/course-module-info.scss index 46d6c970c..75e76aedd 100644 --- a/src/core/features/course/components/module-info/course-module-info.scss +++ b/src/core/features/course/components/module-info/course-module-info.scss @@ -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) { diff --git a/src/core/features/course/components/module-info/module-info.ts b/src/core/features/course/components/module-info/module-info.ts index f57b95e7e..bd1fe0780 100644 --- a/src/core/features/course/components/module-info/module-info.ts +++ b/src/core/features/course/components/module-info/module-info.ts @@ -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 = ''; diff --git a/src/core/features/course/components/module-summary/module-summary.html b/src/core/features/course/components/module-summary/module-summary.html index 820e944ef..6c60ba185 100644 --- a/src/core/features/course/components/module-summary/module-summary.html +++ b/src/core/features/course/components/module-summary/module-summary.html @@ -58,7 +58,7 @@

- + {{ 'addon.storagemanager.downloads' | translate }}

@@ -81,7 +81,7 @@
- + {{ 'core.download' | translate }} diff --git a/src/core/features/course/components/module/core-course-module.html b/src/core/features/course/components/module/core-course-module.html index e1048efe5..579f101e5 100644 --- a/src/core/features/course/components/module/core-course-module.html +++ b/src/core/features/course/components/module/core-course-module.html @@ -17,6 +17,8 @@ +

diff --git a/src/core/features/course/components/module/module.scss b/src/core/features/course/components/module/module.scss index da557da41..c4fd942bd 100644 --- a/src/core/features/course/components/module/module.scss +++ b/src/core/features/course/components/module/module.scss @@ -13,6 +13,7 @@ .core-module-title .item-heading ion-icon { @include margin-horizontal(8px, null); + vertical-align: middle; } .core-module-buttons, diff --git a/src/core/features/course/components/module/module.ts b/src/core/features/course/components/module/module.ts index 0761f2ad6..478dc0b62 100644 --- a/src/core/features/course/components/module/module.ts +++ b/src/core/features/course/components/module/module.ts @@ -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 { 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(); } } diff --git a/src/core/features/course/components/unsupported-module/core-course-unsupported-module.html b/src/core/features/course/components/unsupported-module/core-course-unsupported-module.html index 4fd22a35d..f8b7bd52f 100644 --- a/src/core/features/course/components/unsupported-module/core-course-unsupported-module.html +++ b/src/core/features/course/components/unsupported-module/core-course-unsupported-module.html @@ -1,21 +1,22 @@ -
-

{{ 'core.whoops' | translate }}

-

{{ 'core.uhoh' | translate }}

- -

{{ 'core.course.activitydisabled' | translate }}

-

- {{ 'core.course.activitynotyetviewablesiteupgradeneeded' | translate }} -

-

- {{ 'core.course.activitynotyetviewableremoteaddon' | translate }} -

-

{{ 'core.course.askadmintosupport' | translate }}

- -
-

{{ 'core.course.useactivityonbrowser' | translate }}

- + + + + +

{{ 'core.uhoh' | translate }} + {{ 'core.course.activitydisabled' | translate }} + + {{ 'core.course.activitynotyetviewableremoteaddon' | translate }} + +

+
+
+
+ + +

{{ 'core.course.useactivityonbrowser' | translate }}

+ {{ 'core.openinbrowser' | translate }} -
-
+ + diff --git a/src/core/features/course/components/unsupported-module/unsupported-module.ts b/src/core/features/course/components/unsupported-module/unsupported-module.ts index 5df40ab8f..15c3e7902 100644 --- a/src/core/features/course/components/unsupported-module/unsupported-module.ts +++ b/src/core/features/course/components/unsupported-module/unsupported-module.ts @@ -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); } } diff --git a/src/core/features/course/lang.json b/src/core/features/course/lang.json index 36f792de9..554f22f43 100644 --- a/src/core/features/course/lang.json +++ b/src/core/features/course/lang.json @@ -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:", diff --git a/src/core/features/course/pages/module-preview/module-preview.html b/src/core/features/course/pages/module-preview/module-preview.html index eb42c4b54..010351d87 100644 --- a/src/core/features/course/pages/module-preview/module-preview.html +++ b/src/core/features/course/pages/module-preview/module-preview.html @@ -11,8 +11,8 @@ - + @@ -24,44 +24,24 @@ + [componentId]="module.id" [expandDescription]="true" [showAvailabilityInfo]="true"> +
+ + + - - -
- - - -
- - -
- - {{ 'core.course.hiddenfromstudents' | translate }} - -
-
- - {{ 'core.course.hiddenoncoursepage' | translate }} - -
- - -
- - - -
-
-
- - + + + {{ 'core.course.hiddenfromstudents' | translate }} + + + {{ 'core.course.hiddenoncoursepage' | translate }} + +
+ +
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; diff --git a/src/core/features/courses/components/course-list-item/core-courses-course-list-item.html b/src/core/features/courses/components/course-list-item/core-courses-course-list-item.html index c63576239..39bf2b5e2 100644 --- a/src/core/features/courses/components/course-list-item/core-courses-course-list-item.html +++ b/src/core/features/courses/components/course-list-item/core-courses-course-list-item.html @@ -62,8 +62,8 @@ - +

+ name="fam-cloud-done" color="success" role="status" [attr.aria-label]="'core.downloaded' | translate"> -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'; diff --git a/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html b/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html index d4f915a10..94574fcbe 100644 --- a/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html +++ b/src/core/features/siteplugins/components/module-index/core-siteplugins-module-index.html @@ -5,6 +5,10 @@ + + + + diff --git a/src/core/features/siteplugins/pages/module-index/module-index.html b/src/core/features/siteplugins/pages/module-index/module-index.html index 7bbd67a91..58f3efc7c 100644 --- a/src/core/features/siteplugins/pages/module-index/module-index.html +++ b/src/core/features/siteplugins/pages/module-index/module-index.html @@ -1,4 +1,4 @@ - + diff --git a/src/core/lang.json b/src/core/lang.json index 5b58a7e07..2a72d84e7 100644 --- a/src/core/lang.json +++ b/src/core/lang.json @@ -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.",