Skip to content

Commit bacadbd

Browse files
Merge branch 'main' into feat/rename-tumapply-to-docapply
2 parents 1875c3d + e3785ea commit bacadbd

77 files changed

Lines changed: 979 additions & 361 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/main/java/de/tum/cit/aet/core/util/PDFBuilder.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import com.itextpdf.layout.Canvas;
1717
import com.itextpdf.layout.Document;
1818
import com.itextpdf.layout.borders.Border;
19+
import com.itextpdf.layout.element.AbstractElement;
1920
import com.itextpdf.layout.element.Cell;
2021
import com.itextpdf.layout.element.Div;
2122
import com.itextpdf.layout.element.IBlockElement;
@@ -26,7 +27,10 @@
2627
import com.itextpdf.layout.element.Paragraph;
2728
import com.itextpdf.layout.element.Table;
2829
import com.itextpdf.layout.element.Text;
30+
import com.itextpdf.layout.font.FontProvider;
31+
import com.itextpdf.layout.font.FontSet;
2932
import com.itextpdf.layout.properties.HorizontalAlignment;
33+
import com.itextpdf.layout.properties.Property;
3034
import com.itextpdf.layout.properties.TextAlignment;
3135
import com.itextpdf.layout.properties.UnitValue;
3236
import de.tum.cit.aet.core.exception.PDFGenerationException;
@@ -59,6 +63,9 @@ public class PDFBuilder {
5963
private static final DeviceRgb PRIMARY_COLOR = new DeviceRgb(0x18, 0x72, 0xDD);
6064
private static final DeviceRgb METADATA_COLOR = new DeviceRgb(0x8d, 0x8d, 0x8f);
6165

66+
// Reused across exports so converted HTML uses the same Helvetica font as the rest of the document.
67+
private static final FontProvider HTML_FONT_PROVIDER = createHelveticaFontProvider();
68+
6269
// ----------------- Font Sizes -----------------
6370
private static final float FONT_SIZE_MAIN_HEADING = 18f;
6471

@@ -529,18 +536,52 @@ private void addMetadata(PdfDocument pdfDoc, PdfFont normalFont) {
529536
}
530537
}
531538

539+
/**
540+
* Creates a font provider exposing only the Helvetica family (regular, bold, italic and
541+
* bold-italic) with Helvetica as the default family, so HTML conversion stays on a single font.
542+
*
543+
* @return a font provider limited to the Helvetica family
544+
*/
545+
private static FontProvider createHelveticaFontProvider() {
546+
FontSet fontSet = new FontSet();
547+
fontSet.addFont(StandardFonts.HELVETICA);
548+
fontSet.addFont(StandardFonts.HELVETICA_BOLD);
549+
fontSet.addFont(StandardFonts.HELVETICA_OBLIQUE);
550+
fontSet.addFont(StandardFonts.HELVETICA_BOLDOBLIQUE);
551+
return new FontProvider(fontSet, StandardFonts.HELVETICA);
552+
}
553+
554+
/**
555+
* Drops the font sizes the HTML converter resolved onto nested elements so that they inherit the
556+
* size set on the surrounding block instead. Converted bold and italic runs carry their own font
557+
* size, which would otherwise take precedence and render them larger than the text around them.
558+
* Their font is deliberately kept, since that is what selects the bold or italic Helvetica variant.
559+
*
560+
* @param element the converted element whose descendants should inherit the block font size
561+
*/
562+
private static void clearNestedFontSizes(IElement element) {
563+
if (element instanceof AbstractElement<?> abstractElement) {
564+
for (IElement child : abstractElement.getChildren()) {
565+
child.deleteOwnProperty(Property.FONT_SIZE);
566+
clearNestedFontSizes(child);
567+
}
568+
}
569+
}
570+
532571
private List<IBlockElement> parseHtmlContent(String html, PdfFont normalFont) {
533572
List<IBlockElement> elements = new ArrayList<>();
534573

535574
try {
536575
String processedHtml = html.replaceAll("<ol>", "<ul>").replaceAll("</ol>", "</ul>");
537576

538577
ConverterProperties props = new ConverterProperties();
578+
props.setFontProvider(HTML_FONT_PROVIDER);
539579

540580
List<IElement> pdfElements = HtmlConverter.convertToElements(processedHtml, props);
541581

542582
for (IElement element : pdfElements) {
543583
if (element instanceof IBlockElement blockElement) {
584+
clearNestedFontSizes(blockElement);
544585
if (blockElement instanceof Paragraph) {
545586
((Paragraph) blockElement).setFont(normalFont)
546587
.setFontSize(FONT_SIZE_TEXT)

src/main/webapp/app/app.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ export const appConfig: ApplicationConfig = {
6969
provideAppInitializer(initializeSiteNameSync),
7070
provideZonelessChangeDetection(),
7171
provideRouter(routes, withRouterConfig({ onSameUrlNavigation: 'reload' })),
72+
// PrimeNG still drives its overlay and dialog animations through this provider, so it cannot be
73+
// dropped for animate.enter/animate.leave until PrimeNG stops depending on it.
74+
// eslint-disable-next-line @typescript-eslint/no-deprecated
7275
provideAnimations(),
7376
providePrimeNG({
7477
theme: {

src/main/webapp/app/application/all-applications/all-applications-page.component.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { hasText } from 'app/shared/util/text.util';
12
import { Component, TemplateRef, computed, inject, signal, viewChild } from '@angular/core';
23
import { firstValueFrom } from 'rxjs';
34
import { TableLazyLoadEvent } from 'primeng/table';
@@ -295,15 +296,15 @@ export class AllApplicationsPageComponent {
295296
/** Confirms the delete dialog and dispatches the delete request. */
296297
async onConfirmDelete(): Promise<void> {
297298
const id = this.currentApplicationId();
298-
if (id !== undefined && id !== '') {
299+
if (hasText(id)) {
299300
await this.onDeleteApplication(id);
300301
}
301302
}
302303

303304
/** Confirms the withdraw dialog and dispatches the withdraw request. */
304305
async onConfirmWithdraw(): Promise<void> {
305306
const id = this.currentApplicationId();
306-
if (id !== undefined && id !== '') {
307+
if (hasText(id)) {
307308
await this.onWithdrawApplication(id);
308309
}
309310
}
@@ -336,7 +337,7 @@ export class AllApplicationsPageComponent {
336337
list
337338
.map(u => ({
338339
id: u.userId ?? '',
339-
name: [u.firstName, u.lastName].filter(p => p !== undefined && p !== '').join(' '),
340+
name: [u.firstName, u.lastName].filter(p => hasText(p)).join(' '),
340341
}))
341342
.filter(o => o.id !== '' && o.name !== ''),
342343
);

src/main/webapp/app/application/application-creation/application-creation-form/application-creation-form.component.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { hasText } from 'app/shared/util/text.util';
12
import { Component, TemplateRef, computed, effect, inject, signal, untracked, viewChild } from '@angular/core';
23
import { ProgressStepperComponent, StepData } from 'app/shared/components/molecules/progress-stepper/progress-stepper.component';
34
import { Location } from '@angular/common';
@@ -448,7 +449,7 @@ export default class ApplicationCreationFormComponent {
448449
} catch (error) {
449450
const httpError = error as HttpErrorResponse;
450451
this.showInitErrorMessage(`${applyflow}.loadFailed`);
451-
throw new Error(`Init failed with HTTP ${httpError.status} ${httpError.statusText}: ${httpError.message}`);
452+
throw new Error(`Init failed with HTTP ${httpError.status}: ${httpError.message}`);
452453
}
453454
}
454455
}
@@ -637,7 +638,7 @@ export default class ApplicationCreationFormComponent {
637638
// Bail here too so we don't try to create or migrate an application against
638639
// an unauthenticated session (which would fire "Session expired" toasts).
639640
const userId = this.accountService.loadedUser()?.id;
640-
if (!userId) {
641+
if (!hasText(userId)) {
641642
return;
642643
}
643644

src/main/webapp/app/application/application-creation/application-creation-page1/application-creation-page1.component.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { hasText } from 'app/shared/util/text.util';
12
import { Component, computed, effect, inject, input, model, output, signal } from '@angular/core';
23
import { toSignal } from '@angular/core/rxjs-interop';
34
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
@@ -264,19 +265,19 @@ export default class ApplicationCreationPage1Component {
264265

265266
form.patchValue(patch);
266267

267-
if (!this.data().gender && extractedData.gender) {
268+
if (!this.data().gender && hasText(extractedData.gender)) {
268269
const match = selectGender.find(g => g.value === extractedData.gender);
269270
if (match) this.updateSelect('gender', match);
270271
}
271-
if (!this.data().nationality && extractedData.nationality) {
272+
if (!this.data().nationality && hasText(extractedData.nationality)) {
272273
const match = selectNationality.find(n => n.value === extractedData.nationality);
273274
if (match) this.updateSelect('nationality', match);
274275
}
275-
if (!this.data().country && extractedData.country) {
276+
if (!this.data().country && hasText(extractedData.country)) {
276277
const match = selectCountries.find(c => c.value === extractedData.country);
277278
if (match) this.updateSelect('country', match);
278279
}
279-
if (!this.data().dateOfBirth && extractedData.dateOfBirth) {
280+
if (!hasText(this.data().dateOfBirth) && hasText(extractedData.dateOfBirth)) {
280281
this.setDateOfBirth(extractedData.dateOfBirth);
281282
}
282283

src/main/webapp/app/application/application-creation/application-creation-references/application-creation-references.component.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { hasText } from 'app/shared/util/text.util';
12
import { Component, computed, effect, inject, input, output, signal } from '@angular/core';
23
import { CommonModule } from '@angular/common';
34
import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms';
@@ -123,7 +124,7 @@ export default class ApplicationCreationReferencesComponent {
123124
*/
124125
async onSubmit(): Promise<void> {
125126
const currentId = this.editingId();
126-
if (currentId !== undefined && currentId !== '') {
127+
if (hasText(currentId)) {
127128
await this.onUpdate();
128129
} else {
129130
await this.onAdd();
@@ -161,7 +162,7 @@ export default class ApplicationCreationReferencesComponent {
161162
*/
162163
async onUpdate(): Promise<void> {
163164
const id = this.editingId();
164-
if (id === undefined || id === '') return;
165+
if (!hasText(id)) return;
165166
if (this.addForm.invalid) {
166167
this.addForm.markAllAsTouched();
167168
return;
@@ -185,7 +186,7 @@ export default class ApplicationCreationReferencesComponent {
185186
* @param reference the referee entry to edit
186187
*/
187188
onEdit(reference: ReferenceRequestDTO): void {
188-
if (reference.referenceRequestId === undefined || reference.referenceRequestId === '') return;
189+
if (!hasText(reference.referenceRequestId)) return;
189190
this.selectedTitleOption.set(this.titleOptions.find(option => option.value === reference.title));
190191
this.addForm.reset({
191192
title: reference.title ?? '',
@@ -210,7 +211,7 @@ export default class ApplicationCreationReferencesComponent {
210211
* @param reference the referee entry to remove
211212
*/
212213
async onRemove(reference: ReferenceRequestDTO): Promise<void> {
213-
if (reference.referenceRequestId === undefined || reference.referenceRequestId === '') return;
214+
if (!hasText(reference.referenceRequestId)) return;
214215
this.loading.set(true);
215216
try {
216217
await firstValueFrom(this.referenceApi.remove(this.applicationId(), reference.referenceRequestId));

src/main/webapp/app/application/application-detail-for-applicant/application-detail-for-applicant.component.ts

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { ReferenceRequestDTO } from 'app/generated/model/reference-request-dto';
2626
import { ReferenceAssessmentSectionComponent } from 'app/shared/components/molecules/reference-assessment-section/reference-assessment-section.component';
2727
import LocalizedDatePipe from 'app/shared/pipes/localized-date.pipe';
2828
import { TagComponent } from 'app/shared/components/atoms/tag/tag.component';
29+
import { hasText } from 'app/shared/util/text.util';
2930

3031
import ApplicationCreationReferencesComponent from '../application-creation/application-creation-references/application-creation-references.component';
3132
import { ApplicationStateForApplicantsComponent } from '../application-state-for-applicants/application-state-for-applicants.component';
@@ -114,7 +115,7 @@ export default class ApplicationDetailForApplicantComponent {
114115
if (this.previewDetailData()) return false;
115116
const app = this.application();
116117
if (!app || (app.referenceLettersRequired ?? 0) <= 0) return false;
117-
if (app.jobEndDate) {
118+
if (hasText(app.jobEndDate)) {
118119
const endDate = new Date(app.jobEndDate);
119120
endDate.setHours(23, 59, 59, 999);
120121
if (endDate < new Date()) {
@@ -145,10 +146,10 @@ export default class ApplicationDetailForApplicantComponent {
145146
*/
146147
submittedReferenceLetters = computed(() =>
147148
this.references()
148-
.filter(reference => !!reference.documentId)
149+
.filter(reference => hasText(reference.documentId))
149150
.map(reference => ({
150151
documentId: reference.documentId,
151-
refereeName: [reference.firstName, reference.lastName].filter(part => !!part).join(' '),
152+
refereeName: [reference.firstName, reference.lastName].filter(part => hasText(part)).join(' '),
152153
viewerInput: {
153154
id: reference.documentId as string,
154155
name: `${reference.firstName ?? ''} ${reference.lastName ?? ''}`.trim(),
@@ -238,10 +239,10 @@ export default class ApplicationDetailForApplicantComponent {
238239
this.currentLang();
239240
const applicant = this.application()?.applicant;
240241
const grade = applicant?.bachelorGrade;
241-
if (!grade) return '-';
242+
if (applicant === undefined || !hasText(grade)) return '-';
242243

243244
const limits = { upperLimit: applicant.bachelorGradeUpperLimit, lowerLimit: applicant.bachelorGradeLowerLimit };
244-
if (!limits.upperLimit || !limits.lowerLimit) return grade;
245+
if (!hasText(limits.upperLimit) || !hasText(limits.lowerLimit)) return grade;
245246

246247
const scale =
247248
'(' +
@@ -258,10 +259,10 @@ export default class ApplicationDetailForApplicantComponent {
258259
this.currentLang();
259260
const applicant = this.application()?.applicant;
260261
const grade = applicant?.masterGrade;
261-
if (!grade) return '-';
262+
if (applicant === undefined || !hasText(grade)) return '-';
262263

263264
const limits = { upperLimit: applicant.masterGradeUpperLimit, lowerLimit: applicant.masterGradeLowerLimit };
264-
if (!limits.upperLimit || !limits.lowerLimit) return grade;
265+
if (!hasText(limits.upperLimit) || !hasText(limits.lowerLimit)) return grade;
265266

266267
const scale =
267268
'(' +
@@ -393,7 +394,7 @@ export default class ApplicationDetailForApplicantComponent {
393394

394395
onViewJobDetails(): void {
395396
const jobIdValue = this.application()?.jobId;
396-
if (jobIdValue !== undefined && jobIdValue !== '') {
397+
if (hasText(jobIdValue)) {
397398
void this.router.navigate(['/job/detail', jobIdValue]);
398399
} else {
399400
this.toastService.showErrorKey(`${this.translationKey}.jobIdNotAvailable`);

src/main/webapp/app/config/language.helper.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class JhiLanguageHelper {
2121
constructor() {
2222
const rootRenderer = inject(RendererFactory2);
2323

24-
this._language = new BehaviorSubject<string>(this.translateService.currentLang);
24+
this._language = new BehaviorSubject<string>(this.translateService.getCurrentLang());
2525
this.renderer = rootRenderer.createRenderer(document.querySelector('html'), null);
2626
this.init();
2727
}
@@ -46,8 +46,8 @@ export class JhiLanguageHelper {
4646
updateTitle(titleKey?: string): void {
4747
titleKey ??= this.getPageTitle(this.router.routerState.snapshot.root);
4848

49-
this.translateService.get(titleKey).subscribe(title => {
50-
if (title) {
49+
this.translateService.get(titleKey).subscribe((title: unknown) => {
50+
if (typeof title === 'string' && title !== '') {
5151
this.titleService.setTitle(title);
5252
}
5353
});
@@ -88,11 +88,11 @@ export class JhiLanguageHelper {
8888

8989
private init(): void {
9090
this.translateService.onLangChange.subscribe(() => {
91-
const languageKey = this.translateService.currentLang;
91+
const languageKey = this.translateService.getCurrentLang();
9292
this._language.next(languageKey);
9393
this.localeConversionService.locale = languageKey;
9494
sessionStorage.setItem('locale', languageKey);
95-
this.renderer.setAttribute(document.querySelector('html'), 'lang', this.translateService.currentLang);
95+
this.renderer.setAttribute(document.querySelector('html'), 'lang', this.translateService.getCurrentLang());
9696
this.updateTitle();
9797
});
9898
}

src/main/webapp/app/core/auth/auth-orchestrator.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ export class AuthOrchestratorService {
141141
this.setIfPresent(prefill.firstName, value => this.firstName.set(value));
142142
this.setIfPresent(prefill.lastName, value => this.lastName.set(value));
143143
}
144-
if (opts?.redirectUri) this.redirectUri.set(opts.redirectUri);
144+
if (opts?.redirectUri !== undefined && opts.redirectUri !== '') this.redirectUri.set(opts.redirectUri);
145145

146146
// choose sensible starting substates
147147
if (this.mode() === 'login') {
@@ -167,7 +167,7 @@ export class AuthOrchestratorService {
167167

168168
try {
169169
this.onSuccessCb?.();
170-
if (targetUrl) {
170+
if (targetUrl !== null && targetUrl !== '') {
171171
const path = targetUrl.startsWith(window.location.origin) ? targetUrl.slice(window.location.origin.length) : targetUrl;
172172
void this.router.navigateByUrl(path);
173173
}

src/main/webapp/app/core/auth/has-any-authority.directive.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { AccountService } from 'app/core/auth/account.service';
1919
export default class HasAnyAuthorityDirective {
2020
public roles = input<string | string[]>([], { alias: 'jhiHasAnyAuthority' });
2121

22-
private readonly templateRef = inject(TemplateRef<any>);
22+
private readonly templateRef = inject(TemplateRef<unknown>);
2323
private readonly viewContainerRef = inject(ViewContainerRef);
2424

2525
private embeddedViewCreated = false;

0 commit comments

Comments
 (0)