diff --git a/src/addons/block/onlineusers/components/onlineusers/onlineusers.scss b/src/addons/block/onlineusers/components/onlineusers/onlineusers.scss index c3d4b4c40..c34c11af4 100644 --- a/src/addons/block/onlineusers/components/onlineusers/onlineusers.scss +++ b/src/addons/block/onlineusers/components/onlineusers/onlineusers.scss @@ -28,7 +28,7 @@ } .userpicture { - vertical-align: text-bottom; + border-radius: 50%; } } diff --git a/src/addons/block/timeline/components/components.module.ts b/src/addons/block/timeline/components/components.module.ts index dfd4a5fc4..f54298186 100644 --- a/src/addons/block/timeline/components/components.module.ts +++ b/src/addons/block/timeline/components/components.module.ts @@ -15,8 +15,6 @@ import { NgModule } from '@angular/core'; import { CoreSharedModule } from '@/core/shared.module'; -import { CoreCoursesComponentsModule } from '@features/courses/components/components.module'; -import { CoreCourseComponentsModule } from '@features/course/components/components.module'; import { AddonBlockTimelineComponent } from './timeline/timeline'; import { AddonBlockTimelineEventsComponent } from './events/events'; @@ -28,8 +26,6 @@ import { AddonBlockTimelineEventsComponent } from './events/events'; ], imports: [ CoreSharedModule, - CoreCoursesComponentsModule, - CoreCourseComponentsModule, ], exports: [ AddonBlockTimelineComponent, diff --git a/src/addons/block/timeline/components/events/addon-block-timeline-events.html b/src/addons/block/timeline/components/events/addon-block-timeline-events.html index 0622e2e0b..7907f508c 100644 --- a/src/addons/block/timeline/components/events/addon-block-timeline-events.html +++ b/src/addons/block/timeline/components/events/addon-block-timeline-events.html @@ -1,50 +1,61 @@ +<ion-item lines="none" *ngIf="course"> + <ion-label class="ion-text-wrap"> + <h3> + <span class="sr-only">{{ 'core.courses.aria:coursename' | translate }}</span> + <core-format-text [text]="course.fullname" contextLevel="course" [contextInstanceId]="course.id"></core-format-text> + </h3> + </ion-label> +</ion-item> <ion-item-group *ngFor="let dayEvents of filteredEvents"> - <ion-item-divider [color]="dayEvents.color"> - <ion-label><h3>{{ dayEvents.dayTimestamp * 1000 | coreFormatDate:"strftimedayshort" }}</h3></ion-label> - </ion-item-divider> + <ion-item lines="none"> + <ion-label> + <h4 [class.core-bold]="!course">{{ dayEvents.dayTimestamp * 1000 | coreFormatDate:"strftimedayshort" }}</h4> + </ion-label> + </ion-item> <ng-container *ngFor="let event of dayEvents.events"> - <ion-item class="ion-text-wrap core-course-module-handler item-media" detail="false" (click)="action($event, event.url)" - [attr.aria-label]="event.name" button> - <core-mod-icon *ngIf="event.iconUrl" slot="start" [modicon]="event.iconUrl" [componentId]="event.instance" - [modname]="event.modulename"> - </core-mod-icon> + <ion-item class="addon-block-timeline-activity" detail="false" (click)="action($event, event.url)" [attr.aria-label]="event.name" + button lines="full"> <ion-label> - <p class="item-heading"> - <core-format-text [text]="event.name" contextLevel="module" [contextInstanceId]="event.id" - [courseId]="event.course && event.course.id"> - </core-format-text> - </p> - <p *ngIf="showCourse && event.course"> - <core-format-text [text]="event.course.fullnamedisplay" contextLevel="course" - [contextInstanceId]="event.course.id"> - </core-format-text> - </p> - - <ion-button fill="clear" class="ion-hide-md-up ion-text-wrap" (click)="action($event, event.action.url)" - [title]="event.action.name" [disabled]="!event.action.actionable" *ngIf="event.action"> - {{event.action.name}} - <ion-badge slot="end" class="ion-margin-start" *ngIf="event.action.showitemcount">{{event.action.itemcount}} - </ion-badge> - </ion-button> + <ion-row class="ion-justify-content-between ion-align-items-center ion-no-padding"> + <ion-col class="addon-block-timeline-activity-main ion-no-padding"> + <ion-row class="ion-justify-content-between ion-align-items-center ion-nowrap ion-no-padding"> + <ion-col class="addon-block-timeline-activity-time ion-no-padding"> + <ion-badge color="light">{{event.timesort * 1000 | coreFormatDate:"strftimetime24" }}</ion-badge> + <core-mod-icon *ngIf="event.iconUrl" [modicon]="event.iconUrl" [componentId]="event.instance" + [modname]="event.modulename"> + </core-mod-icon> + </ion-col> + <ion-col class="addon-block-timeline-activity-name ion-no-padding"> + <p class="item-heading"> + <core-format-text [text]="event.activityname || event.name" contextLevel="module" + [contextInstanceId]="event.id" [courseId]="event.course && event.course.id"> + </core-format-text> + <ion-badge *ngIf="event.overdue" color="danger">{{ 'addon.block_timeline.overdue' | translate }} + </ion-badge> + </p> + <p *ngIf="(showCourse && event.course) || event.activitystr"> + <span *ngIf="showCourse && event.course"> + <core-format-text [text]="event.course.fullnamedisplay" contextLevel="course" + [contextInstanceId]="event.course.id"> + </core-format-text> ยท + </span> + <core-format-text [text]="event.activitystr" contextLevel="module" [contextInstanceId]="event.id"> + </core-format-text> + </p> + </ion-col> + </ion-row> + </ion-col> + <ion-col class="addon-block-timeline-activity-action ion-no-padding"> + <ion-button fill="clear" (click)="action($event, event.action.url)" [title]="event.action.name" + [disabled]="!event.action.actionable" *ngIf="event.action"> + {{event.action.name}} + <ion-badge slot="end" class="ion-margin-start" *ngIf="event.action.showitemcount"> + {{event.action.itemcount}} + </ion-badge> + </ion-button> + </ion-col> + </ion-row> </ion-label> - - <div slot="end" class="events-info"> - <div> - <ion-badge color="light">{{event.timesort * 1000 | coreFormatDate:"strftimetime24" }}</ion-badge> - </div> - <ion-button - class="ion-hide-md-down" - fill="clear" - (click)="action($event, event.action.url)" - [title]="event.action.name" - [disabled]="!event.action.actionable" *ngIf="event.action" - > - {{event.action.name}} - <ion-badge slot="end" class="ion-margin-start" *ngIf="event.action.showitemcount"> - {{event.action.itemcount}} - </ion-badge> - </ion-button> - </div> </ion-item> </ng-container> </ion-item-group> @@ -57,6 +68,10 @@ <ion-spinner *ngIf="loadingMore" [attr.aria-label]="'core.loading' | translate"></ion-spinner> </div> -<core-empty-box *ngIf="empty" image="assets/img/icons/activities.svg" [message]="'addon.block_timeline.noevents' | translate" - inline="true"> -</core-empty-box> +<ion-item lines="none" *ngIf="empty && course"> + <ion-label class="ion-text-wrap"> + <p>{{'addon.block_timeline.noevents' | translate}}</p> + </ion-label> +</ion-item> +<core-empty-box *ngIf="empty && !course" image="assets/img/icons/activities.svg" inline="true" + [message]="'addon.block_timeline.noevents' | translate"></core-empty-box> diff --git a/src/addons/block/timeline/components/events/events.scss b/src/addons/block/timeline/components/events/events.scss index c863dbb2a..8ea49fcb1 100644 --- a/src/addons/block/timeline/components/events/events.scss +++ b/src/addons/block/timeline/components/events/events.scss @@ -1,6 +1,41 @@ -.events-info { - display: flex; - flex-direction: column; - text-align: end; - padding: 10px 0; +@import "~theme/globals"; + +h3 { + font-weight: bold; + font-size: 18px; +} + +h4 { + font-size: 15px; +} + +h4.core-bold { + font-weight: bold; +} + +.addon-block-timeline-activity ion-badge { + @include margin-horizontal(0.25rem, 0.5rem); +} + +.addon-block-timeline-activity core-mod-icon { + --margin-end: 0.5rem; +} + +.addon-block-timeline-activity-time, +.addon-block-timeline-activity-action { + flex-grow: 0; +} + +.addon-block-timeline-activity-main, +.addon-block-timeline-activity-name { + flex-grow: 1; + p { + overflow: hidden; + text-overflow: ellipsis; + } +} + +.addon-block-timeline-activity-name { + flex-grow: 1; + overflow: hidden; } diff --git a/src/addons/block/timeline/components/events/events.ts b/src/addons/block/timeline/components/events/events.ts index b0db9c8a0..b9047ff2b 100644 --- a/src/addons/block/timeline/components/events/events.ts +++ b/src/addons/block/timeline/components/events/events.ts @@ -17,11 +17,11 @@ import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreTextUtils } from '@services/utils/text'; import { CoreTimeUtils } from '@services/utils/time'; -import { CoreUtils } from '@services/utils/utils'; import { CoreCourse } from '@features/course/services/course'; import moment from 'moment'; import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper'; import { AddonCalendarEvent } from '@addons/calendar/services/calendar'; +import { CoreEnrolledCourseDataWithOptions } from '@features/courses/services/courses-helper'; /** * Directive to render a list of events in course overview. @@ -34,34 +34,37 @@ import { AddonCalendarEvent } from '@addons/calendar/services/calendar'; export class AddonBlockTimelineEventsComponent implements OnChanges { @Input() events: AddonBlockTimelineEvent[] = []; // The events to render. - @Input() showCourse?: boolean | string; // Whether to show the course name. + @Input() course?: CoreEnrolledCourseDataWithOptions; // Whether to show the course name. @Input() from = 0; // Number of days from today to offset the events. @Input() to?: number; // Number of days from today to limit the events to. If not defined, no limit. - @Input() canLoadMore?: boolean; // Whether more events can be loaded. - @Output() loadMore: EventEmitter<void>; // Notify that more events should be loaded. + @Input() canLoadMore = false; // Whether more events can be loaded. + @Output() loadMore = new EventEmitter(); // Notify that more events should be loaded. + showCourse = false; // Whether to show the course name. empty = true; loadingMore = false; filteredEvents: AddonBlockTimelineEventFilteredEvent[] = []; - constructor() { - this.loadMore = new EventEmitter(); - } - /** - * Detect changes on input properties. + * @inheritdoc */ async ngOnChanges(changes: {[name: string]: SimpleChange}): Promise<void> { - this.showCourse = CoreUtils.isTrueOrOne(this.showCourse); + this.showCourse = !this.course; if (changes.events || changes.from || changes.to) { if (this.events && this.events.length > 0) { const filteredEvents = await this.filterEventsByTime(this.from, this.to); this.empty = !filteredEvents || filteredEvents.length <= 0; + const now = CoreTimeUtils.timestamp(); + const eventsByDay: Record<number, AddonCalendarEvent[]> = {}; filteredEvents.forEach((event) => { const dayTimestamp = CoreTimeUtils.getMidnightForTimestamp(event.timesort); + + // Already calculated on 4.0 onwards but this will be live. + event.overdue = event.timesort < now; + if (eventsByDay[dayTimestamp]) { eventsByDay[dayTimestamp].push(event); } else { @@ -69,15 +72,13 @@ export class AddonBlockTimelineEventsComponent implements OnChanges { } }); - const todaysMidnight = CoreTimeUtils.getMidnightForTimestamp(); - this.filteredEvents = []; - Object.keys(eventsByDay).forEach((key) => { + this.filteredEvents = Object.keys(eventsByDay).map((key) => { const dayTimestamp = parseInt(key); - this.filteredEvents.push({ - color: dayTimestamp < todaysMidnight ? 'danger' : 'light', + + return { dayTimestamp, events: eventsByDay[dayTimestamp], - }); + }; }); } else { this.empty = true; @@ -94,7 +95,7 @@ export class AddonBlockTimelineEventsComponent implements OnChanges { */ protected async filterEventsByTime(start: number, end?: number): Promise<AddonBlockTimelineEvent[]> { start = moment().add(start, 'days').startOf('day').unix(); - end = typeof end != 'undefined' ? moment().add(end, 'days').startOf('day').unix() : end; + end = end !== undefined ? moment().add(end, 'days').startOf('day').unix() : end; return await Promise.all(this.events.filter((event) => { if (end) { @@ -122,12 +123,12 @@ export class AddonBlockTimelineEventsComponent implements OnChanges { /** * Action clicked. * - * @param e Click event. + * @param event Click event. * @param url Url of the action. */ - async action(e: Event, url: string): Promise<void> { - e.preventDefault(); - e.stopPropagation(); + async action(event: Event, url: string): Promise<void> { + event.preventDefault(); + event.stopPropagation(); // Fix URL format. url = CoreTextUtils.decodeHTMLEntities(url); @@ -137,7 +138,7 @@ export class AddonBlockTimelineEventsComponent implements OnChanges { try { const treated = await CoreContentLinksHelper.handleLink(url); if (!treated) { - return CoreSites.getCurrentSite()?.openInBrowserWithAutoLoginIfSameSite(url); + return CoreSites.getRequiredCurrentSite().openInBrowserWithAutoLoginIfSameSite(url); } } finally { modal.dismiss(); @@ -154,5 +155,4 @@ type AddonBlockTimelineEvent = AddonCalendarEvent & { type AddonBlockTimelineEventFilteredEvent = { events: AddonBlockTimelineEvent[]; dayTimestamp: number; - color: string; }; diff --git a/src/addons/block/timeline/components/timeline/addon-block-timeline.html b/src/addons/block/timeline/components/timeline/addon-block-timeline.html index 62105627c..81c32360d 100644 --- a/src/addons/block/timeline/components/timeline/addon-block-timeline.html +++ b/src/addons/block/timeline/components/timeline/addon-block-timeline.html @@ -1,5 +1,7 @@ <ion-item-divider sticky="true"> - <ion-label><h2>{{ 'addon.block_timeline.pluginname' | translate }}</h2></ion-label> + <ion-label> + <h2>{{ 'addon.block_timeline.pluginname' | translate }}</h2> + </ion-label> <core-context-menu slot="end"> <core-context-menu-item *ngIf="loaded" [priority]="900" [content]="'addon.block_timeline.sortbydates' | translate" (action)="switchSort('sortbydates')" [iconAction]="sort == 'sortbydates' ? 'far-dot-circle' : 'far-circle'"> @@ -18,7 +20,7 @@ <ion-select-option class="ion-text-wrap" value="overdue"> {{ 'addon.block_timeline.overdue' | translate }} </ion-select-option> - <ion-select-option class="ion-text-wrap" disabled value="disabled"> + <ion-select-option class="ion-text-wrap core-select-option-title" disabled value="disabled"> {{ 'addon.block_timeline.duedate' | translate }} </ion-select-option> <ion-select-option class="ion-text-wrap" value="next7days"> @@ -36,21 +38,14 @@ </core-combobox> </div> <core-loading [hideUntil]="timeline.loaded" [hidden]="sort != 'sortbydates'" [fullscreen]="false"> - <addon-block-timeline-events [events]="timeline.events" showCourse="true" [canLoadMore]="timeline.canLoadMore" - (loadMore)="loadMoreTimeline()" [from]="dataFrom" [to]="dataTo"></addon-block-timeline-events> + <addon-block-timeline-events [events]="timeline.events" [canLoadMore]="timeline.canLoadMore" (loadMore)="loadMore()" + [from]="dataFrom" [to]="dataTo"></addon-block-timeline-events> </core-loading> - <core-loading [hideUntil]="timelineCourses.loaded" [hidden]="sort != 'sortbycourses'" - [fullscreen]="false" class="safe-area-padding"> - <ion-grid class="ion-no-padding"> - <ion-row class="ion-no-padding"> - <ion-col *ngFor="let course of timelineCourses.courses" class="ion-no-padding" size="12" size-md="6"> - <core-courses-course-progress [course]="course"> - <addon-block-timeline-events [events]="course.events" [canLoadMore]="course.canLoadMore" - (loadMore)="loadMoreCourse(course)" [from]="dataFrom" [to]="dataTo"></addon-block-timeline-events> - </core-courses-course-progress> - </ion-col> - </ion-row> - </ion-grid> + <core-loading [hideUntil]="timelineCourses.loaded" [hidden]="sort != 'sortbycourses'" [fullscreen]="false" class="safe-area-page"> + <ng-container *ngFor="let course of timelineCourses.courses"> + <addon-block-timeline-events [events]="course.events" [canLoadMore]="course.canLoadMore" (loadMore)="loadMore(course)" + [course]="course" [from]="dataFrom" [to]="dataTo"></addon-block-timeline-events> + </ng-container> <core-empty-box *ngIf="timelineCourses.courses.length == 0" image="assets/img/icons/courses.svg" inline="true" [message]="'addon.block_timeline.nocoursesinprogress' | translate"></core-empty-box> </core-loading> diff --git a/src/addons/block/timeline/components/timeline/timeline.ts b/src/addons/block/timeline/components/timeline/timeline.ts index 29f2e1487..c8ef64b77 100644 --- a/src/addons/block/timeline/components/timeline/timeline.ts +++ b/src/addons/block/timeline/components/timeline/timeline.ts @@ -24,6 +24,7 @@ import { CoreCoursesHelper, CoreEnrolledCourseDataWithOptions } from '@features/ import { CoreSite } from '@classes/site'; import { CoreCourses } from '@features/courses/services/courses'; import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate'; +import { CoreNavigator } from '@services/navigator'; /** * Component to render a timeline block. @@ -36,7 +37,7 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen sort = 'sortbydates'; filter = 'next30days'; - currentSite?: CoreSite; + currentSite!: CoreSite; timeline: { events: AddonCalendarEvent[]; loaded: boolean; @@ -66,10 +67,18 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen } /** - * Component being initialized. + * @inheritdoc */ async ngOnInit(): Promise<void> { - this.currentSite = CoreSites.getRequiredCurrentSite(); + try { + this.currentSite = CoreSites.getRequiredCurrentSite(); + } catch (error) { + CoreDomUtils.showErrorModal(error); + + CoreNavigator.back(); + + return; + } this.filter = await this.currentSite.getLocalSiteConfig('AddonBlockTimelineFilter', this.filter); this.switchFilter(this.filter); @@ -117,28 +126,21 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen } } - /** - * Load more events. - */ - async loadMoreTimeline(): Promise<void> { - try { - await this.fetchMyOverviewTimeline(this.timeline.canLoadMore); - } catch (error) { - CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError); - } - } - /** * Load more events. * - * @param course Course. + * @param course Course. If defined, it will update the course events, timeline otherwise. * @return Promise resolved when done. */ - async loadMoreCourse(course: AddonBlockTimelineCourse): Promise<void> { + async loadMore(course?: AddonBlockTimelineCourse): Promise<void> { try { - const courseEvents = await AddonBlockTimeline.getActionEventsByCourse(course.id, course.canLoadMore); - course.events = course.events?.concat(courseEvents.events); - course.canLoadMore = courseEvents.canLoadMore; + if (course) { + const courseEvents = await AddonBlockTimeline.getActionEventsByCourse(course.id, course.canLoadMore); + course.events = course.events?.concat(courseEvents.events); + course.canLoadMore = courseEvents.canLoadMore; + } else { + await this.fetchMyOverviewTimeline(this.timeline.canLoadMore); + } } catch (error) { CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError); } @@ -188,12 +190,12 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen */ switchFilter(filter: string): void { this.filter = filter; - this.currentSite?.setLocalSiteConfig('AddonBlockTimelineFilter', this.filter); + this.currentSite.setLocalSiteConfig('AddonBlockTimelineFilter', this.filter); switch (this.filter) { case 'overdue': this.dataFrom = -14; - this.dataTo = 0; + this.dataTo = 1; break; case 'next7days': this.dataFrom = 0; @@ -226,7 +228,7 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen */ switchSort(sort: string): void { this.sort = sort; - this.currentSite?.setLocalSiteConfig('AddonBlockTimelineSort', this.sort); + this.currentSite.setLocalSiteConfig('AddonBlockTimelineSort', this.sort); if (!this.timeline.loaded && this.sort == 'sortbydates') { this.fetchContent(); @@ -237,7 +239,7 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen } -type AddonBlockTimelineCourse = CoreEnrolledCourseDataWithOptions & { +export type AddonBlockTimelineCourse = CoreEnrolledCourseDataWithOptions & { events?: AddonCalendarEvent[]; canLoadMore?: number; }; diff --git a/src/addons/calendar/services/calendar.ts b/src/addons/calendar/services/calendar.ts index b4ab29361..f21f71869 100644 --- a/src/addons/calendar/services/calendar.ts +++ b/src/addons/calendar/services/calendar.ts @@ -1707,14 +1707,19 @@ export type AddonCalendarEventBase = { userid?: number; // Userid. repeatid?: number; // Repeatid. eventcount?: number; // Eventcount. + component?: string; // Component. modulename?: string; // Modulename. + activityname?: string; // Activityname. + activitystr?: string; // Activitystr. instance?: number; // Instance. eventtype: AddonCalendarEventType; // Eventtype. timestart: number; // Timestart. timeduration: number; // Timeduration. timesort: number; // Timesort. + timeusermidnight: number; // Timeusermidnight. visible: number; // Visible. timemodified: number; // Timemodified. + overdue?: boolean; // Overdue. icon: { key: string; // Key. component: string; // Component. diff --git a/src/core/features/block/components/block/block.scss b/src/core/features/block/components/block/block.scss index c33c495a9..72e49c32f 100644 --- a/src/core/features/block/components/block/block.scss +++ b/src/core/features/block/components/block/block.scss @@ -7,4 +7,8 @@ ion-item-divider { min-height: var(--item-divider-min-height); } + + ::ng-deep core-loading { + --loading-inline-min-height: 44px; + } } diff --git a/src/core/features/courses/components/course-progress/core-courses-course-progress.html b/src/core/features/courses/components/course-progress/core-courses-course-progress.html index d36b8cb38..7adb8fcc3 100644 --- a/src/core/features/courses/components/course-progress/core-courses-course-progress.html +++ b/src/core/features/courses/components/course-progress/core-courses-course-progress.html @@ -1,14 +1,12 @@ <ion-card [attr.course-color]="course.color ? null : course.colorNumber"> <div (click)="openCourse()" class="core-course-thumb" [class.core-course-color-img]="course.courseImage" [style.background-color]="course.color"> - <img *ngIf="course.courseImage" [src]="course.courseImage" core-external-content alt=""/> + <img *ngIf="course.courseImage" [src]="course.courseImage" core-external-content alt="" /> </div> <ion-item button lines="none" (click)="openCourse()" [attr.aria-label]="course.displayname || course.fullname" class="core-course-header" [class.item-disabled]="course.visible == 0" - [class.core-course-only-title]="!showAll || progress < 0 && completionUserTracked === false" - detail="false"> - <ion-label - class="ion-text-wrap core-course-title" + [class.core-course-only-title]="!showAll || progress < 0 && completionUserTracked === false" detail="false"> + <ion-label class="ion-text-wrap core-course-title" [class.core-course-with-buttons]="courseOptionMenuEnabled || (downloadCourseEnabled && showDownload)" [class.core-course-with-spinner]="(downloadCourseEnabled && prefetchCourseData.icon == 'spinner') || showSpinner"> <p *ngIf="course.categoryname || (course.displayname && course.shortname && course.fullname != course.displayname)" @@ -19,8 +17,7 @@ </span> <span *ngIf="course.categoryname && course.displayname && course.shortname && course.fullname != course.displayname" class="core-course-category"> | </span> - <span *ngIf="course.displayname && course.shortname && course.fullname != course.displayname" - class="core-course-shortname"> + <span *ngIf="course.displayname && course.shortname && course.fullname != course.displayname" class="core-course-shortname"> <core-format-text [text]="course.shortname" contextLevel="course" [contextInstanceId]="course.id"> </core-format-text> </span> @@ -35,12 +32,8 @@ </ion-label> <div class="core-button-spinner" *ngIf="downloadCourseEnabled && !courseOptionMenuEnabled && showDownload" slot="end"> - <core-download-refresh - [status]="prefetchCourseData.status" - [enabled]="downloadCourseEnabled" - [statusTranslatable]="prefetchCourseData.statusTranslatable" - canTrustDownload="false" - [loading]="prefetchCourseData.loading" + <core-download-refresh [status]="prefetchCourseData.status" [enabled]="downloadCourseEnabled" + [statusTranslatable]="prefetchCourseData.statusTranslatable" canTrustDownload="false" [loading]="prefetchCourseData.loading" (action)="prefetchCourse()"></core-download-refresh> </div> @@ -50,9 +43,8 @@ [attr.aria-label]="'core.loading' | translate"></ion-spinner> <!-- Downloaded icon. --> - <ion-icon *ngIf="downloadCourseEnabled && prefetchCourseData.downloadSucceeded && !showSpinner" - class="core-icon-downloaded" name="cloud-done" color="success" role="status" - [attr.aria-label]="'core.downloaded' | translate"></ion-icon> + <ion-icon *ngIf="downloadCourseEnabled && prefetchCourseData.downloadSucceeded && !showSpinner" class="core-icon-downloaded" + name="cloud-done" color="success" role="status" [attr.aria-label]="'core.downloaded' | translate"></ion-icon> <!-- Options menu. --> <ion-button fill="clear" color="dark" (click)="showCourseOptionsMenu($event)" *ngIf="!showSpinner" @@ -61,11 +53,9 @@ </ion-button> </div> </ion-item> - <ion-item *ngIf="showAll && progress >= 0 && completionUserTracked !== false" lines="none" - class="core-course-progress"> + <ion-item *ngIf="showAll && progress >= 0 && completionUserTracked !== false" lines="none" class="core-course-progress"> <ion-label> <core-progress-bar [progress]="progress" a11yText="core.courses.aria:courseprogress"></core-progress-bar> </ion-label> </ion-item> - <ng-content></ng-content> </ion-card> diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index c74dae05a..4605eabae 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -788,6 +788,13 @@ ion-select::part(icon) { opacity: 1; } +ion-select-popover ion-item.core-select-option-title { + cursor: pointer; + ion-radio { + display: none; + } +} + ion-searchbar { .searchbar-search-icon.ios { top: 4px;