Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/addons/badges/pages/user-badges/user-badges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { AfterViewInit, Component, OnDestroy, ViewChild } from '@angular/core';
import { AfterViewInit, Component, OnDestroy, viewChild } from '@angular/core';
import { AddonBadges, AddonBadgesUserBadge } from '../../services/badges';
import { CoreSites } from '@services/sites';
import { CorePromiseUtils } from '@singletons/promise-utils';
Expand Down Expand Up @@ -42,7 +42,7 @@ export default class AddonBadgesUserBadgesPage implements AfterViewInit, OnDestr
currentTime = 0;
badges: CoreListItemsManager<AddonBadgesUserBadge, AddonBadgesUserBadgesSource>;

@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
readonly splitView = viewChild.required(CoreSplitViewComponent);

protected logView: () => void;

Expand Down Expand Up @@ -77,7 +77,7 @@ export default class AddonBadgesUserBadgesPage implements AfterViewInit, OnDestr
async ngAfterViewInit(): Promise<void> {
await this.fetchInitialBadges();

this.badges.start(this.splitView);
this.badges.start(this.splitView());
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/addons/block/completionstatus/services/block-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler
import { CoreCourseBlock } from '@features/course/services/course';
import { makeSingleton } from '@singletons';
import { AddonCourseCompletion } from '@addons/coursecompletion/services/coursecompletion';
import { CoreCourseCompletionHelper } from '@features/course/services/course-completion-helper';
import { ContextLevel } from '@/core/constants';

/**
Expand All @@ -33,7 +34,7 @@ export class AddonBlockCompletionStatusHandlerService extends CoreBlockBaseHandl
* @inheritdoc
*/
async isEnabled(): Promise<boolean> {
return AddonCourseCompletion.isCompletionEnabledInSite();
return CoreCourseCompletionHelper.isCompletionEnabledInSite();
}

/**
Expand Down
15 changes: 15 additions & 0 deletions src/addons/block/myoverview/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// (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.

export const ADDON_BLOCK_MYOVERVIEW_BLOCK_NAME = 'myoverview';
3 changes: 2 additions & 1 deletion src/addons/block/myoverview/services/block-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { CoreBlockHandlerData } from '@features/block/services/block-delegate';
import { CoreCourses } from '@features/courses/services/courses';
import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler';
import { makeSingleton } from '@singletons';
import { ADDON_BLOCK_MYOVERVIEW_BLOCK_NAME } from '../constants';

/**
* Block handler.
Expand All @@ -26,7 +27,7 @@ import { makeSingleton } from '@singletons';
export class AddonBlockMyOverviewHandlerService extends CoreBlockBaseHandler {

name = 'AddonBlockMyOverview';
blockName = 'myoverview';
blockName = ADDON_BLOCK_MYOVERVIEW_BLOCK_NAME;

/**
* @inheritdoc
Expand Down
3 changes: 2 additions & 1 deletion src/addons/block/selfcompletion/services/block-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler
import { CoreCourseBlock } from '@features/course/services/course';
import { makeSingleton } from '@singletons';
import { AddonCourseCompletion } from '@addons/coursecompletion/services/coursecompletion';
import { CoreCourseCompletionHelper } from '@features/course/services/course-completion-helper';
import { ContextLevel } from '@/core/constants';

/**
Expand All @@ -33,7 +34,7 @@ export class AddonBlockSelfCompletionHandlerService extends CoreBlockBaseHandler
* @inheritdoc
*/
async isEnabled(): Promise<boolean> {
return AddonCourseCompletion.isCompletionEnabledInSite();
return CoreCourseCompletionHelper.isCompletionEnabledInSite();
}

/**
Expand Down
87 changes: 28 additions & 59 deletions src/addons/block/timeline/classes/section.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,76 +14,55 @@

import { AddonBlockTimeline } from '@addons/block/timeline/services/timeline';
import { AddonCalendarEvent } from '@addons/calendar/services/calendar';
import { signal } from '@angular/core';
import { CoreCourseModuleHelper } from '@features/course/services/course-module-helper';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
import { CoreEnrolledCourseDataWithOptions } from '@features/courses/services/courses-helper';
import { CoreTime } from '@singletons/time';
import { BehaviorSubject, Observable } from 'rxjs';

/**
* A collection of events displayed in the timeline block.
*/
export class AddonBlockTimelineSection {

search: string | null;
overdue: boolean;
dateRange: AddonBlockTimelineDateRange;
course?: CoreEnrolledCourseDataWithOptions;

private dataSubject$: BehaviorSubject<AddonBlockTimelineSectionData>;
readonly events = signal<AddonBlockTimelineDayEvents[]>([]);
readonly lastEventId = signal<number | undefined>(undefined);
readonly canLoadMore = signal(false);
readonly loadingMore = signal(false);

constructor(
search: string | null,
overdue: boolean,
dateRange: AddonBlockTimelineDateRange,
course?: CoreEnrolledCourseDataWithOptions,
courseEvents?: AddonCalendarEvent[],
canLoadMore?: number,
public search: string,
public overdue: boolean,
public dateRange: AddonBlockTimelineDateRange,
public course?: CoreEnrolledCourseDataWithOptions,
) {
this.search = search;
this.overdue = overdue;
this.dateRange = dateRange;
this.course = course;
this.dataSubject$ = new BehaviorSubject<AddonBlockTimelineSectionData>({
events: [],
lastEventId: canLoadMore,
canLoadMore: typeof canLoadMore !== 'undefined',
loadingMore: false,
});

if (courseEvents) {
// eslint-disable-next-line promise/catch-or-return
this.reduceEvents(courseEvents, overdue, dateRange).then(events => this.dataSubject$.next({
...this.dataSubject$.value,
events,
}));
}
}

get data$(): Observable<AddonBlockTimelineSectionData> {
return this.dataSubject$;
}

/**
* Load more events.
*/
async loadMore(): Promise<void> {
this.dataSubject$.next({
...this.dataSubject$.value,
loadingMore: true,
});
this.loadingMore.set(true);

const lastEventId = this.dataSubject$.value.lastEventId;
const { events, canLoadMore } = this.course
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't new, but the fact that "canLoadMore" is an event ID is really confusing, I expect a variable named "canLoadMore" to be a boolean. The timeline service is not exported to services, so this can be a good opportunity to rename that variable to something that makes more sense.

? await AddonBlockTimeline.getActionEventsByCourse(this.course.id, lastEventId, this.search ?? '')
: await AddonBlockTimeline.getActionEventsByTimesort(lastEventId, this.search ?? '');

this.dataSubject$.next({
events: this.dataSubject$.value.events.concat(await this.reduceEvents(events, this.overdue, this.dateRange)),
lastEventId: canLoadMore,
canLoadMore: canLoadMore !== undefined,
loadingMore: false,
});
? await AddonBlockTimeline.getActionEventsByCourse(this.course.id, this.lastEventId(), this.search)
: await AddonBlockTimeline.getActionEventsByTimesort(this.lastEventId(), this.search);
await this.addEvents(events, canLoadMore);
}

/**
* Add events to the section.
*
* @param events Events to add.
* @param lastEventId Last event ID, if any.
*/
async addEvents(events: AddonCalendarEvent[], lastEventId?: number): Promise<void> {
const newEvents = await this.reduceEvents(events, this.overdue, this.dateRange);

this.events.update((events) => events.concat(newEvents));
this.lastEventId.set(lastEventId);
this.canLoadMore.set(lastEventId !== undefined);
this.loadingMore.set(false);
}

/**
Expand Down Expand Up @@ -177,16 +156,6 @@ export class AddonBlockTimelineSection {

}

/**
* Section data.
*/
export type AddonBlockTimelineSectionData = {
events: AddonBlockTimelineDayEvents[];
lastEventId?: number;
canLoadMore: boolean;
loadingMore: boolean;
};

/**
* Timestamps to use during event filtering.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,89 +1,93 @@
@if (course) {
@if (course()) {
<ion-item>
<ion-label class="ion-text-wrap">
<h3>
<span class="sr-only">{{ 'core.courses.aria:coursename' | translate }}</span>
<core-format-text [text]="course.displayname || course.fullname" contextLevel="course" [contextInstanceId]="course.id" />
<core-format-text
[text]="course().displayname || course().fullname" contextLevel="course" [contextInstanceId]="course().id" />
</h3>
</ion-label>
</ion-item>
}
<ion-item-group *ngFor="let dayEvents of events">
<ion-item>
<ion-label>
@if (course) {
<h4><ng-container *ngTemplateOutlet="date" /></h4>
} @else {
<h3><ng-container *ngTemplateOutlet="date" /></h3>
}
<ng-template #date>
{{ dayEvents.dayTimestamp * 1000 | coreFormatDate:"strftimedaydate" }}
</ng-template>
</ion-label>
</ion-item>
<ng-container *ngFor="let event of dayEvents.events">
<ion-item class="addon-block-timeline-activity" [detail]="false" (click)="action($event, event.url)" [attr.aria-label]="event.name"
button lines="full" [attr.data-event-course-id]="event.course?.id">
@for (dayEvents of events(); track dayEvents.dayTimestamp) {
<ion-item-group>
<ion-item>
<ion-label>
<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-text-nowrap">
<small>{{event.timesort * 1000 | coreFormatDate:"strftimetime24" }}</small>
@if (event.iconUrl) {
<core-mod-icon [modicon]="event.iconUrl" [componentId]="event.instance" [modname]="event.modulename"
[purpose]="event.purpose" [colorize]="colorizeIcons" [isBranded]="event.branded" />
}
</ion-col>
<ion-col class="addon-block-timeline-activity-name ion-no-padding">
<p class="item-heading">
<span>
<core-format-text [text]="event.activityname || event.name" contextLevel="module"
[contextInstanceId]="event.id" [courseId]="event.course?.id" />
</span>
@if (event.overdue) {
<ion-badge color="danger">{{ 'addon.block_timeline.overdue' | translate }}
</ion-badge>
@if (course()) {
<h4><ng-container *ngTemplateOutlet="date" /></h4>
} @else {
<h3><ng-container *ngTemplateOutlet="date" /></h3>
}
<ng-template #date>
{{ dayEvents.dayTimestamp * 1000 | coreFormatDate:"strftimedaydate" }}
</ng-template>
</ion-label>
</ion-item>
@for (event of dayEvents.events; track event.id) {
<ion-item class="addon-block-timeline-activity" [detail]="false" (click)="action($event, event.url)"
[attr.aria-label]="event.name" button lines="full" [attr.data-event-course-id]="event.course?.id">
<ion-label>
<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-text-nowrap">
<small>{{event.timesort * 1000 | coreFormatDate:"strftimetime24" }}</small>
@if (event.iconUrl) {
<core-mod-icon [modicon]="event.iconUrl" [componentId]="event.instance" [modname]="event.modulename"
[purpose]="event.purpose" [colorize]="colorizeIcons" [isBranded]="event.branded" />
}
</p>
@if (showInlineCourse && event.course) {
<p>
</ion-col>
<ion-col class="addon-block-timeline-activity-name ion-no-padding">
<p class="item-heading">
<span>
<core-format-text [text]="event.course.fullnamedisplay" contextLevel="course"
[contextInstanceId]="event.course.id" />
<core-format-text [text]="event.activityname || event.name" contextLevel="module"
[contextInstanceId]="event.id" [courseId]="event.course?.id" />
</span>
@if (event.overdue) {
<ion-badge color="danger">{{ 'addon.block_timeline.overdue' | translate }}
</ion-badge>
}
</p>
}
@if (event.activitystr) {
<p>
<span>
@if (event.activitystr) {
<core-format-text [text]="event.activitystr" contextLevel="module" [contextInstanceId]="event.id" />
}
</span>
</p>
}
</ion-col>
</ion-row>
@if (event.action && event.action.actionable) {
<div class="addon-block-timeline-activity-action">
<ion-button fill="outline" (click)="action($event, event.action.url)" [title]="event.action.name" class="chip">
{{event.action.name}}
@if (event.action.showitemcount) {
<ion-badge slot="end" class="ion-margin-start">
{{event.action.itemcount}}
</ion-badge>
@if (showInlineCourse() && event.course) {
<p>
<span>
<core-format-text [text]="event.course.fullnamedisplay" contextLevel="course"
[contextInstanceId]="event.course.id" />
</span>
</p>
}
</ion-button>
</div>
}
</ion-label>
</ion-item>
</ng-container>
</ion-item-group>
@if (event.activitystr) {
<p>
<span>
@if (event.activitystr) {
<core-format-text [text]="event.activitystr" contextLevel="module"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This if is duplicated, it's already a couple of lines above.

[contextInstanceId]="event.id" />
}
</span>
</p>
}
</ion-col>
</ion-row>
@if (event.action && event.action.actionable) {
<div class="addon-block-timeline-activity-action">
<ion-button fill="outline" (click)="action($event, event.action.url)" [title]="event.action.name" class="chip">
{{event.action.name}}
@if (event.action.showitemcount) {
<ion-badge slot="end" class="ion-margin-start">
{{event.action.itemcount}}
</ion-badge>
}
</ion-button>
</div>
}
</ion-label>
</ion-item>
}
</ion-item-group>
}

@if (canLoadMore) {
@if (canLoadMore()) {
<div class="ion-padding ion-text-center">
<!-- Button and spinner to show more attempts. -->
@if (loadingMore) {
@if (loadingMore()) {
<ion-spinner [attr.aria-label]="'core.loading' | translate" />
} @else {
<ion-button expand="block" (click)="loadMore.emit()" fill="outline">
Expand Down
Loading
Loading