diff --git a/scripts/langindex.json b/scripts/langindex.json index 7dbfa28cf..2ad44374b 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1582,6 +1582,7 @@ "core.course.completion_setby:auto:todo": "course", "core.course.completion_setby:manual:done": "course", "core.course.completion_setby:manual:markdone": "course", + "core.course.completionmenuitem": "completion", "core.course.completionrequirements": "course", "core.course.confirmdownload": "local_moodlemobileapp", "core.course.confirmdownloadunknownsize": "local_moodlemobileapp", @@ -1620,6 +1621,7 @@ "core.course.relativedatessubmissionduedatebefore": "course", "core.course.section": "moodle", "core.course.startdate": "moodle", + "core.course.studentsmust": "completion", "core.course.thisweek": "format_weeks/currentsection", "core.course.todo": "completion", "core.course.tour_navigation_course_index_student_content": "tool_usertours", @@ -1628,6 +1630,7 @@ "core.course.viewcourse": "block_timeline", "core.course.warningmanualcompletionmodified": "local_moodlemobileapp", "core.course.warningofflinemanualcompletiondeleted": "local_moodlemobileapp", + "core.course.youmust": "completion", "core.coursedetails": "moodle", "core.coursenogroups": "local_moodlemobileapp", "core.courses.addtofavourites": "block_myoverview", @@ -2313,8 +2316,9 @@ "core.scanqr": "local_moodlemobileapp", "core.scrollbackward": "local_moodlemobileapp", "core.scrollforward": "local_moodlemobileapp", - "core.search.allcourses": "search", + "core.search": "moodle", "core.search.allcategories": "local_moodlemobileapp", + "core.search.allcourses": "search", "core.search.empty": "local_moodlemobileapp", "core.search.filtercategories": "local_moodlemobileapp", "core.search.filtercourses": "local_moodlemobileapp", @@ -2323,7 +2327,6 @@ "core.search.noresults": "local_moodlemobileapp", "core.search.noresultshelp": "local_moodlemobileapp", "core.search.resultby": "local_moodlemobileapp", - "core.search": "moodle", "core.searching": "local_moodlemobileapp", "core.searchresults": "moodle", "core.sec": "moodle", diff --git a/src/addons/block/blogtags/components/blogtags/blogtags.scss b/src/addons/block/blogtags/components/blogtags/blogtags.scss index 9554c90a9..85844a8fd 100644 --- a/src/addons/block/blogtags/components/blogtags/blogtags.scss +++ b/src/addons/block/blogtags/components/blogtags/blogtags.scss @@ -22,7 +22,7 @@ contain: content; vertical-align: baseline; text-decoration: none; - border-radius: var(--small-radius); + border-radius: var(--radius-xs); } .s20 { font-size: 1.5em; diff --git a/src/addons/block/tags/components/tags/tags.scss b/src/addons/block/tags/components/tags/tags.scss index e6141bf4e..5580a6bdd 100644 --- a/src/addons/block/tags/components/tags/tags.scss +++ b/src/addons/block/tags/components/tags/tags.scss @@ -24,7 +24,7 @@ contain: content; vertical-align: baseline; text-decoration: none; - border-radius: var(--small-radius); + border-radius: var(--radius-xs); } .s20 { font-size: 2.7em; diff --git a/src/addons/block/timeline/components/events/events.scss b/src/addons/block/timeline/components/events/events.scss index f55d845f1..07634e1d5 100644 --- a/src/addons/block/timeline/components/events/events.scss +++ b/src/addons/block/timeline/components/events/events.scss @@ -23,7 +23,7 @@ h4.core-bold { } core-mod-icon { - padding: 8px; + --padding: 8px; --margin-end: 0.5rem; --margin-vertical: 0; } diff --git a/src/addons/mod/assign/tests/behat/snapshots/test-basic-usage-of-assignment-activity-in-app-editadd-submission-online-text--add-new-attempt-from-previous-submission--submit-for-grading_8.png b/src/addons/mod/assign/tests/behat/snapshots/test-basic-usage-of-assignment-activity-in-app-editadd-submission-online-text--add-new-attempt-from-previous-submission--submit-for-grading_8.png index 9c5a9dc4d..c5c9d3eca 100644 Binary files a/src/addons/mod/assign/tests/behat/snapshots/test-basic-usage-of-assignment-activity-in-app-editadd-submission-online-text--add-new-attempt-from-previous-submission--submit-for-grading_8.png and b/src/addons/mod/assign/tests/behat/snapshots/test-basic-usage-of-assignment-activity-in-app-editadd-submission-online-text--add-new-attempt-from-previous-submission--submit-for-grading_8.png differ diff --git a/src/addons/mod/forum/services/handlers/module.ts b/src/addons/mod/forum/services/handlers/module.ts index 7917e8df8..005ccce63 100644 --- a/src/addons/mod/forum/services/handlers/module.ts +++ b/src/addons/mod/forum/services/handlers/module.ts @@ -21,7 +21,6 @@ import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/ import { CoreConstants, ModPurpose } from '@/core/constants'; import { CoreModuleHandlerBase } from '@features/course/classes/module-base-handler'; import { CoreCourseModuleData } from '@features/course/services/course-helper'; -import { CoreIonicColorNames } from '@singletons/colors'; /** * Handler to support forum modules. @@ -57,7 +56,6 @@ export class AddonModForumModuleHandlerService extends CoreModuleHandlerBase imp const data = await super.getData(module, courseId); if ('afterlink' in module && !!module.afterlink) { - data.extraBadgeColor = undefined; const match = />(\d+)[^<]+/.exec(module.afterlink); data.extraBadge = match ? Translate.instant('addon.mod_forum.unreadpostsnumber', { $a : match[1] }) : ''; } else { @@ -113,7 +111,6 @@ export class AddonModForumModuleHandlerService extends CoreModuleHandlerBase imp } data.extraBadge = Translate.instant('core.loading'); - data.extraBadgeColor = CoreIonicColorNames.DARK; try { // Handle unread posts. @@ -122,7 +119,6 @@ export class AddonModForumModuleHandlerService extends CoreModuleHandlerBase imp siteId, }); - data.extraBadgeColor = undefined; data.extraBadge = forum.unreadpostscount ? Translate.instant( 'addon.mod_forum.unreadpostsnumber', @@ -131,7 +127,6 @@ export class AddonModForumModuleHandlerService extends CoreModuleHandlerBase imp : ''; } catch { // Ignore errors. - data.extraBadgeColor = undefined; data.extraBadge = ''; } } diff --git a/src/addons/mod/forum/tests/behat/snapshots/test-basic-usage-of-forum-activity-in-app-reply-a-post_9.png b/src/addons/mod/forum/tests/behat/snapshots/test-basic-usage-of-forum-activity-in-app-reply-a-post_9.png index 73d12cb73..5ed3c24af 100644 Binary files a/src/addons/mod/forum/tests/behat/snapshots/test-basic-usage-of-forum-activity-in-app-reply-a-post_9.png and b/src/addons/mod/forum/tests/behat/snapshots/test-basic-usage-of-forum-activity-in-app-reply-a-post_9.png differ diff --git a/src/addons/mod/lti/services/handlers/module.ts b/src/addons/mod/lti/services/handlers/module.ts index 65becc928..574758a90 100644 --- a/src/addons/mod/lti/services/handlers/module.ts +++ b/src/addons/mod/lti/services/handlers/module.ts @@ -59,7 +59,7 @@ export class AddonModLtiModuleHandlerService extends CoreModuleHandlerBase imple ): Promise { const data = await super.getData(module, courseId, sectionId, forCoursePage); data.showDownloadButton = false; - data.buttons = [{ + data.button = { icon: 'fas-up-right-from-square', label: 'addon.mod_lti.launchactivity', action: (event: Event, module: CoreCourseModuleData, courseId: number): void => { @@ -68,7 +68,7 @@ export class AddonModLtiModuleHandlerService extends CoreModuleHandlerBase imple CoreCourse.storeModuleViewed(courseId, module.id); }, - }]; + }; return data; } diff --git a/src/addons/mod/quiz/tests/behat/snapshots/attempt-a-quiz-in-app-submit-a-quiz--review-a-quiz-attempt_26.png b/src/addons/mod/quiz/tests/behat/snapshots/attempt-a-quiz-in-app-submit-a-quiz--review-a-quiz-attempt_26.png index 1ed74c01b..ed8787ff3 100644 Binary files a/src/addons/mod/quiz/tests/behat/snapshots/attempt-a-quiz-in-app-submit-a-quiz--review-a-quiz-attempt_26.png and b/src/addons/mod/quiz/tests/behat/snapshots/attempt-a-quiz-in-app-submit-a-quiz--review-a-quiz-attempt_26.png differ diff --git a/src/addons/mod/quiz/tests/behat/snapshots/attempt-a-quiz-in-app-submit-a-quiz--review-a-quiz-attempt_36.png b/src/addons/mod/quiz/tests/behat/snapshots/attempt-a-quiz-in-app-submit-a-quiz--review-a-quiz-attempt_36.png index e7da98964..31ca59293 100644 Binary files a/src/addons/mod/quiz/tests/behat/snapshots/attempt-a-quiz-in-app-submit-a-quiz--review-a-quiz-attempt_36.png and b/src/addons/mod/quiz/tests/behat/snapshots/attempt-a-quiz-in-app-submit-a-quiz--review-a-quiz-attempt_36.png differ diff --git a/src/addons/mod/resource/services/handlers/module.ts b/src/addons/mod/resource/services/handlers/module.ts index 8d112fb72..b4594ca6a 100644 --- a/src/addons/mod/resource/services/handlers/module.ts +++ b/src/addons/mod/resource/services/handlers/module.ts @@ -73,14 +73,14 @@ export class AddonModResourceModuleHandlerService extends CoreModuleHandlerBase const handlerData = await super.getData(module, courseId, sectionId, forCoursePage); handlerData.updateStatus = (status) => { - if (!handlerData.buttons) { + if (!handlerData.button) { return; } - handlerData.buttons[0].hidden = status !== CoreConstants.DOWNLOADED || + handlerData.button.hidden = status !== CoreConstants.DOWNLOADED || AddonModResourceHelper.isDisplayedInIframe(module); }; - handlerData.buttons = [{ + handlerData.button = { hidden: true, icon: openWithPicker ? 'fas-share-from-square' : 'fas-file', label: module.name + ': ' + Translate.instant(openWithPicker ? 'core.openwith' : 'addon.mod_resource.openthefile'), @@ -92,7 +92,7 @@ export class AddonModResourceModuleHandlerService extends CoreModuleHandlerBase CoreCourse.storeModuleViewed(courseId, module.id); } }, - }]; + }; this.getResourceData(module, courseId, handlerData).then((extra) => { handlerData.extraBadge = extra; @@ -144,11 +144,11 @@ export class AddonModResourceModuleHandlerService extends CoreModuleHandlerBase // Check if the button needs to be shown or not. promises.push(this.hideOpenButton(module).then((hideOpenButton) => { - if (!handlerData.buttons) { + if (!handlerData.button) { return; } - handlerData.buttons[0].hidden = hideOpenButton; + handlerData.button.hidden = hideOpenButton; return; })); @@ -166,58 +166,70 @@ export class AddonModResourceModuleHandlerService extends CoreModuleHandlerBase await Promise.all(promises); - const extra: string[] = []; - if (module.contentsinfo) { // No need to use the list of files. - extra.push(CoreTextUtils.cleanTags(module.afterlink)); - } else if (module.contents && module.contents[0]) { - const files = module.contents; - const file = files[0]; + return CoreTextUtils.cleanTags(module.afterlink); + } - if (options.showsize) { - const size = options.filedetails - ? options.filedetails.size - : files.reduce((result, file) => result + (file.filesize || 0), 0); + if (!module.contents || !module.contents[0]) { + return ''; + } - extra.push(CoreTextUtils.bytesToSize(size, 1)); - } + const extra: string[] = []; + const files = module.contents; + const mainFile = files[0]; - if (options.showtype) { - // We should take it from options.filedetails.size if available but it's already translated. - extra.push(CoreMimetypeUtils.getMimetypeDescription(file)); - } + if (options.showsize) { + const size = options.filedetails + ? options.filedetails.size + : files.reduce((result, file) => result + (file.filesize || 0), 0); - if (options.showdate) { - const timecreated = 'timecreated' in file ? file.timecreated : 0; + extra.push(CoreTextUtils.bytesToSize(size, 1)); + } - if (options.filedetails && options.filedetails.modifieddate) { - extra.push(Translate.instant( - 'addon.mod_resource.modifieddate', - { $a: CoreTimeUtils.userDate(options.filedetails.modifieddate * 1000, 'core.strftimedatetimeshort') }, - )); - } else if (options.filedetails && options.filedetails.uploadeddate) { - extra.push(Translate.instant( - 'addon.mod_resource.uploadeddate', - { $a: CoreTimeUtils.userDate(options.filedetails.uploadeddate * 1000, 'core.strftimedatetimeshort') }, - )); - } else if ((file.timemodified || 0) > timecreated + CoreConstants.SECONDS_MINUTE * 5) { - /* Modified date may be up to several minutes later than uploaded date just because - teacher did not submit the form promptly. Give teacher up to 5 minutes to do it. */ - extra.push(Translate.instant( - 'addon.mod_resource.modifieddate', - { $a: CoreTimeUtils.userDate((file.timemodified || 0) * 1000, 'core.strftimedatetimeshort') }, - )); - } else { - extra.push(Translate.instant( - 'addon.mod_resource.uploadeddate', - { $a: CoreTimeUtils.userDate(timecreated * 1000, 'core.strftimedatetimeshort') }, - )); - } + if (options.showtype) { + // We should take it from options.filedetails.size if available but it's already translated. + extra.push(CoreMimetypeUtils.getMimetypeDescription(mainFile)); + } + + if (options.showdate) { + const timecreated = 'timecreated' in mainFile ? mainFile.timecreated : 0; + + if (options.filedetails && options.filedetails.modifieddate) { + extra.push(Translate.instant( + 'addon.mod_resource.modifieddate', + { $a: CoreTimeUtils.userDate(options.filedetails.modifieddate * 1000, 'core.strftimedatetimeshort') }, + )); + } else if (options.filedetails && options.filedetails.uploadeddate) { + extra.push(Translate.instant( + 'addon.mod_resource.uploadeddate', + { $a: CoreTimeUtils.userDate(options.filedetails.uploadeddate * 1000, 'core.strftimedatetimeshort') }, + )); + } else if ((mainFile.timemodified || 0) > timecreated + CoreConstants.SECONDS_MINUTE * 5) { + /* Modified date may be up to several minutes later than uploaded date just because + teacher did not submit the form promptly. Give teacher up to 5 minutes to do it. */ + extra.push(Translate.instant( + 'addon.mod_resource.modifieddate', + { $a: CoreTimeUtils.userDate((mainFile.timemodified || 0) * 1000, 'core.strftimedatetimeshort') }, + )); + } else { + extra.push(Translate.instant( + 'addon.mod_resource.uploadeddate', + { $a: CoreTimeUtils.userDate(timecreated * 1000, 'core.strftimedatetimeshort') }, + )); } } - return extra.join(' '); + return extra.join(' ยท '); + } + + /** + * @inheritdoc + */ + async manualCompletionAlwaysShown(module: CoreCourseModuleData): Promise { + const hideButton = await this.hideOpenButton(module); + + return !hideButton; } /** diff --git a/src/addons/mod/url/services/handlers/module.ts b/src/addons/mod/url/services/handlers/module.ts index b0c0f8be3..02e9c557f 100644 --- a/src/addons/mod/url/services/handlers/module.ts +++ b/src/addons/mod/url/services/handlers/module.ts @@ -93,20 +93,20 @@ export class AddonModUrlModuleHandlerService extends CoreModuleHandlerBase imple modal.dismiss(); } }, - buttons: [{ + button: { hidden: true, // Hide it until we calculate if it should be displayed or not. icon: 'fas-link', label: 'core.openmodinbrowser', action: (event: Event, module: CoreCourseModuleData, courseId: number): void => { openUrl(module, courseId); }, - }], + }, }; const hideButton = await CoreUtils.ignoreErrors(this.hideLinkButton(module)); - if (handlerData.buttons && hideButton !== undefined) { - handlerData.buttons[0].hidden = hideButton; + if (handlerData.button && hideButton !== undefined) { + handlerData.button.hidden = hideButton; } try { diff --git a/src/addons/notifications/notifications.scss b/src/addons/notifications/notifications.scss index 2f144dd2a..60caa6312 100644 --- a/src/addons/notifications/notifications.scss +++ b/src/addons/notifications/notifications.scss @@ -11,12 +11,12 @@ position: absolute; right: -4px; bottom: -4px; - padding: 0.2rem; + --padding: 0.2rem; } .core-avatar-extra-img { background: var(--background-color); - border-radius: var(--medium-radius); + border-radius: var(--radius-sm); img { max-width: var(--extra-icon-size); max-height: var(--extra-icon-size); @@ -39,7 +39,7 @@ } padding: 0.7rem; background: var(--background-color); - border-radius: var(--small-radius); + border-radius: var(--radius-xs); } .core-notification-icon { diff --git a/src/addons/notifications/pages/list/list.scss b/src/addons/notifications/pages/list/list.scss index 28bc96fc3..cc83595d1 100644 --- a/src/addons/notifications/pages/list/list.scss +++ b/src/addons/notifications/pages/list/list.scss @@ -39,7 +39,7 @@ ion-item { div.core-notification-icon, core-mod-icon.core-notification-icon { - padding: 8px; + --padding: 8px; max-width: var(--core-avatar-size); max-height: var(--core-avatar-size); } diff --git a/src/addons/storagemanager/pages/course-storage/course-storage.scss b/src/addons/storagemanager/pages/course-storage/course-storage.scss index cf6193060..becd7cebb 100644 --- a/src/addons/storagemanager/pages/course-storage/course-storage.scss +++ b/src/addons/storagemanager/pages/course-storage/course-storage.scss @@ -51,7 +51,7 @@ ion-badge { ion-item core-mod-icon { --size: 18px; - padding: 9px; + --padding: 9px; --margin-vertical: 8px; --margin-end: 8px; } diff --git a/src/core/components/combobox/combobox.scss b/src/core/components/combobox/combobox.scss index e270c8528..bb19aa559 100644 --- a/src/core/components/combobox/combobox.scss +++ b/src/core/components/combobox/combobox.scss @@ -131,25 +131,4 @@ ion-button { ion-icon { margin: var(--icon-margin); } - - .select-icon { - margin: var(--icon-margin); - width: 19px; - height: 19px; - position: relative; - - .select-icon-inner { - left: 5px; - top: 50%; - margin-top: -2px; - position: absolute; - width: 0px; - height: 0px; - color: currentcolor; - pointer-events: none; - border-top: 5px solid; - border-right: 5px solid transparent; - border-left: 5px solid transparent; - } - } } diff --git a/src/core/components/error-info/error-info.scss b/src/core/components/error-info/error-info.scss index 97922518f..05c0a69dc 100644 --- a/src/core/components/error-info/error-info.scss +++ b/src/core/components/error-info/error-info.scss @@ -1,6 +1,6 @@ .core-error-info { background: var(--gray-200); - border-radius: var(--small-radius); + border-radius: var(--radius-xs); font-size: var(--font-size-sm); color: var(--gray-900); diff --git a/src/core/components/message/message.scss b/src/core/components/message/message.scss index 70e80d774..3ece454ac 100644 --- a/src/core/components/message/message.scss +++ b/src/core/components/message/message.scss @@ -18,7 +18,7 @@ position: relative; border: 0; - border-radius: var(--medium-radius); + border-radius: var(--radius-sm); margin: 8px; width: 90%; max-width: var(--list-item-max-width); diff --git a/src/core/components/mod-icon/mod-icon.scss b/src/core/components/mod-icon/mod-icon.scss index 5ab0db1f0..8a6085ae4 100644 --- a/src/core/components/mod-icon/mod-icon.scss +++ b/src/core/components/mod-icon/mod-icon.scss @@ -2,10 +2,11 @@ :host { display: inline-block; - --size: var(--module-icon-size); + --size: var(--module-icon-size, 24px); + --padding: var(--module-icon-padding, 8px); + --icon-radius: var(--module-icon-radius, var(--radius-xs)); --margin-end: 0px; --margin-vertical: 0px; - --icon-radius: var(--small-radius); --filter: brightness(0) invert(1); margin-top: var(--margin-vertical); @@ -13,7 +14,7 @@ @include margin-horizontal(null, var(--margin-end)); border-radius: var(--icon-radius); - padding: 0.7rem; + padding: var(--padding); background-color: $gray-100; line-height: var(--size); @@ -57,5 +58,4 @@ img { :host-context(ion-card ion-item) { --margin-vertical: 12px; --margin-end: 12px; - --icon-radius: var(--module-icon-radius); } diff --git a/src/core/components/send-message-form/send-message-form.scss b/src/core/components/send-message-form/send-message-form.scss index 1607e186e..89f1fac85 100644 --- a/src/core/components/send-message-form/send-message-form.scss +++ b/src/core/components/send-message-form/send-message-form.scss @@ -3,7 +3,7 @@ --textarea-color: var(--core-input-text); --textarea-border-width: var(--core-input-border-width); --textarea-border-color: var(--core-input-stroke); - --textarea-radius: var(--huge-radius); + --textarea-radius: var(--radius-xl); form { position: relative; diff --git a/src/core/components/sheet-modal/sheet-modal.scss b/src/core/components/sheet-modal/sheet-modal.scss index d80185bab..a03fd57e8 100644 --- a/src/core/components/sheet-modal/sheet-modal.scss +++ b/src/core/components/sheet-modal/sheet-modal.scss @@ -17,7 +17,7 @@ } .sheet-modal--wrapper { - border-radius: var(--big-radius) var(--big-radius) 0 0; + border-radius: var(--radius-lg) var(--radius-lg) 0 0; @include padding(24px, 16px, 24px, 16px); background-color: var(--ion-overlay-background-color, var(--ion-background-color, #fff)); diff --git a/src/core/components/timer/timer.scss b/src/core/components/timer/timer.scss index aa91db278..0a7bf56da 100644 --- a/src/core/components/timer/timer.scss +++ b/src/core/components/timer/timer.scss @@ -20,7 +20,7 @@ div.core-timer { background: var(--timer-background); color: var(--timer-text-color, var(--text-color)); - border-radius: var(--big-radius); + border-radius: var(--radius-lg); } button { diff --git a/src/core/features/course/components/components.module.ts b/src/core/features/course/components/components.module.ts index f663770b5..598626b03 100644 --- a/src/core/features/course/components/components.module.ts +++ b/src/core/features/course/components/components.module.ts @@ -30,6 +30,7 @@ import { CoreCourseModuleNavigationComponent } from './module-navigation/module- import { CoreCourseModuleSummaryComponent } from './module-summary/module-summary'; import { CoreCourseCourseIndexTourComponent } from './course-index-tour/course-index-tour'; import { CoreRemindersComponentsModule } from '@features/reminders/components/components.module'; +import { CoreCourseModuleCompletionDetailsComponent } from './module-completion-details/module-completion-details'; @NgModule({ declarations: [ @@ -46,6 +47,7 @@ import { CoreRemindersComponentsModule } from '@features/reminders/components/co CoreCourseUnsupportedModuleComponent, CoreCourseModuleNavigationComponent, CoreCourseModuleSummaryComponent, + CoreCourseModuleCompletionDetailsComponent, ], imports: [ CoreBlockComponentsModule, @@ -66,6 +68,7 @@ import { CoreRemindersComponentsModule } from '@features/reminders/components/co CoreCourseUnsupportedModuleComponent, CoreCourseModuleNavigationComponent, CoreCourseModuleSummaryComponent, + CoreCourseModuleCompletionDetailsComponent, ], }) export class CoreCourseComponentsModule {} diff --git a/src/core/features/course/components/course-format/course-format.html b/src/core/features/course/components/course-format/course-format.html index 1ea5891d1..2e1f6aeb5 100644 --- a/src/core/features/course/components/course-format/course-format.html +++ b/src/core/features/course/components/course-format/course-format.html @@ -96,7 +96,7 @@ {{highlighted}} - + diff --git a/src/core/features/course/components/course-format/course-format.scss b/src/core/features/course/components/course-format/course-format.scss index da38b60b6..7975742e0 100644 --- a/src/core/features/course/components/course-format/course-format.scss +++ b/src/core/features/course/components/course-format/course-format.scss @@ -16,6 +16,5 @@ } .course-section { - --padding-start: 12px; --inner-padding-end: 12px; } diff --git a/src/core/features/course/components/module-completion-details/module-completion-details.html b/src/core/features/course/components/module-completion-details/module-completion-details.html new file mode 100644 index 000000000..6ef5932eb --- /dev/null +++ b/src/core/features/course/components/module-completion-details/module-completion-details.html @@ -0,0 +1,44 @@ + +
+ +

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

+

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

+ + + + + + +
+ + {{ 'core.course.completion_automatic:done' | translate }} + {{ rule.rulevalue.description }} +
+ +
+ + {{ 'core.course.completion_automatic:failed' | translate }} + {{ rule.rulevalue.description }} +
+ +
+ + {{ 'core.course.completion_automatic:todo' | translate }} + {{ rule.rulevalue.description }} +
+
+ + +
+ + {{ rule.rulevalue.description }} +
+
+ + +
+ + {{ 'core.course.completion_manual:markdone' | translate }} +
+
+
diff --git a/src/core/features/course/components/module-completion-details/module-completion-details.scss b/src/core/features/course/components/module-completion-details/module-completion-details.scss new file mode 100644 index 000000000..bbd4a147a --- /dev/null +++ b/src/core/features/course/components/module-completion-details/module-completion-details.scss @@ -0,0 +1,22 @@ +:host { + h2, div { + font-size: 16px; + } + h2 { + margin-top: 0px; + margin-bottom: 8px; + line-height: 27px; + } + ion-list { + line-height: 32px; + } + + ion-icon { + width: 24px; + vertical-align: middle; + } + + ion-icon.completion_dot { + font-size: 4px; + } +} diff --git a/src/core/features/course/components/module-completion-details/module-completion-details.ts b/src/core/features/course/components/module-completion-details/module-completion-details.ts new file mode 100644 index 000000000..c7d6ffc83 --- /dev/null +++ b/src/core/features/course/components/module-completion-details/module-completion-details.ts @@ -0,0 +1,91 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Input, OnInit } from '@angular/core'; + +import { + CoreCourseModuleCompletionStatus, + CoreCourseModuleWSRuleDetails, +} from '@features/course/services/course'; +import { CoreCourseModuleCompletionData } from '@features/course/services/course-helper'; +import { CoreUser } from '@features/user/services/user'; +import { Translate } from '@singletons'; + +/** + * Component to show automatic completion details dialog. + */ +@Component({ + selector: 'core-course-module-completion-details', + templateUrl: 'module-completion-details.html', + styleUrls: ['module-completion-details.scss'], +}) +export class CoreCourseModuleCompletionDetailsComponent implements OnInit { + + @Input() completion?: CoreCourseModuleCompletionData; // The completion status. + + isTrackedUser = false; + isManual = false; + completionDetails: CompletionRule[] = []; + + /** + * @inheritdoc + */ + async ngOnInit(): Promise { + if (!this.completion) { + return; + } + + this.isManual = !this.completion.isautomatic; + this.isTrackedUser = !!this.completion.istrackeduser; + + if (!this.completion?.details) { + return; + } + + const details = this.completion.details; + + // Format rules. + this.completionDetails = await Promise.all(details.map(async (rule: CompletionRule) => { + rule.statusComplete = rule.rulevalue.status === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE || + rule.rulevalue.status === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_PASS; + rule.statusCompleteFail = rule.rulevalue.status === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_FAIL; + rule.statusIncomplete = rule.rulevalue.status === CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE; + rule.accessibleDescription = null; + + if (this.completion?.overrideby) { + const fullName = await CoreUser.getUserFullNameWithDefault(this.completion.overrideby, this.completion.courseId); + + const setByData = { + $a: { + condition: rule.rulevalue.description, + setby: fullName, + }, + }; + const overrideStatus = rule.statusComplete ? 'done' : 'todo'; + + rule.accessibleDescription = Translate.instant('core.course.completion_setby:auto:' + overrideStatus, setByData); + } + + return rule; + })); + } + +} + +type CompletionRule = CoreCourseModuleWSRuleDetails & { + statusComplete?: boolean; + statusCompleteFail?: boolean; + statusIncomplete?: boolean; + accessibleDescription?: string | null; +}; diff --git a/src/core/features/course/components/module-completion-legacy/module-completion-legacy.scss b/src/core/features/course/components/module-completion-legacy/module-completion-legacy.scss index 0eedd0acf..08d29868d 100644 --- a/src/core/features/course/components/module-completion-legacy/module-completion-legacy.scss +++ b/src/core/features/course/components/module-completion-legacy/module-completion-legacy.scss @@ -1,14 +1,13 @@ :host { - min-width: var(--a11y-min-target-size); - min-height: var(--a11y-min-target-size); + display: contents; --size: 30px; img { - padding: 5px; + padding: 2px; width: var(--size); vertical-align: middle; max-width: none; - margin: 7px; + margin: 4px; } ion-button { @@ -16,5 +15,11 @@ --padding-start: 0px; --padding-end: 0px; --padding-bottom: 0px; + margin: 0; + --a11y-target-min-size: 32px; + width: var(--a11y-target-min-size); + height: var(--a11y-target-min-size); + min-width: var(--a11y-target-min-size); + min-height: var(--a11y-target-min-size);; } } diff --git a/src/core/features/course/components/module-completion-legacy/module-completion-legacy.ts b/src/core/features/course/components/module-completion-legacy/module-completion-legacy.ts index 5ba425701..aa2c210da 100644 --- a/src/core/features/course/components/module-completion-legacy/module-completion-legacy.ts +++ b/src/core/features/course/components/module-completion-legacy/module-completion-legacy.ts @@ -147,7 +147,10 @@ export class CoreCourseModuleCompletionLegacyComponent extends CoreCourseModuleC return; } - await CoreCourseHelper.changeManualCompletion(this.completion, event); + event.stopPropagation(); + event.preventDefault(); + + await CoreCourseHelper.changeManualCompletion(this.completion); CoreEvents.trigger(CoreEvents.MANUAL_COMPLETION_CHANGED, { completion: this.completion }); } diff --git a/src/core/features/course/components/module-completion/core-course-module-completion.html b/src/core/features/course/components/module-completion/core-course-module-completion.html index dbc62caca..e6487d401 100644 --- a/src/core/features/course/components/module-completion/core-course-module-completion.html +++ b/src/core/features/course/components/module-completion/core-course-module-completion.html @@ -1,70 +1,45 @@ - - -
+ + + + + {{ 'core.course.todo' | translate }} + + - - - - - - {{ 'core.course.completion_automatic:done' | translate }} - {{ rule.rulevalue.description }} - - + + + {{'core.course.done' | translate }} + + + - - - - {{ 'core.course.completion_automatic:failed' | translate }} - {{ rule.rulevalue.description }} - - - - - - - {{ 'core.course.completion_automatic:todo' | translate }} - {{ rule.rulevalue.description }} - - - - - - - - - - {{ 'core.course.completion_automatic:todo' | translate }} - {{ rule.rulevalue.description }} - - - -
- - - - - - {{ 'core.course.todo' | translate }} - - - - - {{'core.course.done' | translate }} - - - - {{'core.course.failed' | translate }} - + + + + {{ 'core.course.completion_manual:done' | translate }} + + + + {{ 'core.course.completion_manual:markdone' | translate }} + + - - - + + {{ 'core.course.completionmenuitem' | translate }} + +
diff --git a/src/core/features/course/components/module-completion/module-completion.scss b/src/core/features/course/components/module-completion/module-completion.scss new file mode 100644 index 000000000..27c7e8302 --- /dev/null +++ b/src/core/features/course/components/module-completion/module-completion.scss @@ -0,0 +1,17 @@ +:host { + display: block; + margin: var(--margin, 4px); + + ion-button { + margin: 0px; + } + + ion-button.button-solid.ion-color-success::part(native){ + background: var(--ion-color-tint); + color: var(--ion-color-shade); + } + + ion-button.button-outline::part(native){ + border-color: var(--gray-400); + } +} diff --git a/src/core/features/course/components/module-completion/module-completion.ts b/src/core/features/course/components/module-completion/module-completion.ts index 5a6d5b986..c0c3d3dcb 100644 --- a/src/core/features/course/components/module-completion/module-completion.ts +++ b/src/core/features/course/components/module-completion/module-completion.ts @@ -12,17 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input } from '@angular/core'; +import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChange } from '@angular/core'; import { CoreCourseModuleCompletionBaseComponent } from '@features/course/classes/module-completion'; import { - CoreCourseCompletionMode, CoreCourseModuleCompletionStatus, CoreCourseModuleCompletionTracking, - CoreCourseModuleWSRuleDetails, } from '@features/course/services/course'; +import { CoreDomUtils } from '@services/utils/dom'; +import { CoreCourseModuleCompletionDetailsComponent } from '../module-completion-details/module-completion-details'; +import { CoreCourseHelper } from '@features/course/services/course-helper'; import { CoreUser } from '@features/user/services/user'; import { Translate } from '@singletons'; +import { CoreEventObserver, CoreEvents } from '@singletons/events'; /** * Component to handle activity completion. It shows a checkbox with the current status, and allows manually changing @@ -36,61 +38,132 @@ import { Translate } from '@singletons'; @Component({ selector: 'core-course-module-completion', templateUrl: 'core-course-module-completion.html', + styleUrls: ['module-completion.scss'], }) -export class CoreCourseModuleCompletionComponent extends CoreCourseModuleCompletionBaseComponent { +export class CoreCourseModuleCompletionComponent + extends CoreCourseModuleCompletionBaseComponent + implements OnInit, OnChanges, OnDestroy { @Input() showCompletionConditions = false; // Whether to show activity completion conditions. @Input() showManualCompletion = false; // Whether to show manual completion. - @Input() mode: CoreCourseCompletionMode = CoreCourseCompletionMode.FULL; // Show full completion status or a basic mode. - details?: CompletionRule[]; + completed = false; accessibleDescription: string | null = null; - completionStatus?: CoreCourseModuleCompletionStatus; + showCompletionInfo = false; + protected completionObserver?: CoreEventObserver; + + /** + * @inheritdoc + */ + ngOnInit(): void { + if (!this.completion) { + return; + } + + const hasConditions = !this.completion.isautomatic || (this.completion.details?.length || 0) > 0; + this.showCompletionInfo = hasConditions && (this.showCompletionConditions || this.showManualCompletion); + if (!this.showCompletionInfo) { + return; + } + + if (!this.completion.isautomatic && this.completion.istrackeduser) { + this.completionObserver = CoreEvents.on(CoreEvents.MANUAL_COMPLETION_CHANGED, (data) => { + if (!this.completion || this.completion.cmid != data.completion.cmid) { + return; + } + + this.completion = data.completion; + this.calculateData(); + this.completionChanged.emit(this.completion); + }); + } + } /** * @inheritdoc */ protected async calculateData(): Promise { - if (!this.completion?.details) { + if (!this.completion || !this.completion.istrackeduser) { return; } - this.completionStatus = !this.completion?.istrackeduser || - this.completion.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_NONE + const completionStatus = this.completion.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_NONE ? undefined : this.completion.state; - // Format rules. - this.details = await Promise.all(this.completion.details.map(async (rule: CompletionRule) => { - rule.statuscomplete = rule.rulevalue.status == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE || - rule.rulevalue.status == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_PASS; - rule.statuscompletefail = rule.rulevalue.status == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_FAIL; - rule.statusincomplete = rule.rulevalue.status == CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE; - rule.accessibleDescription = null; + this.completed = completionStatus !== CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE && + completionStatus !== CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_FAIL; - if (this.completion?.overrideby) { + if (!this.completion.isautomatic) { + // Set an accessible description for manual completions with overridden completion state. + if (this.completion.overrideby) { const fullName = await CoreUser.getUserFullNameWithDefault(this.completion.overrideby, this.completion.courseId); const setByData = { $a: { - condition: rule.rulevalue.description, + activityname: this.moduleName, setby: fullName, }, }; - const overrideStatus = rule.statuscomplete ? 'done' : 'todo'; + const setByLangKey = this.completion.state ? 'completion_setby:manual:done' : 'completion_setby:manual:markdone'; + this.accessibleDescription = Translate.instant('core.course.' + setByLangKey, setByData); + } else { + const langKey = this.completion.state ? 'completion_manual:aria:done' : 'completion_manual:aria:markdone'; + this.accessibleDescription = Translate.instant('core.course.' + langKey, { $a: this.moduleName }); + } + } + } - rule.accessibleDescription = Translate.instant('core.course.completion_setby:auto:' + overrideStatus, setByData); + /** + * Completion clicked. + * + * @param event The click event. + */ + async completionClicked(event: Event): Promise { + if (!this.completion || !this.showCompletionInfo) { + return; + } + + event.stopPropagation(); + event.preventDefault(); + + if (this.completion.isautomatic || !this.completion.istrackeduser) { + // Fake clicked element to correct position of the popover. + let target: HTMLElement | null = event.target as HTMLElement; + if (target && target.tagName !== 'ION-BUTTON') { + target = target.parentElement; } - return rule; - })); + CoreDomUtils.openPopover({ + component: CoreCourseModuleCompletionDetailsComponent, + componentProps: { + completion: this.completion, + }, + showBackdrop: true, + event: { target } as Event, + }); + } else { + await CoreCourseHelper.changeManualCompletion(this.completion); + + CoreEvents.trigger(CoreEvents.MANUAL_COMPLETION_CHANGED, { completion: this.completion }); + + } + } + + /** + * @inheritdoc + */ + ngOnChanges(changes: { [name: string]: SimpleChange }): void { + if (changes.completion && this.completion && this.completion.istrackeduser) { + this.calculateData(); + } + } + + /** + * @inheritdoc + */ + ngOnDestroy(): void { + this.completionObserver?.off(); } } - -type CompletionRule = CoreCourseModuleWSRuleDetails & { - statuscomplete?: boolean; - statuscompletefail?: boolean; - statusincomplete?: boolean; - accessibleDescription?: string | null; -}; 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 dc84dd852..389a72714 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 @@ -10,18 +10,19 @@ core-mod-icon { align-self: flex-start; + --padding: 4px; + @include margin-horizontal(null, 8px); } h1 ion-icon { color: var(--medium); @include margin-horizontal(8px, null); font-size: 80%; - } .core-module-info-box { background: var(--light); - border-radius: var(--small-radius); + border-radius: var(--radius-xs); margin: 8px; padding: 8px; @@ -59,30 +60,8 @@ } } - core-course-module-completion ::ng-deep ion-button { - min-height: 28px; - margin: 0; - font-size: 12px; - text-transform: none; - font-weight: normal; - - ion-icon { - font-size: 16px; - min-width: 16px; - @include margin(0, 8px, 0, 0); - - &[slot=start] { - @include margin(null, 8px, null, 0); - } - - &[slot=end] { - @include margin(null, 0, null, 8px); - } - } - - ion-label { - white-space: normal !important; - } + core-course-module-completion { + --margin: 0px; } } diff --git a/src/core/features/course/components/module-manual-completion/core-course-module-manual-completion.html b/src/core/features/course/components/module-manual-completion/core-course-module-manual-completion.html index abe56f66d..837781567 100644 --- a/src/core/features/course/components/module-manual-completion/core-course-module-manual-completion.html +++ b/src/core/features/course/components/module-manual-completion/core-course-module-manual-completion.html @@ -1,14 +1,14 @@ + class="ion-text-wrap chip"> {{ 'core.course.completion_manual:done' | translate }} + class="ion-text-wrap chip"> {{ 'core.course.completion_manual:markdone' | translate }} @@ -16,7 +16,7 @@ - + {{ 'core.course.completion_manual:markdone' | translate }} diff --git a/src/core/features/course/components/module-manual-completion/module-manual-completion.ts b/src/core/features/course/components/module-manual-completion/module-manual-completion.ts index 5d3c51f4f..c59624f9a 100644 --- a/src/core/features/course/components/module-manual-completion/module-manual-completion.ts +++ b/src/core/features/course/components/module-manual-completion/module-manual-completion.ts @@ -13,7 +13,6 @@ // limitations under the License. import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChange } from '@angular/core'; -import { CoreCourseCompletionMode } from '@features/course/services/course'; import { CoreCourseHelper, CoreCourseModuleCompletionData } from '@features/course/services/course-helper'; import { CoreUser } from '@features/user/services/user'; import { Translate } from '@singletons'; @@ -21,6 +20,8 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events'; /** * Component to display a button for manual completion. + * + * @deprecated since 4.3. Not used anymore. */ @Component({ selector: 'core-course-module-manual-completion', @@ -30,7 +31,6 @@ export class CoreCourseModuleManualCompletionComponent implements OnInit, OnChan @Input() completion?: CoreCourseModuleCompletionData; // The completion status. @Input() moduleName?: string; // The name of the module this completion affects. - @Input() mode: CoreCourseCompletionMode = CoreCourseCompletionMode.FULL; // Show full completion status or a basic mode. @Output() completionChanged = new EventEmitter(); // Notify when completion changes. accessibleDescription: string | null = null; @@ -100,7 +100,7 @@ export class CoreCourseModuleManualCompletionComponent implements OnInit, OnChan event.stopPropagation(); event.preventDefault(); - await CoreCourseHelper.changeManualCompletion(this.completion, event); + await CoreCourseHelper.changeManualCompletion(this.completion); CoreEvents.trigger(CoreEvents.MANUAL_COMPLETION_CHANGED, { completion: this.completion }); } diff --git a/src/core/features/course/components/module-summary/module-summary.scss b/src/core/features/course/components/module-summary/module-summary.scss index a4bb647d2..e524c4757 100644 --- a/src/core/features/course/components/module-summary/module-summary.scss +++ b/src/core/features/course/components/module-summary/module-summary.scss @@ -11,7 +11,7 @@ h1 { .core-modulename { text-transform: uppercase; core-mod-icon { - padding: 3px; + --padding: 3px; --size: 10px; margin: 0; } 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 e76e3df4a..209ee9b14 100644 --- a/src/core/features/course/components/module/core-course-module.html +++ b/src/core/features/course/components/module/core-course-module.html @@ -1,91 +1,90 @@ - + - + + +
+ + +
+

+ + + + +

- - +
+ + + + {{ 'core.course.hiddenfromstudents' | translate }} + + + + {{ 'core.course.hiddenoncoursepage' | translate }} + +
+
+ +
+ + + - -

- - - - -

+ + + -
- - - + + + + - - - - - - - {{ 'core.course.hiddenfromstudents' | translate }} - - - {{ 'core.course.hiddenoncoursepage' | translate }} - + + + + +
- - -
- - +
+ + +
+ + + -
- -
- - - - -
-
- - - - - - - - -
- -
- - -
+ + +
+ +
@@ -96,7 +95,10 @@
+
+
@@ -106,9 +108,8 @@ - + diff --git a/src/core/features/course/components/module/module.scss b/src/core/features/course/components/module/module.scss index 84b5db76c..88523de67 100644 --- a/src/core/features/course/components/module/module.scss +++ b/src/core/features/course/components/module/module.scss @@ -1,73 +1,148 @@ @import "~theme/globals"; :host { - --horizontal-margin: 10px; - --vertical-margin: 10px; + --horizontal-margin: 12px; + --vertical-margin: 12px; + --card-padding: 16px; ion-card { margin: var(--vertical-margin) var(--horizontal-margin); } ion-item { - --padding-start: 12px; + --padding-start: var(--card-padding); + --inner-padding-end: var(--card-padding); + ion-label { + margin-top: var(--card-padding); + margin-bottom: var(--card-padding); + &>:last-child { + margin-bottom: 0px; + } + } } - ion-item.core-module-main-item { + .activity-main { --min-height: 52px; + display: flex; + flex-direction: row; - .core-module-title .item-heading ion-icon { - @include margin-horizontal(8px, null); - vertical-align: middle; + core-mod-icon { + margin-top: 0px; + margin-bottom: 0px; + --module-icon-padding: 4px; + --module-icon-radius: var(--radius-xs); + + @include margin-horizontal(null, 8px); + align-self: self-start; } - .core-module-buttons, - .buttons.core-module-buttons { + .activity-title { + flex-grow: 1; + align-self: center; + @include margin-horizontal(null, var(--card-padding)); + + .item-heading ion-icon { + @include margin-horizontal(8px, null); + vertical-align: middle; + } + } + + .core-module-buttons { + align-self: self-start; margin: 0; - } - .core-module-buttons, - .core-module-buttons-more { display: flex; flex-flow: row; align-items: center; z-index: 1; justify-content: space-around; align-content: center; - } - .core-module-buttons core-course-module-completion, - .core-module-buttons-more button { - cursor: pointer; - pointer-events: auto; - } + ion-button.core-module-button-more { + cursor: pointer; + pointer-events: auto; + margin: 0px 8px; + --a11y-target-min-size: 32px; + width: var(--a11y-target-min-size); + height: var(--a11y-target-min-size); + min-width: var(--a11y-target-min-size); + min-height: var(--a11y-target-min-size); + --padding-start: 0px; + --padding-end: 0px; + ion-icon { + font-size: 20px; + } + } - .core-module-buttons core-course-module-completion { - text-align: center; + core-course-module-completion { + --margin: 0px; + } } .core-module-additional-info { display: flex; align-items: center; flex-wrap: wrap; + margin-top: 4px; + margin-bottom: 4px; + + ion-badge { + @include margin-horizontal(null, 4px); + font-size: 12px; + font-weight: normal; + } } } - .core-course-module-info { - .core-module-dates-availabilityinfo { - background: var(--light); - border-radius: var(--small-radius); - padding: 8px; - } + .core-module-description ::ng-deep img { + border-radius: var(--radius-lg); + } - .core-module-dates + .core-module-availabilityinfo { - border-top: 1px solid var(--stroke); - padding-top: 8px; - } + core-course-module-completion { + --margin: 8px 0px; + } + .activity-dates { + display: flex; + flex-direction: row; + flex-wrap: wrap; + + core-reminders-date { + --display-icon: none; + } + } + + .activity-description-availabilityinfo, + .activity-extrabadges { + margin-top: 8px; + padding-top: 8px; + border-top: 1px solid var(--stroke); + } + + .activity-extrabadges { + color: var(--gray-700); + } + + .activity-description-availabilityinfo { .core-module-availabilityinfo { - font-size: 90%; - ::ng-deep ul { - margin-top: 0.5em; + background: var(--gray-300); + border-radius: var(--radius-sm); + margin-top: 8px; + padding: 8px; + font-size: 14px; + line-height: 120%; + + ::ng-deep ul { + margin-top: 8px; + margin-bottom: 0px; + + li { + margin-bottom: 4px; + } + } + + ion-icon { + @include margin-horizontal(null, 8px); } } } @@ -79,19 +154,6 @@ clear: both; } - .core-module-main-item + .core-course-module-info ion-label { - margin-top: 0px; - } - - .core-module-availabilityinfo ion-icon, - .core-module-dates ion-icon { - @include margin-horizontal(null, 8px); - } - - .core-course-module-info ::ng-deep core-course-module-completion .core-module-automatic-completion-conditions .completioninfo.completion_complete { - display: none; - } - .core-course-last-module-viewed { padding: 8px 12px; color: var(--subdued-text-color); @@ -102,14 +164,25 @@ } } + @include media-breakpoint-down(md) { + .core-module-buttons core-course-module-completion { + display: none; + } + } + + @include media-breakpoint-up(md) { + core-course-module-completion.activity-extra { + display: none; + } + } + &.indented ion-card { border: none; - border-radius: 0; - margin-inline-start: calc(var(--horizontal-margin) + 1rem); + --ion-card-radius: 0; + @include margin-horizontal(calc(var(--horizontal-margin) + 1rem), null); } &.indented + ::ng-deep core-course-module.indented ion-card { border-top: 1px solid var(--border-color); } - } diff --git a/src/core/features/course/components/module/module.ts b/src/core/features/course/components/module/module.ts index 00b9ea2e0..cccd63e81 100644 --- a/src/core/features/course/components/module/module.ts +++ b/src/core/features/course/components/module/module.ts @@ -21,8 +21,8 @@ import { CoreCourseSection, CoreCourseHelper, } from '@features/course/services/course-helper'; -import { CoreCourse, CoreCourseModuleCompletionStatus, CoreCourseModuleCompletionTracking } from '@features/course/services/course'; -import { CoreCourseModuleDelegate, CoreCourseModuleHandlerButton } from '@features/course/services/module-delegate'; +import { CoreCourse } from '@features/course/services/course'; +import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; import { CoreCourseModulePrefetchDelegate, CoreCourseModulePrefetchHandler, @@ -55,11 +55,10 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy { @HostBinding('class.indented') indented = false; modNameTranslated = ''; - hasInfo = false; + hasCompletion = false; // Whether activity has completion to be shown. showManualCompletion = false; // Whether to show manual completion when completion conditions are disabled. prefetchStatusIcon$ = new BehaviorSubject(''); // Module prefetch status icon. prefetchStatusText$ = new BehaviorSubject(''); // Module prefetch status text. - autoCompletionTodo = false; moduleHasView = true; protected prefetchHandler?: CoreCourseModulePrefetchHandler; @@ -78,7 +77,7 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy { this.showLegacyCompletion = this.showLegacyCompletion ?? CoreConstants.CONFIG.uselegacycompletion ?? !site.isVersionGreaterEqualThan('3.11'); - this.checkShowManualCompletion(); + this.checkShowCompletion(); if (!this.module.handlerData) { return; @@ -87,21 +86,6 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy { this.module.handlerData.a11yTitle = this.module.handlerData.a11yTitle ?? this.module.handlerData.title; this.moduleHasView = CoreCourse.moduleHasView(this.module); - const completionStatus = this.showCompletionConditions && this.module.completiondata?.isautomatic && - this.module.completiondata.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_AUTOMATIC - ? this.module.completiondata.state - : undefined; - - this.autoCompletionTodo = completionStatus == CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE || - completionStatus == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_FAIL; - - this.hasInfo = !!( - this.module.description || - (this.showActivityDates && this.module.dates && this.module.dates.length) || - (this.autoCompletionTodo && !this.showLegacyCompletion) || - (this.module.availabilityinfo) - ); - if (this.module.handlerData?.showDownloadButton) { const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(this.module, this.module.course); this.updateModuleStatus(status); @@ -160,9 +144,14 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy { /** * Check whether manual completion should be shown. */ - protected async checkShowManualCompletion(): Promise { + protected async checkShowCompletion(): Promise { this.showManualCompletion = this.showCompletionConditions || await CoreCourseModuleDelegate.manualCompletionAlwaysShown(this.module); + + this.hasCompletion = !!this.module.completiondata && this.module.uservisible && + (!this.module.completiondata.isautomatic || (this.module.completiondata.details?.length || 0) > 0) && + (this.showCompletionConditions || this.showManualCompletion); + } /** @@ -180,9 +169,9 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy { * Function called when a button is clicked. * * @param event Click event. - * @param button The clicked button. */ - buttonClicked(event: Event, button: CoreCourseModuleHandlerButton): void { + buttonClicked(event: Event): void { + const button = this.module.handlerData?.button ?? this.module.handlerData?.buttons?.[0]; if (!button || !button.action) { return; } diff --git a/src/core/features/course/lang.json b/src/core/features/course/lang.json index d1501f66e..22ab99028 100644 --- a/src/core/features/course/lang.json +++ b/src/core/features/course/lang.json @@ -12,6 +12,7 @@ "completion_manual:aria:markdone": "Mark {{$a}} as done", "completion_manual:done": "Done", "completion_manual:markdone": "Mark as done", + "completionmenuitem": "Completion", "completion_setby:auto:done": "Done: {{$a.condition}} (set by {{$a.setby}})", "completion_setby:auto:todo": "To do: {{$a.condition}} (set by {{$a.setby}})", "completion_setby:manual:done": "{{$a.activityname}} is marked by {{$a.setby}} as done. Press to undo.", @@ -54,6 +55,7 @@ "relativedatessubmissionduedatebefore": "{{$a.datediffstr}} before course start", "section": "Section", "startdate": "Course start date", + "studentsmust": "Students must", "thisweek": "This week", "todo": "To do", "tour_navigation_course_index_student_content": "Browse through activities and track your progress.", @@ -61,5 +63,6 @@ "useactivityonbrowser": "You can still use it using your device's web browser.", "viewcourse": "View course", "warningmanualcompletionmodified": "The manual completion of an activity was modified on the site.", + "youmust": "You must", "warningofflinemanualcompletiondeleted": "Some offline manual completion of course '{{name}}' has been deleted. {{error}}" } diff --git a/src/core/features/course/pages/course-summary/course-summary.scss b/src/core/features/course/pages/course-summary/course-summary.scss index f0b9ff3c2..03c882ad9 100644 --- a/src/core/features/course/pages/course-summary/course-summary.scss +++ b/src/core/features/course/pages/course-summary/course-summary.scss @@ -43,8 +43,8 @@ .course-container { position: relative; - top: calc(var(--thumb-height) - var(--big-radius)); - border-radius: var(--big-radius) var(--big-radius) 0 0; + top: calc(var(--thumb-height) - var(--radius-lg)); + border-radius: var(--radius-lg) var(--radius-lg) 0 0; background-color: var(--ion-background-color); box-shadow: var(--drop-shadow-top); clip-path: inset(-5px 0px 0px 0px); @@ -68,7 +68,7 @@ .core-course-dates { background: var(--light); - border-radius: var(--small-radius); + border-radius: var(--radius-xs); padding: 8px; width: 100%; 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 4995c5aa9..c2656b26b 100644 --- a/src/core/features/course/pages/module-preview/module-preview.html +++ b/src/core/features/course/pages/module-preview/module-preview.html @@ -29,17 +29,18 @@ -
- - - + +
+

+

- + + {{ 'core.course.hiddenfromstudents' | translate }} - + + {{ 'core.course.hiddenoncoursepage' | translate }}
diff --git a/src/core/features/course/pages/module-preview/module-preview.scss b/src/core/features/course/pages/module-preview/module-preview.scss index 21bd09377..1481b21b3 100644 --- a/src/core/features/course/pages/module-preview/module-preview.scss +++ b/src/core/features/course/pages/module-preview/module-preview.scss @@ -2,7 +2,7 @@ .core-module-availabilityinfo { background: var(--light); - border-radius: var(--small-radius); + border-radius: var(--radius-xs); padding: 8px; font-size: 90%; ::ng-deep ul { diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index 283d3e6d2..7cefea8cc 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -2012,12 +2012,10 @@ export class CoreCourseHelperProvider { * Completion clicked. * * @param completion The completion. - * @param event The click event. * @returns Promise resolved with the result. */ async changeManualCompletion( completion: CoreCourseModuleCompletionData, - event?: Event, ): Promise { if (!completion) { return; @@ -2028,9 +2026,6 @@ export class CoreCourseHelperProvider { return; } - event?.preventDefault(); - event?.stopPropagation(); - const modal = await CoreDomUtils.showModalLoading(); completion.state = completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE ? CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE diff --git a/src/core/features/course/services/course.ts b/src/core/features/course/services/course.ts index 13217003d..c536c25d7 100644 --- a/src/core/features/course/services/course.ts +++ b/src/core/features/course/services/course.ts @@ -81,6 +81,9 @@ export enum CoreCourseModuleCompletionStatus { COMPLETION_COMPLETE_FAIL = 3, } +/** + * @deprecated since 4.3 Not used anymore. + */ export enum CoreCourseCompletionMode { FULL = 'full', BASIC = 'basic', diff --git a/src/core/features/course/services/module-delegate.ts b/src/core/features/course/services/module-delegate.ts index 8ee7cd75d..faf5b59fc 100644 --- a/src/core/features/course/services/module-delegate.ts +++ b/src/core/features/course/services/module-delegate.ts @@ -156,6 +156,8 @@ export interface CoreCourseModuleHandlerData { /** * The color of the extra badge. Default: primary. + * + * @deprecated since 4.3 Not used anymore. */ extraBadgeColor?: CoreIonicColorNames; @@ -168,9 +170,16 @@ export interface CoreCourseModuleHandlerData { /** * The buttons to display in the module item. + * + * @deprecated since 4.3 Use button instead. It will only display the first. */ buttons?: CoreCourseModuleHandlerButton[]; + /** + * The button to display in the module item. + */ + button?: CoreCourseModuleHandlerButton; + /** * Whether to display a spinner where the download button is displayed. The module icon, title, etc. will be displayed. */ diff --git a/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_46.png b/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_46.png index ccb14fbed..431c0a806 100644 Binary files a/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_46.png and b/src/core/features/course/tests/behat/snapshots/test-basic-usage-of-one-course-in-app-view-course-contents_46.png differ diff --git a/src/core/features/editor/components/rich-text-editor/rich-text-editor.scss b/src/core/features/editor/components/rich-text-editor/rich-text-editor.scss index a335fddcb..b009050e0 100644 --- a/src/core/features/editor/components/rich-text-editor/rich-text-editor.scss +++ b/src/core/features/editor/components/rich-text-editor/rich-text-editor.scss @@ -125,7 +125,7 @@ margin: 0 auto; font-size: 18px; background-color: var(--toobar-background); - border-radius: var(--small-radius); + border-radius: var(--radius-xs); @include core-transition(background-color, 200ms); color: var(--button-color); cursor: pointer; diff --git a/src/core/features/grades/pages/course/course.scss b/src/core/features/grades/pages/course/course.scss index 870b68b36..60ff2ab0a 100644 --- a/src/core/features/grades/pages/course/course.scss +++ b/src/core/features/grades/pages/course/course.scss @@ -86,7 +86,7 @@ } core-mod-icon { - padding: 0px; + --padding: 0px; --size: 16px; background: transparent; --filter: var(--mod-icon-filter); diff --git a/src/core/features/login/tests/behat/snapshots/test-basic-usage-of-login-in-app-add-a-new-account-in-the-app--site-name-in-displayed-when-adding-a-new-account_13.png b/src/core/features/login/tests/behat/snapshots/test-basic-usage-of-login-in-app-add-a-new-account-in-the-app--site-name-in-displayed-when-adding-a-new-account_13.png index c8b65b11e..b5a0d1bc1 100644 Binary files a/src/core/features/login/tests/behat/snapshots/test-basic-usage-of-login-in-app-add-a-new-account-in-the-app--site-name-in-displayed-when-adding-a-new-account_13.png and b/src/core/features/login/tests/behat/snapshots/test-basic-usage-of-login-in-app-add-a-new-account-in-the-app--site-name-in-displayed-when-adding-a-new-account_13.png differ diff --git a/src/core/features/reminders/components/date/date.scss b/src/core/features/reminders/components/date/date.scss index e898db6da..8d16930b4 100644 --- a/src/core/features/reminders/components/date/date.scss +++ b/src/core/features/reminders/components/date/date.scss @@ -3,6 +3,8 @@ :host { display: flex; flex-direction: row; + margin-top: 8px; + @include margin-horizontal(null, 8px); } div { @@ -12,10 +14,7 @@ div { align-self: center; ion-icon { + display: var(--display-icon, inline-block); @include margin-horizontal(0px, 4px); } } - -core-reminders-date + :host { - margin-top: 12px; -} diff --git a/src/core/features/search/components/global-search-result/global-search-result.scss b/src/core/features/search/components/global-search-result/global-search-result.scss index 7be59d0c0..566a5a56d 100644 --- a/src/core/features/search/components/global-search-result/global-search-result.scss +++ b/src/core/features/search/components/global-search-result/global-search-result.scss @@ -19,7 +19,7 @@ margin-inline-end: var(--spacing-2); margin-top: 0px; margin-bottom: 0px; - padding: 0px; + --padding: 0px; background: transparent; } diff --git a/src/core/features/sitehome/pages/index/index.html b/src/core/features/sitehome/pages/index/index.html index 227654f9b..4d80cc5c1 100644 --- a/src/core/features/sitehome/pages/index/index.html +++ b/src/core/features/sitehome/pages/index/index.html @@ -11,7 +11,7 @@ - + diff --git a/src/core/features/sitehome/pages/index/index.scss b/src/core/features/sitehome/pages/index/index.scss index 2581b9366..8a8e71e12 100644 --- a/src/core/features/sitehome/pages/index/index.scss +++ b/src/core/features/sitehome/pages/index/index.scss @@ -2,12 +2,12 @@ ion-item ion-icon { display: inline-block; - border-radius: var(--module-icon-radius); - padding: 0.7rem; + border-radius: var(--radius-xs); + padding: 4px; background-color: var(--gray-100); color: var(--gray-900); line-height: var(--size); - --margin-end: 1rem; + --margin-end: 8px; @include margin-horizontal(null, var(--margin-end)); } diff --git a/src/theme/components/collapsible-item.scss b/src/theme/components/collapsible-item.scss index 616bcb02e..679cb75c6 100644 --- a/src/theme/components/collapsible-item.scss +++ b/src/theme/components/collapsible-item.scss @@ -38,7 +38,7 @@ min-width: var(--toggle-size); height: var(--toggle-size); width: var(--toggle-size); - --border-radius: var(--huge-radius); + --border-radius: var(--radius-xl); border-radius: var(--border-radius); --padding-start: 0px; --padding-end: 0px; diff --git a/src/theme/components/format-text.scss b/src/theme/components/format-text.scss index 13ba068ae..55296f0ff 100644 --- a/src/theme/components/format-text.scss +++ b/src/theme/components/format-text.scss @@ -74,7 +74,7 @@ core-format-text { position: absolute; @include position(null, 10px, 10px, null); color: var(--ion-text-color); - border-radius: var(--huge-radius); + border-radius: var(--radius-xl); background-color: var(--core-format-text-viewer-icon-background); display: flex; @@ -284,7 +284,7 @@ core-rich-text-editor .core-rte-editor { border: 1px solid var(--gray-500); background: var(--contrast-background); padding: 6px 8px; - border-radius: var(--small-radius); + border-radius: var(--radius-xs); margin-left: 2px; margin-right: 2px; margin-bottom: 10px; diff --git a/src/theme/components/mod-label.scss b/src/theme/components/mod-label.scss index e68f7324f..357b9b9ca 100644 --- a/src/theme/components/mod-label.scss +++ b/src/theme/components/mod-label.scss @@ -1,8 +1,16 @@ -.item.core-course-module-handler.addon-mod-label-handler { +.core-course-module-handler.addon-mod-label-handler { align-items: center !important; cursor: auto !important; &:hover { opacity: 1; } + + .activity-title { + display: flex; + flex-direction: column; + .item-heading { + order: 2; + } + } } diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index 5ebfcbfd0..3f9e59ec3 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -166,7 +166,7 @@ ion-header { --color: var(--core-header-buttons-color); --background: var(--core-header-buttons-background); --ion-toolbar-color: var(--core-header-buttons-color); - --border-radius: var(--huge-radius); + --border-radius: var(--radius-xl); --primary: var(--core-header-buttons-color); } ion-back-button::part(text) { @@ -376,7 +376,7 @@ ion-button:not(.button-has-icon-only):not(.button-small) > ion-icon { } ion-button.button.button-clear.button-has-icon-only { - --border-radius: var(--huge-radius); + --border-radius: var(--radius-xl); } ion-button.button.button-clear { @@ -389,6 +389,27 @@ ion-button.button.button-outline { --border-radius: var(--core-input-radius); } +ion-button .select-icon { + margin: var(--icon-margin); + width: 19px; + height: 19px; + position: relative; + + .select-icon-inner { + left: 5px; + top: 50%; + margin-top: -2px; + position: absolute; + width: 0px; + height: 0px; + color: currentcolor; + pointer-events: none; + border-top: 5px solid; + border-right: 5px solid transparent; + border-left: 5px solid transparent; + } +} + [role="button"], .clickable { cursor: pointer; @@ -463,10 +484,9 @@ div.core-iframe-network-error { left: -15%; } -ion-alert.core-nohead { - .alert-head { - padding-bottom: 0; - } +ion-alert.core-nohead .alert-head, +ion-alert .alert-head:empty { + padding-bottom: 0; } @keyframes scaleFrom0 { @@ -498,7 +518,7 @@ ion-alert .alert-button.timed-button{ } ion-alert { - --border-radius: var(--huge-radius); + --border-radius: var(--radius-xl); &.md, &.ios { --max-width: 80%; @@ -531,7 +551,7 @@ ion-alert { } ion-loading { - --border-radius: var(--huge-radius); + --border-radius: var(--radius-xl); .loading-wrapper { border-radius: var(--border-radius) !important; @@ -741,7 +761,7 @@ body.core-iframe-fullscreen ion-router-outlet { } .core-password-modal { - --border-radius: var(--medium-radius); + --border-radius: var(--radius-sm); --min-width: auto; --min-height: 300px; --width: 384px; @@ -837,6 +857,7 @@ body.core-iframe-fullscreen ion-router-outlet { --border-width: 0; --border-color: var(--color-tint); + --border-radius: var(--radius-sm); --background: var(--color-tint); --color: var(--color-shade); @@ -940,6 +961,10 @@ ion-card { display: inline !important; } +.section-summary core-format-text img { + border-radius: var(--radius-lg); +} + ion-list.core-course-module-list-wrapper, .list-item-limited-width, .core-course-module-list-wrapper, @@ -978,7 +1003,7 @@ ion-action-sheet.md { } .action-sheet-group:first-child { - border-radius: var(--big-radius) var(--big-radius) 0 0; + border-radius: var(--radius-lg) var(--radius-lg) 0 0; } .action-sheet-group-cancel { @@ -1125,11 +1150,11 @@ ion-select-popover { ion-badge { line-height: 1.1; padding: 2px 8px; - border-radius: var(--big-radius); + border-radius: var(--radius-lg); } ion-chip, -ion-button.chip { +ion-button.button.chip { line-height: 1.1; font-size: 12px; min-height: 24px; @@ -1148,7 +1173,11 @@ ion-button.chip { } } -ion-button.chip { +ion-button.button.chip { + --border-radius: var(--radius-md); + min-height: 32px; + font-size: 14px; + ion-icon[slot=start] { @include margin(0, 8px, 0, 0); } @@ -1839,13 +1868,34 @@ video::-webkit-media-text-track-display { white-space: normal !important; } -ion-modal.core-modal-no-background { - --background: transparent; - --box-shadow: none !important; - pointer-events: none; +ion-modal { + .modal-wrapper { + --border-radius: var(--modal-radius); + } - ion-backdrop { - display: none; + &.core-modal-lateral, + &.core-modal-fullscreen { + --modal-radius: 0px; + } + + &.core-modal-no-background { + --background: transparent; + --box-shadow: none !important; + pointer-events: none; + + ion-backdrop { + display: none; + } + } +} + +ion-popover { + .popover-wrapper .popover-content { + border-radius: var(--modal-radius); + } + &.md { + margin-top: 2px; + margin-bottom: 2px; } } diff --git a/src/theme/theme.design-system.scss b/src/theme/theme.design-system.scss index 7cdb11dd7..21bf109e1 100644 --- a/src/theme/theme.design-system.scss +++ b/src/theme/theme.design-system.scss @@ -10,10 +10,21 @@ html { --font-size-normal: 14px; // Radiuses - --small-radius: 4px; - --medium-radius: 8px; - --big-radius: 16px; - --huge-radius: 24px; + --radius-none: 0px; + --radius-xs: 4px; + --radius-sm: 8px; + --radius-md: 12px; + --radius-lg: 16px; + --radius-xl: 24px; + --radius-round: 50%; + + /** @deprecated since 4.3 **/ + --small-radius: var(--radius-xs); + --medium-radius: var(--radius-sm); + --big-radius: var(--radius-lg); + --huge-radius: var(--radius-xl); + + // A11y --a11y-min-target-size: 44px; diff --git a/src/theme/theme.light.scss b/src/theme/theme.light.scss index f2d892099..cfe8b4574 100644 --- a/src/theme/theme.light.scss +++ b/src/theme/theme.light.scss @@ -67,14 +67,12 @@ html { --core-input-stroke: var(--gray-500); --core-input-text: var(--dark); --core-input-background: var(--ion-background-color); - --core-input-radius: var(--small-radius); + --core-input-radius: var(--radius-xs); --core-input-border-width: 1px; - --module-icon-size: 24px; - --module-icon-radius: var(--medium-radius); - --list-item-max-width: 768px; + --modal-radius: var(--radius-md); --modal-lateral-max-width: 320px; --modal-lateral-margin: 56px; @@ -82,7 +80,7 @@ html { --loader-background-color: rgba(0, 0, 0, .1); --loader-shine: 251, 251, 251; - --loader-radius: var(--small-radius); + --loader-radius: var(--radius-xs); --loader-display: block; --drop-shadow-color: 0, 0, 0, 0.5; @@ -94,9 +92,9 @@ html { --subdued-text-color: var(--medium); --ion-card-color: var(--text-color); - --ion-card-vertical-margin: 10px; - --ion-card-horizontal-margin: 10px; - --ion-card-radius: var(--medium-radius); + --ion-card-vertical-margin: 12px; + --ion-card-horizontal-margin: 12px; + --ion-card-radius: var(--radius-lg); --ion-card-border-width: 1px; --ion-card-border-color: var(--stroke); ion-card { @@ -333,7 +331,7 @@ html { --core-avatar-radius: 50%; --core-courseimage-on-course-size: 72px; - --core-courseimage-radius: var(--medium-radius); + --core-courseimage-radius: var(--radius-sm); --core-navigation-background: var(--contrast-background); diff --git a/upgrade.txt b/upgrade.txt index dcb5816f9..f38922c56 100644 --- a/upgrade.txt +++ b/upgrade.txt @@ -7,6 +7,7 @@ information provided here is intended especially for developers. - Font Awesome icon library has been updated to 6.4.0. But nothing has changed, only version number. - The analytics system in the app has been refactored and some functions that could trigger analytics calls no longer do it, now you need to use CoreAnalytics instead. Some functions in CoreCourseLogHelper and CorePushNotificationsProvider have been deprecated. - Due to the analytics refactor, the parameters of most log functions have changed. + - CoreCourseModuleHandlerData.buttons has been deprecated, now only one button in atribute button will be shown. === 4.2.0 ===