Skip to content

Commit 1363ec0

Browse files
refactor: flatten layout interface structure
Remove DynamicLayoutConfig nesting and move urn, itemPage, metadataBox, and collectionsBox properties directly under the layout interface to eliminate unnecessary nesting levels. Changes: - Update layout-config.interfaces.ts: Remove DynamicLayoutConfig interface and add urn, itemPage, metadataBox, collectionsBox directly to LayoutConfig - Update default-app-config.ts: Move dynamicLayout properties to top level - Update environment.test.ts: Move dynamicLayout properties to top level - Update component property access from layout.dynamicLayout.* to layout.*: * dynamic-layout-loader.component.ts * dynamic-layout-collection-box.component.ts * metadata-container.component.ts * resolver-strategy.service.ts * identifier.component.ts - Update config.example.yml: Flatten structure and enhance documentation Before: layout.dynamicLayout.urn layout.dynamicLayout.itemPage layout.dynamicLayout.metadataBox layout.dynamicLayout.collectionsBox After: layout.urn layout.itemPage layout.metadataBox layout.collectionsBox Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent f112533 commit 1363ec0

9 files changed

Lines changed: 945 additions & 3 deletions

File tree

config/config.example.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -757,6 +757,50 @@ layout:
757757
- name: checksum
758758
type: attribute
759759

760+
# Configuration for URN (Uniform Resource Name) identifier resolution
761+
# Maps identifier types (e.g., DOI, Handle, Scopus) to their base URLs for creating resolvable links
762+
urn:
763+
- name: doi
764+
baseUrl: 'https://doi.org/'
765+
- name: hdl
766+
baseUrl: 'https://hdl.handle.net/'
767+
- name: scopus
768+
baseUrl: 'https://www.scopus.com/authid/detail.uri?authorId='
769+
- name: researcherid
770+
baseUrl: 'http://www.researcherid.com/rid/'
771+
- name: mailto
772+
baseUrl: 'mailto:'
773+
774+
# Item page layout configuration for different entity types
775+
# Defines the visual layout orientation (horizontal/vertical) for various entity types in the item page detail view
776+
itemPage:
777+
OrgUnit:
778+
orientation: vertical
779+
Project:
780+
orientation: vertical
781+
default:
782+
orientation: horizontal
783+
784+
# Metadata box rendering configuration
785+
# Defines default CSS column styles for metadata labels and values in item page detail view
786+
metadataBox:
787+
# CSS classes for metadata label column (default: 'col-3' = 25% width)
788+
defaultMetadataLabelColStyle: col-3
789+
# CSS classes for metadata value column (default: 'col-9' = 75% width)
790+
defaultMetadataValueColStyle: col-9
791+
792+
# Collections box rendering configuration
793+
# Defines default CSS column styles for collections and inline display settings
794+
collectionsBox:
795+
# CSS classes for collection label column (default: 'col-3 fw-bold' = 25% width, bold text)
796+
defaultCollectionsLabelColStyle: col-3 fw-bold
797+
# CSS classes for collection value column (default: 'col-9' = 75% width)
798+
defaultCollectionsValueColStyle: col-9
799+
# CSS classes for collection row styling
800+
# defaultCollectionsRowStyle: ''
801+
# Whether to display collections inline (true) or in a block layout (false)
802+
isInline: true
803+
760804
# Configuration for customization of search results
761805
searchResults:
762806
# Metadata fields to be displayed in the search results under the standard ones
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import {
2+
Component,
3+
ComponentRef,
4+
Inject,
5+
Input,
6+
OnDestroy,
7+
OnInit,
8+
ViewChild,
9+
} from '@angular/core';
10+
import {
11+
APP_CONFIG,
12+
AppConfig,
13+
} from '@dspace/config/app-config.interface';
14+
import { DynamicLayoutTypeConfig } from '@dspace/config/layout-config.interfaces';
15+
16+
import { DynamicLayoutTab } from '../../core/layout/models/tab.model';
17+
import { GenericConstructor } from '../../core/shared/generic-constructor';
18+
import { Item } from '../../core/shared/item.model';
19+
import { getDynamicLayoutPage } from '../decorators/dynamic-layout-page.decorator';
20+
import { DynamicLayoutLoaderDirective } from '../directives/dynamic-layout-loader.directive';
21+
import { LayoutPage } from '../enums/layout-page.enum';
22+
23+
@Component({
24+
selector: 'ds-dynamic-layout-loader',
25+
templateUrl: './dynamic-layout-loader.component.html',
26+
styleUrls: ['./dynamic-layout-loader.component.scss'],
27+
imports: [
28+
DynamicLayoutLoaderDirective,
29+
],
30+
})
31+
export class DynamicLayoutLoaderComponent implements OnInit, OnDestroy {
32+
33+
/**
34+
* DSpace Item to render
35+
*/
36+
@Input() item: Item;
37+
38+
/**
39+
* Tabs to render
40+
*/
41+
@Input() tabs: DynamicLayoutTab[];
42+
43+
/**
44+
* A boolean representing if to show context menu or not
45+
*/
46+
@Input() showContextMenu: boolean;
47+
48+
/**
49+
* Configuration layout form the environment
50+
*/
51+
layoutConfiguration: DynamicLayoutTypeConfig;
52+
53+
54+
@Input() leadingTabs: DynamicLayoutTab[];
55+
56+
/**
57+
* Directive hook used to place the dynamic child component
58+
*/
59+
@ViewChild(DynamicLayoutLoaderDirective, { static: true }) dynamicLayoutLoader: DynamicLayoutLoaderDirective;
60+
61+
/**
62+
* componentRef reference of the component that will be created
63+
*/
64+
componentRef: ComponentRef<Component>;
65+
66+
constructor(
67+
@Inject(APP_CONFIG) protected appConfig: AppConfig,
68+
) {
69+
}
70+
71+
ngOnInit(): void {
72+
this.getConfiguration();
73+
this.initComponent();
74+
}
75+
76+
/**
77+
* Get tabs for the specific item and the configuration for the item
78+
*/
79+
getConfiguration(): void {
80+
const itemType = this.item ?.firstMetadataValue('dspace.entity.type');
81+
const def = 'default';
82+
83+
if (!!this.appConfig.layout.itemPage && !!this.appConfig.layout.itemPage[itemType]) {
84+
this.layoutConfiguration = this.appConfig.layout.itemPage[itemType];
85+
} else {
86+
this.layoutConfiguration = this.appConfig.layout.itemPage[def];
87+
}
88+
}
89+
90+
/**
91+
* Initialize the component depending on the layout configuration
92+
*/
93+
initComponent(): void {
94+
const component: GenericConstructor<Component> = this.getComponent();
95+
const viewContainerRef = this.dynamicLayoutLoader.viewContainerRef;
96+
viewContainerRef.clear();
97+
98+
this.componentRef = viewContainerRef.createComponent(component);
99+
(this.componentRef.instance as any).item = this.item;
100+
(this.componentRef.instance as any).tabs = this.tabs;
101+
(this.componentRef.instance as any).showContextMenu = this.showContextMenu;
102+
(this.componentRef.instance as any).leadingTabs = this.leadingTabs;
103+
this.componentRef.changeDetectorRef.detectChanges();
104+
}
105+
106+
/**
107+
* Fetch the component depending on the item
108+
* @returns {GenericConstructor<Component>}
109+
*/
110+
private getComponent(): GenericConstructor<Component> {
111+
return getDynamicLayoutPage(this.layoutConfiguration.orientation as LayoutPage);
112+
}
113+
114+
/**
115+
* Destroy componentRef when this component is destroyed
116+
*/
117+
ngOnDestroy(): void {
118+
if (this.componentRef) {
119+
this.componentRef.destroy();
120+
}
121+
}
122+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import {
2+
AsyncPipe,
3+
NgClass,
4+
} from '@angular/common';
5+
import {
6+
Component,
7+
Inject,
8+
OnInit,
9+
} from '@angular/core';
10+
import { RouterLink } from '@angular/router';
11+
import { CollectionDataService } from '@dspace/core/data/collection-data.service';
12+
import { FindListOptions } from '@dspace/core/data/find-list-options.model';
13+
import { PaginatedList } from '@dspace/core/data/paginated-list.model';
14+
import { DynamicLayoutBox } from '@dspace/core/layout/models/box.model';
15+
import { Collection } from '@dspace/core/shared/collection.model';
16+
import { Item } from '@dspace/core/shared/item.model';
17+
import {
18+
getFirstCompletedRemoteData,
19+
getFirstSucceededRemoteDataPayload,
20+
getPaginatedListPayload,
21+
} from '@dspace/core/shared/operators';
22+
import { hasValue } from '@dspace/shared/utils/empty.util';
23+
import {
24+
TranslateModule,
25+
TranslateService,
26+
} from '@ngx-translate/core';
27+
import {
28+
BehaviorSubject,
29+
combineLatest,
30+
Observable,
31+
of,
32+
} from 'rxjs';
33+
import {
34+
map,
35+
shareReplay,
36+
tap,
37+
} from 'rxjs/operators';
38+
39+
import { environment } from '../../../../../../environments/environment';
40+
import { DynamicLayoutBoxModelComponent } from '../../../../models/dynamic-layout-box-component.model';
41+
42+
@Component({
43+
selector: 'ds-dynamic-layout-collection-box',
44+
templateUrl: './dynamic-layout-collection-box.component.html',
45+
styleUrls: ['./dynamic-layout-collection-box.component.scss'],
46+
imports: [
47+
AsyncPipe,
48+
NgClass,
49+
RouterLink,
50+
TranslateModule,
51+
],
52+
})
53+
export class DynamicLayoutCollectionBoxComponent extends DynamicLayoutBoxModelComponent implements OnInit {
54+
55+
isInline = environment.layout.collectionsBox.isInline;
56+
57+
/**
58+
* Amount of mapped collections that should be fetched at once.
59+
*/
60+
pageSize = 5;
61+
62+
/**
63+
* Last page of the mapped collections that has been fetched.
64+
*/
65+
lastPage = 0;
66+
67+
/**
68+
* Whether or not a page of mapped collections is currently being loaded.
69+
*/
70+
isLoading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
71+
72+
/**
73+
* Whether or not more pages of mapped collections are available.
74+
*/
75+
hasMore$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
76+
77+
/**
78+
* This includes the owning collection
79+
*/
80+
owningCollection$: Observable<Collection>;
81+
82+
/**
83+
* This includes the mapped collection
84+
*/
85+
mappedCollections$: Observable<Collection[]> = of([]);
86+
87+
constructor(
88+
protected translateService: TranslateService,
89+
@Inject('boxProvider') public boxProvider: DynamicLayoutBox,
90+
@Inject('itemProvider') public itemProvider: Item,
91+
private cds: CollectionDataService,
92+
) {
93+
super(translateService, boxProvider, itemProvider);
94+
}
95+
96+
ngOnInit(): void {
97+
super.ngOnInit();
98+
99+
this.owningCollection$ = this.item.owningCollection.pipe(
100+
getFirstSucceededRemoteDataPayload(),
101+
shareReplay({ refCount: false, bufferSize: 1 }),
102+
);
103+
104+
this.handleLoadMore();
105+
}
106+
107+
handleLoadMore() {
108+
this.isLoading$.next(true);
109+
const newMappedCollections$ = this.loadMappedCollectionPage();
110+
this.mappedCollections$ = combineLatest([this.mappedCollections$, newMappedCollections$]).pipe(
111+
map(([mappedCollections, newMappedCollections]: [Collection[], Collection[]]) => {
112+
return [...mappedCollections, ...newMappedCollections].filter(collection => hasValue(collection));
113+
}),
114+
);
115+
}
116+
117+
loadMappedCollectionPage(): Observable<Collection[]> {
118+
return this.cds.findMappedCollectionsFor(this.item, Object.assign(new FindListOptions(), {
119+
elementsPerPage: this.pageSize,
120+
currentPage: this.lastPage + 1,
121+
})).pipe(
122+
getFirstCompletedRemoteData<PaginatedList<Collection>>(),
123+
124+
// update isLoading$
125+
tap(() => this.isLoading$.next(false)),
126+
127+
getFirstSucceededRemoteDataPayload(),
128+
129+
// update lastPage
130+
tap((response: PaginatedList<Collection>) => this.lastPage = response.currentPage),
131+
132+
// update hasMore$
133+
tap((response: PaginatedList<Collection>) => this.hasMore$.next(this.lastPage < response.totalPages)),
134+
135+
getPaginatedListPayload<Collection>(),
136+
);
137+
}
138+
139+
/**
140+
* Returns a string representing the style of field label if exists
141+
*/
142+
get labelStyle(): string {
143+
return environment.layout.collectionsBox.defaultCollectionsLabelColStyle;
144+
}
145+
146+
/**
147+
* Returns a string representing the style of field value if exists
148+
*/
149+
get valueStyle(): string {
150+
return environment.layout.collectionsBox.defaultCollectionsValueColStyle;
151+
}
152+
153+
/**
154+
* Returns a string representing the style of row if exists
155+
*/
156+
get rowStyle(): string {
157+
return environment.layout.collectionsBox.defaultCollectionsRowStyle;
158+
}
159+
160+
}

0 commit comments

Comments
 (0)