diff --git a/src/app/layouts/app-layout/app-layout.html b/src/app/layouts/app-layout/app-layout.html index e8e82c7..635ffaa 100644 --- a/src/app/layouts/app-layout/app-layout.html +++ b/src/app/layouts/app-layout/app-layout.html @@ -1,3 +1,9 @@ + +
@@ -31,6 +37,10 @@ Admin users } + @if (changePasswordSuccess()) { @@ -40,6 +50,10 @@
} + @if (aboutOpen()) { + + } + @if (changePasswordOpen() && loggedUser()?.username; as username) { - ISTI-CNR +// SPDX-License-Identifier: AGPL-3.0-or-later +// Attribution term under AGPL-3.0 section 7(b): see LICENSE-ADDENDUM. + import { afterNextRender, ChangeDetectionStrategy, Component, inject, signal } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatIconModule } from '@angular/material/icon'; @@ -9,12 +13,13 @@ import { ChangePasswordRequest } from '@models/user'; import { Authorization } from '@services/authorization/authorization'; import { BlocksService } from '@services/blocks/blocks'; import { ContainersService } from '@services/containers/containers'; +import { AboutDialogComponent } from '@shared/about-dialog/about-dialog'; import { ChangePasswordDialogComponent } from '@shared/change-password-dialog/change-password-dialog'; import { scheduleSignalClear } from '@utilities/temporary-signal'; @Component({ selector: 'app-app-layout', - imports: [RouterOutlet, RouterLink, RouterLinkActive, ChangePasswordDialogComponent, MatButtonModule, MatIconModule, MatMenuModule, MatTabsModule, MatToolbarModule], + imports: [RouterOutlet, RouterLink, RouterLinkActive, AboutDialogComponent, ChangePasswordDialogComponent, MatButtonModule, MatIconModule, MatMenuModule, MatTabsModule, MatToolbarModule], templateUrl: './app-layout.html', styleUrl: './app-layout.css', changeDetection: ChangeDetectionStrategy.OnPush @@ -28,6 +33,7 @@ export class AppLayout { private containersService = inject(ContainersService); loggedUser = this.authService.loggedInUser; + aboutOpen = signal(false); changePasswordOpen = signal(false); changePasswordSaving = signal(false); changePasswordError = signal(null); @@ -58,6 +64,14 @@ logout() { }); } + openAboutDialog() { + this.aboutOpen.set(true); + } + + closeAboutDialog() { + this.aboutOpen.set(false); + } + openChangePasswordDialog() { if (!this.loggedUser()?.username?.trim()) return; this.changePasswordError.set(null); diff --git a/src/app/shared/about-dialog/about-dialog.css b/src/app/shared/about-dialog/about-dialog.css new file mode 100644 index 0000000..571e628 --- /dev/null +++ b/src/app/shared/about-dialog/about-dialog.css @@ -0,0 +1,133 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Lucio Lelii - ISTI-CNR + * SPDX-License-Identifier: AGPL-3.0-or-later + * Attribution term under AGPL-3.0 section 7(b): see LICENSE-ADDENDUM. + */ + +/* Matches the chrome of the password dialogs; kept separate because this one is not a form. */ +.about-dialog-backdrop { + position: fixed; + inset: 0; + z-index: 9999; + display: flex; + align-items: center; + justify-content: center; + padding: 16px; + background: rgba(15, 23, 42, 0.5); +} + +.about-dialog-modal { + width: min(560px, calc(100vw - 32px)); + max-height: min(80vh, 720px); + overflow: auto; + border: 1px solid #e2e8f0; + border-radius: 20px; + background: #ffffff; + box-shadow: 0 24px 64px rgba(15, 23, 42, 0.28); + padding: 24px; +} + +.about-dialog-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; +} + +.about-dialog-title { + margin: 0; + font-size: 20px; + font-weight: 600; + color: #0f172a; +} + +.about-dialog-subtitle { + margin: 4px 0 0; + font-size: 13px; + color: #64748b; +} + +.about-dialog-body { + margin-top: 20px; + display: flex; + flex-direction: column; + gap: 18px; + font-size: 13.5px; + line-height: 1.6; + color: #334155; +} + +.about-dialog-body p { + margin: 0; +} + +.about-dialog-section-title { + margin: 0 0 6px; + font-size: 12px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + color: #1d4ed8; +} + +.about-dialog-copyright { + font-weight: 600; + color: #0f172a; +} + +.about-dialog-affiliation { + font-size: 12.5px; + color: #64748b; +} + +.about-dialog-note { + font-size: 12.5px; + color: #64748b; + margin-bottom: 6px; +} + +.about-dialog-attribution { + margin: 0; + padding: 10px 14px; + border-left: 3px solid #2563eb; + border-radius: 0 8px 8px 0; + background: #f1f5f9; + font-style: italic; + color: #0f172a; +} + +.about-dialog-links { + margin: 0; + padding: 0; + list-style: none; + display: flex; + flex-direction: column; + gap: 6px; +} + +.about-dialog-links li { + display: flex; + align-items: center; + gap: 6px; + flex-wrap: wrap; +} + +.about-dialog-link-icon { + font-size: 18px; + width: 18px; + height: 18px; + color: #64748b; +} + +.about-dialog-body a { + color: #1d4ed8; + word-break: break-all; +} + +.about-dialog-body code { + padding: 1px 5px; + border: 1px solid #e2e8f0; + border-radius: 4px; + background: #f1f5f9; + font-size: 12px; +} diff --git a/src/app/shared/about-dialog/about-dialog.html b/src/app/shared/about-dialog/about-dialog.html new file mode 100644 index 0000000..d49d891 --- /dev/null +++ b/src/app/shared/about-dialog/about-dialog.html @@ -0,0 +1,66 @@ + + +
+
+
+
+

About HumAIn Flow

+

Licence, attribution and source code

+
+ +
+ +
+
+ +

{{ affiliation }}

+
+ +
+

Licence

+

+ HumAIn Flow is free software, released under the + {{ licenseName }}, + with an additional attribution term under section 7(b) of that licence. +

+
+ +
+

Attribution

+

+ This notice must be preserved in any work based on HumAIn Flow. +

+
{{ attribution }}
+
+ +
+

Source code

+

+ You are entitled to the complete source of the version you are using, including any + modification made to it. +

+ +
+ +
+

Citation

+

+ If you use HumAIn Flow in academic work, please cite it. The citation metadata is in the + CITATION.cff file of each repository. +

+
+
+
+
diff --git a/src/app/shared/about-dialog/about-dialog.ts b/src/app/shared/about-dialog/about-dialog.ts new file mode 100644 index 0000000..60796cb --- /dev/null +++ b/src/app/shared/about-dialog/about-dialog.ts @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2025-2026 Lucio Lelii - ISTI-CNR +// SPDX-License-Identifier: AGPL-3.0-or-later +// Attribution term under AGPL-3.0 section 7(b): see LICENSE-ADDENDUM. + +import { ChangeDetectionStrategy, Component, output } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; + +/** + * The application's Appropriate Legal Notices, in the sense section 0 of the GNU AGPL gives that + * term. + * + * This is not decoration. Two obligations are discharged here and nowhere else in the interface: + * the attribution that LICENSE-ADDENDUM requires every derivative to keep visible to its users, + * and the offer of the Corresponding Source that section 13 owes to anyone interacting with the + * program over a network. Changing the wording is fine; removing either one puts the deployment + * out of compliance with the licence it ships under. + */ +@Component({ + selector: 'app-about-dialog', + imports: [MatButtonModule, MatIconModule], + templateUrl: './about-dialog.html', + styleUrl: './about-dialog.css', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AboutDialogComponent { + readonly closed = output(); + + readonly copyright = 'Copyright © 2025-2026 Lucio Lelii'; + readonly affiliation = + 'Institute of Information Science and Technologies "A. Faedo" (ISTI-CNR), Pisa, Italy'; + + readonly attribution = 'Based on HumAIn Flow, originally developed by Lucio Lelii (ISTI-CNR)'; + + readonly licenseName = 'GNU Affero General Public License v3.0 or later'; + readonly licenseUrl = 'https://www.gnu.org/licenses/agpl-3.0.html'; + + readonly sources = [ + { label: 'Web interface', url: 'https://github.com/luciolelii/humanaiFlow-angular' }, + { label: 'Backend service', url: 'https://github.com/luciolelii/humainflow-service' } + ]; + + close(event?: Event) { + event?.stopPropagation(); + this.closed.emit(); + } +}