diff --git a/src/app/item-page/item-page.module.ts b/src/app/item-page/item-page.module.ts index 18bbc4fd606..8d3aae57850 100644 --- a/src/app/item-page/item-page.module.ts +++ b/src/app/item-page/item-page.module.ts @@ -77,6 +77,7 @@ import { NgChartsModule } from 'ng2-charts'; import { ClarinGenericItemFieldComponent } from './simple/field-components/clarin-generic-item-field/clarin-generic-item-field.component'; import { ClarinCollectionsItemFieldComponent } from './simple/field-components/clarin-collections-item-field/clarin-collections-item-field.component'; import { ClarinFilesItemFieldComponent } from './simple/field-components/clarin-files-item-field/clarin-files-item-field.component'; +import { ClarinItemVersionsFieldComponent } from './simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component'; import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import {PreviewSectionComponent} from './simple/field-components/preview-section/preview-section.component'; import { @@ -147,6 +148,7 @@ const DECLARATIONS = [ ClarinGenericItemFieldComponent, ClarinCollectionsItemFieldComponent, ClarinFilesItemFieldComponent, + ClarinItemVersionsFieldComponent, ClarinSponsorItemFieldComponent, PreviewSectionComponent, FileDescriptionComponent, diff --git a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html new file mode 100644 index 00000000000..5e2f6dd6b36 --- /dev/null +++ b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.html @@ -0,0 +1,45 @@ +
+
+
+ +
+ {{ "item.version.history.head" | translate }} + +
+
+ + +
+
+ +
* {{ "item.version.history.selected" | translate }}
+
+
+
diff --git a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.scss b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.scss new file mode 100644 index 00000000000..f60052e1dc9 --- /dev/null +++ b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.scss @@ -0,0 +1,17 @@ +@import '../../item-page.component.scss'; + +@media (min-width: 992px) { + .col-2-5 { + flex: 0 0 20%; + max-width: 20%; + } +} + +.dropdown-versions { + background-color: #f8f9fa; + border-radius: 4px; + padding: 2%; + + max-height: calc(var(--ds-dso-selector-list-max-height) / 2); + overflow-y: auto; +} diff --git a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts new file mode 100644 index 00000000000..5ed269c5988 --- /dev/null +++ b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts @@ -0,0 +1,171 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { Observable, of, combineLatest } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; +import { ItemVersionsComponent } from '../../../versions/item-versions.component'; +import { Item } from '../../../../core/shared/item.model'; +import { Version } from '../../../../core/shared/version.model'; +import { RemoteData } from '../../../../core/data/remote-data'; + +/** + * Local type definition matching the parent component's VersionsDTO structure + */ +interface VersionsDTO { + totalElements: number; + versionDTOs: VersionDTO[]; +} + +interface VersionDTO { + version: Version; + canEditVersion: Observable; + canDeleteVersion: Observable; +} + +/** + * Enhanced VersionDTO with pre-computed workspace/workflow IDs for template optimization + */ +interface EnhancedVersionDTO extends VersionDTO { + versionItem$: Observable>; + workspaceId$: Observable; + workflowId$: Observable; + isCurrentVersion: boolean; +} + +/** + * Clarin-specific field component for User/Anonymous view of item version history that extends ItemVersionsComponent + */ +@Component({ + selector: 'ds-clarin-item-versions-field', + templateUrl: './clarin-item-versions-field.component.html', + styleUrls: ['./clarin-item-versions-field.component.scss'] +}) +export class ClarinItemVersionsFieldComponent extends ItemVersionsComponent implements OnInit { + + /** + * Icon name for the clarin field + */ + @Input() iconName?: string; + + /** + * Toggle state for version history display + */ + showVersionHistory = false; + + /** + * Observable to check if metadata field should be shown - clarin-specific implementation + * Returns true if there are multiple versions to display + */ + showMetadataValue: Observable; + + /** + * Enhanced versions with pre-computed workspace/workflow IDs + */ + enhancedVersions$: Observable; + + ngOnInit(): void { + // Call parent's ngOnInit first to set up all the observables + super.ngOnInit(); + + // Set up clarin-specific showMetadataValue logic + if (this.versionsDTO$) { + this.showMetadataValue = this.versionsDTO$.pipe( + map((versionsDTO: VersionsDTO) => versionsDTO && versionsDTO.totalElements > 1) + ); + + // Pre-compute workspace/workflow IDs to optimize template performance + this.enhancedVersions$ = combineLatest([ + this.versionsDTO$, + this.versionRD$ + ]).pipe( + map(([versionsDTO, versionRD]) => { + const currentVersionId = versionRD?.payload?.id; + return versionsDTO.versionDTOs.map(versionDTO => { + const versionItem$ = versionDTO.version.item; + const workspaceId$ = (this.hasDraftVersion$ ?? of(false)).pipe( + switchMap(hasDraftVersion => + hasDraftVersion ? this.getWorkspaceId(versionItem$) : of(undefined) + ) + ); + const workflowId$ = workspaceId$.pipe( + switchMap((workspaceId) => + workspaceId ? of(undefined) : this.getWorkflowId(versionItem$) + ) + ); + return { + ...versionDTO, + versionItem$, + workspaceId$, + workflowId$, + isCurrentVersion: versionDTO.version.id === currentVersionId + } as EnhancedVersionDTO; + }); + }) + ); + } else { + // Fallback: check if isAdmin$ is available, otherwise hide the component + this.showMetadataValue = this.isAdmin$ ? this.isAdmin$ : of(false); + } + } + + /** + * Toggle the visibility of version history + */ + toggleVersionHistory(): void { + this.showVersionHistory = !this.showVersionHistory; + } + + /** + * Get the display name for a version item + * @param versionItem the item to get the name for + */ + getVersionItemDisplayName(versionItem: Item): string { + return versionItem?.firstMetadataValue('dc.title') || versionItem?.name || 'Untitled'; + } + + /** + * Get the appropriate aria-label for the toggle button + * @returns The aria-label text for accessibility + */ + getToggleAriaLabel(): string { + const action = this.showVersionHistory + ? this.translateService.instant('item.version.history.collapse') + : this.translateService.instant('item.version.history.expand'); + const history = this.translateService.instant('item.version.history.label'); + return `${action} ${history}`; + } + + /** + * Get workspace ID for a version item if there's a draft version, otherwise return undefined + * This method optimizes the template logic by pre-computing the conditional check + * @param versionItem the version item's observable + */ + getVersionWorkspaceId(versionItem: Observable): Observable { + return (this.hasDraftVersion$ ?? of(false)).pipe( + switchMap(hasDraftVersion => + hasDraftVersion ? this.getWorkspaceId(versionItem) : of(undefined) + ) + ); + } + + /** + * Get workflow ID for a version item if workspace ID is not available + * This method optimizes the template logic by handling the conditional workflow ID logic + * @param versionItem the version item's observable + * @param workspaceId$ the workspace ID observable + */ + getVersionWorkflowId(versionItem: Observable, workspaceId$: Observable): Observable { + return workspaceId$.pipe( + switchMap((workspaceId) => + workspaceId ? of(undefined) : this.getWorkflowId(versionItem) + ) + ); + } + + /** + * TrackBy function for version list to optimize *ngFor performance + * @param index the index of the item + * @param versionDTO the version DTO to track + */ + trackByVersionId(index: number, versionDTO: EnhancedVersionDTO): string { + return versionDTO.version.id; + } +} diff --git a/src/app/item-page/simple/item-types/publication/publication.component.html b/src/app/item-page/simple/item-types/publication/publication.component.html index ef32bd9c880..aae2dfa2c7f 100644 --- a/src/app/item-page/simple/item-types/publication/publication.component.html +++ b/src/app/item-page/simple/item-types/publication/publication.component.html @@ -107,6 +107,10 @@ [iconName]="'fa-sitemap'" [separator]="'
'"> + +
{{"item.page.link.full" | translate}} diff --git a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html index 7ce77f678f4..55eb672b9d2 100644 --- a/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html +++ b/src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html @@ -114,6 +114,10 @@ [iconName]="'fa-sitemap'" [separator]="'
'"> + +
{{"item.page.link.full" | translate}} diff --git a/src/app/item-page/versions/item-versions.component.html b/src/app/item-page/versions/item-versions.component.html index 09e8f21e72d..34e9444ed20 100644 --- a/src/app/item-page/versions/item-versions.component.html +++ b/src/app/item-page/versions/item-versions.component.html @@ -1,149 +1,157 @@ -
-
-

{{ "item.version.history.head" | translate }}

- - {{ "item.version.history.selected.alert" | translate : { version: itemVersion.version } }} - - - - - - - - - - - - - - + + +
{{ "item.version.history.table.version" | translate }}{{ "item.version.history.table.editor" | translate }}{{ "item.version.history.table.date" | translate }}{{ "item.version.history.table.summary" | translate }}
- - +
+
+
+

{{ "item.version.history.head" | translate }}

+ + {{ "item.version.history.selected.alert" | translate : { version: itemVersion.version } }} + + + + + + + + + + + + + + - - - + + + - - -
{{ "item.version.history.table.version" | translate }}{{ "item.version.history.table.editor" | translate }}{{ "item.version.history.table.date" | translate }}{{ "item.version.history.table.summary" | translate }}
+ + *ngVar="((hasDraftVersion$ | async) ? getWorkspaceId(versionDTO.version.item) : undefined) as workspaceId$"> + -
+
- - - {{ versionDTO.version.version }} - - - {{ versionDTO.version.version }} - - * + + + + + {{ versionItem.firstMetadataValue('dc.title') || versionItem.name || 'Untitled' }} + + + + + + {{ versionItem.firstMetadataValue('dc.title') || versionItem.name || 'Untitled' }} + + + * - - {{ "item.version.history.table.workspaceItem" | translate }} - + + {{ "item.version.history.table.workspaceItem" | translate }} + - - {{ "item.version.history.table.workflowItem" | translate }} - + + {{ "item.version.history.table.workflowItem" | translate }} + -
+
-
+
-
- - - - - - - - - - -
+ + + + + + + + +
-
+ +
- -
- {{ versionDTO.version.submitterName }} - - {{ versionDTO.version.created | date : 'yyyy-MM-dd HH:mm:ss' }} - -
- - {{ versionDTO.version.summary }} - - - -
+
+ {{ versionDTO.version.submitterName }} + + {{ versionDTO.version.created | date : 'yyyy-MM-dd HH:mm:ss' }} + +
+ + {{ versionDTO.version.summary }} + + + +
-
- - - - - - - - - -
-
-
* {{ "item.version.history.selected" | translate }}
-
+
+ + + + + + + + + +
+
+
* {{ "item.version.history.selected" | translate }}
+
+
-
- - - - + + + + + diff --git a/src/app/item-page/versions/item-versions.component.ts b/src/app/item-page/versions/item-versions.component.ts index 500cd69ffc4..0d90f24d2ce 100644 --- a/src/app/item-page/versions/item-versions.component.ts +++ b/src/app/item-page/versions/item-versions.component.ts @@ -139,6 +139,12 @@ export class ItemVersionsComponent implements OnDestroy, OnInit { */ hasDraftVersion$: Observable; + /** + * Check if the current user is an admin + * Used to control component visibility + */ + isAdmin$: Observable; + /** * The amount of versions to display per page */ @@ -196,25 +202,25 @@ export class ItemVersionsComponent implements OnDestroy, OnInit { * Loading of the name from the items handles for the items which are stored in the metadata dc.relation.replaces` and * `dc.relation.isreplacedby` * Names are stored in this dict to avoid endless calling rest API to get the name of the Item. - * @private - */ - private nameCache: { [handle: string]: string } = {}; - - constructor(private versionHistoryService: VersionHistoryDataService, - private versionService: VersionDataService, - private itemService: ItemDataService, - private paginationService: PaginationService, - private formBuilder: UntypedFormBuilder, - private modalService: NgbModal, - private notificationsService: NotificationsService, - private translateService: TranslateService, - private router: Router, - private itemVersionShared: ItemVersionsSharedService, - private authorizationService: AuthorizationDataService, - private workspaceItemDataService: WorkspaceitemDataService, - private workflowItemDataService: WorkflowItemDataService, - private configurationService: ConfigurationDataService, - private dsoNameService: DSONameService + * @access protected - Accessible to extending components that may need to cache item names from handles + */ + protected nameCache: { [handle: string]: string } = {}; + + constructor(protected versionHistoryService: VersionHistoryDataService, + protected versionService: VersionDataService, + protected itemService: ItemDataService, + protected paginationService: PaginationService, + protected formBuilder: UntypedFormBuilder, + protected modalService: NgbModal, + protected notificationsService: NotificationsService, + protected translateService: TranslateService, + protected router: Router, + protected itemVersionShared: ItemVersionsSharedService, + protected authorizationService: AuthorizationDataService, + protected workspaceItemDataService: WorkspaceitemDataService, + protected workflowItemDataService: WorkflowItemDataService, + protected configurationService: ConfigurationDataService, + protected dsoNameService: DSONameService ) { } @@ -465,7 +471,7 @@ export class ItemVersionsComponent implements OnDestroy, OnInit { getRemoteDataPayload(), map((versions: PaginatedList) => ({ totalElements: versions.totalElements, - versionDTOs: (versions?.page ?? []).map((version: Version) => ({ + versionDTOs: [...(versions?.page ?? [])].reverse().map((version: Version) => ({ version: version, canEditVersion: this.canEditVersion$(version), canDeleteVersion: this.canDeleteVersion$(version), @@ -538,6 +544,9 @@ export class ItemVersionsComponent implements OnDestroy, OnInit { this.canCreateVersion$ = this.authorizationService.isAuthorized(FeatureID.CanCreateVersion, this.item.self); + // Initialize admin check for component visibility + this.isAdmin$ = this.authorizationService.isAuthorized(FeatureID.AdministratorOf); + // If there is a draft item in the version history the 'Create version' button is disabled and a different tooltip message is shown this.hasDraftVersion$ = this.versionHistoryRD$.pipe( getFirstSucceededRemoteDataPayload(), diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index f928e07d09f..a7611335547 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -3998,6 +3998,15 @@ // "item.version.history.head": "Version History", "item.version.history.head": "Historie verzí", + // "item.version.history.label": "version history", + "item.version.history.label": "historie verzí", + + // "item.version.history.expand": "Expand", + "item.version.history.expand": "Rozbalit", + + // "item.version.history.collapse": "Collapse", + "item.version.history.collapse": "Sbalit", + // "item.version.history.return": "Back", "item.version.history.return": "Zpět", diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index c6006102d8d..8b4a37616b6 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -2665,6 +2665,12 @@ "item.version.history.head": "Version History", + "item.version.history.label": "version history", + + "item.version.history.expand": "Expand", + + "item.version.history.collapse": "Collapse", + "item.version.history.return": "Back", "item.version.history.selected": "Selected version",