Skip to content

Commit 2e0c520

Browse files
authored
UFAL/Item versioning table view edit (#999)
* Changed method of rendering table with versions to toggle dropdown * Refactor by Copilot's suggestions * Added version history as item-field * Divided Admin view and Anonymous/User view of version history and changed UI of item-versions-field * Added clarin-item-versions-field to publication template, added spacing between icon and heading * Added keyboard event handling (enter) * Edit of manual Observable creation * clarin-item-versions-field extends item-versions to avoid duplicate code * reverted new admin view of item-versions history to usual look * Usage of more common way for checking if is Admin * removed duplicate interface * change to duplicate interfaces, but avoiding any type * implementation of more effective method concating strings * Added documentation for the need of protected access modifiers * Fixed documentation to valid JSDoc tag, prevention for not initializing this.isAdmin$ * Changed isAdmin warpper to section * added calc() to scss * Fix of uninitialized isAdmin handling * Optimization of hasDraftVersion logic - exposing it as an observable * Fixed logic checking value of this.hasDraftVersion$ * Reverted css, removed unclear typing from new methods * Fixed versionItem type handling * Changed expand/collapse text to translation keys * fix: avoid array mutation by creating copy before reversing version list * perf: optimize version history by pre-computing workspace/workflow IDs and using trackBy
1 parent ea6ab24 commit 2e0c520

File tree

10 files changed

+430
-155
lines changed

10 files changed

+430
-155
lines changed

src/app/item-page/item-page.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ import { NgChartsModule } from 'ng2-charts';
7878
import { ClarinGenericItemFieldComponent } from './simple/field-components/clarin-generic-item-field/clarin-generic-item-field.component';
7979
import { ClarinCollectionsItemFieldComponent } from './simple/field-components/clarin-collections-item-field/clarin-collections-item-field.component';
8080
import { ClarinFilesItemFieldComponent } from './simple/field-components/clarin-files-item-field/clarin-files-item-field.component';
81+
import { ClarinItemVersionsFieldComponent } from './simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component';
8182
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
8283
import {PreviewSectionComponent} from './simple/field-components/preview-section/preview-section.component';
8384
import {
@@ -149,6 +150,7 @@ const DECLARATIONS = [
149150
ClarinGenericItemFieldComponent,
150151
ClarinCollectionsItemFieldComponent,
151152
ClarinFilesItemFieldComponent,
153+
ClarinItemVersionsFieldComponent,
152154
ClarinSponsorItemFieldComponent,
153155
PreviewSectionComponent,
154156
FileDescriptionComponent,
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
<div class="row clarin-item-page-field justify-content-start" *ngIf="showMetadataValue | async">
2+
<div class="col-lg-3 col-2-5 d-flex align-self-start">
3+
<div><i [class]="'fas ' + iconName + ' fa-xs'"></i></div>
4+
<!-- Toggle Header -->
5+
<div
6+
class="pl-1 d-flex align-items-center"
7+
(click)="toggleVersionHistory()"
8+
(keydown.enter)="toggleVersionHistory()"
9+
role="button"
10+
tabindex="0"
11+
[attr.aria-expanded]="showVersionHistory"
12+
aria-controls="version-history-content"
13+
[attr.aria-label]="getToggleAriaLabel()">
14+
<b class="mr-1">{{ "item.version.history.head" | translate }}</b>
15+
<i class="fas" [ngClass]="showVersionHistory ? 'fa-chevron-up' : 'fa-chevron-down'"></i>
16+
</div>
17+
</div>
18+
19+
<!-- Collapsible Content -->
20+
<div
21+
id="version-history-content"
22+
class="col-lg-9 collapse"
23+
[ngClass]="{'show': showVersionHistory}"
24+
[attr.aria-hidden]="!showVersionHistory">
25+
<div *ngIf="enhancedVersions$ | async as enhancedVersions">
26+
<ul class="list-unstyled dropdown-versions">
27+
<li *ngFor="let enhancedVersion of enhancedVersions; trackBy: trackByVersionId"
28+
[id]="'version-row-' + enhancedVersion.version.id">
29+
<ng-container *ngIf="(enhancedVersion.versionItem$ | async)?.payload as versionItem">
30+
<ng-container *ngIf="(enhancedVersion.workspaceId$ | async) || (enhancedVersion.workflowId$ | async); else itemNameWithLink">
31+
{{ getVersionItemDisplayName(versionItem) }}
32+
</ng-container>
33+
<ng-template #itemNameWithLink>
34+
<a [routerLink]="getVersionRoute(enhancedVersion.version.id)">
35+
{{ getVersionItemDisplayName(versionItem) }}
36+
</a>
37+
</ng-template>
38+
<span *ngIf="enhancedVersion.isCurrentVersion">*</span>
39+
</ng-container>
40+
</li>
41+
</ul>
42+
<div>*&nbsp;{{ "item.version.history.selected" | translate }}</div>
43+
</div>
44+
</div>
45+
</div>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
@import '../../item-page.component.scss';
2+
3+
@media (min-width: 992px) {
4+
.col-2-5 {
5+
flex: 0 0 20%;
6+
max-width: 20%;
7+
}
8+
}
9+
10+
.dropdown-versions {
11+
background-color: #f8f9fa;
12+
border-radius: 4px;
13+
padding: 2%;
14+
15+
max-height: calc(var(--ds-dso-selector-list-max-height) / 2);
16+
overflow-y: auto;
17+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import { Component, Input, OnInit } from '@angular/core';
2+
import { Observable, of, combineLatest } from 'rxjs';
3+
import { map, switchMap } from 'rxjs/operators';
4+
import { ItemVersionsComponent } from '../../../versions/item-versions.component';
5+
import { Item } from '../../../../core/shared/item.model';
6+
import { Version } from '../../../../core/shared/version.model';
7+
import { RemoteData } from '../../../../core/data/remote-data';
8+
9+
/**
10+
* Local type definition matching the parent component's VersionsDTO structure
11+
*/
12+
interface VersionsDTO {
13+
totalElements: number;
14+
versionDTOs: VersionDTO[];
15+
}
16+
17+
interface VersionDTO {
18+
version: Version;
19+
canEditVersion: Observable<boolean>;
20+
canDeleteVersion: Observable<boolean>;
21+
}
22+
23+
/**
24+
* Enhanced VersionDTO with pre-computed workspace/workflow IDs for template optimization
25+
*/
26+
interface EnhancedVersionDTO extends VersionDTO {
27+
versionItem$: Observable<RemoteData<Item>>;
28+
workspaceId$: Observable<string | undefined>;
29+
workflowId$: Observable<string | undefined>;
30+
isCurrentVersion: boolean;
31+
}
32+
33+
/**
34+
* Clarin-specific field component for User/Anonymous view of item version history that extends ItemVersionsComponent
35+
*/
36+
@Component({
37+
selector: 'ds-clarin-item-versions-field',
38+
templateUrl: './clarin-item-versions-field.component.html',
39+
styleUrls: ['./clarin-item-versions-field.component.scss']
40+
})
41+
export class ClarinItemVersionsFieldComponent extends ItemVersionsComponent implements OnInit {
42+
43+
/**
44+
* Icon name for the clarin field
45+
*/
46+
@Input() iconName?: string;
47+
48+
/**
49+
* Toggle state for version history display
50+
*/
51+
showVersionHistory = false;
52+
53+
/**
54+
* Observable to check if metadata field should be shown - clarin-specific implementation
55+
* Returns true if there are multiple versions to display
56+
*/
57+
showMetadataValue: Observable<boolean>;
58+
59+
/**
60+
* Enhanced versions with pre-computed workspace/workflow IDs
61+
*/
62+
enhancedVersions$: Observable<EnhancedVersionDTO[]>;
63+
64+
ngOnInit(): void {
65+
// Call parent's ngOnInit first to set up all the observables
66+
super.ngOnInit();
67+
68+
// Set up clarin-specific showMetadataValue logic
69+
if (this.versionsDTO$) {
70+
this.showMetadataValue = this.versionsDTO$.pipe(
71+
map((versionsDTO: VersionsDTO) => versionsDTO && versionsDTO.totalElements > 1)
72+
);
73+
74+
// Pre-compute workspace/workflow IDs to optimize template performance
75+
this.enhancedVersions$ = combineLatest([
76+
this.versionsDTO$,
77+
this.versionRD$
78+
]).pipe(
79+
map(([versionsDTO, versionRD]) => {
80+
const currentVersionId = versionRD?.payload?.id;
81+
return versionsDTO.versionDTOs.map(versionDTO => {
82+
const versionItem$ = versionDTO.version.item;
83+
const workspaceId$ = (this.hasDraftVersion$ ?? of(false)).pipe(
84+
switchMap(hasDraftVersion =>
85+
hasDraftVersion ? this.getWorkspaceId(versionItem$) : of(undefined)
86+
)
87+
);
88+
const workflowId$ = workspaceId$.pipe(
89+
switchMap((workspaceId) =>
90+
workspaceId ? of(undefined) : this.getWorkflowId(versionItem$)
91+
)
92+
);
93+
return {
94+
...versionDTO,
95+
versionItem$,
96+
workspaceId$,
97+
workflowId$,
98+
isCurrentVersion: versionDTO.version.id === currentVersionId
99+
} as EnhancedVersionDTO;
100+
});
101+
})
102+
);
103+
} else {
104+
// Fallback: check if isAdmin$ is available, otherwise hide the component
105+
this.showMetadataValue = this.isAdmin$ ? this.isAdmin$ : of(false);
106+
}
107+
}
108+
109+
/**
110+
* Toggle the visibility of version history
111+
*/
112+
toggleVersionHistory(): void {
113+
this.showVersionHistory = !this.showVersionHistory;
114+
}
115+
116+
/**
117+
* Get the display name for a version item
118+
* @param versionItem the item to get the name for
119+
*/
120+
getVersionItemDisplayName(versionItem: Item): string {
121+
return versionItem?.firstMetadataValue('dc.title') || versionItem?.name || 'Untitled';
122+
}
123+
124+
/**
125+
* Get the appropriate aria-label for the toggle button
126+
* @returns The aria-label text for accessibility
127+
*/
128+
getToggleAriaLabel(): string {
129+
const action = this.showVersionHistory
130+
? this.translateService.instant('item.version.history.collapse')
131+
: this.translateService.instant('item.version.history.expand');
132+
const history = this.translateService.instant('item.version.history.label');
133+
return `${action} ${history}`;
134+
}
135+
136+
/**
137+
* Get workspace ID for a version item if there's a draft version, otherwise return undefined
138+
* This method optimizes the template logic by pre-computing the conditional check
139+
* @param versionItem the version item's observable
140+
*/
141+
getVersionWorkspaceId(versionItem: Observable<Item>): Observable<string | undefined> {
142+
return (this.hasDraftVersion$ ?? of(false)).pipe(
143+
switchMap(hasDraftVersion =>
144+
hasDraftVersion ? this.getWorkspaceId(versionItem) : of(undefined)
145+
)
146+
);
147+
}
148+
149+
/**
150+
* Get workflow ID for a version item if workspace ID is not available
151+
* This method optimizes the template logic by handling the conditional workflow ID logic
152+
* @param versionItem the version item's observable
153+
* @param workspaceId$ the workspace ID observable
154+
*/
155+
getVersionWorkflowId(versionItem: Observable<Item>, workspaceId$: Observable<string | undefined>): Observable<string | undefined> {
156+
return workspaceId$.pipe(
157+
switchMap((workspaceId) =>
158+
workspaceId ? of(undefined) : this.getWorkflowId(versionItem)
159+
)
160+
);
161+
}
162+
163+
/**
164+
* TrackBy function for version list to optimize *ngFor performance
165+
* @param index the index of the item
166+
* @param versionDTO the version DTO to track
167+
*/
168+
trackByVersionId(index: number, versionDTO: EnhancedVersionDTO): string {
169+
return versionDTO.version.id;
170+
}
171+
}

src/app/item-page/simple/item-types/publication/publication.component.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@
107107
[iconName]="'fa-sitemap'"
108108
[separator]="'<br />'">
109109
</ds-clarin-collections-item-field>
110+
<ds-clarin-item-versions-field
111+
[item]="object"
112+
[iconName]="'fa-history'">
113+
</ds-clarin-item-versions-field>
110114
<div class="clarin-item-page-field">
111115
<a [routerLink]="[itemPageRoute + '/full']">
112116
{{"item.page.link.full" | translate}}

src/app/item-page/simple/item-types/untyped-item/untyped-item.component.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@
114114
[iconName]="'fa-sitemap'"
115115
[separator]="'<br />'">
116116
</ds-clarin-collections-item-field>
117+
<ds-clarin-item-versions-field
118+
[item]="object"
119+
[iconName]="'fa-history'">
120+
</ds-clarin-item-versions-field>
117121
<div class="clarin-item-page-field">
118122
<a [routerLink]="[itemPageRoute + '/full']">
119123
{{"item.page.link.full" | translate}}

0 commit comments

Comments
 (0)