MOBILE-3926 assign: Submissions swipe navigation
parent
c041e2a314
commit
f45f984d8e
|
@ -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<AddonModAssignSubmissionForList> {
|
||||
|
||||
/**
|
||||
* @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<void> {
|
||||
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<void> {
|
||||
// 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<void>[] = [];
|
||||
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),
|
||||
<AddonModAssignGrade | undefined> 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.
|
||||
};
|
|
@ -16,10 +16,10 @@
|
|||
|
||||
<ion-content>
|
||||
<core-split-view>
|
||||
<ion-refresher slot="fixed" [disabled]="!loaded || !submissions.loaded" (ionRefresh)="refreshList($event.target)">
|
||||
<ion-refresher slot="fixed" [disabled]="!submissions.loaded" (ionRefresh)="refreshList($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="loaded && submissions.loaded">
|
||||
<core-loading [hideUntil]="submissions.loaded">
|
||||
<core-empty-box *ngIf="!submissions || submissions.empty" icon="fas-file-signature"
|
||||
[message]="'addon.mod_assign.submissionstatus_' | translate">
|
||||
</core-empty-box>
|
||||
|
@ -32,7 +32,7 @@
|
|||
<ion-label id="addon-assign-groupslabel" *ngIf="groupInfo.visibleGroups">
|
||||
{{ 'core.groupsvisible' | translate }}
|
||||
</ion-label>
|
||||
<ion-select [(ngModel)]="groupId" (ionChange)="setGroup(groupId)" aria-labelledby="addon-assign-groupslabel"
|
||||
<ion-select [(ngModel)]="groupId" (ionChange)="reloadSubmissions()" aria-labelledby="addon-assign-groupslabel"
|
||||
interface="action-sheet" slot="end" [interfaceOptions]="{header: 'core.group' | translate}">
|
||||
<ion-select-option *ngFor="let groupOpt of groupInfo.groups" [value]="groupOpt.id">
|
||||
{{groupOpt.name}}
|
||||
|
|
|
@ -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<AddonModAssignAutoSyncData | AddonModAssignManualSyncData>(
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void>[] = [];
|
||||
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),
|
||||
<AddonModAssignGrade | undefined> 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<void> {
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
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<void> {
|
||||
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<AddonModAssignSubmissionForList> {
|
||||
|
||||
constructor(pageComponent: unknown) {
|
||||
super(pageComponent);
|
||||
}
|
||||
class AddonModAssignSubmissionListManager
|
||||
extends CoreListItemsManager<AddonModAssignSubmissionForList, AddonModAssignSubmissionsSource> {
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
|
@ -366,17 +233,9 @@ class AddonModAssignSubmissionListManager extends CorePageItemsListManager<Addon
|
|||
protected getItemQueryParams(submission: AddonModAssignSubmissionForList): Params {
|
||||
return {
|
||||
blindId: submission.blindid,
|
||||
groupId: this.getSource().groupId,
|
||||
selectedStatus: this.getSource().SELECTED_STATUS,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculated data for an assign submission.
|
||||
*/
|
||||
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.
|
||||
};
|
||||
|
|
|
@ -20,12 +20,14 @@
|
|||
</core-navbar-buttons>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
|
||||
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshSubmission($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<addon-mod-assign-submission [courseId]="courseId" [moduleId]="moduleId" [submitId]="submitId" [blindId]="blindId">
|
||||
</addon-mod-assign-submission>
|
||||
</core-loading>
|
||||
<core-swipe-navigation [manager]="submissions">
|
||||
<ion-refresher slot="fixed" [disabled]="!loaded" (ionRefresh)="refreshSubmission($event.target)">
|
||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<addon-mod-assign-submission *ngIf="loaded"
|
||||
[courseId]="courseId" [moduleId]="moduleId" [submitId]="submitId" [blindId]="blindId">
|
||||
</addon-mod-assign-submission>
|
||||
</core-loading>
|
||||
</core-swipe-navigation>
|
||||
</ion-content>
|
||||
|
|
|
@ -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<AddonModAssignSubmissionForList, AddonModAssignSubmissionsSource> {
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
* Updates listener.
|
||||
*/
|
||||
export interface CoreItemsListSourceListener<Item> {
|
||||
onItemsUpdated(items: Item[], hasMoreItems: boolean): void;
|
||||
onReset(): void;
|
||||
onItemsUpdated?(items: Item[], hasMoreItems: boolean): void;
|
||||
onReset?(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -91,7 +91,7 @@ export abstract class CoreItemsManagerSource<Item = unknown> {
|
|||
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<Item = unknown> {
|
|||
this.items = items;
|
||||
this.hasMoreItems = hasMoreItems;
|
||||
|
||||
this.listeners.forEach(listener => listener.onItemsUpdated(items, hasMoreItems));
|
||||
this.listeners.forEach(listener => listener.onItemsUpdated?.call(listener, items, hasMoreItems));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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<T = unknown>(name: string, routeOptions: CoreNavigatorCurrentRouteOptions = {}): T | undefined {
|
||||
getRouteParam<T = string>(name: string, routeOptions: CoreNavigatorCurrentRouteOptions = {}): T | undefined {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let value: any;
|
||||
|
||||
|
|
Loading…
Reference in New Issue