diff --git a/src/addons/mod/assign/classes/submissions-source.ts b/src/addons/mod/assign/classes/submissions-source.ts new file mode 100644 index 000000000..f9a21f280 --- /dev/null +++ b/src/addons/mod/assign/classes/submissions-source.ts @@ -0,0 +1,238 @@ +// (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 { CoreItemsManagerSource } from '@classes/items-management/items-manager-source'; +import { CoreGroupInfo, CoreGroups } from '@services/groups'; +import { CoreSites } from '@services/sites'; +import { CoreUtils } from '@services/utils/utils'; +import { Translate } from '@singletons'; +import { CoreEvents } from '@singletons/events'; +import { + AddonModAssign, + AddonModAssignAssign, + AddonModAssignGrade, + AddonModAssignProvider, + AddonModAssignSubmission, +} from '../services/assign'; +import { AddonModAssignHelper, AddonModAssignSubmissionFormatted } from '../services/assign-helper'; +import { AddonModAssignOffline } from '../services/assign-offline'; +import { AddonModAssignSync, AddonModAssignSyncProvider } from '../services/assign-sync'; + +/** + * Provides a collection of assignment submissions. + */ +export class AddonModAssignSubmissionsSource extends CoreItemsManagerSource { + + /** + * @inheritdoc + */ + static getSourceId(courseId: number, moduleId: number, selectedStatus?: string): string { + selectedStatus = selectedStatus ?? '__empty__'; + + return `submissions-${courseId}-${moduleId}-${selectedStatus}`; + } + + readonly COURSE_ID: number; + readonly MODULE_ID: number; + readonly SELECTED_STATUS: string | undefined; + + assign?: AddonModAssignAssign; + groupId = 0; + groupInfo: CoreGroupInfo = { + groups: [], + separateGroups: false, + visibleGroups: false, + defaultGroupId: 0, + }; + + protected submissionsData: { canviewsubmissions: boolean; submissions?: AddonModAssignSubmission[] } = { + canviewsubmissions: false, + }; + + constructor(courseId: number, moduleId: number, selectedStatus?: string) { + super(); + + this.COURSE_ID = courseId; + this.MODULE_ID = moduleId; + this.SELECTED_STATUS = selectedStatus; + } + + /** + * Invalidate assignment cache. + */ + async invalidateCache(): Promise { + await Promise.all([ + AddonModAssign.invalidateAssignmentData(this.COURSE_ID), + this.assign && AddonModAssign.invalidateAllSubmissionData(this.assign.id), + this.assign && AddonModAssign.invalidateAssignmentUserMappingsData(this.assign.id), + this.assign && AddonModAssign.invalidateAssignmentGradesData(this.assign.id), + this.assign && AddonModAssign.invalidateListParticipantsData(this.assign.id), + ]); + } + + /** + * Load assignment. + */ + async loadAssignment(sync: boolean = false): Promise { + // Get assignment data. + this.assign = await AddonModAssign.getAssignment(this.COURSE_ID, this.MODULE_ID); + + if (sync) { + try { + // Try to synchronize data. + const result = await AddonModAssignSync.syncAssign(this.assign.id); + + if (result && result.updated) { + CoreEvents.trigger( + AddonModAssignSyncProvider.MANUAL_SYNCED, + { + assignId: this.assign.id, + warnings: result.warnings, + gradesBlocked: result.gradesBlocked, + context: 'submission-list', + }, + CoreSites.getCurrentSiteId(), + ); + } + } catch (error) { + // Ignore errors, probably user is offline or sync is blocked. + } + } + + // Get assignment submissions. + this.submissionsData = await AddonModAssign.getSubmissions(this.assign.id, { cmId: this.assign.cmid }); + + if (!this.submissionsData.canviewsubmissions) { + // User shouldn't be able to reach here. + throw new Error('Cannot view submissions.'); + } + + // Check if groupmode is enabled to avoid showing wrong numbers. + this.groupInfo = await CoreGroups.getActivityGroupInfo(this.assign.cmid, false); + + this.groupId = CoreGroups.validateGroupId(this.groupId, this.groupInfo); + + await this.reload(); + } + + /** + * @inheritdoc + */ + protected async loadPageItems(): Promise<{ items: AddonModAssignSubmissionForList[] }> { + const assign = this.assign; + + if (!assign) { + throw new Error('Can\'t load submissions without assignment'); + } + + // Fetch submissions and grades. + const submissions = + await AddonModAssignHelper.getSubmissionsUserData( + assign, + this.submissionsData.submissions, + this.groupId, + ); + // Get assignment grades only if workflow is not enabled to check grading date. + const grades = !assign.markingworkflow + ? await AddonModAssign.getAssignmentGrades(assign.id, { cmId: assign.cmid }) + : []; + + // Filter the submissions to get only the ones with the right status and add some extra data. + const getNeedGrading = this.SELECTED_STATUS == AddonModAssignProvider.NEED_GRADING; + const searchStatus = getNeedGrading ? AddonModAssignProvider.SUBMISSION_STATUS_SUBMITTED : this.SELECTED_STATUS; + + const promises: Promise[] = []; + const showSubmissions: AddonModAssignSubmissionForList[] = []; + + submissions.forEach((submission: AddonModAssignSubmissionForList) => { + if (!searchStatus || searchStatus == submission.status) { + promises.push( + CoreUtils.ignoreErrors( + AddonModAssignOffline.getSubmissionGrade(assign.id, submission.userid), + ).then(async (data) => { + if (getNeedGrading) { + // Only show the submissions that need to be graded. + const add = await AddonModAssign.needsSubmissionToBeGraded(submission, assign.id); + + if (!add) { + return; + } + } + + // Load offline grades. + const notSynced = !!data && submission.timemodified < data.timemodified; + + if (submission.gradingstatus == 'graded' && !assign.markingworkflow) { + // Get the last grade of the submission. + const grade = grades + .filter((grade) => grade.userid == submission.userid) + .reduce( + (a, b) => (a && a.timemodified > b.timemodified ? a : b), + undefined, + ); + + if (grade && grade.timemodified < submission.timemodified) { + submission.gradingstatus = AddonModAssignProvider.GRADED_FOLLOWUP_SUBMIT; + } + } + submission.statusColor = AddonModAssign.getSubmissionStatusColor(submission.status); + submission.gradingColor = AddonModAssign.getSubmissionGradingStatusColor( + submission.gradingstatus, + ); + + // Show submission status if not submitted for grading. + if (submission.statusColor != 'success' || !submission.gradingstatus) { + submission.statusTranslated = Translate.instant( + 'addon.mod_assign.submissionstatus_' + submission.status, + ); + } else { + submission.statusTranslated = ''; + } + + if (notSynced) { + submission.gradingStatusTranslationId = 'addon.mod_assign.gradenotsynced'; + submission.gradingColor = ''; + } else if (submission.statusColor != 'danger' || submission.gradingColor != 'danger') { + // Show grading status if one of the statuses is not done. + submission.gradingStatusTranslationId = AddonModAssign.getSubmissionGradingStatusTranslationId( + submission.gradingstatus, + ); + } else { + submission.gradingStatusTranslationId = ''; + } + + showSubmissions.push(submission); + + return; + }), + ); + } + }); + + await Promise.all(promises); + + return { items: showSubmissions }; + } + +} + +/** + * Calculated data for an assign submission. + */ +export type AddonModAssignSubmissionForList = AddonModAssignSubmissionFormatted & { + statusColor?: string; // Calculated in the app. Color of the submission status. + gradingColor?: string; // Calculated in the app. Color of the submission grading status. + statusTranslated?: string; // Calculated in the app. Translated text of the submission status. + gradingStatusTranslationId?: string; // Calculated in the app. Key of the text of the submission grading status. +}; diff --git a/src/addons/mod/assign/pages/submission-list/submission-list.html b/src/addons/mod/assign/pages/submission-list/submission-list.html index 3d9cb57a0..0a6635371 100644 --- a/src/addons/mod/assign/pages/submission-list/submission-list.html +++ b/src/addons/mod/assign/pages/submission-list/submission-list.html @@ -16,10 +16,10 @@ - + - + @@ -32,7 +32,7 @@ {{ 'core.groupsvisible' | translate }} - {{groupOpt.name}} diff --git a/src/addons/mod/assign/pages/submission-list/submission-list.page.ts b/src/addons/mod/assign/pages/submission-list/submission-list.page.ts index 90f02a382..0803f1e86 100644 --- a/src/addons/mod/assign/pages/submission-list/submission-list.page.ts +++ b/src/addons/mod/assign/pages/submission-list/submission-list.page.ts @@ -14,28 +14,20 @@ import { Component, OnDestroy, AfterViewInit, ViewChild } from '@angular/core'; import { Params } from '@angular/router'; -import { CorePageItemsListManager } from '@classes/page-items-list-manager'; +import { CoreItemsManagerSourcesTracker } from '@classes/items-management/items-manager-sources-tracker'; +import { CoreListItemsManager } from '@classes/items-management/list-items-manager'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { IonRefresher } from '@ionic/angular'; -import { CoreGroupInfo, CoreGroups } from '@services/groups'; +import { CoreGroupInfo } from '@services/groups'; import { CoreNavigator } from '@services/navigator'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; -import { CoreUtils } from '@services/utils/utils'; import { Translate } from '@singletons'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; -import { - AddonModAssignAssign, - AddonModAssignSubmission, - AddonModAssignProvider, - AddonModAssign, - AddonModAssignGrade, -} from '../../services/assign'; -import { AddonModAssignHelper, AddonModAssignSubmissionFormatted } from '../../services/assign-helper'; -import { AddonModAssignOffline } from '../../services/assign-offline'; +import { AddonModAssignSubmissionForList, AddonModAssignSubmissionsSource } from '../../classes/submissions-source'; +import { AddonModAssignAssign, AddonModAssignProvider } from '../../services/assign'; import { AddonModAssignSyncProvider, - AddonModAssignSync, AddonModAssignManualSyncData, AddonModAssignAutoSyncData, } from '../../services/assign-sync'; @@ -51,47 +43,26 @@ export class AddonModAssignSubmissionListPage implements AfterViewInit, OnDestro @ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent; - title = ''; // Title to display. - assign?: AddonModAssignAssign; // Assignment. - submissions: AddonModAssignSubmissionListManager; // List of submissions - loaded = false; // Whether data has been loaded. - groupId = 0; // Group ID to show. - courseId!: number; // Course ID the assignment belongs to. - moduleId!: number; // Module ID the submission belongs to. + title = ''; + submissions!: AddonModAssignSubmissionListManager; // List of submissions - groupInfo: CoreGroupInfo = { - groups: [], - separateGroups: false, - visibleGroups: false, - defaultGroupId: 0, - }; - - protected selectedStatus?: string; // The status to see. protected gradedObserver: CoreEventObserver; // Observer to refresh data when a grade changes. protected syncObserver: CoreEventObserver; // Observer to refresh data when the async is synchronized. - protected submissionsData: { canviewsubmissions: boolean; submissions?: AddonModAssignSubmission[] } = { - canviewsubmissions: false, - }; + protected sourceUnsubscribe?: () => void; constructor() { - this.submissions = new AddonModAssignSubmissionListManager(AddonModAssignSubmissionListPage); - // Update data if some grade changes. this.gradedObserver = CoreEvents.on( AddonModAssignProvider.GRADED_EVENT, (data) => { if ( - this.loaded && - this.assign && - data.assignmentId == this.assign.id && + this.submissions.loaded && + this.submissions.getSource().assign && + data.assignmentId == this.submissions.getSource().assign?.id && data.userId == CoreSites.getCurrentSiteUserId() ) { // Grade changed, refresh the data. - this.loaded = false; - - this.refreshAllData(true).finally(() => { - this.loaded = true; - }); + this.refreshAllData(true); } }, CoreSites.getCurrentSiteId(), @@ -102,29 +73,36 @@ export class AddonModAssignSubmissionListPage implements AfterViewInit, OnDestro this.syncObserver = CoreEvents.onMultiple( events, (data) => { - if (!this.loaded || ('context' in data && data.context == 'submission-list')) { + if (!this.submissions.loaded || ('context' in data && data.context == 'submission-list')) { return; } - this.loaded = false; - - this.refreshAllData(false).finally(() => { - this.loaded = true; - }); + this.refreshAllData(false); }, CoreSites.getCurrentSiteId(), ); - } - /** - * Component being initialized. - */ - ngAfterViewInit(): void { try { - this.moduleId = CoreNavigator.getRequiredRouteNumberParam('cmId'); - this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); - this.groupId = CoreNavigator.getRouteNumberParam('groupId') || 0; - this.selectedStatus = CoreNavigator.getRouteParam('status'); + const moduleId = CoreNavigator.getRequiredRouteNumberParam('cmId'); + const courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); + const groupId = CoreNavigator.getRouteNumberParam('groupId') || 0; + const selectedStatus = CoreNavigator.getRouteParam('status'); + const submissionsSource = CoreItemsManagerSourcesTracker.getOrCreateSource( + AddonModAssignSubmissionsSource, + [courseId, moduleId, selectedStatus], + ); + + submissionsSource.groupId = groupId; + this.sourceUnsubscribe = submissionsSource.addListener({ + onItemsUpdated: () => { + this.title = this.submissions.getSource().assign?.name || this.title; + }, + }); + + this.submissions = new AddonModAssignSubmissionListManager( + submissionsSource, + AddonModAssignSubmissionListPage, + ); } catch (error) { CoreDomUtils.showErrorModal(error); @@ -132,18 +110,48 @@ export class AddonModAssignSubmissionListPage implements AfterViewInit, OnDestro return; } + } + + get assign(): AddonModAssignAssign | undefined { + return this.submissions.getSource().assign; + } + + get groupInfo(): CoreGroupInfo { + return this.submissions.getSource().groupInfo; + } + + get moduleId(): number { + return this.submissions.getSource().MODULE_ID; + } + + get courseId(): number { + return this.submissions.getSource().COURSE_ID; + } + + get groupId(): number { + return this.submissions.getSource().groupId; + } + + set groupId(value: number) { + this.submissions.getSource().groupId = value; + } + + /** + * @inheritdoc + */ + ngAfterViewInit(): void { + const selectedStatus = this.submissions.getSource().SELECTED_STATUS; + this.title = Translate.instant( + selectedStatus + ? ( + selectedStatus === AddonModAssignProvider.NEED_GRADING + ? 'addon.mod_assign.numberofsubmissionsneedgrading' + : `addon.mod_assign.submissionstatus_${selectedStatus}` + ) + : 'addon.mod_assign.numberofparticipants', + ); - if (this.selectedStatus) { - if (this.selectedStatus == AddonModAssignProvider.NEED_GRADING) { - this.title = Translate.instant('addon.mod_assign.numberofsubmissionsneedgrading'); - } else { - this.title = Translate.instant('addon.mod_assign.submissionstatus_' + this.selectedStatus); - } - } else { - this.title = Translate.instant('addon.mod_assign.numberofparticipants'); - } this.fetchAssignment(true).finally(() => { - this.loaded = true; this.submissions.start(this.splitView); }); } @@ -156,148 +164,12 @@ export class AddonModAssignSubmissionListPage implements AfterViewInit, OnDestro */ protected async fetchAssignment(sync = false): Promise { try { - // Get assignment data. - this.assign = await AddonModAssign.getAssignment(this.courseId, this.moduleId); - - this.title = this.assign.name || this.title; - - if (sync) { - try { - // Try to synchronize data. - const result = await AddonModAssignSync.syncAssign(this.assign.id); - - if (result && result.updated) { - CoreEvents.trigger( - AddonModAssignSyncProvider.MANUAL_SYNCED, - { - assignId: this.assign.id, - warnings: result.warnings, - gradesBlocked: result.gradesBlocked, - context: 'submission-list', - }, - CoreSites.getCurrentSiteId(), - ); - } - } catch (error) { - // Ignore errors, probably user is offline or sync is blocked. - } - } - - // Get assignment submissions. - this.submissionsData = await AddonModAssign.getSubmissions(this.assign.id, { cmId: this.assign.cmid }); - - if (!this.submissionsData.canviewsubmissions) { - // User shouldn't be able to reach here. - throw new Error('Cannot view submissions.'); - } - - // Check if groupmode is enabled to avoid showing wrong numbers. - this.groupInfo = await CoreGroups.getActivityGroupInfo(this.assign.cmid, false); - - await this.setGroup(CoreGroups.validateGroupId(this.groupId, this.groupInfo)); + await this.submissions.getSource().loadAssignment(sync); } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'Error getting assigment data.'); } } - /** - * Set group to see the summary. - * - * @param groupId Group ID. - * @return Resolved when done. - */ - async setGroup(groupId: number): Promise { - this.groupId = groupId; - - // Fetch submissions and grades. - const submissions = - await AddonModAssignHelper.getSubmissionsUserData( - this.assign!, - this.submissionsData.submissions, - this.groupId, - ); - // Get assignment grades only if workflow is not enabled to check grading date. - const grades = !this.assign!.markingworkflow - ? await AddonModAssign.getAssignmentGrades(this.assign!.id, { cmId: this.assign!.cmid }) - : []; - - // Filter the submissions to get only the ones with the right status and add some extra data. - const getNeedGrading = this.selectedStatus == AddonModAssignProvider.NEED_GRADING; - const searchStatus = getNeedGrading ? AddonModAssignProvider.SUBMISSION_STATUS_SUBMITTED : this.selectedStatus; - - const promises: Promise[] = []; - const showSubmissions: AddonModAssignSubmissionForList[] = []; - - submissions.forEach((submission: AddonModAssignSubmissionForList) => { - if (!searchStatus || searchStatus == submission.status) { - promises.push( - CoreUtils.ignoreErrors( - AddonModAssignOffline.getSubmissionGrade(this.assign!.id, submission.userid), - ).then(async (data) => { - if (getNeedGrading) { - // Only show the submissions that need to be graded. - const add = await AddonModAssign.needsSubmissionToBeGraded(submission, this.assign!.id); - - if (!add) { - return; - } - } - - // Load offline grades. - const notSynced = !!data && submission.timemodified < data.timemodified; - - if (submission.gradingstatus == 'graded' && !this.assign!.markingworkflow) { - // Get the last grade of the submission. - const grade = grades - .filter((grade) => grade.userid == submission.userid) - .reduce( - (a, b) => (a && a.timemodified > b.timemodified ? a : b), - undefined, - ); - - if (grade && grade.timemodified < submission.timemodified) { - submission.gradingstatus = AddonModAssignProvider.GRADED_FOLLOWUP_SUBMIT; - } - } - submission.statusColor = AddonModAssign.getSubmissionStatusColor(submission.status); - submission.gradingColor = AddonModAssign.getSubmissionGradingStatusColor( - submission.gradingstatus, - ); - - // Show submission status if not submitted for grading. - if (submission.statusColor != 'success' || !submission.gradingstatus) { - submission.statusTranslated = Translate.instant( - 'addon.mod_assign.submissionstatus_' + submission.status, - ); - } else { - submission.statusTranslated = ''; - } - - if (notSynced) { - submission.gradingStatusTranslationId = 'addon.mod_assign.gradenotsynced'; - submission.gradingColor = ''; - } else if (submission.statusColor != 'danger' || submission.gradingColor != 'danger') { - // Show grading status if one of the statuses is not done. - submission.gradingStatusTranslationId = AddonModAssign.getSubmissionGradingStatusTranslationId( - submission.gradingstatus, - ); - } else { - submission.gradingStatusTranslationId = ''; - } - - showSubmissions.push(submission); - - return; - }), - ); - } - }); - - await Promise.all(promises); - - this.submissions.setItems(showSubmissions); - } - /** * Refresh all the data. * @@ -305,18 +177,8 @@ export class AddonModAssignSubmissionListPage implements AfterViewInit, OnDestro * @return Promise resolved when done. */ protected async refreshAllData(sync?: boolean): Promise { - const promises: Promise[] = []; - - promises.push(AddonModAssign.invalidateAssignmentData(this.courseId)); - if (this.assign) { - promises.push(AddonModAssign.invalidateAllSubmissionData(this.assign.id)); - promises.push(AddonModAssign.invalidateAssignmentUserMappingsData(this.assign.id)); - promises.push(AddonModAssign.invalidateAssignmentGradesData(this.assign.id)); - promises.push(AddonModAssign.invalidateListParticipantsData(this.assign.id)); - } - try { - await Promise.all(promises); + await this.submissions.getSource().invalidateCache(); } finally { this.fetchAssignment(sync); } @@ -333,6 +195,13 @@ export class AddonModAssignSubmissionListPage implements AfterViewInit, OnDestro }); } + /** + * Reload submissions list. + */ + async reloadSubmissions(): Promise { + await this.submissions.reload(); + } + /** * Component being destroyed. */ @@ -340,6 +209,7 @@ export class AddonModAssignSubmissionListPage implements AfterViewInit, OnDestro this.gradedObserver?.off(); this.syncObserver?.off(); this.submissions.destroy(); + this.sourceUnsubscribe && this.sourceUnsubscribe(); } } @@ -347,11 +217,8 @@ export class AddonModAssignSubmissionListPage implements AfterViewInit, OnDestro /** * Helper class to manage submissions. */ -class AddonModAssignSubmissionListManager extends CorePageItemsListManager { - - constructor(pageComponent: unknown) { - super(pageComponent); - } +class AddonModAssignSubmissionListManager + extends CoreListItemsManager { /** * @inheritdoc @@ -366,17 +233,9 @@ class AddonModAssignSubmissionListManager extends CorePageItemsListManager - - - - - - - - + + + + + + + + + diff --git a/src/addons/mod/assign/pages/submission-review/submission-review.ts b/src/addons/mod/assign/pages/submission-review/submission-review.ts index 803074c9f..02a3e4e3b 100644 --- a/src/addons/mod/assign/pages/submission-review/submission-review.ts +++ b/src/addons/mod/assign/pages/submission-review/submission-review.ts @@ -12,14 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit, ViewChild } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { ActivatedRoute, ActivatedRouteSnapshot, Params } from '@angular/router'; +import { CoreItemsManagerSourcesTracker } from '@classes/items-management/items-manager-sources-tracker'; +import { CoreSwipeItemsManager } from '@classes/items-management/swipe-items-manager'; import { CoreCourse } from '@features/course/services/course'; import { CanLeave } from '@guards/can-leave'; import { IonRefresher } from '@ionic/angular'; import { CoreNavigator } from '@services/navigator'; import { CoreScreen } from '@services/screen'; import { CoreDomUtils } from '@services/utils/dom'; +import { AddonModAssignSubmissionForList, AddonModAssignSubmissionsSource } from '../../classes/submissions-source'; import { AddonModAssignSubmissionComponent } from '../../components/submission/submission'; import { AddonModAssign, AddonModAssignAssign } from '../../services/assign'; @@ -30,11 +33,12 @@ import { AddonModAssign, AddonModAssignAssign } from '../../services/assign'; selector: 'page-addon-mod-assign-submission-review', templateUrl: 'submission-review.html', }) -export class AddonModAssignSubmissionReviewPage implements OnInit, CanLeave { +export class AddonModAssignSubmissionReviewPage implements OnInit, OnDestroy, CanLeave { @ViewChild(AddonModAssignSubmissionComponent) submissionComponent?: AddonModAssignSubmissionComponent; title = ''; // Title to display. + submissions?: AddonModAssignSubmissionSwipeItemsManager; moduleId!: number; // Module ID the submission belongs to. courseId!: number; // Course ID the assignment belongs to. submitId!: number; // User that did the submission. @@ -46,9 +50,7 @@ export class AddonModAssignSubmissionReviewPage implements OnInit, CanLeave { protected blindMarking = false; // Whether it uses blind marking. protected forceLeave = false; // To allow leaving the page without checking for changes. - constructor( - protected route: ActivatedRoute, - ) { } + constructor(protected route: ActivatedRoute) { } /** * Component being initialized. @@ -60,6 +62,19 @@ export class AddonModAssignSubmissionReviewPage implements OnInit, CanLeave { this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId'); this.submitId = CoreNavigator.getRouteNumberParam('submitId') || 0; this.blindId = CoreNavigator.getRouteNumberParam('blindId', { params }); + const groupId = CoreNavigator.getRequiredRouteNumberParam('groupId'); + const selectedStatus = CoreNavigator.getRouteParam('selectedStatus'); + const submissionsSource = CoreItemsManagerSourcesTracker.getOrCreateSource( + AddonModAssignSubmissionsSource, + [this.courseId, this.moduleId, selectedStatus], + ); + + this.submissions?.destroy(); + + submissionsSource.groupId = groupId; + this.submissions = new AddonModAssignSubmissionSwipeItemsManager(submissionsSource); + + this.submissions.start(); } catch (error) { CoreDomUtils.showErrorModal(error); @@ -74,6 +89,13 @@ export class AddonModAssignSubmissionReviewPage implements OnInit, CanLeave { }); } + /** + * @inheritdoc + */ + ngOnDestroy(): void { + this.submissions?.destroy(); + } + /** * Check if we can leave the page or not. * @@ -190,3 +212,36 @@ export class AddonModAssignSubmissionReviewPage implements OnInit, CanLeave { } } + +/** + * Helper to manage swiping within a collection of submissions. + */ +class AddonModAssignSubmissionSwipeItemsManager + extends CoreSwipeItemsManager { + + /** + * @inheritdoc + */ + protected getItemPath(submission: AddonModAssignSubmissionForList): string { + return String(submission.submitid); + } + + /** + * @inheritdoc + */ + protected getItemQueryParams(submission: AddonModAssignSubmissionForList): Params { + return { + blindId: submission.blindid, + groupId: this.getSource().groupId, + selectedStatus: this.getSource().SELECTED_STATUS, + }; + } + + /** + * @inheritdoc + */ + protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot): string | null { + return route.params.submitId; + } + +} diff --git a/src/core/classes/items-management/items-manager-source.ts b/src/core/classes/items-management/items-manager-source.ts index c794a776f..68baccf70 100644 --- a/src/core/classes/items-management/items-manager-source.ts +++ b/src/core/classes/items-management/items-manager-source.ts @@ -16,8 +16,8 @@ * Updates listener. */ export interface CoreItemsListSourceListener { - onItemsUpdated(items: Item[], hasMoreItems: boolean): void; - onReset(): void; + onItemsUpdated?(items: Item[], hasMoreItems: boolean): void; + onReset?(): void; } /** @@ -91,7 +91,7 @@ export abstract class CoreItemsManagerSource { this.items = null; this.hasMoreItems = true; - this.listeners.forEach(listener => listener.onReset()); + this.listeners.forEach(listener => listener.onReset?.call(listener)); } /** @@ -170,7 +170,7 @@ export abstract class CoreItemsManagerSource { this.items = items; this.hasMoreItems = hasMoreItems; - this.listeners.forEach(listener => listener.onItemsUpdated(items, hasMoreItems)); + this.listeners.forEach(listener => listener.onItemsUpdated?.call(listener, items, hasMoreItems)); } } diff --git a/src/core/classes/items-management/list-items-manager.ts b/src/core/classes/items-management/list-items-manager.ts index cfb7f4750..9bbf3fdfc 100644 --- a/src/core/classes/items-management/list-items-manager.ts +++ b/src/core/classes/items-management/list-items-manager.ts @@ -70,15 +70,6 @@ export abstract class CoreListItemsManager< // Calculate current selected item. this.updateSelectedItem(); - // Select default item if none is selected on a non-mobile layout. - if (!CoreScreen.isMobile && this.selectedItem === null && !splitView.isNested) { - const defaultItem = this.getDefaultItem(); - - if (defaultItem) { - this.select(defaultItem); - } - } - // Log activity. await CoreUtils.ignoreErrors(this.logActivity()); } @@ -175,6 +166,25 @@ export abstract class CoreListItemsManager< return !!this.splitView && !this.splitView?.isNested; } + /** + * @inheritdoc + */ + protected updateSelectedItem(route: ActivatedRouteSnapshot | null = null): void { + super.updateSelectedItem(route); + + if (CoreScreen.isMobile || this.selectedItem !== null || this.splitView?.isNested) { + return; + } + + const defaultItem = this.getDefaultItem(); + + if (!defaultItem) { + return; + } + + this.select(defaultItem); + } + /** * Get the item that should be selected by default. */ diff --git a/src/core/services/navigator.ts b/src/core/services/navigator.ts index 5cc058cb8..8d7d02c81 100644 --- a/src/core/services/navigator.ts +++ b/src/core/services/navigator.ts @@ -290,7 +290,7 @@ export class CoreNavigatorService { * @param routeOptions Optional routeOptions to get the params or route value from. If missing, it will autodetect. * @return Value of the parameter, undefined if not found. */ - getRouteParam(name: string, routeOptions: CoreNavigatorCurrentRouteOptions = {}): T | undefined { + getRouteParam(name: string, routeOptions: CoreNavigatorCurrentRouteOptions = {}): T | undefined { // eslint-disable-next-line @typescript-eslint/no-explicit-any let value: any;