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 @@ + + +

+ {{ 'core.courses.aria:coursename' | translate }} + +

+
+
- -

{{ dayEvents.dayTimestamp * 1000 | coreFormatDate:"strftimedayshort" }}

-
+ + +

{{ dayEvents.dayTimestamp * 1000 | coreFormatDate:"strftimedayshort" }}

+
+
- - - + -

- - -

-

- - -

- - - {{event.action.name}} - {{event.action.itemcount}} - - + + + + + {{event.timesort * 1000 | coreFormatDate:"strftimetime24" }} + + + + +

+ + + {{ 'addon.block_timeline.overdue' | translate }} + +

+

+ + + · + + + +

+
+
+
+ + + {{event.action.name}} + + {{event.action.itemcount}} + + + +
- -
-
- {{event.timesort * 1000 | coreFormatDate:"strftimetime24" }} -
- - {{event.action.name}} - - {{event.action.itemcount}} - - -
@@ -57,6 +68,10 @@ - - + + +

{{'addon.block_timeline.noevents' | translate}}

+
+
+ 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; // 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 { - 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 = {}; 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 { 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 { - e.preventDefault(); - e.stopPropagation(); + async action(event: Event, url: string): Promise { + 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 @@ -

{{ 'addon.block_timeline.pluginname' | translate }}

+ +

{{ 'addon.block_timeline.pluginname' | translate }}

+
@@ -18,7 +20,7 @@ {{ 'addon.block_timeline.overdue' | translate }} - + {{ 'addon.block_timeline.duedate' | translate }} @@ -36,21 +38,14 @@ - + - - - - - - - - - - + + + + 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 { - 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 { - 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 { + async loadMore(course?: AddonBlockTimelineCourse): Promise { 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 @@
- +
- +

| - + @@ -35,12 +32,8 @@

-
@@ -50,9 +43,8 @@ [attr.aria-label]="'core.loading' | translate"> - +
- + -
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;