Skip to content

Commit

Permalink
Development: Use signals in lecture add attachment form (#9656)
Browse files Browse the repository at this point in the history
  • Loading branch information
florian-glombik authored Nov 7, 2024
1 parent d3357f8 commit 4d34faa
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 114 deletions.
32 changes: 13 additions & 19 deletions src/main/webapp/app/lecture/lecture-attachments.component.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
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';
import { Attachment, AttachmentType } from 'app/entities/attachment.model';
import { AttachmentService } from 'app/lecture/attachment.service';
import { faEye, faPaperclip, faPencilAlt, faQuestionCircle, faSpinner, faTimes, faTrash } from '@fortawesome/free-solid-svg-icons';
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 { LectureService } from 'app/lecture/lecture.service';

@Component({
Expand All @@ -18,6 +17,17 @@ 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;

protected readonly allowedFileExtensions = ALLOWED_FILE_EXTENSIONS_HUMAN_READABLE;
protected readonly acceptedFileExtensionsFileBrowser = ACCEPTED_FILE_EXTENSIONS_FILE_BROWSER;

@ViewChild('fileInput', { static: false }) fileInput: ElementRef;
@Input() lectureId: number | undefined;
@Input() showHeader = true;
Expand All @@ -33,29 +43,13 @@ export class LectureAttachmentsComponent implements OnInit, OnDestroy {
errorMessage?: string;
viewButtonAvailable: Record<number, boolean> = {};

// 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(',');

private dialogErrorSource = new Subject<string>();
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,
) {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@
/>
</div>
</div>
@if (isFileTooBig) {
@if (isFileTooBig()) {
<div class="alert alert-danger">
{{ 'artemisApp.attachmentUnit.createAttachmentUnit.fileTooBig' | artemisTranslate }}
{{ 'artemisApp.attachmentUnit.createAttachmentUnit.fileLimitation' | artemisTranslate }}
</div>
}
@if (!fileName && fileInputTouched) {
@if (!fileName() && fileInputTouched) {
<div class="alert alert-danger" jhiTranslate="artemisApp.attachmentUnit.createAttachmentUnit.fileRequiredValidationError"></div>
}
</div>
Expand All @@ -81,7 +81,7 @@
formControlName="competencyLinks"
/>
</div>
<div class="form-group" [hidden]="!isEditMode">
<div class="form-group" [hidden]="!isEditMode()">
<label for="updateNotificationText" jhiTranslate="artemisApp.attachmentUnit.createAttachmentUnit.updateNotificationText"></label>
<textarea
class="form-control"
Expand All @@ -105,10 +105,10 @@
</div>
<div class="row">
<div class="col-12">
<button id="submitButton" class="btn btn-primary" type="submit" [disabled]="!isSubmitPossible">
<button id="submitButton" class="btn btn-primary" type="submit" [disabled]="!isFormValid()">
<span jhiTranslate="entity.action.submit"></span>
</button>
@if (hasCancelButton) {
@if (hasCancelButton()) {
<button type="button" (click)="cancelForm()" class="btn btn-default">
<fa-icon [icon]="faTimes" />&nbsp;<span jhiTranslate="entity.action.cancel"></span>
</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core';
import { Component, ElementRef, OnChanges, ViewChild, computed, inject, input, output, 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 { ACCEPTED_FILE_EXTENSIONS_FILE_BROWSER, ALLOWED_FILE_EXTENSIONS_HUMAN_READABLE } 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 { toSignal } from '@angular/core/rxjs-interop';

export interface AttachmentUnitFormData {
formProperties: FormProperties;
Expand All @@ -32,66 +32,49 @@ export interface FileProperties {
selector: 'jhi-attachment-unit-form',
templateUrl: './attachment-unit-form.component.html',
})
export class AttachmentUnitFormComponent implements OnInit, OnChanges {
@Input()
formData: AttachmentUnitFormData;
@Input()
isEditMode = false;
export class AttachmentUnitFormComponent implements OnChanges {
protected readonly faQuestionCircle = faQuestionCircle;
protected readonly faTimes = faTimes;

// 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(',');
protected readonly allowedFileExtensions = ALLOWED_FILE_EXTENSIONS_HUMAN_READABLE;
protected readonly acceptedFileExtensionsFileBrowser = ACCEPTED_FILE_EXTENSIONS_FILE_BROWSER;

faQuestionCircle = faQuestionCircle;
formData = input<AttachmentUnitFormData>();
isEditMode = input<boolean>(false);

@Output()
formSubmitted: EventEmitter<AttachmentUnitFormData> = new EventEmitter<AttachmentUnitFormData>();
form: FormGroup;
formSubmitted = output<AttachmentUnitFormData>();

@Input()
hasCancelButton: boolean;
@Output()
onCancel: EventEmitter<any> = new EventEmitter<any>();

faTimes = faTimes;
hasCancelButton = input<boolean>(false);
onCancel = output<void>();

// 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<string | undefined>(undefined);
isFileTooBig = signal<boolean>(false);

ngOnChanges(): void {
this.initializeForm();
if (this.isEditMode && this.formData) {
this.setFormValues(this.formData);
}
}
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)]],
competencyLinks: [undefined as CompetencyLectureUnitLink[] | undefined],
});
private readonly statusChanges = toSignal(this.form.statusChanges ?? 'INVALID');

ngOnInit(): void {
this.initializeForm();
}
isFormValid = computed(() => {
return (this.statusChanges() === 'VALID' || this.fileName()) && !this.isFileTooBig();
});

private initializeForm() {
if (this.form) {
return;
ngOnChanges(): void {
if (this.isEditMode() && this.formData()) {
this.setFormValues(this.formData()!);
}
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 {
Expand All @@ -100,15 +83,15 @@ 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({
// without extension
name: this.file.name.replace(/\.[^/.]+$/, ''),
});
}
this.isFileTooBig = this.file.size > MAX_FILE_SIZE;
this.isFileTooBig.set(this.file.size > MAX_FILE_SIZE);
}

get nameControl() {
Expand All @@ -131,16 +114,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({
Expand All @@ -157,7 +136,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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ <h2 jhiTranslate="artemisApp.exerciseUnit.createExerciseUnit.headline"></h2>
<p jhiTranslate="artemisApp.exerciseUnit.createExerciseUnit.description"></p>
</div>
<div class="col-sm-6 mx-auto text-end">
@if (hasCancelButton) {
@if (hasCancelButton()) {
<button type="button" (click)="cancelForm()" class="btn btn-default">
<fa-icon [icon]="faTimes" />&nbsp;<span jhiTranslate="entity.action.cancel"></span>
</button>
}
@if (hasCreateExerciseButton) {
@if (hasCreateExerciseButton()) {
<button
id="createExerciseButton"
type="button"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Component, Input, OnInit, input, output } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ExerciseUnit } from 'app/entities/lecture-unit/exerciseUnit.model';
import { CourseManagementService } from 'app/course/manage/course-management.service';
Expand All @@ -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<any> = new EventEmitter<any>();
@Output()
onExerciseUnitCreated: EventEmitter<any> = new EventEmitter<any>();
@Input() lectureId: number | undefined;
@Input() courseId: number | undefined;
hasCancelButton = input<boolean>();
hasCreateExerciseButton = input<boolean>();
shouldNavigateOnSubmit = input<boolean>(true);
currentWizardStep = input<number>();

faTimes = faTimes;
onCancel = output<void>();
onExerciseUnitCreated = output<void>();

predicate = 'type';
reverse = false;
Expand All @@ -45,9 +38,6 @@ export class CreateExerciseUnitComponent implements OnInit {
exercisesAvailableForUnitCreation: Exercise[] = [];
exercisesToCreateUnitFor: Exercise[] = [];

// Icons
faSort = faSort;

constructor(
private activatedRoute: ActivatedRoute,
private router: Router,
Expand Down Expand Up @@ -98,7 +88,7 @@ export class CreateExerciseUnitComponent implements OnInit {
.pipe(
concatMap((unit) => this.exerciseUnitService.create(unit, this.lectureId!)),
finalize(() => {
if (this.shouldNavigateOnSubmit) {
if (this.shouldNavigateOnSubmit()) {
this.router.navigate(['../../'], { relativeTo: this.activatedRoute });
} else {
this.onExerciseUnitCreated.emit();
Expand Down Expand Up @@ -136,7 +126,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, step: this.currentWizardStep() },
queryParamsHandling: '',
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ export const UPLOAD_FILE_EXTENSIONS = [
'odf',
];

export const ALLOWED_FILE_EXTENSIONS_HUMAN_READABLE = UPLOAD_FILE_EXTENSIONS.join(', ');
// The list of file extensions for the "accept" attribute of the file input field
export const ACCEPTED_FILE_EXTENSIONS_FILE_BROWSER = UPLOAD_FILE_EXTENSIONS.map((ext) => '.' + ext).join(',');

/**
* The list of file extensions that are readable in a file editor.
* Extensions are case-sensitive.
Expand Down
Loading

0 comments on commit 4d34faa

Please sign in to comment.