Skip to content

Mobile 4739 #4497

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jul 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
264146f
MOBILE-4739 context-menu-popover: Remove unused uniqueId
dpalou Jun 5, 2025
a2803ca
MOBILE-4739 core: Enable input signals in modals
dpalou Jun 6, 2025
85de6d6
MOBILE-4739 components: Use signals in core presentational components
dpalou Jun 6, 2025
b319fde
MOBILE-4739 components: Use signals in chrono and timer components
dpalou Jun 6, 2025
94b198e
MOBILE-4739 components: Use signals in context menu
dpalou Jun 6, 2025
ec08eb4
MOBILE-4739 components: Use signals in core-combobox
dpalou Jun 10, 2025
5f4b13f
MOBILE-4739 components: Use signals in hoirzontal-scroll-controls
dpalou Jun 10, 2025
a426246
MOBILE-4739 components: Use signals in core-navigation-bar
dpalou Jun 10, 2025
9207e5f
MOBILE-4739 components: Use signals in password-modal and sheet-modal
dpalou Jun 11, 2025
9e207e8
MOBILE-4739 components: Use signals in core-progress-bar
dpalou Jun 11, 2025
bd82c54
MOBILE-4739 components: Use signals in send-message-form
dpalou Jun 11, 2025
7594a15
MOBILE-4739 components: Use signals in core-sites-list
dpalou Jun 11, 2025
1dbe9af
MOBILE-4739 components: Use signals in infinite-loading
dpalou Jun 13, 2025
f5d99e1
MOBILE-4739 components: Use signals in core-message
dpalou Jun 13, 2025
6a7197d
MOBILE-4739 components: Use signals in mod-icon
dpalou Jun 16, 2025
4924ae7
MOBILE-4739 icon: Move all effect logic to computed
dpalou Jun 16, 2025
59d2d22
MOBILE-4739 core: Force signal variables to be readonly
dpalou Jul 14, 2025
1dba1fd
MOBILE-4739 core: Fix naming convention errors after rule change
dpalou Jul 15, 2025
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
1 change: 1 addition & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ For more information about upgrading, read the official documentation: https://m
=====

- "model" has been removed for site plugins because it isn't needed and it's not compatible with Angular 18. "model" is meant to support 2-way data binding in custom components, and site plugins cannot create components.
- The (onChange) output in core-combobox has been deprecated, please use (selectionChange) instead.

5.0.0
=====
Expand Down
14 changes: 11 additions & 3 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@ const appConfig = {
rules: {
'@angular-eslint/component-class-suffix': ['error', { suffixes: ['Component', 'Page'] }],
'@angular-eslint/no-output-on-prefix': 'off',
'@angular-eslint/prefer-signals': [
'error',
{
preferInputSignals: false, // @todo: Force input signals when all have been migrated.
preferQuerySignals: false, // @todo: Force query signals when all have been migrated.
preferReadonlySignalProperties: true,
},
],
'@typescript-eslint/adjacent-overload-signatures': 'error',
'@typescript-eslint/no-restricted-types': [
'error',
Expand Down Expand Up @@ -132,17 +140,17 @@ const appConfig = {
},
{
selector: 'property',
modifiers: ['public', 'readonly'],
modifiers: ['public', 'static', 'readonly'],
format: ['UPPER_CASE'],
},
{
selector: 'property',
modifiers: ['protected', 'readonly'],
modifiers: ['protected', 'static', 'readonly'],
format: ['UPPER_CASE'],
},
{
selector: 'property',
modifiers: ['private', 'readonly'],
modifiers: ['private', 'static', 'readonly'],
format: ['UPPER_CASE'],
},
{
Expand Down
14 changes: 7 additions & 7 deletions src/addons/badges/classes/user-badges-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ import { AddonBadges, AddonBadgesUserBadge } from '../services/badges';
*/
export class AddonBadgesUserBadgesSource extends CoreRoutedItemsManagerSource<AddonBadgesUserBadge> {

readonly COURSE_ID: number;
readonly USER_ID: number;
readonly courseId: number;
readonly userId: number;

constructor(courseId: number, userId: number) {
super();

this.COURSE_ID = courseId;
this.USER_ID = userId;
this.courseId = courseId;
this.userId = userId;
}

/**
Expand All @@ -43,16 +43,16 @@ export class AddonBadgesUserBadgesSource extends CoreRoutedItemsManagerSource<Ad
*/
getItemQueryParams(): Params {
return {
courseId: this.COURSE_ID,
userId: this.USER_ID,
courseId: this.courseId,
userId: this.userId,
};
}

/**
* @inheritdoc
*/
protected async loadPageItems(): Promise<{ items: AddonBadgesUserBadge[] }> {
const badges = await AddonBadges.getUserBadges(this.COURSE_ID, this.USER_ID);
const badges = await AddonBadges.getUserBadges(this.courseId, this.userId);

return { items: badges };
}
Expand Down
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 @@ -65,7 +65,7 @@ export default class AddonBadgesUserBadgesPage implements AfterViewInit, OnDestr
type: CoreAnalyticsEventType.VIEW_ITEM_LIST,
ws: 'core_badges_view_user_badges',
name: Translate.instant('addon.badges.badges'),
data: { courseId: this.badges.getSource().COURSE_ID, category: 'badges' },
data: { courseId: this.badges.getSource().courseId, category: 'badges' },
url: '/badges/mybadges.php',
});
});
Expand Down Expand Up @@ -95,8 +95,8 @@ export default class AddonBadgesUserBadgesPage implements AfterViewInit, OnDestr
async refreshBadges(refresher?: HTMLIonRefresherElement): Promise<void> {
await CorePromiseUtils.ignoreErrors(
AddonBadges.invalidateUserBadges(
this.badges.getSource().COURSE_ID,
this.badges.getSource().USER_ID,
this.badges.getSource().courseId,
this.badges.getSource().userId,
),
);
await CorePromiseUtils.ignoreErrors(this.badges.reload());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ <h2>{{ 'addon.block_myoverview.pluginname' | translate }}</h2>
@if (filters.enabled) {
<ion-col size="auto">
<core-combobox [label]="'core.courses.filtermycourses' | translate" [selection]="filters.timeFilterSelected"
(onChange)="filterOptionsChanged($event)">
(selectionChange)="filterCourses()">
@if (filters.show.allincludinghidden) {
<ion-select-option class="ion-text-wrap core-select-option-border-bottom" value="allincludinghidden">
{{ 'addon.block_myoverview.allincludinghidden' | translate }}
Expand Down Expand Up @@ -95,7 +95,7 @@ <h2>{{ 'addon.block_myoverview.pluginname' | translate }}</h2>
</ion-col>
@if (sort.enabled) {
<ion-col size="auto">
<core-combobox [label]="'core.sortby' | translate" [selection]="sort.selected" (onChange)="sortCourses($event)"
<core-combobox [label]="'core.sortby' | translate" [selection]="sort.selected" (selectionChange)="sortCourses()"
icon="fas-arrow-down-short-wide" class="no-border">
<ion-select-option class="ion-text-wrap" value="fullname">
{{'addon.block_myoverview.title' | translate}}
Expand Down
21 changes: 5 additions & 16 deletions src/addons/block/myoverview/components/myoverview/myoverview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -623,7 +623,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
}
}

this.sortCourses(this.sort.selected);
this.sortCourses(false);

// Refresh prefetch data (if enabled).
this.prefetchIconsInitialized = false;
Expand All @@ -648,15 +648,15 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
/**
* Sort courses
*
* @param sort Sort by value.
* @param saveSort Whether to save the sort option.
*/
sortCourses(sort: string): void {
sortCourses(saveSort = true): void {
if (!this.sort.enabled) {
return;
}

if (this.sort.selected != sort) {
this.saveSort(sort);
if (saveSort) {
this.saveSort(this.sort.selected);
}

if (this.sort.selected == 'lastaccess') {
Expand Down Expand Up @@ -713,17 +713,6 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
await this.currentSite.setLocalSiteConfig('AddonBlockMyOverviewSort', this.sort.selected);
}

/**
* Option selected save and apply filter.
*
* @param selected Option selected.
* @returns Promise resolved when done.
*/
async filterOptionsChanged(selected: string): Promise<void> {
this.filters.timeFilterSelected = selected;
this.filterCourses();
}

/**
* Go to search courses.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ <h2>{{ 'addon.block_timeline.pluginname' | translate }}</h2>
}
<ion-row class="ion-justify-content-between ion-align-items-center addon-block-timeline-filter">
<ion-col size="auto">
<core-combobox [formControl]="filter" (onChange)="filterChanged($event)"
<core-combobox [formControl]="filter" (selectionChange)="filterChanged($event)"
[label]="'addon.block_timeline.ariadayfilter' | translate">
<ion-select-option *ngFor="let option of statusFilterOptions; last as last"
[attr.class]="'ion-text-wrap' + last ? ' core-select-option-border-bottom' : ''" [value]="option.value">
Expand All @@ -39,7 +39,7 @@ <h2>{{ 'addon.block_timeline.pluginname' | translate }}</h2>
</ion-col>
}
<ion-col size="auto">
<core-combobox [label]="'core.sortby' | translate" [formControl]="sort" (onChange)="sortChanged($event)"
<core-combobox [label]="'core.sortby' | translate" [formControl]="sort" (selectionChange)="sortChanged($event)"
icon="fas-arrow-down-short-wide" class="no-border">
<ion-select-option *ngFor="let option of sortOptions" class="ion-text-wrap" [value]="option.value">
{{ option.name | translate }}
Expand Down
8 changes: 4 additions & 4 deletions src/addons/blog/pages/index/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export default class AddonBlogIndexPage implements OnInit, OnDestroy {
protected siteHomeId: number;
protected logView: () => void;

loaded = signal(false);
readonly loaded = signal(false);
canLoadMore = false;
loadMoreError = false;
entries: (AddonBlogOfflinePostFormatted | AddonBlogPostFormatted)[] = [];
Expand All @@ -91,11 +91,11 @@ export default class AddonBlogIndexPage implements OnInit, OnDestroy {
syncObserver: CoreEventObserver;
onlineObserver: Subscription;
optionsAvailable = false;
hasOfflineDataToSync = signal(false);
isOnline = signal(false);
readonly hasOfflineDataToSync = signal(false);
readonly isOnline = signal(false);
siteId: string;
syncIcon = CoreConstants.ICON_SYNC;
syncHidden = computed(() => !this.loaded() || !this.isOnline() || !this.hasOfflineDataToSync());
readonly syncHidden = computed(() => !this.loaded() || !this.isOnline() || !this.hasOfflineDataToSync());

constructor() {
this.currentUserId = CoreSites.getCurrentSiteUserId();
Expand Down
6 changes: 3 additions & 3 deletions src/addons/calendar/classes/events-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-i
*/
export class AddonCalendarEventsSource extends CoreRoutedItemsManagerSource<AddonCalendarEventToDisplay> {

readonly DATE: string;
readonly date: string;

private events: AddonCalendarEventToDisplay[] = [];

constructor(date: string) {
super();

this.DATE = date;
this.date = date;
}

/**
Expand Down Expand Up @@ -58,7 +58,7 @@ export class AddonCalendarEventsSource extends CoreRoutedItemsManagerSource<Addo
* @inheritdoc
*/
getItemQueryParams(): Params {
return { date: this.DATE };
return { date: this.date };
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,17 @@ export class AddonCompetencyCourseCompetenciesSource
return `${courseId}-${userId || 'current-user'}`;
}

readonly COURSE_ID: number;
readonly USER_ID?: number;
readonly courseId: number;
readonly userId?: number;

courseCompetencies?: AddonCompetencyDataForCourseCompetenciesPageWSResponse;
user?: CoreUserProfile;

constructor(courseId: number, userId?: number) {
super();

this.COURSE_ID = courseId;
this.USER_ID = userId;
this.courseId = courseId;
this.userId = userId;
}

/**
Expand All @@ -70,7 +70,7 @@ export class AddonCompetencyCourseCompetenciesSource
* Invalidate course cache.
*/
async invalidateCache(): Promise<void> {
await CorePromiseUtils.ignoreErrors(AddonCompetency.invalidateCourseCompetencies(this.COURSE_ID, this.USER_ID));
await CorePromiseUtils.ignoreErrors(AddonCompetency.invalidateCourseCompetencies(this.courseId, this.userId));
}

/**
Expand All @@ -89,8 +89,8 @@ export class AddonCompetencyCourseCompetenciesSource
*/
private async loadCourseCompetencies(): Promise<void> {
[this.courseCompetencies, this.user] = await Promise.all([
AddonCompetency.getCourseCompetencies(this.COURSE_ID, this.USER_ID),
AddonCompetencyHelper.getProfile(this.USER_ID),
AddonCompetency.getCourseCompetencies(this.courseId, this.userId),
AddonCompetencyHelper.getProfile(this.userId),
]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@ import { AddonCompetencyHelper } from '../services/competency-helper';
*/
export class AddonCompetencyPlanCompetenciesSource extends CoreRoutedItemsManagerSource<AddonCompetencyDataForPlanPageCompetency> {

readonly PLAN_ID: number;
readonly planId: number;

plan?: AddonCompetencyDataForPlanPageWSResponse;
user?: CoreUserProfile;

constructor(planId: number) {
super();

this.PLAN_ID = planId;
this.planId = planId;
}

/**
Expand All @@ -60,7 +60,7 @@ export class AddonCompetencyPlanCompetenciesSource extends CoreRoutedItemsManage
* Invalidate plan cache.
*/
async invalidateCache(): Promise<void> {
await CorePromiseUtils.ignoreErrors(AddonCompetency.invalidateLearningPlan(this.PLAN_ID));
await CorePromiseUtils.ignoreErrors(AddonCompetency.invalidateLearningPlan(this.planId));
}

/**
Expand All @@ -78,7 +78,7 @@ export class AddonCompetencyPlanCompetenciesSource extends CoreRoutedItemsManage
* Load learning plan.
*/
private async loadLearningPlan(): Promise<void> {
this.plan = await AddonCompetency.getLearningPlan(this.PLAN_ID);
this.plan = await AddonCompetency.getLearningPlan(this.planId);
this.plan.plan.statusname = AddonCompetencyHelper.getPlanStatusName(this.plan.plan.status);

// Get the user profile image.
Expand Down
12 changes: 6 additions & 6 deletions src/addons/competency/classes/competency-plans-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ export class AddonCompetencyPlansSource extends CoreRoutedItemsManagerSource<Add
return userId ? String(userId) : 'current-user';
}

readonly USER_ID?: number;
readonly userId?: number;

constructor(userId?: number) {
super();

this.USER_ID = userId;
this.userId = userId;
}

/**
Expand All @@ -50,8 +50,8 @@ export class AddonCompetencyPlansSource extends CoreRoutedItemsManagerSource<Add
* @inheritdoc
*/
getItemQueryParams(): Params {
if (this.USER_ID) {
return { userId: this.USER_ID };
if (this.userId) {
return { userId: this.userId };
}

return {};
Expand All @@ -61,14 +61,14 @@ export class AddonCompetencyPlansSource extends CoreRoutedItemsManagerSource<Add
* Invalidate learning plans cache.
*/
async invalidateCache(): Promise<void> {
await AddonCompetency.invalidateLearningPlans(this.USER_ID);
await AddonCompetency.invalidateLearningPlans(this.userId);
}

/**
* @inheritdoc
*/
protected async loadPageItems(): Promise<{ items: AddonCompetencyPlanFormatted[] }> {
const plans = await AddonCompetency.getLearningPlans(this.USER_ID);
const plans = await AddonCompetency.getLearningPlans(this.userId);

plans.forEach((plan: AddonCompetencyPlanFormatted) => {
plan.statusname = AddonCompetencyHelper.getPlanStatusName(plan.status);
Expand Down
12 changes: 6 additions & 6 deletions src/addons/competency/pages/competencies/competencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export default class AddonCompetencyCompetenciesPage implements AfterViewInit, O
} else {
this.title = Translate.instant('addon.competency.coursecompetencies');
this.contextLevel = ContextLevel.COURSE;
this.contextInstanceId = source.COURSE_ID;
this.contextInstanceId = source.courseId;
}

this.logView();
Expand Down Expand Up @@ -153,15 +153,15 @@ export default class AddonCompetencyCompetenciesPage implements AfterViewInit, O
name: this.title,
data: {
category: 'competency',
planid: source.PLAN_ID,
planid: source.planId,
},
url: `/admin/tool/lp/plan.php?id=${source.PLAN_ID}`,
url: `/admin/tool/lp/plan.php?id=${source.planId}`,
});

return;
}

if (source.USER_ID && source.USER_ID !== CoreSites.getCurrentSiteUserId()) {
if (source.userId && source.userId !== CoreSites.getCurrentSiteUserId()) {
// Only log event when viewing own competencies. In LMS viewing students competencies uses a different view.
return;
}
Expand All @@ -172,9 +172,9 @@ export default class AddonCompetencyCompetenciesPage implements AfterViewInit, O
name: this.title,
data: {
category: 'competency',
courseid: source.COURSE_ID,
courseid: source.courseId,
},
url: `/admin/tool/lp/coursecompetencies.php?courseid=${source.COURSE_ID}`,
url: `/admin/tool/lp/coursecompetencies.php?courseid=${source.courseId}`,
});
}

Expand Down
Loading