Skip to content

Commit 6796924

Browse files
committed
Divided Admin view and Anonymous/User view of version history and changed UI of item-versions-field
1 parent fa5214e commit 6796924

10 files changed

Lines changed: 419 additions & 126 deletions

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import { NgChartsModule } from 'ng2-charts';
7777
import { ClarinGenericItemFieldComponent } from './simple/field-components/clarin-generic-item-field/clarin-generic-item-field.component';
7878
import { ClarinCollectionsItemFieldComponent } from './simple/field-components/clarin-collections-item-field/clarin-collections-item-field.component';
7979
import { ClarinFilesItemFieldComponent } from './simple/field-components/clarin-files-item-field/clarin-files-item-field.component';
80+
import { ClarinItemVersionsFieldComponent } from './simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component';
8081
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
8182
import {PreviewSectionComponent} from './simple/field-components/preview-section/preview-section.component';
8283
import {
@@ -147,6 +148,7 @@ const DECLARATIONS = [
147148
ClarinGenericItemFieldComponent,
148149
ClarinCollectionsItemFieldComponent,
149150
ClarinFilesItemFieldComponent,
151+
ClarinItemVersionsFieldComponent,
150152
ClarinSponsorItemFieldComponent,
151153
PreviewSectionComponent,
152154
FileDescriptionComponent,
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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 class="pl-1 d-flex align-items-center cursor-pointer" (click)="toggleVersionHistory()">
6+
<b class="mb-0 me-2">{{ "item.version.history.head" | translate }}</b>
7+
<i class="fas" [ngClass]="showVersionHistory ? 'fa-chevron-up' : 'fa-chevron-down'"></i>
8+
</div>
9+
</div>
10+
11+
<!-- Collapsible Content -->
12+
<div class="col-lg-9 collapse" [ngClass]="{'show': showVersionHistory}">
13+
<div *ngIf="(versionsDTO$ | async) as versionsDTO">
14+
<div *ngIf="(versionRD$ | async)?.payload as itemVersion">
15+
<ul class="list-unstyled dropdown-versions">
16+
<li *ngFor="let versionDTO of versionsDTO.versionDTOs" [id]="'version-row-' + versionDTO.version.id">
17+
<!-- Get the ID of the workspace/workflow item (`undefined` if they don't exist).
18+
Conditionals inside *ngVar are needed in order to avoid useless calls. -->
19+
<ng-container
20+
*ngVar="((hasDraftVersion$ | async) ? getWorkspaceId(versionDTO.version.item) : undefined) as workspaceId$">
21+
<ng-container
22+
*ngVar=" ((workspaceId$ | async) ? undefined : getWorkflowId(versionDTO.version.item)) as workflowId$">
23+
<span
24+
*ngIf="(workspaceId$ | async) || (workflowId$ | async); then itemNameWithoutLink else itemNameWithLink"></span>
25+
<ng-template #itemNameWithLink>
26+
<ng-container *ngIf="(versionDTO.version.item | async)?.payload as versionItem">
27+
<a [routerLink]="getVersionRoute(versionDTO.version.id)">
28+
{{ getVersionItemDisplayName(versionItem) }}
29+
</a>
30+
</ng-container>
31+
</ng-template>
32+
<ng-template #itemNameWithoutLink>
33+
<ng-container *ngIf="(versionDTO.version.item | async)?.payload as versionItem">
34+
{{ getVersionItemDisplayName(versionItem) }}
35+
</ng-container>
36+
</ng-template>
37+
<span *ngIf="versionDTO.version.id === itemVersion?.id">*</span>
38+
</ng-container>
39+
</ng-container>
40+
</li>
41+
</ul>
42+
<div>*&nbsp;{{ "item.version.history.selected" | translate }}</div>
43+
</div>
44+
</div>
45+
</div>
46+
</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: 200px;
16+
overflow-y: scroll;
17+
}
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
2+
import { Item } from '../../../../core/shared/item.model';
3+
import { Version } from '../../../../core/shared/version.model';
4+
import { RemoteData } from '../../../../core/data/remote-data';
5+
import { combineLatest, Observable, Subscription } from 'rxjs';
6+
import { VersionHistory } from '../../../../core/shared/version-history.model';
7+
import {
8+
getAllSucceededRemoteData,
9+
getFirstCompletedRemoteData,
10+
getFirstSucceededRemoteDataPayload,
11+
getRemoteDataPayload
12+
} from '../../../../core/shared/operators';
13+
import { map, switchMap } from 'rxjs/operators';
14+
import { PaginatedList } from '../../../../core/data/paginated-list.model';
15+
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
16+
import { VersionHistoryDataService } from '../../../../core/data/version-history-data.service';
17+
import { PaginatedSearchOptions } from '../../../../shared/search/models/paginated-search-options.model';
18+
import { followLink } from '../../../../shared/utils/follow-link-config.model';
19+
import { hasValue, hasValueOperator } from '../../../../shared/empty.util';
20+
import { PaginationService } from '../../../../core/pagination/pagination.service';
21+
import { getItemVersionRoute } from '../../../item-page-routing-paths';
22+
import { WorkspaceItem } from '../../../../core/submission/models/workspaceitem.model';
23+
import { WorkspaceitemDataService } from '../../../../core/submission/workspaceitem-data.service';
24+
import { WorkflowItemDataService } from '../../../../core/submission/workflowitem-data.service';
25+
26+
interface VersionsDTO {
27+
totalElements: number;
28+
versionDTOs: VersionDTO[];
29+
}
30+
31+
interface VersionDTO {
32+
version: Version;
33+
}
34+
35+
@Component({
36+
selector: 'ds-clarin-item-versions-field',
37+
templateUrl: './clarin-item-versions-field.component.html',
38+
styleUrls: ['./clarin-item-versions-field.component.scss']
39+
})
40+
41+
/**
42+
* Component for displaying item versions as a field in the clarin item page
43+
*/
44+
export class ClarinItemVersionsFieldComponent implements OnDestroy, OnInit {
45+
46+
/**
47+
* The item to display version history for
48+
*/
49+
@Input() item: Item;
50+
51+
/**
52+
* Fontawesome v5. icon name with default settings.
53+
*/
54+
@Input() iconName: string;
55+
56+
/**
57+
* Array of active subscriptions
58+
*/
59+
subs: Subscription[] = [];
60+
61+
/**
62+
* The item's version
63+
*/
64+
versionRD$: Observable<RemoteData<Version>>;
65+
66+
/**
67+
* The item's full version history (remote data)
68+
*/
69+
versionHistoryRD$: Observable<RemoteData<VersionHistory>>;
70+
71+
/**
72+
* The item's full version history
73+
*/
74+
versionHistory$: Observable<VersionHistory>;
75+
76+
/**
77+
* The version history information that is used to render the HTML
78+
*/
79+
versionsDTO$: Observable<VersionsDTO>;
80+
81+
/**
82+
* Verify if there is an inprogress submission in the version history
83+
*/
84+
hasDraftVersion$: Observable<boolean>;
85+
86+
/**
87+
* The amount of versions to display per page
88+
*/
89+
pageSize = 10;
90+
91+
/**
92+
* The page options to use for fetching the versions
93+
* Start at page 1 and always use the set page size
94+
*/
95+
options = Object.assign(new PaginationComponentOptions(), {
96+
id: 'clarin-ivo',
97+
currentPage: 1,
98+
pageSize: this.pageSize
99+
});
100+
101+
/**
102+
* Toggle state for version history
103+
*/
104+
showVersionHistory = false;
105+
106+
/**
107+
* Observable to check if component should be displayed
108+
*/
109+
showMetadataValue: Observable<boolean>;
110+
111+
constructor(private versionHistoryService: VersionHistoryDataService,
112+
private paginationService: PaginationService,
113+
private workspaceItemDataService: WorkspaceitemDataService,
114+
private workflowItemDataService: WorkflowItemDataService
115+
) {
116+
}
117+
118+
/**
119+
* Toggle the visibility of version history
120+
*/
121+
toggleVersionHistory(): void {
122+
this.showVersionHistory = !this.showVersionHistory;
123+
}
124+
125+
/**
126+
* Get the route to the specified version
127+
* @param versionId the ID of the version for which the route will be retrieved
128+
*/
129+
getVersionRoute(versionId: string) {
130+
return getItemVersionRoute(versionId);
131+
}
132+
133+
/**
134+
* Get all versions for the given version history and store them in versionsDTO$
135+
* @param versionHistory$
136+
*/
137+
getAllVersions(versionHistory$: Observable<VersionHistory>): void {
138+
const currentPagination = this.paginationService.getCurrentPagination(this.options.id, this.options);
139+
this.versionsDTO$ = combineLatest([versionHistory$, currentPagination]).pipe(
140+
switchMap(([versionHistory, options]: [VersionHistory, PaginationComponentOptions]) => {
141+
return this.versionHistoryService.getVersions(versionHistory.id,
142+
new PaginatedSearchOptions({pagination: Object.assign({}, options, {currentPage: options.currentPage})}),
143+
false, true, followLink('item'));
144+
}),
145+
getFirstCompletedRemoteData(),
146+
getRemoteDataPayload(),
147+
map((versions: PaginatedList<Version>) => ({
148+
totalElements: versions.totalElements,
149+
versionDTOs: (versions?.page ?? []).reverse().map((version: Version) => ({
150+
version: version,
151+
})),
152+
})),
153+
);
154+
}
155+
156+
/**
157+
* Get the ID of the workspace item, if present, otherwise return undefined
158+
* @param versionItem the item for which retrieve the workspace item id
159+
*/
160+
getWorkspaceId(versionItem): Observable<string> {
161+
return versionItem.pipe(
162+
getFirstSucceededRemoteDataPayload(),
163+
map((item: Item) => item.uuid),
164+
switchMap((itemUuid: string) => this.workspaceItemDataService.findByItem(itemUuid, true)),
165+
getFirstCompletedRemoteData<WorkspaceItem>(),
166+
map((res: RemoteData<WorkspaceItem>) => res?.payload?.id ),
167+
);
168+
}
169+
170+
/**
171+
* Get the ID of the workflow item, if present, otherwise return undefined
172+
* @param versionItem the item for which retrieve the workspace item id
173+
*/
174+
getWorkflowId(versionItem): Observable<string> {
175+
return versionItem.pipe(
176+
getFirstSucceededRemoteDataPayload(),
177+
map((item: Item) => item.uuid),
178+
switchMap((itemUuid: string) => this.workflowItemDataService.findByItem(itemUuid, true)),
179+
getFirstCompletedRemoteData<WorkspaceItem>(),
180+
map((res: RemoteData<WorkspaceItem>) => res?.payload?.id ),
181+
);
182+
}
183+
184+
/**
185+
* Get the display name for a version item
186+
* @param versionItem the item to get the name for
187+
*/
188+
getVersionItemDisplayName(versionItem: Item): string {
189+
return versionItem.firstMetadataValue('dc.title') || versionItem.name || 'Untitled';
190+
}
191+
192+
/**
193+
* Initialize all observables
194+
*/
195+
ngOnInit(): void {
196+
if (hasValue(this.item?.version)) {
197+
this.versionRD$ = this.item.version;
198+
this.versionHistoryRD$ = this.versionRD$.pipe(
199+
getAllSucceededRemoteData(),
200+
getRemoteDataPayload(),
201+
hasValueOperator(),
202+
switchMap((version: Version) => version.versionhistory),
203+
);
204+
this.versionHistory$ = this.versionHistoryRD$.pipe(
205+
getFirstSucceededRemoteDataPayload(),
206+
hasValueOperator(),
207+
);
208+
209+
// If there is a draft item in the version history
210+
this.hasDraftVersion$ = this.versionHistoryRD$.pipe(
211+
getFirstSucceededRemoteDataPayload(),
212+
map((res) => Boolean(res?.draftVersion)),
213+
);
214+
215+
this.getAllVersions(this.versionHistory$);
216+
217+
// Determine if component should be displayed (when there are multiple versions)
218+
this.showMetadataValue = this.versionsDTO$.pipe(
219+
map((versionsDTO: VersionsDTO) => versionsDTO && versionsDTO.totalElements > 1)
220+
);
221+
} else {
222+
// No version history available
223+
this.showMetadataValue = new Observable(observer => observer.next(false));
224+
}
225+
}
226+
227+
/**
228+
* Unsub all subscriptions
229+
*/
230+
cleanupSubscribes() {
231+
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
232+
}
233+
234+
ngOnDestroy(): void {
235+
this.cleanupSubscribes();
236+
this.paginationService.clearPagination(this.options.id);
237+
}
238+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<ds-themed-item-alerts [item]="item"></ds-themed-item-alerts>
99
<ds-item-versions-notice [item]="item"></ds-item-versions-notice>
1010
<ds-listable-object-component-loader *ngIf="!item.isWithdrawn || (isAdmin$|async)" [object]="item" [viewMode]="viewMode"></ds-listable-object-component-loader>
11-
<!-- <ds-item-versions class="mt-2" [item]="item" [displayActions]="false"></ds-item-versions> -->
11+
<ds-item-versions class="mt-2" [item]="item" [displayActions]="false"></ds-item-versions>
1212
<div *ngIf="(hasFiles | async) === true">
1313
<ds-clarin-files-section [item]="item" [itemHandle]="itemHandle"></ds-clarin-files-section>
1414
</div>

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,10 @@
114114
[iconName]="'fa-sitemap'"
115115
[separator]="'<br />'">
116116
</ds-clarin-collections-item-field>
117-
<ds-item-versions
117+
<ds-clarin-item-versions-field
118118
[item]="object"
119-
[iconName]="'fa-history'"
120-
[displayTitle]="false"
121-
[displayActions]="true">
122-
</ds-item-versions>
119+
[iconName]="'fa-history'">
120+
</ds-clarin-item-versions-field>
123121
<div class="clarin-item-page-field">
124122
<a [routerLink]="[itemPageRoute + '/full']">
125123
{{"item.page.link.full" | translate}}

0 commit comments

Comments
 (0)