From 05786e94d3da95884598ae59760fdd32d0ccb8bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 27 Sep 2023 12:20:12 +0200 Subject: [PATCH] MOBILE-4348 module: Renew completion --- scripts/langindex.json | 7 +- .../mod/resource/services/handlers/module.ts | 9 ++ src/core/components/combobox/combobox.scss | 21 --- .../course/components/components.module.ts | 3 + .../module-completion-details.html | 44 ++++++ .../module-completion-details.scss | 22 +++ .../module-completion-details.ts | 91 ++++++++++++ .../module-completion-legacy.scss | 13 +- .../module-completion-legacy.ts | 5 +- .../core-course-module-completion.html | 103 +++++--------- .../module-completion/module-completion.scss | 17 +++ .../module-completion/module-completion.ts | 133 ++++++++++++++---- .../module-info/course-module-info.scss | 26 +--- .../core-course-module-manual-completion.html | 6 +- .../module-manual-completion.ts | 6 +- .../components/module/core-course-module.html | 15 +- .../course/components/module/module.ts | 21 ++- src/core/features/course/lang.json | 3 + .../features/course/services/course-helper.ts | 5 - src/core/features/course/services/course.ts | 3 + src/theme/theme.base.scss | 69 +++++++-- src/theme/theme.light.scss | 1 + 22 files changed, 431 insertions(+), 192 deletions(-) create mode 100644 src/core/features/course/components/module-completion-details/module-completion-details.html create mode 100644 src/core/features/course/components/module-completion-details/module-completion-details.scss create mode 100644 src/core/features/course/components/module-completion-details/module-completion-details.ts create mode 100644 src/core/features/course/components/module-completion/module-completion.scss 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/mod/resource/services/handlers/module.ts b/src/addons/mod/resource/services/handlers/module.ts index 8d112fb72..2e2bfbeb0 100644 --- a/src/addons/mod/resource/services/handlers/module.ts +++ b/src/addons/mod/resource/services/handlers/module.ts @@ -220,6 +220,15 @@ export class AddonModResourceModuleHandlerService extends CoreModuleHandlerBase return extra.join(' '); } + /** + * @inheritdoc + */ + async manualCompletionAlwaysShown(module: CoreCourseModuleData): Promise { + const hideButton = await this.hideOpenButton(module); + + return !hideButton; + } + /** * @inheritdoc */ 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/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/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 e9f9146ff..a95a6898e 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 @@ -60,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/core-course-module.html b/src/core/features/course/components/module/core-course-module.html index e76e3df4a..0f4185250 100644 --- a/src/core/features/course/components/module/core-course-module.html +++ b/src/core/features/course/components/module/core-course-module.html @@ -23,13 +23,6 @@

- - - - @@ -72,10 +65,10 @@ contextLevel="module" [contextInstanceId]="module.id" [courseId]="module.course"> - - + +
(''); // Module prefetch status icon. prefetchStatusText$ = new BehaviorSubject(''); // Module prefetch status text. - autoCompletionTodo = false; moduleHasView = true; protected prefetchHandler?: CoreCourseModulePrefetchHandler; @@ -78,7 +78,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,18 +87,10 @@ 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.hasCompletion && !this.showLegacyCompletion) || (this.module.availabilityinfo) ); @@ -160,9 +152,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); + } /** 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/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/theme/theme.base.scss b/src/theme/theme.base.scss index 8d55ea74f..792077422 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -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 { @@ -1130,7 +1150,7 @@ ion-badge { } ion-chip, -ion-button.chip { +ion-button.button.chip { line-height: 1.1; font-size: 12px; min-height: 24px; @@ -1149,7 +1169,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); } @@ -1840,13 +1864,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.light.scss b/src/theme/theme.light.scss index 067c14541..ee7d4df9d 100644 --- a/src/theme/theme.light.scss +++ b/src/theme/theme.light.scss @@ -72,6 +72,7 @@ html { --list-item-max-width: 768px; + --modal-radius: var(--radius-md); --modal-lateral-max-width: 320px; --modal-lateral-margin: 56px;