From dc9acd132deb60b20df74f9d61d49434d256664f Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 20:39:45 +0100 Subject: [PATCH 001/125] Adhere to standard structure in lecture-update.component.ts --- .../app/lecture/lecture-update.component.ts | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 8e8fbd4f6dcb..1400b0e39b6b 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -21,7 +21,16 @@ import { FormulaAction } from 'app/shared/monaco-editor/model/actions/formula.ac styleUrls: ['./lecture-update.component.scss'], }) export class LectureUpdateComponent implements OnInit { - readonly documentationType: DocumentationType = 'Lecture'; + protected readonly documentationType: DocumentationType = 'Lecture'; + protected readonly faQuestionCircle = faQuestionCircle; + protected readonly faSave = faSave; + protected readonly faPuzzleProcess = faPuzzlePiece; + protected readonly faBan = faBan; + protected readonly faHandShakeAngle = faHandshakeAngle; + // A human-readable list of allowed file extensions + protected readonly allowedFileExtensions = UPLOAD_FILE_EXTENSIONS.join(', '); + // The list of file extensions for the "accept" attribute of the file input field + protected readonly acceptedFileExtensionsFileBrowser = UPLOAD_FILE_EXTENSIONS.map((ext) => '.' + ext).join(','); @ViewChild(LectureUpdateWizardComponent, { static: false }) wizardComponent: LectureUpdateWizardComponent; @@ -38,18 +47,6 @@ export class LectureUpdateComponent implements OnInit { fileName: string; fileInputTouched = false; - // Icons - faQuestionCircle = faQuestionCircle; - faSave = faSave; - faPuzzleProcess = faPuzzlePiece; - faBan = faBan; - faHandShakeAngle = faHandshakeAngle; - - // A human-readable list of allowed file extensions - readonly allowedFileExtensions = UPLOAD_FILE_EXTENSIONS.join(', '); - // The list of file extensions for the "accept" attribute of the file input field - readonly acceptedFileExtensionsFileBrowser = UPLOAD_FILE_EXTENSIONS.map((ext) => '.' + ext).join(','); - toggleModeFunction = () => this.toggleWizardMode(); saveLectureFunction = () => this.save(); From 9474b66d29d99f0766bcc9b7d93eda531d0ecbc5 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 20:43:21 +0100 Subject: [PATCH 002/125] Refactor existing component --- .../app/lecture/lecture-update.component.html | 3 +-- .../wizard-mode/lecture-update-wizard.component.ts | 14 ++++---------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index 9ba49083c37c..a92b869651b0 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -8,8 +8,7 @@ [lecture]="lecture" [isSaving]="isSaving" /> - } - @if (!isShowingWizardMode) { + } @else {
diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts index d360ffc962ca..622fbe13c067 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts @@ -2,7 +2,6 @@ import { Component, Input, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { CourseManagementService } from 'app/course/manage/course-management.service'; import { Lecture } from 'app/entities/lecture.model'; -import { ArtemisNavigationUtilService } from 'app/utils/navigation.utils'; import { faArrowRight, faCheck, faHandshakeAngle } from '@fortawesome/free-solid-svg-icons'; import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; import { take } from 'rxjs/operators'; @@ -13,6 +12,10 @@ import { take } from 'rxjs/operators'; styleUrls: ['./lecture-update-wizard.component.scss'], }) export class LectureUpdateWizardComponent implements OnInit { + protected readonly faCheck = faCheck; + protected readonly faHandShakeAngle = faHandshakeAngle; + protected readonly faArrowRight = faArrowRight; + @Input() toggleModeFunction: () => void; @Input() saveLectureFunction: () => void; @Input() validateDatesFunction: () => void; @@ -28,21 +31,12 @@ export class LectureUpdateWizardComponent implements OnInit { currentStep: number; - // Icons - faCheck = faCheck; - faHandShakeAngle = faHandshakeAngle; - faArrowRight = faArrowRight; - constructor( protected courseService: CourseManagementService, protected activatedRoute: ActivatedRoute, - private navigationUtilService: ArtemisNavigationUtilService, private router: Router, ) {} - /** - * Life cycle hook called by Angular to indicate that Angular is done creating the component - */ ngOnInit() { this.isSaving = false; From 1c46b24a0bcb5490484766ccad35577231bc2aeb Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 20:43:58 +0100 Subject: [PATCH 003/125] Icons needed for test --- .../lecture/wizard-mode/lecture-update-wizard.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts index 622fbe13c067..dee7c167652a 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts @@ -12,9 +12,9 @@ import { take } from 'rxjs/operators'; styleUrls: ['./lecture-update-wizard.component.scss'], }) export class LectureUpdateWizardComponent implements OnInit { - protected readonly faCheck = faCheck; - protected readonly faHandShakeAngle = faHandshakeAngle; - protected readonly faArrowRight = faArrowRight; + readonly faCheck = faCheck; + readonly faHandShakeAngle = faHandshakeAngle; + readonly faArrowRight = faArrowRight; @Input() toggleModeFunction: () => void; @Input() saveLectureFunction: () => void; From 504db1702a5985ac9d084fdaca4061881f9461cb Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 20:46:00 +0100 Subject: [PATCH 004/125] Refactor comment to renamed method --- .../wizard-mode/lecture-update-wizard.component.html | 8 +++++++- .../wizard-mode/lecture-update-wizard.component.ts | 5 +---- .../lecture/wizard-mode/lecture-wizard.component.spec.ts | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.html b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.html index cb704901206e..43852c293ac7 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.html +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.html @@ -46,7 +46,13 @@
-
diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts index dee7c167652a..1df89ee2e72a 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts @@ -61,10 +61,7 @@ export class LectureUpdateWizardComponent implements OnInit { }); } - /** - * Progress to the next step of the wizard mode - */ - next() { + progressToNextStep() { if (this.currentStep === this.LECTURE_UPDATE_WIZARD_PERIOD_STEP || this.currentStep === this.LECTURE_UPDATE_WIZARD_UNIT_STEP) { this.saveLectureFunction(); return; diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts index 716de13900a0..269205bdac7f 100644 --- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts @@ -120,7 +120,7 @@ describe('LectureWizardComponent', () => { wizardComponentFixture.whenStable().then(() => { expect(wizardComponent.currentStep).toBe(1); - wizardComponent.next(); + wizardComponent.progressToNextStep(); expect(wizardComponent.currentStep).toBe(2); }); })); @@ -133,7 +133,7 @@ describe('LectureWizardComponent', () => { wizardComponentFixture.whenStable().then(() => { expect(wizardComponent.currentStep).toBe(4); - wizardComponent.next(); + wizardComponent.progressToNextStep(); expect(saveStub).toHaveBeenCalledOnce(); }); })); From 8292a5833816d70c43c5996ba548966245e1f334 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 21:07:18 +0100 Subject: [PATCH 005/125] Start adding the status bar --- .../app/lecture/lecture-update.component.html | 1 + .../app/lecture/lecture-update.component.ts | 53 +++++++++++++++++-- src/main/webapp/app/lecture/lecture.module.ts | 10 ++-- .../lecture-wizard-period.component.ts | 4 +- .../lecture-wizard-title.component.ts | 6 ++- .../lecture-wizard-period.component.spec.ts | 12 ++--- .../lecture-wizard-title.component.spec.ts | 10 ++-- .../lecture-wizard.component.spec.ts | 10 ++-- 8 files changed, 78 insertions(+), 28 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index a92b869651b0..f4f48b5db400 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -9,6 +9,7 @@ [isSaving]="isSaving" /> } @else { +
diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 1400b0e39b6b..0ed078cf3fe0 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -1,7 +1,7 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; -import { Observable } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; import { AlertService } from 'app/core/util/alert.service'; import { LectureService } from './lecture.service'; import { CourseManagementService } from '../course/manage/course-management.service'; @@ -14,13 +14,17 @@ import { faBan, faHandshakeAngle, faPuzzlePiece, faQuestionCircle, faSave } from import { LectureUpdateWizardComponent } from 'app/lecture/wizard-mode/lecture-update-wizard.component'; import { UPLOAD_FILE_EXTENSIONS } from 'app/shared/constants/file-extensions.constants'; import { FormulaAction } from 'app/shared/monaco-editor/model/actions/formula.action'; +import { ProgrammingExerciseInformationComponent } from 'app/exercises/programming/manage/update/update-components/information/programming-exercise-information.component'; +import { ProgrammingExerciseDifficultyComponent } from 'app/exercises/programming/manage/update/update-components/difficulty/programming-exercise-difficulty.component'; +import { FormSectionStatus } from 'app/forms/form-status-bar/form-status-bar.component'; +import { LectureUpdatePeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; @Component({ selector: 'jhi-lecture-update', templateUrl: './lecture-update.component.html', styleUrls: ['./lecture-update.component.scss'], }) -export class LectureUpdateComponent implements OnInit { +export class LectureUpdateComponent implements OnInit, AfterViewInit, OnDestroy { protected readonly documentationType: DocumentationType = 'Lecture'; protected readonly faQuestionCircle = faQuestionCircle; protected readonly faSave = faSave; @@ -33,6 +37,8 @@ export class LectureUpdateComponent implements OnInit { protected readonly acceptedFileExtensionsFileBrowser = UPLOAD_FILE_EXTENSIONS.map((ext) => '.' + ext).join(','); @ViewChild(LectureUpdateWizardComponent, { static: false }) wizardComponent: LectureUpdateWizardComponent; + @ViewChild(ProgrammingExerciseInformationComponent) lectureTitleComponent?: LectureUpdateTitleComponent; + @ViewChild(ProgrammingExerciseDifficultyComponent) lecturePeriodComponent?: LectureUpdatePeriodComponent; lecture: Lecture; isSaving: boolean; @@ -40,6 +46,9 @@ export class LectureUpdateComponent implements OnInit { processUnitMode: boolean; isShowingWizardMode: boolean; + formStatusSections: FormSectionStatus[]; + inputFieldSubscriptions: (Subscription | undefined)[] = []; + courses: Course[]; domainActionsDescription = [new FormulaAction()]; @@ -84,6 +93,44 @@ export class LectureUpdateComponent implements OnInit { }); } + ngAfterViewInit() { + this.inputFieldSubscriptions.push(this.lectureTitleComponent?.formValidChanges?.subscribe(() => this.calculateFormStatusSections())); + this.inputFieldSubscriptions.push(this.lecturePeriodComponent?.formValidChanges?.subscribe(() => this.calculateFormStatusSections())); + } + + ngOnDestroy() { + for (const subscription of this.inputFieldSubscriptions) { + subscription?.unsubscribe(); + } + } + + calculateFormStatusSections() { + this.formStatusSections = [ + // { + // title: 'artemisApp.programmingExercise.wizardMode.detailedSteps.generalInfoStepTitle', + // valid: this.exerciseInfoComponent?.formValid ?? false, + // }, + // { + // title: 'artemisApp.programmingExercise.wizardMode.detailedSteps.difficultyStepTitle', + // valid: (this.exerciseDifficultyComponent?.teamConfigComponent.formValid && this.validIdeSelection()) ?? false, + // }, + // { + // title: 'artemisApp.programmingExercise.wizardMode.detailedSteps.languageStepTitle', + // valid: this.exerciseLanguageComponent?.formValid ?? false, + // }, + // { + // title: 'artemisApp.programmingExercise.wizardMode.detailedSteps.problemStepTitle', + // valid: true, + // empty: !this.programmingExercise.problemStatement, + // }, + // { + // title: 'artemisApp.programmingExercise.wizardMode.detailedSteps.gradingStepTitle', + // valid: Boolean(this.exerciseGradingComponent?.formValid && (this.isExamMode || this.exercisePlagiarismComponent?.formValid)), + // empty: this.exerciseGradingComponent?.formEmpty, + // }, + ]; + } + /** * Revert to the previous state, equivalent with pressing the back button on your browser * Returns to the detail page if there is no previous state and we edited an existing lecture diff --git a/src/main/webapp/app/lecture/lecture.module.ts b/src/main/webapp/app/lecture/lecture.module.ts index 5cdad5fc5fa2..70741e2ecec4 100644 --- a/src/main/webapp/app/lecture/lecture.module.ts +++ b/src/main/webapp/app/lecture/lecture.module.ts @@ -15,8 +15,8 @@ import { ArtemisLectureUnitManagementModule } from 'app/lecture/lecture-unit/lec import { ArtemisMarkdownModule } from 'app/shared/markdown.module'; import { LectureImportComponent } from 'app/lecture/lecture-import.component'; import { ArtemisCompetenciesModule } from 'app/course/competencies/competency.module'; -import { LectureUpdateWizardTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component'; -import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; +import { LectureUpdateTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component'; +import { LectureUpdatePeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; import { LectureUpdateWizardAttachmentsComponent } from 'app/lecture/wizard-mode/lecture-wizard-attachments.component'; import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; import { LectureUpdateWizardStepComponent } from 'app/lecture/wizard-mode/lecture-update-wizard-step.component'; @@ -24,6 +24,7 @@ import { TitleChannelNameModule } from 'app/shared/form/title-channel-name/title import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component'; import { DetailModule } from 'app/detail-overview-list/detail.module'; import { CompetencyFormComponent } from 'app/course/competencies/forms/competency/competency-form.component'; +import { FormsModule } from 'app/forms/forms.module'; const ENTITY_STATES = [...lectureRoute]; @@ -40,6 +41,7 @@ const ENTITY_STATES = [...lectureRoute]; TitleChannelNameModule, DetailModule, CompetencyFormComponent, + FormsModule, ], declarations: [ LectureComponent, @@ -48,8 +50,8 @@ const ENTITY_STATES = [...lectureRoute]; LectureUpdateComponent, LectureUpdateWizardComponent, LectureAttachmentsComponent, - LectureUpdateWizardTitleComponent, - LectureUpdateWizardPeriodComponent, + LectureUpdateTitleComponent, + LectureUpdatePeriodComponent, LectureUpdateWizardAttachmentsComponent, LectureUpdateWizardUnitsComponent, LectureUpdateWizardStepComponent, diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-period.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-period.component.ts index 8953b9e67deb..2b3fe734a057 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-period.component.ts +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-period.component.ts @@ -5,10 +5,8 @@ import { Lecture } from 'app/entities/lecture.model'; selector: 'jhi-lecture-update-wizard-period', templateUrl: './lecture-wizard-period.component.html', }) -export class LectureUpdateWizardPeriodComponent { +export class LectureUpdatePeriodComponent { @Input() currentStep: number; @Input() lecture: Lecture; @Input() validateDatesFunction: () => void; - - constructor() {} } diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts index 2c456af3f0e7..265c93f2514f 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts @@ -1,16 +1,18 @@ import { Component, Input } from '@angular/core'; import { Lecture } from 'app/entities/lecture.model'; import { FormulaAction } from 'app/shared/monaco-editor/model/actions/formula.action'; +import { Subject } from 'rxjs'; @Component({ selector: 'jhi-lecture-update-wizard-title', templateUrl: './lecture-wizard-title.component.html', }) -export class LectureUpdateWizardTitleComponent { +export class LectureUpdateTitleComponent { @Input() currentStep: number; @Input() lecture: Lecture; domainActionsDescription = [new FormulaAction()]; - constructor() {} + formValid: boolean; + formValidChanges = new Subject(); } diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts index f06067251d59..b8de6a0f6885 100644 --- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts @@ -2,24 +2,24 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; import { MockComponent, MockPipe } from 'ng-mocks'; import { Lecture } from 'app/entities/lecture.model'; -import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; +import { LectureUpdatePeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -describe('LectureWizardPeriodComponent', () => { - let wizardPeriodComponentFixture: ComponentFixture; - let wizardPeriodComponent: LectureUpdateWizardPeriodComponent; +describe('LectureUpdatePeriodComponent', () => { + let wizardPeriodComponentFixture: ComponentFixture; + let wizardPeriodComponent: LectureUpdatePeriodComponent; beforeEach(() => { TestBed.configureTestingModule({ imports: [ReactiveFormsModule, FormsModule], - declarations: [LectureUpdateWizardPeriodComponent, MockPipe(ArtemisTranslatePipe), MockComponent(FormDateTimePickerComponent)], + declarations: [LectureUpdatePeriodComponent, MockPipe(ArtemisTranslatePipe), MockComponent(FormDateTimePickerComponent)], providers: [], schemas: [], }) .compileComponents() .then(() => { - wizardPeriodComponentFixture = TestBed.createComponent(LectureUpdateWizardPeriodComponent); + wizardPeriodComponentFixture = TestBed.createComponent(LectureUpdatePeriodComponent); wizardPeriodComponent = wizardPeriodComponentFixture.componentInstance; wizardPeriodComponent.lecture = new Lecture(); }); diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts index 36701004edbb..fd6a2855bc73 100644 --- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { LectureUpdateWizardTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component'; +import { LectureUpdateTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component'; import { Lecture } from 'app/entities/lecture.model'; import { MockComponent } from 'ng-mocks'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -7,19 +7,19 @@ import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-chan import { MarkdownEditorMonacoComponent } from 'app/shared/markdown-editor/monaco/markdown-editor-monaco.component'; describe('LectureWizardTitleComponent', () => { - let wizardTitleComponentFixture: ComponentFixture; - let wizardTitleComponent: LectureUpdateWizardTitleComponent; + let wizardTitleComponentFixture: ComponentFixture; + let wizardTitleComponent: LectureUpdateTitleComponent; beforeEach(() => { TestBed.configureTestingModule({ imports: [ReactiveFormsModule, FormsModule], - declarations: [LectureUpdateWizardTitleComponent, MockComponent(MarkdownEditorMonacoComponent), MockComponent(LectureTitleChannelNameComponent)], + declarations: [LectureUpdateTitleComponent, MockComponent(MarkdownEditorMonacoComponent), MockComponent(LectureTitleChannelNameComponent)], providers: [], schemas: [], }) .compileComponents() .then(() => { - wizardTitleComponentFixture = TestBed.createComponent(LectureUpdateWizardTitleComponent); + wizardTitleComponentFixture = TestBed.createComponent(LectureUpdateTitleComponent); wizardTitleComponent = wizardTitleComponentFixture.componentInstance; wizardTitleComponent.lecture = new Lecture(); }); diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts index 269205bdac7f..91197ed12927 100644 --- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts @@ -14,13 +14,13 @@ import { CourseManagementService } from 'app/course/manage/course-management.ser import { LectureUpdateWizardStepComponent } from 'app/lecture/wizard-mode/lecture-update-wizard-step.component'; import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; import { LectureUpdateWizardAttachmentsComponent } from 'app/lecture/wizard-mode/lecture-wizard-attachments.component'; -import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; -import { LectureUpdateWizardTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component'; +import { LectureUpdatePeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; +import { LectureUpdateTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component'; import { TranslateDirective } from 'app/shared/language/translate.directive'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import dayjs from 'dayjs/esm'; -describe('LectureWizardComponent', () => { +describe('LectureUpdateWizardComponent', () => { let wizardComponentFixture: ComponentFixture; let wizardComponent: LectureUpdateWizardComponent; @@ -33,8 +33,8 @@ describe('LectureWizardComponent', () => { MockComponent(LectureUpdateWizardStepComponent), MockComponent(LectureUpdateWizardUnitsComponent), MockComponent(LectureUpdateWizardAttachmentsComponent), - MockComponent(LectureUpdateWizardPeriodComponent), - MockComponent(LectureUpdateWizardTitleComponent), + MockComponent(LectureUpdatePeriodComponent), + MockComponent(LectureUpdateTitleComponent), MockComponent(FaIconComponent), MockDirective(TranslateDirective), ], From f3e8b14fdec0b166dab8502ee8e39bdf24c319f2 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 21:16:13 +0100 Subject: [PATCH 006/125] Introduce more signals, mark non signals as deprecated --- .../app/lecture/lecture-update.component.ts | 35 +++++++++---------- .../lecture-wizard-title.component.ts | 4 --- .../title-channel-name.component.ts | 7 ++++ 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 0ed078cf3fe0..3fa4557d1167 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -1,7 +1,7 @@ -import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Component, OnInit, ViewChild, effect, viewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; -import { Observable, Subscription } from 'rxjs'; +import { Observable } from 'rxjs'; import { AlertService } from 'app/core/util/alert.service'; import { LectureService } from './lecture.service'; import { CourseManagementService } from '../course/manage/course-management.service'; @@ -14,17 +14,17 @@ import { faBan, faHandshakeAngle, faPuzzlePiece, faQuestionCircle, faSave } from import { LectureUpdateWizardComponent } from 'app/lecture/wizard-mode/lecture-update-wizard.component'; import { UPLOAD_FILE_EXTENSIONS } from 'app/shared/constants/file-extensions.constants'; import { FormulaAction } from 'app/shared/monaco-editor/model/actions/formula.action'; -import { ProgrammingExerciseInformationComponent } from 'app/exercises/programming/manage/update/update-components/information/programming-exercise-information.component'; import { ProgrammingExerciseDifficultyComponent } from 'app/exercises/programming/manage/update/update-components/difficulty/programming-exercise-difficulty.component'; import { FormSectionStatus } from 'app/forms/form-status-bar/form-status-bar.component'; import { LectureUpdatePeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; +import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component'; @Component({ selector: 'jhi-lecture-update', templateUrl: './lecture-update.component.html', styleUrls: ['./lecture-update.component.scss'], }) -export class LectureUpdateComponent implements OnInit, AfterViewInit, OnDestroy { +export class LectureUpdateComponent implements OnInit { protected readonly documentationType: DocumentationType = 'Lecture'; protected readonly faQuestionCircle = faQuestionCircle; protected readonly faSave = faSave; @@ -37,8 +37,8 @@ export class LectureUpdateComponent implements OnInit, AfterViewInit, OnDestroy protected readonly acceptedFileExtensionsFileBrowser = UPLOAD_FILE_EXTENSIONS.map((ext) => '.' + ext).join(','); @ViewChild(LectureUpdateWizardComponent, { static: false }) wizardComponent: LectureUpdateWizardComponent; - @ViewChild(ProgrammingExerciseInformationComponent) lectureTitleComponent?: LectureUpdateTitleComponent; @ViewChild(ProgrammingExerciseDifficultyComponent) lecturePeriodComponent?: LectureUpdatePeriodComponent; + titleSection = viewChild.required(LectureTitleChannelNameComponent); lecture: Lecture; isSaving: boolean; @@ -47,7 +47,6 @@ export class LectureUpdateComponent implements OnInit, AfterViewInit, OnDestroy isShowingWizardMode: boolean; formStatusSections: FormSectionStatus[]; - inputFieldSubscriptions: (Subscription | undefined)[] = []; courses: Course[]; @@ -66,7 +65,18 @@ export class LectureUpdateComponent implements OnInit, AfterViewInit, OnDestroy protected activatedRoute: ActivatedRoute, private navigationUtilService: ArtemisNavigationUtilService, private router: Router, - ) {} + ) { + effect(() => { + // noinspection UnnecessaryLocalVariableJS: not inlined because the variable name improves readability + const updatedFormStatusSections: FormSectionStatus[] = [ + { + title: 'artemisApp.programmingExercise.wizardMode.detailedSteps.generalInfoStepTitle', + valid: Boolean(this.titleSection().titleChannelNameComponent().formValidSignal()), + }, + ]; + this.formStatusSections = updatedFormStatusSections; + }); + } /** * Life cycle hook called by Angular to indicate that Angular is done creating the component @@ -93,17 +103,6 @@ export class LectureUpdateComponent implements OnInit, AfterViewInit, OnDestroy }); } - ngAfterViewInit() { - this.inputFieldSubscriptions.push(this.lectureTitleComponent?.formValidChanges?.subscribe(() => this.calculateFormStatusSections())); - this.inputFieldSubscriptions.push(this.lecturePeriodComponent?.formValidChanges?.subscribe(() => this.calculateFormStatusSections())); - } - - ngOnDestroy() { - for (const subscription of this.inputFieldSubscriptions) { - subscription?.unsubscribe(); - } - } - calculateFormStatusSections() { this.formStatusSections = [ // { diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts index 265c93f2514f..b67bb7d545a8 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts @@ -1,7 +1,6 @@ import { Component, Input } from '@angular/core'; import { Lecture } from 'app/entities/lecture.model'; import { FormulaAction } from 'app/shared/monaco-editor/model/actions/formula.action'; -import { Subject } from 'rxjs'; @Component({ selector: 'jhi-lecture-update-wizard-title', @@ -12,7 +11,4 @@ export class LectureUpdateTitleComponent { @Input() lecture: Lecture; domainActionsDescription = [new FormulaAction()]; - - formValid: boolean; - formValidChanges = new Subject(); } diff --git a/src/main/webapp/app/shared/form/title-channel-name/title-channel-name.component.ts b/src/main/webapp/app/shared/form/title-channel-name/title-channel-name.component.ts index 6fbbfdca412f..7e4e9e582350 100644 --- a/src/main/webapp/app/shared/form/title-channel-name/title-channel-name.component.ts +++ b/src/main/webapp/app/shared/form/title-channel-name/title-channel-name.component.ts @@ -30,7 +30,14 @@ export class TitleChannelNameComponent implements AfterViewInit, OnDestroy, OnIn @Output() channelNameChange = new EventEmitter(); formValidSignal = signal(false); + + /** + * @deprecated Use formValidSignal instead. + */ formValid: boolean; + /** + * @deprecated Use formValidSignal instead. + */ formValidChanges = new Subject(); fieldTitleSubscription?: Subscription; From 52bf471640792b14099e2c9bb835d954f6d23fd0 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 21:18:03 +0100 Subject: [PATCH 007/125] Fix error by adding titleChannelNameComponent child --- .../app/lecture/lecture-title-channel-name.component.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/webapp/app/lecture/lecture-title-channel-name.component.ts b/src/main/webapp/app/lecture/lecture-title-channel-name.component.ts index 0bda4027f089..f2c8ffbc915e 100644 --- a/src/main/webapp/app/lecture/lecture-title-channel-name.component.ts +++ b/src/main/webapp/app/lecture/lecture-title-channel-name.component.ts @@ -1,6 +1,7 @@ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnInit, viewChild } from '@angular/core'; import { isCommunicationEnabled } from 'app/entities/course.model'; import { Lecture } from 'app/entities/lecture.model'; +import { TitleChannelNameComponent } from 'app/shared/form/title-channel-name/title-channel-name.component'; @Component({ selector: 'jhi-lecture-title-channel-name', @@ -9,6 +10,8 @@ import { Lecture } from 'app/entities/lecture.model'; export class LectureTitleChannelNameComponent implements OnInit { @Input() lecture: Lecture; + titleChannelNameComponent = viewChild.required(TitleChannelNameComponent); + hideChannelNameInput = false; ngOnInit() { this.hideChannelNameInput = !this.requiresChannelName(this.lecture); From 2e1a07fe276e453bb3365735c15003ee86fae247 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 21:26:50 +0100 Subject: [PATCH 008/125] Make all information available on one page (no course set not handled yet) --- .../lecture/lecture-attachments.component.ts | 22 ++++------ .../app/lecture/lecture-update.component.html | 5 ++- .../app/lecture/lecture-update.component.ts | 41 ++++++------------- .../lecture-wizard-units.component.html | 2 +- .../date-time-picker.component.ts | 14 +++---- src/main/webapp/i18n/de/lecture.json | 2 +- src/main/webapp/i18n/en/lecture.json | 2 +- 7 files changed, 35 insertions(+), 53 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-attachments.component.ts b/src/main/webapp/app/lecture/lecture-attachments.component.ts index 5b12fa24b510..42db003ee379 100644 --- a/src/main/webapp/app/lecture/lecture-attachments.component.ts +++ b/src/main/webapp/app/lecture/lecture-attachments.component.ts @@ -1,8 +1,7 @@ import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { HttpClient, HttpErrorResponse, HttpResponse } from '@angular/common/http'; +import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; import { Lecture } from 'app/entities/lecture.model'; -import { FileUploaderService } from 'app/shared/http/file-uploader.service'; import dayjs from 'dayjs/esm'; import { Subject } from 'rxjs'; import { FileService } from 'app/shared/http/file.service'; @@ -18,6 +17,14 @@ import { LectureService } from 'app/lecture/lecture.service'; styleUrls: ['./lecture-attachments.component.scss'], }) export class LectureAttachmentsComponent implements OnInit, OnDestroy { + protected readonly faSpinner = faSpinner; + protected readonly faTimes = faTimes; + protected readonly faTrash = faTrash; + protected readonly faPencilAlt = faPencilAlt; + protected readonly faPaperclip = faPaperclip; + protected readonly faQuestionCircle = faQuestionCircle; + protected readonly faEye = faEye; + @ViewChild('fileInput', { static: false }) fileInput: ElementRef; @Input() lectureId: number | undefined; @Input() showHeader = true; @@ -41,21 +48,10 @@ export class LectureAttachmentsComponent implements OnInit, OnDestroy { private dialogErrorSource = new Subject(); dialogError$ = this.dialogErrorSource.asObservable(); - // Icons - faSpinner = faSpinner; - faTimes = faTimes; - faTrash = faTrash; - faPencilAlt = faPencilAlt; - faPaperclip = faPaperclip; - faQuestionCircle = faQuestionCircle; - faEye = faEye; - constructor( protected activatedRoute: ActivatedRoute, private attachmentService: AttachmentService, private lectureService: LectureService, - private httpClient: HttpClient, - private fileUploaderService: FileUploaderService, private fileService: FileService, ) {} diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index f4f48b5db400..49c4c3b4046e 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -9,12 +9,11 @@ [isSaving]="isSaving" /> } @else { -
-

+

@@ -26,6 +25,8 @@

+
diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 3fa4557d1167..66073c5847a0 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -70,9 +70,21 @@ export class LectureUpdateComponent implements OnInit { // noinspection UnnecessaryLocalVariableJS: not inlined because the variable name improves readability const updatedFormStatusSections: FormSectionStatus[] = [ { - title: 'artemisApp.programmingExercise.wizardMode.detailedSteps.generalInfoStepTitle', + title: 'artemisApp.lecture.wizardMode.steps.titleStepTitle', valid: Boolean(this.titleSection().titleChannelNameComponent().formValidSignal()), }, + { + title: 'artemisApp.lecture.wizardMode.steps.periodStepTitle', + valid: true, // TODO retrieve the valid status from the datepickeres + }, + { + title: 'artemisApp.lecture.wizardMode.steps.attachmentsStepTitle', + valid: true, // TODO retrieve the valid status + }, + { + title: 'artemisApp.lecture.wizardMode.steps.unitsStepTitle', + valid: true, // TODO retrieve the valid status + }, ]; this.formStatusSections = updatedFormStatusSections; }); @@ -103,33 +115,6 @@ export class LectureUpdateComponent implements OnInit { }); } - calculateFormStatusSections() { - this.formStatusSections = [ - // { - // title: 'artemisApp.programmingExercise.wizardMode.detailedSteps.generalInfoStepTitle', - // valid: this.exerciseInfoComponent?.formValid ?? false, - // }, - // { - // title: 'artemisApp.programmingExercise.wizardMode.detailedSteps.difficultyStepTitle', - // valid: (this.exerciseDifficultyComponent?.teamConfigComponent.formValid && this.validIdeSelection()) ?? false, - // }, - // { - // title: 'artemisApp.programmingExercise.wizardMode.detailedSteps.languageStepTitle', - // valid: this.exerciseLanguageComponent?.formValid ?? false, - // }, - // { - // title: 'artemisApp.programmingExercise.wizardMode.detailedSteps.problemStepTitle', - // valid: true, - // empty: !this.programmingExercise.problemStatement, - // }, - // { - // title: 'artemisApp.programmingExercise.wizardMode.detailedSteps.gradingStepTitle', - // valid: Boolean(this.exerciseGradingComponent?.formValid && (this.isExamMode || this.exercisePlagiarismComponent?.formValid)), - // empty: this.exerciseGradingComponent?.formEmpty, - // }, - ]; - } - /** * Revert to the previous state, equivalent with pressing the back button on your browser * Returns to the detail page if there is no previous state and we edited an existing lecture diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html index 9d838c196f05..a9ef8a627d26 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html @@ -1,5 +1,5 @@
-

+

void; /** diff --git a/src/main/webapp/i18n/de/lecture.json b/src/main/webapp/i18n/de/lecture.json index 5c1dd797824e..600ea45ef1c9 100644 --- a/src/main/webapp/i18n/de/lecture.json +++ b/src/main/webapp/i18n/de/lecture.json @@ -73,7 +73,7 @@ "wizardMode": { "steps": { "titleStepTitle": "Titel", - "titleStepMessage": "Gib einen Titel und eine aussagekräftige Beschreibung für die neue Vorlesung ein.", + "titleStepMessage": "Gib einen Titel und eine aussagekräftige Beschreibung für die Vorlesung ein.", "periodStepTitle": "Zeitraum", "periodStepMessage": "Lege das Start- und Enddatum der Vorlesung fest.", "attachmentsStepTitle": "Anhänge", diff --git a/src/main/webapp/i18n/en/lecture.json b/src/main/webapp/i18n/en/lecture.json index b03b840b400f..cc0f1289886a 100644 --- a/src/main/webapp/i18n/en/lecture.json +++ b/src/main/webapp/i18n/en/lecture.json @@ -73,7 +73,7 @@ "wizardMode": { "steps": { "titleStepTitle": "Title", - "titleStepMessage": "Add a title and meaningful description to the new lecture.", + "titleStepMessage": "Add a title and meaningful description to the lecture.", "periodStepTitle": "Period", "periodStepMessage": "Specify the begin and end dates of the lecture.", "attachmentsStepTitle": "Attachments", From e444f199a833f4a59f44d90171673841a2dceca2 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 21:35:20 +0100 Subject: [PATCH 009/125] Introducing more signals --- .../text-unit-form.component.html | 2 +- .../text-unit-form.component.ts | 30 ++++++----- .../app/lecture/lecture-update.component.ts | 6 ++- .../lecture-wizard-units.component.html | 3 +- .../lecture-wizard-units.component.ts | 50 ++++++++++--------- .../title-channel-name.component.ts | 4 +- 6 files changed, 51 insertions(+), 44 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.html b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.html index 403f20a774cf..528ffa4ffb09 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.html +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.html @@ -50,7 +50,7 @@
- @if (hasCancelButton) { diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts index 28817538a1e4..e8a8584a4e8e 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, signal } from '@angular/core'; import dayjs from 'dayjs/esm'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; @@ -21,18 +21,15 @@ export interface TextUnitFormData { styles: [], }) export class TextUnitFormComponent implements OnInit, OnChanges, OnDestroy { - @Input() - formData: TextUnitFormData; + protected readonly faTimes = faTimes; + + @Input() formData: TextUnitFormData; @Input() isEditMode = false; @Output() formSubmitted: EventEmitter = new EventEmitter(); - @Input() - hasCancelButton: boolean; - @Output() - onCancel: EventEmitter = new EventEmitter(); - - faTimes = faTimes; + @Input() hasCancelButton: boolean; + @Output() onCancel: EventEmitter = new EventEmitter(); form: FormGroup; // not included in reactive form @@ -40,8 +37,11 @@ export class TextUnitFormComponent implements OnInit, OnChanges, OnDestroy { contentLoadedFromCache = false; firstMarkdownChangeHappened = false; + isFormValid = signal(false); + private markdownChanges = new Subject(); private markdownChangesSubscription: Subscription; + private formValidityChangesSubscription: Subscription; constructor( private fb: FormBuilder, @@ -66,6 +66,7 @@ export class TextUnitFormComponent implements OnInit, OnChanges, OnDestroy { ngOnDestroy() { this.markdownChangesSubscription.unsubscribe(); + this.formValidityChangesSubscription.unsubscribe(); } ngOnInit(): void { @@ -97,6 +98,13 @@ export class TextUnitFormComponent implements OnInit, OnChanges, OnDestroy { releaseDate: [undefined as dayjs.Dayjs | undefined], competencyLinks: [undefined as CompetencyLectureUnitLink[] | undefined], }); + + if (this.formValidityChangesSubscription) { + this.formValidityChangesSubscription.unsubscribe(); + } + this.formValidityChangesSubscription = this.form.statusChanges.subscribe(() => { + this.isFormValid.set(this.form.valid); + }); } private setFormValues(formData: TextUnitFormData) { @@ -115,10 +123,6 @@ export class TextUnitFormComponent implements OnInit, OnChanges, OnDestroy { this.formSubmitted.emit(textUnitFormData); } - get isSubmitPossible() { - return !this.form.invalid; - } - onMarkdownChange(markdown: string) { this.markdownChanges.next(markdown); } diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 66073c5847a0..efb48ef0a633 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -18,6 +18,7 @@ import { ProgrammingExerciseDifficultyComponent } from 'app/exercises/programmin import { FormSectionStatus } from 'app/forms/form-status-bar/form-status-bar.component'; import { LectureUpdatePeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component'; +import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; @Component({ selector: 'jhi-lecture-update', @@ -39,6 +40,7 @@ export class LectureUpdateComponent implements OnInit { @ViewChild(LectureUpdateWizardComponent, { static: false }) wizardComponent: LectureUpdateWizardComponent; @ViewChild(ProgrammingExerciseDifficultyComponent) lecturePeriodComponent?: LectureUpdatePeriodComponent; titleSection = viewChild.required(LectureTitleChannelNameComponent); + unitSection = viewChild.required(LectureUpdateWizardUnitsComponent); lecture: Lecture; isSaving: boolean; @@ -71,7 +73,7 @@ export class LectureUpdateComponent implements OnInit { const updatedFormStatusSections: FormSectionStatus[] = [ { title: 'artemisApp.lecture.wizardMode.steps.titleStepTitle', - valid: Boolean(this.titleSection().titleChannelNameComponent().formValidSignal()), + valid: Boolean(this.titleSection().titleChannelNameComponent().isFormValidSignal()), }, { title: 'artemisApp.lecture.wizardMode.steps.periodStepTitle', @@ -83,7 +85,7 @@ export class LectureUpdateComponent implements OnInit { }, { title: 'artemisApp.lecture.wizardMode.steps.unitsStepTitle', - valid: true, // TODO retrieve the valid status + valid: Boolean(this.unitSection()), }, ]; this.formStatusSections = updatedFormStatusSections; diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html index a9ef8a627d26..780955f3ab02 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html @@ -12,8 +12,7 @@

- } - @if (isAnyUnitFormOpen()) { + } @else {
@if (!isEditingLectureUnit) {

diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts index 1488598c3ad7..26f44aa90f64 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, Input, OnInit, ViewChild, computed, signal } from '@angular/core'; import { Lecture } from 'app/entities/lecture.model'; import { TextUnit } from 'app/entities/lecture-unit/textUnit.model'; import { VideoUnit } from 'app/entities/lecture-unit/videoUnit.model'; @@ -32,12 +32,14 @@ export class LectureUpdateWizardUnitsComponent implements OnInit { @ViewChild(LectureUnitManagementComponent, { static: false }) unitManagementComponent: LectureUnitManagementComponent; + isUnitConfigurationValid = signal(false); + isEditingLectureUnit: boolean; - isTextUnitFormOpen: boolean; - isExerciseUnitFormOpen: boolean; - isVideoUnitFormOpen: boolean; - isOnlineUnitFormOpen: boolean; - isAttachmentUnitFormOpen: boolean; + isTextUnitFormOpen = signal(false); + isExerciseUnitFormOpen = signal(false); + isVideoUnitFormOpen = signal(false); + isOnlineUnitFormOpen = signal(false); + isAttachmentUnitFormOpen = signal(false); currentlyProcessedTextUnit: TextUnit; currentlyProcessedVideoUnit: VideoUnit; @@ -71,33 +73,33 @@ export class LectureUpdateWizardUnitsComponent implements OnInit { switch (type) { case LectureUnitType.TEXT: - this.isTextUnitFormOpen = true; + this.isTextUnitFormOpen.set(true); break; case LectureUnitType.EXERCISE: - this.isExerciseUnitFormOpen = true; + this.isExerciseUnitFormOpen.set(true); break; case LectureUnitType.VIDEO: - this.isVideoUnitFormOpen = true; + this.isVideoUnitFormOpen.set(true); break; case LectureUnitType.ONLINE: - this.isOnlineUnitFormOpen = true; + this.isOnlineUnitFormOpen.set(true); break; case LectureUnitType.ATTACHMENT: - this.isAttachmentUnitFormOpen = true; + this.isAttachmentUnitFormOpen.set(true); break; } } - isAnyUnitFormOpen(): boolean { - return this.isTextUnitFormOpen || this.isVideoUnitFormOpen || this.isOnlineUnitFormOpen || this.isAttachmentUnitFormOpen || this.isExerciseUnitFormOpen; - } + isAnyUnitFormOpen = computed(() => { + return this.isTextUnitFormOpen() || this.isVideoUnitFormOpen() || this.isOnlineUnitFormOpen() || this.isAttachmentUnitFormOpen() || this.isExerciseUnitFormOpen(); + }); onCloseLectureUnitForms() { - this.isTextUnitFormOpen = false; - this.isVideoUnitFormOpen = false; - this.isOnlineUnitFormOpen = false; - this.isAttachmentUnitFormOpen = false; - this.isExerciseUnitFormOpen = false; + this.isTextUnitFormOpen.set(false); + this.isVideoUnitFormOpen.set(false); + this.isOnlineUnitFormOpen.set(false); + this.isAttachmentUnitFormOpen.set(false); + this.isExerciseUnitFormOpen.set(false); } createEditTextUnit(formData: TextUnitFormData) { @@ -258,11 +260,11 @@ export class LectureUpdateWizardUnitsComponent implements OnInit { this.currentlyProcessedOnlineUnit = lectureUnit as OnlineUnit; this.currentlyProcessedAttachmentUnit = lectureUnit as AttachmentUnit; - this.isTextUnitFormOpen = lectureUnit.type === LectureUnitType.TEXT; - this.isVideoUnitFormOpen = lectureUnit.type === LectureUnitType.VIDEO; - this.isExerciseUnitFormOpen = lectureUnit.type === LectureUnitType.EXERCISE; - this.isOnlineUnitFormOpen = lectureUnit.type === LectureUnitType.ONLINE; - this.isAttachmentUnitFormOpen = lectureUnit.type === LectureUnitType.ATTACHMENT; + this.isTextUnitFormOpen.set(lectureUnit.type === LectureUnitType.TEXT); + this.isVideoUnitFormOpen.set(lectureUnit.type === LectureUnitType.VIDEO); + this.isExerciseUnitFormOpen.set(lectureUnit.type === LectureUnitType.EXERCISE); + this.isOnlineUnitFormOpen.set(lectureUnit.type === LectureUnitType.ONLINE); + this.isAttachmentUnitFormOpen.set(lectureUnit.type === LectureUnitType.ATTACHMENT); switch (lectureUnit.type) { case LectureUnitType.TEXT: diff --git a/src/main/webapp/app/shared/form/title-channel-name/title-channel-name.component.ts b/src/main/webapp/app/shared/form/title-channel-name/title-channel-name.component.ts index 7e4e9e582350..11879e9100d7 100644 --- a/src/main/webapp/app/shared/form/title-channel-name/title-channel-name.component.ts +++ b/src/main/webapp/app/shared/form/title-channel-name/title-channel-name.component.ts @@ -29,7 +29,7 @@ export class TitleChannelNameComponent implements AfterViewInit, OnDestroy, OnIn @Output() titleChange = new EventEmitter(); @Output() channelNameChange = new EventEmitter(); - formValidSignal = signal(false); + isFormValidSignal = signal(false); /** * @deprecated Use formValidSignal instead. @@ -95,7 +95,7 @@ export class TitleChannelNameComponent implements AfterViewInit, OnDestroy, OnIn calculateFormValid(): void { const updatedFormValidValue = Boolean(this.field_title.valid && (!this.isChannelFieldDisplayed() || this.field_channel_name()?.valid)); - this.formValidSignal.set(updatedFormValidValue); + this.isFormValidSignal.set(updatedFormValidValue); this.formValid = updatedFormValidValue; this.formValidChanges.next(this.formValid); } From 91be135de8002542aaf84cee74578d4d26466ca0 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 21:39:54 +0100 Subject: [PATCH 010/125] Retrieving form valid from video unit form and get rid of method call in template --- .../video-unit-form.component.html | 2 +- .../video-unit-form.component.ts | 46 ++++++++++--------- .../lecture-wizard-units.component.ts | 20 +++++--- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.html b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.html index 28218967e14e..3ff6083ca085 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.html +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.html @@ -107,7 +107,7 @@
- @if (hasCancelButton) { diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.ts index 65ca7da964f2..2d2ce9c1bcbc 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.ts @@ -1,9 +1,10 @@ import dayjs from 'dayjs/esm'; -import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, signal } from '@angular/core'; import { AbstractControl, FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms'; import urlParser from 'js-video-url-parser'; import { faArrowLeft, faTimes } from '@fortawesome/free-solid-svg-icons'; import { CompetencyLectureUnitLink } from 'app/entities/competency.model'; +import { Subscription } from 'rxjs'; export interface VideoUnitFormData { name?: string; @@ -57,28 +58,24 @@ function videoSourceUrlValidator(control: AbstractControl): ValidationErrors | u selector: 'jhi-video-unit-form', templateUrl: './video-unit-form.component.html', }) -export class VideoUnitFormComponent implements OnInit, OnChanges { - @Input() - formData: VideoUnitFormData; - @Input() - isEditMode = false; - - @Output() - formSubmitted: EventEmitter = new EventEmitter(); - form: FormGroup; +export class VideoUnitFormComponent implements OnInit, OnChanges, OnDestroy { + protected readonly faTimes = faTimes; + protected readonly faArrowLeft = faArrowLeft; + + @Input() formData: VideoUnitFormData; + @Input() isEditMode = false; - @Input() - hasCancelButton: boolean; - @Output() - onCancel: EventEmitter = new EventEmitter(); + @Output() formSubmitted: EventEmitter = new EventEmitter(); + form: FormGroup; - faTimes = faTimes; + @Input() hasCancelButton: boolean; + @Output() onCancel: EventEmitter = new EventEmitter(); videoSourceUrlValidator = videoSourceUrlValidator; videoSourceTransformUrlValidator = videoSourceTransformUrlValidator; - // Icons - faArrowLeft = faArrowLeft; + isFormValid = signal(false); + private formValidityChangesSubscription: Subscription; constructor(private fb: FormBuilder) {} @@ -113,6 +110,10 @@ export class VideoUnitFormComponent implements OnInit, OnChanges { this.initializeForm(); } + ngOnDestroy() { + this.formValidityChangesSubscription.unsubscribe(); + } + private initializeForm() { if (this.form) { return; @@ -125,6 +126,13 @@ export class VideoUnitFormComponent implements OnInit, OnChanges { urlHelper: [undefined as string | undefined, this.videoSourceTransformUrlValidator], competencyLinks: [undefined as CompetencyLectureUnitLink[] | undefined], }); + + if (this.formValidityChangesSubscription) { + this.formValidityChangesSubscription.unsubscribe(); + } + this.formValidityChangesSubscription = this.form.statusChanges.subscribe(() => { + this.isFormValid.set(this.form.valid); + }); } private setFormValues(formData: VideoUnitFormData) { @@ -136,10 +144,6 @@ export class VideoUnitFormComponent implements OnInit, OnChanges { this.formSubmitted.emit(videoUnitFormData); } - get isSubmitPossible() { - return !this.form.invalid; - } - get isTransformable() { if (this.urlHelperControl!.value === undefined || this.urlHelperControl!.value === null || this.urlHelperControl!.value === '') { return false; diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts index 26f44aa90f64..330defa1cc01 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts @@ -1,13 +1,13 @@ -import { Component, Input, OnInit, ViewChild, computed, signal } from '@angular/core'; +import { Component, Input, OnInit, ViewChild, computed, signal, viewChild } from '@angular/core'; import { Lecture } from 'app/entities/lecture.model'; import { TextUnit } from 'app/entities/lecture-unit/textUnit.model'; import { VideoUnit } from 'app/entities/lecture-unit/videoUnit.model'; import { OnlineUnit } from 'app/entities/lecture-unit/onlineUnit.model'; import { AttachmentUnit } from 'app/entities/lecture-unit/attachmentUnit.model'; -import { TextUnitFormData } from 'app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component'; -import { VideoUnitFormData } from 'app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component'; -import { OnlineUnitFormData } from 'app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component'; -import { AttachmentUnitFormData } from 'app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component'; +import { TextUnitFormComponent, TextUnitFormData } from 'app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component'; +import { VideoUnitFormComponent, VideoUnitFormData } from 'app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component'; +import { OnlineUnitFormComponent, OnlineUnitFormData } from 'app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component'; +import { AttachmentUnitFormComponent, AttachmentUnitFormData } from 'app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component'; import { LectureUnit, LectureUnitType } from 'app/entities/lecture-unit/lectureUnit.model'; import { onError } from 'app/shared/util/global.utils'; import { Attachment, AttachmentType } from 'app/entities/attachment.model'; @@ -21,6 +21,7 @@ import { HttpErrorResponse } from '@angular/common/http'; import { AttachmentUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/attachmentUnit.service'; import dayjs from 'dayjs/esm'; import { ActivatedRoute } from '@angular/router'; +import { CreateExerciseUnitComponent } from 'app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component'; @Component({ selector: 'jhi-lecture-update-wizard-units', @@ -32,7 +33,14 @@ export class LectureUpdateWizardUnitsComponent implements OnInit { @ViewChild(LectureUnitManagementComponent, { static: false }) unitManagementComponent: LectureUnitManagementComponent; - isUnitConfigurationValid = signal(false); + textUnitForm = viewChild(TextUnitFormComponent); + videoUnitForm = viewChild(VideoUnitFormComponent); + onlineUnitForm = viewChild(OnlineUnitFormComponent); + attachmentUnitForm = viewChild(AttachmentUnitFormComponent); + exerciseUnitForm = viewChild(CreateExerciseUnitComponent); + isUnitConfigurationValid = computed(() => { + return this.textUnitForm()?.isFormValid() && this.videoUnitForm()?.isFormValid(); + }); isEditingLectureUnit: boolean; isTextUnitFormOpen = signal(false); From 166ee3820e34101ecf95e2087a60cc8cf7ae0f60 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 21:43:48 +0100 Subject: [PATCH 011/125] Retrieving form valid from online unit form and get rid of method call in template --- .../online-unit-form.component.html | 2 +- .../online-unit-form.component.ts | 47 ++++++++++--------- .../lecture-wizard-units.component.ts | 2 +- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.html b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.html index 2a895b2d195e..f1a020db3ebd 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.html +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.html @@ -73,7 +73,7 @@
- @if (hasCancelButton) { diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts index 3648cb3bb8db..d659ec2b3701 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts @@ -1,12 +1,12 @@ import dayjs from 'dayjs/esm'; -import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, signal } from '@angular/core'; import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { faArrowLeft, faTimes } from '@fortawesome/free-solid-svg-icons'; -import { map } from 'rxjs'; import { HttpResponse } from '@angular/common/http'; import { OnlineResourceDTO } from 'app/lecture/lecture-unit/lecture-unit-management/online-resource-dto.model'; import { OnlineUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/onlineUnit.service'; import { CompetencyLectureUnitLink } from 'app/entities/competency.model'; +import { Subscription, map } from 'rxjs'; export interface OnlineUnitFormData { name?: string; @@ -32,27 +32,23 @@ function urlValidator(control: AbstractControl) { selector: 'jhi-online-unit-form', templateUrl: './online-unit-form.component.html', }) -export class OnlineUnitFormComponent implements OnInit, OnChanges { - @Input() - formData: OnlineUnitFormData; - @Input() - isEditMode = false; - - @Output() - formSubmitted: EventEmitter = new EventEmitter(); - form: FormGroup; +export class OnlineUnitFormComponent implements OnInit, OnChanges, OnDestroy { + protected faTimes = faTimes; + protected faArrowLeft = faArrowLeft; + + @Input() formData: OnlineUnitFormData; + @Input() isEditMode = false; - @Input() - hasCancelButton: boolean; - @Output() - onCancel: EventEmitter = new EventEmitter(); + @Output() formSubmitted: EventEmitter = new EventEmitter(); + form: FormGroup; - faTimes = faTimes; + @Input() hasCancelButton: boolean; + @Output() onCancel: EventEmitter = new EventEmitter(); urlValidator = urlValidator; - // Icons - faArrowLeft = faArrowLeft; + isFormValid = signal(false); + private formValidityChangesSubscription: Subscription; constructor( private fb: FormBuilder, @@ -86,6 +82,10 @@ export class OnlineUnitFormComponent implements OnInit, OnChanges { this.initializeForm(); } + ngOnDestroy() { + this.formValidityChangesSubscription.unsubscribe(); + } + private initializeForm() { if (this.form) { return; @@ -97,6 +97,13 @@ export class OnlineUnitFormComponent implements OnInit, OnChanges { source: [undefined, [Validators.required, this.urlValidator]], competencyLinks: [undefined as CompetencyLectureUnitLink[] | undefined], }); + + if (this.formValidityChangesSubscription) { + this.formValidityChangesSubscription.unsubscribe(); + } + this.formValidityChangesSubscription = this.form.statusChanges.subscribe(() => { + this.isFormValid.set(this.form.valid); + }); } private setFormValues(formData: OnlineUnitFormData) { @@ -136,10 +143,6 @@ export class OnlineUnitFormComponent implements OnInit, OnChanges { this.formSubmitted.emit(onlineUnitFormData); } - get isSubmitPossible() { - return !this.form.invalid; - } - cancelForm() { this.onCancel.emit(); } diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts index 330defa1cc01..e74555035683 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts @@ -39,7 +39,7 @@ export class LectureUpdateWizardUnitsComponent implements OnInit { attachmentUnitForm = viewChild(AttachmentUnitFormComponent); exerciseUnitForm = viewChild(CreateExerciseUnitComponent); isUnitConfigurationValid = computed(() => { - return this.textUnitForm()?.isFormValid() && this.videoUnitForm()?.isFormValid(); + return this.textUnitForm()?.isFormValid() && this.videoUnitForm()?.isFormValid() && this.onlineUnitForm()?.isFormValid(); }); isEditingLectureUnit: boolean; From 8ddbd9e4d345068b6733ea70fc4926d48c80e2ac Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 21:49:27 +0100 Subject: [PATCH 012/125] Retrieving form valid from attachment unit form and get rid of method call in template --- .../attachment-unit-form.component.html | 6 +- .../attachment-unit-form.component.ts | 64 +++++++++---------- .../lecture-wizard-units.component.ts | 2 +- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component.html b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component.html index c7d9568fb7c2..4de9ac96dcfa 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component.html +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component.html @@ -60,13 +60,13 @@ />
- @if (isFileTooBig) { + @if (isFileTooBig()) {
{{ 'artemisApp.attachmentUnit.createAttachmentUnit.fileTooBig' | artemisTranslate }} {{ 'artemisApp.attachmentUnit.createAttachmentUnit.fileLimitation' | artemisTranslate }}
} - @if (!fileName && fileInputTouched) { + @if (!fileName() && fileInputTouched) {
}
@@ -105,7 +105,7 @@
- @if (hasCancelButton) { diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component.ts index 766379c5847b..b5e1aece70db 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component.ts @@ -1,11 +1,11 @@ -import { Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core'; +import { Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild, computed, signal } from '@angular/core'; import dayjs from 'dayjs/esm'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { TranslateService } from '@ngx-translate/core'; import { faQuestionCircle, faTimes } from '@fortawesome/free-solid-svg-icons'; import { UPLOAD_FILE_EXTENSIONS } from 'app/shared/constants/file-extensions.constants'; import { CompetencyLectureUnitLink } from 'app/entities/competency.model'; import { MAX_FILE_SIZE } from 'app/shared/constants/input.constants'; +import { Subscription } from 'rxjs'; export interface AttachmentUnitFormData { formProperties: FormProperties; @@ -33,47 +33,51 @@ export interface FileProperties { templateUrl: './attachment-unit-form.component.html', }) export class AttachmentUnitFormComponent implements OnInit, OnChanges { - @Input() - formData: AttachmentUnitFormData; - @Input() - isEditMode = false; - + protected readonly faQuestionCircle = faQuestionCircle; + protected readonly faTimes = faTimes; // A human-readable list of allowed file extensions - readonly allowedFileExtensions = UPLOAD_FILE_EXTENSIONS.join(', '); + protected readonly allowedFileExtensions = UPLOAD_FILE_EXTENSIONS.join(', '); // The list of file extensions for the "accept" attribute of the file input field - readonly acceptedFileExtensionsFileBrowser = UPLOAD_FILE_EXTENSIONS.map((ext) => '.' + ext).join(','); + protected readonly acceptedFileExtensionsFileBrowser = UPLOAD_FILE_EXTENSIONS.map((ext) => '.' + ext).join(','); - faQuestionCircle = faQuestionCircle; + @Input() formData: AttachmentUnitFormData; + @Input() isEditMode = false; - @Output() - formSubmitted: EventEmitter = new EventEmitter(); + @Output() formSubmitted: EventEmitter = new EventEmitter(); form: FormGroup; - @Input() - hasCancelButton: boolean; - @Output() - onCancel: EventEmitter = new EventEmitter(); - - faTimes = faTimes; + @Input() hasCancelButton: boolean; + @Output() onCancel: EventEmitter = new EventEmitter(); // have to handle the file input as a special case at is not part of the reactive form @ViewChild('fileInput', { static: false }) fileInput: ElementRef; file: File; - fileName?: string; fileInputTouched = false; - isFileTooBig: boolean; - constructor( - private translateService: TranslateService, - private fb: FormBuilder, - ) {} + fileName = signal(undefined); + isFileTooBig = signal(false); + isFormValidWithoutExtraValidation = signal(false); + isFormValid = computed(() => { + return (this.isFormValidWithoutExtraValidation() || this.fileName()) && !this.isFileTooBig(); + }); + + private formValidityChangesSubscription: Subscription; + + constructor(private fb: FormBuilder) {} ngOnChanges(): void { this.initializeForm(); if (this.isEditMode && this.formData) { this.setFormValues(this.formData); } + + if (this.formValidityChangesSubscription) { + this.formValidityChangesSubscription.unsubscribe(); + } + this.formValidityChangesSubscription = this.form.statusChanges.subscribe(() => { + this.isFormValidWithoutExtraValidation.set(this.form.valid); + }); } ngOnInit(): void { @@ -100,7 +104,7 @@ export class AttachmentUnitFormComponent implements OnInit, OnChanges { return; } this.file = input.files[0]; - this.fileName = this.file.name; + this.fileName.set(this.file.name); // automatically set the name in case it is not yet specified if (this.form && (this.nameControl?.value == undefined || this.nameControl?.value == '')) { this.form.patchValue({ @@ -108,7 +112,7 @@ export class AttachmentUnitFormComponent implements OnInit, OnChanges { name: this.file.name.replace(/\.[^/.]+$/, ''), }); } - this.isFileTooBig = this.file.size > MAX_FILE_SIZE; + this.isFileTooBig.set(this.file.size > MAX_FILE_SIZE); } get nameControl() { @@ -131,16 +135,12 @@ export class AttachmentUnitFormComponent implements OnInit, OnChanges { return this.form.get('version'); } - get isSubmitPossible() { - return !(this.form.invalid || !this.fileName) && !this.isFileTooBig; - } - submitForm() { const formValue = this.form.value; const formProperties: FormProperties = { ...formValue }; const fileProperties: FileProperties = { file: this.file, - fileName: this.fileName, + fileName: this.fileName(), }; this.formSubmitted.emit({ @@ -157,7 +157,7 @@ export class AttachmentUnitFormComponent implements OnInit, OnChanges { this.file = formData?.fileProperties?.file; } if (formData?.fileProperties?.fileName) { - this.fileName = formData?.fileProperties?.fileName; + this.fileName.set(formData?.fileProperties?.fileName); } } diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts index e74555035683..1cc7bd9487b7 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts @@ -39,7 +39,7 @@ export class LectureUpdateWizardUnitsComponent implements OnInit { attachmentUnitForm = viewChild(AttachmentUnitFormComponent); exerciseUnitForm = viewChild(CreateExerciseUnitComponent); isUnitConfigurationValid = computed(() => { - return this.textUnitForm()?.isFormValid() && this.videoUnitForm()?.isFormValid() && this.onlineUnitForm()?.isFormValid(); + return this.textUnitForm()?.isFormValid() && this.videoUnitForm()?.isFormValid() && this.onlineUnitForm()?.isFormValid() && this.attachmentUnitForm()?.isFormValid(); }); isEditingLectureUnit: boolean; From 4066e345821ba24d7e8e4b577545f2df8ed054d2 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 21:51:09 +0100 Subject: [PATCH 013/125] Forward unit valid state to status bar --- .../create-exercise-unit.component.ts | 30 +++++++------------ .../app/lecture/lecture-update.component.ts | 2 +- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component.ts index 2c3ee4dee97d..78fdcf085a83 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component.ts @@ -18,25 +18,18 @@ import { faSort, faTimes } from '@fortawesome/free-solid-svg-icons'; styleUrls: ['./create-exercise-unit.component.scss'], }) export class CreateExerciseUnitComponent implements OnInit { - @Input() - hasCancelButton: boolean; - @Input() - hasCreateExerciseButton: boolean; - @Input() - shouldNavigateOnSubmit = true; - @Input() - lectureId: number | undefined; - @Input() - courseId: number | undefined; - @Input() - currentWizardStep: number; + protected readonly faTimes = faTimes; + protected readonly faSort = faSort; - @Output() - onCancel: EventEmitter = new EventEmitter(); - @Output() - onExerciseUnitCreated: EventEmitter = new EventEmitter(); + @Input() hasCancelButton: boolean; + @Input() hasCreateExerciseButton: boolean; + @Input() shouldNavigateOnSubmit = true; + @Input() lectureId: number | undefined; + @Input() courseId: number | undefined; + @Input() currentWizardStep: number; - faTimes = faTimes; + @Output() onCancel: EventEmitter = new EventEmitter(); + @Output() onExerciseUnitCreated: EventEmitter = new EventEmitter(); predicate = 'type'; reverse = false; @@ -45,9 +38,6 @@ export class CreateExerciseUnitComponent implements OnInit { exercisesAvailableForUnitCreation: Exercise[] = []; exercisesToCreateUnitFor: Exercise[] = []; - // Icons - faSort = faSort; - constructor( private activatedRoute: ActivatedRoute, private router: Router, diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index efb48ef0a633..7d2090f957fc 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -85,7 +85,7 @@ export class LectureUpdateComponent implements OnInit { }, { title: 'artemisApp.lecture.wizardMode.steps.unitsStepTitle', - valid: Boolean(this.unitSection()), + valid: Boolean(this.unitSection().isUnitConfigurationValid()), }, ]; this.formStatusSections = updatedFormStatusSections; From 7f5d8a998272d1bcb6c8534c87c2bfd788004668 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 21:52:50 +0100 Subject: [PATCH 014/125] Fixing accessing signals --- .../wizard-mode/lecture-wizard-units.component.html | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html index 780955f3ab02..3736d642d80e 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html @@ -16,11 +16,10 @@

@if (!isEditingLectureUnit) {

- } - @if (isEditingLectureUnit) { + } @else {

} - @if (isTextUnitFormOpen) { + @if (isTextUnitFormOpen()) { } - @if (isVideoUnitFormOpen) { + @if (isVideoUnitFormOpen()) { } - @if (isOnlineUnitFormOpen) { + @if (isOnlineUnitFormOpen()) { } - @if (isAttachmentUnitFormOpen) { + @if (isAttachmentUnitFormOpen()) { } - @if (isExerciseUnitFormOpen) { + @if (isExerciseUnitFormOpen()) { Date: Sat, 2 Nov 2024 21:54:06 +0100 Subject: [PATCH 015/125] Fixing statusbar state for units when no unit dialog is open --- .../wizard-mode/lecture-wizard-units.component.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts index 1cc7bd9487b7..e274d8f8592b 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts @@ -37,9 +37,14 @@ export class LectureUpdateWizardUnitsComponent implements OnInit { videoUnitForm = viewChild(VideoUnitFormComponent); onlineUnitForm = viewChild(OnlineUnitFormComponent); attachmentUnitForm = viewChild(AttachmentUnitFormComponent); - exerciseUnitForm = viewChild(CreateExerciseUnitComponent); + exerciseUnitForm = viewChild(CreateExerciseUnitComponent); // TODO add status fo unit form isUnitConfigurationValid = computed(() => { - return this.textUnitForm()?.isFormValid() && this.videoUnitForm()?.isFormValid() && this.onlineUnitForm()?.isFormValid() && this.attachmentUnitForm()?.isFormValid(); + return ( + (this.textUnitForm()?.isFormValid() || !this.isTextUnitFormOpen()) && + (this.videoUnitForm()?.isFormValid() || !this.isVideoUnitFormOpen()) && + (this.onlineUnitForm()?.isFormValid() || !this.isOnlineUnitFormOpen()) && + (this.attachmentUnitForm()?.isFormValid() || !this.isAttachmentUnitFormOpen()) + ); }); isEditingLectureUnit: boolean; From b4617c5a1767691ae3dc8339c0ec7c34bbac0d63 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 21:56:22 +0100 Subject: [PATCH 016/125] Add note for needed changes in lecture-update.component.html --- src/main/webapp/app/lecture/lecture-update.component.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index 49c4c3b4046e..8b63ad50ce2c 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -27,6 +27,8 @@

+ +
From 6998fbd3ec160792c0cb76838d738c943d94703c Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 22:00:32 +0100 Subject: [PATCH 017/125] Use signal instead of subscription --- .../text-unit-form.component.ts | 39 ++++++------------- .../app/lecture/lecture-update.component.ts | 8 ++-- 2 files changed, 16 insertions(+), 31 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts index e8a8584a4e8e..bc02188fe251 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, signal } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, computed, inject } from '@angular/core'; import dayjs from 'dayjs/esm'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; @@ -6,7 +6,8 @@ import { Subject, Subscription } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { faTimes } from '@fortawesome/free-solid-svg-icons'; -import { CompetencyLectureUnitLink } from 'app/entities/competency.model'; +import { Competency, CompetencyLectureUnitLink } from 'app/entities/competency.model'; +import { toSignal } from '@angular/core/rxjs-interop'; export interface TextUnitFormData { name?: string; @@ -31,20 +32,25 @@ export class TextUnitFormComponent implements OnInit, OnChanges, OnDestroy { @Input() hasCancelButton: boolean; @Output() onCancel: EventEmitter = new EventEmitter(); - form: FormGroup; + private readonly formBuilder = inject(FormBuilder); + form: FormGroup = this.formBuilder.group({ + name: [undefined as string | undefined, [Validators.required, Validators.maxLength(255)]], + releaseDate: [undefined as dayjs.Dayjs | undefined], + competencies: [undefined as Competency[] | undefined], + }); + // not included in reactive form content: string | undefined; contentLoadedFromCache = false; firstMarkdownChangeHappened = false; - isFormValid = signal(false); + private readonly statusChanges = toSignal(this.form.statusChanges ?? 'INVALID'); + isFormValid = computed(() => this.statusChanges() === 'VALID'); private markdownChanges = new Subject(); private markdownChangesSubscription: Subscription; - private formValidityChangesSubscription: Subscription; constructor( - private fb: FormBuilder, private router: Router, private translateService: TranslateService, ) {} @@ -58,7 +64,6 @@ export class TextUnitFormComponent implements OnInit, OnChanges, OnDestroy { } ngOnChanges(): void { - this.initializeForm(); if (this.isEditMode && this.formData) { this.setFormValues(this.formData); } @@ -66,7 +71,6 @@ export class TextUnitFormComponent implements OnInit, OnChanges, OnDestroy { ngOnDestroy() { this.markdownChangesSubscription.unsubscribe(); - this.formValidityChangesSubscription.unsubscribe(); } ngOnInit(): void { @@ -86,25 +90,6 @@ export class TextUnitFormComponent implements OnInit, OnChanges, OnDestroy { this.firstMarkdownChangeHappened = true; } }); - this.initializeForm(); - } - - private initializeForm() { - if (this.form) { - return; - } - this.form = this.fb.group({ - name: [undefined as string | undefined, [Validators.required, Validators.maxLength(255)]], - releaseDate: [undefined as dayjs.Dayjs | undefined], - competencyLinks: [undefined as CompetencyLectureUnitLink[] | undefined], - }); - - if (this.formValidityChangesSubscription) { - this.formValidityChangesSubscription.unsubscribe(); - } - this.formValidityChangesSubscription = this.form.statusChanges.subscribe(() => { - this.isFormValid.set(this.form.valid); - }); } private setFormValues(formData: TextUnitFormData) { diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 7d2090f957fc..6c3c8314f0ec 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -40,7 +40,7 @@ export class LectureUpdateComponent implements OnInit { @ViewChild(LectureUpdateWizardComponent, { static: false }) wizardComponent: LectureUpdateWizardComponent; @ViewChild(ProgrammingExerciseDifficultyComponent) lecturePeriodComponent?: LectureUpdatePeriodComponent; titleSection = viewChild.required(LectureTitleChannelNameComponent); - unitSection = viewChild.required(LectureUpdateWizardUnitsComponent); + unitSection = viewChild(LectureUpdateWizardUnitsComponent); lecture: Lecture; isSaving: boolean; @@ -85,7 +85,7 @@ export class LectureUpdateComponent implements OnInit { }, { title: 'artemisApp.lecture.wizardMode.steps.unitsStepTitle', - valid: Boolean(this.unitSection().isUnitConfigurationValid()), + valid: Boolean(this.unitSection()?.isUnitConfigurationValid()), }, ]; this.formStatusSections = updatedFormStatusSections; @@ -119,8 +119,8 @@ export class LectureUpdateComponent implements OnInit { /** * Revert to the previous state, equivalent with pressing the back button on your browser - * Returns to the detail page if there is no previous state and we edited an existing lecture - * Returns to the overview page if there is no previous state and we created a new lecture + * Returns to the detail page if there is no previous state, and we edited an existing lecture + * Returns to the overview page if there is no previous state, and we created a new lecture */ previousState() { this.navigationUtilService.navigateBackWithOptional(['course-management', this.lecture.course!.id!.toString(), 'lectures'], this.lecture.id?.toString()); From 9b207575b851f9b3a116fba59c84a56cabd295bf Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 22:04:08 +0100 Subject: [PATCH 018/125] Use signal instead of subscription: video-unit-form.component.ts --- .../text-unit-form.component.ts | 11 ++-- .../video-unit-form.component.ts | 54 ++++++------------- 2 files changed, 21 insertions(+), 44 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts index bc02188fe251..5859350d31e5 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts @@ -32,18 +32,19 @@ export class TextUnitFormComponent implements OnInit, OnChanges, OnDestroy { @Input() hasCancelButton: boolean; @Output() onCancel: EventEmitter = new EventEmitter(); + // not included in reactive form + content: string | undefined; + contentLoadedFromCache = false; + firstMarkdownChangeHappened = false; + private readonly formBuilder = inject(FormBuilder); + form: FormGroup = this.formBuilder.group({ name: [undefined as string | undefined, [Validators.required, Validators.maxLength(255)]], releaseDate: [undefined as dayjs.Dayjs | undefined], competencies: [undefined as Competency[] | undefined], }); - // not included in reactive form - content: string | undefined; - contentLoadedFromCache = false; - firstMarkdownChangeHappened = false; - private readonly statusChanges = toSignal(this.form.statusChanges ?? 'INVALID'); isFormValid = computed(() => this.statusChanges() === 'VALID'); diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.ts index 2d2ce9c1bcbc..da1775692397 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.ts @@ -1,10 +1,10 @@ import dayjs from 'dayjs/esm'; -import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, signal } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, Output, computed, inject } from '@angular/core'; import { AbstractControl, FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms'; import urlParser from 'js-video-url-parser'; import { faArrowLeft, faTimes } from '@fortawesome/free-solid-svg-icons'; -import { CompetencyLectureUnitLink } from 'app/entities/competency.model'; -import { Subscription } from 'rxjs'; +import { Competency, CompetencyLectureUnitLink } from 'app/entities/competency.model'; +import { toSignal } from '@angular/core/rxjs-interop'; export interface VideoUnitFormData { name?: string; @@ -58,7 +58,7 @@ function videoSourceUrlValidator(control: AbstractControl): ValidationErrors | u selector: 'jhi-video-unit-form', templateUrl: './video-unit-form.component.html', }) -export class VideoUnitFormComponent implements OnInit, OnChanges, OnDestroy { +export class VideoUnitFormComponent implements OnChanges { protected readonly faTimes = faTimes; protected readonly faArrowLeft = faArrowLeft; @@ -66,7 +66,6 @@ export class VideoUnitFormComponent implements OnInit, OnChanges, OnDestroy { @Input() isEditMode = false; @Output() formSubmitted: EventEmitter = new EventEmitter(); - form: FormGroup; @Input() hasCancelButton: boolean; @Output() onCancel: EventEmitter = new EventEmitter(); @@ -74,10 +73,17 @@ export class VideoUnitFormComponent implements OnInit, OnChanges, OnDestroy { videoSourceUrlValidator = videoSourceUrlValidator; videoSourceTransformUrlValidator = videoSourceTransformUrlValidator; - isFormValid = signal(false); - private formValidityChangesSubscription: Subscription; - - constructor(private fb: FormBuilder) {} + private readonly formBuilder = inject(FormBuilder); + form: FormGroup = this.formBuilder.group({ + name: [undefined as string | undefined, [Validators.required, Validators.maxLength(255)]], + description: [undefined as string | undefined, [Validators.maxLength(1000)]], + releaseDate: [undefined as dayjs.Dayjs | undefined], + source: [undefined as string | undefined, [Validators.required, this.videoSourceUrlValidator]], + urlHelper: [undefined as string | undefined, this.videoSourceTransformUrlValidator], + competencies: [undefined as Competency[] | undefined], + }); + private readonly statusChanges = toSignal(this.form.statusChanges ?? 'INVALID'); + isFormValid = computed(() => this.statusChanges() === 'VALID'); get nameControl() { return this.form.get('name'); @@ -100,41 +106,11 @@ export class VideoUnitFormComponent implements OnInit, OnChanges, OnDestroy { } ngOnChanges(): void { - this.initializeForm(); if (this.isEditMode && this.formData) { this.setFormValues(this.formData); } } - ngOnInit(): void { - this.initializeForm(); - } - - ngOnDestroy() { - this.formValidityChangesSubscription.unsubscribe(); - } - - private initializeForm() { - if (this.form) { - return; - } - this.form = this.fb.group({ - name: [undefined as string | undefined, [Validators.required, Validators.maxLength(255)]], - description: [undefined as string | undefined, [Validators.maxLength(1000)]], - releaseDate: [undefined as dayjs.Dayjs | undefined], - source: [undefined as string | undefined, [Validators.required, this.videoSourceUrlValidator]], - urlHelper: [undefined as string | undefined, this.videoSourceTransformUrlValidator], - competencyLinks: [undefined as CompetencyLectureUnitLink[] | undefined], - }); - - if (this.formValidityChangesSubscription) { - this.formValidityChangesSubscription.unsubscribe(); - } - this.formValidityChangesSubscription = this.form.statusChanges.subscribe(() => { - this.isFormValid.set(this.form.valid); - }); - } - private setFormValues(formData: VideoUnitFormData) { this.form.patchValue(formData); } From 30e6d2d1eb65f6ef216685e948cce27349005635 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 22:06:09 +0100 Subject: [PATCH 019/125] Use signal instead of subscription: online-unit-form.component.ts --- .../online-unit-form.component.ts | 58 ++++++------------- 1 file changed, 17 insertions(+), 41 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts index d659ec2b3701..9a84a6e6a326 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts @@ -1,13 +1,13 @@ import dayjs from 'dayjs/esm'; -import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, signal } from '@angular/core'; +import { Component, EventEmitter, Input, OnChanges, Output, computed, inject } from '@angular/core'; import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { faArrowLeft, faTimes } from '@fortawesome/free-solid-svg-icons'; import { HttpResponse } from '@angular/common/http'; import { OnlineResourceDTO } from 'app/lecture/lecture-unit/lecture-unit-management/online-resource-dto.model'; import { OnlineUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/onlineUnit.service'; -import { CompetencyLectureUnitLink } from 'app/entities/competency.model'; -import { Subscription, map } from 'rxjs'; - +import { Competency, CompetencyLectureUnitLink } from 'app/entities/competency.model'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { map } from 'rxjs'; export interface OnlineUnitFormData { name?: string; description?: string; @@ -32,7 +32,7 @@ function urlValidator(control: AbstractControl) { selector: 'jhi-online-unit-form', templateUrl: './online-unit-form.component.html', }) -export class OnlineUnitFormComponent implements OnInit, OnChanges, OnDestroy { +export class OnlineUnitFormComponent implements OnChanges { protected faTimes = faTimes; protected faArrowLeft = faArrowLeft; @@ -40,20 +40,25 @@ export class OnlineUnitFormComponent implements OnInit, OnChanges, OnDestroy { @Input() isEditMode = false; @Output() formSubmitted: EventEmitter = new EventEmitter(); - form: FormGroup; @Input() hasCancelButton: boolean; @Output() onCancel: EventEmitter = new EventEmitter(); urlValidator = urlValidator; - isFormValid = signal(false); - private formValidityChangesSubscription: Subscription; + private readonly formBuilder = inject(FormBuilder); + form: FormGroup = this.formBuilder.group({ + name: [undefined, [Validators.required, Validators.maxLength(255)]], + description: [undefined, [Validators.maxLength(1000)]], + releaseDate: [undefined], + source: [undefined, [Validators.required, this.urlValidator]], + competencies: [undefined as Competency[] | undefined], + }); + + private readonly statusChanges = toSignal(this.form.statusChanges ?? 'INVALID'); + isFormValid = computed(() => this.statusChanges() === 'VALID'); - constructor( - private fb: FormBuilder, - private onlineUnitService: OnlineUnitService, - ) {} + constructor(private onlineUnitService: OnlineUnitService) {} get nameControl() { return this.form.get('name'); @@ -72,40 +77,11 @@ export class OnlineUnitFormComponent implements OnInit, OnChanges, OnDestroy { } ngOnChanges(): void { - this.initializeForm(); if (this.isEditMode && this.formData) { this.setFormValues(this.formData); } } - ngOnInit(): void { - this.initializeForm(); - } - - ngOnDestroy() { - this.formValidityChangesSubscription.unsubscribe(); - } - - private initializeForm() { - if (this.form) { - return; - } - this.form = this.fb.group({ - name: [undefined, [Validators.required, Validators.maxLength(255)]], - description: [undefined, [Validators.maxLength(1000)]], - releaseDate: [undefined], - source: [undefined, [Validators.required, this.urlValidator]], - competencyLinks: [undefined as CompetencyLectureUnitLink[] | undefined], - }); - - if (this.formValidityChangesSubscription) { - this.formValidityChangesSubscription.unsubscribe(); - } - this.formValidityChangesSubscription = this.form.statusChanges.subscribe(() => { - this.isFormValid.set(this.form.valid); - }); - } - private setFormValues(formData: OnlineUnitFormData) { this.form.patchValue(formData); } From 6671e5281dbac63775ce3abe76372e5122bb80bd Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 22:08:37 +0100 Subject: [PATCH 020/125] Use signal instead of subscription: attachment-unit-form.component.ts --- .../attachment-unit-form.component.ts | 52 ++++++------------- 1 file changed, 17 insertions(+), 35 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component.ts index b5e1aece70db..2de384538ad2 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component.ts @@ -1,11 +1,11 @@ -import { Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild, computed, signal } from '@angular/core'; +import { Component, ElementRef, EventEmitter, Input, OnChanges, Output, ViewChild, computed, inject, signal } from '@angular/core'; import dayjs from 'dayjs/esm'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { faQuestionCircle, faTimes } from '@fortawesome/free-solid-svg-icons'; import { UPLOAD_FILE_EXTENSIONS } from 'app/shared/constants/file-extensions.constants'; -import { CompetencyLectureUnitLink } from 'app/entities/competency.model'; +import { Competency, CompetencyLectureUnitLink } from 'app/entities/competency.model'; import { MAX_FILE_SIZE } from 'app/shared/constants/input.constants'; -import { Subscription } from 'rxjs'; +import { toSignal } from '@angular/core/rxjs-interop'; export interface AttachmentUnitFormData { formProperties: FormProperties; @@ -32,7 +32,7 @@ export interface FileProperties { selector: 'jhi-attachment-unit-form', templateUrl: './attachment-unit-form.component.html', }) -export class AttachmentUnitFormComponent implements OnInit, OnChanges { +export class AttachmentUnitFormComponent implements OnChanges { protected readonly faQuestionCircle = faQuestionCircle; protected readonly faTimes = faTimes; // A human-readable list of allowed file extensions @@ -44,7 +44,6 @@ export class AttachmentUnitFormComponent implements OnInit, OnChanges { @Input() isEditMode = false; @Output() formSubmitted: EventEmitter = new EventEmitter(); - form: FormGroup; @Input() hasCancelButton: boolean; @Output() onCancel: EventEmitter = new EventEmitter(); @@ -57,45 +56,28 @@ export class AttachmentUnitFormComponent implements OnInit, OnChanges { fileName = signal(undefined); isFileTooBig = signal(false); - isFormValidWithoutExtraValidation = signal(false); - isFormValid = computed(() => { - return (this.isFormValidWithoutExtraValidation() || this.fileName()) && !this.isFileTooBig(); + + private readonly formBuilder = inject(FormBuilder); + form: FormGroup = this.formBuilder.group({ + name: [undefined as string | undefined, [Validators.required, Validators.maxLength(255)]], + description: [undefined as string | undefined, [Validators.maxLength(1000)]], + releaseDate: [undefined as dayjs.Dayjs | undefined], + version: [{ value: 1, disabled: true }], + updateNotificationText: [undefined as string | undefined, [Validators.maxLength(1000)]], + competencies: [undefined as Competency[] | undefined], }); + private readonly statusChanges = toSignal(this.form.statusChanges ?? 'INVALID'); - private formValidityChangesSubscription: Subscription; + isFormValid = computed(() => { + return (this.statusChanges() === 'VALID' || this.fileName()) && !this.isFileTooBig(); + }); constructor(private fb: FormBuilder) {} ngOnChanges(): void { - this.initializeForm(); if (this.isEditMode && this.formData) { this.setFormValues(this.formData); } - - if (this.formValidityChangesSubscription) { - this.formValidityChangesSubscription.unsubscribe(); - } - this.formValidityChangesSubscription = this.form.statusChanges.subscribe(() => { - this.isFormValidWithoutExtraValidation.set(this.form.valid); - }); - } - - ngOnInit(): void { - this.initializeForm(); - } - - private initializeForm() { - if (this.form) { - return; - } - this.form = this.fb.group({ - name: [undefined as string | undefined, [Validators.required, Validators.maxLength(255)]], - description: [undefined as string | undefined, [Validators.maxLength(1000)]], - releaseDate: [undefined as dayjs.Dayjs | undefined], - version: [{ value: 1, disabled: true }], - updateNotificationText: [undefined as string | undefined, [Validators.maxLength(1000)]], - competencyLinks: [undefined as CompetencyLectureUnitLink[] | undefined], - }); } onFileChange(event: Event): void { From e066a2aa90bd13ab7dfa216041adafe8ef3ac375 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 22:10:38 +0100 Subject: [PATCH 021/125] Use signals in datepicker --- .../app/lecture/lecture-update.component.ts | 30 ++++++++++++------- .../date-time-picker.component.html | 8 ++--- .../date-time-picker.component.ts | 8 ++--- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 6c3c8314f0ec..886060e9adc3 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, ViewChild, effect, viewChild } from '@angular/core'; +import { Component, OnInit, ViewChild, effect, signal, viewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; import { Observable } from 'rxjs'; @@ -37,6 +37,8 @@ export class LectureUpdateComponent implements OnInit { // The list of file extensions for the "accept" attribute of the file input field protected readonly acceptedFileExtensionsFileBrowser = UPLOAD_FILE_EXTENSIONS.map((ext) => '.' + ext).join(','); + isEditMode = signal(false); + @ViewChild(LectureUpdateWizardComponent, { static: false }) wizardComponent: LectureUpdateWizardComponent; @ViewChild(ProgrammingExerciseDifficultyComponent) lecturePeriodComponent?: LectureUpdatePeriodComponent; titleSection = viewChild.required(LectureTitleChannelNameComponent); @@ -70,7 +72,7 @@ export class LectureUpdateComponent implements OnInit { ) { effect(() => { // noinspection UnnecessaryLocalVariableJS: not inlined because the variable name improves readability - const updatedFormStatusSections: FormSectionStatus[] = [ + let updatedFormStatusSections: FormSectionStatus[] = [ { title: 'artemisApp.lecture.wizardMode.steps.titleStepTitle', valid: Boolean(this.titleSection().titleChannelNameComponent().isFormValidSignal()), @@ -79,15 +81,21 @@ export class LectureUpdateComponent implements OnInit { title: 'artemisApp.lecture.wizardMode.steps.periodStepTitle', valid: true, // TODO retrieve the valid status from the datepickeres }, - { - title: 'artemisApp.lecture.wizardMode.steps.attachmentsStepTitle', - valid: true, // TODO retrieve the valid status - }, - { - title: 'artemisApp.lecture.wizardMode.steps.unitsStepTitle', - valid: Boolean(this.unitSection()?.isUnitConfigurationValid()), - }, ]; + if (this.isEditMode()) { + updatedFormStatusSections = [ + ...updatedFormStatusSections, + { + title: 'artemisApp.lecture.wizardMode.steps.attachmentsStepTitle', + valid: true, // TODO retrieve the valid status + }, + { + title: 'artemisApp.lecture.wizardMode.steps.unitsStepTitle', + valid: Boolean(this.unitSection()?.isUnitConfigurationValid()), + }, + ]; + } + this.formStatusSections = updatedFormStatusSections; }); } @@ -115,6 +123,8 @@ export class LectureUpdateComponent implements OnInit { this.isShowingWizardMode = params.shouldBeInWizardMode; } }); + + this.isEditMode.set(this.lecture.id !== undefined); } /** diff --git a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.html b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.html index b1983a2ef7d2..7a5480a8caca 100644 --- a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.html +++ b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.html @@ -19,8 +19,8 @@ #dateInput="ngModel" class="form-control position-relative ps-5" id="date-input-field" - [ngClass]="{ 'is-invalid': error || dateInput.invalid || (requiredField && !dateInput.value) || warning, 'border-warning': warning }" - [class.ng-invalid]="error || dateInput.invalid || (requiredField && !dateInput.value) || warning" + [ngClass]="{ 'is-invalid': error() || dateInput.invalid || (requiredField() && !dateInput.value) || warning(), 'border-warning': warning() }" + [class.ng-invalid]="error() || dateInput.invalid || (requiredField() && !dateInput.value) || warning()" [ngModel]="value" [disabled]="disabled" [min]="minDate" @@ -40,10 +40,10 @@
-@if (dateInput.invalid || (requiredField && !dateInput.value)) { +@if (dateInput.invalid || (requiredField() && !dateInput.value)) {
} -@if (warning) { +@if (warning()) {
diff --git a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts index 86a1f85a2371..6ba5e2b82b84 100644 --- a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts +++ b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, Output, ViewChild, forwardRef } from '@angular/core'; +import { Component, EventEmitter, Input, Output, ViewChild, forwardRef, input } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgModel } from '@angular/forms'; import { faCalendarAlt, faCircleXmark, faClock, faGlobe, faQuestionCircle, faTriangleExclamation } from '@fortawesome/free-solid-svg-icons'; import dayjs from 'dayjs/esm'; @@ -28,9 +28,9 @@ export class FormDateTimePickerComponent implements ControlValueAccessor { @Input() labelTooltip: string; @Input() value: any; @Input() disabled: boolean; - @Input() error: boolean; - @Input() warning: boolean; - @Input() requiredField: boolean = false; + error = input(false); + warning = input(false); + requiredField = input(false); @Input() startAt?: dayjs.Dayjs; // Default selected date. By default, this sets it to the current time without seconds or milliseconds; @Input() min?: dayjs.Dayjs; // Dates before this date are not selectable. @Input() max?: dayjs.Dayjs; // Dates after this date are not selectable. From 50ba4c28a585cbb8f3c7e3528cf2c937463fef47 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 22:13:41 +0100 Subject: [PATCH 022/125] Finally using signals in datepicker --- .../date-time-picker.component.html | 4 ++-- .../date-time-picker.component.ts | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.html b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.html index 7a5480a8caca..7aff1507622e 100644 --- a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.html +++ b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.html @@ -19,8 +19,8 @@ #dateInput="ngModel" class="form-control position-relative ps-5" id="date-input-field" - [ngClass]="{ 'is-invalid': error() || dateInput.invalid || (requiredField() && !dateInput.value) || warning(), 'border-warning': warning() }" - [class.ng-invalid]="error() || dateInput.invalid || (requiredField() && !dateInput.value) || warning()" + [ngClass]="{ 'is-invalid': !isValid(), 'border-warning': warning() }" + [class.ng-invalid]="!isValid()" [ngModel]="value" [disabled]="disabled" [min]="minDate" diff --git a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts index 6ba5e2b82b84..e93a8578d717 100644 --- a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts +++ b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, Output, ViewChild, forwardRef, input } from '@angular/core'; +import { Component, EventEmitter, Input, Output, ViewChild, computed, forwardRef, input, signal } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgModel } from '@angular/forms'; import { faCalendarAlt, faCircleXmark, faClock, faGlobe, faQuestionCircle, faTriangleExclamation } from '@fortawesome/free-solid-svg-icons'; import dayjs from 'dayjs/esm'; @@ -39,11 +39,23 @@ export class FormDateTimePickerComponent implements ControlValueAccessor { private onChange?: (val?: dayjs.Dayjs) => void; + protected isInputValid = signal(false); + protected dateInputValue = signal(''); + isValid = computed(() => { + const isInvalid = this.error() || !this.isInputValid() || (this.requiredField() && !this.dateInputValue()) || this.warning(); + return !isInvalid; + }); + updateSignals(): void { + this.isInputValid.set(!Boolean(this.dateInput.invalid)); + this.dateInputValue.set(this.dateInput.value); + } + /** * Emits the value change from component. */ valueChanged() { this.valueChange.emit(); + this.updateSignals(); } /** @@ -57,6 +69,7 @@ export class FormDateTimePickerComponent implements ControlValueAccessor { } else { this.value = value; } + this.updateSignals(); } /** @@ -117,5 +130,6 @@ export class FormDateTimePickerComponent implements ControlValueAccessor { */ clearDate() { this.dateInput.reset(undefined); + this.updateSignals(); } } From ebce6d50d8d423c9922cbd668103ac15a342a4a3 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 22:15:01 +0100 Subject: [PATCH 023/125] Fix StatusBar computation of period value --- .../app/lecture/lecture-update.component.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 886060e9adc3..349e3e7c9cd3 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, ViewChild, effect, signal, viewChild } from '@angular/core'; +import { Component, OnInit, Signal, ViewChild, computed, effect, signal, viewChild, viewChildren } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; import { Observable } from 'rxjs'; @@ -19,6 +19,7 @@ import { FormSectionStatus } from 'app/forms/form-status-bar/form-status-bar.com import { LectureUpdatePeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component'; import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; +import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; @Component({ selector: 'jhi-lecture-update', @@ -42,8 +43,18 @@ export class LectureUpdateComponent implements OnInit { @ViewChild(LectureUpdateWizardComponent, { static: false }) wizardComponent: LectureUpdateWizardComponent; @ViewChild(ProgrammingExerciseDifficultyComponent) lecturePeriodComponent?: LectureUpdatePeriodComponent; titleSection = viewChild.required(LectureTitleChannelNameComponent); + periodSectionDatepickers = viewChildren(FormDateTimePickerComponent); unitSection = viewChild(LectureUpdateWizardUnitsComponent); + isPeriodSectionValid: Signal = computed(() => { + for (const periodSectionDatepicker of this.periodSectionDatepickers()) { + if (!periodSectionDatepicker.isValid()) { + return false; + } + } + return true; + }); + lecture: Lecture; isSaving: boolean; isProcessing: boolean; @@ -79,7 +90,7 @@ export class LectureUpdateComponent implements OnInit { }, { title: 'artemisApp.lecture.wizardMode.steps.periodStepTitle', - valid: true, // TODO retrieve the valid status from the datepickeres + valid: Boolean(this.isPeriodSectionValid()), }, ]; if (this.isEditMode()) { From 289b79f24425891d6b9d0963cb7dec8abce3ce7d Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 2 Nov 2024 22:16:26 +0100 Subject: [PATCH 024/125] Add comment on adjustment within edit mode --- src/main/webapp/app/lecture/lecture-update.component.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 349e3e7c9cd3..a2a252c83dc6 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -93,6 +93,8 @@ export class LectureUpdateComponent implements OnInit { valid: Boolean(this.isPeriodSectionValid()), }, ]; + + // only in edit mode we have the lecture id which is currently required to store units and attachments if (this.isEditMode()) { updatedFormStatusSections = [ ...updatedFormStatusSections, From 80862a867b021c808d75cef2991f498f6c40bb90 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Wed, 6 Nov 2024 21:00:22 +0100 Subject: [PATCH 025/125] Fix signal call --- .../information/programming-exercise-information.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/app/exercises/programming/manage/update/update-components/information/programming-exercise-information.component.ts b/src/main/webapp/app/exercises/programming/manage/update/update-components/information/programming-exercise-information.component.ts index 78158c26e1ce..6893eef163cf 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/update-components/information/programming-exercise-information.component.ts +++ b/src/main/webapp/app/exercises/programming/manage/update/update-components/information/programming-exercise-information.component.ts @@ -161,7 +161,7 @@ export class ProgrammingExerciseInformationComponent implements AfterViewInit, O const areAuxiliaryRepositoriesValid = this.areAuxiliaryRepositoriesValid(); const areCheckoutPathsValid = this.areCheckoutPathsValid(); this.formValid = Boolean( - this.exerciseTitleChannelComponent()?.titleChannelNameComponent?.formValidSignal() && + this.exerciseTitleChannelComponent()?.titleChannelNameComponent?.isFormValidSignal() && this.getIsShortNameFieldValid() && isCheckoutSolutionRepositoryValid && isRecreateBuildPlansValid && From 3f770619dba0c353265ea68778e5e754c286180a Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Wed, 6 Nov 2024 21:00:34 +0100 Subject: [PATCH 026/125] Make links clickable --- src/main/webapp/app/lecture/lecture-update.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index 8b63ad50ce2c..cef83e325c23 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -26,9 +26,9 @@

- + - +
From c638460b9f4a1f195b1920504828b468d7a25b48 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Wed, 6 Nov 2024 21:34:30 +0100 Subject: [PATCH 027/125] Use signals for date-time-picker.component.ts --- .../date-time-picker.component.html | 26 ++++----- .../date-time-picker.component.ts | 56 +++++++++---------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.html b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.html index b1983a2ef7d2..90af5cb06b92 100644 --- a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.html +++ b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.html @@ -1,14 +1,14 @@ @if (labelName) { } -@if (labelTooltip) { - +@if (labelTooltip()) { + } -@if (shouldDisplayTimeZoneWarning) { +@if (shouldDisplayTimeZoneWarning()) { @@ -19,12 +19,12 @@ #dateInput="ngModel" class="form-control position-relative ps-5" id="date-input-field" - [ngClass]="{ 'is-invalid': error || dateInput.invalid || (requiredField && !dateInput.value) || warning, 'border-warning': warning }" - [class.ng-invalid]="error || dateInput.invalid || (requiredField && !dateInput.value) || warning" + [ngClass]="{ 'is-invalid': error() || dateInput.invalid || (requiredField() && !dateInput.value) || warning(), 'border-warning': warning() }" + [class.ng-invalid]="error() || dateInput.invalid || (requiredField() && !dateInput.value) || warning()" [ngModel]="value" - [disabled]="disabled" - [min]="minDate" - [max]="maxDate" + [disabled]="disabled()" + [min]="minDate()" + [max]="maxDate()" (ngModelChange)="updateField($event)" [owlDateTime]="dt" name="datePicker" @@ -38,12 +38,12 @@
- +
-@if (dateInput.invalid || (requiredField && !dateInput.value)) { -
+@if (dateInput.invalid || (requiredField() && !dateInput.value)) { +
} -@if (warning) { +@if (warning()) {
diff --git a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts index 58196f94f02d..d02dba86213d 100644 --- a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts +++ b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, Output, ViewChild, forwardRef } from '@angular/core'; +import { Component, Input, ViewChild, computed, forwardRef, input, output } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgModel } from '@angular/forms'; import { faCalendarAlt, faCircleXmark, faClock, faGlobe, faQuestionCircle, faTriangleExclamation } from '@fortawesome/free-solid-svg-icons'; import dayjs from 'dayjs/esm'; @@ -16,26 +16,26 @@ import dayjs from 'dayjs/esm'; ], }) export class FormDateTimePickerComponent implements ControlValueAccessor { + protected readonly faCalendarAlt = faCalendarAlt; + protected readonly faGlobe = faGlobe; + protected readonly faClock = faClock; + protected readonly faQuestionCircle = faQuestionCircle; + protected readonly faCircleXmark = faCircleXmark; + protected readonly faTriangleExclamation = faTriangleExclamation; + @ViewChild('dateInput', { static: false }) dateInput: NgModel; - @Input() labelName: string; - @Input() labelTooltip: string; + labelName = input(); + labelTooltip = input(); @Input() value: any; - @Input() disabled: boolean; - @Input() error: boolean; - @Input() warning: boolean; - @Input() requiredField: boolean = false; - @Input() startAt?: dayjs.Dayjs; // Default selected date. By default, this sets it to the current time without seconds or milliseconds; - @Input() min?: dayjs.Dayjs; // Dates before this date are not selectable. - @Input() max?: dayjs.Dayjs; // Dates after this date are not selectable. - @Input() shouldDisplayTimeZoneWarning = true; // Displays a warning that the current time zone might differ from the participants'. - @Output() valueChange = new EventEmitter(); - - readonly faCalendarAlt = faCalendarAlt; - readonly faGlobe = faGlobe; - readonly faClock = faClock; - readonly faQuestionCircle = faQuestionCircle; - readonly faCircleXmark = faCircleXmark; - readonly faTriangleExclamation = faTriangleExclamation; + disabled = input(); + error = input(); + warning = input(); + requiredField = input(false); + startAt? = input(); // Default selected date. By default, this sets it to the current time without seconds or milliseconds; + min? = input(); // Dates before this date are not selectable. + max? = input(); // Dates after this date are not selectable. + shouldDisplayTimeZoneWarning = input(true); // Displays a warning that the current time zone might differ from the participants'. + valueChange = output(); private onChange?: (val?: dayjs.Dayjs) => void; @@ -91,17 +91,17 @@ export class FormDateTimePickerComponent implements ControlValueAccessor { return Intl.DateTimeFormat().resolvedOptions().timeZone; } - get startDate(): Date | null { - return this.convertToDate(this.startAt ?? dayjs().startOf('minutes')); - } + startDate = computed(() => { + return this.convertToDate(this.startAt?.() ?? dayjs().startOf('minutes')); + }); - get minDate(): Date | null { - return this.convertToDate(this.min); - } + minDate = computed(() => { + return this.convertToDate(this.min?.()); + }); - get maxDate(): Date | null { - return this.convertToDate(this.max); - } + maxDate = computed(() => { + return this.convertToDate(this.max?.()); + }); /** * Function that converts a possibly undefined dayjs value to a date or null. From 026a5700e025c131704bcec35f4aed54badb96f7 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Wed, 6 Nov 2024 21:37:47 +0100 Subject: [PATCH 028/125] Introduce signal for easy retrieval of valid state --- .../date-time-picker.component.html | 4 ++-- .../date-time-picker.component.ts | 18 +++++++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.html b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.html index 90af5cb06b92..fcfa651b61a6 100644 --- a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.html +++ b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.html @@ -19,8 +19,8 @@ #dateInput="ngModel" class="form-control position-relative ps-5" id="date-input-field" - [ngClass]="{ 'is-invalid': error() || dateInput.invalid || (requiredField() && !dateInput.value) || warning(), 'border-warning': warning() }" - [class.ng-invalid]="error() || dateInput.invalid || (requiredField() && !dateInput.value) || warning()" + [ngClass]="{ 'is-invalid': isValid(), 'border-warning': warning() }" + [class.ng-invalid]="!isValid()" [ngModel]="value" [disabled]="disabled()" [min]="minDate()" diff --git a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts index d02dba86213d..eacc3eefcf02 100644 --- a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts +++ b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, ViewChild, computed, forwardRef, input, output } from '@angular/core'; +import { Component, Input, ViewChild, computed, forwardRef, input, output, signal } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgModel } from '@angular/forms'; import { faCalendarAlt, faCircleXmark, faClock, faGlobe, faQuestionCircle, faTriangleExclamation } from '@fortawesome/free-solid-svg-icons'; import dayjs from 'dayjs/esm'; @@ -37,6 +37,19 @@ export class FormDateTimePickerComponent implements ControlValueAccessor { shouldDisplayTimeZoneWarning = input(true); // Displays a warning that the current time zone might differ from the participants'. valueChange = output(); + protected isInputValid = signal(false); + protected dateInputValue = signal(''); + + isValid = computed(() => { + const isInvalid = this.error() || !this.isInputValid() || (this.requiredField() && !this.dateInputValue()) || this.warning(); + return !isInvalid; + }); + + private updateSignals(): void { + this.isInputValid.set(!Boolean(this.dateInput.invalid)); + this.dateInputValue.set(this.dateInput.value); + } + private onChange?: (val?: dayjs.Dayjs) => void; /** @@ -44,6 +57,7 @@ export class FormDateTimePickerComponent implements ControlValueAccessor { */ valueChanged() { this.valueChange.emit(); + this.updateSignals(); } /** @@ -57,6 +71,7 @@ export class FormDateTimePickerComponent implements ControlValueAccessor { } else { this.value = value; } + this.updateSignals(); } /** @@ -117,5 +132,6 @@ export class FormDateTimePickerComponent implements ControlValueAccessor { */ clearDate() { this.dateInput.reset(undefined); + this.updateSignals(); } } From 9b954b2c86c412539d046e78e652afbb9f2f9d3d Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Wed, 6 Nov 2024 21:40:51 +0100 Subject: [PATCH 029/125] Fix tests for date-time-picker.component.ts --- .../app/shared/date-time-picker/date-time-picker.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts index eacc3eefcf02..c1f06c23a88e 100644 --- a/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts +++ b/src/main/webapp/app/shared/date-time-picker/date-time-picker.component.ts @@ -46,8 +46,8 @@ export class FormDateTimePickerComponent implements ControlValueAccessor { }); private updateSignals(): void { - this.isInputValid.set(!Boolean(this.dateInput.invalid)); - this.dateInputValue.set(this.dateInput.value); + this.isInputValid.set(!Boolean(this.dateInput?.invalid)); + this.dateInputValue.set(this.dateInput?.value); } private onChange?: (val?: dayjs.Dayjs) => void; From 7848fc512e0e152099c35082d35f88e6a86575e2 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Fri, 8 Nov 2024 04:22:57 +0100 Subject: [PATCH 030/125] Place import at same place as before merge to reduce diff --- .../online-unit-form/online-unit-form.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts index 429417966be3..67adf4f2c8dc 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts @@ -2,12 +2,12 @@ import dayjs from 'dayjs/esm'; import { Component, OnChanges, computed, inject, input, output } from '@angular/core'; import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { faArrowLeft, faTimes } from '@fortawesome/free-solid-svg-icons'; +import { map } from 'rxjs'; import { HttpResponse } from '@angular/common/http'; import { OnlineResourceDTO } from 'app/lecture/lecture-unit/lecture-unit-management/online-resource-dto.model'; import { OnlineUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/onlineUnit.service'; import { CompetencyLectureUnitLink } from 'app/entities/competency.model'; import { toSignal } from '@angular/core/rxjs-interop'; -import { map } from 'rxjs'; export interface OnlineUnitFormData { name?: string; From ba2851ee211af4f9f87440f1fd2a507a27881464 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Fri, 8 Nov 2024 13:30:39 +0100 Subject: [PATCH 031/125] Undo renaming to reduce the potential of merge conflicts and minimize the diff --- .../webapp/app/lecture/lecture-update.component.ts | 4 ++-- src/main/webapp/app/lecture/lecture.module.ts | 8 ++++---- .../wizard-mode/lecture-wizard-period.component.ts | 2 +- .../wizard-mode/lecture-wizard-title.component.ts | 2 +- .../lecture-wizard-period.component.spec.ts | 10 +++++----- .../wizard-mode/lecture-wizard-title.component.spec.ts | 10 +++++----- .../wizard-mode/lecture-wizard.component.spec.ts | 8 ++++---- 7 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index a2a252c83dc6..ee3f7fb19b37 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -16,7 +16,7 @@ import { UPLOAD_FILE_EXTENSIONS } from 'app/shared/constants/file-extensions.con import { FormulaAction } from 'app/shared/monaco-editor/model/actions/formula.action'; import { ProgrammingExerciseDifficultyComponent } from 'app/exercises/programming/manage/update/update-components/difficulty/programming-exercise-difficulty.component'; import { FormSectionStatus } from 'app/forms/form-status-bar/form-status-bar.component'; -import { LectureUpdatePeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; +import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component'; import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; @@ -41,7 +41,7 @@ export class LectureUpdateComponent implements OnInit { isEditMode = signal(false); @ViewChild(LectureUpdateWizardComponent, { static: false }) wizardComponent: LectureUpdateWizardComponent; - @ViewChild(ProgrammingExerciseDifficultyComponent) lecturePeriodComponent?: LectureUpdatePeriodComponent; + @ViewChild(ProgrammingExerciseDifficultyComponent) lecturePeriodComponent?: LectureUpdateWizardPeriodComponent; titleSection = viewChild.required(LectureTitleChannelNameComponent); periodSectionDatepickers = viewChildren(FormDateTimePickerComponent); unitSection = viewChild(LectureUpdateWizardUnitsComponent); diff --git a/src/main/webapp/app/lecture/lecture.module.ts b/src/main/webapp/app/lecture/lecture.module.ts index 70741e2ecec4..298af287f797 100644 --- a/src/main/webapp/app/lecture/lecture.module.ts +++ b/src/main/webapp/app/lecture/lecture.module.ts @@ -15,8 +15,8 @@ import { ArtemisLectureUnitManagementModule } from 'app/lecture/lecture-unit/lec import { ArtemisMarkdownModule } from 'app/shared/markdown.module'; import { LectureImportComponent } from 'app/lecture/lecture-import.component'; import { ArtemisCompetenciesModule } from 'app/course/competencies/competency.module'; -import { LectureUpdateTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component'; -import { LectureUpdatePeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; +import { LectureUpdateWizardTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component'; +import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; import { LectureUpdateWizardAttachmentsComponent } from 'app/lecture/wizard-mode/lecture-wizard-attachments.component'; import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; import { LectureUpdateWizardStepComponent } from 'app/lecture/wizard-mode/lecture-update-wizard-step.component'; @@ -50,8 +50,8 @@ const ENTITY_STATES = [...lectureRoute]; LectureUpdateComponent, LectureUpdateWizardComponent, LectureAttachmentsComponent, - LectureUpdateTitleComponent, - LectureUpdatePeriodComponent, + LectureUpdateWizardTitleComponent, + LectureUpdateWizardPeriodComponent, LectureUpdateWizardAttachmentsComponent, LectureUpdateWizardUnitsComponent, LectureUpdateWizardStepComponent, diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-period.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-period.component.ts index 2b3fe734a057..7d9b48571621 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-period.component.ts +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-period.component.ts @@ -5,7 +5,7 @@ import { Lecture } from 'app/entities/lecture.model'; selector: 'jhi-lecture-update-wizard-period', templateUrl: './lecture-wizard-period.component.html', }) -export class LectureUpdatePeriodComponent { +export class LectureUpdateWizardPeriodComponent { @Input() currentStep: number; @Input() lecture: Lecture; @Input() validateDatesFunction: () => void; diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts index b67bb7d545a8..e354df72e629 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts @@ -6,7 +6,7 @@ import { FormulaAction } from 'app/shared/monaco-editor/model/actions/formula.ac selector: 'jhi-lecture-update-wizard-title', templateUrl: './lecture-wizard-title.component.html', }) -export class LectureUpdateTitleComponent { +export class LectureUpdateWizardTitleComponent { @Input() currentStep: number; @Input() lecture: Lecture; diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts index b8de6a0f6885..c807cf4c8a1e 100644 --- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts @@ -2,24 +2,24 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; import { MockComponent, MockPipe } from 'ng-mocks'; import { Lecture } from 'app/entities/lecture.model'; -import { LectureUpdatePeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; +import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; describe('LectureUpdatePeriodComponent', () => { - let wizardPeriodComponentFixture: ComponentFixture; - let wizardPeriodComponent: LectureUpdatePeriodComponent; + let wizardPeriodComponentFixture: ComponentFixture; + let wizardPeriodComponent: LectureUpdateWizardPeriodComponent; beforeEach(() => { TestBed.configureTestingModule({ imports: [ReactiveFormsModule, FormsModule], - declarations: [LectureUpdatePeriodComponent, MockPipe(ArtemisTranslatePipe), MockComponent(FormDateTimePickerComponent)], + declarations: [LectureUpdateWizardPeriodComponent, MockPipe(ArtemisTranslatePipe), MockComponent(FormDateTimePickerComponent)], providers: [], schemas: [], }) .compileComponents() .then(() => { - wizardPeriodComponentFixture = TestBed.createComponent(LectureUpdatePeriodComponent); + wizardPeriodComponentFixture = TestBed.createComponent(LectureUpdateWizardPeriodComponent); wizardPeriodComponent = wizardPeriodComponentFixture.componentInstance; wizardPeriodComponent.lecture = new Lecture(); }); diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts index fd6a2855bc73..36701004edbb 100644 --- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { LectureUpdateTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component'; +import { LectureUpdateWizardTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component'; import { Lecture } from 'app/entities/lecture.model'; import { MockComponent } from 'ng-mocks'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -7,19 +7,19 @@ import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-chan import { MarkdownEditorMonacoComponent } from 'app/shared/markdown-editor/monaco/markdown-editor-monaco.component'; describe('LectureWizardTitleComponent', () => { - let wizardTitleComponentFixture: ComponentFixture; - let wizardTitleComponent: LectureUpdateTitleComponent; + let wizardTitleComponentFixture: ComponentFixture; + let wizardTitleComponent: LectureUpdateWizardTitleComponent; beforeEach(() => { TestBed.configureTestingModule({ imports: [ReactiveFormsModule, FormsModule], - declarations: [LectureUpdateTitleComponent, MockComponent(MarkdownEditorMonacoComponent), MockComponent(LectureTitleChannelNameComponent)], + declarations: [LectureUpdateWizardTitleComponent, MockComponent(MarkdownEditorMonacoComponent), MockComponent(LectureTitleChannelNameComponent)], providers: [], schemas: [], }) .compileComponents() .then(() => { - wizardTitleComponentFixture = TestBed.createComponent(LectureUpdateTitleComponent); + wizardTitleComponentFixture = TestBed.createComponent(LectureUpdateWizardTitleComponent); wizardTitleComponent = wizardTitleComponentFixture.componentInstance; wizardTitleComponent.lecture = new Lecture(); }); diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts index 91197ed12927..1aeba59d7a0d 100644 --- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts @@ -14,8 +14,8 @@ import { CourseManagementService } from 'app/course/manage/course-management.ser import { LectureUpdateWizardStepComponent } from 'app/lecture/wizard-mode/lecture-update-wizard-step.component'; import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; import { LectureUpdateWizardAttachmentsComponent } from 'app/lecture/wizard-mode/lecture-wizard-attachments.component'; -import { LectureUpdatePeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; -import { LectureUpdateTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component'; +import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; +import { LectureUpdateWizardTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component'; import { TranslateDirective } from 'app/shared/language/translate.directive'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import dayjs from 'dayjs/esm'; @@ -33,8 +33,8 @@ describe('LectureUpdateWizardComponent', () => { MockComponent(LectureUpdateWizardStepComponent), MockComponent(LectureUpdateWizardUnitsComponent), MockComponent(LectureUpdateWizardAttachmentsComponent), - MockComponent(LectureUpdatePeriodComponent), - MockComponent(LectureUpdateTitleComponent), + MockComponent(LectureUpdateWizardPeriodComponent), + MockComponent(LectureUpdateWizardTitleComponent), MockComponent(FaIconComponent), MockDirective(TranslateDirective), ], From c5f7164507098e2a92d26a3f21c9dcd5d19fdae3 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Fri, 8 Nov 2024 13:33:11 +0100 Subject: [PATCH 032/125] Fix inconsistency of naming in client test description --- .../lecture/wizard-mode/lecture-wizard-period.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts index c807cf4c8a1e..f06067251d59 100644 --- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts @@ -6,7 +6,7 @@ import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lect import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -describe('LectureUpdatePeriodComponent', () => { +describe('LectureWizardPeriodComponent', () => { let wizardPeriodComponentFixture: ComponentFixture; let wizardPeriodComponent: LectureUpdateWizardPeriodComponent; From cce13533c840fa70a8f5ca3f2c08657d877878ca Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sun, 10 Nov 2024 03:40:55 +0100 Subject: [PATCH 033/125] Add section titles and display all steps --- .../app/lecture/lecture-update.component.html | 107 ++++++++++-------- 1 file changed, 62 insertions(+), 45 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index cef83e325c23..29cf142ed633 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -26,57 +26,74 @@

- - - -
- -
- - -
-
-
- -
-
- + @if (lecture.course) { +
+ +
+ +
-
- +
+ } +
+
+

+

+ +
+ +
- @if (lecture.course) { -
- -
- +
+

+

+
+
+ +
+
+ +
+
+
- } +
+
+

+

+ +

+ + +
-
- - - +
+ +
+ + + +
@if (processUnitMode) {
From 0c08b380c44ef65901438c710a80af148fc91d54 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sun, 10 Nov 2024 03:48:53 +0100 Subject: [PATCH 034/125] Display all sections --- .../app/lecture/lecture-update.component.ts | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index ee3f7fb19b37..9659951fa22c 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -83,7 +83,7 @@ export class LectureUpdateComponent implements OnInit { ) { effect(() => { // noinspection UnnecessaryLocalVariableJS: not inlined because the variable name improves readability - let updatedFormStatusSections: FormSectionStatus[] = [ + const updatedFormStatusSections: FormSectionStatus[] = [ { title: 'artemisApp.lecture.wizardMode.steps.titleStepTitle', valid: Boolean(this.titleSection().titleChannelNameComponent().isFormValidSignal()), @@ -92,30 +92,20 @@ export class LectureUpdateComponent implements OnInit { title: 'artemisApp.lecture.wizardMode.steps.periodStepTitle', valid: Boolean(this.isPeriodSectionValid()), }, + { + title: 'artemisApp.lecture.wizardMode.steps.attachmentsStepTitle', + valid: true, // TODO retrieve the valid status + }, + { + title: 'artemisApp.lecture.wizardMode.steps.unitsStepTitle', + valid: Boolean(this.unitSection()?.isUnitConfigurationValid()), + }, ]; - // only in edit mode we have the lecture id which is currently required to store units and attachments - if (this.isEditMode()) { - updatedFormStatusSections = [ - ...updatedFormStatusSections, - { - title: 'artemisApp.lecture.wizardMode.steps.attachmentsStepTitle', - valid: true, // TODO retrieve the valid status - }, - { - title: 'artemisApp.lecture.wizardMode.steps.unitsStepTitle', - valid: Boolean(this.unitSection()?.isUnitConfigurationValid()), - }, - ]; - } - this.formStatusSections = updatedFormStatusSections; }); } - /** - * Life cycle hook called by Angular to indicate that Angular is done creating the component - */ ngOnInit() { this.isSaving = false; this.processUnitMode = false; From d52521394bd338054af880b4638049d6e3f4d8a9 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sun, 10 Nov 2024 03:49:30 +0100 Subject: [PATCH 035/125] Consider date validation in text unit form --- .../text-unit-form/text-unit-form.component.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts index b6d7b31447b2..3ed05d37b8c1 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component.ts @@ -1,4 +1,4 @@ -import { Component, OnChanges, OnDestroy, OnInit, computed, inject, input, output } from '@angular/core'; +import { Component, OnChanges, OnDestroy, OnInit, computed, inject, input, output, viewChild } from '@angular/core'; import dayjs from 'dayjs/esm'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Router } from '@angular/router'; @@ -8,6 +8,7 @@ import { TranslateService } from '@ngx-translate/core'; import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { CompetencyLectureUnitLink } from 'app/entities/competency.model'; import { toSignal } from '@angular/core/rxjs-interop'; +import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; export interface TextUnitFormData { name?: string; @@ -32,6 +33,8 @@ export class TextUnitFormComponent implements OnInit, OnChanges, OnDestroy { hasCancelButton = input(false); onCancel = output(); + datePickerComponent = viewChild(FormDateTimePickerComponent); + // not included in reactive form content: string | undefined; contentLoadedFromCache = false; @@ -46,7 +49,7 @@ export class TextUnitFormComponent implements OnInit, OnChanges, OnDestroy { }); private readonly statusChanges = toSignal(this.form.statusChanges ?? 'INVALID'); - isFormValid = computed(() => this.statusChanges() === 'VALID'); + isFormValid = computed(() => this.statusChanges() === 'VALID' && this.datePickerComponent()?.isValid()); private markdownChanges = new Subject(); private markdownChangesSubscription: Subscription; From 84797cc3de973c567882be082665f84817407f4d Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sun, 10 Nov 2024 03:55:50 +0100 Subject: [PATCH 036/125] Consider date for validity changes --- .../attachment-unit-form/attachment-unit-form.component.ts | 7 +++++-- .../online-unit-form/online-unit-form.component.ts | 7 +++++-- .../video-unit-form/video-unit-form.component.ts | 7 +++++-- .../lecture/wizard-mode/lecture-wizard-units.component.ts | 2 -- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component.ts index 6ed702b59547..1bc42524d04b 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, OnChanges, ViewChild, computed, inject, input, output, signal } from '@angular/core'; +import { Component, ElementRef, OnChanges, ViewChild, computed, inject, input, output, signal, viewChild } from '@angular/core'; import dayjs from 'dayjs/esm'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { faQuestionCircle, faTimes } from '@fortawesome/free-solid-svg-icons'; @@ -6,6 +6,7 @@ import { ACCEPTED_FILE_EXTENSIONS_FILE_BROWSER, ALLOWED_FILE_EXTENSIONS_HUMAN_RE import { CompetencyLectureUnitLink } from 'app/entities/competency.model'; import { MAX_FILE_SIZE } from 'app/shared/constants/input.constants'; import { toSignal } from '@angular/core/rxjs-interop'; +import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; export interface AttachmentUnitFormData { formProperties: FormProperties; @@ -47,6 +48,8 @@ export class AttachmentUnitFormComponent implements OnChanges { hasCancelButton = input(false); onCancel = output(); + datePickerComponent = viewChild(FormDateTimePickerComponent); + // have to handle the file input as a special case at is not part of the reactive form @ViewChild('fileInput', { static: false }) fileInput: ElementRef; @@ -68,7 +71,7 @@ export class AttachmentUnitFormComponent implements OnChanges { private readonly statusChanges = toSignal(this.form.statusChanges ?? 'INVALID'); isFormValid = computed(() => { - return (this.statusChanges() === 'VALID' || this.fileName()) && !this.isFileTooBig(); + return (this.statusChanges() === 'VALID' || this.fileName()) && !this.isFileTooBig() && this.datePickerComponent()?.isValid(); }); ngOnChanges(): void { diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts index 67adf4f2c8dc..241feda894e8 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component.ts @@ -1,5 +1,5 @@ import dayjs from 'dayjs/esm'; -import { Component, OnChanges, computed, inject, input, output } from '@angular/core'; +import { Component, OnChanges, computed, inject, input, output, viewChild } from '@angular/core'; import { AbstractControl, FormBuilder, FormGroup, Validators } from '@angular/forms'; import { faArrowLeft, faTimes } from '@fortawesome/free-solid-svg-icons'; import { map } from 'rxjs'; @@ -8,6 +8,7 @@ import { OnlineResourceDTO } from 'app/lecture/lecture-unit/lecture-unit-managem import { OnlineUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/onlineUnit.service'; import { CompetencyLectureUnitLink } from 'app/entities/competency.model'; import { toSignal } from '@angular/core/rxjs-interop'; +import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; export interface OnlineUnitFormData { name?: string; @@ -45,6 +46,8 @@ export class OnlineUnitFormComponent implements OnChanges { hasCancelButton = input(false); onCancel = output(); + datePickerComponent = viewChild(FormDateTimePickerComponent); + urlValidator = urlValidator; private readonly formBuilder = inject(FormBuilder); @@ -59,7 +62,7 @@ export class OnlineUnitFormComponent implements OnChanges { }); private readonly statusChanges = toSignal(this.form.statusChanges ?? 'INVALID'); - isFormValid = computed(() => this.statusChanges() === 'VALID'); + isFormValid = computed(() => this.statusChanges() === 'VALID' && this.datePickerComponent()?.isValid()); get nameControl() { return this.form.get('name'); diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.ts index 543526c9491b..0ffea72952dc 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component.ts @@ -1,10 +1,11 @@ import dayjs from 'dayjs/esm'; -import { Component, computed, effect, inject, input, output, untracked } from '@angular/core'; +import { Component, computed, effect, inject, input, output, untracked, viewChild } from '@angular/core'; import { AbstractControl, FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms'; import urlParser from 'js-video-url-parser'; import { faArrowLeft, faTimes } from '@fortawesome/free-solid-svg-icons'; import { CompetencyLectureUnitLink } from 'app/entities/competency.model'; import { toSignal } from '@angular/core/rxjs-interop'; +import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; export interface VideoUnitFormData { name?: string; @@ -72,6 +73,8 @@ export class VideoUnitFormComponent { hasCancelButton = input(); onCancel = output(); + datePickerComponent = viewChild(FormDateTimePickerComponent); + videoSourceUrlValidator = videoSourceUrlValidator; videoSourceTransformUrlValidator = videoSourceTransformUrlValidator; @@ -84,7 +87,7 @@ export class VideoUnitFormComponent { competencyLinks: [undefined as CompetencyLectureUnitLink[] | undefined], }); private readonly statusChanges = toSignal(this.form.statusChanges ?? 'INVALID'); - isFormValid = computed(() => this.statusChanges() === 'VALID'); + isFormValid = computed(() => this.statusChanges() === 'VALID' && this.datePickerComponent()?.isValid()); constructor() { effect(() => { diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts index e274d8f8592b..56b45cf2b226 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts @@ -21,7 +21,6 @@ import { HttpErrorResponse } from '@angular/common/http'; import { AttachmentUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/attachmentUnit.service'; import dayjs from 'dayjs/esm'; import { ActivatedRoute } from '@angular/router'; -import { CreateExerciseUnitComponent } from 'app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component'; @Component({ selector: 'jhi-lecture-update-wizard-units', @@ -37,7 +36,6 @@ export class LectureUpdateWizardUnitsComponent implements OnInit { videoUnitForm = viewChild(VideoUnitFormComponent); onlineUnitForm = viewChild(OnlineUnitFormComponent); attachmentUnitForm = viewChild(AttachmentUnitFormComponent); - exerciseUnitForm = viewChild(CreateExerciseUnitComponent); // TODO add status fo unit form isUnitConfigurationValid = computed(() => { return ( (this.textUnitForm()?.isFormValid() || !this.isTextUnitFormOpen()) && From 6c06ff4a67fa0edd59350455e1c6d7d7cbfa4e28 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sun, 10 Nov 2024 04:20:02 +0100 Subject: [PATCH 037/125] Change title based on editing / creating lecture --- src/main/webapp/app/lecture/lecture-update.component.html | 2 +- src/main/webapp/app/lecture/lecture-update.component.ts | 2 +- src/main/webapp/i18n/de/lecture.json | 1 + src/main/webapp/i18n/en/lecture.json | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index 29cf142ed633..fa3f748f9662 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -13,7 +13,7 @@
-

+

diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 9659951fa22c..fd0e90c9e8f9 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -127,7 +127,7 @@ export class LectureUpdateComponent implements OnInit { } }); - this.isEditMode.set(this.lecture.id !== undefined); + this.isEditMode.set(!this.router.url.endsWith('/new')); } /** diff --git a/src/main/webapp/i18n/de/lecture.json b/src/main/webapp/i18n/de/lecture.json index 600ea45ef1c9..8155e88d451b 100644 --- a/src/main/webapp/i18n/de/lecture.json +++ b/src/main/webapp/i18n/de/lecture.json @@ -4,6 +4,7 @@ "home": { "title": "Vorlesungen", "createLabel": "Vorlesung erstellen", + "editLabel": "Vorlesung bearbeiten", "filterLabel": "Filter", "filterOptions": { "filterPast": "Vergangene", diff --git a/src/main/webapp/i18n/en/lecture.json b/src/main/webapp/i18n/en/lecture.json index cc0f1289886a..ce933fd09f49 100644 --- a/src/main/webapp/i18n/en/lecture.json +++ b/src/main/webapp/i18n/en/lecture.json @@ -4,6 +4,7 @@ "home": { "title": "Lectures", "createLabel": "Create Lecture", + "editLabel": "Edit Lecture", "filterLabel": "Filter", "filterOptions": { "filterPast": "Past", From 2ad54cfaa983290b4f44e1ef91e5d76a79030f09 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sun, 10 Nov 2024 04:25:23 +0100 Subject: [PATCH 038/125] Fix code smell --- .../app/lecture/lecture-attachments.component.html | 3 +-- .../app/lecture/lecture-attachments.component.ts | 12 +++++++----- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-attachments.component.html b/src/main/webapp/app/lecture/lecture-attachments.component.html index c20d21a173c5..b7affeb3d06f 100644 --- a/src/main/webapp/app/lecture/lecture-attachments.component.html +++ b/src/main/webapp/app/lecture/lecture-attachments.component.html @@ -130,8 +130,7 @@

@if (!attachmentToBeCreated.id) {

- } - @if (attachmentToBeCreated.id) { + } @else {

}
diff --git a/src/main/webapp/app/lecture/lecture-attachments.component.ts b/src/main/webapp/app/lecture/lecture-attachments.component.ts index 6d89f669946a..acb790140ad3 100644 --- a/src/main/webapp/app/lecture/lecture-attachments.component.ts +++ b/src/main/webapp/app/lecture/lecture-attachments.component.ts @@ -69,12 +69,14 @@ export class LectureAttachmentsComponent implements OnInit, OnDestroy { } loadAttachments(): void { - this.attachmentService.findAllByLectureId(this.lecture.id!).subscribe((attachmentsResponse: HttpResponse) => { - this.attachments = attachmentsResponse.body!; - this.attachments.forEach((attachment) => { - this.viewButtonAvailable[attachment.id!] = this.isViewButtonAvailable(attachment.link!); + if (this.lecture?.id !== undefined) { + this.attachmentService.findAllByLectureId(this.lecture.id).subscribe((attachmentsResponse: HttpResponse) => { + this.attachments = attachmentsResponse.body!; + this.attachments.forEach((attachment) => { + this.viewButtonAvailable[attachment.id!] = this.isViewButtonAvailable(attachment.link!); + }); }); - }); + } } ngOnDestroy(): void { From 2859b691cb16170e75f7e6c718b3c38fa5b4e795 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sun, 10 Nov 2024 04:49:35 +0100 Subject: [PATCH 039/125] Fix attachment creation for new lecture --- .../app/lecture/lecture-update.component.ts | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index fd0e90c9e8f9..52faf4297c98 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -60,6 +60,8 @@ export class LectureUpdateComponent implements OnInit { isProcessing: boolean; processUnitMode: boolean; isShowingWizardMode: boolean; + /** Adding attachments and lecture units requires a lecture id, we save the lecture in the background on creation to make sure the id is present in case an attachment / lecture unit is added */ + isAutomaticSaveOnCreation: boolean = true; formStatusSections: FormSectionStatus[]; @@ -128,6 +130,10 @@ export class LectureUpdateComponent implements OnInit { }); this.isEditMode.set(!this.router.url.endsWith('/new')); + + if (!this.isEditMode()) { + this.save(true); + } } /** @@ -143,9 +149,10 @@ export class LectureUpdateComponent implements OnInit { * Save the changes on a lecture * This function is called by pressing save after creating or editing a lecture */ - save() { + save(isAutomaticSaveOnCreation: boolean = false) { this.isSaving = true; this.isProcessing = true; + this.isAutomaticSaveOnCreation = isAutomaticSaveOnCreation; if (this.lecture.id !== undefined) { this.subscribeToSaveResponse(this.lectureService.update(this.lecture)); } else { @@ -199,24 +206,30 @@ export class LectureUpdateComponent implements OnInit { * Action on successful lecture creation or edit */ protected onSaveSuccess(lecture: Lecture) { - if (this.isShowingWizardMode && !this.lecture.id) { + this.isSaving = false; + + const isAutomaticSaveForIdForAttachmentsAndUnits = !this.isEditMode() && this.isAutomaticSaveOnCreation; + if (isAutomaticSaveForIdForAttachmentsAndUnits) { + this.lectureService.findWithDetails(lecture.id!).subscribe({ + next: (response: HttpResponse) => { + this.lecture = response.body!; + }, + }); + } else if (this.isShowingWizardMode && !this.lecture.id) { this.lectureService.findWithDetails(lecture.id!).subscribe({ next: (response: HttpResponse) => { - this.isSaving = false; this.lecture = response.body!; this.alertService.success(`Lecture with title ${lecture.title} was successfully created.`); this.wizardComponent.onLectureCreationSucceeded(); }, }); } else if (this.processUnitMode) { - this.isSaving = false; this.isProcessing = false; this.alertService.success(`Lecture with title ${lecture.title} was successfully ${this.lecture.id !== undefined ? 'updated' : 'created'}.`); this.router.navigate(['course-management', lecture.course!.id, 'lectures', lecture.id, 'unit-management', 'attachment-units', 'process'], { state: { file: this.file, fileName: this.fileName }, }); } else { - this.isSaving = false; this.router.navigate(['course-management', lecture.course!.id, 'lectures', lecture.id]); } } @@ -227,6 +240,11 @@ export class LectureUpdateComponent implements OnInit { */ protected onSaveError(errorRes: HttpErrorResponse) { this.isSaving = false; + + const isAutomaticSaveForIdForAttachmentsAndUnits = this.isAutomaticSaveOnCreation; + if (isAutomaticSaveForIdForAttachmentsAndUnits) { + return; + } if (errorRes.error && errorRes.error.title) { this.alertService.addErrorAlert(errorRes.error.title, errorRes.error.message, errorRes.error.params); } else { From 0f3f54f456853c0046b564160d23198269fd9c67 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sun, 10 Nov 2024 05:00:25 +0100 Subject: [PATCH 040/125] Use signal for lecture --- .../app/lecture/lecture-update.component.ts | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 52faf4297c98..b10ec7f9a7fa 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -12,7 +12,7 @@ import { ArtemisNavigationUtilService } from 'app/utils/navigation.utils'; import { DocumentationType } from 'app/shared/components/documentation-button/documentation-button.component'; import { faBan, faHandshakeAngle, faPuzzlePiece, faQuestionCircle, faSave } from '@fortawesome/free-solid-svg-icons'; import { LectureUpdateWizardComponent } from 'app/lecture/wizard-mode/lecture-update-wizard.component'; -import { UPLOAD_FILE_EXTENSIONS } from 'app/shared/constants/file-extensions.constants'; +import { ACCEPTED_FILE_EXTENSIONS_FILE_BROWSER, ALLOWED_FILE_EXTENSIONS_HUMAN_READABLE } from 'app/shared/constants/file-extensions.constants'; import { FormulaAction } from 'app/shared/monaco-editor/model/actions/formula.action'; import { ProgrammingExerciseDifficultyComponent } from 'app/exercises/programming/manage/update/update-components/difficulty/programming-exercise-difficulty.component'; import { FormSectionStatus } from 'app/forms/form-status-bar/form-status-bar.component'; @@ -33,10 +33,8 @@ export class LectureUpdateComponent implements OnInit { protected readonly faPuzzleProcess = faPuzzlePiece; protected readonly faBan = faBan; protected readonly faHandShakeAngle = faHandshakeAngle; - // A human-readable list of allowed file extensions - protected readonly allowedFileExtensions = UPLOAD_FILE_EXTENSIONS.join(', '); - // The list of file extensions for the "accept" attribute of the file input field - protected readonly acceptedFileExtensionsFileBrowser = UPLOAD_FILE_EXTENSIONS.map((ext) => '.' + ext).join(','); + protected readonly allowedFileExtensions = ALLOWED_FILE_EXTENSIONS_HUMAN_READABLE; + protected readonly acceptedFileExtensionsFileBrowser = ACCEPTED_FILE_EXTENSIONS_FILE_BROWSER; isEditMode = signal(false); @@ -55,12 +53,15 @@ export class LectureUpdateComponent implements OnInit { return true; }); - lecture: Lecture; + lecture = signal(new Lecture()); isSaving: boolean; isProcessing: boolean; processUnitMode: boolean; isShowingWizardMode: boolean; - /** Adding attachments and lecture units requires a lecture id, we save the lecture in the background on creation to make sure the id is present in case an attachment / lecture unit is added */ + /** + * Adding attachments and lecture units requires a lecture id, we save the lecture in the background on creation + * to make sure the id is present in case an attachment / lecture unit is added + */ isAutomaticSaveOnCreation: boolean = true; formStatusSections: FormSectionStatus[]; @@ -116,10 +117,10 @@ export class LectureUpdateComponent implements OnInit { this.activatedRoute.parent!.data.subscribe((data) => { // Create a new lecture to use unless we fetch an existing lecture const lecture = data['lecture']; - this.lecture = lecture ?? new Lecture(); + this.lecture.set(lecture ?? new Lecture()); const course = data['course']; if (course) { - this.lecture.course = course; + this.lecture().course = course; } }); @@ -142,7 +143,7 @@ export class LectureUpdateComponent implements OnInit { * Returns to the overview page if there is no previous state, and we created a new lecture */ previousState() { - this.navigationUtilService.navigateBackWithOptional(['course-management', this.lecture.course!.id!.toString(), 'lectures'], this.lecture.id?.toString()); + this.navigationUtilService.navigateBackWithOptional(['course-management', this.lecture().course!.id!.toString(), 'lectures'], this.lecture().id?.toString()); } /** @@ -153,11 +154,11 @@ export class LectureUpdateComponent implements OnInit { this.isSaving = true; this.isProcessing = true; this.isAutomaticSaveOnCreation = isAutomaticSaveOnCreation; - if (this.lecture.id !== undefined) { - this.subscribeToSaveResponse(this.lectureService.update(this.lecture)); + if (this.lecture().id !== undefined) { + this.subscribeToSaveResponse(this.lectureService.update(this.lecture())); } else { // Newly created lectures must have a channel name, which cannot be undefined - this.subscribeToSaveResponse(this.lectureService.create(this.lecture)); + this.subscribeToSaveResponse(this.lectureService.create(this.lecture())); } } @@ -192,7 +193,7 @@ export class LectureUpdateComponent implements OnInit { } /** - * @callback Callback function after saving a lecture, handles appropriate action in case of error + * @callback callback after saving a lecture, handles appropriate action in case of error * @param result The Http response from the server */ protected subscribeToSaveResponse(result: Observable>) { @@ -212,20 +213,20 @@ export class LectureUpdateComponent implements OnInit { if (isAutomaticSaveForIdForAttachmentsAndUnits) { this.lectureService.findWithDetails(lecture.id!).subscribe({ next: (response: HttpResponse) => { - this.lecture = response.body!; + this.lecture.set(response.body!); }, }); - } else if (this.isShowingWizardMode && !this.lecture.id) { + } else if (this.isShowingWizardMode && !this.lecture().id) { this.lectureService.findWithDetails(lecture.id!).subscribe({ next: (response: HttpResponse) => { - this.lecture = response.body!; + this.lecture.set(response.body!); this.alertService.success(`Lecture with title ${lecture.title} was successfully created.`); this.wizardComponent.onLectureCreationSucceeded(); }, }); } else if (this.processUnitMode) { this.isProcessing = false; - this.alertService.success(`Lecture with title ${lecture.title} was successfully ${this.lecture.id !== undefined ? 'updated' : 'created'}.`); + this.alertService.success(`Lecture with title ${lecture.title} was successfully ${this.lecture().id !== undefined ? 'updated' : 'created'}.`); this.router.navigate(['course-management', lecture.course!.id, 'lectures', lecture.id, 'unit-management', 'attachment-units', 'process'], { state: { file: this.file, fileName: this.fileName }, }); @@ -253,18 +254,18 @@ export class LectureUpdateComponent implements OnInit { } onDatesValuesChanged() { - const startDate = this.lecture.startDate; - const endDate = this.lecture.endDate; - const visibleDate = this.lecture.visibleDate; + const startDate = this.lecture().startDate; + const endDate = this.lecture().endDate; + const visibleDate = this.lecture().visibleDate; // Prevent endDate from being before startDate, if both dates are set if (endDate && startDate?.isAfter(endDate)) { - this.lecture.endDate = startDate.clone(); + this.lecture().endDate = startDate.clone(); } // Prevent visibleDate from being after startDate, if both dates are set if (visibleDate && startDate?.isBefore(visibleDate)) { - this.lecture.visibleDate = startDate.clone(); + this.lecture().visibleDate = startDate.clone(); } } } From d5b5dd044e925a8d29612a2053c0ef6e264043a0 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sun, 10 Nov 2024 05:36:00 +0100 Subject: [PATCH 041/125] Fix attachments within lecture create view --- .../lecture-attachments.component.html | 25 +++++----- .../lecture/lecture-attachments.component.ts | 47 ++++++++++--------- .../app/lecture/lecture-update.component.html | 36 +++++++------- 3 files changed, 56 insertions(+), 52 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-attachments.component.html b/src/main/webapp/app/lecture/lecture-attachments.component.html index b7affeb3d06f..f2fc026abf8a 100644 --- a/src/main/webapp/app/lecture/lecture-attachments.component.html +++ b/src/main/webapp/app/lecture/lecture-attachments.component.html @@ -1,29 +1,29 @@
- @if (lecture) { + @if (lecture()) {
- @if (showHeader) { + @if (showHeader()) {
-

: {{ lecture.title }} - {{ lecture.course?.shortName }}

+

: {{ lecture().title }} - {{ lecture().course?.shortName }}


- {{ lecture.startDate | artemisDate }} + {{ lecture().startDate | artemisDate }}
- {{ lecture.endDate | artemisDate }} + {{ lecture().endDate | artemisDate }}
-
+

@@ -83,7 +83,7 @@

- @if (lecture.isAtLeastInstructor) { + @if (lecture().isAtLeastInstructor) {

- -
@@ -211,7 +212,7 @@

@if (!attachmentToBeCreated) {
-
diff --git a/src/main/webapp/app/lecture/lecture-attachments.component.ts b/src/main/webapp/app/lecture/lecture-attachments.component.ts index acb790140ad3..33ef513a4502 100644 --- a/src/main/webapp/app/lecture/lecture-attachments.component.ts +++ b/src/main/webapp/app/lecture/lecture-attachments.component.ts @@ -1,4 +1,4 @@ -import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { Component, ElementRef, OnDestroy, ViewChild, effect, input, signal } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; import { Lecture } from 'app/entities/lecture.model'; @@ -16,7 +16,7 @@ import { LectureService } from 'app/lecture/lecture.service'; templateUrl: './lecture-attachments.component.html', styleUrls: ['./lecture-attachments.component.scss'], }) -export class LectureAttachmentsComponent implements OnInit, OnDestroy { +export class LectureAttachmentsComponent implements OnDestroy { protected readonly faSpinner = faSpinner; protected readonly faTimes = faTimes; protected readonly faTrash = faTrash; @@ -29,10 +29,11 @@ export class LectureAttachmentsComponent implements OnInit, OnDestroy { protected readonly acceptedFileExtensionsFileBrowser = ACCEPTED_FILE_EXTENSIONS_FILE_BROWSER; @ViewChild('fileInput', { static: false }) fileInput: ElementRef; - @Input() lectureId: number | undefined; - @Input() showHeader = true; + lectureId = input(); + showHeader = input(true); + redirectToLecturePage = input(true); - lecture: Lecture; + lecture = signal(new Lecture()); attachments: Attachment[] = []; attachmentToBeCreated?: Attachment; attachmentBackup?: Attachment; @@ -51,26 +52,26 @@ export class LectureAttachmentsComponent implements OnInit, OnDestroy { private attachmentService: AttachmentService, private lectureService: LectureService, private fileService: FileService, - ) {} - - ngOnInit() { - this.notificationText = undefined; - this.activatedRoute.parent!.data.subscribe(({ lecture }) => { - if (this.lectureId) { - this.lectureService.findWithDetails(this.lectureId).subscribe((lectureResponse: HttpResponse) => { - this.lecture = lectureResponse.body!; + ) { + effect(() => { + this.notificationText = undefined; + this.activatedRoute.parent!.data.subscribe(({ lecture }) => { + if (this.lectureId()) { + this.lectureService.findWithDetails(this.lectureId()!).subscribe((lectureResponse: HttpResponse) => { + this.lecture.set(lectureResponse.body!); + this.loadAttachments(); + }); + } else { + this.lecture.set(lecture); this.loadAttachments(); - }); - } else { - this.lecture = lecture; - this.loadAttachments(); - } + } + }); }); } loadAttachments(): void { - if (this.lecture?.id !== undefined) { - this.attachmentService.findAllByLectureId(this.lecture.id).subscribe((attachmentsResponse: HttpResponse) => { + if (this.lecture().id !== undefined) { + this.attachmentService.findAllByLectureId(this.lecture().id!).subscribe((attachmentsResponse: HttpResponse) => { this.attachments = attachmentsResponse.body!; this.attachments.forEach((attachment) => { this.viewButtonAvailable[attachment.id!] = this.isViewButtonAvailable(attachment.link!); @@ -93,7 +94,7 @@ export class LectureAttachmentsComponent implements OnInit, OnDestroy { addAttachment(): void { const newAttachment = new Attachment(); - newAttachment.lecture = this.lecture; + newAttachment.lecture = this.lecture(); newAttachment.attachmentType = AttachmentType.FILE; newAttachment.version = 0; newAttachment.uploadDate = dayjs(); @@ -135,8 +136,8 @@ export class LectureAttachmentsComponent implements OnInit, OnDestroy { this.attachmentService.create(this.attachmentToBeCreated!, this.attachmentFile!).subscribe({ next: (attachmentRes: HttpResponse) => { this.attachments.push(attachmentRes.body!); - this.lectureService.findWithDetails(this.lecture.id!).subscribe((lectureResponse: HttpResponse) => { - this.lecture = lectureResponse.body!; + this.lectureService.findWithDetails(this.lecture().id!).subscribe((lectureResponse: HttpResponse) => { + this.lecture.set(lectureResponse.body!); }); this.attachmentFile = undefined; this.attachmentToBeCreated = undefined; diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index fa3f748f9662..0356223a2341 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -5,7 +5,7 @@ [toggleModeFunction]="toggleModeFunction" [saveLectureFunction]="saveLectureFunction" [validateDatesFunction]="onDatesValuesChanged" - [lecture]="lecture" + [lecture]="lecture()" [isSaving]="isSaving" /> } @else { @@ -26,24 +26,27 @@

- @if (lecture.course) { + @if (lecture().course) {
-
- - -
+ @if (lecture().course?.title) { +
+ +
+ }
}

- -
- - -
+ + @if (lecture()) { +
+ + +
+ }

@@ -52,7 +55,7 @@

- - +

- +
From 261b0580c7e2c12384ed08d1f5b137a32a3ec5fd Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sun, 10 Nov 2024 06:05:53 +0100 Subject: [PATCH 042/125] Fix console errors --- .../lecture/lecture-attachments.component.ts | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-attachments.component.ts b/src/main/webapp/app/lecture/lecture-attachments.component.ts index 33ef513a4502..84482dd47be7 100644 --- a/src/main/webapp/app/lecture/lecture-attachments.component.ts +++ b/src/main/webapp/app/lecture/lecture-attachments.component.ts @@ -53,24 +53,27 @@ export class LectureAttachmentsComponent implements OnDestroy { private lectureService: LectureService, private fileService: FileService, ) { - effect(() => { - this.notificationText = undefined; - this.activatedRoute.parent!.data.subscribe(({ lecture }) => { - if (this.lectureId()) { - this.lectureService.findWithDetails(this.lectureId()!).subscribe((lectureResponse: HttpResponse) => { - this.lecture.set(lectureResponse.body!); + effect( + () => { + this.notificationText = undefined; + this.activatedRoute.parent!.data.subscribe(({ lecture }) => { + if (this.lectureId()) { + this.lectureService.findWithDetails(this.lectureId()!).subscribe((lectureResponse: HttpResponse) => { + this.lecture.set(lectureResponse.body!); + this.loadAttachments(); + }); + } else { + this.lecture.set(lecture); this.loadAttachments(); - }); - } else { - this.lecture.set(lecture); - this.loadAttachments(); - } - }); - }); + } + }); + }, + { allowSignalWrites: true }, + ); } loadAttachments(): void { - if (this.lecture().id !== undefined) { + if (this.lecture()?.id !== undefined) { this.attachmentService.findAllByLectureId(this.lecture().id!).subscribe((attachmentsResponse: HttpResponse) => { this.attachments = attachmentsResponse.body!; this.attachments.forEach((attachment) => { From 1a2fe16d182ad1a36e94a384b90d8abf71db848d Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sun, 10 Nov 2024 06:06:34 +0100 Subject: [PATCH 043/125] Delete lecture on cancel in create mode --- .../app/lecture/lecture-update.component.html | 2 +- .../app/lecture/lecture-update.component.ts | 26 +++++++++++++++++-- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index 0356223a2341..86e81605e478 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -135,7 +135,7 @@

-
- @if (showBackToWizardModeButton) { - - } diff --git a/src/main/webapp/app/course/manage/course-management-exercises.component.ts b/src/main/webapp/app/course/manage/course-management-exercises.component.ts index b22fa31fa819..5a1c027ee515 100644 --- a/src/main/webapp/app/course/manage/course-management-exercises.component.ts +++ b/src/main/webapp/app/course/manage/course-management-exercises.component.ts @@ -28,7 +28,6 @@ export class CourseManagementExercisesComponent implements OnInit { filteredModelingExercisesCount = 0; filteredFileUploadExercisesCount = 0; exerciseFilter: ExerciseFilter; - showBackToWizardModeButton = false; lectureIdForGoingBack: number; lectureWizardStepForGoingBack: number; @@ -55,11 +54,7 @@ export class CourseManagementExercisesComponent implements OnInit { } }); - this.route.queryParams.subscribe((params) => { - this.showBackToWizardModeButton = params.shouldHaveBackButtonToWizard; - this.lectureIdForGoingBack = params.lectureId; - this.lectureWizardStepForGoingBack = params.step; - }); + // TODO investigate shouldHaveBackButtonToWizard this.exerciseFilter = new ExerciseFilter(''); } diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index 0356223a2341..485ffc79eac4 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -1,167 +1,157 @@
- @if (isShowingWizardMode) { - - } @else { - -
-
-
-

- -
-
-
-
- -
+ +
+
+
+

+
- - @if (lecture().course) { -
- - @if (lecture().course?.title) { -
- -
- } -
- } -
-
-

-

- - @if (lecture()) { -
- - -
- } +
+
+
-
-

-

+
+
+ + @if (lecture().course) { +
+ + @if (lecture().course?.title) {
-
- -
-
- -
-
- -
+
-
-
-

-

- -

- -
+ }
+ } +
- -
- - - +

+

+ + @if (lecture()) { +
+ + +
+ } +
+
+

+

+
+
+ +
+
+ +
+
+ +
- @if (processUnitMode) { -
-
    -
  • -
  • -
  • -
+
+

+

+ +

+ +
+
+
+ +
+ + + +
+
+ @if (processUnitMode) { +
+
    +
  • +
  • +
  • +
+
+ } + @if (processUnitMode) { +
+ + + +
+
+ +
- } + @if (!fileName && fileInputTouched) { +
+ } +
+ } +
+
+ + +
@if (processUnitMode) { -
- - - -
-
- -
+
+
+
- @if (!fileName && fileInputTouched) { -
- }
} -
-
- - -
- @if (processUnitMode) { -
-
- -
-
- } -
- - } +
+
diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 992d4f4bcaa0..6034b2e252d8 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -11,7 +11,6 @@ import { onError } from 'app/shared/util/global.utils'; import { ArtemisNavigationUtilService } from 'app/utils/navigation.utils'; import { DocumentationType } from 'app/shared/components/documentation-button/documentation-button.component'; import { faBan, faHandshakeAngle, faPuzzlePiece, faQuestionCircle, faSave } from '@fortawesome/free-solid-svg-icons'; -import { LectureUpdateWizardComponent } from 'app/lecture/wizard-mode/lecture-update-wizard.component'; import { ACCEPTED_FILE_EXTENSIONS_FILE_BROWSER, ALLOWED_FILE_EXTENSIONS_HUMAN_READABLE } from 'app/shared/constants/file-extensions.constants'; import { FormulaAction } from 'app/shared/monaco-editor/model/actions/formula.action'; import { ProgrammingExerciseDifficultyComponent } from 'app/exercises/programming/manage/update/update-components/difficulty/programming-exercise-difficulty.component'; @@ -36,7 +35,6 @@ export class LectureUpdateComponent implements OnInit { protected readonly allowedFileExtensions = ALLOWED_FILE_EXTENSIONS_HUMAN_READABLE; protected readonly acceptedFileExtensionsFileBrowser = ACCEPTED_FILE_EXTENSIONS_FILE_BROWSER; - @ViewChild(LectureUpdateWizardComponent, { static: false }) wizardComponent: LectureUpdateWizardComponent; @ViewChild(ProgrammingExerciseDifficultyComponent) lecturePeriodComponent?: LectureUpdateWizardPeriodComponent; titleSection = viewChild.required(LectureTitleChannelNameComponent); periodSectionDatepickers = viewChildren(FormDateTimePickerComponent); @@ -57,14 +55,6 @@ export class LectureUpdateComponent implements OnInit { isSaving: boolean; isProcessing: boolean; processUnitMode: boolean; - isShowingWizardMode: boolean; - /** - * Adding attachments and lecture units and attachments requires a lecture id. - * We save the lecture in the background on creation to make sure the id is present in case an attachment / lecture unit is added - * - * Note: this means we have to delete the lecture in case the creation is cancelled - */ - isAutomaticSaveOnCreation: boolean = true; formStatusSections: FormSectionStatus[]; @@ -75,9 +65,6 @@ export class LectureUpdateComponent implements OnInit { fileName: string; fileInputTouched = false; - toggleModeFunction = () => this.toggleWizardMode(); - saveLectureFunction = () => this.save(); - constructor( protected alertService: AlertService, protected lectureService: LectureService, @@ -115,7 +102,6 @@ export class LectureUpdateComponent implements OnInit { this.isSaving = false; this.processUnitMode = false; this.isProcessing = false; - this.isShowingWizardMode = false; this.activatedRoute.parent!.data.subscribe((data) => { // Create a new lecture to use unless we fetch an existing lecture const lecture = data['lecture']; @@ -126,17 +112,8 @@ export class LectureUpdateComponent implements OnInit { } }); - this.activatedRoute.queryParams.subscribe((params) => { - if (params.shouldBeInWizardMode) { - this.isShowingWizardMode = params.shouldBeInWizardMode; - } - }); - + // TODO investigate where wizard mode is opened by url this.isEditMode.set(!this.router.url.endsWith('/new')); - - if (!this.isEditMode()) { - this.save(true); - } } /** @@ -152,11 +129,10 @@ export class LectureUpdateComponent implements OnInit { * Save the changes on a lecture * This function is called by pressing save after creating or editing a lecture */ - save(isAutomaticSaveOnCreation: boolean = false) { + save() { this.pressedSave = true; this.isSaving = true; this.isProcessing = true; - this.isAutomaticSaveOnCreation = isAutomaticSaveOnCreation; if (this.lecture().id !== undefined) { this.subscribeToSaveResponse(this.lectureService.update(this.lecture())); } else { @@ -165,14 +141,6 @@ export class LectureUpdateComponent implements OnInit { } } - /** - * Activate or deactivate the wizard mode for easier lecture creation. - * This function is called by pressing "Switch to guided mode" when creating a new lecture - */ - toggleWizardMode() { - this.isShowingWizardMode = !this.isShowingWizardMode; - } - proceedToUnitSplit() { this.save(); } @@ -212,22 +180,7 @@ export class LectureUpdateComponent implements OnInit { protected onSaveSuccess(lecture: Lecture) { this.isSaving = false; - const isAutomaticSaveForIdForAttachmentsAndUnits = !this.isEditMode() && this.isAutomaticSaveOnCreation; - if (isAutomaticSaveForIdForAttachmentsAndUnits) { - this.lectureService.findWithDetails(lecture.id!).subscribe({ - next: (response: HttpResponse) => { - this.lecture.set(response.body!); - }, - }); - } else if (this.isShowingWizardMode && !this.lecture().id) { - this.lectureService.findWithDetails(lecture.id!).subscribe({ - next: (response: HttpResponse) => { - this.lecture.set(response.body!); - this.alertService.success(`Lecture with title ${lecture.title} was successfully created.`); - this.wizardComponent.onLectureCreationSucceeded(); - }, - }); - } else if (this.processUnitMode) { + if (this.processUnitMode) { this.isProcessing = false; this.alertService.success(`Lecture with title ${lecture.title} was successfully ${this.lecture().id !== undefined ? 'updated' : 'created'}.`); this.router.navigate(['course-management', lecture.course!.id, 'lectures', lecture.id, 'unit-management', 'attachment-units', 'process'], { @@ -245,10 +198,6 @@ export class LectureUpdateComponent implements OnInit { protected onSaveError(errorRes: HttpErrorResponse) { this.isSaving = false; - const isAutomaticSaveForIdForAttachmentsAndUnits = this.isAutomaticSaveOnCreation; - if (isAutomaticSaveForIdForAttachmentsAndUnits) { - return; - } if (errorRes.error && errorRes.error.title) { this.alertService.addErrorAlert(errorRes.error.title, errorRes.error.message, errorRes.error.params); } else { diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.html b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.html deleted file mode 100644 index 43852c293ac7..000000000000 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.html +++ /dev/null @@ -1,60 +0,0 @@ -
- @if (currentStep >= LECTURE_UPDATE_WIZARD_TITLE_STEP) { - - } - @if (currentStep >= LECTURE_UPDATE_WIZARD_PERIOD_STEP) { - - } - @if (currentStep >= LECTURE_UPDATE_WIZARD_ATTACHMENT_STEP) { - - } - @if (currentStep >= LECTURE_UPDATE_WIZARD_UNIT_STEP) { - - } - -
-
- -
-
-
- -
- -
- -
- -
-
-
- -
-
-
diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.scss b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.scss deleted file mode 100644 index 4c77040d9087..000000000000 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.scss +++ /dev/null @@ -1,70 +0,0 @@ -.form-step + .form-step::before { - content: ''; - display: block; - height: 1px; - background: gray; - margin-bottom: 1rem; -} - -.code-hint-generation-wrapper { - .planned, - .finished, - .check { - color: var(--success); - } - - .unset { - color: var(--secondary); - } - - .current { - color: var(--warning); - } - - li { - display: list-item !important; - } - - .connector { - width: 100%; - min-width: 50px; - border-top: 2px solid; - margin-top: 1em; - } -} - -.status-step { - width: 25px; - overflow-x: visible; - justify-content: flex-start; - display: flex; - flex-direction: column; - align-items: center; - - .status-step-content { - min-width: fit-content; - white-space: nowrap; - - .selected-label { - font-weight: bold; - } - } - - .header-icon { - z-index: 2; - font-size: 2em; - - & > .step-icon { - cursor: pointer; - background-color: var(--programming-exercise-instruction-step-wizard-card-header-background); - width: 30px; - height: 30px; - text-align: center; - padding: 6px 0; - font-size: 12px; - line-height: 1.428571429; - border-radius: 15px; - border-color: var(--programming-exercise-instruction-step-wizard-btn-border-color); - } - } -} diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts deleted file mode 100644 index 1df89ee2e72a..000000000000 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { Component, Input, OnInit, ViewChild } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { CourseManagementService } from 'app/course/manage/course-management.service'; -import { Lecture } from 'app/entities/lecture.model'; -import { faArrowRight, faCheck, faHandshakeAngle } from '@fortawesome/free-solid-svg-icons'; -import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; -import { take } from 'rxjs/operators'; - -@Component({ - selector: 'jhi-lecture-update-wizard', - templateUrl: './lecture-update-wizard.component.html', - styleUrls: ['./lecture-update-wizard.component.scss'], -}) -export class LectureUpdateWizardComponent implements OnInit { - readonly faCheck = faCheck; - readonly faHandShakeAngle = faHandshakeAngle; - readonly faArrowRight = faArrowRight; - - @Input() toggleModeFunction: () => void; - @Input() saveLectureFunction: () => void; - @Input() validateDatesFunction: () => void; - @Input() lecture: Lecture; - @Input() isSaving: boolean; - - @ViewChild(LectureUpdateWizardUnitsComponent, { static: false }) unitsComponent: LectureUpdateWizardUnitsComponent; - - readonly LECTURE_UPDATE_WIZARD_TITLE_STEP = 1; - readonly LECTURE_UPDATE_WIZARD_PERIOD_STEP = 2; - readonly LECTURE_UPDATE_WIZARD_ATTACHMENT_STEP = 3; - readonly LECTURE_UPDATE_WIZARD_UNIT_STEP = 4; - - currentStep: number; - - constructor( - protected courseService: CourseManagementService, - protected activatedRoute: ActivatedRoute, - private router: Router, - ) {} - - ngOnInit() { - this.isSaving = false; - - this.activatedRoute.queryParams.pipe(take(1)).subscribe((params) => { - if (params.step && !isNaN(+params.step)) { - this.currentStep = +params.step; - } else { - if (this.lecture.id) { - this.currentStep = this.LECTURE_UPDATE_WIZARD_UNIT_STEP; - } else if (this.lecture.startDate === undefined && this.lecture.endDate === undefined) { - this.currentStep = this.LECTURE_UPDATE_WIZARD_TITLE_STEP; - } else if (!this.lecture.id) { - this.currentStep = this.LECTURE_UPDATE_WIZARD_PERIOD_STEP; - } - } - - this.router.navigate([], { - relativeTo: this.activatedRoute, - queryParamsHandling: '', - replaceUrl: true, - }); - }); - } - - progressToNextStep() { - if (this.currentStep === this.LECTURE_UPDATE_WIZARD_PERIOD_STEP || this.currentStep === this.LECTURE_UPDATE_WIZARD_UNIT_STEP) { - this.saveLectureFunction(); - return; - } - - this.currentStep++; - } - - /** - * Called when the lecture has been successfully created in the parent component to advance in the wizard - */ - onLectureCreationSucceeded() { - this.currentStep++; - } - - getNextIcon() { - return this.currentStep < this.LECTURE_UPDATE_WIZARD_UNIT_STEP ? faArrowRight : faCheck; - } - - getNextText() { - return this.currentStep < this.LECTURE_UPDATE_WIZARD_UNIT_STEP ? 'artemisApp.lecture.home.nextStepLabel' : 'entity.action.finish'; - } - - toggleWizardMode() { - if (this.currentStep <= this.LECTURE_UPDATE_WIZARD_PERIOD_STEP) { - this.toggleModeFunction(); - } else { - this.router.navigate(['course-management', this.lecture.course!.id, 'lectures', this.lecture.id]); - } - } -} diff --git a/src/main/webapp/i18n/de/lecture.json b/src/main/webapp/i18n/de/lecture.json index de8a2b4f92bc..09a8167f2ef0 100644 --- a/src/main/webapp/i18n/de/lecture.json +++ b/src/main/webapp/i18n/de/lecture.json @@ -13,9 +13,6 @@ "filterUnspecifiedDates": "Ohne Zeitangaben" }, "createOrEditLabel": "Vorlesung erstellen oder bearbeiten", - "switchToGuidedModeLabel": "Zum geführten Modus wechseln", - "switchToTraditionalModeLabel": "Zum normalen Modus wechseln", - "nextStepLabel": "Weiter", "ingestLecturesInPyrisLabel": "Vorlesungen an Iris senden" }, "created": "Vorlesung erstellt mit ID {{ param }}", diff --git a/src/main/webapp/i18n/en/lecture.json b/src/main/webapp/i18n/en/lecture.json index 3e60bbf06e22..72dd2551900f 100644 --- a/src/main/webapp/i18n/en/lecture.json +++ b/src/main/webapp/i18n/en/lecture.json @@ -13,9 +13,6 @@ "filterUnspecifiedDates": "Unspecified Date(s)" }, "createOrEditLabel": "Create or Edit Lecture", - "switchToGuidedModeLabel": "Switch to guided mode", - "switchToTraditionalModeLabel": "Switch to normal mode", - "nextStepLabel": "Next", "ingestLecturesInPyrisLabel": "Send Lectures to Iris" }, "created": "Created new lecture with identifier {{ param }}", diff --git a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts index 03be222b4cb4..6a0389c9d60e 100644 --- a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts @@ -197,15 +197,6 @@ describe('LectureUpdateComponent', () => { expect(updateSpy).toHaveBeenCalledWith({ id: 6, title: 'test1Updated', channelName: 'test1Updated' }); })); - it('should switch to wizard mode', fakeAsync(() => { - lectureUpdateComponent.isShowingWizardMode = false; - const wizardModeButton = jest.spyOn(lectureUpdateComponent, 'toggleWizardMode'); - lectureUpdateComponent.toggleWizardMode(); - tick(); - expect(wizardModeButton).toHaveBeenCalledOnce(); - expect(lectureUpdateComponent.isShowingWizardMode).toBeTrue(); - })); - it('should be in wizard mode', fakeAsync(() => { activatedRoute = TestBed.inject(ActivatedRoute); activatedRoute.queryParams = of({ From 6898439b35ad222d078c22dfb6250ff6abf24d7f Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Mon, 25 Nov 2024 19:58:26 +0100 Subject: [PATCH 053/125] Fix build errors and remove unused component --- .../app/lecture/lecture-update.component.html | 7 - src/main/webapp/app/lecture/lecture.module.ts | 5 - .../lecture-update-wizard-step.component.html | 16 -- .../lecture-update-wizard-step.component.ts | 24 -- .../lecture-wizard.component.spec.ts | 216 ------------------ 5 files changed, 268 deletions(-) delete mode 100644 src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard-step.component.html delete mode 100644 src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard-step.component.ts delete mode 100644 src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index 485ffc79eac4..8e9812faa85a 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -8,13 +8,6 @@

-
-
- -
-
@if (lecture().course) { diff --git a/src/main/webapp/app/lecture/lecture.module.ts b/src/main/webapp/app/lecture/lecture.module.ts index 298af287f797..eafc90e0b6bb 100644 --- a/src/main/webapp/app/lecture/lecture.module.ts +++ b/src/main/webapp/app/lecture/lecture.module.ts @@ -1,12 +1,10 @@ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; - import { ArtemisSharedModule } from 'app/shared/shared.module'; import { FormDateTimePickerModule } from 'app/shared/date-time-picker/date-time-picker.module'; import { ArtemisSharedComponentModule } from 'app/shared/components/shared-component.module'; import { ArtemisMarkdownEditorModule } from 'app/shared/markdown-editor/markdown-editor.module'; import { LectureUpdateComponent } from 'app/lecture/lecture-update.component'; -import { LectureUpdateWizardComponent } from 'app/lecture/wizard-mode/lecture-update-wizard.component'; import { LectureComponent } from 'app/lecture/lecture.component'; import { LectureDetailComponent } from 'app/lecture/lecture-detail.component'; import { LectureAttachmentsComponent } from 'app/lecture/lecture-attachments.component'; @@ -19,7 +17,6 @@ import { LectureUpdateWizardTitleComponent } from 'app/lecture/wizard-mode/lectu import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; import { LectureUpdateWizardAttachmentsComponent } from 'app/lecture/wizard-mode/lecture-wizard-attachments.component'; import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; -import { LectureUpdateWizardStepComponent } from 'app/lecture/wizard-mode/lecture-update-wizard-step.component'; import { TitleChannelNameModule } from 'app/shared/form/title-channel-name/title-channel-name.module'; import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component'; import { DetailModule } from 'app/detail-overview-list/detail.module'; @@ -48,13 +45,11 @@ const ENTITY_STATES = [...lectureRoute]; LectureDetailComponent, LectureImportComponent, LectureUpdateComponent, - LectureUpdateWizardComponent, LectureAttachmentsComponent, LectureUpdateWizardTitleComponent, LectureUpdateWizardPeriodComponent, LectureUpdateWizardAttachmentsComponent, LectureUpdateWizardUnitsComponent, - LectureUpdateWizardStepComponent, LectureTitleChannelNameComponent, ], }) diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard-step.component.html b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard-step.component.html deleted file mode 100644 index fdaee7117471..000000000000 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard-step.component.html +++ /dev/null @@ -1,16 +0,0 @@ -
-
- @if (isPerformed) { - - } - @if (!isPerformed) { - - } -
-
- Current step - @if (descriptionTranslationKey) { - - } -
-
diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard-step.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard-step.component.ts deleted file mode 100644 index 9c8d6973a233..000000000000 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard-step.component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { faCheck, faDotCircle } from '@fortawesome/free-solid-svg-icons'; - -@Component({ - selector: 'jhi-lecture-update-wizard-step', - templateUrl: './lecture-update-wizard-step.component.html', - styleUrls: ['./lecture-update-wizard.component.scss'], -}) -export class LectureUpdateWizardStepComponent { - @Input() - currentlySelected: boolean; - - @Input() - isPerformed: boolean; - - @Input() - labelTranslationKey: string; - - @Input() - descriptionTranslationKey: string; - - protected readonly faCheck = faCheck; - protected readonly faDotCircle = faDotCircle; -} diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts deleted file mode 100644 index 1aeba59d7a0d..000000000000 --- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing'; -import { MockComponent, MockDirective, MockPipe, MockProvider } from 'ng-mocks'; -import { ActivatedRoute, Router } from '@angular/router'; -import { MockRouter } from '../../../helpers/mocks/mock-router'; -import { of } from 'rxjs'; -import { Lecture } from 'app/entities/lecture.model'; -import { MockTranslateService } from '../../../helpers/mocks/service/mock-translate.service'; -import { TranslateService } from '@ngx-translate/core'; -import { Course } from 'app/entities/course.model'; -import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; -import { LectureUpdateWizardComponent } from 'app/lecture/wizard-mode/lecture-update-wizard.component'; -import { ArtemisNavigationUtilService } from 'app/utils/navigation.utils'; -import { CourseManagementService } from 'app/course/manage/course-management.service'; -import { LectureUpdateWizardStepComponent } from 'app/lecture/wizard-mode/lecture-update-wizard-step.component'; -import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; -import { LectureUpdateWizardAttachmentsComponent } from 'app/lecture/wizard-mode/lecture-wizard-attachments.component'; -import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; -import { LectureUpdateWizardTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component'; -import { TranslateDirective } from 'app/shared/language/translate.directive'; -import { FaIconComponent } from '@fortawesome/angular-fontawesome'; -import dayjs from 'dayjs/esm'; - -describe('LectureUpdateWizardComponent', () => { - let wizardComponentFixture: ComponentFixture; - let wizardComponent: LectureUpdateWizardComponent; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [], - declarations: [ - LectureUpdateWizardComponent, - MockPipe(ArtemisTranslatePipe), - MockComponent(LectureUpdateWizardStepComponent), - MockComponent(LectureUpdateWizardUnitsComponent), - MockComponent(LectureUpdateWizardAttachmentsComponent), - MockComponent(LectureUpdateWizardPeriodComponent), - MockComponent(LectureUpdateWizardTitleComponent), - MockComponent(FaIconComponent), - MockDirective(TranslateDirective), - ], - providers: [ - MockProvider(ArtemisNavigationUtilService), - MockProvider(CourseManagementService), - { provide: TranslateService, useClass: MockTranslateService }, - { provide: Router, useClass: MockRouter }, - { - provide: ActivatedRoute, - useValue: { queryParams: of({}) }, - }, - ], - schemas: [], - }) - .compileComponents() - .then(() => { - wizardComponentFixture = TestBed.createComponent(LectureUpdateWizardComponent); - wizardComponent = wizardComponentFixture.componentInstance; - - const course = new Course(); - course.id = 2; - - wizardComponent.lecture = new Lecture(); - wizardComponent.lecture.id = 1; - wizardComponent.lecture.course = course; - }); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it('should initialize and set step with given lecture', fakeAsync(() => { - wizardComponentFixture.detectChanges(); - expect(wizardComponent).not.toBeNull(); - - wizardComponentFixture.whenStable().then(() => { - expect(wizardComponent.currentStep).toBe(4); - }); - })); - - it('should initialize and set step without given lecture', fakeAsync(() => { - wizardComponent.lecture.id = undefined; - - wizardComponentFixture.detectChanges(); - expect(wizardComponent).not.toBeNull(); - - wizardComponentFixture.whenStable().then(() => { - expect(wizardComponent.currentStep).toBe(1); - }); - })); - - it('should initialize and set step without given lecture but preset date', fakeAsync(() => { - wizardComponent.lecture.id = undefined; - wizardComponent.lecture.startDate = dayjs().year(2010).month(3).date(5); - - wizardComponentFixture.detectChanges(); - expect(wizardComponent).not.toBeNull(); - - wizardComponentFixture.whenStable().then(() => { - expect(wizardComponent.currentStep).toBe(2); - }); - })); - - it('should initialize and set given step', fakeAsync(() => { - const route = TestBed.inject(ActivatedRoute); - route.queryParams = of({ step: 3 }); - - wizardComponentFixture.detectChanges(); - expect(wizardComponent).not.toBeNull(); - - wizardComponentFixture.whenStable().then(() => { - expect(wizardComponent.currentStep).toBe(3); - }); - })); - - it('should increase the step when clicked', fakeAsync(() => { - const route = TestBed.inject(ActivatedRoute); - route.queryParams = of({ step: 1 }); - - wizardComponentFixture.detectChanges(); - - wizardComponentFixture.whenStable().then(() => { - expect(wizardComponent.currentStep).toBe(1); - wizardComponent.progressToNextStep(); - expect(wizardComponent.currentStep).toBe(2); - }); - })); - - it('should save the lecture when finishing the last step', fakeAsync(() => { - wizardComponent.saveLectureFunction = () => {}; - const saveStub = jest.spyOn(wizardComponent, 'saveLectureFunction'); - - wizardComponentFixture.detectChanges(); - - wizardComponentFixture.whenStable().then(() => { - expect(wizardComponent.currentStep).toBe(4); - wizardComponent.progressToNextStep(); - expect(saveStub).toHaveBeenCalledOnce(); - }); - })); - - it('should increase the step after lecture created', fakeAsync(() => { - wizardComponentFixture.detectChanges(); - - wizardComponentFixture.whenStable().then(() => { - wizardComponent.currentStep = 2; - wizardComponent.onLectureCreationSucceeded(); - expect(wizardComponent.currentStep).toBe(3); - }); - })); - - it('should return correct icon for last step', fakeAsync(() => { - wizardComponentFixture.detectChanges(); - - wizardComponentFixture.whenStable().then(() => { - wizardComponent.currentStep = 5; - const result = wizardComponent.getNextIcon(); - expect(result).toBe(wizardComponent.faCheck); - }); - })); - - it('should return correct icon for not last step', fakeAsync(() => { - wizardComponentFixture.detectChanges(); - - wizardComponentFixture.whenStable().then(() => { - wizardComponent.currentStep = 3; - const result = wizardComponent.getNextIcon(); - expect(result).toBe(wizardComponent.faArrowRight); - }); - })); - - it('should return correct text for last step', fakeAsync(() => { - wizardComponentFixture.detectChanges(); - - wizardComponentFixture.whenStable().then(() => { - wizardComponent.currentStep = 5; - const result = wizardComponent.getNextText(); - expect(result).toBe('entity.action.finish'); - }); - })); - - it('should return correct text for not last step', fakeAsync(() => { - wizardComponentFixture.detectChanges(); - - wizardComponentFixture.whenStable().then(() => { - wizardComponent.currentStep = 3; - const result = wizardComponent.getNextText(); - expect(result).toBe('artemisApp.lecture.home.nextStepLabel'); - }); - })); - - it('should toggle wizard when lecture not created', fakeAsync(() => { - wizardComponent.toggleModeFunction = () => {}; - const toggleStub = jest.spyOn(wizardComponent, 'toggleModeFunction'); - - wizardComponentFixture.detectChanges(); - - wizardComponentFixture.whenStable().then(() => { - wizardComponent.currentStep = 1; - wizardComponent.toggleWizardMode(); - expect(toggleStub).toHaveBeenCalledOnce(); - }); - })); - - it('should navigate when toggling wizard after lecture was created', fakeAsync(() => { - const router = TestBed.inject(Router); - const navigateStub = jest.spyOn(router, 'navigate'); - - wizardComponentFixture.detectChanges(); - - wizardComponentFixture.whenStable().then(() => { - wizardComponent.currentStep = 3; - wizardComponent.toggleWizardMode(); - expect(navigateStub).toHaveBeenCalledTimes(2); // 1 from init to clear the params and 1 from toggling - }); - })); -}); From ce8b364bf4db23fef485c829ebf7b8fe164e220a Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Tue, 26 Nov 2024 11:25:01 +0100 Subject: [PATCH 054/125] Share current state --- .../create-exercise-unit.component.ts | 3 +- .../app/lecture/lecture-update.component.html | 32 +++++++++-------- .../app/lecture/lecture-update.component.ts | 35 ++++++++++++------- .../lecture-wizard-units.component.html | 1 - .../lecture-wizard-units.component.ts | 1 - 5 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component.ts index 3ca8d54564fb..21d08667f60d 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component.ts @@ -26,7 +26,6 @@ export class CreateExerciseUnitComponent implements OnInit { hasCancelButton = input(); hasCreateExerciseButton = input(); shouldNavigateOnSubmit = input(true); - currentWizardStep = input(); onCancel = output(); onExerciseUnitCreated = output(); @@ -126,7 +125,7 @@ export class CreateExerciseUnitComponent implements OnInit { createNewExercise() { this.router.navigate(['/course-management', this.courseId, 'exercises'], { - queryParams: { shouldHaveBackButtonToWizard: 'true', lectureId: this.lectureId, step: this.currentWizardStep() }, + queryParams: { shouldHaveBackButtonToWizard: 'true', lectureId: this.lectureId }, queryParamsHandling: '', }); } diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index 8e9812faa85a..6b75fd17ee95 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -65,22 +65,26 @@

-
-

-

- -

- -
+ @if (isEditMode()) { +
+

+

+ +

+ +
+ }
-
- -
- - - + @if (isEditMode()) { +
+ +
+ + + +
-
+ } @if (processUnitMode) {
    diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 6034b2e252d8..a3ce4efe7821 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -10,7 +10,7 @@ import { Course } from 'app/entities/course.model'; import { onError } from 'app/shared/util/global.utils'; import { ArtemisNavigationUtilService } from 'app/utils/navigation.utils'; import { DocumentationType } from 'app/shared/components/documentation-button/documentation-button.component'; -import { faBan, faHandshakeAngle, faPuzzlePiece, faQuestionCircle, faSave } from '@fortawesome/free-solid-svg-icons'; +import { faBan, faPuzzlePiece, faQuestionCircle, faSave } from '@fortawesome/free-solid-svg-icons'; import { ACCEPTED_FILE_EXTENSIONS_FILE_BROWSER, ALLOWED_FILE_EXTENSIONS_HUMAN_READABLE } from 'app/shared/constants/file-extensions.constants'; import { FormulaAction } from 'app/shared/monaco-editor/model/actions/formula.action'; import { ProgrammingExerciseDifficultyComponent } from 'app/exercises/programming/manage/update/update-components/difficulty/programming-exercise-difficulty.component'; @@ -31,7 +31,6 @@ export class LectureUpdateComponent implements OnInit { protected readonly faSave = faSave; protected readonly faPuzzleProcess = faPuzzlePiece; protected readonly faBan = faBan; - protected readonly faHandShakeAngle = faHandshakeAngle; protected readonly allowedFileExtensions = ALLOWED_FILE_EXTENSIONS_HUMAN_READABLE; protected readonly acceptedFileExtensionsFileBrowser = ACCEPTED_FILE_EXTENSIONS_FILE_BROWSER; @@ -75,7 +74,22 @@ export class LectureUpdateComponent implements OnInit { ) { effect(() => { // noinspection UnnecessaryLocalVariableJS: not inlined because the variable name improves readability - const updatedFormStatusSections: FormSectionStatus[] = [ + const updatedFormStatusSections: FormSectionStatus[] = []; + + if (this.isEditMode()) { + updatedFormStatusSections.push( + { + title: 'artemisApp.lecture.wizardMode.steps.attachmentsStepTitle', + valid: true, // TODO retrieve the valid status + }, + { + title: 'artemisApp.lecture.wizardMode.steps.unitsStepTitle', + valid: Boolean(this.unitSection()?.isUnitConfigurationValid()), + }, + ); + } + + updatedFormStatusSections.unshift( { title: 'artemisApp.lecture.wizardMode.steps.titleStepTitle', valid: Boolean(this.titleSection().titleChannelNameComponent().isFormValidSignal()), @@ -84,15 +98,7 @@ export class LectureUpdateComponent implements OnInit { title: 'artemisApp.lecture.wizardMode.steps.periodStepTitle', valid: Boolean(this.isPeriodSectionValid()), }, - { - title: 'artemisApp.lecture.wizardMode.steps.attachmentsStepTitle', - valid: true, // TODO retrieve the valid status - }, - { - title: 'artemisApp.lecture.wizardMode.steps.unitsStepTitle', - valid: Boolean(this.unitSection()?.isUnitConfigurationValid()), - }, - ]; + ); this.formStatusSections = updatedFormStatusSections; }); @@ -187,8 +193,11 @@ export class LectureUpdateComponent implements OnInit { state: { file: this.file, fileName: this.fileName }, }); } else { - this.router.navigate(['course-management', lecture.course!.id, 'lectures', lecture.id]); + this.router.navigate(['course-management', lecture.course!.id, 'lectures', lecture.id, 'edit']); } + // else { + // this.router.navigate(['course-management', lecture.course!.id, 'lectures', lecture.id]); + // } } /** diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html index 3736d642d80e..3fa1cdd3ef3a 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html @@ -61,7 +61,6 @@

    Date: Tue, 26 Nov 2024 23:53:04 +0100 Subject: [PATCH 055/125] On create stay on edit page --- .../webapp/app/lecture/lecture-update.component.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index a3ce4efe7821..b3c81ffb4658 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -192,12 +192,14 @@ export class LectureUpdateComponent implements OnInit { this.router.navigate(['course-management', lecture.course!.id, 'lectures', lecture.id, 'unit-management', 'attachment-units', 'process'], { state: { file: this.file, fileName: this.fileName }, }); + } else if (this.isEditMode()) { + this.router.navigate(['course-management', lecture.course!.id, 'lectures', lecture.id]); } else { - this.router.navigate(['course-management', lecture.course!.id, 'lectures', lecture.id, 'edit']); + // after create we stay on the edit page, as not attachments and lecture units are available (we need the lecture id to save them) + this.isEditMode.set(true); + this.lecture.set(lecture); + window.history.replaceState({}, '', `course-management/${lecture.course!.id}/lectures/${lecture.id}/edit`); } - // else { - // this.router.navigate(['course-management', lecture.course!.id, 'lectures', lecture.id]); - // } } /** From 4f7727abb19b51e3cd54ae94335d82db5fa9c161 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Tue, 26 Nov 2024 23:58:23 +0100 Subject: [PATCH 056/125] Move the buttons up --- .../app/lecture/lecture-update.component.html | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index 6b75fd17ee95..a255fcd53191 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -65,6 +65,14 @@

+
+ + +
@if (isEditMode()) {

@@ -122,14 +130,6 @@

}
-
- - -
@if (processUnitMode) {
From 1d32e8b663a82c46b16675ff6620ffdba7b439bf Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Wed, 27 Nov 2024 00:25:46 +0100 Subject: [PATCH 057/125] Scroll to bottom section if lecture is created anew --- .../app/lecture/lecture-update.component.ts | 82 +++++++++++-------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index b3c81ffb4658..80b8bfbb67f7 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -1,10 +1,9 @@ -import { Component, OnInit, Signal, ViewChild, computed, effect, signal, viewChild, viewChildren } from '@angular/core'; +import { Component, OnInit, Signal, ViewChild, computed, effect, inject, signal, viewChild, viewChildren } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; import { Observable } from 'rxjs'; import { AlertService } from 'app/core/util/alert.service'; import { LectureService } from './lecture.service'; -import { CourseManagementService } from '../course/manage/course-management.service'; import { Lecture } from 'app/entities/lecture.model'; import { Course } from 'app/entities/course.model'; import { onError } from 'app/shared/util/global.utils'; @@ -14,7 +13,7 @@ import { faBan, faPuzzlePiece, faQuestionCircle, faSave } from '@fortawesome/fre import { ACCEPTED_FILE_EXTENSIONS_FILE_BROWSER, ALLOWED_FILE_EXTENSIONS_HUMAN_READABLE } from 'app/shared/constants/file-extensions.constants'; import { FormulaAction } from 'app/shared/monaco-editor/model/actions/formula.action'; import { ProgrammingExerciseDifficultyComponent } from 'app/exercises/programming/manage/update/update-components/difficulty/programming-exercise-difficulty.component'; -import { FormSectionStatus } from 'app/forms/form-status-bar/form-status-bar.component'; +import { FormSectionStatus, FormStatusBarComponent } from 'app/forms/form-status-bar/form-status-bar.component'; import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component'; import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; @@ -34,10 +33,17 @@ export class LectureUpdateComponent implements OnInit { protected readonly allowedFileExtensions = ALLOWED_FILE_EXTENSIONS_HUMAN_READABLE; protected readonly acceptedFileExtensionsFileBrowser = ACCEPTED_FILE_EXTENSIONS_FILE_BROWSER; + private readonly alertService = inject(AlertService); + private readonly lectureService = inject(LectureService); + private readonly activatedRoute = inject(ActivatedRoute); + private readonly navigationUtilService = inject(ArtemisNavigationUtilService); + private readonly router = inject(Router); + @ViewChild(ProgrammingExerciseDifficultyComponent) lecturePeriodComponent?: LectureUpdateWizardPeriodComponent; titleSection = viewChild.required(LectureTitleChannelNameComponent); periodSectionDatepickers = viewChildren(FormDateTimePickerComponent); unitSection = viewChild(LectureUpdateWizardUnitsComponent); + formStatusBar = viewChild(FormStatusBarComponent); isPeriodSectionValid: Signal = computed(() => { for (const periodSectionDatepicker of this.periodSectionDatepickers()) { @@ -64,44 +70,49 @@ export class LectureUpdateComponent implements OnInit { fileName: string; fileInputTouched = false; - constructor( - protected alertService: AlertService, - protected lectureService: LectureService, - protected courseService: CourseManagementService, - protected activatedRoute: ActivatedRoute, - private navigationUtilService: ArtemisNavigationUtilService, - private router: Router, - ) { - effect(() => { - // noinspection UnnecessaryLocalVariableJS: not inlined because the variable name improves readability - const updatedFormStatusSections: FormSectionStatus[] = []; - - if (this.isEditMode()) { - updatedFormStatusSections.push( + isNewlyCreatedExercise = false; + + constructor() { + effect( + function updateFormStatusBarAfterLectureCreation() { + const updatedFormStatusSections: FormSectionStatus[] = []; + + if (this.isEditMode()) { + updatedFormStatusSections.push( + { + title: 'artemisApp.lecture.wizardMode.steps.attachmentsStepTitle', + valid: true, // TODO retrieve the valid status + }, + { + title: 'artemisApp.lecture.wizardMode.steps.unitsStepTitle', + valid: Boolean(this.unitSection()?.isUnitConfigurationValid()), + }, + ); + } + + updatedFormStatusSections.unshift( { - title: 'artemisApp.lecture.wizardMode.steps.attachmentsStepTitle', - valid: true, // TODO retrieve the valid status + title: 'artemisApp.lecture.wizardMode.steps.titleStepTitle', + valid: Boolean(this.titleSection().titleChannelNameComponent().isFormValidSignal()), }, { - title: 'artemisApp.lecture.wizardMode.steps.unitsStepTitle', - valid: Boolean(this.unitSection()?.isUnitConfigurationValid()), + title: 'artemisApp.lecture.wizardMode.steps.periodStepTitle', + valid: Boolean(this.isPeriodSectionValid()), }, ); - } - updatedFormStatusSections.unshift( - { - title: 'artemisApp.lecture.wizardMode.steps.titleStepTitle', - valid: Boolean(this.titleSection().titleChannelNameComponent().isFormValidSignal()), - }, - { - title: 'artemisApp.lecture.wizardMode.steps.periodStepTitle', - valid: Boolean(this.isPeriodSectionValid()), - }, - ); - - this.formStatusSections = updatedFormStatusSections; - }); + this.formStatusSections = updatedFormStatusSections; + }.bind(this), + ); + + effect( + function scrollToLastSectionAfterLectureCreation() { + if (this.unitSection() && this.isNewlyCreatedExercise) { + this.isNewlyCreatedExercise = false; + this.formStatusBar()?.scrollToHeadline('artemisApp.lecture.wizardMode.steps.periodStepTitle'); + } + }.bind(this), + ); } ngOnInit() { @@ -196,6 +207,7 @@ export class LectureUpdateComponent implements OnInit { this.router.navigate(['course-management', lecture.course!.id, 'lectures', lecture.id]); } else { // after create we stay on the edit page, as not attachments and lecture units are available (we need the lecture id to save them) + this.isNewlyCreatedExercise = true; this.isEditMode.set(true); this.lecture.set(lecture); window.history.replaceState({}, '', `course-management/${lecture.course!.id}/lectures/${lecture.id}/edit`); From bcb180feb7f71d93a86b6a738d4619530a3535c3 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Wed, 27 Nov 2024 00:33:10 +0100 Subject: [PATCH 058/125] Leave TODO --- src/main/webapp/app/lecture/lecture-attachments.component.ts | 1 + src/main/webapp/app/lecture/lecture-update.component.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/webapp/app/lecture/lecture-attachments.component.ts b/src/main/webapp/app/lecture/lecture-attachments.component.ts index bd5a706dd1b5..d9c661796bdd 100644 --- a/src/main/webapp/app/lecture/lecture-attachments.component.ts +++ b/src/main/webapp/app/lecture/lecture-attachments.component.ts @@ -93,6 +93,7 @@ export class LectureAttachmentsComponent implements OnDestroy { return attachmentLink.endsWith('.pdf') ?? false; } + // TODO make this a computed signal get isSubmitPossible(): boolean { return !!(this.attachmentToBeCreated?.name && (this.attachmentFile || this.attachmentToBeCreated?.link)); } diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 80b8bfbb67f7..aee0fa80caa9 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -18,6 +18,7 @@ import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lect import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component'; import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; +import { LectureAttachmentsComponent } from 'app/lecture/lecture-attachments.component'; @Component({ selector: 'jhi-lecture-update', @@ -42,6 +43,7 @@ export class LectureUpdateComponent implements OnInit { @ViewChild(ProgrammingExerciseDifficultyComponent) lecturePeriodComponent?: LectureUpdateWizardPeriodComponent; titleSection = viewChild.required(LectureTitleChannelNameComponent); periodSectionDatepickers = viewChildren(FormDateTimePickerComponent); + attachmentsSection = viewChild(LectureAttachmentsComponent); unitSection = viewChild(LectureUpdateWizardUnitsComponent); formStatusBar = viewChild(FormStatusBarComponent); @@ -81,7 +83,7 @@ export class LectureUpdateComponent implements OnInit { updatedFormStatusSections.push( { title: 'artemisApp.lecture.wizardMode.steps.attachmentsStepTitle', - valid: true, // TODO retrieve the valid status + valid: true, // TODO use actual value here }, { title: 'artemisApp.lecture.wizardMode.steps.unitsStepTitle', From 75e835c99090b319a11a882aa9a1f70b4c5b4aa9 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 28 Nov 2024 00:56:20 +0100 Subject: [PATCH 059/125] Use a signal in lecture-attachments.component.ts to check whether the input is valid --- .../webapp/app/entities/attachment.model.ts | 2 - .../lecture-attachments.component.html | 40 +++--- .../lecture-attachments.component.scss | 4 + .../lecture/lecture-attachments.component.ts | 134 ++++++++++++++---- .../app/lecture/lecture-update.component.html | 2 +- .../app/lecture/lecture-update.component.ts | 2 +- .../lecture-attachments.component.spec.ts | 36 ++--- 7 files changed, 147 insertions(+), 73 deletions(-) diff --git a/src/main/webapp/app/entities/attachment.model.ts b/src/main/webapp/app/entities/attachment.model.ts index c070019513ee..78e6b639da8d 100644 --- a/src/main/webapp/app/entities/attachment.model.ts +++ b/src/main/webapp/app/entities/attachment.model.ts @@ -20,6 +20,4 @@ export class Attachment implements BaseEntity { lecture?: Lecture; exercise?: Exercise; attachmentUnit?: AttachmentUnit; - - constructor() {} } diff --git a/src/main/webapp/app/lecture/lecture-attachments.component.html b/src/main/webapp/app/lecture/lecture-attachments.component.html index 6192f270d64e..75408a33841b 100644 --- a/src/main/webapp/app/lecture/lecture-attachments.component.html +++ b/src/main/webapp/app/lecture/lecture-attachments.component.html @@ -53,7 +53,7 @@

@for (attachment of attachments; track trackId($index, attachment)) { - + {{ attachment.id }} @@ -91,7 +91,7 @@

}

@if (lecture().isAtLeastInstructor) {

}
- @if (attachmentToBeCreated?.id === attachment?.id) { + @if (attachmentToBeUpdatedOrCreated()?.id === attachment?.id) {
} @@ -125,11 +125,11 @@

}

- @if (attachmentToBeCreated) { -
+ @if (attachmentToBeUpdatedOrCreated()) { +
- @if (!attachmentToBeCreated.id) { + @if (!attachmentToBeUpdatedOrCreated()!.id) {

} @else {

@@ -140,11 +140,11 @@

- +

@@ -175,22 +177,17 @@

}
- +
- @if (attachmentToBeCreated.id) { + @if (attachmentToBeUpdatedOrCreated()!.id) {
} @@ -198,7 +195,7 @@

- @@ -207,9 +204,8 @@

-
- } - @if (!attachmentToBeCreated) { + + } @else {
}

@if (lecture().isAtLeastInstructor) {
- @if (attachmentToBeUpdatedOrCreated()?.id === attachment?.id) { + @if (attachmentToBeUpdatedOrCreated?.id === attachment?.id) {
} @@ -125,11 +125,11 @@

}
- @if (attachmentToBeUpdatedOrCreated()) { + @if (attachmentToBeUpdatedOrCreated) {
- @if (!attachmentToBeUpdatedOrCreated()!.id) { + @if (!attachmentToBeUpdatedOrCreated!.id) {

} @else {

@@ -144,7 +144,7 @@

- @if (attachmentToBeUpdatedOrCreated()!.id) { + @if (attachmentToBeUpdatedOrCreated!.id) {
(new Lecture()); attachments: Attachment[] = []; - attachmentToBeUpdatedOrCreated = signal(undefined); + attachmentToBeUpdatedOrCreated?: Attachment; attachmentBackup?: Attachment; - attachmentFile = signal(undefined); + attachmentFile?: File; isDownloadingAttachmentLink?: string; notificationText?: string; erroredFile?: File; @@ -116,18 +116,13 @@ export class LectureAttachmentsComponent implements OnDestroy { return attachmentLink.endsWith('.pdf') ?? false; } - isSubmitPossible = computed(() => { - console.log('is submit possible was updated'); - return !!(this.attachmentToBeUpdatedOrCreated()?.name && (this.attachmentFile() || this.attachmentToBeUpdatedOrCreated()?.link)); - }); - addAttachment(): void { const newAttachment = new Attachment(); newAttachment.lecture = this.lecture(); newAttachment.attachmentType = AttachmentType.FILE; newAttachment.version = 0; newAttachment.uploadDate = dayjs(); - this.attachmentToBeUpdatedOrCreated.set(newAttachment); + this.attachmentToBeUpdatedOrCreated = newAttachment; } /** @@ -136,17 +131,17 @@ export class LectureAttachmentsComponent implements OnDestroy { saveAttachment(): void { console.log('form value', this.form.value); - if (!this.attachmentToBeUpdatedOrCreated()) { + if (!this.attachmentToBeUpdatedOrCreated) { return; } - const updatedOrCreatedAttachment: Attachment = { ...this.attachmentToBeUpdatedOrCreated() }; + const updatedOrCreatedAttachment: Attachment = { ...this.attachmentToBeUpdatedOrCreated }; updatedOrCreatedAttachment.version!++; updatedOrCreatedAttachment.uploadDate = dayjs(); updatedOrCreatedAttachment.name = this.form.value.attachmentName; updatedOrCreatedAttachment.releaseDate = this.form.value.releaseDate; - if (!this.attachmentFile() && !updatedOrCreatedAttachment.id) { + if (!this.attachmentFile && !updatedOrCreatedAttachment.id) { return; } @@ -155,7 +150,7 @@ export class LectureAttachmentsComponent implements OnDestroy { if (this.notificationText) { requestOptions.notificationText = this.notificationText; } - this.attachmentService.update(updatedOrCreatedAttachment.id, updatedOrCreatedAttachment, this.attachmentFile(), requestOptions).subscribe({ + this.attachmentService.update(updatedOrCreatedAttachment.id, updatedOrCreatedAttachment, this.attachmentFile, requestOptions).subscribe({ next: (attachmentRes: HttpResponse) => { this.resetAttachmentFormVariables(); this.notificationText = undefined; @@ -166,14 +161,14 @@ export class LectureAttachmentsComponent implements OnDestroy { error: (error: HttpErrorResponse) => this.handleFailedUpload(error), }); } else { - this.attachmentService.create(updatedOrCreatedAttachment, this.attachmentFile()!).subscribe({ + this.attachmentService.create(updatedOrCreatedAttachment, this.attachmentFile!).subscribe({ next: (attachmentRes: HttpResponse) => { this.attachments.push(attachmentRes.body!); this.lectureService.findWithDetails(this.lecture().id!).subscribe((lectureResponse: HttpResponse) => { this.lecture.set(lectureResponse.body!); }); - this.attachmentFile.set(undefined); - this.attachmentToBeUpdatedOrCreated.set(undefined); + this.attachmentFile = undefined; + this.attachmentToBeUpdatedOrCreated = undefined; this.attachmentBackup = undefined; this.loadAttachments(); this.clearFormValues(); @@ -193,17 +188,17 @@ export class LectureAttachmentsComponent implements OnDestroy { } private resetAttachmentFormVariables() { - this.attachmentFile.set(undefined); - this.attachmentToBeUpdatedOrCreated.set(undefined); + this.attachmentFile = undefined; + this.attachmentToBeUpdatedOrCreated = undefined; this.attachmentBackup = undefined; this.clearFormValues(); } private handleFailedUpload(error: HttpErrorResponse): void { this.errorMessage = error.message; - this.erroredFile = this.attachmentFile(); + this.erroredFile = this.attachmentFile; this.fileInput.nativeElement.value = ''; - this.attachmentFile.set(undefined); + this.attachmentFile = undefined; this.resetAttachment(); } @@ -219,7 +214,7 @@ export class LectureAttachmentsComponent implements OnDestroy { notificationText: this.notificationText, }); - this.attachmentToBeUpdatedOrCreated.set(attachment); + this.attachmentToBeUpdatedOrCreated = attachment; this.attachmentBackup = Object.assign({}, attachment, {}); } @@ -241,7 +236,7 @@ export class LectureAttachmentsComponent implements OnDestroy { if (this.attachmentBackup) { this.resetAttachment(); } - this.attachmentToBeUpdatedOrCreated.set(undefined); + this.attachmentToBeUpdatedOrCreated = undefined; this.erroredFile = undefined; } @@ -279,24 +274,13 @@ export class LectureAttachmentsComponent implements OnDestroy { return; } const attachmentFile = input.files[0]; - this.attachmentFile.set(attachmentFile); + this.attachmentFile = attachmentFile; + this.attachmentToBeUpdatedOrCreated!.link = attachmentFile.name; - if (!this.attachmentToBeUpdatedOrCreated()) { - return; + const shouldAutomaticallySetName = !this.attachmentToBeUpdatedOrCreated!.name; + if (shouldAutomaticallySetName) { + this.attachmentToBeUpdatedOrCreated!.name = this.attachmentFile.name.replace(/\.[^/.]+$/, ''); } - this.attachmentToBeUpdatedOrCreated.update((attachment) => { - if (attachment) { - attachment.link = attachmentFile.name; - - const shouldAutomaticallySetAttachmentName = !attachment.name; - if (shouldAutomaticallySetAttachmentName) { - attachment.name = this.determineAttachmentNameBasedOnFileName(attachmentFile.name); - } - } - return attachment; - }); - - this.attachmentToBeUpdatedOrCreated.set(this.attachmentToBeUpdatedOrCreated()); } private determineAttachmentNameBasedOnFileName(fileName: string): string { From 565479799d3983c93d610912adc3192bac4bbb3e Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 28 Nov 2024 02:11:00 +0100 Subject: [PATCH 061/125] Merge attachment name and link --- .../app/lecture/lecture-attachments.component.html | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-attachments.component.html b/src/main/webapp/app/lecture/lecture-attachments.component.html index 9c688b900a44..24f68c2b87ea 100644 --- a/src/main/webapp/app/lecture/lecture-attachments.component.html +++ b/src/main/webapp/app/lecture/lecture-attachments.component.html @@ -44,7 +44,6 @@

- @@ -57,21 +56,21 @@

{{ attachment.id }} - {{ attachment.name }} - {{ attachment.attachmentType }} @if (!isDownloadingAttachmentLink) { {{ attachment.name }} - } - @if (isDownloadingAttachmentLink === attachment.link) { + } @else if (isDownloadingAttachmentLink === attachment.link) { {{ 'artemisApp.courseOverview.lectureDetails.isDownloading' | artemisTranslate }} + } @else { + {{ attachment.name }} } + {{ attachment.attachmentType }} {{ attachment.releaseDate | artemisDate }} {{ attachment.uploadDate | artemisDate }} From 7a935a6bdcba622b71b9c108d4e6a594515de69b Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 28 Nov 2024 02:26:45 +0100 Subject: [PATCH 062/125] Fix updating attachment name --- .../app/lecture/lecture-attachments.component.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-attachments.component.ts b/src/main/webapp/app/lecture/lecture-attachments.component.ts index 7a404d377113..5b2c64e0ffad 100644 --- a/src/main/webapp/app/lecture/lecture-attachments.component.ts +++ b/src/main/webapp/app/lecture/lecture-attachments.component.ts @@ -207,9 +207,9 @@ export class LectureAttachmentsComponent implements OnDestroy { this.fileInput.nativeElement.value = ''; } + // attachmentFileName can only be set to an empty string due to security reasons in current angular version (18) this.setFormValues({ attachmentName: attachment?.name, - attachmentFileName: attachment?.link, releaseDate: dayjs(attachment?.releaseDate), notificationText: this.notificationText, }); @@ -238,6 +238,7 @@ export class LectureAttachmentsComponent implements OnDestroy { } this.attachmentToBeUpdatedOrCreated = undefined; this.erroredFile = undefined; + this.clearFormValues(); } resetAttachment(): void { @@ -276,10 +277,10 @@ export class LectureAttachmentsComponent implements OnDestroy { const attachmentFile = input.files[0]; this.attachmentFile = attachmentFile; this.attachmentToBeUpdatedOrCreated!.link = attachmentFile.name; - - const shouldAutomaticallySetName = !this.attachmentToBeUpdatedOrCreated!.name; - if (shouldAutomaticallySetName) { - this.attachmentToBeUpdatedOrCreated!.name = this.attachmentFile.name.replace(/\.[^/.]+$/, ''); + if (!this.attachmentToBeUpdatedOrCreated!.name) { + const derivedFileName = this.determineAttachmentNameBasedOnFileName(attachmentFile.name); + this.attachmentToBeUpdatedOrCreated!.name = derivedFileName; + this.form.patchValue({ attachmentName: derivedFileName }); } } From 33336ded4c56600e0009a68d618d29bfb8e9287b Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 28 Nov 2024 03:11:07 +0100 Subject: [PATCH 063/125] Use signals for form validation --- .../lecture-attachments.component.html | 21 ++++---- .../lecture/lecture-attachments.component.ts | 53 ++++++++++--------- 2 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-attachments.component.html b/src/main/webapp/app/lecture/lecture-attachments.component.html index 24f68c2b87ea..607f3cb10fb3 100644 --- a/src/main/webapp/app/lecture/lecture-attachments.component.html +++ b/src/main/webapp/app/lecture/lecture-attachments.component.html @@ -52,7 +52,7 @@

@for (attachment of attachments; track trackId($index, attachment)) { - + {{ attachment.id }} @@ -90,7 +90,7 @@

}
- @if (attachmentToBeUpdatedOrCreated?.id === attachment?.id) { + @if (attachmentToBeUpdatedOrCreated()?.id === attachment?.id) {
} @@ -124,11 +124,11 @@

}
- @if (attachmentToBeUpdatedOrCreated) { + @if (attachmentToBeUpdatedOrCreated()) {
- @if (!attachmentToBeUpdatedOrCreated!.id) { + @if (!attachmentToBeUpdatedOrCreated()!.id) {

} @else {

@@ -143,7 +143,7 @@

@@ -178,7 +179,7 @@

- @if (attachmentToBeUpdatedOrCreated!.id) { + @if (attachmentToBeUpdatedOrCreated()!.id) {
(new Lecture()); attachments: Attachment[] = []; - attachmentToBeUpdatedOrCreated?: Attachment; + attachmentToBeUpdatedOrCreated = signal(undefined); attachmentBackup?: Attachment; - attachmentFile?: File; + attachmentFile = signal(undefined); isDownloadingAttachmentLink?: string; notificationText?: string; erroredFile?: File; @@ -69,13 +69,17 @@ export class LectureAttachmentsComponent implements OnDestroy { form: FormGroup = this.formBuilder.group({ attachmentName: [undefined as string | undefined, [Validators.required]], - attachmentFileName: [undefined as string | undefined, [Validators.required]], + attachmentFileName: [undefined as string | undefined], releaseDate: [undefined as dayjs.Dayjs | undefined], notificationText: [undefined as string | undefined], }); + isFileSelectionValid = computed(() => { + return this.attachmentFile() || this.attachmentToBeUpdatedOrCreated()?.link; + }); + private readonly statusChanges = toSignal(this.form.statusChanges ?? 'INVALID'); - isFormValid = computed(() => this.statusChanges() === 'VALID' && this.datePickerComponent()?.isValid()); + isFormValid = computed(() => this.statusChanges() === 'VALID' && this.isFileSelectionValid() && this.datePickerComponent()?.isValid()); constructor() { effect( @@ -122,7 +126,7 @@ export class LectureAttachmentsComponent implements OnDestroy { newAttachment.attachmentType = AttachmentType.FILE; newAttachment.version = 0; newAttachment.uploadDate = dayjs(); - this.attachmentToBeUpdatedOrCreated = newAttachment; + this.attachmentToBeUpdatedOrCreated.set(newAttachment); } /** @@ -131,17 +135,17 @@ export class LectureAttachmentsComponent implements OnDestroy { saveAttachment(): void { console.log('form value', this.form.value); - if (!this.attachmentToBeUpdatedOrCreated) { + if (!this.attachmentToBeUpdatedOrCreated()) { return; } - const updatedOrCreatedAttachment: Attachment = { ...this.attachmentToBeUpdatedOrCreated }; + const updatedOrCreatedAttachment: Attachment = { ...this.attachmentToBeUpdatedOrCreated() }; updatedOrCreatedAttachment.version!++; updatedOrCreatedAttachment.uploadDate = dayjs(); updatedOrCreatedAttachment.name = this.form.value.attachmentName; updatedOrCreatedAttachment.releaseDate = this.form.value.releaseDate; - if (!this.attachmentFile && !updatedOrCreatedAttachment.id) { + if (!this.attachmentFile() && !updatedOrCreatedAttachment.id) { return; } @@ -150,7 +154,7 @@ export class LectureAttachmentsComponent implements OnDestroy { if (this.notificationText) { requestOptions.notificationText = this.notificationText; } - this.attachmentService.update(updatedOrCreatedAttachment.id, updatedOrCreatedAttachment, this.attachmentFile, requestOptions).subscribe({ + this.attachmentService.update(updatedOrCreatedAttachment.id, updatedOrCreatedAttachment, this.attachmentFile(), requestOptions).subscribe({ next: (attachmentRes: HttpResponse) => { this.resetAttachmentFormVariables(); this.notificationText = undefined; @@ -161,14 +165,14 @@ export class LectureAttachmentsComponent implements OnDestroy { error: (error: HttpErrorResponse) => this.handleFailedUpload(error), }); } else { - this.attachmentService.create(updatedOrCreatedAttachment, this.attachmentFile!).subscribe({ + this.attachmentService.create(updatedOrCreatedAttachment, this.attachmentFile()!).subscribe({ next: (attachmentRes: HttpResponse) => { this.attachments.push(attachmentRes.body!); this.lectureService.findWithDetails(this.lecture().id!).subscribe((lectureResponse: HttpResponse) => { this.lecture.set(lectureResponse.body!); }); - this.attachmentFile = undefined; - this.attachmentToBeUpdatedOrCreated = undefined; + this.attachmentFile.set(undefined); + this.attachmentToBeUpdatedOrCreated.set(undefined); this.attachmentBackup = undefined; this.loadAttachments(); this.clearFormValues(); @@ -188,17 +192,17 @@ export class LectureAttachmentsComponent implements OnDestroy { } private resetAttachmentFormVariables() { - this.attachmentFile = undefined; - this.attachmentToBeUpdatedOrCreated = undefined; + this.attachmentFile.set(undefined); + this.attachmentToBeUpdatedOrCreated.set(undefined); this.attachmentBackup = undefined; this.clearFormValues(); } private handleFailedUpload(error: HttpErrorResponse): void { this.errorMessage = error.message; - this.erroredFile = this.attachmentFile; + this.erroredFile = this.attachmentFile(); this.fileInput.nativeElement.value = ''; - this.attachmentFile = undefined; + this.attachmentFile.set(undefined); this.resetAttachment(); } @@ -214,7 +218,7 @@ export class LectureAttachmentsComponent implements OnDestroy { notificationText: this.notificationText, }); - this.attachmentToBeUpdatedOrCreated = attachment; + this.attachmentToBeUpdatedOrCreated.set(attachment); this.attachmentBackup = Object.assign({}, attachment, {}); } @@ -236,7 +240,7 @@ export class LectureAttachmentsComponent implements OnDestroy { if (this.attachmentBackup) { this.resetAttachment(); } - this.attachmentToBeUpdatedOrCreated = undefined; + this.attachmentToBeUpdatedOrCreated.set(undefined); this.erroredFile = undefined; this.clearFormValues(); } @@ -274,12 +278,13 @@ export class LectureAttachmentsComponent implements OnDestroy { if (!input.files?.length) { return; } - const attachmentFile = input.files[0]; - this.attachmentFile = attachmentFile; - this.attachmentToBeUpdatedOrCreated!.link = attachmentFile.name; - if (!this.attachmentToBeUpdatedOrCreated!.name) { - const derivedFileName = this.determineAttachmentNameBasedOnFileName(attachmentFile.name); - this.attachmentToBeUpdatedOrCreated!.name = derivedFileName; + + const file = input.files[0]; + this.attachmentFile.set(file); + this.attachmentToBeUpdatedOrCreated()!.link = file.name; + if (!this.attachmentToBeUpdatedOrCreated()!.name) { + const derivedFileName = this.determineAttachmentNameBasedOnFileName(file.name); + this.attachmentToBeUpdatedOrCreated()!.name = derivedFileName; this.form.patchValue({ attachmentName: derivedFileName }); } } From e5e8603962eb9735b4149b2d00fed99e8bc43fb3 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 28 Nov 2024 03:23:35 +0100 Subject: [PATCH 064/125] Fix tests --- .../lecture/lecture-attachments.component.ts | 6 +- .../lecture-attachments.component.spec.ts | 76 +++++++++++-------- 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-attachments.component.ts b/src/main/webapp/app/lecture/lecture-attachments.component.ts index d12568b324d7..fe9d048776b1 100644 --- a/src/main/webapp/app/lecture/lecture-attachments.component.ts +++ b/src/main/webapp/app/lecture/lecture-attachments.component.ts @@ -133,8 +133,6 @@ export class LectureAttachmentsComponent implements OnDestroy { * If there is an attachment to save, it will be created or updated depending on its current state. The file will be automatically provided with the request. */ saveAttachment(): void { - console.log('form value', this.form.value); - if (!this.attachmentToBeUpdatedOrCreated()) { return; } @@ -142,8 +140,8 @@ export class LectureAttachmentsComponent implements OnDestroy { const updatedOrCreatedAttachment: Attachment = { ...this.attachmentToBeUpdatedOrCreated() }; updatedOrCreatedAttachment.version!++; updatedOrCreatedAttachment.uploadDate = dayjs(); - updatedOrCreatedAttachment.name = this.form.value.attachmentName; - updatedOrCreatedAttachment.releaseDate = this.form.value.releaseDate; + updatedOrCreatedAttachment.name = this.form.value.attachmentName ?? undefined; + updatedOrCreatedAttachment.releaseDate = this.form.value.releaseDate ?? undefined; if (!this.attachmentFile() && !updatedOrCreatedAttachment.id) { return; diff --git a/src/test/javascript/spec/component/lecture/lecture-attachments.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-attachments.component.spec.ts index 68d4ed6d5312..5659fa956b5d 100644 --- a/src/test/javascript/spec/component/lecture/lecture-attachments.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-attachments.component.spec.ts @@ -9,18 +9,19 @@ import { LectureAttachmentsComponent } from 'app/lecture/lecture-attachments.com import { AttachmentService } from 'app/lecture/attachment.service'; import { FileService } from 'app/shared/http/file.service'; import { HtmlForMarkdownPipe } from 'app/shared/pipes/html-for-markdown.pipe'; -import { MockComponent, MockDirective, MockPipe, MockProvider } from 'ng-mocks'; +import { MockDirective, MockModule, MockPipe, MockProvider } from 'ng-mocks'; import { MockFileService } from '../../helpers/mocks/service/mock-file.service'; import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; import { DeleteButtonDirective } from 'app/shared/delete-dialog/delete-button.directive'; -import { NgModel } from '@angular/forms'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { of, take, throwError } from 'rxjs'; import { HttpResponse } from '@angular/common/http'; import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'; import { LectureService } from 'app/lecture/lecture.service'; import { RouterModule } from '@angular/router'; +import { OwlDateTimeModule, OwlNativeDateTimeModule } from '@danielmoncada/angular-datetime-picker'; describe('LectureAttachmentsComponent', () => { let comp: LectureAttachmentsComponent; @@ -85,12 +86,19 @@ describe('LectureAttachmentsComponent', () => { beforeEach(() => { return TestBed.configureTestingModule({ - imports: [ArtemisTestModule, MockDirective(NgbTooltip), RouterModule], + imports: [ + ArtemisTestModule, + MockDirective(NgbTooltip), + RouterModule, + ReactiveFormsModule, + FormsModule, + MockModule(OwlDateTimeModule), + MockModule(OwlNativeDateTimeModule), + ], declarations: [ LectureAttachmentsComponent, - MockComponent(FormDateTimePickerComponent), + FormDateTimePickerComponent, MockDirective(DeleteButtonDirective), - MockDirective(NgModel), MockPipe(ArtemisTranslatePipe), MockPipe(HtmlForMarkdownPipe), MockPipe(ArtemisDatePipe), @@ -140,15 +148,17 @@ describe('LectureAttachmentsComponent', () => { fixture.detectChanges(); jest.spyOn(attachmentService, 'create').mockReturnValue(of(new HttpResponse({ body: newAttachment }))); const addAttachmentButton = fixture.debugElement.query(By.css('#add-attachment')); - expect(comp.attachmentToBeUpdatedOrCreated).toBeUndefined(); + expect(comp.attachmentToBeUpdatedOrCreated()).toBeUndefined(); expect(addAttachmentButton).not.toBeNull(); addAttachmentButton.nativeElement.click(); fixture.detectChanges(); - comp.attachmentFile = new File([''], 'Test-File.pdf', { type: 'application/pdf' }); + comp.attachmentFile.set(new File([''], 'Test-File.pdf', { type: 'application/pdf' })); const uploadAttachmentButton = fixture.debugElement.query(By.css('#upload-attachment')); expect(uploadAttachmentButton).not.toBeNull(); - expect(comp.attachmentToBeUpdatedOrCreated).not.toBeNull(); - comp.attachmentToBeUpdatedOrCreated!.name = 'Test File Name'; + expect(comp.attachmentToBeUpdatedOrCreated()).not.toBeNull(); + comp.attachmentToBeUpdatedOrCreated()!.name = 'Test File Name'; + comp.form.value.name = 'Test File Name'; + comp.form.value.releaseDate = dayjs(); fixture.detectChanges(); expect(uploadAttachmentButton.nativeElement.disabled).toBeFalse(); uploadAttachmentButton.nativeElement.click(); @@ -162,16 +172,16 @@ describe('LectureAttachmentsComponent', () => { attachmentServiceCreateStub.mockReturnValue(throwError(() => new Error('File too large'))); fixture.detectChanges(); const addAttachmentButton = fixture.debugElement.query(By.css('#add-attachment')); - expect(comp.attachmentToBeUpdatedOrCreated).toBeUndefined(); + expect(comp.attachmentToBeUpdatedOrCreated()).toBeUndefined(); expect(addAttachmentButton).not.toBeNull(); addAttachmentButton.nativeElement.click(); fixture.detectChanges(); const fakeBlob = { name: 'Test-File.pdf', size: 100000000000000000 }; - comp.attachmentFile = fakeBlob as File; + comp.attachmentFile.set(fakeBlob as File); const uploadAttachmentButton = fixture.debugElement.query(By.css('#upload-attachment')); expect(uploadAttachmentButton).not.toBeNull(); - expect(comp.attachmentToBeUpdatedOrCreated).not.toBeNull(); - comp.attachmentToBeUpdatedOrCreated!.name = 'Test File Name'; + expect(comp.attachmentToBeUpdatedOrCreated()).not.toBeNull(); + comp.attachmentToBeUpdatedOrCreated()!.name = 'Test File Name'; fixture.detectChanges(); expect(uploadAttachmentButton.nativeElement.disabled).toBeFalse(); uploadAttachmentButton.nativeElement.click(); @@ -185,12 +195,12 @@ describe('LectureAttachmentsComponent', () => { it('should exit saveAttachment', fakeAsync(() => { fixture.detectChanges(); - comp.attachmentToBeUpdatedOrCreated = undefined; + comp.attachmentToBeUpdatedOrCreated.set(undefined); comp.saveAttachment(); expect(attachmentServiceFindAllByLectureIdStub).toHaveBeenCalledOnce(); expect(attachmentServiceCreateStub).not.toHaveBeenCalled(); expect(attachmentServiceUpdateStub).not.toHaveBeenCalled(); - expect(comp.attachmentToBeUpdatedOrCreated).toBeUndefined(); + expect(comp.attachmentToBeUpdatedOrCreated()).toBeUndefined(); })); it('should reset on error for create', fakeAsync(() => { @@ -199,18 +209,19 @@ describe('LectureAttachmentsComponent', () => { const attachment = { lecture: comp.lecture, attachmentType: AttachmentType.FILE, - version: 1, + version: 0, uploadDate: dayjs(), } as Attachment; const file = new File([''], 'Test-File.pdf', { type: 'application/pdf' }); comp.fileInput = { nativeElement: { value: 'Test-File.pdf' } }; - comp.attachmentToBeUpdatedOrCreated = attachment; - comp.attachmentFile = file; + comp.attachmentToBeUpdatedOrCreated.set(attachment); + comp.attachmentFile.set(file); const attachmentServiceCreateStub = jest.spyOn(attachmentService, 'create').mockReturnValue(throwError(() => new Error(errorMessage))); comp.saveAttachment(); + attachment.version = 1; expect(attachmentServiceCreateStub).toHaveBeenCalledExactlyOnceWith(attachment, file); - expect(comp.attachmentToBeUpdatedOrCreated).toEqual(attachment); - expect(comp.attachmentFile).toBeUndefined(); + expect(comp.attachmentToBeUpdatedOrCreated()).toEqual(attachment); + expect(comp.attachmentFile()).toBeUndefined(); expect(comp.erroredFile).toEqual(file); expect(comp.errorMessage).toBe(errorMessage); expect(comp.fileInput.nativeElement.value).toBe(''); @@ -234,25 +245,26 @@ describe('LectureAttachmentsComponent', () => { const file = new File([''], 'Test-File.pdf', { type: 'application/pdf' }); if (withFile) { comp.fileInput.nativeElement.value = 'Test-File.pdf'; - comp.attachmentFile = file; + comp.attachmentFile.set(file); } - comp.attachmentToBeUpdatedOrCreated = attachment; + comp.attachmentToBeUpdatedOrCreated.set(attachment); comp.notificationText = notification; comp.attachmentBackup = backup; comp.attachments = [attachment]; // Do change attachment.name = 'New Name'; + comp.form.value.attachmentName = 'New Name'; const attachmentServiceUpdateStub = jest.spyOn(attachmentService, 'update').mockReturnValue(throwError(() => new Error(errorMessage))); comp.saveAttachment(); expect(attachmentServiceUpdateStub).toHaveBeenCalledExactlyOnceWith(1, attachment, withFile ? file : undefined, { notificationText: notification }); - expect(comp.attachmentToBeUpdatedOrCreated).toEqual(attachment); + expect(comp.attachmentToBeUpdatedOrCreated()).toEqual(attachment); expect(comp.errorMessage).toBe(errorMessage); expect(comp.fileInput.nativeElement.value).toBe(''); expect(comp.attachments).toEqual([backup]); expect(comp.attachmentBackup).toBeUndefined(); - expect(comp.attachmentFile).toBeUndefined(); + expect(comp.attachmentFile()).toBeUndefined(); if (withFile) { expect(comp.erroredFile).toEqual(file); @@ -264,13 +276,13 @@ describe('LectureAttachmentsComponent', () => { it('should update Attachment', fakeAsync(() => { fixture.detectChanges(); - comp.attachmentToBeUpdatedOrCreated = { + comp.attachmentToBeUpdatedOrCreated.set({ id: 1, lecture: comp.lecture, attachmentType: AttachmentType.FILE, version: 1, uploadDate: dayjs(), - } as Attachment; + } as Attachment); comp.notificationText = 'wow how did i get here'; const attachmentServiceUpdateStub = jest.spyOn(attachmentService, 'update').mockReturnValue( of( @@ -294,10 +306,10 @@ describe('LectureAttachmentsComponent', () => { it('should edit attachment', fakeAsync(() => { fixture.detectChanges(); - comp.attachmentToBeUpdatedOrCreated = undefined; - expect(comp.attachmentToBeUpdatedOrCreated).toBeUndefined(); + comp.attachmentToBeUpdatedOrCreated.set(undefined); + expect(comp.attachmentToBeUpdatedOrCreated()).toBeUndefined(); comp.editAttachment(newAttachment); - expect(comp.attachmentToBeUpdatedOrCreated).toBe(newAttachment); + expect(comp.attachmentToBeUpdatedOrCreated()).toBe(newAttachment); expect(comp.attachmentBackup).toEqual(newAttachment); expect(attachmentServiceFindAllByLectureIdStub).toHaveBeenCalledOnce(); })); @@ -374,10 +386,10 @@ describe('LectureAttachmentsComponent', () => { files: [myBlob1, myBlob2], } as unknown as EventTarget, } as Event; - comp.attachmentToBeUpdatedOrCreated = newAttachment; + comp.attachmentToBeUpdatedOrCreated.set(newAttachment); comp.setLectureAttachment(object); - expect(comp.attachmentFile).toBe(myBlob1); - expect(comp.attachmentToBeUpdatedOrCreated.link).toBe(myBlob1.name); + expect(comp.attachmentFile()).toBe(myBlob1); + expect(comp.attachmentToBeUpdatedOrCreated()?.link).toBe(myBlob1.name); expect(attachmentServiceFindAllByLectureIdStub).toHaveBeenCalledOnce(); })); From 75161cf45f335e68789f579204de4687f03f09bf Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 28 Nov 2024 03:59:30 +0100 Subject: [PATCH 065/125] Fix tests --- .../lecture/lecture-attachments.component.spec.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/test/javascript/spec/component/lecture/lecture-attachments.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-attachments.component.spec.ts index 5659fa956b5d..2bf8f9fcee8e 100644 --- a/src/test/javascript/spec/component/lecture/lecture-attachments.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-attachments.component.spec.ts @@ -156,9 +156,10 @@ describe('LectureAttachmentsComponent', () => { const uploadAttachmentButton = fixture.debugElement.query(By.css('#upload-attachment')); expect(uploadAttachmentButton).not.toBeNull(); expect(comp.attachmentToBeUpdatedOrCreated()).not.toBeNull(); - comp.attachmentToBeUpdatedOrCreated()!.name = 'Test File Name'; - comp.form.value.name = 'Test File Name'; - comp.form.value.releaseDate = dayjs(); + comp.form.patchValue({ + attachmentName: 'Test File Name', + releaseDate: dayjs(), + }); fixture.detectChanges(); expect(uploadAttachmentButton.nativeElement.disabled).toBeFalse(); uploadAttachmentButton.nativeElement.click(); @@ -181,7 +182,10 @@ describe('LectureAttachmentsComponent', () => { const uploadAttachmentButton = fixture.debugElement.query(By.css('#upload-attachment')); expect(uploadAttachmentButton).not.toBeNull(); expect(comp.attachmentToBeUpdatedOrCreated()).not.toBeNull(); - comp.attachmentToBeUpdatedOrCreated()!.name = 'Test File Name'; + comp.form.patchValue({ + attachmentName: 'Test File Name', + releaseDate: dayjs(), + }); fixture.detectChanges(); expect(uploadAttachmentButton.nativeElement.disabled).toBeFalse(); uploadAttachmentButton.nativeElement.click(); From 29e12d751827012bf87acf84f4f1b967d95bfb04 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 28 Nov 2024 04:05:22 +0100 Subject: [PATCH 066/125] Set notification text --- src/main/webapp/app/lecture/lecture-attachments.component.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/webapp/app/lecture/lecture-attachments.component.ts b/src/main/webapp/app/lecture/lecture-attachments.component.ts index fe9d048776b1..913b76c95b1b 100644 --- a/src/main/webapp/app/lecture/lecture-attachments.component.ts +++ b/src/main/webapp/app/lecture/lecture-attachments.component.ts @@ -142,6 +142,7 @@ export class LectureAttachmentsComponent implements OnDestroy { updatedOrCreatedAttachment.uploadDate = dayjs(); updatedOrCreatedAttachment.name = this.form.value.attachmentName ?? undefined; updatedOrCreatedAttachment.releaseDate = this.form.value.releaseDate ?? undefined; + this.notificationText = this.form.value.notificationText ?? undefined; if (!this.attachmentFile() && !updatedOrCreatedAttachment.id) { return; From df1b600d3bf2dc6441612c719ce99089cb1c328d Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 28 Nov 2024 06:01:57 +0100 Subject: [PATCH 067/125] Fix attachments section valid status --- src/main/webapp/app/lecture/lecture-update.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 75418b372c55..262efcd20275 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -83,7 +83,7 @@ export class LectureUpdateComponent implements OnInit { updatedFormStatusSections.push( { title: 'artemisApp.lecture.wizardMode.steps.attachmentsStepTitle', - valid: Boolean(this.attachmentsSection()?.isSubmitPossible()), + valid: Boolean(!this.attachmentsSection() || this.attachmentsSection()?.isFormValid()), }, { title: 'artemisApp.lecture.wizardMode.steps.unitsStepTitle', From 09bad0f2e23bb277a389b5d8a2c52da0be62e9b5 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 28 Nov 2024 06:07:15 +0100 Subject: [PATCH 068/125] Make attachment section valid if no dialog open --- src/main/webapp/app/lecture/lecture-attachments.component.ts | 4 +++- src/main/webapp/app/lecture/lecture-update.component.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-attachments.component.ts b/src/main/webapp/app/lecture/lecture-attachments.component.ts index 913b76c95b1b..275c46bda862 100644 --- a/src/main/webapp/app/lecture/lecture-attachments.component.ts +++ b/src/main/webapp/app/lecture/lecture-attachments.component.ts @@ -79,7 +79,9 @@ export class LectureAttachmentsComponent implements OnDestroy { }); private readonly statusChanges = toSignal(this.form.statusChanges ?? 'INVALID'); - isFormValid = computed(() => this.statusChanges() === 'VALID' && this.isFileSelectionValid() && this.datePickerComponent()?.isValid()); + isFormValid = computed( + () => !this.attachmentToBeUpdatedOrCreated() || (this.statusChanges() === 'VALID' && this.isFileSelectionValid() && this.datePickerComponent()?.isValid()), + ); constructor() { effect( diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 262efcd20275..b0db8533d03a 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -83,7 +83,7 @@ export class LectureUpdateComponent implements OnInit { updatedFormStatusSections.push( { title: 'artemisApp.lecture.wizardMode.steps.attachmentsStepTitle', - valid: Boolean(!this.attachmentsSection() || this.attachmentsSection()?.isFormValid()), + valid: Boolean(this.attachmentsSection()?.isFormValid()), }, { title: 'artemisApp.lecture.wizardMode.steps.unitsStepTitle', From 8b4d0cff2471a58d2fef17b29f1aaf881786a2c6 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 28 Nov 2024 06:25:23 +0100 Subject: [PATCH 069/125] Move lecture to general info --- .../app/lecture/lecture-update.component.html | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index cc65aea25575..5aa2ab3d21dc 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -10,20 +10,20 @@

- @if (lecture().course) { -
- - @if (lecture().course?.title) { -
- -
- } -
- }

+ @if (lecture().course) { +
+ + @if (lecture().course?.title) { +
+ +
+ } +
+ } @if (lecture()) {
From edc1f2756d8f9b428543a5d8dae7a1ab778e8e91 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 28 Nov 2024 13:19:10 +0100 Subject: [PATCH 070/125] Disable save button if no changes have been made to title or period section --- .../exercise-title-channel-name.component.ts | 6 ++--- .../app/lecture/lecture-update.component.html | 7 +++++- .../app/lecture/lecture-update.component.ts | 23 +++++++++++++++++++ .../title-channel-name.component.ts | 6 ++--- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/main/webapp/app/exercises/shared/exercise-title-channel-name/exercise-title-channel-name.component.ts b/src/main/webapp/app/exercises/shared/exercise-title-channel-name/exercise-title-channel-name.component.ts index 5ad7384d3d17..5b53397e616e 100644 --- a/src/main/webapp/app/exercises/shared/exercise-title-channel-name/exercise-title-channel-name.component.ts +++ b/src/main/webapp/app/exercises/shared/exercise-title-channel-name/exercise-title-channel-name.component.ts @@ -1,4 +1,4 @@ -import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewChild, effect, inject, input, signal } from '@angular/core'; +import { Component, Input, OnChanges, SimpleChanges, ViewChild, effect, inject, input, output, signal } from '@angular/core'; import { Course, isCommunicationEnabled } from 'app/entities/course.model'; import { Exercise } from 'app/entities/exercise.model'; import { TitleChannelNameComponent } from 'app/shared/form/title-channel-name/title-channel-name.component'; @@ -22,8 +22,8 @@ export class ExerciseTitleChannelNameComponent implements OnChanges { @ViewChild(TitleChannelNameComponent) titleChannelNameComponent: TitleChannelNameComponent; - @Output() onTitleChange = new EventEmitter(); - @Output() onChannelNameChange = new EventEmitter(); + onTitleChange = output(); + onChannelNameChange = output(); private readonly exerciseService: ExerciseService = inject(ExerciseService); diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index 5aa2ab3d21dc..e70825a11e85 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -69,7 +69,12 @@

  -

diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index b0db8533d03a..4137c38c5c56 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -19,6 +19,7 @@ import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-chan import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; import { LectureAttachmentsComponent } from 'app/lecture/lecture-attachments.component'; +import cloneDeep from 'lodash-es/cloneDeep'; @Component({ selector: 'jhi-lecture-update', @@ -59,6 +60,7 @@ export class LectureUpdateComponent implements OnInit { isEditMode = signal(false); pressedSave: boolean = false; lecture = signal(new Lecture()); + lectureOnInit: Lecture; isSaving: boolean; isProcessing: boolean; processUnitMode: boolean; @@ -74,7 +76,27 @@ export class LectureUpdateComponent implements OnInit { isNewlyCreatedExercise = false; + isChangeMadeToTitleOrPeriodSection = false; + + handleTitleChange() { + console.log('triggered update isChangeMadeToTitleOrPeriodSection'); + this.isChangeMadeToTitleOrPeriodSection = this.lecture().title !== this.lectureOnInit.title || this.lecture().channelName !== this.lectureOnInit.channelName; + } + constructor() { + effect(() => { + this.titleSection() + .titleChannelNameComponent() + .titleChange.subscribe(() => { + this.handleTitleChange(); + }); + this.titleSection() + .titleChannelNameComponent() + .channelNameChange.subscribe(() => { + this.handleTitleChange(); + }); + }); + effect( function updateFormStatusBarAfterLectureCreation() { const updatedFormStatusSections: FormSectionStatus[] = []; @@ -133,6 +155,7 @@ export class LectureUpdateComponent implements OnInit { // TODO investigate where wizard mode is opened by url this.isEditMode.set(!this.router.url.endsWith('/new')); + this.lectureOnInit = cloneDeep(this.lecture()); } /** diff --git a/src/main/webapp/app/shared/form/title-channel-name/title-channel-name.component.ts b/src/main/webapp/app/shared/form/title-channel-name/title-channel-name.component.ts index 269e3a7e84e4..b7f139bc5182 100644 --- a/src/main/webapp/app/shared/form/title-channel-name/title-channel-name.component.ts +++ b/src/main/webapp/app/shared/form/title-channel-name/title-channel-name.component.ts @@ -1,4 +1,4 @@ -import { AfterViewInit, Component, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild, computed, effect, input, signal, viewChild } from '@angular/core'; +import { AfterViewInit, Component, Input, OnDestroy, OnInit, ViewChild, computed, effect, input, output, signal, viewChild } from '@angular/core'; import { ControlContainer, NgForm, NgModel } from '@angular/forms'; import { Subject, Subscription } from 'rxjs'; import { ProgrammingExerciseInputField } from 'app/exercises/programming/manage/update/programming-exercise-update.helper'; @@ -26,8 +26,8 @@ export class TitleChannelNameComponent implements AfterViewInit, OnDestroy, OnIn @ViewChild('field_title') field_title: NgModel; field_channel_name = viewChild('field_channel_name'); - @Output() titleChange = new EventEmitter(); - @Output() channelNameChange = new EventEmitter(); + titleChange = output(); + channelNameChange = output(); isFormValidSignal = signal(false); /** From 0e04cdd1fc722d88474fa0642d87202b5f2bdd84 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 28 Nov 2024 13:23:43 +0100 Subject: [PATCH 071/125] Enable save button if description was updated --- .../webapp/app/lecture/lecture-update.component.html | 7 ++++++- .../webapp/app/lecture/lecture-update.component.ts | 11 +++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index e70825a11e85..2ec36017dd70 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -28,7 +28,12 @@

- +

}
diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 4137c38c5c56..f2556b4f5bed 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -78,9 +78,12 @@ export class LectureUpdateComponent implements OnInit { isChangeMadeToTitleOrPeriodSection = false; - handleTitleChange() { + updateIsChangesMadeToTitleOrPeriodSection() { console.log('triggered update isChangeMadeToTitleOrPeriodSection'); - this.isChangeMadeToTitleOrPeriodSection = this.lecture().title !== this.lectureOnInit.title || this.lecture().channelName !== this.lectureOnInit.channelName; + this.isChangeMadeToTitleOrPeriodSection = + this.lecture().title !== this.lectureOnInit.title || + this.lecture().channelName !== this.lectureOnInit.channelName || + this.lecture().description !== this.lectureOnInit.description; } constructor() { @@ -88,12 +91,12 @@ export class LectureUpdateComponent implements OnInit { this.titleSection() .titleChannelNameComponent() .titleChange.subscribe(() => { - this.handleTitleChange(); + this.updateIsChangesMadeToTitleOrPeriodSection(); }); this.titleSection() .titleChannelNameComponent() .channelNameChange.subscribe(() => { - this.handleTitleChange(); + this.updateIsChangesMadeToTitleOrPeriodSection(); }); }); From 4b52b4bddac84c7f16eb8f27adbffdad795d368d Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 28 Nov 2024 13:33:56 +0100 Subject: [PATCH 072/125] Enable save button if date was modified --- .../webapp/app/lecture/lecture-update.component.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index f2556b4f5bed..1f8d950b3130 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -20,6 +20,7 @@ import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lectu import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; import { LectureAttachmentsComponent } from 'app/lecture/lecture-attachments.component'; import cloneDeep from 'lodash-es/cloneDeep'; +import dayjs from 'dayjs'; @Component({ selector: 'jhi-lecture-update', @@ -78,12 +79,14 @@ export class LectureUpdateComponent implements OnInit { isChangeMadeToTitleOrPeriodSection = false; - updateIsChangesMadeToTitleOrPeriodSection() { - console.log('triggered update isChangeMadeToTitleOrPeriodSection'); + protected updateIsChangesMadeToTitleOrPeriodSection() { this.isChangeMadeToTitleOrPeriodSection = this.lecture().title !== this.lectureOnInit.title || this.lecture().channelName !== this.lectureOnInit.channelName || - this.lecture().description !== this.lectureOnInit.description; + this.lecture().description !== this.lectureOnInit.description || + !dayjs(this.lecture().visibleDate).isSame(dayjs(this.lectureOnInit.visibleDate)) || + !dayjs(this.lecture().startDate).isSame(dayjs(this.lectureOnInit.startDate)) || + !dayjs(this.lecture().endDate).isSame(dayjs(this.lectureOnInit.endDate)); } constructor() { @@ -98,6 +101,11 @@ export class LectureUpdateComponent implements OnInit { .channelNameChange.subscribe(() => { this.updateIsChangesMadeToTitleOrPeriodSection(); }); + this.periodSectionDatepickers().forEach((datepicker) => { + datepicker.valueChange.subscribe(() => { + this.updateIsChangesMadeToTitleOrPeriodSection(); + }); + }); }); effect( From 4403d25365c64a382f19eabb25678f8b4e06ef71 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 28 Nov 2024 13:42:25 +0100 Subject: [PATCH 073/125] Unsubscribe on destroy --- .../app/lecture/lecture-update.component.ts | 66 +++++++++++-------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 1f8d950b3130..b9f94733cfe1 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -1,7 +1,7 @@ -import { Component, OnInit, Signal, ViewChild, computed, effect, inject, signal, viewChild, viewChildren } from '@angular/core'; +import { Component, OnDestroy, OnInit, Signal, ViewChild, computed, effect, inject, signal, viewChild, viewChildren } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; -import { Observable } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; import { AlertService } from 'app/core/util/alert.service'; import { LectureService } from './lecture.service'; import { Lecture } from 'app/entities/lecture.model'; @@ -27,7 +27,7 @@ import dayjs from 'dayjs'; templateUrl: './lecture-update.component.html', styleUrls: ['./lecture-update.component.scss'], }) -export class LectureUpdateComponent implements OnInit { +export class LectureUpdateComponent implements OnInit, OnDestroy { protected readonly documentationType: DocumentationType = 'Lecture'; protected readonly faQuestionCircle = faQuestionCircle; protected readonly faSave = faSave; @@ -79,33 +79,31 @@ export class LectureUpdateComponent implements OnInit { isChangeMadeToTitleOrPeriodSection = false; - protected updateIsChangesMadeToTitleOrPeriodSection() { - this.isChangeMadeToTitleOrPeriodSection = - this.lecture().title !== this.lectureOnInit.title || - this.lecture().channelName !== this.lectureOnInit.channelName || - this.lecture().description !== this.lectureOnInit.description || - !dayjs(this.lecture().visibleDate).isSame(dayjs(this.lectureOnInit.visibleDate)) || - !dayjs(this.lecture().startDate).isSame(dayjs(this.lectureOnInit.startDate)) || - !dayjs(this.lecture().endDate).isSame(dayjs(this.lectureOnInit.endDate)); - } + private subscriptions = new Subscription(); constructor() { effect(() => { - this.titleSection() - .titleChannelNameComponent() - .titleChange.subscribe(() => { - this.updateIsChangesMadeToTitleOrPeriodSection(); - }); - this.titleSection() - .titleChannelNameComponent() - .channelNameChange.subscribe(() => { - this.updateIsChangesMadeToTitleOrPeriodSection(); - }); - this.periodSectionDatepickers().forEach((datepicker) => { - datepicker.valueChange.subscribe(() => { - this.updateIsChangesMadeToTitleOrPeriodSection(); - }); - }); + this.subscriptions.add( + this.titleSection() + .titleChannelNameComponent() + .titleChange.subscribe(() => { + this.updateIsChangesMadeToTitleOrPeriodSection(); + }), + ); + this.subscriptions.add( + this.titleSection() + .titleChannelNameComponent() + .channelNameChange.subscribe(() => { + this.updateIsChangesMadeToTitleOrPeriodSection(); + }), + ); + this.subscriptions.add( + this.periodSectionDatepickers().forEach((datepicker) => { + datepicker.valueChange.subscribe(() => { + this.updateIsChangesMadeToTitleOrPeriodSection(); + }); + }), + ); }); effect( @@ -169,6 +167,20 @@ export class LectureUpdateComponent implements OnInit { this.lectureOnInit = cloneDeep(this.lecture()); } + ngOnDestroy() { + this.subscriptions.unsubscribe(); + } + + protected updateIsChangesMadeToTitleOrPeriodSection() { + this.isChangeMadeToTitleOrPeriodSection = + this.lecture().title !== this.lectureOnInit.title || + this.lecture().channelName !== this.lectureOnInit.channelName || + this.lecture().description !== this.lectureOnInit.description || + !dayjs(this.lecture().visibleDate).isSame(dayjs(this.lectureOnInit.visibleDate)) || + !dayjs(this.lecture().startDate).isSame(dayjs(this.lectureOnInit.startDate)) || + !dayjs(this.lecture().endDate).isSame(dayjs(this.lectureOnInit.endDate)); + } + /** * Revert to the previous state, equivalent with pressing the back button on your browser * Returns to the detail page if there is no previous state, and we edited an existing lecture From 2f273daea114a6361813d554c01bb5c9b9789322 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 28 Nov 2024 13:50:34 +0100 Subject: [PATCH 074/125] Add a warning when leaving the page with unsaved changes --- src/main/webapp/app/lecture/lecture-update.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index b9f94733cfe1..f97af4073ca0 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -168,6 +168,10 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { } ngOnDestroy() { + if (this.isChangeMadeToTitleOrPeriodSection) { + // TODO add a proper modal and find out where the changes are + alert('Unsaved changes in Title and/or period section, are you sure you want to leave without saving?'); + } this.subscriptions.unsubscribe(); } From f519c56b169ac2370540060fe279e5cf5c63f560 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Tue, 3 Dec 2024 14:51:59 +0100 Subject: [PATCH 075/125] Add modal for closing lecture with unsaved changes --- .../close-edit-lecture-dialog.component.html | 13 +++++++++++ ...lose-edit-lecture-dialog.component.spec.ts | 22 +++++++++++++++++++ .../close-edit-lecture-dialog.component.ts | 21 ++++++++++++++++++ .../app/lecture/lecture-update.component.ts | 18 +++++++++++++-- 4 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.html create mode 100644 src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.spec.ts create mode 100644 src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.ts diff --git a/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.html b/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.html new file mode 100644 index 000000000000..1edf7f09ca50 --- /dev/null +++ b/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.html @@ -0,0 +1,13 @@ + + + + + diff --git a/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.spec.ts b/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.spec.ts new file mode 100644 index 000000000000..4c3c67e6bd0e --- /dev/null +++ b/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CloseEditLectureDialogComponent } from './close-edit-lecture-dialog.component'; + +describe('CloseEditLectureDialogComponentTsComponent', () => { + let component: CloseEditLectureDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CloseEditLectureDialogComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(CloseEditLectureDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.ts b/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.ts new file mode 100644 index 000000000000..d90669d215a2 --- /dev/null +++ b/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.ts @@ -0,0 +1,21 @@ +import { Component, output } from '@angular/core'; +import { TranslateDirective } from 'app/shared/language/translate.directive'; +import { faTimes } from '@fortawesome/free-solid-svg-icons'; +import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; + +@Component({ + selector: 'jhi-close-edit-lecture-dialog', + standalone: true, + imports: [TranslateDirective, ArtemisSharedCommonModule], + templateUrl: './close-edit-lecture-dialog.component.html', + styleUrl: './close-edit-lecture-dialog.component.scss', +}) +export class CloseEditLectureDialogComponent { + protected readonly faTimes = faTimes; + + shallCloseWindow = output(); + + closeWindow(isCloseConfirmed: boolean): void { + this.shallCloseWindow.emit(isCloseConfirmed); + } +} diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index f97af4073ca0..7da82e0d486f 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -21,6 +21,8 @@ import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-ti import { LectureAttachmentsComponent } from 'app/lecture/lecture-attachments.component'; import cloneDeep from 'lodash-es/cloneDeep'; import dayjs from 'dayjs'; +import { CloseEditLectureDialogComponent } from 'app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; @Component({ selector: 'jhi-lecture-update', @@ -41,6 +43,7 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { private readonly activatedRoute = inject(ActivatedRoute); private readonly navigationUtilService = inject(ArtemisNavigationUtilService); private readonly router = inject(Router); + private readonly modalService = inject(NgbModal); @ViewChild(ProgrammingExerciseDifficultyComponent) lecturePeriodComponent?: LectureUpdateWizardPeriodComponent; titleSection = viewChild.required(LectureTitleChannelNameComponent); @@ -168,11 +171,22 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { } ngOnDestroy() { - if (this.isChangeMadeToTitleOrPeriodSection) { + // this.modalRef = this.modalService.open(DeleteDialogComponent, { size: 'lg', backdrop: 'static', animation }); + this.subscriptions.unsubscribe(); + + if (this.isChangeMadeToTitleOrPeriodSection && this.isEditMode()) { // TODO add a proper modal and find out where the changes are + this.openCloseEditLectureWithUnsavedChangesDialog(); alert('Unsaved changes in Title and/or period section, are you sure you want to leave without saving?'); } - this.subscriptions.unsubscribe(); + } + + openCloseEditLectureWithUnsavedChangesDialog(): void { + this.modalService.open(CloseEditLectureDialogComponent, { + size: 'lg', + backdrop: 'static', + animation: true, + }); } protected updateIsChangesMadeToTitleOrPeriodSection() { From 3dd5ee2ed021b10d121884bd1cb2a89656489345 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 5 Dec 2024 23:34:59 +0100 Subject: [PATCH 076/125] Open modal when unsaved changes to title or period section --- .../close-edit-lecture-dialog.component.html | 14 ++++++------- .../close-edit-lecture-dialog.component.ts | 8 +++---- .../lecture/hasLectureUnsavedChanges.guard.ts | 21 +++++++++++++++++++ .../app/lecture/lecture-update.component.ts | 16 -------------- src/main/webapp/app/lecture/lecture.route.ts | 3 ++- 5 files changed, 34 insertions(+), 28 deletions(-) create mode 100644 src/main/webapp/app/lecture/hasLectureUnsavedChanges.guard.ts diff --git a/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.html b/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.html index 1edf7f09ca50..6ed3fefd8856 100644 --- a/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.html +++ b/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.html @@ -1,13 +1,13 @@ - - diff --git a/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.ts b/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.ts index d90669d215a2..5296cd09d5ba 100644 --- a/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.ts +++ b/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.ts @@ -1,21 +1,21 @@ -import { Component, output } from '@angular/core'; +import { Component, inject } from '@angular/core'; import { TranslateDirective } from 'app/shared/language/translate.directive'; import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; @Component({ selector: 'jhi-close-edit-lecture-dialog', standalone: true, imports: [TranslateDirective, ArtemisSharedCommonModule], templateUrl: './close-edit-lecture-dialog.component.html', - styleUrl: './close-edit-lecture-dialog.component.scss', }) export class CloseEditLectureDialogComponent { protected readonly faTimes = faTimes; - shallCloseWindow = output(); + protected readonly activeModal = inject(NgbActiveModal); closeWindow(isCloseConfirmed: boolean): void { - this.shallCloseWindow.emit(isCloseConfirmed); + this.activeModal.close(isCloseConfirmed); } } diff --git a/src/main/webapp/app/lecture/hasLectureUnsavedChanges.guard.ts b/src/main/webapp/app/lecture/hasLectureUnsavedChanges.guard.ts new file mode 100644 index 000000000000..6742485b5c90 --- /dev/null +++ b/src/main/webapp/app/lecture/hasLectureUnsavedChanges.guard.ts @@ -0,0 +1,21 @@ +import { inject } from '@angular/core'; +import { CanDeactivateFn } from '@angular/router'; +import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { CloseEditLectureDialogComponent } from 'app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component'; +import { LectureUpdateComponent } from 'app/lecture/lecture-update.component'; +import { Observable, from, of } from 'rxjs'; + +export const hasLectureUnsavedChangesGuard: CanDeactivateFn = (component: LectureUpdateComponent): Observable => { + if (component.isChangeMadeToTitleOrPeriodSection) { + const modalService = inject(NgbModal); + + const modalRef: NgbModalRef = modalService.open(CloseEditLectureDialogComponent, { + size: 'lg', + backdrop: 'static', + animation: true, + }); + return from(modalRef.result); + } + + return of(true); +}; diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 7da82e0d486f..314653c22c22 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -21,7 +21,6 @@ import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-ti import { LectureAttachmentsComponent } from 'app/lecture/lecture-attachments.component'; import cloneDeep from 'lodash-es/cloneDeep'; import dayjs from 'dayjs'; -import { CloseEditLectureDialogComponent } from 'app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; @Component({ @@ -171,22 +170,7 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { } ngOnDestroy() { - // this.modalRef = this.modalService.open(DeleteDialogComponent, { size: 'lg', backdrop: 'static', animation }); this.subscriptions.unsubscribe(); - - if (this.isChangeMadeToTitleOrPeriodSection && this.isEditMode()) { - // TODO add a proper modal and find out where the changes are - this.openCloseEditLectureWithUnsavedChangesDialog(); - alert('Unsaved changes in Title and/or period section, are you sure you want to leave without saving?'); - } - } - - openCloseEditLectureWithUnsavedChangesDialog(): void { - this.modalService.open(CloseEditLectureDialogComponent, { - size: 'lg', - backdrop: 'static', - animation: true, - }); } protected updateIsChangesMadeToTitleOrPeriodSection() { diff --git a/src/main/webapp/app/lecture/lecture.route.ts b/src/main/webapp/app/lecture/lecture.route.ts index 11850d96840f..4172fd719fe2 100644 --- a/src/main/webapp/app/lecture/lecture.route.ts +++ b/src/main/webapp/app/lecture/lecture.route.ts @@ -17,6 +17,7 @@ import { CourseManagementTabBarComponent } from 'app/course/manage/course-manage import { PdfPreviewComponent } from 'app/lecture/pdf-preview/pdf-preview.component'; import { Attachment } from 'app/entities/attachment.model'; import { AttachmentService } from 'app/lecture/attachment.service'; +import { hasLectureUnsavedChangesGuard } from 'app/lecture/hasLectureUnsavedChanges.guard'; @Injectable({ providedIn: 'root' }) export class LectureResolve implements Resolve { @@ -131,7 +132,7 @@ export const lectureRoute: Routes = [ authorities: [Authority.EDITOR, Authority.INSTRUCTOR, Authority.ADMIN], pageTitle: 'global.generic.edit', }, - canActivate: [UserRouteAccessService], + canDeactivate: [hasLectureUnsavedChangesGuard], }, ...lectureUnitRoute, ], From 5d66c233a7a3ff3cac717bb88f354bce3851bef5 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 5 Dec 2024 23:38:52 +0100 Subject: [PATCH 077/125] Do not show warning modal when cancel is used --- src/main/webapp/app/lecture/hasLectureUnsavedChanges.guard.ts | 4 ++++ src/main/webapp/app/lecture/lecture-update.component.ts | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/main/webapp/app/lecture/hasLectureUnsavedChanges.guard.ts b/src/main/webapp/app/lecture/hasLectureUnsavedChanges.guard.ts index 6742485b5c90..ace7036ddf35 100644 --- a/src/main/webapp/app/lecture/hasLectureUnsavedChanges.guard.ts +++ b/src/main/webapp/app/lecture/hasLectureUnsavedChanges.guard.ts @@ -6,6 +6,10 @@ import { LectureUpdateComponent } from 'app/lecture/lecture-update.component'; import { Observable, from, of } from 'rxjs'; export const hasLectureUnsavedChangesGuard: CanDeactivateFn = (component: LectureUpdateComponent): Observable => { + if (!component.shouldDisplayDismissWarning) { + return of(true); + } + if (component.isChangeMadeToTitleOrPeriodSection) { const modalService = inject(NgbModal); diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 314653c22c22..ecbea5e0c19e 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -80,6 +80,7 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { isNewlyCreatedExercise = false; isChangeMadeToTitleOrPeriodSection = false; + shouldDisplayDismissWarning = true; private subscriptions = new Subscription(); @@ -189,6 +190,7 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { * Returns to the overview page if there is no previous state, and we created a new lecture */ previousState() { + this.shouldDisplayDismissWarning = false; this.navigationUtilService.navigateBackWithOptional(['course-management', this.lecture().course!.id!.toString(), 'lectures'], this.lecture().id?.toString()); } From a0eb4651616835190501b3a49dce2aadf6c3e65d Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Thu, 5 Dec 2024 23:54:55 +0100 Subject: [PATCH 078/125] Pass the information which section contains unsaved changes --- .../close-edit-lecture-dialog.component.html | 9 ++++++++- .../close-edit-lecture-dialog.component.ts | 6 +++++- .../lecture/hasLectureUnsavedChanges.guard.ts | 3 +++ .../app/lecture/lecture-update.component.ts | 18 ++++++++++++++---- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.html b/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.html index 6ed3fefd8856..b11bdbab7caa 100644 --- a/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.html +++ b/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.html @@ -1,7 +1,14 @@ - + diff --git a/src/main/webapp/i18n/de/lecture.json b/src/main/webapp/i18n/de/lecture.json index c6924e09f879..1caf061634f8 100644 --- a/src/main/webapp/i18n/de/lecture.json +++ b/src/main/webapp/i18n/de/lecture.json @@ -89,6 +89,12 @@ "competencyTitle": "Titel", "competencyConnectedUnits": "Verknüpfte Einheiten", "competencyNoConnectedUnits": "Keine verknüpften Einheiten" + }, + "dismissChangesModal": { + "title": "Ungespeicherte Änderungen der Vorlesung verwerfen", + "message": "Bist du sicher, dass die ungespeicherten Änderungen verworfen werden sollen?", + "sectionsThatContainUnsavedChanges": "Die folgenden Abschnitte enthalten ungespeicherte Änderungen:", + "sectionThatContainsUnsavedChanges": "Der folgenden Abschnitt enthält ungespeicherte Änderungen:" } }, "attachment": { diff --git a/src/main/webapp/i18n/en/lecture.json b/src/main/webapp/i18n/en/lecture.json index cd938a84dd13..488ca5a2a059 100644 --- a/src/main/webapp/i18n/en/lecture.json +++ b/src/main/webapp/i18n/en/lecture.json @@ -89,6 +89,12 @@ "competencyTitle": "Title", "competencyConnectedUnits": "Connected Units", "competencyNoConnectedUnits": "No connected units" + }, + "dismissChangesModal": { + "title": "Discard unsaved lecture changes", + "message": "Are you sure you want to discard your unsaved changes?", + "sectionsThatContainUnsavedChanges": "The following sections contain unsaved changes:", + "sectionThatContainsUnsavedChanges": "The following section contains unsaved changes:" } }, "attachment": { From 8d4224fd26d954eff7493c0fda44fc8cf822880d Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Fri, 6 Dec 2024 00:26:35 +0100 Subject: [PATCH 080/125] Disable save if attachments / units section is invalid (and therefore contains unsaved changes) --- .../webapp/app/lecture/lecture-update.component.html | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index 2ec36017dd70..77fe0e2d6903 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -77,7 +77,14 @@

  From 55897beebff1d0fe79750604c9ec2c5587129131 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Fri, 6 Dec 2024 00:58:30 +0100 Subject: [PATCH 081/125] Fix tests --- .../lecture/lecture-update.component.spec.ts | 104 ++++-------------- 1 file changed, 20 insertions(+), 84 deletions(-) diff --git a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts index 6a0389c9d60e..34b9416a097f 100644 --- a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts @@ -8,7 +8,6 @@ import { TranslateService } from '@ngx-translate/core'; import { Lecture } from 'app/entities/lecture.model'; import { LectureUpdateComponent } from 'app/lecture/lecture-update.component'; import { LectureService } from 'app/lecture/lecture.service'; -import { LectureUpdateWizardComponent } from 'app/lecture/wizard-mode/lecture-update-wizard.component'; import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; @@ -25,9 +24,6 @@ import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-chan import { MarkdownEditorMonacoComponent } from 'app/shared/markdown-editor/monaco/markdown-editor-monaco.component'; describe('LectureUpdateComponent', () => { - let lectureUpdateWizardComponentFixture: ComponentFixture; - let lectureUpdateWizardComponent: LectureUpdateWizardComponent; - let lectureService: LectureService; let lectureUpdateComponentFixture: ComponentFixture; let lectureUpdateComponent: LectureUpdateComponent; @@ -49,7 +45,6 @@ describe('LectureUpdateComponent', () => { declarations: [ LectureUpdateComponent, MockComponent(LectureTitleChannelNameComponent), - MockComponent(LectureUpdateWizardComponent), MockComponent(FormDateTimePickerComponent), MockComponent(MarkdownEditorMonacoComponent), MockComponent(DocumentationButtonComponent), @@ -84,9 +79,6 @@ describe('LectureUpdateComponent', () => { lectureUpdateComponentFixture = TestBed.createComponent(LectureUpdateComponent); lectureUpdateComponent = lectureUpdateComponentFixture.componentInstance; - lectureUpdateWizardComponentFixture = TestBed.createComponent(LectureUpdateWizardComponent); - lectureUpdateWizardComponent = lectureUpdateWizardComponentFixture.componentInstance; - lectureService = TestBed.inject(LectureService); router = TestBed.get(Router); activatedRoute = TestBed.inject(ActivatedRoute); @@ -98,7 +90,7 @@ describe('LectureUpdateComponent', () => { }); it('should create lecture', fakeAsync(() => { - lectureUpdateComponent.lecture = { title: 'test1', channelName: 'test1' } as Lecture; + lectureUpdateComponent.lecture.set({ title: 'test1', channelName: 'test1' } as Lecture); const navigateSpy = jest.spyOn(router, 'navigate'); const createSpy = jest.spyOn(lectureService, 'create').mockReturnValue( @@ -126,54 +118,11 @@ describe('LectureUpdateComponent', () => { expect(createSpy).toHaveBeenCalledWith({ title: 'test1', channelName: 'test1' }); })); - it('should create lecture in wizard mode', () => { - lectureUpdateComponent.lecture = { title: '', channelName: '' } as Lecture; - lectureUpdateComponent.isShowingWizardMode = true; - lectureUpdateComponent.wizardComponent = lectureUpdateWizardComponent; - - const createSpy = jest.spyOn(lectureService, 'create').mockReturnValue( - of>( - new HttpResponse({ - body: { - title: 'test1', - course: { - id: 1, - }, - } as Lecture, - }), - ), - ); - - const findSpy = jest.spyOn(lectureService, 'findWithDetails').mockReturnValue( - of>( - new HttpResponse({ - body: { - id: 3, - title: 'test1', - course: { - id: 1, - }, - } as Lecture, - }), - ), - ); - - const onLectureCreationSucceededSpy = jest.spyOn(lectureUpdateWizardComponent, 'onLectureCreationSucceeded'); - - lectureUpdateComponent.save(); - - expect(createSpy).toHaveBeenCalledOnce(); - expect(createSpy).toHaveBeenCalledWith({ title: '', channelName: '' }); - - expect(findSpy).toHaveBeenCalledOnce(); - expect(onLectureCreationSucceededSpy).toHaveBeenCalledOnce(); - }); - it('should edit a lecture', fakeAsync(() => { activatedRoute.parent!.data = of({ course: { id: 1 }, lecture: { id: 6 } }); lectureUpdateComponentFixture.detectChanges(); - lectureUpdateComponent.lecture = { id: 6, title: 'test1Updated', channelName: 'test1Updated' } as Lecture; + lectureUpdateComponent.lecture.set({ id: 6, title: 'test1Updated', channelName: 'test1Updated' } as Lecture); const updateSpy = jest.spyOn(lectureService, 'update').mockReturnValue( of>( @@ -197,19 +146,6 @@ describe('LectureUpdateComponent', () => { expect(updateSpy).toHaveBeenCalledWith({ id: 6, title: 'test1Updated', channelName: 'test1Updated' }); })); - it('should be in wizard mode', fakeAsync(() => { - activatedRoute = TestBed.inject(ActivatedRoute); - activatedRoute.queryParams = of({ - shouldBeInWizardMode: true, - }); - - lectureUpdateComponent.ngOnInit(); - lectureUpdateComponentFixture.detectChanges(); - tick(); - - expect(lectureUpdateComponent.isShowingWizardMode).toBeTrue(); - })); - it('should select process units checkbox', fakeAsync(() => { lectureUpdateComponent.processUnitMode = false; const selectProcessUnit = jest.spyOn(lectureUpdateComponent, 'onSelectProcessUnit'); @@ -240,7 +176,7 @@ describe('LectureUpdateComponent', () => { lectureUpdateComponent.file = new File([''], 'testFile.pdf', { type: 'application/pdf' }); lectureUpdateComponent.fileName = 'testFile'; lectureUpdateComponent.processUnitMode = true; - lectureUpdateComponent.lecture = { title: 'test1', channelName: 'test1' } as Lecture; + lectureUpdateComponent.lecture.set({ title: 'test1', channelName: 'test1' } as Lecture); const navigateSpy = jest.spyOn(router, 'navigate'); const createSpy = jest.spyOn(lectureService, 'create').mockReturnValue( @@ -293,45 +229,45 @@ describe('LectureUpdateComponent', () => { activatedRoute.parent!.data = of({ course: { id: 1 }, lecture: { id: 6 } }); lectureUpdateComponentFixture.detectChanges(); - lectureUpdateComponent.lecture = { id: 6, title: 'test1Updated' } as Lecture; + lectureUpdateComponent.lecture.set({ id: 6, title: 'test1Updated' } as Lecture); const setDatesSpy = jest.spyOn(lectureUpdateComponent, 'onDatesValuesChanged'); - lectureUpdateComponent.lecture.visibleDate = dayjs().year(2022).month(3).date(7); - lectureUpdateComponent.lecture.startDate = dayjs().year(2022).month(3).date(5); - lectureUpdateComponent.lecture.endDate = dayjs().year(2022).month(3).date(1); + lectureUpdateComponent.lecture().visibleDate = dayjs().year(2022).month(3).date(7); + lectureUpdateComponent.lecture().startDate = dayjs().year(2022).month(3).date(5); + lectureUpdateComponent.lecture().endDate = dayjs().year(2022).month(3).date(1); lectureUpdateComponent.onDatesValuesChanged(); expect(setDatesSpy).toHaveBeenCalledOnce(); - expect(lectureUpdateComponent.lecture.startDate).toEqual(lectureUpdateComponent.lecture.endDate); - expect(lectureUpdateComponent.lecture.startDate).toEqual(lectureUpdateComponent.lecture.visibleDate); + expect(lectureUpdateComponent.lecture().startDate).toEqual(lectureUpdateComponent.lecture().endDate); + expect(lectureUpdateComponent.lecture().startDate).toEqual(lectureUpdateComponent.lecture().visibleDate); lectureUpdateComponentFixture.detectChanges(); tick(); - lectureUpdateComponent.lecture.startDate = undefined; - lectureUpdateComponent.lecture.endDate = undefined; - lectureUpdateComponent.lecture.visibleDate = undefined; + lectureUpdateComponent.lecture().startDate = undefined; + lectureUpdateComponent.lecture().endDate = undefined; + lectureUpdateComponent.lecture().visibleDate = undefined; lectureUpdateComponent.onDatesValuesChanged(); expect(setDatesSpy).toHaveBeenCalledTimes(2); - expect(lectureUpdateComponent.lecture.startDate).toBeUndefined(); - expect(lectureUpdateComponent.lecture.endDate).toBeUndefined(); - expect(lectureUpdateComponent.lecture.visibleDate).toBeUndefined(); + expect(lectureUpdateComponent.lecture().startDate).toBeUndefined(); + expect(lectureUpdateComponent.lecture().endDate).toBeUndefined(); + expect(lectureUpdateComponent.lecture().visibleDate).toBeUndefined(); lectureUpdateComponentFixture.detectChanges(); tick(); - lectureUpdateComponent.lecture.visibleDate = dayjs().year(2022).month(1).date(1); - lectureUpdateComponent.lecture.startDate = dayjs().year(2022).month(1).date(2); - lectureUpdateComponent.lecture.endDate = dayjs().year(2022).month(1).date(3); + lectureUpdateComponent.lecture().visibleDate = dayjs().year(2022).month(1).date(1); + lectureUpdateComponent.lecture().startDate = dayjs().year(2022).month(1).date(2); + lectureUpdateComponent.lecture().endDate = dayjs().year(2022).month(1).date(3); lectureUpdateComponent.onDatesValuesChanged(); expect(setDatesSpy).toHaveBeenCalledTimes(3); - expect(lectureUpdateComponent.lecture.visibleDate.toDate()).toBeBefore(lectureUpdateComponent.lecture.startDate.toDate()); - expect(lectureUpdateComponent.lecture.startDate.toDate()).toBeBefore(lectureUpdateComponent.lecture.endDate.toDate()); + expect(lectureUpdateComponent.lecture().visibleDate?.toDate()).toBeBefore(lectureUpdateComponent.lecture().startDate?.toDate()); + expect(lectureUpdateComponent.lecture().startDate?.toDate()).toBeBefore(lectureUpdateComponent.lecture().endDate?.toDate()); })); }); From 852e3ca1db53c43f9abe1ca7a365fe4377ec9788 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Fri, 6 Dec 2024 01:21:33 +0100 Subject: [PATCH 082/125] Fix tests (by adding imports) --- .../lecture-unit-management.component.ts | 16 ++++--- .../app/lecture/lecture-update.component.ts | 2 - .../lecture/lecture-update.component.spec.ts | 44 ++++++++++++++++--- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/lecture-unit-management.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/lecture-unit-management.component.ts index 6ca545a7881c..acd9e93ed058 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/lecture-unit-management.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/lecture-unit-management.component.ts @@ -89,13 +89,15 @@ export class LectureUnitManagementComponent implements OnInit, OnDestroy { }); this.updateOrderSubject = new Subject(); - this.activatedRoute.parent!.params.subscribe((params) => { - this.lectureId ??= +params['lectureId']; - if (this.lectureId) { - // TODO: the lecture (without units) is already available through the lecture.route.ts resolver, it's not really good that we load it twice - this.loadData(); - } - }); + if (this.activatedRoute?.parent?.params) { + this.activatedRoute.parent.params.subscribe((params) => { + this.lectureId ??= +params['lectureId']; + if (this.lectureId) { + // TODO: the lecture (without units) is already available through the lecture.route.ts resolver, it's not really good that we load it twice + this.loadData(); + } + }); + } // debounceTime limits the amount of put requests sent for updating the lecture unit order this.updateOrderSubjectSubscription = this.updateOrderSubject.pipe(debounceTime(1000)).subscribe(() => { diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index d55a536ef717..dbe4c2b28a5a 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -21,7 +21,6 @@ import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-ti import { LectureAttachmentsComponent } from 'app/lecture/lecture-attachments.component'; import cloneDeep from 'lodash-es/cloneDeep'; import dayjs from 'dayjs'; -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; @Component({ selector: 'jhi-lecture-update', @@ -42,7 +41,6 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { private readonly activatedRoute = inject(ActivatedRoute); private readonly navigationUtilService = inject(ArtemisNavigationUtilService); private readonly router = inject(Router); - private readonly modalService = inject(NgbModal); @ViewChild(ProgrammingExerciseDifficultyComponent) lecturePeriodComponent?: LectureUpdateWizardPeriodComponent; titleSection = viewChild.required(LectureTitleChannelNameComponent); diff --git a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts index 34b9416a097f..868079cd081d 100644 --- a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts @@ -13,7 +13,7 @@ import { ArtemisDatePipe } from 'app/shared/pipes/artemis-date.pipe'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { HtmlForMarkdownPipe } from 'app/shared/pipes/html-for-markdown.pipe'; import dayjs from 'dayjs/esm'; -import { MockComponent, MockModule, MockPipe } from 'ng-mocks'; +import { MockComponent, MockDirective, MockModule, MockPipe } from 'ng-mocks'; import { of } from 'rxjs'; import { MockRouterLinkDirective } from '../../helpers/mocks/directive/mock-router-link.directive'; import { MockRouter } from '../../helpers/mocks/mock-router'; @@ -22,6 +22,16 @@ import { ArtemisTestModule } from '../../test.module'; import { DocumentationButtonComponent } from 'app/shared/components/documentation-button/documentation-button.component'; import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component'; import { MarkdownEditorMonacoComponent } from 'app/shared/markdown-editor/monaco/markdown-editor-monaco.component'; +import { LectureAttachmentsComponent } from '../../../../../main/webapp/app/lecture/lecture-attachments.component'; +import { LectureUpdateWizardUnitsComponent } from '../../../../../main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component'; +import { FormStatusBarComponent } from '../../../../../main/webapp/app/forms/form-status-bar/form-status-bar.component'; +import { LectureUpdateWizardPeriodComponent } from '../../../../../main/webapp/app/lecture/wizard-mode/lecture-wizard-period.component'; +import { TitleChannelNameComponent } from '../../../../../main/webapp/app/shared/form/title-channel-name/title-channel-name.component'; +import { OwlDateTimeModule } from '@danielmoncada/angular-datetime-picker'; +import { ArtemisSharedModule } from '../../../../../main/webapp/app/shared/shared.module'; +import { LectureUnitManagementComponent } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/lecture-unit-management.component'; +import { UnitCreationCardComponent } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/unit-creation-card/unit-creation-card.component'; +import { CustomNotIncludedInValidatorDirective } from '../../../../../main/webapp/app/shared/validators/custom-not-included-in-validator.directive'; describe('LectureUpdateComponent', () => { let lectureService: LectureService; @@ -41,17 +51,25 @@ describe('LectureUpdateComponent', () => { pastLecture.endDate = yesterday; TestBed.configureTestingModule({ - imports: [ArtemisTestModule, FormsModule, MockModule(NgbTooltipModule)], + imports: [ArtemisTestModule, ArtemisSharedModule, FormsModule, MockModule(NgbTooltipModule), MockModule(OwlDateTimeModule)], declarations: [ LectureUpdateComponent, - MockComponent(LectureTitleChannelNameComponent), - MockComponent(FormDateTimePickerComponent), + LectureUpdateWizardPeriodComponent, + LectureTitleChannelNameComponent, + TitleChannelNameComponent, + FormDateTimePickerComponent, + LectureAttachmentsComponent, + LectureUpdateWizardUnitsComponent, + LectureUnitManagementComponent, + MockComponent(FormStatusBarComponent), MockComponent(MarkdownEditorMonacoComponent), MockComponent(DocumentationButtonComponent), - MockPipe(ArtemisTranslatePipe), + ArtemisTranslatePipe, MockPipe(ArtemisDatePipe), MockPipe(HtmlForMarkdownPipe), MockRouterLinkDirective, + UnitCreationCardComponent, + MockDirective(CustomNotIncludedInValidatorDirective), ], providers: [ { provide: TranslateService, useClass: MockTranslateService }, @@ -89,6 +107,22 @@ describe('LectureUpdateComponent', () => { jest.restoreAllMocks(); }); + it('should initialize', () => { + // lectureUpdateComponent.ngOnInit(); + lectureUpdateComponentFixture.detectChanges(); + + expect(lectureUpdateComponent.isSaving).toBeFalse(); + expect(lectureUpdateComponent.processUnitMode).toBeFalse(); + expect(lectureUpdateComponent.isProcessing).toBeFalse(); + expect(lectureUpdateComponent.lecture().id).toBeUndefined(); + expect(lectureUpdateComponent.lecture().title).toBeUndefined(); + expect(lectureUpdateComponent.lecture().channelName).toBeUndefined(); + expect(lectureUpdateComponent.lecture().description).toBeUndefined(); + expect(lectureUpdateComponent.lecture().visibleDate).toBeUndefined(); + expect(lectureUpdateComponent.lecture().startDate).toBeUndefined(); + expect(lectureUpdateComponent.lecture().endDate).toBeUndefined(); + }); + it('should create lecture', fakeAsync(() => { lectureUpdateComponent.lecture.set({ title: 'test1', channelName: 'test1' } as Lecture); const navigateSpy = jest.spyOn(router, 'navigate'); From 033b00f3dd1653b8f1e2ba0845a1715121417d9d Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Fri, 6 Dec 2024 01:25:33 +0100 Subject: [PATCH 083/125] Mock more components in tests --- .../component/lecture/lecture-update.component.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts index 868079cd081d..62771c04c650 100644 --- a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts @@ -54,21 +54,21 @@ describe('LectureUpdateComponent', () => { imports: [ArtemisTestModule, ArtemisSharedModule, FormsModule, MockModule(NgbTooltipModule), MockModule(OwlDateTimeModule)], declarations: [ LectureUpdateComponent, - LectureUpdateWizardPeriodComponent, LectureTitleChannelNameComponent, TitleChannelNameComponent, FormDateTimePickerComponent, LectureAttachmentsComponent, LectureUpdateWizardUnitsComponent, - LectureUnitManagementComponent, + MockComponent(LectureUpdateWizardPeriodComponent), + MockComponent(LectureUnitManagementComponent), MockComponent(FormStatusBarComponent), MockComponent(MarkdownEditorMonacoComponent), MockComponent(DocumentationButtonComponent), - ArtemisTranslatePipe, + MockPipe(ArtemisTranslatePipe), MockPipe(ArtemisDatePipe), MockPipe(HtmlForMarkdownPipe), MockRouterLinkDirective, - UnitCreationCardComponent, + MockComponent(UnitCreationCardComponent), MockDirective(CustomNotIncludedInValidatorDirective), ], providers: [ From af98ddba29642b9e885570b043a8c0756cedda14 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Fri, 6 Dec 2024 01:47:03 +0100 Subject: [PATCH 084/125] Fix client test pre build --- .../lecture/lecture-update.component.spec.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts index 62771c04c650..9229be3dd560 100644 --- a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts @@ -301,7 +301,17 @@ describe('LectureUpdateComponent', () => { lectureUpdateComponent.onDatesValuesChanged(); expect(setDatesSpy).toHaveBeenCalledTimes(3); - expect(lectureUpdateComponent.lecture().visibleDate?.toDate()).toBeBefore(lectureUpdateComponent.lecture().startDate?.toDate()); - expect(lectureUpdateComponent.lecture().startDate?.toDate()).toBeBefore(lectureUpdateComponent.lecture().endDate?.toDate()); + + if (lectureUpdateComponent.lecture().visibleDate && lectureUpdateComponent.lecture().startDate) { + expect(lectureUpdateComponent.lecture().visibleDate!.toDate()).toBeBefore(lectureUpdateComponent.lecture().startDate!.toDate()); + } else { + throw new Error('visibleDate and startDate should not be undefined'); + } + + if (lectureUpdateComponent.lecture().startDate && lectureUpdateComponent.lecture().endDate) { + expect(lectureUpdateComponent.lecture().startDate!.toDate()).toBeBefore(lectureUpdateComponent.lecture().endDate!.toDate()); + } else { + throw new Error('startDate and endDate should not be undefined'); + } })); }); From d774475e4637c38dbb55aa02c780d92e91732677 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Fri, 13 Dec 2024 23:56:42 +0100 Subject: [PATCH 085/125] Fix client test --- .../lecture/lecture-update.component.spec.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts index 9229be3dd560..5c86e4b2c130 100644 --- a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts @@ -123,9 +123,8 @@ describe('LectureUpdateComponent', () => { expect(lectureUpdateComponent.lecture().endDate).toBeUndefined(); }); - it('should create lecture', fakeAsync(() => { + it('should create lecture', () => { lectureUpdateComponent.lecture.set({ title: 'test1', channelName: 'test1' } as Lecture); - const navigateSpy = jest.spyOn(router, 'navigate'); const createSpy = jest.spyOn(lectureService, 'create').mockReturnValue( of( @@ -142,18 +141,15 @@ describe('LectureUpdateComponent', () => { ); lectureUpdateComponent.save(); - tick(); lectureUpdateComponentFixture.detectChanges(); - const expectedPath = ['course-management', 1, 'lectures', 3]; - expect(navigateSpy).toHaveBeenCalledWith(expectedPath); - expect(createSpy).toHaveBeenCalledOnce(); expect(createSpy).toHaveBeenCalledWith({ title: 'test1', channelName: 'test1' }); - })); + }); it('should edit a lecture', fakeAsync(() => { activatedRoute.parent!.data = of({ course: { id: 1 }, lecture: { id: 6 } }); + const navigateSpy = jest.spyOn(router, 'navigate'); lectureUpdateComponentFixture.detectChanges(); lectureUpdateComponent.lecture.set({ id: 6, title: 'test1Updated', channelName: 'test1Updated' } as Lecture); @@ -176,6 +172,9 @@ describe('LectureUpdateComponent', () => { tick(); lectureUpdateComponentFixture.detectChanges(); + const expectedPath = ['course-management', 1, 'lectures', 6]; + expect(navigateSpy).toHaveBeenCalledWith(expectedPath); + expect(updateSpy).toHaveBeenCalledOnce(); expect(updateSpy).toHaveBeenCalledWith({ id: 6, title: 'test1Updated', channelName: 'test1Updated' }); })); From e370861941c263d3133a078816309e78a99699b9 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Fri, 13 Dec 2024 23:58:08 +0100 Subject: [PATCH 086/125] Fix client test --- .../spec/component/lecture/lecture-update.component.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts index 5c86e4b2c130..b6dcd533685b 100644 --- a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts @@ -239,7 +239,7 @@ describe('LectureUpdateComponent', () => { expect(navigateSpy).toHaveBeenCalledWith(expectedPath, { state: { file: lectureUpdateComponent.file, fileName: lectureUpdateComponent.fileName } }); })); - it('should call onFileChange on changed file', fakeAsync(() => { + it('should call onFileChange on changed file', () => { lectureUpdateComponent.processUnitMode = false; lectureUpdateComponentFixture.detectChanges(); expect(lectureUpdateComponentFixture.debugElement.nativeElement.querySelector('#fileInput')).toBeFalsy(); @@ -255,7 +255,7 @@ describe('LectureUpdateComponent', () => { expect(lectureUpdateComponentFixture.debugElement.nativeElement.querySelector('#fileInput')).toBeTruthy(); fileInput.dispatchEvent(new Event('change')); expect(onFileChangeStub).toHaveBeenCalledOnce(); - })); + }); it('should set lecture visible date, start date and end date correctly', fakeAsync(() => { activatedRoute = TestBed.inject(ActivatedRoute); From b673ada812a0418e923bb81166ae40d5a42a13d6 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 00:02:48 +0100 Subject: [PATCH 087/125] Remove unused component --- src/main/webapp/app/lecture/lecture.module.ts | 2 -- .../lecture-wizard-title.component.html | 11 ------ .../lecture-wizard-title.component.ts | 14 -------- .../lecture-wizard-title.component.spec.ts | 36 ------------------- 4 files changed, 63 deletions(-) delete mode 100644 src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.html delete mode 100644 src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts delete mode 100644 src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts diff --git a/src/main/webapp/app/lecture/lecture.module.ts b/src/main/webapp/app/lecture/lecture.module.ts index eafc90e0b6bb..74e484325d01 100644 --- a/src/main/webapp/app/lecture/lecture.module.ts +++ b/src/main/webapp/app/lecture/lecture.module.ts @@ -13,7 +13,6 @@ import { ArtemisLectureUnitManagementModule } from 'app/lecture/lecture-unit/lec import { ArtemisMarkdownModule } from 'app/shared/markdown.module'; import { LectureImportComponent } from 'app/lecture/lecture-import.component'; import { ArtemisCompetenciesModule } from 'app/course/competencies/competency.module'; -import { LectureUpdateWizardTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component'; import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; import { LectureUpdateWizardAttachmentsComponent } from 'app/lecture/wizard-mode/lecture-wizard-attachments.component'; import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; @@ -46,7 +45,6 @@ const ENTITY_STATES = [...lectureRoute]; LectureImportComponent, LectureUpdateComponent, LectureAttachmentsComponent, - LectureUpdateWizardTitleComponent, LectureUpdateWizardPeriodComponent, LectureUpdateWizardAttachmentsComponent, LectureUpdateWizardUnitsComponent, diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.html b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.html deleted file mode 100644 index 9100d8436184..000000000000 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.html +++ /dev/null @@ -1,11 +0,0 @@ -
-

-

- - - -
- - -
-
diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts deleted file mode 100644 index e354df72e629..000000000000 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-title.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { Lecture } from 'app/entities/lecture.model'; -import { FormulaAction } from 'app/shared/monaco-editor/model/actions/formula.action'; - -@Component({ - selector: 'jhi-lecture-update-wizard-title', - templateUrl: './lecture-wizard-title.component.html', -}) -export class LectureUpdateWizardTitleComponent { - @Input() currentStep: number; - @Input() lecture: Lecture; - - domainActionsDescription = [new FormulaAction()]; -} diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts deleted file mode 100644 index 36701004edbb..000000000000 --- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { LectureUpdateWizardTitleComponent } from 'app/lecture/wizard-mode/lecture-wizard-title.component'; -import { Lecture } from 'app/entities/lecture.model'; -import { MockComponent } from 'ng-mocks'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component'; -import { MarkdownEditorMonacoComponent } from 'app/shared/markdown-editor/monaco/markdown-editor-monaco.component'; - -describe('LectureWizardTitleComponent', () => { - let wizardTitleComponentFixture: ComponentFixture; - let wizardTitleComponent: LectureUpdateWizardTitleComponent; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ReactiveFormsModule, FormsModule], - declarations: [LectureUpdateWizardTitleComponent, MockComponent(MarkdownEditorMonacoComponent), MockComponent(LectureTitleChannelNameComponent)], - providers: [], - schemas: [], - }) - .compileComponents() - .then(() => { - wizardTitleComponentFixture = TestBed.createComponent(LectureUpdateWizardTitleComponent); - wizardTitleComponent = wizardTitleComponentFixture.componentInstance; - wizardTitleComponent.lecture = new Lecture(); - }); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it('should initialize', () => { - wizardTitleComponentFixture.detectChanges(); - expect(wizardTitleComponent).not.toBeNull(); - }); -}); From a1970b5f8dc29d99e9a81d3517d6842282e8d752 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 00:06:35 +0100 Subject: [PATCH 088/125] Remove unused wizard attachment component --- src/main/webapp/app/lecture/lecture.module.ts | 2 -- .../lecture-wizard-attachments.component.html | 7 ---- .../lecture-wizard-attachments.component.ts | 13 ------- ...cture-wizard-attachments.component.spec.ts | 34 ------------------- 4 files changed, 56 deletions(-) delete mode 100644 src/main/webapp/app/lecture/wizard-mode/lecture-wizard-attachments.component.html delete mode 100644 src/main/webapp/app/lecture/wizard-mode/lecture-wizard-attachments.component.ts delete mode 100644 src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-attachments.component.spec.ts diff --git a/src/main/webapp/app/lecture/lecture.module.ts b/src/main/webapp/app/lecture/lecture.module.ts index 74e484325d01..acdbb1db9501 100644 --- a/src/main/webapp/app/lecture/lecture.module.ts +++ b/src/main/webapp/app/lecture/lecture.module.ts @@ -14,7 +14,6 @@ import { ArtemisMarkdownModule } from 'app/shared/markdown.module'; import { LectureImportComponent } from 'app/lecture/lecture-import.component'; import { ArtemisCompetenciesModule } from 'app/course/competencies/competency.module'; import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; -import { LectureUpdateWizardAttachmentsComponent } from 'app/lecture/wizard-mode/lecture-wizard-attachments.component'; import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; import { TitleChannelNameModule } from 'app/shared/form/title-channel-name/title-channel-name.module'; import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component'; @@ -46,7 +45,6 @@ const ENTITY_STATES = [...lectureRoute]; LectureUpdateComponent, LectureAttachmentsComponent, LectureUpdateWizardPeriodComponent, - LectureUpdateWizardAttachmentsComponent, LectureUpdateWizardUnitsComponent, LectureTitleChannelNameComponent, ], diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-attachments.component.html b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-attachments.component.html deleted file mode 100644 index 5b58c99bb28f..000000000000 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-attachments.component.html +++ /dev/null @@ -1,7 +0,0 @@ -
-

-

- @if (currentStep >= 3) { - - } -
diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-attachments.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-attachments.component.ts deleted file mode 100644 index 867ad557e393..000000000000 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-attachments.component.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { Lecture } from 'app/entities/lecture.model'; - -@Component({ - selector: 'jhi-lecture-update-wizard-attachments', - templateUrl: './lecture-wizard-attachments.component.html', -}) -export class LectureUpdateWizardAttachmentsComponent { - @Input() currentStep: number; - @Input() lecture: Lecture; - - constructor() {} -} diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-attachments.component.spec.ts b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-attachments.component.spec.ts deleted file mode 100644 index 0e3f9909ad52..000000000000 --- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-attachments.component.spec.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MockPipe } from 'ng-mocks'; -import { Lecture } from 'app/entities/lecture.model'; -import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; -import { LectureUpdateWizardAttachmentsComponent } from 'app/lecture/wizard-mode/lecture-wizard-attachments.component'; - -describe('LectureWizardAttachmentsComponent', () => { - let wizardAttachmentsComponentFixture: ComponentFixture; - let wizardAttachmentsComponent: LectureUpdateWizardAttachmentsComponent; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [], - declarations: [LectureUpdateWizardAttachmentsComponent, MockPipe(ArtemisTranslatePipe)], - providers: [], - schemas: [], - }) - .compileComponents() - .then(() => { - wizardAttachmentsComponentFixture = TestBed.createComponent(LectureUpdateWizardAttachmentsComponent); - wizardAttachmentsComponent = wizardAttachmentsComponentFixture.componentInstance; - wizardAttachmentsComponent.lecture = new Lecture(); - }); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it('should initialize', () => { - wizardAttachmentsComponentFixture.detectChanges(); - expect(wizardAttachmentsComponent).not.toBeNull(); - }); -}); From c7aecf2eb9c1cbde74de8b53a3d8c800f084d91d Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 00:14:29 +0100 Subject: [PATCH 089/125] Rename lecture-units.component.ts (get rid of outdated "wizard" description) --- src/main/webapp/app/lecture/lecture-update.component.html | 2 +- src/main/webapp/app/lecture/lecture-update.component.ts | 4 ++-- src/main/webapp/app/lecture/lecture.module.ts | 4 ++-- ...rd-units.component.html => lecture-units.component.html} | 0 ...wizard-units.component.ts => lecture-units.component.ts} | 6 +++--- 5 files changed, 8 insertions(+), 8 deletions(-) rename src/main/webapp/app/lecture/wizard-mode/{lecture-wizard-units.component.html => lecture-units.component.html} (100%) rename src/main/webapp/app/lecture/wizard-mode/{lecture-wizard-units.component.ts => lecture-units.component.ts} (98%) diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index 77fe0e2d6903..fa8b21f39ffb 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -102,7 +102,7 @@

@if (isEditMode()) {
- +
diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index dbe4c2b28a5a..6505fe3c4876 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -16,11 +16,11 @@ import { ProgrammingExerciseDifficultyComponent } from 'app/exercises/programmin import { FormSectionStatus, FormStatusBarComponent } from 'app/forms/form-status-bar/form-status-bar.component'; import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component'; -import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; import { LectureAttachmentsComponent } from 'app/lecture/lecture-attachments.component'; import cloneDeep from 'lodash-es/cloneDeep'; import dayjs from 'dayjs'; +import { LectureUpdateUnitsComponent } from 'app/lecture/wizard-mode/lecture-units.component'; @Component({ selector: 'jhi-lecture-update', @@ -46,7 +46,7 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { titleSection = viewChild.required(LectureTitleChannelNameComponent); periodSectionDatepickers = viewChildren(FormDateTimePickerComponent); attachmentsSection = viewChild(LectureAttachmentsComponent); - unitSection = viewChild(LectureUpdateWizardUnitsComponent); + unitSection = viewChild(LectureUpdateUnitsComponent); formStatusBar = viewChild(FormStatusBarComponent); isPeriodSectionValid: Signal = computed(() => { diff --git a/src/main/webapp/app/lecture/lecture.module.ts b/src/main/webapp/app/lecture/lecture.module.ts index acdbb1db9501..cecdad974a08 100644 --- a/src/main/webapp/app/lecture/lecture.module.ts +++ b/src/main/webapp/app/lecture/lecture.module.ts @@ -14,12 +14,12 @@ import { ArtemisMarkdownModule } from 'app/shared/markdown.module'; import { LectureImportComponent } from 'app/lecture/lecture-import.component'; import { ArtemisCompetenciesModule } from 'app/course/competencies/competency.module'; import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; -import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; import { TitleChannelNameModule } from 'app/shared/form/title-channel-name/title-channel-name.module'; import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component'; import { DetailModule } from 'app/detail-overview-list/detail.module'; import { CompetencyFormComponent } from 'app/course/competencies/forms/competency/competency-form.component'; import { FormsModule } from 'app/forms/forms.module'; +import { LectureUpdateUnitsComponent } from 'app/lecture/wizard-mode/lecture-units.component'; const ENTITY_STATES = [...lectureRoute]; @@ -45,8 +45,8 @@ const ENTITY_STATES = [...lectureRoute]; LectureUpdateComponent, LectureAttachmentsComponent, LectureUpdateWizardPeriodComponent, - LectureUpdateWizardUnitsComponent, LectureTitleChannelNameComponent, + LectureUpdateUnitsComponent, ], }) export class ArtemisLectureModule {} diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html b/src/main/webapp/app/lecture/wizard-mode/lecture-units.component.html similarity index 100% rename from src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.html rename to src/main/webapp/app/lecture/wizard-mode/lecture-units.component.html diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-units.component.ts similarity index 98% rename from src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts rename to src/main/webapp/app/lecture/wizard-mode/lecture-units.component.ts index 4ba2640d8fc9..d9c11741275a 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component.ts +++ b/src/main/webapp/app/lecture/wizard-mode/lecture-units.component.ts @@ -23,10 +23,10 @@ import dayjs from 'dayjs/esm'; import { ActivatedRoute } from '@angular/router'; @Component({ - selector: 'jhi-lecture-update-wizard-units', - templateUrl: './lecture-wizard-units.component.html', + selector: 'jhi-lecture-update-units', + templateUrl: './lecture-units.component.html', }) -export class LectureUpdateWizardUnitsComponent implements OnInit { +export class LectureUpdateUnitsComponent implements OnInit { @Input() lecture: Lecture; @ViewChild(LectureUnitManagementComponent, { static: false }) unitManagementComponent: LectureUnitManagementComponent; From 73ab801937c49f7820338bd6788bd085c079dada Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 00:47:57 +0100 Subject: [PATCH 090/125] Adjust folder structure, remove duplicated code and move code to lecture-period component instead, fix submit button validation for create lecture --- .../lecture-period.component.html} | 8 ++-- .../lecture-period.component.ts | 23 +++++++++++ .../lecture-units.component.html | 0 .../lecture-units.component.ts | 0 .../app/lecture/lecture-update.component.html | 41 +++---------------- .../app/lecture/lecture-update.component.ts | 34 ++++++--------- src/main/webapp/app/lecture/lecture.module.ts | 6 +-- .../lecture-wizard-period.component.ts | 12 ------ .../lecture/lecture-update.component.spec.ts | 8 ++-- .../lecture-wizard-period.component.spec.ts | 13 +++--- 10 files changed, 58 insertions(+), 87 deletions(-) rename src/main/webapp/app/lecture/{wizard-mode/lecture-wizard-period.component.html => lecture-period/lecture-period.component.html} (78%) create mode 100644 src/main/webapp/app/lecture/lecture-period/lecture-period.component.ts rename src/main/webapp/app/lecture/{wizard-mode => lecture-units}/lecture-units.component.html (100%) rename src/main/webapp/app/lecture/{wizard-mode => lecture-units}/lecture-units.component.ts (100%) delete mode 100644 src/main/webapp/app/lecture/wizard-mode/lecture-wizard-period.component.ts diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-period.component.html b/src/main/webapp/app/lecture/lecture-period/lecture-period.component.html similarity index 78% rename from src/main/webapp/app/lecture/wizard-mode/lecture-wizard-period.component.html rename to src/main/webapp/app/lecture/lecture-period/lecture-period.component.html index 7e205ffa58f9..5434a21491a2 100644 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-period.component.html +++ b/src/main/webapp/app/lecture/lecture-period/lecture-period.component.html @@ -1,11 +1,11 @@
-

+

(); + @Input() validateDatesFunction: () => void; + + periodSectionDatepickers = viewChildren(FormDateTimePickerComponent); + + isPeriodSectionValid: Signal = computed(() => { + for (const periodSectionDatepicker of this.periodSectionDatepickers()) { + if (!periodSectionDatepicker.isValid()) { + return false; + } + } + return true; + }); +} diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-units.component.html b/src/main/webapp/app/lecture/lecture-units/lecture-units.component.html similarity index 100% rename from src/main/webapp/app/lecture/wizard-mode/lecture-units.component.html rename to src/main/webapp/app/lecture/lecture-units/lecture-units.component.html diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-units.component.ts b/src/main/webapp/app/lecture/lecture-units/lecture-units.component.ts similarity index 100% rename from src/main/webapp/app/lecture/wizard-mode/lecture-units.component.ts rename to src/main/webapp/app/lecture/lecture-units/lecture-units.component.ts diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index fa8b21f39ffb..dbb189be3314 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -37,39 +37,7 @@

-

-
-
- -
-
- -
-
- -
-
-
+
diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 6505fe3c4876..1bdda0a8803f 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy, OnInit, Signal, ViewChild, computed, effect, inject, signal, viewChild, viewChildren } from '@angular/core'; +import { Component, OnDestroy, OnInit, effect, inject, signal, viewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; import { Observable, Subscription } from 'rxjs'; @@ -12,15 +12,13 @@ import { DocumentationType } from 'app/shared/components/documentation-button/do import { faBan, faPuzzlePiece, faQuestionCircle, faSave } from '@fortawesome/free-solid-svg-icons'; import { ACCEPTED_FILE_EXTENSIONS_FILE_BROWSER, ALLOWED_FILE_EXTENSIONS_HUMAN_READABLE } from 'app/shared/constants/file-extensions.constants'; import { FormulaAction } from 'app/shared/monaco-editor/model/actions/formula.action'; -import { ProgrammingExerciseDifficultyComponent } from 'app/exercises/programming/manage/update/update-components/difficulty/programming-exercise-difficulty.component'; import { FormSectionStatus, FormStatusBarComponent } from 'app/forms/form-status-bar/form-status-bar.component'; -import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component'; -import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; import { LectureAttachmentsComponent } from 'app/lecture/lecture-attachments.component'; import cloneDeep from 'lodash-es/cloneDeep'; import dayjs from 'dayjs'; -import { LectureUpdateUnitsComponent } from 'app/lecture/wizard-mode/lecture-units.component'; +import { LectureUpdateUnitsComponent } from 'app/lecture/lecture-units/lecture-units.component'; +import { LectureUpdatePeriodComponent } from 'app/lecture/lecture-period/lecture-period.component'; @Component({ selector: 'jhi-lecture-update', @@ -42,22 +40,12 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { private readonly navigationUtilService = inject(ArtemisNavigationUtilService); private readonly router = inject(Router); - @ViewChild(ProgrammingExerciseDifficultyComponent) lecturePeriodComponent?: LectureUpdateWizardPeriodComponent; titleSection = viewChild.required(LectureTitleChannelNameComponent); - periodSectionDatepickers = viewChildren(FormDateTimePickerComponent); + lecturePeriodSection = viewChild.required(LectureUpdatePeriodComponent); attachmentsSection = viewChild(LectureAttachmentsComponent); unitSection = viewChild(LectureUpdateUnitsComponent); formStatusBar = viewChild(FormStatusBarComponent); - isPeriodSectionValid: Signal = computed(() => { - for (const periodSectionDatepicker of this.periodSectionDatepickers()) { - if (!periodSectionDatepicker.isValid()) { - return false; - } - } - return true; - }); - isEditMode = signal(false); pressedSave: boolean = false; lecture = signal(new Lecture()); @@ -99,11 +87,13 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { }), ); this.subscriptions.add( - this.periodSectionDatepickers().forEach((datepicker) => { - datepicker.valueChange.subscribe(() => { - this.updateIsChangesMadeToTitleOrPeriodSection(); - }); - }), + this.lecturePeriodSection() + .periodSectionDatepickers() + .forEach((datepicker) => { + datepicker.valueChange.subscribe(() => { + this.updateIsChangesMadeToTitleOrPeriodSection(); + }); + }), ); }); @@ -131,7 +121,7 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { }, { title: 'artemisApp.lecture.wizardMode.steps.periodStepTitle', - valid: Boolean(this.isPeriodSectionValid()), + valid: Boolean(this.lecturePeriodSection().isPeriodSectionValid()), }, ); diff --git a/src/main/webapp/app/lecture/lecture.module.ts b/src/main/webapp/app/lecture/lecture.module.ts index cecdad974a08..83afd92a0e56 100644 --- a/src/main/webapp/app/lecture/lecture.module.ts +++ b/src/main/webapp/app/lecture/lecture.module.ts @@ -13,13 +13,13 @@ import { ArtemisLectureUnitManagementModule } from 'app/lecture/lecture-unit/lec import { ArtemisMarkdownModule } from 'app/shared/markdown.module'; import { LectureImportComponent } from 'app/lecture/lecture-import.component'; import { ArtemisCompetenciesModule } from 'app/course/competencies/competency.module'; -import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; import { TitleChannelNameModule } from 'app/shared/form/title-channel-name/title-channel-name.module'; import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component'; import { DetailModule } from 'app/detail-overview-list/detail.module'; import { CompetencyFormComponent } from 'app/course/competencies/forms/competency/competency-form.component'; import { FormsModule } from 'app/forms/forms.module'; -import { LectureUpdateUnitsComponent } from 'app/lecture/wizard-mode/lecture-units.component'; +import { LectureUpdateUnitsComponent } from 'app/lecture/lecture-units/lecture-units.component'; +import { LectureUpdatePeriodComponent } from 'app/lecture/lecture-period/lecture-period.component'; const ENTITY_STATES = [...lectureRoute]; @@ -44,9 +44,9 @@ const ENTITY_STATES = [...lectureRoute]; LectureImportComponent, LectureUpdateComponent, LectureAttachmentsComponent, - LectureUpdateWizardPeriodComponent, LectureTitleChannelNameComponent, LectureUpdateUnitsComponent, + LectureUpdatePeriodComponent, ], }) export class ArtemisLectureModule {} diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-period.component.ts b/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-period.component.ts deleted file mode 100644 index 7d9b48571621..000000000000 --- a/src/main/webapp/app/lecture/wizard-mode/lecture-wizard-period.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, Input } from '@angular/core'; -import { Lecture } from 'app/entities/lecture.model'; - -@Component({ - selector: 'jhi-lecture-update-wizard-period', - templateUrl: './lecture-wizard-period.component.html', -}) -export class LectureUpdateWizardPeriodComponent { - @Input() currentStep: number; - @Input() lecture: Lecture; - @Input() validateDatesFunction: () => void; -} diff --git a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts index b6dcd533685b..3aec6bce117b 100644 --- a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts @@ -23,15 +23,15 @@ import { DocumentationButtonComponent } from 'app/shared/components/documentatio import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component'; import { MarkdownEditorMonacoComponent } from 'app/shared/markdown-editor/monaco/markdown-editor-monaco.component'; import { LectureAttachmentsComponent } from '../../../../../main/webapp/app/lecture/lecture-attachments.component'; -import { LectureUpdateWizardUnitsComponent } from '../../../../../main/webapp/app/lecture/wizard-mode/lecture-wizard-units.component'; import { FormStatusBarComponent } from '../../../../../main/webapp/app/forms/form-status-bar/form-status-bar.component'; -import { LectureUpdateWizardPeriodComponent } from '../../../../../main/webapp/app/lecture/wizard-mode/lecture-wizard-period.component'; import { TitleChannelNameComponent } from '../../../../../main/webapp/app/shared/form/title-channel-name/title-channel-name.component'; import { OwlDateTimeModule } from '@danielmoncada/angular-datetime-picker'; import { ArtemisSharedModule } from '../../../../../main/webapp/app/shared/shared.module'; import { LectureUnitManagementComponent } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/lecture-unit-management.component'; import { UnitCreationCardComponent } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/unit-creation-card/unit-creation-card.component'; import { CustomNotIncludedInValidatorDirective } from '../../../../../main/webapp/app/shared/validators/custom-not-included-in-validator.directive'; +import { LectureUpdatePeriodComponent } from 'app/lecture/lecture-period/lecture-period.component'; +import { LectureUpdateUnitsComponent } from '../../../../../main/webapp/app/lecture/lecture-units/lecture-units.component'; describe('LectureUpdateComponent', () => { let lectureService: LectureService; @@ -58,8 +58,8 @@ describe('LectureUpdateComponent', () => { TitleChannelNameComponent, FormDateTimePickerComponent, LectureAttachmentsComponent, - LectureUpdateWizardUnitsComponent, - MockComponent(LectureUpdateWizardPeriodComponent), + LectureUpdateUnitsComponent, + MockComponent(LectureUpdatePeriodComponent), MockComponent(LectureUnitManagementComponent), MockComponent(FormStatusBarComponent), MockComponent(MarkdownEditorMonacoComponent), diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts index f06067251d59..37ba355bc37c 100644 --- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts @@ -2,26 +2,27 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; import { MockComponent, MockPipe } from 'ng-mocks'; import { Lecture } from 'app/entities/lecture.model'; -import { LectureUpdateWizardPeriodComponent } from 'app/lecture/wizard-mode/lecture-wizard-period.component'; import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { LectureUpdatePeriodComponent } from '../../../../../../main/webapp/app/lecture/lecture-period/lecture-period.component'; describe('LectureWizardPeriodComponent', () => { - let wizardPeriodComponentFixture: ComponentFixture; - let wizardPeriodComponent: LectureUpdateWizardPeriodComponent; + let wizardPeriodComponentFixture: ComponentFixture; + let wizardPeriodComponent: LectureUpdatePeriodComponent; beforeEach(() => { TestBed.configureTestingModule({ imports: [ReactiveFormsModule, FormsModule], - declarations: [LectureUpdateWizardPeriodComponent, MockPipe(ArtemisTranslatePipe), MockComponent(FormDateTimePickerComponent)], + declarations: [LectureUpdatePeriodComponent, MockPipe(ArtemisTranslatePipe), MockComponent(FormDateTimePickerComponent)], providers: [], schemas: [], }) .compileComponents() .then(() => { - wizardPeriodComponentFixture = TestBed.createComponent(LectureUpdateWizardPeriodComponent); + wizardPeriodComponentFixture = TestBed.createComponent(LectureUpdatePeriodComponent); wizardPeriodComponent = wizardPeriodComponentFixture.componentInstance; - wizardPeriodComponent.lecture = new Lecture(); + + wizardPeriodComponentFixture.componentRef.setInput('lecture', new Lecture()); }); }); From 223642b084d35e2b215a2d74ce4093cedb2f1575 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 00:50:25 +0100 Subject: [PATCH 091/125] Fix lecture update test --- .../spec/component/lecture/lecture-update.component.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts index 3aec6bce117b..92f62be41b4c 100644 --- a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts @@ -59,7 +59,7 @@ describe('LectureUpdateComponent', () => { FormDateTimePickerComponent, LectureAttachmentsComponent, LectureUpdateUnitsComponent, - MockComponent(LectureUpdatePeriodComponent), + LectureUpdatePeriodComponent, MockComponent(LectureUnitManagementComponent), MockComponent(FormStatusBarComponent), MockComponent(MarkdownEditorMonacoComponent), From 651b4e3c69df85cad478e9e43d45626270a58b5e Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 00:58:10 +0100 Subject: [PATCH 092/125] Fix dismiss warning popping up on save --- src/main/webapp/app/lecture/lecture-update.component.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 1bdda0a8803f..be39311a52ba 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -197,6 +197,7 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { * This function is called by pressing save after creating or editing a lecture */ save() { + this.shouldDisplayDismissWarning = false; this.pressedSave = true; this.isSaving = true; this.isProcessing = true; @@ -261,6 +262,7 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { this.isEditMode.set(true); this.lecture.set(lecture); window.history.replaceState({}, '', `course-management/${lecture.course!.id}/lectures/${lecture.id}/edit`); + this.shouldDisplayDismissWarning = true; } } From b4f6c23596f2a8e811ef4c77ac4c27c8228c4e2e Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 01:04:24 +0100 Subject: [PATCH 093/125] Fix client tests --- ...ec.ts => lecture-period.component.spec.ts} | 8 +-- ...pec.ts => lecture-units.component.spec.ts} | 54 +++++++++---------- 2 files changed, 31 insertions(+), 31 deletions(-) rename src/test/javascript/spec/component/lecture/{wizard-mode/lecture-wizard-period.component.spec.ts => lecture-period.component.spec.ts} (74%) rename src/test/javascript/spec/component/lecture/{wizard-mode/lecture-wizard-units.component.spec.ts => lecture-units.component.spec.ts} (91%) diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-period.component.spec.ts similarity index 74% rename from src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts rename to src/test/javascript/spec/component/lecture/lecture-period.component.spec.ts index 37ba355bc37c..1f6309915829 100644 --- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-period.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-period.component.spec.ts @@ -1,10 +1,10 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { FormDateTimePickerComponent } from 'app/shared/date-time-picker/date-time-picker.component'; +import { FormDateTimePickerComponent } from '../../../../../main/webapp/app/shared/date-time-picker/date-time-picker.component'; import { MockComponent, MockPipe } from 'ng-mocks'; -import { Lecture } from 'app/entities/lecture.model'; -import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe'; +import { Lecture } from '../../../../../main/webapp/app/entities/lecture.model'; +import { ArtemisTranslatePipe } from '../../../../../main/webapp/app/shared/pipes/artemis-translate.pipe'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { LectureUpdatePeriodComponent } from '../../../../../../main/webapp/app/lecture/lecture-period/lecture-period.component'; +import { LectureUpdatePeriodComponent } from '../../../../../main/webapp/app/lecture/lecture-period/lecture-period.component'; describe('LectureWizardPeriodComponent', () => { let wizardPeriodComponentFixture: ComponentFixture; diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-units.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-units.component.spec.ts similarity index 91% rename from src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-units.component.spec.ts rename to src/test/javascript/spec/component/lecture/lecture-units.component.spec.ts index c850a83f6ecc..9984850ff37a 100644 --- a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-units.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-units.component.spec.ts @@ -1,33 +1,33 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { VideoUnitFormData } from 'app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component'; +import { VideoUnitFormData } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component'; import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { VideoUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/videoUnit.service'; +import { VideoUnitService } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/videoUnit.service'; import { MockComponent, MockProvider } from 'ng-mocks'; -import { AlertService } from 'app/core/util/alert.service'; +import { AlertService } from '../../../../../main/webapp/app/core/util/alert.service'; import { ActivatedRoute, Router } from '@angular/router'; -import { MockRouter } from '../../../helpers/mocks/mock-router'; +import { MockRouter } from '../../helpers/mocks/mock-router'; import { of, throwError } from 'rxjs'; -import { VideoUnit } from 'app/entities/lecture-unit/videoUnit.model'; +import { VideoUnit } from '../../../../../main/webapp/app/entities/lecture-unit/videoUnit.model'; import dayjs from 'dayjs/esm'; import { HttpResponse } from '@angular/common/http'; import { By } from '@angular/platform-browser'; -import { LectureUpdateWizardUnitsComponent } from 'app/lecture/wizard-mode/lecture-wizard-units.component'; -import { Lecture } from 'app/entities/lecture.model'; -import { TextUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/textUnit.service'; -import { OnlineUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/onlineUnit.service'; -import { AttachmentUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/attachmentUnit.service'; -import { LectureUnitType } from 'app/entities/lecture-unit/lectureUnit.model'; -import { LectureUnitManagementComponent } from 'app/lecture/lecture-unit/lecture-unit-management/lecture-unit-management.component'; -import { TextUnitFormData } from 'app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component'; -import { TextUnit } from 'app/entities/lecture-unit/textUnit.model'; -import { OnlineUnitFormData } from 'app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component'; -import { OnlineUnit } from 'app/entities/lecture-unit/onlineUnit.model'; -import { AttachmentUnitFormData } from 'app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component'; -import { Attachment, AttachmentType } from 'app/entities/attachment.model'; -import { AttachmentUnit } from 'app/entities/lecture-unit/attachmentUnit.model'; -import { objectToJsonBlob } from 'app/utils/blob-util'; -import { CreateExerciseUnitComponent } from 'app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component'; -import { CompetencyLectureUnitLink } from '../../../../../../main/webapp/app/entities/competency.model'; +import { Lecture } from '../../../../../main/webapp/app/entities/lecture.model'; +import { TextUnitService } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/textUnit.service'; +import { OnlineUnitService } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/onlineUnit.service'; +import { AttachmentUnitService } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachmentUnit.service'; +import { LectureUnitType } from '../../../../../main/webapp/app/entities/lecture-unit/lectureUnit.model'; +import { LectureUnitManagementComponent } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/lecture-unit-management.component'; +import { TextUnitFormData } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component'; +import { TextUnit } from '../../../../../main/webapp/app/entities/lecture-unit/textUnit.model'; +import { OnlineUnitFormData } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component'; +import { OnlineUnit } from '../../../../../main/webapp/app/entities/lecture-unit/onlineUnit.model'; +import { AttachmentUnitFormData } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component'; +import { Attachment, AttachmentType } from '../../../../../main/webapp/app/entities/attachment.model'; +import { AttachmentUnit } from '../../../../../main/webapp/app/entities/lecture-unit/attachmentUnit.model'; +import { objectToJsonBlob } from '../../../../../main/webapp/app/utils/blob-util'; +import { CreateExerciseUnitComponent } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component'; +import { CompetencyLectureUnitLink } from '../../../../../main/webapp/app/entities/competency.model'; +import { LectureUpdateUnitsComponent } from '../../../../../main/webapp/app/lecture/lecture-units/lecture-units.component'; @Component({ selector: 'jhi-video-unit-form', template: '' }) class VideoUnitFormStubComponent { @@ -41,9 +41,9 @@ class UnitCreationCardStubComponent { @Output() onUnitCreationCardClicked: EventEmitter = new EventEmitter(); } -describe('LectureWizardUnitComponent', () => { - let wizardUnitComponentFixture: ComponentFixture; - let wizardUnitComponent: LectureUpdateWizardUnitsComponent; +describe('LectureUpdateUnitsComponent', () => { + let wizardUnitComponentFixture: ComponentFixture; + let wizardUnitComponent: LectureUpdateUnitsComponent; beforeEach(() => { TestBed.configureTestingModule({ @@ -51,7 +51,7 @@ describe('LectureWizardUnitComponent', () => { declarations: [ VideoUnitFormStubComponent, UnitCreationCardStubComponent, - LectureUpdateWizardUnitsComponent, + LectureUpdateUnitsComponent, MockComponent(CreateExerciseUnitComponent), MockComponent(LectureUnitManagementComponent), ], @@ -72,7 +72,7 @@ describe('LectureWizardUnitComponent', () => { }) .compileComponents() .then(() => { - wizardUnitComponentFixture = TestBed.createComponent(LectureUpdateWizardUnitsComponent); + wizardUnitComponentFixture = TestBed.createComponent(LectureUpdateUnitsComponent); wizardUnitComponent = wizardUnitComponentFixture.componentInstance; wizardUnitComponent.lecture = new Lecture(); wizardUnitComponent.lecture.id = 1; From afde01eeba976fa29c92e19728f7de7ae47a628e Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 01:06:15 +0100 Subject: [PATCH 094/125] Remove unused translations --- src/main/webapp/i18n/de/lecture.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/webapp/i18n/de/lecture.json b/src/main/webapp/i18n/de/lecture.json index 1caf061634f8..378418fe5935 100644 --- a/src/main/webapp/i18n/de/lecture.json +++ b/src/main/webapp/i18n/de/lecture.json @@ -78,9 +78,7 @@ "attachmentsStepTitle": "Anhänge", "attachmentsStepMessage": "Lade Anhänge für die Vorlesung hoch.", "unitsStepTitle": "Vorlesungseinheiten", - "unitsStepMessage": "Füge Inhalte zur Vorlesung hinzu durch Erstellung von Vorlesungseinheiten.", - "competenciesStepTitle": "Kompetenzen", - "competenciesStepMessage": "Verknüpfe die Einheiten dieser Vorlesung mit Kompetenzen, um zu zeigen, welche Kompetenzen Studierende erreichen werden, wenn sie die Einheit abschließen." + "unitsStepMessage": "Füge Inhalte zur Vorlesung hinzu durch Erstellung von Vorlesungseinheiten." }, "newLectureUnit": "Neue Vorlesungseinheit", "editLectureUnit": "Vorlesungseinheit bearbeiten", From b4fd92c5761e3ca9ea152b3d8cc47df9ace287ae Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 14:16:54 +0100 Subject: [PATCH 095/125] Fix pre-build --- .../lecture}/close-edit-lecture-dialog.component.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename src/{main/webapp/app/lecture/close-edit-lecture-dialog.component.ts => test/javascript/spec/component/lecture}/close-edit-lecture-dialog.component.spec.ts (63%) diff --git a/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.spec.ts b/src/test/javascript/spec/component/lecture/close-edit-lecture-dialog.component.spec.ts similarity index 63% rename from src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.spec.ts rename to src/test/javascript/spec/component/lecture/close-edit-lecture-dialog.component.spec.ts index 4c3c67e6bd0e..6805e5e93e05 100644 --- a/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/close-edit-lecture-dialog.component.spec.ts @@ -1,14 +1,14 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { CloseEditLectureDialogComponent } from 'app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component'; +import { ArtemisTestModule } from '../../test.module'; -import { CloseEditLectureDialogComponent } from './close-edit-lecture-dialog.component'; - -describe('CloseEditLectureDialogComponentTsComponent', () => { +describe('CloseEditLectureDialogComponent', () => { let component: CloseEditLectureDialogComponent; let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [CloseEditLectureDialogComponent], + imports: [ArtemisTestModule, CloseEditLectureDialogComponent], }).compileComponents(); fixture = TestBed.createComponent(CloseEditLectureDialogComponent); From 857b4e1450cae81369eaf732d01322842e207af2 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 14:20:21 +0100 Subject: [PATCH 096/125] Remove unused translations --- src/main/webapp/i18n/de/lecture.json | 1 - src/main/webapp/i18n/en/lecture.json | 5 +---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/webapp/i18n/de/lecture.json b/src/main/webapp/i18n/de/lecture.json index 378418fe5935..9da168a3a90b 100644 --- a/src/main/webapp/i18n/de/lecture.json +++ b/src/main/webapp/i18n/de/lecture.json @@ -83,7 +83,6 @@ "newLectureUnit": "Neue Vorlesungseinheit", "editLectureUnit": "Vorlesungseinheit bearbeiten", "newExercise": "Neue Übung erstellen", - "backToWizard": "Zurück zur Vorlesung", "competencyTitle": "Titel", "competencyConnectedUnits": "Verknüpfte Einheiten", "competencyNoConnectedUnits": "Keine verknüpften Einheiten" diff --git a/src/main/webapp/i18n/en/lecture.json b/src/main/webapp/i18n/en/lecture.json index 488ca5a2a059..0628fd19780e 100644 --- a/src/main/webapp/i18n/en/lecture.json +++ b/src/main/webapp/i18n/en/lecture.json @@ -78,14 +78,11 @@ "attachmentsStepTitle": "Attachments", "attachmentsStepMessage": "Upload attachments to this lecture.", "unitsStepTitle": "Units", - "unitsStepMessage": "Add content to the lecture by creating different kinds of lecture units.", - "competenciesStepTitle": "Competencies", - "competenciesStepMessage": "Make it easily visible what knowledge students will achieve when completing the units of this lecture by connecting them to competencies." + "unitsStepMessage": "Add content to the lecture by creating different kinds of lecture units." }, "newLectureUnit": "New Lecture Unit", "editLectureUnit": "Edit Lecture Unit", "newExercise": "Create new exercise", - "backToWizard": "Back to lecture", "competencyTitle": "Title", "competencyConnectedUnits": "Connected Units", "competencyNoConnectedUnits": "No connected units" From 7aa82c2ae27bd6be4bc606bf506ef75893337f83 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 14:24:48 +0100 Subject: [PATCH 097/125] Fix typo --- .../close-edit-lecture-dialog.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.ts b/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.ts index 74d3be1cf56b..54cdf3644570 100644 --- a/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.ts +++ b/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.ts @@ -15,7 +15,7 @@ export class CloseEditLectureDialogComponent { protected readonly activeModal = inject(NgbActiveModal); - // not input signals yet as not can not be initialized with current ng-bootstrap version https://stackoverflow.com/a/79094268/16540383 + // no input signals yet as they can not be initialized with current ng-bootstrap version https://stackoverflow.com/a/79094268/16540383 @Input() hasUnsavedChangesInTitleSection: boolean; @Input() hasUnsavedChangesInPeriodSection: boolean; From 2c7091a6809fffe1daec4d92927f00668ba6e7a8 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 14:26:25 +0100 Subject: [PATCH 098/125] Remove TODO as discussed with Stephan and Ramona --- .../exercise-filter/exercise-filter-modal.component.html | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/webapp/app/shared/exercise-filter/exercise-filter-modal.component.html b/src/main/webapp/app/shared/exercise-filter/exercise-filter-modal.component.html index 1a87237a4d46..218a925805ca 100644 --- a/src/main/webapp/app/shared/exercise-filter/exercise-filter-modal.component.html +++ b/src/main/webapp/app/shared/exercise-filter/exercise-filter-modal.component.html @@ -70,11 +70,6 @@
} - - - - - @if (difficultyFilter?.isDisplayed && difficultyFilter) { From 5a7a867f1774f1a2c6bef9c0d38d5fa225ad2d31 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 14:28:23 +0100 Subject: [PATCH 099/125] Remove unused variable --- src/main/webapp/app/lecture/lecture-attachments.component.ts | 1 - src/main/webapp/app/lecture/lecture-update.component.html | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-attachments.component.ts b/src/main/webapp/app/lecture/lecture-attachments.component.ts index b712b2e1ec53..95af8f8c3357 100644 --- a/src/main/webapp/app/lecture/lecture-attachments.component.ts +++ b/src/main/webapp/app/lecture/lecture-attachments.component.ts @@ -49,7 +49,6 @@ export class LectureAttachmentsComponent implements OnDestroy { lectureId = input(); showHeader = input(true); - redirectToLecturePage = input(true); lecture = signal(new Lecture()); attachments: Attachment[] = []; diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index dbb189be3314..4492831d9fe5 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -65,7 +65,7 @@

- +

}
From cf9114b04cf1c49a039140bd99aad0955caf7e3a Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 14:32:41 +0100 Subject: [PATCH 100/125] Remove unused variable, method, and import --- .../course-management-exercises.component.ts | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/src/main/webapp/app/course/manage/course-management-exercises.component.ts b/src/main/webapp/app/course/manage/course-management-exercises.component.ts index 5a1c027ee515..f33792f4fdab 100644 --- a/src/main/webapp/app/course/manage/course-management-exercises.component.ts +++ b/src/main/webapp/app/course/manage/course-management-exercises.component.ts @@ -1,10 +1,8 @@ import { Component, ContentChild, OnInit, TemplateRef } from '@angular/core'; import { Course } from 'app/entities/course.model'; -import { CourseManagementService } from './course-management.service'; -import { ActivatedRoute, Router } from '@angular/router'; +import { ActivatedRoute } from '@angular/router'; import { ExerciseFilter } from 'app/entities/exercise-filter.model'; import { DocumentationType } from 'app/shared/components/documentation-button/documentation-button.component'; -import { faHandshakeAngle } from '@fortawesome/free-solid-svg-icons'; import { ExerciseType } from 'app/entities/exercise.model'; @Component({ @@ -28,21 +26,13 @@ export class CourseManagementExercisesComponent implements OnInit { filteredModelingExercisesCount = 0; filteredFileUploadExercisesCount = 0; exerciseFilter: ExerciseFilter; - lectureIdForGoingBack: number; - lectureWizardStepForGoingBack: number; - - faHandshakeAngle = faHandshakeAngle; // extension points, see shared/extension-point @ContentChild('overrideGenerateAndImportButton') overrideGenerateAndImportButton: TemplateRef; @ContentChild('overrideProgrammingExerciseCard') overrideProgrammingExerciseCard: TemplateRef; @ContentChild('overrideNonProgrammingExerciseCard') overrideNonProgrammingExerciseCard: TemplateRef; - constructor( - private courseService: CourseManagementService, - private router: Router, - private route: ActivatedRoute, - ) {} + constructor(private route: ActivatedRoute) {} /** * initializes course @@ -54,8 +44,6 @@ export class CourseManagementExercisesComponent implements OnInit { } }); - // TODO investigate shouldHaveBackButtonToWizard - this.exerciseFilter = new ExerciseFilter(''); } @@ -99,11 +87,4 @@ export class CourseManagementExercisesComponent implements OnInit { shouldHideExerciseCard(type: string): boolean { return !['all', type].includes(this.exerciseFilter.exerciseTypeSearch); } - - goBackToWizardMode() { - this.router.navigate(['/course-management', this.course.id, 'lectures', this.lectureIdForGoingBack, 'edit'], { - queryParams: { shouldBeInWizardMode: 'true', shouldOpenCreateExercise: 'true', step: this.lectureWizardStepForGoingBack }, - queryParamsHandling: '', - }); - } } From 1184dcc8d144b53b3c26e3d1fd0dbf4e81fd009e Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 14:34:48 +0100 Subject: [PATCH 101/125] Use inject instead of constructor --- .../course/manage/course-management-exercises.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/webapp/app/course/manage/course-management-exercises.component.ts b/src/main/webapp/app/course/manage/course-management-exercises.component.ts index f33792f4fdab..70afc4bd3471 100644 --- a/src/main/webapp/app/course/manage/course-management-exercises.component.ts +++ b/src/main/webapp/app/course/manage/course-management-exercises.component.ts @@ -1,4 +1,4 @@ -import { Component, ContentChild, OnInit, TemplateRef } from '@angular/core'; +import { Component, ContentChild, OnInit, TemplateRef, inject } from '@angular/core'; import { Course } from 'app/entities/course.model'; import { ActivatedRoute } from '@angular/router'; import { ExerciseFilter } from 'app/entities/exercise-filter.model'; @@ -32,7 +32,7 @@ export class CourseManagementExercisesComponent implements OnInit { @ContentChild('overrideProgrammingExerciseCard') overrideProgrammingExerciseCard: TemplateRef; @ContentChild('overrideNonProgrammingExerciseCard') overrideNonProgrammingExerciseCard: TemplateRef; - constructor(private route: ActivatedRoute) {} + private readonly route = inject(ActivatedRoute); /** * initializes course From 414377acaa71517e2bb7bdec4bdc3936dc2b3de6 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 14:42:12 +0100 Subject: [PATCH 102/125] Remove should have back button to wizard code --- .../file-upload-exercise-update.component.ts | 14 -------------- .../manage/modeling-exercise-update.component.ts | 13 ------------- .../quiz/manage/quiz-exercise-update.component.ts | 13 ------------- .../text-exercise-update.component.ts | 13 ------------- .../create-exercise-unit.component.ts | 2 +- .../text-exercise-update.component.spec.ts | 1 - 6 files changed, 1 insertion(+), 55 deletions(-) diff --git a/src/main/webapp/app/exercises/file-upload/manage/file-upload-exercise-update.component.ts b/src/main/webapp/app/exercises/file-upload/manage/file-upload-exercise-update.component.ts index 8fc4511e452e..9b9bbb55781c 100644 --- a/src/main/webapp/app/exercises/file-upload/manage/file-upload-exercise-update.component.ts +++ b/src/main/webapp/app/exercises/file-upload/manage/file-upload-exercise-update.component.ts @@ -50,7 +50,6 @@ export class FileUploadExerciseUpdateComponent implements AfterViewInit, OnDestr fileUploadExercise: FileUploadExercise; backupExercise: FileUploadExercise; isSaving: boolean; - goBackAfterSaving = false; exerciseCategories: ExerciseCategory[]; existingCategories: ExerciseCategory[]; notificationText?: string; @@ -59,8 +58,6 @@ export class FileUploadExerciseUpdateComponent implements AfterViewInit, OnDestr isImport: boolean; examCourseId?: number; - saveCommand: SaveExerciseCommand; - formStatusSections: FormSectionStatus[]; // Subcriptions @@ -104,11 +101,6 @@ export class FileUploadExerciseUpdateComponent implements AfterViewInit, OnDestr this.examCourseId = getCourseId(fileUploadExercise); }); - this.activatedRoute.queryParams.subscribe((params) => { - if (params.shouldHaveBackButtonToWizard) { - this.goBackAfterSaving = true; - } - }); this.activatedRoute.url .pipe( tap( @@ -264,12 +256,6 @@ export class FileUploadExerciseUpdateComponent implements AfterViewInit, OnDestr private onSaveSuccess(exercise: Exercise) { this.isSaving = false; - if (this.goBackAfterSaving) { - this.navigationUtilService.navigateBack(); - - return; - } - this.navigationUtilService.navigateForwardFromExerciseUpdateOrCreation(exercise); } diff --git a/src/main/webapp/app/exercises/modeling/manage/modeling-exercise-update.component.ts b/src/main/webapp/app/exercises/modeling/manage/modeling-exercise-update.component.ts index 29612cd3c373..bbba2f32751f 100644 --- a/src/main/webapp/app/exercises/modeling/manage/modeling-exercise-update.component.ts +++ b/src/main/webapp/app/exercises/modeling/manage/modeling-exercise-update.component.ts @@ -70,7 +70,6 @@ export class ModelingExerciseUpdateComponent implements AfterViewInit, OnDestroy isImport: boolean; isExamMode: boolean; semiAutomaticAssessmentAvailable = true; - goBackAfterSaving = false; formSectionStatus: FormSectionStatus[]; @@ -186,12 +185,6 @@ export class ModelingExerciseUpdateComponent implements AfterViewInit, OnDestroy ) .subscribe(); - this.activatedRoute.queryParams.subscribe((params) => { - if (params.shouldHaveBackButtonToWizard) { - this.goBackAfterSaving = true; - } - }); - this.isSaving = false; this.notificationText = undefined; } @@ -292,12 +285,6 @@ export class ModelingExerciseUpdateComponent implements AfterViewInit, OnDestroy this.eventManager.broadcast({ name: 'modelingExerciseListModification', content: 'OK' }); this.isSaving = false; - if (this.goBackAfterSaving) { - this.navigationUtilService.navigateBack(); - - return; - } - this.navigationUtilService.navigateForwardFromExerciseUpdateOrCreation(exercise); } diff --git a/src/main/webapp/app/exercises/quiz/manage/quiz-exercise-update.component.ts b/src/main/webapp/app/exercises/quiz/manage/quiz-exercise-update.component.ts index f212b3469e6c..0c03bd54d888 100644 --- a/src/main/webapp/app/exercises/quiz/manage/quiz-exercise-update.component.ts +++ b/src/main/webapp/app/exercises/quiz/manage/quiz-exercise-update.component.ts @@ -52,7 +52,6 @@ export class QuizExerciseUpdateComponent extends QuizExerciseValidationDirective notificationText?: string; isImport = false; - goBackAfterSaving = false; /** Constants for 'Add existing questions' and 'Import file' features **/ showExistingQuestions = false; @@ -149,12 +148,6 @@ export class QuizExerciseUpdateComponent extends QuizExerciseValidationDirective this.isImport = true; } - this.route.queryParams.subscribe((params) => { - if (params.shouldHaveBackButtonToWizard) { - this.goBackAfterSaving = true; - } - }); - /** Query the courseService for the participationId given by the params */ if (this.courseId) { this.courseService.find(this.courseId).subscribe((response: HttpResponse) => { @@ -520,12 +513,6 @@ export class QuizExerciseUpdateComponent extends QuizExerciseValidationDirective this.savedEntity = cloneDeep(quizExercise); this.changeDetector.detectChanges(); - if (this.goBackAfterSaving) { - this.navigationUtilService.navigateBack(); - - return; - } - // Navigate back only if it's an import // If we edit the exercise, a user might just want to save the current state of the added quiz questions without going back if (this.isImport) { diff --git a/src/main/webapp/app/exercises/text/manage/text-exercise/text-exercise-update.component.ts b/src/main/webapp/app/exercises/text/manage/text-exercise/text-exercise-update.component.ts index 18a5e8131205..7d0b69df31a3 100644 --- a/src/main/webapp/app/exercises/text/manage/text-exercise/text-exercise-update.component.ts +++ b/src/main/webapp/app/exercises/text/manage/text-exercise/text-exercise-update.component.ts @@ -67,7 +67,6 @@ export class TextExerciseUpdateComponent implements OnInit, OnDestroy, AfterView examCourseId?: number; isExamMode: boolean; isImport = false; - goBackAfterSaving = false; AssessmentType = AssessmentType; isAthenaEnabled$: Observable | undefined; @@ -170,12 +169,6 @@ export class TextExerciseUpdateComponent implements OnInit, OnDestroy, AfterView ) .subscribe(); - this.activatedRoute.queryParams.subscribe((params) => { - if (params.shouldHaveBackButtonToWizard) { - this.goBackAfterSaving = true; - } - }); - this.isAthenaEnabled$ = this.athenaService.isEnabled(); this.isSaving = false; @@ -278,12 +271,6 @@ export class TextExerciseUpdateComponent implements OnInit, OnDestroy, AfterView this.eventManager.broadcast({ name: 'textExerciseListModification', content: 'OK' }); this.isSaving = false; - if (this.goBackAfterSaving) { - this.navigationUtilService.navigateBack(); - - return; - } - this.navigationUtilService.navigateForwardFromExerciseUpdateOrCreation(exercise); } diff --git a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component.ts b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component.ts index 21d08667f60d..3b1f3e60ce34 100644 --- a/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component.ts +++ b/src/main/webapp/app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component.ts @@ -125,7 +125,7 @@ export class CreateExerciseUnitComponent implements OnInit { createNewExercise() { this.router.navigate(['/course-management', this.courseId, 'exercises'], { - queryParams: { shouldHaveBackButtonToWizard: 'true', lectureId: this.lectureId }, + queryParams: { lectureId: this.lectureId }, queryParamsHandling: '', }); } diff --git a/src/test/javascript/spec/component/text-exercise/text-exercise-update.component.spec.ts b/src/test/javascript/spec/component/text-exercise/text-exercise-update.component.spec.ts index 78fea1728c50..ccd075b95b2d 100644 --- a/src/test/javascript/spec/component/text-exercise/text-exercise-update.component.spec.ts +++ b/src/test/javascript/spec/component/text-exercise/text-exercise-update.component.spec.ts @@ -243,7 +243,6 @@ describe('TextExercise Management Update Component', () => { route.params = of({ courseId }); route.url = of([{ path: 'import' } as UrlSegment]); route.data = of({ textExercise }); - route.queryParams = of({ shouldHaveBackButtonToWizard: true }); }); it('should set isImport and remove all dates', fakeAsync(() => { From 79ad78ed596037b602417e1fe171f08a193d7270 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 14:51:36 +0100 Subject: [PATCH 103/125] Fix typo --- src/main/webapp/app/lecture/lecture-update.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index be39311a52ba..74b2def45c11 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -153,7 +153,6 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { } }); - // TODO investigate where wizard mode is opened by url this.isEditMode.set(!this.router.url.endsWith('/new')); this.lectureOnInit = cloneDeep(this.lecture()); } @@ -257,7 +256,7 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { } else if (this.isEditMode()) { this.router.navigate(['course-management', lecture.course!.id, 'lectures', lecture.id]); } else { - // after create we stay on the edit page, as not attachments and lecture units are available (we need the lecture id to save them) + // after create we stay on the edit page, as now attachments and lecture units are available (we need the lecture id to save them) this.isNewlyCreatedExercise = true; this.isEditMode.set(true); this.lecture.set(lecture); From 7ea1e8df31585814cb3bb847a131840984403ef2 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Sat, 14 Dec 2024 14:53:03 +0100 Subject: [PATCH 104/125] Re-add accidentally removed canActivate --- src/main/webapp/app/lecture/lecture.route.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/webapp/app/lecture/lecture.route.ts b/src/main/webapp/app/lecture/lecture.route.ts index 4172fd719fe2..6e610b8ae22f 100644 --- a/src/main/webapp/app/lecture/lecture.route.ts +++ b/src/main/webapp/app/lecture/lecture.route.ts @@ -132,6 +132,7 @@ export const lectureRoute: Routes = [ authorities: [Authority.EDITOR, Authority.INSTRUCTOR, Authority.ADMIN], pageTitle: 'global.generic.edit', }, + canActivate: [UserRouteAccessService], canDeactivate: [hasLectureUnsavedChangesGuard], }, ...lectureUnitRoute, From 78d9e36c3f5004e8d5e08fdda2018ba4a5776198 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Wed, 18 Dec 2024 15:58:21 +0100 Subject: [PATCH 105/125] Remove unused files and rename folder --- .../close-edit-lecture-dialog.component.html | 32 ------------------- .../close-edit-lecture-dialog.component.ts | 25 --------------- .../close-edit-lecture-modal.component.html | 0 .../close-edit-lecture-modal.component.ts | 0 .../lecture/hasLectureUnsavedChanges.guard.ts | 2 +- ...lose-edit-lecture-dialog.component.spec.ts | 22 ------------- ...close-edit-lecture-modal.component.spec.ts | 2 +- 7 files changed, 2 insertions(+), 81 deletions(-) delete mode 100644 src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.html delete mode 100644 src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.ts rename src/main/webapp/app/lecture/{close-edit-lecture-dialog => close-edit-lecture-modal}/close-edit-lecture-modal.component.html (100%) rename src/main/webapp/app/lecture/{close-edit-lecture-dialog => close-edit-lecture-modal}/close-edit-lecture-modal.component.ts (100%) delete mode 100644 src/test/javascript/spec/component/lecture/close-edit-lecture-dialog.component.spec.ts diff --git a/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.html b/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.html deleted file mode 100644 index bbf20c1164e0..000000000000 --- a/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.html +++ /dev/null @@ -1,32 +0,0 @@ - - - diff --git a/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.ts b/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.ts deleted file mode 100644 index 54cdf3644570..000000000000 --- a/src/main/webapp/app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Component, Input, inject } from '@angular/core'; -import { TranslateDirective } from 'app/shared/language/translate.directive'; -import { faTimes } from '@fortawesome/free-solid-svg-icons'; -import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module'; -import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; - -@Component({ - selector: 'jhi-close-edit-lecture-dialog', - standalone: true, - imports: [TranslateDirective, ArtemisSharedCommonModule], - templateUrl: './close-edit-lecture-dialog.component.html', -}) -export class CloseEditLectureDialogComponent { - protected readonly faTimes = faTimes; - - protected readonly activeModal = inject(NgbActiveModal); - - // no input signals yet as they can not be initialized with current ng-bootstrap version https://stackoverflow.com/a/79094268/16540383 - @Input() hasUnsavedChangesInTitleSection: boolean; - @Input() hasUnsavedChangesInPeriodSection: boolean; - - closeWindow(isCloseConfirmed: boolean): void { - this.activeModal.close(isCloseConfirmed); - } -} diff --git a/src/main/webapp/app/lecture/close-edit-lecture-dialog/close-edit-lecture-modal.component.html b/src/main/webapp/app/lecture/close-edit-lecture-modal/close-edit-lecture-modal.component.html similarity index 100% rename from src/main/webapp/app/lecture/close-edit-lecture-dialog/close-edit-lecture-modal.component.html rename to src/main/webapp/app/lecture/close-edit-lecture-modal/close-edit-lecture-modal.component.html diff --git a/src/main/webapp/app/lecture/close-edit-lecture-dialog/close-edit-lecture-modal.component.ts b/src/main/webapp/app/lecture/close-edit-lecture-modal/close-edit-lecture-modal.component.ts similarity index 100% rename from src/main/webapp/app/lecture/close-edit-lecture-dialog/close-edit-lecture-modal.component.ts rename to src/main/webapp/app/lecture/close-edit-lecture-modal/close-edit-lecture-modal.component.ts diff --git a/src/main/webapp/app/lecture/hasLectureUnsavedChanges.guard.ts b/src/main/webapp/app/lecture/hasLectureUnsavedChanges.guard.ts index 96413d4b7dba..f608b63a1cb2 100644 --- a/src/main/webapp/app/lecture/hasLectureUnsavedChanges.guard.ts +++ b/src/main/webapp/app/lecture/hasLectureUnsavedChanges.guard.ts @@ -3,7 +3,7 @@ import { CanDeactivateFn } from '@angular/router'; import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; import { LectureUpdateComponent } from 'app/lecture/lecture-update.component'; import { Observable, from, of } from 'rxjs'; -import { CloseEditLectureModalComponent } from 'app/lecture/close-edit-lecture-dialog/close-edit-lecture-modal.component'; +import { CloseEditLectureModalComponent } from 'app/lecture/close-edit-lecture-modal/close-edit-lecture-modal.component'; export const hasLectureUnsavedChangesGuard: CanDeactivateFn = (component: LectureUpdateComponent): Observable => { if (!component.shouldDisplayDismissWarning) { diff --git a/src/test/javascript/spec/component/lecture/close-edit-lecture-dialog.component.spec.ts b/src/test/javascript/spec/component/lecture/close-edit-lecture-dialog.component.spec.ts deleted file mode 100644 index 6805e5e93e05..000000000000 --- a/src/test/javascript/spec/component/lecture/close-edit-lecture-dialog.component.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { CloseEditLectureDialogComponent } from 'app/lecture/close-edit-lecture-dialog.component.ts/close-edit-lecture-dialog.component'; -import { ArtemisTestModule } from '../../test.module'; - -describe('CloseEditLectureDialogComponent', () => { - let component: CloseEditLectureDialogComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ArtemisTestModule, CloseEditLectureDialogComponent], - }).compileComponents(); - - fixture = TestBed.createComponent(CloseEditLectureDialogComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/test/javascript/spec/component/lecture/close-edit-lecture-modal.component.spec.ts b/src/test/javascript/spec/component/lecture/close-edit-lecture-modal.component.spec.ts index 93b92f1d9627..e35dfed6baa2 100644 --- a/src/test/javascript/spec/component/lecture/close-edit-lecture-modal.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/close-edit-lecture-modal.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ArtemisTestModule } from '../../test.module'; -import { CloseEditLectureModalComponent } from '../../../../../main/webapp/app/lecture/close-edit-lecture-dialog/close-edit-lecture-modal.component'; +import { CloseEditLectureModalComponent } from '../../../../../main/webapp/app/lecture/close-edit-lecture-modal/close-edit-lecture-modal.component'; describe('CloseEditLectureModalComponent', () => { let component: CloseEditLectureModalComponent; From 17c0c26c4dc7e048f1a040cb15435cea665a125e Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Wed, 18 Dec 2024 16:07:29 +0100 Subject: [PATCH 106/125] Reduce diff and fix client test --- .../webapp/app/lecture/lecture-update.component.ts | 3 --- .../lecture-update-wizard.component.html | 0 .../lecture/lecture-update.component.spec.ts | 13 ++++++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) delete mode 100644 src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.html diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index de06ed0143fb..fab85ccf3830 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -142,9 +142,6 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { ); } - /** - * Life cycle hook called by Angular to indicate that Angular is done creating the component - */ ngOnInit() { this.isSaving = false; this.processUnitMode = false; diff --git a/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.html b/src/main/webapp/app/lecture/wizard-mode/lecture-update-wizard.component.html deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts index 3bb72b91b2fd..96c1b2dbed3b 100644 --- a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts @@ -27,6 +27,11 @@ import { TitleChannelNameComponent } from '../../../../../main/webapp/app/shared import { LectureUpdatePeriodComponent } from '../../../../../main/webapp/app/lecture/lecture-period/lecture-period.component'; import { LectureUnitManagementComponent } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/lecture-unit-management.component'; import { OwlDateTimeModule } from '@danielmoncada/angular-datetime-picker'; +import { FormStatusBarComponent } from '../../../../../main/webapp/app/forms/form-status-bar/form-status-bar.component'; +import { ArtemisSharedModule } from '../../../../../main/webapp/app/shared/shared.module'; +import { LectureAttachmentsComponent } from '../../../../../main/webapp/app/lecture/lecture-attachments.component'; +import { LectureUpdateUnitsComponent } from '../../../../../main/webapp/app/lecture/lecture-units/lecture-units.component'; +import { UnitCreationCardComponent } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/unit-creation-card/unit-creation-card.component'; describe('LectureUpdateComponent', () => { let lectureService: LectureService; @@ -46,21 +51,24 @@ describe('LectureUpdateComponent', () => { pastLecture.endDate = yesterday; TestBed.configureTestingModule({ - imports: [ArtemisTestModule, FormsModule, MockModule(NgbTooltipModule), MockModule(OwlDateTimeModule)], + imports: [ArtemisTestModule, MockModule(ArtemisSharedModule), FormsModule, MockModule(NgbTooltipModule), MockModule(OwlDateTimeModule)], declarations: [ LectureUpdateComponent, LectureTitleChannelNameComponent, TitleChannelNameComponent, FormDateTimePickerComponent, + LectureAttachmentsComponent, + LectureUpdateUnitsComponent, LectureUpdatePeriodComponent, - MockComponent(LectureUpdateWizardComponent), MockComponent(LectureUnitManagementComponent), + MockComponent(FormStatusBarComponent), MockComponent(MarkdownEditorMonacoComponent), MockComponent(DocumentationButtonComponent), MockPipe(ArtemisTranslatePipe), MockPipe(ArtemisDatePipe), MockPipe(HtmlForMarkdownPipe), MockRouterLinkDirective, + MockComponent(UnitCreationCardComponent), MockDirective(CustomNotIncludedInValidatorDirective), ], providers: [ @@ -100,7 +108,6 @@ describe('LectureUpdateComponent', () => { }); it('should initialize', () => { - // lectureUpdateComponent.ngOnInit(); lectureUpdateComponentFixture.detectChanges(); expect(lectureUpdateComponent.isSaving).toBeFalse(); From bf71fa1c7c080763c6878b9da2823d032a47fc51 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Wed, 18 Dec 2024 16:11:04 +0100 Subject: [PATCH 107/125] Remove should initialize test --- .../lecture/lecture-update.component.spec.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts index 96c1b2dbed3b..9a424e67f9d9 100644 --- a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts @@ -107,21 +107,6 @@ describe('LectureUpdateComponent', () => { jest.restoreAllMocks(); }); - it('should initialize', () => { - lectureUpdateComponentFixture.detectChanges(); - - expect(lectureUpdateComponent.isSaving).toBeFalse(); - expect(lectureUpdateComponent.processUnitMode).toBeFalse(); - expect(lectureUpdateComponent.isProcessing).toBeFalse(); - expect(lectureUpdateComponent.lecture().id).toBeUndefined(); - expect(lectureUpdateComponent.lecture().title).toBeUndefined(); - expect(lectureUpdateComponent.lecture().channelName).toBeUndefined(); - expect(lectureUpdateComponent.lecture().description).toBeUndefined(); - expect(lectureUpdateComponent.lecture().visibleDate).toBeUndefined(); - expect(lectureUpdateComponent.lecture().startDate).toBeUndefined(); - expect(lectureUpdateComponent.lecture().endDate).toBeUndefined(); - }); - it('should create lecture', () => { lectureUpdateComponent.lecture.set({ title: 'test1', channelName: 'test1' } as Lecture); From ea760c1786ff812296a9004c894489137f3d13bd Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Wed, 18 Dec 2024 16:12:37 +0100 Subject: [PATCH 108/125] Remove empty files --- .../lecture/wizard-mode/lecture-wizard-title.component.spec.ts | 0 .../lecture/wizard-mode/lecture-wizard.component.spec.ts | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts delete mode 100644 src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard-title.component.spec.ts deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts b/src/test/javascript/spec/component/lecture/wizard-mode/lecture-wizard.component.spec.ts deleted file mode 100644 index e69de29bb2d1..000000000000 From 4bad53f4db42111733304e483b16ef154519019d Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Wed, 18 Dec 2024 16:21:44 +0100 Subject: [PATCH 109/125] Reduce diff from imports --- .../lecture/lecture-units.component.spec.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/test/javascript/spec/component/lecture/lecture-units.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-units.component.spec.ts index 9984850ff37a..7481e5ca35b5 100644 --- a/src/test/javascript/spec/component/lecture/lecture-units.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-units.component.spec.ts @@ -11,23 +11,23 @@ import { VideoUnit } from '../../../../../main/webapp/app/entities/lecture-unit/ import dayjs from 'dayjs/esm'; import { HttpResponse } from '@angular/common/http'; import { By } from '@angular/platform-browser'; -import { Lecture } from '../../../../../main/webapp/app/entities/lecture.model'; -import { TextUnitService } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/textUnit.service'; -import { OnlineUnitService } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/onlineUnit.service'; -import { AttachmentUnitService } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachmentUnit.service'; -import { LectureUnitType } from '../../../../../main/webapp/app/entities/lecture-unit/lectureUnit.model'; -import { LectureUnitManagementComponent } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/lecture-unit-management.component'; -import { TextUnitFormData } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component'; -import { TextUnit } from '../../../../../main/webapp/app/entities/lecture-unit/textUnit.model'; -import { OnlineUnitFormData } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component'; -import { OnlineUnit } from '../../../../../main/webapp/app/entities/lecture-unit/onlineUnit.model'; -import { AttachmentUnitFormData } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component'; -import { Attachment, AttachmentType } from '../../../../../main/webapp/app/entities/attachment.model'; -import { AttachmentUnit } from '../../../../../main/webapp/app/entities/lecture-unit/attachmentUnit.model'; -import { objectToJsonBlob } from '../../../../../main/webapp/app/utils/blob-util'; -import { CreateExerciseUnitComponent } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component'; -import { CompetencyLectureUnitLink } from '../../../../../main/webapp/app/entities/competency.model'; +import { Lecture } from 'app/entities/lecture.model'; +import { TextUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/textUnit.service'; +import { OnlineUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/onlineUnit.service'; +import { AttachmentUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/attachmentUnit.service'; +import { LectureUnitType } from 'app/entities/lecture-unit/lectureUnit.model'; +import { LectureUnitManagementComponent } from 'app/lecture/lecture-unit/lecture-unit-management/lecture-unit-management.component'; +import { TextUnitFormData } from 'app/lecture/lecture-unit/lecture-unit-management/text-unit-form/text-unit-form.component'; +import { TextUnit } from 'app/entities/lecture-unit/textUnit.model'; +import { OnlineUnitFormData } from 'app/lecture/lecture-unit/lecture-unit-management/online-unit-form/online-unit-form.component'; +import { OnlineUnit } from 'app/entities/lecture-unit/onlineUnit.model'; +import { AttachmentUnitFormData } from 'app/lecture/lecture-unit/lecture-unit-management/attachment-unit-form/attachment-unit-form.component'; +import { Attachment, AttachmentType } from 'app/entities/attachment.model'; +import { AttachmentUnit } from 'app/entities/lecture-unit/attachmentUnit.model'; +import { objectToJsonBlob } from 'app/utils/blob-util'; +import { CreateExerciseUnitComponent } from 'app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component'; import { LectureUpdateUnitsComponent } from '../../../../../main/webapp/app/lecture/lecture-units/lecture-units.component'; +import { CompetencyLectureUnitLink } from '../../../../../main/webapp/app/entities/competency.model'; @Component({ selector: 'jhi-video-unit-form', template: '' }) class VideoUnitFormStubComponent { From f02aa1df5c6d3ce9fafc831ed2b892f1bfc494c7 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Wed, 18 Dec 2024 16:22:38 +0100 Subject: [PATCH 110/125] Reduce diff from imports --- .../lecture/lecture-units.component.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/javascript/spec/component/lecture/lecture-units.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-units.component.spec.ts index 7481e5ca35b5..fd8a0190e1f9 100644 --- a/src/test/javascript/spec/component/lecture/lecture-units.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-units.component.spec.ts @@ -1,13 +1,13 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { VideoUnitFormData } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component'; +import { VideoUnitFormData } from 'app/lecture/lecture-unit/lecture-unit-management/video-unit-form/video-unit-form.component'; import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; -import { VideoUnitService } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/videoUnit.service'; +import { VideoUnitService } from 'app/lecture/lecture-unit/lecture-unit-management/videoUnit.service'; import { MockComponent, MockProvider } from 'ng-mocks'; -import { AlertService } from '../../../../../main/webapp/app/core/util/alert.service'; +import { AlertService } from 'app/core/util/alert.service'; import { ActivatedRoute, Router } from '@angular/router'; import { MockRouter } from '../../helpers/mocks/mock-router'; import { of, throwError } from 'rxjs'; -import { VideoUnit } from '../../../../../main/webapp/app/entities/lecture-unit/videoUnit.model'; +import { VideoUnit } from 'app/entities/lecture-unit/videoUnit.model'; import dayjs from 'dayjs/esm'; import { HttpResponse } from '@angular/common/http'; import { By } from '@angular/platform-browser'; @@ -26,8 +26,8 @@ import { Attachment, AttachmentType } from 'app/entities/attachment.model'; import { AttachmentUnit } from 'app/entities/lecture-unit/attachmentUnit.model'; import { objectToJsonBlob } from 'app/utils/blob-util'; import { CreateExerciseUnitComponent } from 'app/lecture/lecture-unit/lecture-unit-management/create-exercise-unit/create-exercise-unit.component'; -import { LectureUpdateUnitsComponent } from '../../../../../main/webapp/app/lecture/lecture-units/lecture-units.component'; -import { CompetencyLectureUnitLink } from '../../../../../main/webapp/app/entities/competency.model'; +import { LectureUpdateUnitsComponent } from 'app/lecture/lecture-units/lecture-units.component'; +import { CompetencyLectureUnitLink } from 'app/entities/competency.model'; @Component({ selector: 'jhi-video-unit-form', template: '' }) class VideoUnitFormStubComponent { From 01ba04ce40b68e7ff87c3e391421d12a40eea82e Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Wed, 18 Dec 2024 16:24:16 +0100 Subject: [PATCH 111/125] Reduce diff from imports --- .../lecture/lecture-update.component.spec.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts index 9a424e67f9d9..3fbe373664c9 100644 --- a/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts +++ b/src/test/javascript/spec/component/lecture/lecture-update.component.spec.ts @@ -22,16 +22,16 @@ import { ArtemisTestModule } from '../../test.module'; import { DocumentationButtonComponent } from 'app/shared/components/documentation-button/documentation-button.component'; import { LectureTitleChannelNameComponent } from 'app/lecture/lecture-title-channel-name.component'; import { MarkdownEditorMonacoComponent } from 'app/shared/markdown-editor/monaco/markdown-editor-monaco.component'; -import { CustomNotIncludedInValidatorDirective } from '../../../../../main/webapp/app/shared/validators/custom-not-included-in-validator.directive'; -import { TitleChannelNameComponent } from '../../../../../main/webapp/app/shared/form/title-channel-name/title-channel-name.component'; -import { LectureUpdatePeriodComponent } from '../../../../../main/webapp/app/lecture/lecture-period/lecture-period.component'; -import { LectureUnitManagementComponent } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/lecture-unit-management.component'; +import { CustomNotIncludedInValidatorDirective } from 'app/shared/validators/custom-not-included-in-validator.directive'; import { OwlDateTimeModule } from '@danielmoncada/angular-datetime-picker'; -import { FormStatusBarComponent } from '../../../../../main/webapp/app/forms/form-status-bar/form-status-bar.component'; -import { ArtemisSharedModule } from '../../../../../main/webapp/app/shared/shared.module'; -import { LectureAttachmentsComponent } from '../../../../../main/webapp/app/lecture/lecture-attachments.component'; -import { LectureUpdateUnitsComponent } from '../../../../../main/webapp/app/lecture/lecture-units/lecture-units.component'; -import { UnitCreationCardComponent } from '../../../../../main/webapp/app/lecture/lecture-unit/lecture-unit-management/unit-creation-card/unit-creation-card.component'; +import { TitleChannelNameComponent } from 'app/shared/form/title-channel-name/title-channel-name.component'; +import { LectureUpdatePeriodComponent } from 'app/lecture/lecture-period/lecture-period.component'; +import { LectureUnitManagementComponent } from 'app/lecture/lecture-unit/lecture-unit-management/lecture-unit-management.component'; +import { FormStatusBarComponent } from 'app/forms/form-status-bar/form-status-bar.component'; +import { ArtemisSharedModule } from 'app/shared/shared.module'; +import { LectureAttachmentsComponent } from 'app/lecture/lecture-attachments.component'; +import { LectureUpdateUnitsComponent } from 'app/lecture/lecture-units/lecture-units.component'; +import { UnitCreationCardComponent } from 'app/lecture/lecture-unit/lecture-unit-management/unit-creation-card/unit-creation-card.component'; describe('LectureUpdateComponent', () => { let lectureService: LectureService; From a1f45056bb393b21b8dc294f4fd9e0641ae1181d Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Wed, 18 Dec 2024 19:26:40 +0100 Subject: [PATCH 112/125] Disable save button if no changes to title or description have been made --- src/main/webapp/app/lecture/lecture-update.component.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index fab85ccf3830..886c673122a1 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -271,7 +271,9 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { // after create we stay on the edit page, as now attachments and lecture units are available (we need the lecture id to save them) this.isNewlyCreatedExercise = true; this.isEditMode.set(true); + this.lectureOnInit = cloneDeep(lecture); this.lecture.set(lecture); + this.updateIsChangesMadeToTitleOrPeriodSection(); window.history.replaceState({}, '', `course-management/${lecture.course!.id}/lectures/${lecture.id}/edit`); this.shouldDisplayDismissWarning = true; } From c295874dae3fe4e7b6ab6690dde1fa1f88a8c6ae Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Wed, 18 Dec 2024 19:38:10 +0100 Subject: [PATCH 113/125] Make constructor easier to read --- .../app/lecture/lecture-update.component.ts | 64 ++++++++++--------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 886c673122a1..60ab815edd1b 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -100,37 +100,9 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { } }); - effect( - function updateFormStatusBarAfterLectureCreation() { - const updatedFormStatusSections: FormSectionStatus[] = []; - - if (this.isEditMode()) { - updatedFormStatusSections.push( - { - title: 'artemisApp.lecture.wizardMode.steps.attachmentsStepTitle', - valid: Boolean(this.attachmentsSection()?.isFormValid()), - }, - { - title: 'artemisApp.lecture.wizardMode.steps.unitsStepTitle', - valid: Boolean(this.unitSection()?.isUnitConfigurationValid()), - }, - ); - } - - updatedFormStatusSections.unshift( - { - title: 'artemisApp.lecture.wizardMode.steps.titleStepTitle', - valid: Boolean(this.titleSection().titleChannelNameComponent().isFormValidSignal()), - }, - { - title: 'artemisApp.lecture.wizardMode.steps.periodStepTitle', - valid: Boolean(this.lecturePeriodSection().isPeriodSectionValid()), - }, - ); - - this.formStatusSections = updatedFormStatusSections; - }.bind(this), - ); + effect(() => { + this.updateFormStatusBar(); + }); effect( function scrollToLastSectionAfterLectureCreation() { @@ -164,6 +136,36 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { this.subscriptions.unsubscribe(); } + updateFormStatusBar() { + const updatedFormStatusSections: FormSectionStatus[] = []; + + if (this.isEditMode()) { + updatedFormStatusSections.push( + { + title: 'artemisApp.lecture.wizardMode.steps.attachmentsStepTitle', + valid: Boolean(this.attachmentsSection()?.isFormValid()), + }, + { + title: 'artemisApp.lecture.wizardMode.steps.unitsStepTitle', + valid: Boolean(this.unitSection()?.isUnitConfigurationValid()), + }, + ); + } + + updatedFormStatusSections.unshift( + { + title: 'artemisApp.lecture.wizardMode.steps.titleStepTitle', + valid: Boolean(this.titleSection().titleChannelNameComponent().isFormValidSignal()), + }, + { + title: 'artemisApp.lecture.wizardMode.steps.periodStepTitle', + valid: Boolean(this.lecturePeriodSection().isPeriodSectionValid()), + }, + ); + + this.formStatusSections = updatedFormStatusSections; + } + isChangeMadeToTitleSection() { return ( this.lecture().title !== this.lectureOnInit.title || From ee94d3a3f9ed8b74bddc87cae5c233b12343edbd Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Fri, 20 Dec 2024 12:38:15 +0100 Subject: [PATCH 114/125] Fix e2e test --- .../playwright/e2e/lecture/LectureManagement.spec.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/test/playwright/e2e/lecture/LectureManagement.spec.ts b/src/test/playwright/e2e/lecture/LectureManagement.spec.ts index 74590f2f5e14..c229c40f701d 100644 --- a/src/test/playwright/e2e/lecture/LectureManagement.spec.ts +++ b/src/test/playwright/e2e/lecture/LectureManagement.spec.ts @@ -41,10 +41,17 @@ test.describe('Lecture management', { tag: '@fast' }, () => { const lectureResponse = await lectureCreation.save(); const lecture: Lecture = await lectureResponse.json(); expect(lectureResponse.status()).toBe(201); + await expect(page).toHaveURL(`/course-management/${course.id}/lectures/${lecture.id}/edit`); + + const adjustedDescription = description! + 'change to enable save button again'; + await lectureCreation.typeDescription(adjustedDescription); + const lectureResponseFromEdit = await lectureCreation.save(); + const lectureFromEdit: Lecture = await lectureResponseFromEdit.json(); + expect(lectureResponseFromEdit.status()).toBe(200); + await page.waitForURL(`**/${course.id}/lectures/${lectureFromEdit.id}`); - await page.waitForURL(`**/${course.id}/lectures/${lecture.id}`); await expect(lectureManagement.getLectureTitle()).toContainText(lectureData.title); - await expect(lectureManagement.getLectureDescription()).toContainText(description!); + await expect(lectureManagement.getLectureDescription()).toContainText(adjustedDescription!); await expect(lectureManagement.getLectureVisibleDate()).toContainText(lectureData.visibleDate!.format(dateFormat)); await expect(lectureManagement.getLectureStartDate()).toContainText(lectureData.startDate!.format(dateFormat)); await expect(lectureManagement.getLectureEndDate()).toContainText(lectureData.endDate!.format(dateFormat)); From 526326ea1c16da39108e15a4683f091d41c53317 Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Fri, 20 Dec 2024 13:12:08 +0100 Subject: [PATCH 115/125] Address Ramonas feedback --- .../app/lecture/lecture-update.component.html | 12 +---- .../app/lecture/lecture-update.component.ts | 44 ++++++++++++------- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/main/webapp/app/lecture/lecture-update.component.html b/src/main/webapp/app/lecture/lecture-update.component.html index 4492831d9fe5..fb45a60a0151 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.html +++ b/src/main/webapp/app/lecture/lecture-update.component.html @@ -46,15 +46,7 @@

  @@ -126,7 +118,7 @@

-   + @if (isProcessing) { } diff --git a/src/main/webapp/app/lecture/lecture-update.component.ts b/src/main/webapp/app/lecture/lecture-update.component.ts index 60ab815edd1b..238562ed7072 100644 --- a/src/main/webapp/app/lecture/lecture-update.component.ts +++ b/src/main/webapp/app/lecture/lecture-update.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy, OnInit, effect, inject, signal, viewChild } from '@angular/core'; +import { Component, OnDestroy, OnInit, computed, effect, inject, signal, viewChild } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { HttpErrorResponse, HttpResponse } from '@angular/common/http'; import { Observable, Subscription } from 'rxjs'; @@ -69,6 +69,15 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { isChangeMadeToTitleOrPeriodSection = false; shouldDisplayDismissWarning = true; + areSectionsValid = computed(() => { + return ( + this.titleSection().titleChannelNameComponent().isFormValidSignal() && + this.lecturePeriodSection().isPeriodSectionValid() && + (this.unitSection()?.isUnitConfigurationValid() ?? true) && + (this.attachmentsSection()?.isFormValid() ?? true) + ); + }); + private subscriptions = new Subscription(); constructor() { @@ -139,6 +148,17 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { updateFormStatusBar() { const updatedFormStatusSections: FormSectionStatus[] = []; + updatedFormStatusSections.push( + { + title: 'artemisApp.lecture.wizardMode.steps.titleStepTitle', + valid: Boolean(this.titleSection().titleChannelNameComponent().isFormValidSignal()), + }, + { + title: 'artemisApp.lecture.wizardMode.steps.periodStepTitle', + valid: Boolean(this.lecturePeriodSection().isPeriodSectionValid()), + }, + ); + if (this.isEditMode()) { updatedFormStatusSections.push( { @@ -152,17 +172,6 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { ); } - updatedFormStatusSections.unshift( - { - title: 'artemisApp.lecture.wizardMode.steps.titleStepTitle', - valid: Boolean(this.titleSection().titleChannelNameComponent().isFormValidSignal()), - }, - { - title: 'artemisApp.lecture.wizardMode.steps.periodStepTitle', - valid: Boolean(this.lecturePeriodSection().isPeriodSectionValid()), - }, - ); - this.formStatusSections = updatedFormStatusSections; } @@ -261,14 +270,19 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { protected onSaveSuccess(lecture: Lecture) { this.isSaving = false; + if (!lecture.course?.id) { + console.error('Lecture has no course id', lecture); + return; + } + if (this.processUnitMode) { this.isProcessing = false; this.alertService.success(`Lecture with title ${lecture.title} was successfully ${this.lecture().id !== undefined ? 'updated' : 'created'}.`); - this.router.navigate(['course-management', lecture.course!.id, 'lectures', lecture.id, 'unit-management', 'attachment-units', 'process'], { + this.router.navigate(['course-management', lecture.course.id, 'lectures', lecture.id, 'unit-management', 'attachment-units', 'process'], { state: { file: this.file, fileName: this.fileName }, }); } else if (this.isEditMode()) { - this.router.navigate(['course-management', lecture.course!.id, 'lectures', lecture.id]); + this.router.navigate(['course-management', lecture.course.id, 'lectures', lecture.id]); } else { // after create we stay on the edit page, as now attachments and lecture units are available (we need the lecture id to save them) this.isNewlyCreatedExercise = true; @@ -276,7 +290,7 @@ export class LectureUpdateComponent implements OnInit, OnDestroy { this.lectureOnInit = cloneDeep(lecture); this.lecture.set(lecture); this.updateIsChangesMadeToTitleOrPeriodSection(); - window.history.replaceState({}, '', `course-management/${lecture.course!.id}/lectures/${lecture.id}/edit`); + window.history.replaceState({}, '', `course-management/${lecture.course.id}/lectures/${lecture.id}/edit`); this.shouldDisplayDismissWarning = true; } } From bae23ffe0b3f428a9fbfb2f7b678ef6bbd6826ee Mon Sep 17 00:00:00 2001 From: Florian Glombik Date: Fri, 20 Dec 2024 13:37:25 +0100 Subject: [PATCH 116/125] Address Ramonas feedback (translations) --- .../close-edit-lecture-modal.component.html | 4 +-- .../lecture-period.component.html | 4 +-- .../lecture-units.component.html | 8 +++--- .../app/lecture/lecture-update.component.html | 10 +++---- .../app/lecture/lecture-update.component.ts | 10 +++---- src/main/webapp/i18n/de/lecture.json | 28 ++++++++----------- src/main/webapp/i18n/en/lecture.json | 28 ++++++++----------- 7 files changed, 40 insertions(+), 52 deletions(-) diff --git a/src/main/webapp/app/lecture/close-edit-lecture-modal/close-edit-lecture-modal.component.html b/src/main/webapp/app/lecture/close-edit-lecture-modal/close-edit-lecture-modal.component.html index afc317c054bc..d554326acf9e 100644 --- a/src/main/webapp/app/lecture/close-edit-lecture-modal/close-edit-lecture-modal.component.html +++ b/src/main/webapp/app/lecture/close-edit-lecture-modal/close-edit-lecture-modal.component.html @@ -13,12 +13,12 @@