diff --git a/app/controllers/user/SessionController.java b/app/controllers/user/SessionController.java index 6b55ef0dc..2f6f5c7ef 100644 --- a/app/controllers/user/SessionController.java +++ b/app/controllers/user/SessionController.java @@ -76,7 +76,9 @@ public class SessionController extends BaseController { "x-exam-wrong-room", "wrongRoomData", "x-exam-wrong-agent-config", - "wrongAgent" + "wrongAgent", + "x-exam-aquarium-login", + "aquariumLogin" ); @Inject diff --git a/app/repository/EnrolmentRepository.java b/app/repository/EnrolmentRepository.java index bbeddff60..15e1f8ef5 100644 --- a/app/repository/EnrolmentRepository.java +++ b/app/repository/EnrolmentRepository.java @@ -288,6 +288,8 @@ private void handleUpcomingEnrolment( if ( enrolment.getExam() != null && enrolment.getExam().getImplementation() == Exam.Implementation.AQUARIUM ) { + // If user is on a correct aquarium machine then always set a header + headers.put("x-exam-aquarium-login", "true"); // Aquarium exam, don't set headers unless it starts in 5 minutes DateTime threshold = DateTime.now().plusMinutes(5); DateTime start = dateTimeHandler.normalize( diff --git a/app/system/SystemFilter.scala b/app/system/SystemFilter.scala index a1f377a9c..126b8f0e5 100644 --- a/app/system/SystemFilter.scala +++ b/app/system/SystemFilter.scala @@ -27,7 +27,8 @@ class SystemFilter @Inject() (implicit val mat: Materializer, ec: ExecutionConte ("x-exam-wrong-machine", "wrongMachineData"), ("x-exam-unknown-machine", "unknownMachineData"), ("x-exam-wrong-room", "wrongRoomData"), - ("x-exam-wrong-agent-config", "wrongAgent") + ("x-exam-wrong-agent-config", "wrongAgent"), + ("x-exam-aquarium-login", "aquariumLogin") ) import ResultImplicits._ diff --git a/ui/src/app/administrative/reports/categories/answers-report.component.ts b/ui/src/app/administrative/reports/categories/answers-report.component.ts index 7e37d1ab4..09d3ef80b 100644 --- a/ui/src/app/administrative/reports/categories/answers-report.component.ts +++ b/ui/src/app/administrative/reports/categories/answers-report.component.ts @@ -3,7 +3,6 @@ // SPDX-License-Identifier: EUPL-1.2 import { Component } from '@angular/core'; -import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; import { format } from 'date-fns'; import { DatePickerComponent } from 'src/app/shared/date/date-picker.component'; @@ -39,7 +38,7 @@ import { FileService } from 'src/app/shared/file/file.service'; `, selector: 'xm-answers-report', standalone: true, - imports: [DatePickerComponent, NgbPopover, TranslateModule], + imports: [DatePickerComponent, TranslateModule], }) export class AnswersReportComponent { startDate: Date | null = null; diff --git a/ui/src/app/administrative/reports/categories/enrolments-report.component.ts b/ui/src/app/administrative/reports/categories/enrolments-report.component.ts index 347ee0c8e..7fb775bec 100644 --- a/ui/src/app/administrative/reports/categories/enrolments-report.component.ts +++ b/ui/src/app/administrative/reports/categories/enrolments-report.component.ts @@ -3,7 +3,6 @@ // SPDX-License-Identifier: EUPL-1.2 import { Component, Input } from '@angular/core'; -import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { ToastrService } from 'ngx-toastr'; import { FileService } from 'src/app/shared/file/file.service'; @@ -36,7 +35,7 @@ import { Option } from 'src/app/shared/select/select.model'; `, selector: 'xm-enrolments-report', standalone: true, - imports: [DropdownSelectComponent, NgbPopover, TranslateModule], + imports: [DropdownSelectComponent, TranslateModule], }) export class EnrolmentsReportComponent { @Input() examNames: Option[] = []; diff --git a/ui/src/app/administrative/reports/categories/exams-report.component.ts b/ui/src/app/administrative/reports/categories/exams-report.component.ts index 8efed3ca5..955483c4f 100644 --- a/ui/src/app/administrative/reports/categories/exams-report.component.ts +++ b/ui/src/app/administrative/reports/categories/exams-report.component.ts @@ -3,7 +3,6 @@ // SPDX-License-Identifier: EUPL-1.2 import { Component, Input } from '@angular/core'; -import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { ToastrService } from 'ngx-toastr'; import { FileService } from 'src/app/shared/file/file.service'; @@ -39,7 +38,7 @@ import { Option } from 'src/app/shared/select/select.model'; `, selector: 'xm-exams-report', standalone: true, - imports: [DropdownSelectComponent, NgbPopover, TranslateModule], + imports: [DropdownSelectComponent, TranslateModule], }) export class ExamsReportComponent { @Input() examNames: Option[] = []; diff --git a/ui/src/app/administrative/reports/categories/records-report.component.ts b/ui/src/app/administrative/reports/categories/records-report.component.ts index 9724e8315..5b27bb51c 100644 --- a/ui/src/app/administrative/reports/categories/records-report.component.ts +++ b/ui/src/app/administrative/reports/categories/records-report.component.ts @@ -3,7 +3,6 @@ // SPDX-License-Identifier: EUPL-1.2 import { Component } from '@angular/core'; -import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; import { DatePickerComponent } from 'src/app/shared/date/date-picker.component'; import { FileService } from 'src/app/shared/file/file.service'; @@ -38,7 +37,7 @@ import { FileService } from 'src/app/shared/file/file.service'; `, selector: 'xm-records-report', standalone: true, - imports: [DatePickerComponent, NgbPopover, TranslateModule], + imports: [DatePickerComponent, TranslateModule], }) export class RecordsReportComponent { startDate: Date | null = null; diff --git a/ui/src/app/administrative/reports/categories/reviews-report.component.ts b/ui/src/app/administrative/reports/categories/reviews-report.component.ts index a29754b02..0f3b36dc1 100644 --- a/ui/src/app/administrative/reports/categories/reviews-report.component.ts +++ b/ui/src/app/administrative/reports/categories/reviews-report.component.ts @@ -3,7 +3,6 @@ // SPDX-License-Identifier: EUPL-1.2 import { Component } from '@angular/core'; -import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule } from '@ngx-translate/core'; import { format } from 'date-fns'; import { DatePickerComponent } from 'src/app/shared/date/date-picker.component'; @@ -38,7 +37,7 @@ import { FileService } from 'src/app/shared/file/file.service'; `, selector: 'xm-reviews-report', standalone: true, - imports: [DatePickerComponent, NgbPopover, TranslateModule], + imports: [DatePickerComponent, TranslateModule], }) export class ReviewsReportComponent { startDate: Date | null = null; diff --git a/ui/src/app/administrative/reports/categories/rooms-report.component.ts b/ui/src/app/administrative/reports/categories/rooms-report.component.ts index 0e1c70f02..a92e8b4ed 100644 --- a/ui/src/app/administrative/reports/categories/rooms-report.component.ts +++ b/ui/src/app/administrative/reports/categories/rooms-report.component.ts @@ -3,7 +3,6 @@ // SPDX-License-Identifier: EUPL-1.2 import { Component, Input } from '@angular/core'; -import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { format } from 'date-fns'; import { ToastrService } from 'ngx-toastr'; @@ -53,7 +52,7 @@ import { Option } from 'src/app/shared/select/select.model'; `, selector: 'xm-rooms-report', standalone: true, - imports: [DropdownSelectComponent, DatePickerComponent, NgbPopover, TranslateModule], + imports: [DropdownSelectComponent, DatePickerComponent, TranslateModule], }) export class RoomsReportComponent { @Input() rooms: Option[] = []; diff --git a/ui/src/app/administrative/reports/categories/students-report.component.ts b/ui/src/app/administrative/reports/categories/students-report.component.ts index 80cb61f2a..c26a532b2 100644 --- a/ui/src/app/administrative/reports/categories/students-report.component.ts +++ b/ui/src/app/administrative/reports/categories/students-report.component.ts @@ -3,7 +3,6 @@ // SPDX-License-Identifier: EUPL-1.2 import { Component, Input } from '@angular/core'; -import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { format } from 'date-fns'; import { ToastrService } from 'ngx-toastr'; @@ -51,7 +50,7 @@ import { Option } from 'src/app/shared/select/select.model'; `, selector: 'xm-students-report', standalone: true, - imports: [DropdownSelectComponent, DatePickerComponent, NgbPopover, TranslateModule], + imports: [DropdownSelectComponent, DatePickerComponent, TranslateModule], }) export class StudentsReportComponent { @Input() students: Option[] = []; diff --git a/ui/src/app/administrative/reports/categories/teachers-report.component.ts b/ui/src/app/administrative/reports/categories/teachers-report.component.ts index 5b400aa98..60fc294e6 100644 --- a/ui/src/app/administrative/reports/categories/teachers-report.component.ts +++ b/ui/src/app/administrative/reports/categories/teachers-report.component.ts @@ -3,7 +3,6 @@ // SPDX-License-Identifier: EUPL-1.2 import { Component, Input } from '@angular/core'; -import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { format } from 'date-fns'; import { ToastrService } from 'ngx-toastr'; @@ -51,7 +50,7 @@ import { Option } from 'src/app/shared/select/select.model'; `, selector: 'xm-teachers-report', standalone: true, - imports: [DropdownSelectComponent, DatePickerComponent, NgbPopover, TranslateModule], + imports: [DropdownSelectComponent, DatePickerComponent, TranslateModule], }) export class TeachersReportComponent { @Input() teachers: Option[] = []; diff --git a/ui/src/app/app.routes.ts b/ui/src/app/app.routes.ts index 92f291b31..6e878347d 100644 --- a/ui/src/app/app.routes.ts +++ b/ui/src/app/app.routes.ts @@ -58,6 +58,14 @@ export const APP_ROUTES: Route[] = [ import('./enrolment/waiting-room/waiting-room.component').then((mod) => mod.WaitingRoomComponent), title: () => buildTitle('i18n_waiting_room_title'), }, + { + path: 'early', + loadComponent: () => + import('./enrolment/waiting-room/waiting-room-early.component').then( + (mod) => mod.WaitingRoomEarlyComponent, + ), + title: () => buildTitle('i18n_waiting_room_title'), + }, { path: 'wrongroom/:eid/:mid', loadComponent: () => diff --git a/ui/src/app/calendar/calendar.component.ts b/ui/src/app/calendar/calendar.component.ts index 858c69f93..68e88985c 100644 --- a/ui/src/app/calendar/calendar.component.ts +++ b/ui/src/app/calendar/calendar.component.ts @@ -16,9 +16,7 @@ import { PageContentComponent } from 'src/app/shared/components/page-content.com import { PageHeaderComponent } from 'src/app/shared/components/page-header.component'; import { DateTimeService } from 'src/app/shared/date/date.service'; import { ConfirmationDialogService } from 'src/app/shared/dialogs/confirmation-dialog.service'; -import { HistoryBackComponent } from 'src/app/shared/history/history-back.component'; import { CourseCodeComponent } from 'src/app/shared/miscellaneous/course-code.component'; -import { AutoFocusDirective } from 'src/app/shared/select/auto-focus.directive'; import { ExamInfo, Organisation } from './calendar.model'; import { CalendarService } from './calendar.service'; import { CalendarExamInfoComponent } from './helpers/exam-info.component'; @@ -32,8 +30,6 @@ import { SlotPickerComponent } from './helpers/slot-picker.component'; styleUrls: ['./calendar.component.scss'], standalone: true, imports: [ - HistoryBackComponent, - AutoFocusDirective, CalendarExamInfoComponent, OptionalSectionsComponent, OrganisationPickerComponent, diff --git a/ui/src/app/calendar/helpers/selected-room.component.ts b/ui/src/app/calendar/helpers/selected-room.component.ts index 787032eba..8a1f728b7 100644 --- a/ui/src/app/calendar/helpers/selected-room.component.ts +++ b/ui/src/app/calendar/helpers/selected-room.component.ts @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: EUPL-1.2 -import { DatePipe, NgClass, NgIf, UpperCasePipe } from '@angular/common'; +import { DatePipe, NgClass, UpperCasePipe } from '@angular/common'; import { Component, Input, OnChanges, OnInit, signal } from '@angular/core'; import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; @@ -71,7 +71,9 @@ import { OrderByPipe } from 'src/app/shared/sorting/order-by.pipe'; {{ period.startsAt | date: 'dd.MM.yyyy HH:mm' }} - {{ period.endsAt | date: 'dd.MM.yyyy HH:mm' }} {{ period.description }} - (remote) + @if (period.remote) { + (remote) + } } @@ -96,7 +98,7 @@ import { OrderByPipe } from 'src/app/shared/sorting/order-by.pipe'; `, styleUrls: ['../calendar.component.scss'], standalone: true, - imports: [NgClass, NgIf, NgbPopover, UpperCasePipe, DatePipe, TranslateModule, OrderByPipe], + imports: [NgClass, NgbPopover, UpperCasePipe, DatePipe, TranslateModule, OrderByPipe], }) export class SelectedRoomComponent implements OnInit, OnChanges { @Input() room!: ExamRoom; diff --git a/ui/src/app/calendar/helpers/slot-picker.component.ts b/ui/src/app/calendar/helpers/slot-picker.component.ts index 1d009896e..a26bad08e 100644 --- a/ui/src/app/calendar/helpers/slot-picker.component.ts +++ b/ui/src/app/calendar/helpers/slot-picker.component.ts @@ -8,13 +8,7 @@ import type { SimpleChanges } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewEncapsulation, signal } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { EventApi, EventInput } from '@fullcalendar/core'; -import { - NgbCollapse, - NgbDropdown, - NgbDropdownItem, - NgbDropdownMenu, - NgbDropdownToggle, -} from '@ng-bootstrap/ng-bootstrap'; +import { NgbDropdown, NgbDropdownItem, NgbDropdownMenu, NgbDropdownToggle } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { DateTime } from 'luxon'; import { ToastrService } from 'ngx-toastr'; @@ -39,7 +33,6 @@ type AvailableSlot = Slot & { availableMachines: number }; standalone: true, imports: [ NgClass, - NgbCollapse, NgbDropdown, NgbDropdownToggle, NgbDropdownMenu, diff --git a/ui/src/app/dashboard/staff/teacher/categories/exam-list-category.component.ts b/ui/src/app/dashboard/staff/teacher/categories/exam-list-category.component.ts index 86bb13a1a..69ebab6aa 100644 --- a/ui/src/app/dashboard/staff/teacher/categories/exam-list-category.component.ts +++ b/ui/src/app/dashboard/staff/teacher/categories/exam-list-category.component.ts @@ -7,7 +7,7 @@ import type { OnInit } from '@angular/core'; import { Component, EventEmitter, Input, OnDestroy, Output } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { Router, RouterLink } from '@angular/router'; -import { NgbModal, NgbPopover } from '@ng-bootstrap/ng-bootstrap'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { TranslateModule, TranslateService } from '@ngx-translate/core'; import { ToastrService } from 'ngx-toastr'; import { Subject, from } from 'rxjs'; @@ -37,7 +37,6 @@ import { TeacherListComponent } from 'src/app/shared/user/teacher-list.component RouterLink, CourseCodeComponent, TeacherListComponent, - NgbPopover, DatePipe, TranslateModule, OrderByPipe, diff --git a/ui/src/app/enrolment/exams/exam-enrolment-details.component.ts b/ui/src/app/enrolment/exams/exam-enrolment-details.component.ts index bbebe2640..38b75de43 100644 --- a/ui/src/app/enrolment/exams/exam-enrolment-details.component.ts +++ b/ui/src/app/enrolment/exams/exam-enrolment-details.component.ts @@ -11,7 +11,6 @@ import { EnrolmentService } from 'src/app/enrolment/enrolment.service'; import { PageContentComponent } from 'src/app/shared/components/page-content.component'; import { PageHeaderComponent } from 'src/app/shared/components/page-header.component'; import { DateTimeService } from 'src/app/shared/date/date.service'; -import { HistoryBackComponent } from 'src/app/shared/history/history-back.component'; import { MathJaxDirective } from 'src/app/shared/math/math-jax.directive'; import { CommonExamService } from 'src/app/shared/miscellaneous/common-exam.service'; import { CourseCodeComponent } from 'src/app/shared/miscellaneous/course-code.component'; @@ -22,7 +21,6 @@ import { TeacherListComponent } from 'src/app/shared/user/teacher-list.component templateUrl: './exam-enrolment-details.component.html', standalone: true, imports: [ - HistoryBackComponent, CourseCodeComponent, TeacherListComponent, MathJaxDirective, diff --git a/ui/src/app/enrolment/finished/collaborative-exam-participations.component.ts b/ui/src/app/enrolment/finished/collaborative-exam-participations.component.ts index a3548a92f..1d1fedaa6 100644 --- a/ui/src/app/enrolment/finished/collaborative-exam-participations.component.ts +++ b/ui/src/app/enrolment/finished/collaborative-exam-participations.component.ts @@ -14,7 +14,6 @@ import { EnrolmentService } from 'src/app/enrolment/enrolment.service'; import { PageContentComponent } from 'src/app/shared/components/page-content.component'; import { PageHeaderComponent } from 'src/app/shared/components/page-header.component'; import { PaginatorComponent } from 'src/app/shared/paginator/paginator.component'; -import { AutoFocusDirective } from 'src/app/shared/select/auto-focus.directive'; import { OrderByPipe } from 'src/app/shared/sorting/order-by.pipe'; import { ExamParticipationComponent } from './exam-participation.component'; @@ -24,7 +23,6 @@ import { ExamParticipationComponent } from './exam-participation.component'; standalone: true, imports: [ FormsModule, - AutoFocusDirective, NgbDropdown, NgbDropdownToggle, NgbDropdownMenu, diff --git a/ui/src/app/enrolment/finished/exam-participations.component.ts b/ui/src/app/enrolment/finished/exam-participations.component.ts index dd53c83f4..1ffb8eb0e 100644 --- a/ui/src/app/enrolment/finished/exam-participations.component.ts +++ b/ui/src/app/enrolment/finished/exam-participations.component.ts @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: EUPL-1.2 -import { NgFor, NgIf, SlicePipe } from '@angular/common'; +import { SlicePipe } from '@angular/common'; import type { OnInit } from '@angular/core'; import { Component, OnDestroy } from '@angular/core'; import { FormsModule } from '@angular/forms'; @@ -16,7 +16,6 @@ import { EnrolmentService } from 'src/app/enrolment/enrolment.service'; import { PageContentComponent } from 'src/app/shared/components/page-content.component'; import { PageHeaderComponent } from 'src/app/shared/components/page-header.component'; import { PaginatorComponent } from 'src/app/shared/paginator/paginator.component'; -import { AutoFocusDirective } from 'src/app/shared/select/auto-focus.directive'; import { OrderByPipe } from 'src/app/shared/sorting/order-by.pipe'; import { ExamParticipationComponent } from './exam-participation.component'; @@ -26,14 +25,11 @@ import { ExamParticipationComponent } from './exam-participation.component'; standalone: true, imports: [ FormsModule, - AutoFocusDirective, NgbDropdown, NgbDropdownToggle, NgbDropdownMenu, NgbDropdownItem, - NgFor, ExamParticipationComponent, - NgIf, PaginatorComponent, SlicePipe, TranslateModule, diff --git a/ui/src/app/enrolment/waiting-room/waiting-room-early.component.ts b/ui/src/app/enrolment/waiting-room/waiting-room-early.component.ts new file mode 100644 index 000000000..4c9b7bf17 --- /dev/null +++ b/ui/src/app/enrolment/waiting-room/waiting-room-early.component.ts @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2024 The members of the EXAM Consortium +// +// SPDX-License-Identifier: EUPL-1.2 + +import { Component } from '@angular/core'; +import { TranslateModule } from '@ngx-translate/core'; +import { PageContentComponent } from 'src/app/shared/components/page-content.component'; +import { PageHeaderComponent } from 'src/app/shared/components/page-header.component'; + +@Component({ + selector: 'xm-waiting-room-early', + template: ` + + + +
+
+ + {{ 'i18n_redirect_to_exam_notice' | translate }} +
+
+
+ `, + styleUrls: ['../enrolment.shared.scss'], + standalone: true, + imports: [TranslateModule, PageHeaderComponent, PageContentComponent], +}) +export class WaitingRoomEarlyComponent {} diff --git a/ui/src/app/exam/editor/assessment/exam-assessment.component.ts b/ui/src/app/exam/editor/assessment/exam-assessment.component.ts index a808a711d..e3e999275 100644 --- a/ui/src/app/exam/editor/assessment/exam-assessment.component.ts +++ b/ui/src/app/exam/editor/assessment/exam-assessment.component.ts @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2024 The members of the EXAM Consortium +// +// SPDX-License-Identifier: EUPL-1.2 + import { NgClass } from '@angular/common'; import { HttpClient } from '@angular/common/http'; import { Component, OnDestroy, OnInit } from '@angular/core'; @@ -12,10 +16,6 @@ import { ExamService } from 'src/app/exam/exam.service'; import { AutoEvaluationComponent } from './auto-evaluation.component'; import { ExamFeedbackConfigComponent } from './exam-feedback-config.component'; -// SPDX-FileCopyrightText: 2024 The members of the EXAM Consortium -// -// SPDX-License-Identifier: EUPL-1.2 - @Component({ selector: 'xm-exam-assessment', templateUrl: './exam-assessment.component.html', diff --git a/ui/src/app/exam/editor/common/course-picker.service.ts b/ui/src/app/exam/editor/common/course-picker.service.ts index dfa9f9e6f..0a2ea7941 100644 --- a/ui/src/app/exam/editor/common/course-picker.service.ts +++ b/ui/src/app/exam/editor/common/course-picker.service.ts @@ -11,7 +11,5 @@ export class CoursePickerService { constructor(private http: HttpClient) {} getCourses$ = (filter: string, criteria: string) => - this.http.get('/app/courses', { - params: { filter: filter, q: criteria }, - }); + this.http.get('/app/courses', { params: { filter: filter, q: criteria } }); } diff --git a/ui/src/app/exam/editor/creation/course-selection.component.ts b/ui/src/app/exam/editor/creation/course-selection.component.ts index ad2f44bf7..66b455925 100644 --- a/ui/src/app/exam/editor/creation/course-selection.component.ts +++ b/ui/src/app/exam/editor/creation/course-selection.component.ts @@ -17,14 +17,12 @@ import { ExamService } from 'src/app/exam/exam.service'; import { SessionService } from 'src/app/session/session.service'; import { PageContentComponent } from 'src/app/shared/components/page-content.component'; import { PageHeaderComponent } from 'src/app/shared/components/page-header.component'; -import { HistoryBackComponent } from 'src/app/shared/history/history-back.component'; @Component({ selector: 'xm-course-selection', templateUrl: './course-selection.component.html', standalone: true, imports: [ - HistoryBackComponent, NgbPopover, ExamCourseComponent, FormsModule, diff --git a/ui/src/app/exam/editor/creation/new-exam.component.ts b/ui/src/app/exam/editor/creation/new-exam.component.ts index 6569eb183..2c081ad8a 100644 --- a/ui/src/app/exam/editor/creation/new-exam.component.ts +++ b/ui/src/app/exam/editor/creation/new-exam.component.ts @@ -13,20 +13,12 @@ import { ExamService } from 'src/app/exam/exam.service'; import { SessionService } from 'src/app/session/session.service'; import { PageContentComponent } from 'src/app/shared/components/page-content.component'; import { PageHeaderComponent } from 'src/app/shared/components/page-header.component'; -import { HistoryBackComponent } from 'src/app/shared/history/history-back.component'; @Component({ selector: 'xm-new-exam', templateUrl: './new-exam.component.html', standalone: true, - imports: [ - HistoryBackComponent, - FormsModule, - NgbPopover, - TranslateModule, - PageHeaderComponent, - PageContentComponent, - ], + imports: [FormsModule, NgbPopover, TranslateModule, PageHeaderComponent, PageContentComponent], }) export class NewExamComponent implements OnInit { executionTypes: (ExamExecutionType & { name: string })[] = []; diff --git a/ui/src/app/exam/editor/publication/exam-publication.component.ts b/ui/src/app/exam/editor/publication/exam-publication.component.ts index 41b27e0b3..9cd5082bb 100644 --- a/ui/src/app/exam/editor/publication/exam-publication.component.ts +++ b/ui/src/app/exam/editor/publication/exam-publication.component.ts @@ -22,11 +22,9 @@ import { SessionService } from 'src/app/session/session.service'; import { DatePickerComponent } from 'src/app/shared/date/date-picker.component'; import { isBoolean } from 'src/app/shared/miscellaneous/helpers'; import { OrderByPipe } from 'src/app/shared/sorting/order-by.pipe'; -import { CollaborativeExamOwnerSelectorComponent } from './collaborative-exam-owner-picker.component'; import { CustomDurationPickerDialogComponent } from './custom-duration-picker-dialog.component'; import { ExamPublicationParticipantsComponent } from './exam-publication-participants.component'; import { ExaminationEventsComponent } from './examination-events.component'; -import { OrganisationSelectorComponent } from './organisation-picker.component'; import { PublicationDialogComponent } from './publication-dialog.component'; import { PublicationErrorDialogComponent } from './publication-error-dialog.component'; import { PublicationRevocationDialogComponent } from './publication-revocation-dialog.component'; @@ -41,8 +39,6 @@ import { PublicationRevocationDialogComponent } from './publication-revocation-d NgbPopover, NgClass, ExamPublicationParticipantsComponent, - CollaborativeExamOwnerSelectorComponent, - OrganisationSelectorComponent, ExaminationEventsComponent, UpperCasePipe, DatePipe, diff --git a/ui/src/app/examination/examination-status.service.ts b/ui/src/app/examination/examination-status.service.ts index 9d8c8936e..28434e227 100644 --- a/ui/src/app/examination/examination-status.service.ts +++ b/ui/src/app/examination/examination-status.service.ts @@ -4,7 +4,7 @@ import { Injectable } from '@angular/core'; import type { Observable } from 'rxjs'; -import { Subject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class ExaminationStatusService { @@ -12,20 +12,24 @@ export class ExaminationStatusService { public wrongLocation$: Observable; public upcomingExam$: Observable; public examinationStarting$: Observable; + public aquariumLoggedIn$: Observable; private examinationEndingSubscription = new Subject(); private wrongLocationSubscription = new Subject(); private upcomingExamSubscription = new Subject(); private examinationStartingSubscription = new Subject(); + private aquariumLoggedInSubscription = new BehaviorSubject(true); constructor() { this.examinationEnding$ = this.examinationEndingSubscription.asObservable(); this.wrongLocation$ = this.wrongLocationSubscription.asObservable(); this.upcomingExam$ = this.upcomingExamSubscription.asObservable(); this.examinationStarting$ = this.examinationStartingSubscription.asObservable(); + this.aquariumLoggedIn$ = this.aquariumLoggedInSubscription.asObservable(); } notifyEndOfExamination = () => this.examinationEndingSubscription.next(); notifyWrongLocation = () => this.wrongLocationSubscription.next(); notifyUpcomingExamination = () => this.upcomingExamSubscription.next(); notifyStartOfExamination = () => this.examinationStartingSubscription.next(); + notifyAquariumLogin = () => this.aquariumLoggedInSubscription.next(true); } diff --git a/ui/src/app/interceptors/examination-interceptor.ts b/ui/src/app/interceptors/examination-interceptor.ts index 08f12689e..bfcb85e8b 100644 --- a/ui/src/app/interceptors/examination-interceptor.ts +++ b/ui/src/app/interceptors/examination-interceptor.ts @@ -23,6 +23,7 @@ export class ExaminationInterceptor implements HttpInterceptor { tap((event: HttpEvent) => { if (event instanceof HttpResponse) { const response = event as HttpResponse; + const onExamMachine = response.headers.get('x-exam-aquarium-login'); const unknownMachine = response.headers.get('x-exam-unknown-machine'); const wrongRoom = response.headers.get('x-exam-wrong-room'); const wrongMachine = response.headers.get('x-exam-wrong-machine'); @@ -57,6 +58,9 @@ export class ExaminationInterceptor implements HttpInterceptor { // Start/continue exam this.ExaminationStatus.notifyStartOfExamination(); this.router.navigate(['/exam', hash]); + } else if (onExamMachine) { + this.ExaminationStatus.notifyAquariumLogin(); + this.router.navigate(['/early']); } } }), diff --git a/ui/src/app/navigation/navigation.component.ts b/ui/src/app/navigation/navigation.component.ts index eab85b587..e79b206f2 100644 --- a/ui/src/app/navigation/navigation.component.ts +++ b/ui/src/app/navigation/navigation.component.ts @@ -28,6 +28,7 @@ export class NavigationComponent implements OnInit, OnDestroy { links: Link[] = []; mobileMenuOpen = false; user?: User; + stateInitialized = false; private ngUnsubscribe = new Subject(); constructor( @@ -37,13 +38,18 @@ export class NavigationComponent implements OnInit, OnDestroy { private ExaminationStatus: ExaminationStatusService, ) { this.user = this.Session.getUser(); - this.ExaminationStatus.examinationStarting$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(() => { - this.getLinks(false); - }); - this.ExaminationStatus.upcomingExam$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(() => this.getLinks(false)); - this.ExaminationStatus.wrongLocation$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(() => { - this.getLinks(false); - }); + this.ExaminationStatus.examinationStarting$ + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe(() => this.getLinks(false, false)); + this.ExaminationStatus.upcomingExam$ + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe(() => this.getLinks(false, false)); + this.ExaminationStatus.wrongLocation$ + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe(() => this.getLinks(false, false)); + this.ExaminationStatus.aquariumLoggedIn$ + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe(() => this.getLinks(false, false)); this.Session.userChange$.pipe(takeUntil(this.ngUnsubscribe)).subscribe((user: User | undefined) => { this.user = user; this.getLinks(true); @@ -51,22 +57,22 @@ export class NavigationComponent implements OnInit, OnDestroy { } ngOnInit() { - this.user = this.Session.getUser(); - if (this.user && this.user.isAdmin) { - this.Navigation.getAppVersion$().subscribe({ - next: (resp) => (this.appVersion = resp.appVersion), - error: (err) => this.toast.error(err), - }); - this.getLinks(true, true); - } else if (this.user) { - this.getLinks(true); - } else { - this.getLinks(false); - } - } - - isActive(link: Link): boolean { - return window.location.href.includes(link.route); + // Add a small timeout because there is some race condition/view update problem with initial link + // loading if there is an examination starting or started. To be fixed properly if solution found. + window.setTimeout(() => { + this.user = this.Session.getUser(); + if (this.user?.isAdmin) { + this.Navigation.getAppVersion$().subscribe({ + next: (resp) => (this.appVersion = resp.appVersion), + error: (err) => this.toast.error(err), + }); + this.getLinks(true, true); + } else if (this.user) { + this.getLinks(true); + } else { + this.getLinks(false); + } + }, 200); } ngOnDestroy() { @@ -74,9 +80,10 @@ export class NavigationComponent implements OnInit, OnDestroy { this.ngUnsubscribe.complete(); } - getSkipLinkPath = (skipTarget: string) => { - return window.location.toString().includes(skipTarget) ? window.location : window.location + skipTarget; - }; + isActive = (link: Link) => window.location.href.includes(link.route); + + getSkipLinkPath = (skipTarget: string) => + window.location.toString().includes(skipTarget) ? window.location : window.location + skipTarget; openMenu = () => (this.mobileMenuOpen = !this.mobileMenuOpen); diff --git a/ui/src/app/navigation/navigation.service.ts b/ui/src/app/navigation/navigation.service.ts index 7bd80ff22..74aa97748 100644 --- a/ui/src/app/navigation/navigation.service.ts +++ b/ui/src/app/navigation/navigation.service.ts @@ -38,8 +38,7 @@ export class NavigationService { const languageInspector = user.isTeacher && user.isLanguageInspector; // Do not show if waiting for exam to begin - const regex = /waitingroom|wrongmachine|wrongroom/; - const hideDashboard = regex.test(this.router.url); + const hidden = /waitingroom|wrongmachine|wrongroom|early/.test(this.router.url); // Change the menu item title if student const nameForDashboard = student ? 'i18n_user_enrolled_exams_title' : 'i18n_dashboard'; @@ -77,7 +76,7 @@ export class NavigationService { return [ { route: student ? 'dashboard' : admin ? 'staff/admin' : 'staff/teacher', - visible: !hideDashboard, + visible: !hidden, name: nameForDashboard, iconPng: 'icon_enrols.svg', submenu: teacherCollaborativeExamsSubmenu, @@ -200,14 +199,14 @@ export class NavigationService { }, { route: 'exams', - visible: student && !hideDashboard, + visible: student && !hidden, name: 'i18n_exams', iconPng: 'icon_exams.png', submenu: studentCollaborativeExamsSubmenu, }, { route: 'participations', - visible: student && !hideDashboard, + visible: student && !hidden, name: 'i18n_exam_responses', iconPng: 'icon_finished.png', submenu: { diff --git a/ui/src/app/question/library/search/library-search.component.html b/ui/src/app/question/library/search/library-search.component.html index e7c320b47..cb3a50210 100644 --- a/ui/src/app/question/library/search/library-search.component.html +++ b/ui/src/app/question/library/search/library-search.component.html @@ -28,16 +28,17 @@ /> - + @for (course of filteredCourses; track course.id) { + + } @@ -65,18 +66,21 @@ - + @for (exam of filteredExams; track exam.id) { + + } @@ -106,23 +110,21 @@ - + @if (tag.usage && tag.usage > 0) { + + } + + } @@ -152,19 +154,19 @@ - - + @for (section of filteredSections; track section.id) { + + } @@ -193,18 +195,19 @@ - + @for (owner of filteredOwners; track owner.id) { + + } diff --git a/ui/src/app/question/library/search/library-search.component.ts b/ui/src/app/question/library/search/library-search.component.ts index dbd5d5357..1c85098a0 100644 --- a/ui/src/app/question/library/search/library-search.component.ts +++ b/ui/src/app/question/library/search/library-search.component.ts @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: EUPL-1.2 -import { NgClass, NgForOf } from '@angular/common'; +import { NgForOf } from '@angular/common'; import type { OnInit } from '@angular/core'; import { Component, EventEmitter, Output } from '@angular/core'; import { FormsModule } from '@angular/forms'; @@ -33,16 +33,7 @@ interface Filterable { selector: 'xm-library-search', templateUrl: './library-search.component.html', standalone: true, - imports: [ - NgClass, - NgbDropdown, - NgbDropdownToggle, - NgbDropdownMenu, - NgbDropdownItem, - FormsModule, - TranslateModule, - NgForOf, - ], + imports: [NgbDropdown, NgbDropdownToggle, NgbDropdownMenu, NgbDropdownItem, FormsModule, TranslateModule, NgForOf], }) export class LibrarySearchComponent implements OnInit { @Output() updated: EventEmitter = new EventEmitter(); diff --git a/ui/src/app/shared/select/dropdown-select.component.ts b/ui/src/app/shared/select/dropdown-select.component.ts index 321a2dd14..fadfaf518 100644 --- a/ui/src/app/shared/select/dropdown-select.component.ts +++ b/ui/src/app/shared/select/dropdown-select.component.ts @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: EUPL-1.2 -import { NgClass, NgIf, SlicePipe } from '@angular/common'; +import { NgClass, SlicePipe } from '@angular/common'; import type { OnChanges, OnInit } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core'; import { FormsModule } from '@angular/forms'; @@ -41,9 +41,11 @@ import { Option } from './select.model'; } - + @if (allowClearing) { + + } @for (opt of filteredOptions; track $index) {