Skip to content
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

Exam mode: Migrate participate module to signals #10456

Draft
wants to merge 28 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6283c32
migrate exam-bar.component
coolchock Mar 6, 2025
04002c5
migrate exam-timer.component
coolchock Mar 6, 2025
cad80ac
migrate text-exam-summary.component
coolchock Mar 6, 2025
3dbfef9
migrate quiz-exam-summary.component
coolchock Mar 6, 2025
d2f2efc
migrate programming-exam-summary.component
coolchock Mar 6, 2025
442a149
migrate exam-result-overview.component
coolchock Mar 6, 2025
7b9da2d
migrate modeling-exam-summary.component
coolchock Mar 6, 2025
db297ab
exam-result-summary-exercise-card-header.component
coolchock Mar 6, 2025
203be39
migate file-upload-exam-summary.component
coolchock Mar 6, 2025
714f6b6
migrate exam-start-information.component
coolchock Mar 6, 2025
0a051bf
migrate exam-general-information.component
coolchock Mar 6, 2025
8a4cf74
fix exam-result-summary-exercise-card-header.component.spec.ts
coolchock Mar 6, 2025
bee2026
fix exam-start-information.component.spec.ts
coolchock Mar 6, 2025
277edeb
fix exam-general-information.component.spec.ts
coolchock Mar 6, 2025
8431840
fix quiz-exam-summary.component.spec.ts
coolchock Mar 6, 2025
78f6cc7
fix programming-exam-summary.component.spec.ts
coolchock Mar 7, 2025
5ba33b0
fix exam-result-overview.component.spec.ts
coolchock Mar 7, 2025
08a6b7c
fix file-upload-exam-summary.component.spec.ts
coolchock Mar 7, 2025
62daa58
fix modeling-exam-summary.component.spec.ts
coolchock Mar 7, 2025
402136d
migrate exam-participation.component.ts
coolchock Mar 9, 2025
47a5ef6
migrate exam-navigation-bar.component
coolchock Mar 9, 2025
cd3983d
migrate exam-navigation-bar.component
coolchock Mar 9, 2025
605f5c0
set default value
coolchock Mar 9, 2025
21f9921
migrate exam-participation-cover.component
coolchock Mar 9, 2025
1025b43
migrate collapsible-card.component
coolchock Mar 9, 2025
c288d30
migrate overlay and button for live events
coolchock Mar 9, 2025
a2ddf9b
Merge branch 'develop' into chore/exam-mode/migrate-participate-modul…
coolchock Mar 9, 2025
6b5b5a3
fix issue with model
coolchock Mar 9, 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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Input, OnDestroy, OnInit, ViewEncapsulation, inject } from '@angular/core';
import { Component, OnDestroy, OnInit, ViewEncapsulation, inject, input } from '@angular/core';
import { faBullhorn } from '@fortawesome/free-solid-svg-icons';
import { AlertService } from 'app/core/util/alert.service';
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
Expand Down Expand Up @@ -32,24 +32,27 @@ export class ExamLiveEventsButtonComponent implements OnInit, OnDestroy {
private liveEventsSubscription?: Subscription;
private allEventsSubscription?: Subscription;
eventCount = 0;
@Input() examStartDate: dayjs.Dayjs;
examStartDate = input<dayjs.Dayjs>();

// Icons
faBullhorn = faBullhorn;

ngOnInit(): void {
this.allEventsSubscription = this.liveEventsService.observeAllEvents(USER_DISPLAY_RELEVANT_EVENTS_REOPEN).subscribe((events: ExamLiveEvent[]) => {
// do not count the problem statements events that are made before the start of the exam
const filteredEvents = events.filter((event) => !(event.eventType === ExamLiveEventType.PROBLEM_STATEMENT_UPDATE && event.createdDate.isBefore(this.examStartDate)));
const filteredEvents = events.filter((event) => !(event.eventType === ExamLiveEventType.PROBLEM_STATEMENT_UPDATE && event.createdDate.isBefore(this.examStartDate())));
this.eventCount = filteredEvents.length;
});

this.liveEventsSubscription = this.liveEventsService.observeNewEventsAsUser(USER_DISPLAY_RELEVANT_EVENTS, this.examStartDate).subscribe(() => {
// If any unacknowledged event comes in, open the dialog to display it
if (!this.modalRef) {
this.openDialog();
}
});
const examStartDate = this.examStartDate();
if (examStartDate) {
this.liveEventsSubscription = this.liveEventsService.observeNewEventsAsUser(USER_DISPLAY_RELEVANT_EVENTS, examStartDate).subscribe(() => {
// If any unacknowledged event comes in, open the dialog to display it
if (!this.modalRef) {
this.openDialog();
}
});
}
}

ngOnDestroy(): void {
Expand All @@ -69,7 +72,7 @@ export class ExamLiveEventsButtonComponent implements OnInit, OnDestroy {
windowClass: 'live-events-modal-window',
});

this.modalRef.componentInstance.examStartDate = this.examStartDate;
this.modalRef.componentInstance.examStartDate.update(() => this.examStartDate());

from(this.modalRef.result).subscribe(() => (this.modalRef = undefined));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Input, OnDestroy, OnInit, inject } from '@angular/core';
import { Component, OnDestroy, OnInit, inject, model } from '@angular/core';
import { faCheck } from '@fortawesome/free-solid-svg-icons';
import { ExamLiveEventComponent } from 'app/exam/shared/events/exam-live-event.component';
import { Subscription } from 'rxjs';
Expand Down Expand Up @@ -28,7 +28,7 @@ export class ExamLiveEventsOverlayComponent implements OnInit, OnDestroy {
eventsToDisplay?: ExamLiveEvent[];
events: ExamLiveEvent[] = [];

@Input() examStartDate: dayjs.Dayjs;
examStartDate = model<dayjs.Dayjs>();
// Icons
faCheck = faCheck;

Expand All @@ -42,16 +42,19 @@ export class ExamLiveEventsOverlayComponent implements OnInit, OnDestroy {
ngOnInit(): void {
this.allLiveEventsSubscription = this.liveEventsService.observeAllEvents(USER_DISPLAY_RELEVANT_EVENTS_REOPEN).subscribe((events: ExamLiveEvent[]) => {
// display the problem statements events only after the start of the exam
this.events = events.filter((event) => !(event.eventType === ExamLiveEventType.PROBLEM_STATEMENT_UPDATE && event.createdDate.isBefore(this.examStartDate)));
this.events = events.filter((event) => !(event.eventType === ExamLiveEventType.PROBLEM_STATEMENT_UPDATE && event.createdDate.isBefore(this.examStartDate())));
if (!this.eventsToDisplay) {
this.updateEventsToDisplay();
}
});

this.newLiveEventsSubscription = this.liveEventsService.observeNewEventsAsUser(USER_DISPLAY_RELEVANT_EVENTS, this.examStartDate).subscribe((event: ExamLiveEvent) => {
this.unacknowledgedEvents.unshift(event);
this.updateEventsToDisplay();
});
const examStartDate = this.examStartDate();
if (examStartDate) {
this.newLiveEventsSubscription = this.liveEventsService.observeNewEventsAsUser(USER_DISPLAY_RELEVANT_EVENTS, examStartDate).subscribe((event: ExamLiveEvent) => {
this.unacknowledgedEvents.unshift(event);
this.updateEventsToDisplay();
});
}
}

acknowledgeEvent(event: ExamLiveEvent) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
<div class="h5 module-bg exam-bar mt-0 px-3 py-2 rounded rounded-3 sticky-top" [ngClass]="{ 'end-view': isEndView, 'mx-3': !isEndView }">
<div class="h5 module-bg exam-bar mt-0 px-3 py-2 rounded rounded-3 sticky-top" [ngClass]="{ 'end-view': isEndView(), 'mx-3': !isEndView() }">
<div class="d-flex justify-content-between">
<h3 class="align-self-center mb-0 me-3">
{{ examTitle }}
</h3>
@if (!examTimeLineView) {
@if (!examTimeLineView()) {
<div class="d-flex justify-content-between align-items-center">
<jhi-exam-timer
class="me-3"
[criticalTime]="isEndView ? criticalTimeEndView : criticalTime"
[endDate]="endDate"
[isEndView]="isEndView"
[criticalTime]="isEndView() ? criticalTimeEndView : criticalTime"
[endDate]="endDate()"
[isEndView]="isEndView()"
(timerAboutToEnd)="triggerExamAboutToEnd()"
/>
<jhi-exam-live-events-button [examStartDate]="examStartDate" />
@if (!isEndView) {
<jhi-exam-live-events-button [examStartDate]="examStartDate()" />
@if (!isEndView()) {
<button id="hand-in-early" class="btn btn-danger ms-2" aria-label="Hand In Early" (click)="handInEarly()">
<div class="d-flex justify-content-between">
<span>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AfterViewInit, Component, ElementRef, EventEmitter, Input, OnInit, Output, inject } from '@angular/core';
import { AfterViewInit, Component, ElementRef, OnInit, inject, input, output } from '@angular/core';
import { CommonModule } from '@angular/common';

import { ExamParticipationService } from 'app/exam/participate/exam-participation.service';
Expand All @@ -21,18 +21,18 @@ import { TranslateDirective } from 'app/shared/language/translate.directive';
export class ExamBarComponent implements AfterViewInit, OnInit {
private elementRef = inject(ElementRef);

@Output() onExamHandInEarly = new EventEmitter<void>();
@Output() examAboutToEnd = new EventEmitter<void>();
@Output() heightChange = new EventEmitter<number>();
onExamHandInEarly = output<void>();
examAboutToEnd = output<void>();
heightChange = output<number>();

@Input() examTimeLineView = false;
@Input() endDate: dayjs.Dayjs;
@Input() exerciseIndex = 0;
@Input() isEndView: boolean;
@Input() testRunStartTime: dayjs.Dayjs | undefined;
@Input() exam: Exam;
@Input() studentExam: StudentExam;
@Input() examStartDate: dayjs.Dayjs;
examTimeLineView = input(false);
endDate = input.required<dayjs.Dayjs>();
exerciseIndex = input(0);
isEndView = input.required<boolean>();
testRunStartTime = input<dayjs.Dayjs>();
exam = input.required<Exam>();
studentExam = input.required<StudentExam>();
examStartDate = input.required<dayjs.Dayjs>();

readonly faDoorClosed = faDoorClosed;
criticalTime = dayjs.duration(5, 'minutes');
Expand All @@ -45,10 +45,10 @@ export class ExamBarComponent implements AfterViewInit, OnInit {
exercises: Exercise[] = [];

ngOnInit(): void {
this.examTitle = this.exam.title ?? '';
this.exercises = this.studentExam.exercises ?? [];
this.testExam = this.exam.testExam ?? false;
this.testRun = this.studentExam.testRun ?? false;
this.examTitle = this.exam().title ?? '';
this.exercises = this.studentExam().exercises ?? [];
this.testExam = this.exam().testExam ?? false;
this.testRun = this.studentExam().testRun ?? false;
}

/**
Expand Down Expand Up @@ -77,9 +77,9 @@ export class ExamBarComponent implements AfterViewInit, OnInit {
* Save the currently active exercise
*/
saveExercise() {
const submission = ExamParticipationService.getSubmissionForExercise(this.exercises[this.exerciseIndex]);
const submission = ExamParticipationService.getSubmissionForExercise(this.exercises[this.exerciseIndex()]);
// we do not submit programming exercises on a save
if (submission && this.exercises[this.exerciseIndex].type !== ExerciseType.PROGRAMMING) {
if (submission && this.exercises[this.exerciseIndex()].type !== ExerciseType.PROGRAMMING) {
submission.submitted = true;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
-->
<div
class="px-3 pb-3 overflow-auto"
[ngClass]="{ 'content-height-dev': (!isProduction || isTestServer) && !testRunStartTime, 'scrollable-content-exam-cover': !testRunStartTime && startView }"
[ngClass]="{ 'content-height-dev': (!isProduction() || isTestServer()) && !testRunStartTime(), 'scrollable-content-exam()-cover': !testRunStartTime() && startView() }"
>
@if (startView) {
@if (startView()) {
<div class="d-flex justify-content-between">
<h3 class="mt-3">
{{ exam.title }}
{{ exam().title }}
</h3>
<div class="mt-3">
<jhi-exam-live-events-button [examStartDate]="exam.startDate!" />
<jhi-exam-live-events-button [examStartDate]="exam().startDate!" />
</div>
</div>
<hr class="my-0" />
<div class="mt-3">
<jhi-exam-start-information [exam]="exam" [studentExam]="studentExam" [formattedStartText]="formattedGeneralInformation" />
<jhi-exam-start-information [exam]="exam()" [studentExam]="studentExam()" [formattedStartText]="formattedGeneralInformation" />
</div>
<div class="d-inline-flex align-items-center my-3">
<div class="ps-1">
Expand All @@ -26,7 +26,7 @@ <h3 class="mt-3">
id="confirmBox"
(click)="updateConfirmation()"
class="form-check-input me-2"
[class.ms-0]="!this.exam.confirmationStartText"
[class.ms-0]="!this.exam().confirmationStartText"
[required]="inserted"
[disabled]="waitingForExamStart"
/>
Expand Down Expand Up @@ -90,14 +90,14 @@ <h3 class="mt-3">
</ng-container>
@if (waitingForExamStart) {
<div class="exam-waiting-for-start-overlay alert alert-info">
<span jhiTranslate="artemisApp.examParticipation.waitForStart" [translateValues]="{ title: exam.title }"></span>
@if (exam.startDate) {
<span jhiTranslate="artemisApp.examParticipation.waitForStart" [translateValues]="{ title: exam().title }"></span>
@if (exam().startDate) {
<div>
<hr />
<span jhiTranslate="artemisApp.examParticipation.timeUntilPlannedStart"></span>
<span class="text-bold">{{ timeUntilStart }}</span>
<br />
<span>({{ exam.startDate | artemisDate: 'time' }})</span>
<span>({{ exam().startDate | artemisDate: 'time' }})</span>
</div>
}
</div>
Expand All @@ -108,7 +108,7 @@ <h4 id="exam-finished-title">
<span
jhiTranslate="artemisApp.examParticipation.finish"
[translateValues]="{
title: exam.title,
title: exam().title,
}"
></span>
</h4>
Expand All @@ -119,7 +119,7 @@ <h4 id="exam-finished-title">
<div class="mb-1">
<span class="fw-bold" jhiTranslate="artemisApp.examParticipation.submitFinalExam"></span>
</div>
@if (handInEarly) {
@if (handInEarly()) {
<div class="mb-3">
<div class="mb-1 mt-3 fw-bold text-danger">
<fa-icon [icon]="faCircleExclamation" />
Expand All @@ -143,7 +143,7 @@ <h4 id="exam-finished-title">
id="confirmBox"
(click)="updateConfirmation()"
class="form-check-input me-2"
[class.ms-0]="!this.exam.confirmationEndText"
[class.ms-0]="!this.exam().confirmationEndText"
[required]="inserted"
/>
<label for="confirmBox" id="formatted-confirmation-text" class="form-check-label" [innerHTML]="formattedConfirmationText"></label>
Expand Down Expand Up @@ -193,20 +193,20 @@ <h4 id="exam-finished-title">
</div>
</div>
</div>
@if (handInEarly) {
@if (handInEarly()) {
<div class="mt-3">
<div class="mb-2 font-weight-bold text-secondary" jhiTranslate="artemisApp.examParticipation.continueAfterHandInEarlyDescription"></div>
</div>
}
<div class="d-flex justify-content-end gap-3">
@if (handInEarly) {
<button [disabled]="submitInProgress" id="continue" class="btn btn-secondary" (click)="continueAfterHandInEarly()">
@if (handInEarly()) {
<button [disabled]="submitInProgress()" id="continue" class="btn btn-secondary" (click)="continueAfterHandInEarly()">
<fa-icon [icon]="faArrowLeft" />
<span jhiTranslate="artemisApp.examParticipation.continueAfterHandInEarly"></span>
</button>
}
<button id="end-exam" [disabled]="!endButtonEnabled" type="submit" (click)="submitExam()" class="btn btn-primary">
@if (submitInProgress) {
@if (submitInProgress()) {
<fa-icon class="me-1" [icon]="faSpinner" animation="spin" />
} @else {
<fa-icon class="me-1" [icon]="faDoorClosed" />
Expand Down
Loading
Loading