Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development: Use signals in lecture add attachment form #9656

Merged
merged 8 commits into from
Nov 7, 2024
Merged
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;

florian-glombik marked this conversation as resolved.
Show resolved Hide resolved
@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({
florian-glombik marked this conversation as resolved.
Show resolved Hide resolved
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();
});

florian-glombik marked this conversation as resolved.
Show resolved Hide resolved
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
Loading