Skip to content

Commit f2d84e2

Browse files
committed
Merge branch 'ux-plus-2023_02_x' into ux-plus-cris-2024_02_x
# Conflicts: # package.json # src/app/core/layout/models/section.model.ts # src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/image/image.component.ts # src/app/cris-layout/cris-layout.module.ts # src/app/item-page/mirador-viewer/mirador-viewer.component.spec.ts # src/app/item-page/mirador-viewer/mirador-viewer.component.ts # src/app/shared/browse-most-elements/abstract-browse-elements.component.ts # src/app/shared/browse-most-elements/browse-most-elements.component.ts # src/app/shared/browse-most-elements/themed-browse-most-elements.component.ts # src/app/shared/carousel/carousel.component.html # src/app/shared/carousel/carousel.component.spec.ts # src/app/shared/carousel/carousel.component.ts # src/app/shared/carousel/carousel.module.ts # src/app/shared/explore/explore.module.ts # src/app/shared/explore/section-component/carousel-section/carousel-section.component.spec.ts # src/app/shared/explore/section-component/carousel-section/carousel-section.component.ts # src/app/shared/explore/section-component/grid-section/grid-section.component.spec.ts # src/app/shared/explore/section-component/grid-section/grid-section.component.ts # src/app/shared/open-street-map/open-street-map.component.ts # src/app/shared/shared.module.ts # src/app/shared/utils/background-image.directive.ts # src/app/thumbnail/thumbnail.component.html # src/app/thumbnail/thumbnail.component.spec.ts # src/config/default-app-config.ts
2 parents af20c34 + e230110 commit f2d84e2

69 files changed

Lines changed: 3304 additions & 103 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@
158158
"quill": "2.0.3",
159159
"quill-emoji": "^0.2.0",
160160
"react-copy-to-clipboard": "^5.1.0",
161+
"redux-saga": "^1.3.0",
161162
"reflect-metadata": "^0.2.2",
162163
"rxjs": "^7.8.0",
163164
"sanitize-html": "^2.14.0",

src/app/core/layout/models/section.model.ts

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55

66
import { typedObject } from '../../cache/builders/build-decorators';
77
import { CacheableObject } from '../../cache/cacheable-object.model';
8+
import { SortDirection } from '../../cache/models/sort-options.model';
89
import { HALLink } from '../../shared/hal-link.model';
910
import { ResourceType } from '../../shared/resource-type';
1011
import { excludeFromEquals } from '../../utilities/equals.decorators';
@@ -75,8 +76,10 @@ export interface TopSection extends SectionComponent {
7576
}
7677

7778
export interface GridSection extends SectionComponent {
79+
order: SortDirection;
80+
sortField: string;
7881
discoveryConfigurationName: string;
79-
'main-content-link': string;
82+
mainContentLink: string;
8083
}
8184

8285
export interface SearchSection extends SectionComponent {
@@ -114,14 +117,6 @@ export interface TopSectionColumn {
114117
titleKey: string;
115118
}
116119

117-
/**
118-
* Represents the type of template to use for the section
119-
*/
120-
export enum TopSectionTemplateType {
121-
DEFAULT = 'default', // CRIS default template
122-
CARD = 'card', // Card template
123-
}
124-
125120
export enum LayoutModeEnum {
126121
LIST = 'list',
127122
CARD = 'card'
@@ -146,4 +141,53 @@ export interface CarouselSection extends SectionComponent {
146141
captionStyle: string;
147142
titleStyle: string;
148143
bundle: string;
144+
showBlurryBackdrop: boolean;
145+
}
146+
147+
148+
149+
export interface SliderSection extends SectionComponent {
150+
discoveryConfigurationName: string;
151+
order: string;
152+
sortField: string;
153+
numberOfItems: number;
154+
style: string;
155+
title: string;
156+
link: string;
157+
description: string;
158+
componentType: 'slider';
159+
targetBlank: boolean ;
160+
fitWidth: boolean;
161+
fitHeight: boolean;
162+
keepAspectRatio: boolean;
163+
aspectRatio: number;
164+
carouselHeightPx: number;
165+
captionStyle: string;
166+
titleStyle: string;
167+
showBlurryBackdrop: boolean;
168+
}
169+
170+
/**
171+
* Represents an advanced top section in the layout.
172+
*/
173+
export interface AdvancedTopSection extends Omit<TopSection, 'discoveryConfigurationName'|'componentType'> {
174+
/**
175+
* The names of the discovery configurations.
176+
*/
177+
discoveryConfigurationName: string[];
178+
179+
/**
180+
* The component type, which is always 'advanced-top-component'.
181+
*/
182+
componentType: 'advanced-top-component';
183+
}
184+
185+
/*
186+
* Represents the type of template to use for the section
187+
*/
188+
export enum TopSectionTemplateType {
189+
DEFAULT = 'default', // CRIS default template
190+
IMAGES = 'images',
191+
SLIDER = 'slider',
192+
CARD = 'card',
149193
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import {
2+
inject,
3+
Injectable,
4+
} from '@angular/core';
5+
import {
6+
from,
7+
Observable,
8+
} from 'rxjs';
9+
import {
10+
filter,
11+
map,
12+
mergeMap,
13+
reduce,
14+
switchMap,
15+
take,
16+
} from 'rxjs/operators';
17+
18+
import { hasValue } from '../../shared/empty.util';
19+
import { followLink } from '../../shared/utils/follow-link-config.model';
20+
import { BitstreamDataService } from '../data/bitstream-data.service';
21+
import { PaginatedList } from '../data/paginated-list.model';
22+
import { RemoteData } from '../data/remote-data';
23+
import { Bitstream } from '../shared/bitstream.model';
24+
import { BitstreamFormat } from '../shared/bitstream-format.model';
25+
import { DSpaceObject } from '../shared/dspace-object.model';
26+
import { Item } from '../shared/item.model';
27+
import { getFirstCompletedRemoteData } from '../shared/operators';
28+
29+
interface ItemAndImage {
30+
itemUUID: string;
31+
imageHref: string;
32+
}
33+
34+
@Injectable({ providedIn: 'root' })
35+
export class BitstreamImagesService {
36+
37+
private readonly bitstreamDataService = inject(BitstreamDataService);
38+
39+
/**
40+
* Retrieve all items and their image bitstreams
41+
* @param items
42+
* @param bundleName
43+
*/
44+
getItemToImageMap(items: Item[], bundleName = 'ORIGINAL'): Observable<Map<string, string>> {
45+
return from(items).pipe(
46+
mergeMap((item) => this.findImageBitstreams(item, bundleName).pipe(
47+
take(1),
48+
map((bitstream: Bitstream) => <ItemAndImage>{
49+
itemUUID: item.uuid, imageHref: bitstream._links.content.href,
50+
}),
51+
)),
52+
reduce((acc: Map<string, string>, value: ItemAndImage) => {
53+
acc.set(value.itemUUID, value.imageHref);
54+
return acc;
55+
}, new Map<string, string>()),
56+
);
57+
}
58+
59+
/**
60+
* Find all image bitstreams for an item
61+
* @param item the item for which the images should be retrieved
62+
* @param bundleName the bundle name (ORIGINAL by default)
63+
*/
64+
findImageBitstreams(item: Item | DSpaceObject, bundleName = 'ORIGINAL') {
65+
const isImageMimetypeRegex = /^image\//;
66+
67+
// retrieve all bundle's bitstreams for the item
68+
const bitstreamPayload$: Observable<Bitstream> = this.bitstreamDataService.showableByItem(
69+
item.uuid, bundleName, [], {}, true, true, followLink('format'),
70+
).pipe(
71+
getFirstCompletedRemoteData(),
72+
switchMap((rd: RemoteData<PaginatedList<Bitstream>>) => rd.hasSucceeded ? rd.payload.page : new Array<Bitstream>()),
73+
);
74+
75+
// filter bitstreams according to mime type
76+
return bitstreamPayload$.pipe(
77+
switchMap((bitstream: Bitstream) => bitstream.format.pipe(
78+
getFirstCompletedRemoteData(),
79+
filter((bitstreamFormatRD: RemoteData<BitstreamFormat>) =>
80+
bitstreamFormatRD.hasSucceeded && hasValue(bitstreamFormatRD.payload) && hasValue(bitstream) &&
81+
isImageMimetypeRegex.test(bitstreamFormatRD.payload.mimetype),
82+
),
83+
map(() => bitstream),
84+
)),
85+
);
86+
}
87+
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
<div [class]="field.styleValue">
2-
<img *ngIf="(imageUrl$ | async) as url" [src]="url">
2+
<img *ngIf="isBrowser && !isLoading; else imageSkeleton" [src]="imageUrl$ | async">
3+
<ng-template #imageSkeleton>
4+
<ngx-skeleton-loader style="width: 100%; height: 100%;" [theme]="{height: '100%', width: '100%'}"></ngx-skeleton-loader>
5+
</ng-template>
36
</div>
47

src/app/cris-layout/cris-layout-matrix/cris-layout-box-container/boxes/metadata/rendering-types/image/image.component.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
1-
import { AsyncPipe } from '@angular/common';
1+
import {
2+
AsyncPipe,
3+
isPlatformBrowser,
4+
} from '@angular/common';
25
import {
36
Component,
7+
inject,
48
OnInit,
9+
PLATFORM_ID,
510
} from '@angular/core';
11+
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
612
import {
713
BehaviorSubject,
814
Observable,
15+
of,
916
} from 'rxjs';
10-
import { map } from 'rxjs/operators';
17+
import {
18+
catchError,
19+
map,
20+
} from 'rxjs/operators';
1121

1222
import { Bitstream } from '../../../../../../../core/shared/bitstream.model';
1323
import { getPaginatedListPayload } from '../../../../../../../core/shared/operators';
@@ -21,6 +31,7 @@ import { BitstreamRenderingModelComponent } from '../bitstream-rendering-model';
2131
standalone: true,
2232
imports: [
2333
AsyncPipe,
34+
NgxSkeletonLoaderModule,
2435
],
2536
})
2637
export class ImageComponent extends BitstreamRenderingModelComponent implements OnInit {
@@ -29,12 +40,23 @@ export class ImageComponent extends BitstreamRenderingModelComponent implements
2940

3041
imageUrl$: Observable<string>;
3142

43+
platformId = inject(PLATFORM_ID);
44+
45+
isLoading = true;
46+
isBrowser: boolean;
47+
3248
ngOnInit(): void {
49+
this.isBrowser = isPlatformBrowser(this.platformId);
3350
this.getBitstreamsByItem().pipe(
3451
getPaginatedListPayload(),
3552
map((filteredBitstreams: Bitstream[]) => filteredBitstreams.length > 0 ? filteredBitstreams[0] : null),
53+
catchError(() => {
54+
this.isLoading = false;
55+
return of(null);
56+
}),
3657
).subscribe((image) => {
3758
this.bitstream.next(image);
59+
this.isLoading = false;
3860
});
3961

4062
this.imageUrl$ = this.bitstream.asObservable().pipe(
@@ -43,8 +65,4 @@ export class ImageComponent extends BitstreamRenderingModelComponent implements
4365

4466
}
4567

46-
backgroundImageUrl(url: string) {
47-
return `url('${url}')`;
48-
}
49-
5068
}

src/app/home-page/home-page.component.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@
5656
[sectionId]="sectionId"
5757
[twitterSection]="$any(sectionComponent)"></ds-twitter-section>
5858

59+
<ds-advanced-top-section *ngSwitchCase="'advanced-top-component'"
60+
[sectionId]="sectionId"
61+
[advancedTopSection]="$any(sectionComponent)"></ds-advanced-top-section>
62+
5963
</div>
6064
</div>
6165
</div>

src/app/home-page/home-page.component.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
isEmpty,
5151
isNotEmpty,
5252
} from '../shared/empty.util';
53+
import { ThemedAdvancedTopSectionComponent } from '../shared/explore/section-component/advanced-top-section/themed-advanced-top-section.component';
5354
import { ThemedBrowseSectionComponent } from '../shared/explore/section-component/browse-section/themed-browse-section.component';
5455
import { ThemedCarouselSectionComponent } from '../shared/explore/section-component/carousel-section/themed-carousel-section.component';
5556
import { ThemedCountersSectionComponent } from '../shared/explore/section-component/counters-section/themed-counters-section.component';
@@ -87,6 +88,7 @@ import { ThemedHomeNewsComponent } from './home-news/themed-home-news.component'
8788
ThemedCarouselSectionComponent,
8889
ThemedGridSectionComponent,
8990
ThemedTwitterSectionComponent,
91+
ThemedAdvancedTopSectionComponent,
9092
],
9193
})
9294
export class HomePageComponent implements OnInit, OnDestroy {

0 commit comments

Comments
 (0)