Skip to content

Commit 46bafad

Browse files
committed
Angular 21: core, cli, material, cdk, and the built-in control flow migration
1 parent 796155d commit 46bafad

29 files changed

Lines changed: 2154 additions & 1652 deletions

src/Papercut.Service/Web/package-lock.json

Lines changed: 1388 additions & 978 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Papercut.Service/Web/package.json

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@
1616
},
1717
"private": true,
1818
"dependencies": {
19-
"@angular/animations": "^20.3.29",
20-
"@angular/cdk": "^20.2.14",
21-
"@angular/common": "^20.3.29",
22-
"@angular/compiler": "^20.3.29",
23-
"@angular/core": "^20.3.29",
24-
"@angular/forms": "^20.3.29",
25-
"@angular/material": "^20.2.14",
26-
"@angular/platform-browser": "^20.3.29",
27-
"@angular/platform-browser-dynamic": "^20.3.29",
28-
"@angular/router": "^20.3.29",
19+
"@angular/animations": "^21.2.21",
20+
"@angular/cdk": "^21.2.14",
21+
"@angular/common": "^21.2.21",
22+
"@angular/compiler": "^21.2.21",
23+
"@angular/core": "^21.2.21",
24+
"@angular/forms": "^21.2.21",
25+
"@angular/material": "^21.2.14",
26+
"@angular/platform-browser": "^21.2.21",
27+
"@angular/platform-browser-dynamic": "^21.2.21",
28+
"@angular/router": "^21.2.21",
2929
"@fontsource/jetbrains-mono": "^5.3.0",
3030
"@fontsource/plus-jakarta-sans": "^5.3.0",
3131
"@microsoft/signalr": "^8.0.11",
@@ -35,9 +35,9 @@
3535
"zone.js": "~0.15.1"
3636
},
3737
"devDependencies": {
38-
"@angular-devkit/build-angular": "^20.3.34",
39-
"@angular/cli": "^20.3.34",
40-
"@angular/compiler-cli": "^20.3.29",
38+
"@angular-devkit/build-angular": "^21.2.21",
39+
"@angular/cli": "^21.2.21",
40+
"@angular/compiler-cli": "^21.2.21",
4141
"@types/jasmine": "~5.1.0",
4242
"autoprefixer": "^10.4.21",
4343
"jasmine-core": "~5.1.0",

src/Papercut.Service/Web/src/app/components/bottom-toolbar/bottom-toolbar.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { Component, Input, Output, EventEmitter } from '@angular/core';
2-
import { CommonModule } from '@angular/common';
2+
33
import { MatTooltipModule } from '@angular/material/tooltip';
44
import { LucideAngularModule, Forward, Trash2, Trash } from 'lucide-angular';
55
import { ConfirmService } from '../../services/confirm.service';
66

77
@Component({
88
selector: 'app-bottom-toolbar',
9-
imports: [CommonModule, MatTooltipModule, LucideAngularModule],
9+
imports: [MatTooltipModule, LucideAngularModule],
1010
template: `
1111
<div class="bottom-toolbar">
1212
<div class="toolbar-container">

src/Papercut.Service/Web/src/app/components/confirm-dialog/confirm-dialog.component.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
// limitations under the License.
1717

1818
import { Component, Inject } from '@angular/core';
19-
import { CommonModule } from '@angular/common';
19+
2020
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
2121
import { LucideAngularModule, AlertTriangle, HelpCircle, X } from 'lucide-angular';
2222

@@ -46,40 +46,42 @@ export interface ConfirmDialogData {
4646
*/
4747
@Component({
4848
selector: 'app-confirm-dialog',
49-
imports: [CommonModule, MatDialogModule, LucideAngularModule],
49+
imports: [MatDialogModule, LucideAngularModule],
5050
template: `
5151
<div class="confirm-dialog">
5252
<div class="dialog-header">
5353
<lucide-icon [img]="data.danger ? icons.AlertTriangle : icons.HelpCircle"
54-
[size]="16"
55-
[class.icon-danger]="data.danger"></lucide-icon>
54+
[size]="16"
55+
[class.icon-danger]="data.danger"></lucide-icon>
5656
<h2 class="dialog-title">{{ data.title }}</h2>
5757
<button class="dialog-close" (click)="cancel()" aria-label="Close">
5858
<lucide-icon [img]="icons.X" [size]="16"></lucide-icon>
5959
</button>
6060
</div>
61-
61+
6262
<div class="dialog-body">
6363
<p class="confirm-message">{{ data.message }}</p>
64-
<p class="confirm-detail" *ngIf="data.detail">{{ data.detail }}</p>
64+
@if (data.detail) {
65+
<p class="confirm-detail">{{ data.detail }}</p>
66+
}
6567
</div>
66-
68+
6769
<div class="dialog-actions">
6870
<button class="pc-btn"
69-
[attr.cdkFocusInitial]="focusCancel ? '' : null"
70-
(click)="cancel()">
71+
[attr.cdkFocusInitial]="focusCancel ? '' : null"
72+
(click)="cancel()">
7173
{{ data.cancelLabel || 'Cancel' }}
7274
</button>
7375
<button class="pc-btn"
74-
[class.pc-btn-primary]="!data.danger"
75-
[class.pc-btn-danger]="data.danger"
76-
[attr.cdkFocusInitial]="focusCancel ? null : ''"
77-
(click)="confirm()">
76+
[class.pc-btn-primary]="!data.danger"
77+
[class.pc-btn-danger]="data.danger"
78+
[attr.cdkFocusInitial]="focusCancel ? null : ''"
79+
(click)="confirm()">
7880
{{ data.confirmLabel || 'OK' }}
7981
</button>
8082
</div>
8183
</div>
82-
`,
84+
`,
8385
styles: [`
8486
.confirm-dialog {
8587
min-width: 360px;

src/Papercut.Service/Web/src/app/components/connection-status/connection-status.component.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,14 @@ import { SignalRService } from '../../services/signalr.service';
3131
selector: 'app-connection-status',
3232
imports: [CommonModule, MatTooltipModule, LucideAngularModule],
3333
template: `
34-
<span class="conn-offline"
35-
*ngIf="isOffline$ | async"
36-
role="status"
37-
matTooltip="Disconnected from the Papercut service — retrying…">
38-
<lucide-icon [img]="icons.PlugZap" [size]="15"></lucide-icon>
39-
</span>
40-
`,
34+
@if (isOffline$ | async) {
35+
<span class="conn-offline"
36+
role="status"
37+
matTooltip="Disconnected from the Papercut service — retrying…">
38+
<lucide-icon [img]="icons.PlugZap" [size]="15"></lucide-icon>
39+
</span>
40+
}
41+
`,
4142
styles: [`
4243
:host { display: contents; }
4344

src/Papercut.Service/Web/src/app/components/file-downloader/file-downloader.component.ts

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -174,44 +174,46 @@ export class FileDownloaderService {
174174
LucideAngularModule
175175
],
176176
template: `
177-
<div *ngIf="downloads.length > 0" class="fixed bottom-4 right-4 z-50 space-y-2">
178-
<div *ngFor="let download of downloads"
179-
class="rounded-lg shadow-lg border p-3 min-w-80 max-w-96"
180-
style="background: var(--pc-surface); border-color: var(--pc-border);">
181-
<div class="flex items-center gap-3">
182-
<lucide-icon [img]="getStatusIcon(download.status)" [size]="18"
177+
@if (downloads.length > 0) {
178+
<div class="fixed bottom-4 right-4 z-50 space-y-2">
179+
@for (download of downloads; track download) {
180+
<div
181+
class="rounded-lg shadow-lg border p-3 min-w-80 max-w-96"
182+
style="background: var(--pc-surface); border-color: var(--pc-border);">
183+
<div class="flex items-center gap-3">
184+
<lucide-icon [img]="getStatusIcon(download.status)" [size]="18"
183185
[ngClass]="{
184186
'text-accent-text': download.status === 'downloading',
185187
'text-ok': download.status === 'completed',
186188
'text-danger': download.status === 'error'
187189
}"></lucide-icon>
188-
189-
<div class="flex-1 min-w-0">
190-
<div class="text-sm font-medium text-ink-strong truncate">
191-
{{ download.filename }}
192-
</div>
193-
<div class="text-xs text-muted">
194-
{{ getStatusText(download) }}
190+
<div class="flex-1 min-w-0">
191+
<div class="text-sm font-medium text-ink-strong truncate">
192+
{{ download.filename }}
193+
</div>
194+
<div class="text-xs text-muted">
195+
{{ getStatusText(download) }}
196+
</div>
197+
<!-- Progress Bar -->
198+
@if (download.status === 'downloading') {
199+
<mat-progress-bar
200+
mode="determinate"
201+
[value]="download.progress"
202+
class="mt-1">
203+
</mat-progress-bar>
204+
}
205+
</div>
206+
<button mat-icon-button
207+
(click)="removeDownload(download.id)"
208+
class="text-faint hover:text-muted">
209+
<lucide-icon [img]="icons.X" [size]="16"></lucide-icon>
210+
</button>
195211
</div>
196-
197-
<!-- Progress Bar -->
198-
<mat-progress-bar
199-
*ngIf="download.status === 'downloading'"
200-
mode="determinate"
201-
[value]="download.progress"
202-
class="mt-1">
203-
</mat-progress-bar>
204212
</div>
205-
206-
<button mat-icon-button
207-
(click)="removeDownload(download.id)"
208-
class="text-faint hover:text-muted">
209-
<lucide-icon [img]="icons.X" [size]="16"></lucide-icon>
210-
</button>
211-
</div>
213+
}
212214
</div>
213-
</div>
214-
`,
215+
}
216+
`,
215217
styles: [`
216218
:host {
217219
position: fixed;

src/Papercut.Service/Web/src/app/components/forward-dialog/forward-dialog.component.ts

Lines changed: 36 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
// limitations under the License.
1717

1818
import { Component, Inject } from '@angular/core';
19-
import { CommonModule } from '@angular/common';
19+
2020
import { FormsModule } from '@angular/forms';
2121
import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from '@angular/material/dialog';
2222
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
@@ -52,7 +52,7 @@ const EMAIL_REGEX = /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i;
5252
*/
5353
@Component({
5454
selector: 'app-forward-dialog',
55-
imports: [CommonModule, FormsModule, MatDialogModule, MatProgressSpinnerModule, LucideAngularModule],
55+
imports: [FormsModule, MatDialogModule, MatProgressSpinnerModule, LucideAngularModule],
5656
template: `
5757
<div class="forward-dialog">
5858
<div class="dialog-header">
@@ -62,31 +62,35 @@ const EMAIL_REGEX = /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i;
6262
<lucide-icon [img]="icons.X" [size]="16"></lucide-icon>
6363
</button>
6464
</div>
65-
66-
<div class="dialog-subject" *ngIf="data.subject">{{ data.subject }}</div>
67-
65+
66+
@if (data.subject) {
67+
<div class="dialog-subject">{{ data.subject }}</div>
68+
}
69+
6870
<div class="dialog-body">
6971
<div class="field-grid">
70-
<ng-container *ngIf="availableRules.length > 0">
72+
@if (availableRules.length > 0) {
7173
<label class="field-label" for="fwd-rule">Rule</label>
7274
<select id="fwd-rule" class="pc-input" [(ngModel)]="selectedRuleIndex"
73-
(ngModelChange)="applyRule($event)" [disabled]="isSending">
75+
(ngModelChange)="applyRule($event)" [disabled]="isSending">
7476
<option [ngValue]="-1">(populate from a saved rule)</option>
75-
<option *ngFor="let rule of availableRules; let i = index" [ngValue]="i">
76-
{{ rule.name || summarizeRule(rule) }}
77-
</option>
77+
@for (rule of availableRules; track rule; let i = $index) {
78+
<option [ngValue]="i">
79+
{{ rule.name || summarizeRule(rule) }}
80+
</option>
81+
}
7882
</select>
79-
</ng-container>
80-
83+
}
84+
8185
<label class="field-label" for="fwd-server">Server</label>
8286
<div class="field-inline">
8387
<input id="fwd-server" class="pc-input flex-1" [(ngModel)]="server"
84-
placeholder="smtp.example.com" [disabled]="isSending" />
88+
placeholder="smtp.example.com" [disabled]="isSending" />
8589
<label class="field-label-inline" for="fwd-port">Port</label>
8690
<input id="fwd-port" class="pc-input w-20" type="number" [(ngModel)]="port"
87-
min="1" max="65535" [disabled]="isSending" />
91+
min="1" max="65535" [disabled]="isSending" />
8892
</div>
89-
93+
9094
<span class="field-label"></span>
9195
<div class="field-inline field-checks">
9296
<label class="pc-check">
@@ -98,37 +102,40 @@ const EMAIL_REGEX = /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i;
98102
<span>Use Authentication</span>
99103
</label>
100104
</div>
101-
102-
<ng-container *ngIf="useAuthentication">
105+
106+
@if (useAuthentication) {
103107
<label class="field-label" for="fwd-user">Username</label>
104108
<input id="fwd-user" class="pc-input" [(ngModel)]="username" [disabled]="isSending" autocomplete="off" />
105-
106109
<label class="field-label" for="fwd-pass">Password</label>
107110
<input id="fwd-pass" class="pc-input" type="password" [(ngModel)]="password"
108-
[disabled]="isSending" autocomplete="new-password" />
109-
</ng-container>
110-
111+
[disabled]="isSending" autocomplete="new-password" />
112+
}
113+
111114
<label class="field-label" for="fwd-from">From</label>
112115
<input id="fwd-from" class="pc-input pc-mono" [(ngModel)]="fromEmail"
113-
placeholder="sender@example.com" [disabled]="isSending" />
114-
116+
placeholder="sender@example.com" [disabled]="isSending" />
117+
115118
<label class="field-label" for="fwd-to">To</label>
116119
<input id="fwd-to" class="pc-input pc-mono" [(ngModel)]="toEmail"
117-
placeholder="recipient@example.com" [disabled]="isSending" />
120+
placeholder="recipient@example.com" [disabled]="isSending" />
118121
</div>
119-
120-
<div class="dialog-error" *ngIf="error">{{ error }}</div>
122+
123+
@if (error) {
124+
<div class="dialog-error">{{ error }}</div>
125+
}
121126
</div>
122-
127+
123128
<div class="dialog-actions">
124129
<button class="pc-btn" (click)="cancel()" [disabled]="isSending">Cancel</button>
125130
<button class="pc-btn pc-btn-primary" (click)="send()" [disabled]="isSending">
126-
<mat-spinner *ngIf="isSending" diameter="14" strokeWidth="2"></mat-spinner>
131+
@if (isSending) {
132+
<mat-spinner diameter="14" strokeWidth="2"></mat-spinner>
133+
}
127134
<span>{{ isSending ? 'Sending…' : 'Send' }}</span>
128135
</button>
129136
</div>
130137
</div>
131-
`,
138+
`,
132139
styles: [`
133140
.forward-dialog {
134141
min-width: 420px;

0 commit comments

Comments
 (0)