Merge branch 'w2p-117573_remove-observable-function-calls-from-template-7.6'

This commit is contained in:
Alexandre Vryghem
2025-05-02 00:58:00 +02:00
31 changed files with 288 additions and 553 deletions

View File

@@ -1,4 +1,4 @@
@if (registryService.getActiveMetadataField() | async) { @if (activeMetadataField$ | async) {
<h2>{{messagePrefix + '.edit' | translate}}</h2> <h2>{{messagePrefix + '.edit' | translate}}</h2>
} @else { } @else {
<h2>{{messagePrefix + '.create' | translate}}</h2> <h2>{{messagePrefix + '.create' | translate}}</h2>

View File

@@ -19,7 +19,7 @@ import {
TranslateModule, TranslateModule,
TranslateService, TranslateService,
} from '@ngx-translate/core'; } from '@ngx-translate/core';
import { combineLatest } from 'rxjs'; import { Observable } from 'rxjs';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { MetadataField } from '../../../../core/metadata/metadata-field.model'; import { MetadataField } from '../../../../core/metadata/metadata-field.model';
@@ -109,6 +109,8 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
*/ */
@Output() submitForm: EventEmitter<any> = new EventEmitter(); @Output() submitForm: EventEmitter<any> = new EventEmitter();
activeMetadataField$: Observable<MetadataField>;
constructor(public registryService: RegistryService, constructor(public registryService: RegistryService,
private formBuilderService: FormBuilderService, private formBuilderService: FormBuilderService,
private translateService: TranslateService) { private translateService: TranslateService) {
@@ -117,71 +119,65 @@ export class MetadataFieldFormComponent implements OnInit, OnDestroy {
/** /**
* Initialize the component, setting up the necessary Models for the dynamic form * Initialize the component, setting up the necessary Models for the dynamic form
*/ */
ngOnInit() { ngOnInit(): void {
combineLatest([ this.activeMetadataField$ = this.registryService.getActiveMetadataField();
this.translateService.get(`${this.messagePrefix}.element`), this.element = new DynamicInputModel({
this.translateService.get(`${this.messagePrefix}.qualifier`), id: 'element',
this.translateService.get(`${this.messagePrefix}.scopenote`), label: this.translateService.instant(`${this.messagePrefix}.element`),
]).subscribe(([element, qualifier, scopenote]) => { name: 'element',
this.element = new DynamicInputModel({ validators: {
id: 'element', required: null,
label: element, pattern: '^[^. ,]*$',
name: 'element', maxLength: 64,
validators: { },
required: null, required: true,
pattern: '^[^. ,]*$', errorMessages: {
maxLength: 64, pattern: 'error.validation.metadata.element.invalid-pattern',
}, maxLength: 'error.validation.metadata.element.max-length',
required: true, },
errorMessages: { });
pattern: 'error.validation.metadata.element.invalid-pattern', this.qualifier = new DynamicInputModel({
maxLength: 'error.validation.metadata.element.max-length', id: 'qualifier',
}, label: this.translateService.instant(`${this.messagePrefix}.qualifier`),
}); name: 'qualifier',
this.qualifier = new DynamicInputModel({ validators: {
id: 'qualifier', pattern: '^[^. ,]*$',
label: qualifier, maxLength: 64,
name: 'qualifier', },
validators: { required: false,
pattern: '^[^. ,]*$', errorMessages: {
maxLength: 64, pattern: 'error.validation.metadata.qualifier.invalid-pattern',
}, maxLength: 'error.validation.metadata.qualifier.max-length',
required: false, },
errorMessages: { });
pattern: 'error.validation.metadata.qualifier.invalid-pattern', this.scopeNote = new DynamicTextAreaModel({
maxLength: 'error.validation.metadata.qualifier.max-length', id: 'scopeNote',
}, label: this.translateService.instant(`${this.messagePrefix}.scopenote`),
}); name: 'scopeNote',
this.scopeNote = new DynamicTextAreaModel({ required: false,
id: 'scopeNote', rows: 5,
label: scopenote, });
name: 'scopeNote', this.formModel = [
required: false, new DynamicFormGroupModel({
rows: 5, id: 'metadatadatafieldgroup',
}); group:[this.element, this.qualifier, this.scopeNote],
this.formModel = [ }),
new DynamicFormGroupModel( ];
{ this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
id: 'metadatadatafieldgroup', this.registryService.getActiveMetadataField().subscribe((field: MetadataField): void => {
group:[this.element, this.qualifier, this.scopeNote], if (field == null) {
}), this.clearFields();
]; } else {
this.formGroup = this.formBuilderService.createFormGroup(this.formModel); this.formGroup.patchValue({
this.registryService.getActiveMetadataField().subscribe((field: MetadataField): void => { metadatadatafieldgroup: {
if (field == null) { element: field.element,
this.clearFields(); qualifier: field.qualifier,
} else { scopeNote: field.scopeNote,
this.formGroup.patchValue({ },
metadatadatafieldgroup: { });
element: field.element, this.element.disabled = true;
qualifier: field.qualifier, this.qualifier.disabled = true;
scopeNote: field.scopeNote, }
},
});
this.element.disabled = true;
this.qualifier.disabled = true;
}
});
}); });
} }

View File

@@ -1,7 +1,7 @@
<div class="container"> <div class="container">
<ds-alert [type]="'alert-info'" [content]="'item.edit.authorizations.heading'"></ds-alert> <ds-alert [type]="AlertType.Info" [content]="'item.edit.authorizations.heading'"></ds-alert>
<ds-resource-policies [resourceType]="'item'" [resourceName]="(getItemName() | async)" <ds-resource-policies [resourceType]="'item'" [resourceName]="itemName$ | async"
[resourceUUID]="(getItemUUID() | async)"> [resourceUUID]="(item$ | async)?.id">
</ds-resource-policies> </ds-resource-policies>
@for (bundle of (bundles$ | async); track bundle) { @for (bundle of (bundles$ | async); track bundle) {
<ds-resource-policies [resourceType]="'bundle'" [resourceUUID]="bundle.id" [resourceName]="bundle.name"> <ds-resource-policies [resourceType]="'bundle'" [resourceUUID]="bundle.id" [resourceName]="bundle.name">

View File

@@ -169,17 +169,9 @@ describe('ItemAuthorizationsComponent test suite', () => {
})); }));
}); });
it('should get the item UUID', () => {
expect(comp.getItemUUID()).toBeObservable(cold('(a|)', {
a: item.id,
}));
});
it('should get the item\'s bundle', () => { it('should get the item\'s bundle', () => {
expect(comp.getItemBundles()).toBeObservable(cold('a', { expect(comp.bundles$).toBeObservable(cold('a', {
a: bundles, a: bundles,
})); }));

View File

@@ -17,7 +17,6 @@ import {
import { import {
catchError, catchError,
filter, filter,
first,
map, map,
mergeMap, mergeMap,
take, take,
@@ -37,11 +36,11 @@ import {
getFirstSucceededRemoteDataWithNotEmptyPayload, getFirstSucceededRemoteDataWithNotEmptyPayload,
} from '../../../core/shared/operators'; } from '../../../core/shared/operators';
import { AlertComponent } from '../../../shared/alert/alert.component'; import { AlertComponent } from '../../../shared/alert/alert.component';
import { AlertType } from '../../../shared/alert/alert-type';
import { import {
hasValue, hasValue,
isNotEmpty, isNotEmpty,
} from '../../../shared/empty.util'; } from '../../../shared/empty.util';
import { NgForTrackByIdDirective } from '../../../shared/ng-for-track-by-id.directive';
import { ResourcePoliciesComponent } from '../../../shared/resource-policies/resource-policies.component'; import { ResourcePoliciesComponent } from '../../../shared/resource-policies/resource-policies.component';
import { followLink } from '../../../shared/utils/follow-link-config.model'; import { followLink } from '../../../shared/utils/follow-link-config.model';
@@ -61,7 +60,6 @@ interface BundleBitstreamsMapEntry {
ResourcePoliciesComponent, ResourcePoliciesComponent,
NgbCollapseModule, NgbCollapseModule,
TranslateModule, TranslateModule,
NgForTrackByIdDirective,
AsyncPipe, AsyncPipe,
AlertComponent, AlertComponent,
], ],
@@ -88,7 +86,7 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
* The target editing item * The target editing item
* @type {Observable<Item>} * @type {Observable<Item>}
*/ */
private item$: Observable<Item>; item$: Observable<Item>;
/** /**
* Array to track all subscriptions and unsubscribe them onDestroy * Array to track all subscriptions and unsubscribe them onDestroy
@@ -127,16 +125,13 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
*/ */
private bitstreamPageSize = 4; private bitstreamPageSize = 4;
/** itemName$: Observable<string>;
* Initialize instance variables
* readonly AlertType = AlertType;
* @param {LinkService} linkService
* @param {ActivatedRoute} route
* @param nameService
*/
constructor( constructor(
private linkService: LinkService, protected linkService: LinkService,
private route: ActivatedRoute, protected route: ActivatedRoute,
public nameService: DSONameService, public nameService: DSONameService,
) { ) {
} }
@@ -146,36 +141,18 @@ export class ItemAuthorizationsComponent implements OnInit, OnDestroy {
*/ */
ngOnInit(): void { ngOnInit(): void {
this.getBundlesPerItem(); this.getBundlesPerItem();
this.itemName$ = this.getItemName();
} }
/** /**
* Return the item's UUID * Return the item's name
*/ */
getItemUUID(): Observable<string> { private getItemName(): Observable<string> {
return this.item$.pipe(
map((item: Item) => item.id),
first((UUID: string) => isNotEmpty(UUID)),
);
}
/**
* Return the item's name
*/
getItemName(): Observable<string> {
return this.item$.pipe( return this.item$.pipe(
map((item: Item) => this.nameService.getName(item)), map((item: Item) => this.nameService.getName(item)),
); );
} }
/**
* Return all item's bundles
*
* @return an observable that emits all item's bundles
*/
getItemBundles(): Observable<Bundle[]> {
return this.bundles$.asObservable();
}
/** /**
* Get all bundles per item * Get all bundles per item
* and all the bitstreams per bundle * and all the bitstreams per bundle

View File

@@ -1,16 +1,16 @@
<div class="container mb-5"> <div class="container mb-5">
<h1>{{'person.orcid.registry.auth' | translate}}</h1> <h1>{{'person.orcid.registry.auth' | translate}}</h1>
@if ((isLinkedToOrcid() | async)) { @if ((isOrcidLinked$ | async)) {
<div data-test="orcidLinked"> <div data-test="orcidLinked">
<div class="row"> <div class="row">
@if ((hasOrcidAuthorizations() | async)) { @if ((hasOrcidAuthorizations$ | async)) {
<div class="col-sm-6 mb-3" data-test="hasOrcidAuthorizations"> <div class="col-sm-6 mb-3" data-test="hasOrcidAuthorizations">
<div class="card h-100"> <div class="card h-100">
<div class="card-header">{{ 'person.page.orcid.granted-authorizations'| translate }}</div> <div class="card-header">{{ 'person.page.orcid.granted-authorizations'| translate }}</div>
<div class="card-body"> <div class="card-body">
<div class="container p-0"> <div class="container p-0">
<ul> <ul>
@for (auth of (getOrcidAuthorizations() | async); track auth) { @for (auth of (profileAuthorizationScopes$ | async); track auth) {
<li data-test="orcidAuthorization"> <li data-test="orcidAuthorization">
{{getAuthorizationDescription(auth) | translate}} {{getAuthorizationDescription(auth) | translate}}
</li> </li>
@@ -26,16 +26,16 @@
<div class="card-header">{{ 'person.page.orcid.missing-authorizations'| translate }}</div> <div class="card-header">{{ 'person.page.orcid.missing-authorizations'| translate }}</div>
<div class="card-body"> <div class="card-body">
<div class="container"> <div class="container">
@if ((hasMissingOrcidAuthorizations() | async) !== true) { @if ((hasMissingOrcidAuthorizations$ | async) !== true) {
<ds-alert [type]="'alert-success'" data-test="noMissingOrcidAuthorizations"> <ds-alert [type]="AlertType.Success" data-test="noMissingOrcidAuthorizations">
{{'person.page.orcid.no-missing-authorizations-message' | translate}} {{'person.page.orcid.no-missing-authorizations-message' | translate}}
</ds-alert> </ds-alert>
} }
@if ((hasMissingOrcidAuthorizations() | async)) { @if ((hasMissingOrcidAuthorizations$ | async)) {
<ds-alert [type]="'alert-warning'" data-test="missingOrcidAuthorizations"> <ds-alert [type]="AlertType.Warning" data-test="missingOrcidAuthorizations">
{{'person.page.orcid.missing-authorizations-message' | translate}} {{'person.page.orcid.missing-authorizations-message' | translate}}
<ul> <ul>
@for (auth of (getMissingOrcidAuthorizations() | async); track auth) { @for (auth of (profileAuthorizationScopes$ | async); track auth) {
<li data-test="missingOrcidAuthorization"> <li data-test="missingOrcidAuthorization">
{{getAuthorizationDescription(auth) | translate }} {{getAuthorizationDescription(auth) | translate }}
</li> </li>
@@ -48,13 +48,13 @@
</div> </div>
</div> </div>
</div> </div>
@if ((onlyAdminCanDisconnectProfileFromOrcid() | async) && (ownerCanDisconnectProfileFromOrcid() | async) !== true) { @if ((onlyAdminCanDisconnectProfileFromOrcid$ | async) && (ownerCanDisconnectProfileFromOrcid$ | async) !== true) {
<ds-alert <ds-alert
[type]="'alert-warning'" data-test="unlinkOnlyAdmin"> [type]="AlertType.Warning" data-test="unlinkOnlyAdmin">
{{ 'person.page.orcid.remove-orcid-message' | translate}} {{ 'person.page.orcid.remove-orcid-message' | translate}}
</ds-alert> </ds-alert>
} }
@if ((ownerCanDisconnectProfileFromOrcid() | async)) { @if ((ownerCanDisconnectProfileFromOrcid$ | async)) {
<div class="row" data-test="unlinkOwner"> <div class="row" data-test="unlinkOwner">
<div class="col"> <div class="col">
<button type="submit" class="btn btn-danger float-end" (click)="unlinkOrcid()" <button type="submit" class="btn btn-danger float-end" (click)="unlinkOrcid()"
@@ -68,7 +68,7 @@
class='fas fa-circle-notch fa-spin'></i> {{'person.page.orcid.unlink.processing' | translate}}</span> class='fas fa-circle-notch fa-spin'></i> {{'person.page.orcid.unlink.processing' | translate}}</span>
} }
</button> </button>
@if ((hasMissingOrcidAuthorizations() | async)) { @if ((hasMissingOrcidAuthorizations$ | async)) {
<button type="submit" <button type="submit"
class="btn btn-primary float-end" (click)="linkOrcid()"> class="btn btn-primary float-end" (click)="linkOrcid()">
<span><i class="fas fa-check"></i> {{ 'person.page.orcid.grant-authorizations' | translate }}</span> <span><i class="fas fa-check"></i> {{ 'person.page.orcid.grant-authorizations' | translate }}</span>
@@ -83,7 +83,7 @@
<div class="row"> <div class="row">
<div class="col-2"><img alt="orcid-logo" src="../../../../assets/images/orcid.logo.icon.svg"/></div> <div class="col-2"><img alt="orcid-logo" src="../../../../assets/images/orcid.logo.icon.svg"/></div>
<div class="col"> <div class="col">
<ds-alert [type]="'alert-info'">{{ getOrcidNotLinkedMessage() | async }}</ds-alert> <ds-alert [type]="AlertType.Info">{{ getOrcidNotLinkedMessage() }}</ds-alert>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
@@ -97,103 +97,3 @@
</div> </div>
} }
</div> </div>
<ng-template #orcidLinked>
<div data-test="orcidLinked">
<div class="row">
@if ((hasOrcidAuthorizations() | async)) {
<div class="col-sm-6 mb-3" data-test="hasOrcidAuthorizations">
<div class="card h-100">
<div class="card-header">{{ 'person.page.orcid.granted-authorizations'| translate }}</div>
<div class="card-body">
<div class="container p-0">
<ul>
@for (auth of (getOrcidAuthorizations() | async); track auth) {
<li data-test="orcidAuthorization">
{{getAuthorizationDescription(auth) | translate}}
</li>
}
</ul>
</div>
</div>
</div>
</div>
}
<div class="col-sm-6 mb-3">
<div class="card h-100">
<div class="card-header">{{ 'person.page.orcid.missing-authorizations'| translate }}</div>
<div class="card-body">
<div class="container">
@if ((hasMissingOrcidAuthorizations() | async) !== true) {
<ds-alert [type]="'alert-success'" data-test="noMissingOrcidAuthorizations">
{{'person.page.orcid.no-missing-authorizations-message' | translate}}
</ds-alert>
}
@if ((hasMissingOrcidAuthorizations() | async)) {
<ds-alert [type]="'alert-warning'" data-test="missingOrcidAuthorizations">
{{'person.page.orcid.missing-authorizations-message' | translate}}
<ul>
@for (auth of (getMissingOrcidAuthorizations() | async); track auth) {
<li data-test="missingOrcidAuthorization">
{{getAuthorizationDescription(auth) | translate }}
</li>
}
</ul>
</ds-alert>
}
</div>
</div>
</div>
</div>
</div>
@if ((onlyAdminCanDisconnectProfileFromOrcid() | async) && (ownerCanDisconnectProfileFromOrcid() | async) !== true) {
<ds-alert
[type]="'alert-warning'" data-test="unlinkOnlyAdmin">
{{ 'person.page.orcid.remove-orcid-message' | translate}}
</ds-alert>
}
@if ((ownerCanDisconnectProfileFromOrcid() | async)) {
<div class="row" data-test="unlinkOwner">
<div class="col">
<button type="submit" class="btn btn-danger float-end" (click)="unlinkOrcid()"
[dsBtnDisabled]="(unlinkProcessing | async)">
@if ((unlinkProcessing | async) !== true) {
<span><i
class="fas fa-unlink"></i> {{ 'person.page.orcid.unlink' | translate }}</span>
}
@if ((unlinkProcessing | async)) {
<span><i
class='fas fa-circle-notch fa-spin'></i> {{'person.page.orcid.unlink.processing' | translate}}</span>
}
</button>
@if ((hasMissingOrcidAuthorizations() | async)) {
<button type="submit"
class="btn btn-primary float-end" (click)="linkOrcid()">
<span><i class="fas fa-check"></i> {{ 'person.page.orcid.grant-authorizations' | translate }}</span>
</button>
}
</div>
</div>
}
</div>
</ng-template>
<ng-template #orcidNotLinked>
<div data-test="orcidNotLinked">
<div class="row">
<div class="col-2"><img alt="orcid-logo" src="../../../../assets/images/orcid.logo.icon.svg"/></div>
<div class="col">
<ds-alert [type]="'alert-info'">{{ getOrcidNotLinkedMessage() | async }}</ds-alert>
</div>
</div>
<div class="row">
<div class="col">
<button class="btn btn-primary float-end" (click)="linkOrcid()">
<i class="fas fa-link"></i>
{{'person.page.orcid.link' | translate}}
</button>
</div>
</div>
</div>
</ng-template>

View File

@@ -30,6 +30,7 @@ import {
import { Item } from '../../../core/shared/item.model'; import { Item } from '../../../core/shared/item.model';
import { getFirstCompletedRemoteData } from '../../../core/shared/operators'; import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
import { AlertComponent } from '../../../shared/alert/alert.component'; import { AlertComponent } from '../../../shared/alert/alert.component';
import { AlertType } from '../../../shared/alert/alert-type';
import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive'; import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive';
import { NotificationsService } from '../../../shared/notifications/notifications.service'; import { NotificationsService } from '../../../shared/notifications/notifications.service';
import { createFailedRemoteDataObjectFromError$ } from '../../../shared/remote-data.utils'; import { createFailedRemoteDataObjectFromError$ } from '../../../shared/remote-data.utils';
@@ -56,43 +57,49 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
/** /**
* The list of exposed orcid authorization scopes for the orcid profile * The list of exposed orcid authorization scopes for the orcid profile
*/ */
profileAuthorizationScopes: BehaviorSubject<string[]> = new BehaviorSubject<string[]>([]); profileAuthorizationScopes$: BehaviorSubject<string[]> = new BehaviorSubject([]);
hasOrcidAuthorizations$: Observable<boolean>;
/** /**
* The list of all orcid authorization scopes missing in the orcid profile * The list of all orcid authorization scopes missing in the orcid profile
*/ */
missingAuthorizationScopes: BehaviorSubject<string[]> = new BehaviorSubject<string[]>([]); missingAuthorizationScopes: BehaviorSubject<string[]> = new BehaviorSubject([]);
hasMissingOrcidAuthorizations$: Observable<boolean>;
/** /**
* The list of all orcid authorization scopes available * The list of all orcid authorization scopes available
*/ */
orcidAuthorizationScopes: BehaviorSubject<string[]> = new BehaviorSubject<string[]>([]); orcidAuthorizationScopes: BehaviorSubject<string[]> = new BehaviorSubject([]);
/** /**
* A boolean representing if unlink operation is processing * A boolean representing if unlink operation is processing
*/ */
unlinkProcessing: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); unlinkProcessing: BehaviorSubject<boolean> = new BehaviorSubject(false);
/** /**
* A boolean representing if orcid profile is linked * A boolean representing if orcid profile is linked
*/ */
private isOrcidLinked$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); isOrcidLinked$: BehaviorSubject<boolean> = new BehaviorSubject(false);
/** /**
* A boolean representing if only admin can disconnect orcid profile * A boolean representing if only admin can disconnect orcid profile
*/ */
private onlyAdminCanDisconnectProfileFromOrcid$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); onlyAdminCanDisconnectProfileFromOrcid$: BehaviorSubject<boolean> = new BehaviorSubject(false);
/** /**
* A boolean representing if owner can disconnect orcid profile * A boolean representing if owner can disconnect orcid profile
*/ */
private ownerCanDisconnectProfileFromOrcid$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false); ownerCanDisconnectProfileFromOrcid$: BehaviorSubject<boolean> = new BehaviorSubject(false);
/** /**
* An event emitted when orcid profile is unliked successfully * An event emitted when orcid profile is unliked successfully
*/ */
@Output() unlink: EventEmitter<void> = new EventEmitter<void>(); @Output() unlink: EventEmitter<void> = new EventEmitter<void>();
readonly AlertType = AlertType;
constructor( constructor(
private orcidAuthService: OrcidAuthService, private orcidAuthService: OrcidAuthService,
private translateService: TranslateService, private translateService: TranslateService,
@@ -106,6 +113,8 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
this.orcidAuthorizationScopes.next(scopes); this.orcidAuthorizationScopes.next(scopes);
this.initOrcidAuthSettings(); this.initOrcidAuthSettings();
}); });
this.hasOrcidAuthorizations$ = this.hasOrcidAuthorizations();
this.hasMissingOrcidAuthorizations$ = this.hasMissingOrcidAuthorizations();
} }
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
@@ -118,18 +127,11 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
* Check if the list of exposed orcid authorization scopes for the orcid profile has values * Check if the list of exposed orcid authorization scopes for the orcid profile has values
*/ */
hasOrcidAuthorizations(): Observable<boolean> { hasOrcidAuthorizations(): Observable<boolean> {
return this.profileAuthorizationScopes.asObservable().pipe( return this.profileAuthorizationScopes$.pipe(
map((scopes: string[]) => scopes.length > 0), map((scopes: string[]) => scopes.length > 0),
); );
} }
/**
* Return the list of exposed orcid authorization scopes for the orcid profile
*/
getOrcidAuthorizations(): Observable<string[]> {
return this.profileAuthorizationScopes.asObservable();
}
/** /**
* Check if the list of exposed orcid authorization scopes for the orcid profile has values * Check if the list of exposed orcid authorization scopes for the orcid profile has values
*/ */
@@ -139,26 +141,12 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
); );
} }
/** getOrcidNotLinkedMessage(): string {
* Return the list of exposed orcid authorization scopes for the orcid profile
*/
getMissingOrcidAuthorizations(): Observable<string[]> {
return this.profileAuthorizationScopes.asObservable();
}
/**
* Return a boolean representing if orcid profile is linked
*/
isLinkedToOrcid(): Observable<boolean> {
return this.isOrcidLinked$.asObservable();
}
getOrcidNotLinkedMessage(): Observable<string> {
const orcid = this.item.firstMetadataValue('person.identifier.orcid'); const orcid = this.item.firstMetadataValue('person.identifier.orcid');
if (orcid) { if (orcid) {
return this.translateService.get('person.page.orcid.orcid-not-linked-message', { 'orcid': orcid }); return this.translateService.instant('person.page.orcid.orcid-not-linked-message', { 'orcid': orcid });
} else { } else {
return this.translateService.get('person.page.orcid.no-orcid-message'); return this.translateService.instant('person.page.orcid.no-orcid-message');
} }
} }
@@ -171,13 +159,6 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
return 'person.page.orcid.scope.' + scope.substring(1).replace('/', '-'); return 'person.page.orcid.scope.' + scope.substring(1).replace('/', '-');
} }
/**
* Return a boolean representing if only admin can disconnect orcid profile
*/
onlyAdminCanDisconnectProfileFromOrcid(): Observable<boolean> {
return this.onlyAdminCanDisconnectProfileFromOrcid$.asObservable();
}
/** /**
* Return a boolean representing if owner can disconnect orcid profile * Return a boolean representing if owner can disconnect orcid profile
*/ */
@@ -243,7 +224,7 @@ export class OrcidAuthComponent implements OnInit, OnChanges {
} }
private setOrcidAuthorizationsFromItem(): void { private setOrcidAuthorizationsFromItem(): void {
this.profileAuthorizationScopes.next(this.orcidAuthService.getOrcidAuthorizationScopesByItem(this.item)); this.profileAuthorizationScopes$.next(this.orcidAuthService.getOrcidAuthorizationScopesByItem(this.item));
} }
} }

View File

@@ -5,16 +5,16 @@
<div class="container"> <div class="container">
<h2>{{ 'person.orcid.registry.queue' | translate }}</h2> <h2>{{ 'person.orcid.registry.queue' | translate }}</h2>
@if ((processing$ | async) !== true && (getList() | async)?.payload?.totalElements === 0) { @if ((processing$ | async) !== true && (list$ | async)?.payload?.totalElements === 0) {
<ds-alert <ds-alert
[type]="AlertTypeEnum.Info"> [type]="AlertTypeEnum.Info">
{{ 'person.page.orcid.sync-queue.empty-message' | translate}} {{ 'person.page.orcid.sync-queue.empty-message' | translate}}
</ds-alert> </ds-alert>
} }
@if ((processing$ | async) !== true && (getList() | async)?.payload?.totalElements > 0) { @if ((processing$ | async) !== true && (list$ | async)?.payload?.totalElements > 0) {
<ds-pagination <ds-pagination
[paginationOptions]="paginationOptions" [paginationOptions]="paginationOptions"
[collectionSize]="(getList() | async)?.payload?.totalElements" [collectionSize]="(list$ | async)?.payload?.totalElements"
[retainScrollPosition]="false" [hideGear]="true" (paginationChange)="updateList()"> [retainScrollPosition]="false" [hideGear]="true" (paginationChange)="updateList()">
<div class="table-responsive"> <div class="table-responsive">
<table id="groups" class="table table-sm table-striped table-hover table-bordered"> <table id="groups" class="table table-sm table-striped table-hover table-bordered">
@@ -26,7 +26,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@for (entry of (getList() | async)?.payload?.page; track entry) { @for (entry of (list$ | async)?.payload?.page; track entry) {
<tr data-test="orcidQueueElementRow"> <tr data-test="orcidQueueElementRow">
<td style="width: 15%" class="text-center align-middle"> <td style="width: 15%" class="text-center align-middle">
<i [ngClass]="getIconClass(entry)" [ngbTooltip]="getIconTooltip(entry) | translate" <i [ngClass]="getIconClass(entry)" [ngbTooltip]="getIconTooltip(entry) | translate"

View File

@@ -80,13 +80,12 @@ export class OrcidQueueComponent implements OnInit, OnDestroy, OnChanges {
/** /**
* A list of orcid queue records * A list of orcid queue records
*/ */
private list$: BehaviorSubject<RemoteData<PaginatedList<OrcidQueue>>> = new BehaviorSubject<RemoteData<PaginatedList<OrcidQueue>>>({} as any); list$: BehaviorSubject<RemoteData<PaginatedList<OrcidQueue>>> = new BehaviorSubject<RemoteData<PaginatedList<OrcidQueue>>>({} as any);
/** /**
* The AlertType enumeration * The AlertType enumeration
* @type {AlertType}
*/ */
AlertTypeEnum = AlertType; readonly AlertTypeEnum = AlertType;
/** /**
* Array to track all subscriptions and unsubscribe them onDestroy * Array to track all subscriptions and unsubscribe them onDestroy
@@ -132,13 +131,6 @@ export class OrcidQueueComponent implements OnInit, OnDestroy, OnChanges {
); );
} }
/**
* Return the list of orcid queue records
*/
getList(): Observable<RemoteData<PaginatedList<OrcidQueue>>> {
return this.list$.asObservable();
}
/** /**
* Return the icon class for the queue object type * Return the icon class for the queue object type
* *

View File

@@ -1,5 +1,5 @@
<nav [ngClass]="{'open': (menuCollapsed | async) !== true}" <nav [ngClass]="{'open': (menuCollapsed | async) !== true}"
[@slideMobileNav]="(windowService.isXsOrSm() | async) !== true ? 'default' : ((menuCollapsed | async) ? 'collapsed' : 'expanded')" [@slideMobileNav]="(isMobile$ | async) !== true ? 'default' : ((menuCollapsed | async) ? 'collapsed' : 'expanded')"
class="navbar navbar-light navbar-expand-md px-md-0 navbar-container" role="navigation" class="navbar navbar-light navbar-expand-md px-md-0 navbar-container" role="navigation"
[attr.aria-label]="'nav.main.description' | translate" id="main-navbar"> [attr.aria-label]="'nav.main.description' | translate" id="main-navbar">
<!-- TODO remove navbar-container class when https://github.com/twbs/bootstrap/issues/24726 is fixed --> <!-- TODO remove navbar-container class when https://github.com/twbs/bootstrap/issues/24726 is fixed -->

View File

@@ -62,20 +62,20 @@ class="fas fa-plus pe-2"></i>{{'process.overview.new' | translate}}</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
@if ((processBulkDeleteService.isProcessing$() | async) !== true) { @let isProcessing = (isProcessing$ | async);
<div>{{'process.overview.delete.body' | translate: {count: processBulkDeleteService.getAmountOfSelectedProcesses()} }}</div> @if (isProcessing) {
}
@if (processBulkDeleteService.isProcessing$() |async) {
<div class="alert alert-info"> <div class="alert alert-info">
<span class="spinner-border spinner-border-sm spinner-button" role="status" aria-hidden="true"></span> <span class="spinner-border spinner-border-sm spinner-button" role="status" aria-hidden="true"></span>
<span> {{ 'process.overview.delete.processing' | translate: {count: processBulkDeleteService.getAmountOfSelectedProcesses()} }}</span> <span> {{ 'process.overview.delete.processing' | translate: {count: processBulkDeleteService.getAmountOfSelectedProcesses()} }}</span>
</div> </div>
} @else {
<div>{{'process.overview.delete.body' | translate: {count: processBulkDeleteService.getAmountOfSelectedProcesses()} }}</div>
} }
<div class="mt-4"> <div class="mt-4">
<button class="btn btn-primary me-2" [dsBtnDisabled]="processBulkDeleteService.isProcessing$() |async" <button class="btn btn-primary me-2" [dsBtnDisabled]="isProcessing"
(click)="closeModal()">{{'process.detail.delete.cancel' | translate}}</button> (click)="closeModal()">{{'process.detail.delete.cancel' | translate}}</button>
<button id="delete-confirm" class="btn btn-danger" <button id="delete-confirm" class="btn btn-danger"
[dsBtnDisabled]="processBulkDeleteService.isProcessing$() |async" [dsBtnDisabled]="isProcessing"
(click)="deleteSelected()">{{ 'process.overview.delete' | translate: {count: processBulkDeleteService.getAmountOfSelectedProcesses()} }} (click)="deleteSelected()">{{ 'process.overview.delete' | translate: {count: processBulkDeleteService.getAmountOfSelectedProcesses()} }}
</button> </button>
</div> </div>

View File

@@ -12,7 +12,10 @@ import {
import { RouterLink } from '@angular/router'; import { RouterLink } from '@angular/router';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { Subscription } from 'rxjs'; import {
Observable,
Subscription,
} from 'rxjs';
import { BtnDisabledDirective } from '../../shared/btn-disabled.directive'; import { BtnDisabledDirective } from '../../shared/btn-disabled.directive';
import { hasValue } from '../../shared/empty.util'; import { hasValue } from '../../shared/empty.util';
@@ -45,6 +48,8 @@ export class ProcessOverviewComponent implements OnInit, OnDestroy {
isProcessingSub: Subscription; isProcessingSub: Subscription;
isProcessing$: Observable<boolean>;
constructor(protected processOverviewService: ProcessOverviewService, constructor(protected processOverviewService: ProcessOverviewService,
protected modalService: NgbModal, protected modalService: NgbModal,
public processBulkDeleteService: ProcessBulkDeleteService, public processBulkDeleteService: ProcessBulkDeleteService,
@@ -53,6 +58,7 @@ export class ProcessOverviewComponent implements OnInit, OnDestroy {
ngOnInit(): void { ngOnInit(): void {
this.processBulkDeleteService.clearAllProcesses(); this.processBulkDeleteService.clearAllProcesses();
this.isProcessing$ = this.processBulkDeleteService.isProcessing$();
} }
ngOnDestroy(): void { ngOnDestroy(): void {

View File

@@ -17,15 +17,15 @@
</div> </div>
} }
@if (!researcherProfile) { @if (!researcherProfile) {
@let processingCreate = (processingCreate$ | async);
<button class="btn btn-primary me-2" <button class="btn btn-primary me-2"
[dsBtnDisabled]="(isProcessingCreate() | async)" [dsBtnDisabled]="processingCreate"
(click)="createProfile()"> (click)="createProfile()">
@if ((isProcessingCreate() | async)) { @if (processingCreate) {
<span> <span>
<i class='fas fa-circle-notch fa-spin'></i> {{'researcher.profile.action.processing' | translate}} <i class='fas fa-circle-notch fa-spin'></i> {{'researcher.profile.action.processing' | translate}}
</span> </span>
} } @else {
@if ((isProcessingCreate() | async) !== true) {
<span> <span>
<i class="fas fa-plus"></i> &nbsp;{{'researcher.profile.create.new' | translate}} <i class="fas fa-plus"></i> &nbsp;{{'researcher.profile.create.new' | translate}}
</span> </span>
@@ -37,12 +37,11 @@
<i class="fas fa-info-circle"></i> {{'researcher.profile.view' | translate}} <i class="fas fa-info-circle"></i> {{'researcher.profile.view' | translate}}
</button> </button>
<button class="btn btn-danger" [dsBtnDisabled]="!researcherProfile" (click)="deleteProfile(researcherProfile)"> <button class="btn btn-danger" [dsBtnDisabled]="!researcherProfile" (click)="deleteProfile(researcherProfile)">
@if ((isProcessingDelete() | async)) { @if ((processingDelete$ | async)) {
<span> <span>
<i class='fas fa-circle-notch fa-spin'></i> {{'researcher.profile.action.processing' | translate}} <i class='fas fa-circle-notch fa-spin'></i> {{'researcher.profile.action.processing' | translate}}
</span> </span>
} } @else {
@if ((isProcessingDelete() | async) !== true) {
<span> <span>
<i class="fas fa-trash-alt"></i> &nbsp;{{'researcher.profile.delete' | translate}} <i class="fas fa-trash-alt"></i> &nbsp;{{'researcher.profile.delete' | translate}}
</span> </span>

View File

@@ -11,10 +11,7 @@ import {
TranslateService, TranslateService,
} from '@ngx-translate/core'; } from '@ngx-translate/core';
import { UiSwitchModule } from 'ngx-ui-switch'; import { UiSwitchModule } from 'ngx-ui-switch';
import { import { BehaviorSubject } from 'rxjs';
BehaviorSubject,
Observable,
} from 'rxjs';
import { import {
map, map,
mergeMap, mergeMap,
@@ -187,24 +184,6 @@ export class ProfilePageResearcherFormComponent implements OnInit {
}); });
} }
/**
* Return a boolean representing if a delete operation is pending.
*
* @return {Observable<boolean>}
*/
isProcessingDelete(): Observable<boolean> {
return this.processingDelete$.asObservable();
}
/**
* Return a boolean representing if a create operation is pending.
*
* @return {Observable<boolean>}
*/
isProcessingCreate(): Observable<boolean> {
return this.processingCreate$.asObservable();
}
/** /**
* Create a new profile related to the current user from scratch. * Create a new profile related to the current user from scratch.
*/ */

View File

@@ -54,15 +54,15 @@
</ds-alert> </ds-alert>
} }
@if (isRecaptchaCookieAccepted() && (googleRecaptchaService.captchaVersion() | async) === 'v2') { @if (isRecaptchaCookieAccepted() && (captchaVersion$ | async) === 'v2') {
<div class="my-3"> <div class="my-3">
<ds-google-recaptcha [captchaMode]="(googleRecaptchaService.captchaMode() | async)" <ds-google-recaptcha [captchaMode]="(captchaMode$ | async)"
(executeRecaptcha)="register($event)" (checkboxChecked)="onCheckboxChecked($event)" (executeRecaptcha)="register($event)" (checkboxChecked)="onCheckboxChecked($event)"
(showNotification)="showNotification($event)"></ds-google-recaptcha> (showNotification)="showNotification($event)"></ds-google-recaptcha>
</div> </div>
} }
@if ((!registrationVerification || ((googleRecaptchaService.captchaVersion() | async) !== 'v2' && (googleRecaptchaService.captchaMode() | async) === 'invisible'))) { @if ((!registrationVerification || ((captchaVersion$ | async) !== 'v2' && (captchaMode$ | async) === 'invisible'))) {
<button class="btn btn-primary" [dsBtnDisabled]="form.invalid || registrationVerification && !isRecaptchaCookieAccepted() || disableUntilChecked" (click)="register()"> <button class="btn btn-primary" [dsBtnDisabled]="form.invalid || registrationVerification && !isRecaptchaCookieAccepted() || disableUntilChecked" (click)="register()">
{{ MESSAGE_PREFIX + '.submit' | translate }} {{ MESSAGE_PREFIX + '.submit' | translate }}
</button> </button>

View File

@@ -107,13 +107,9 @@ export class RegisterEmailFormComponent implements OnDestroy, OnInit {
subscriptions: Subscription[] = []; subscriptions: Subscription[] = [];
captchaVersion(): Observable<string> { captchaVersion$: Observable<string>;
return this.googleRecaptchaService.captchaVersion();
}
captchaMode(): Observable<string> { captchaMode$: Observable<string>;
return this.googleRecaptchaService.captchaMode();
}
constructor( constructor(
private epersonRegistrationService: EpersonRegistrationService, private epersonRegistrationService: EpersonRegistrationService,
@@ -135,6 +131,8 @@ export class RegisterEmailFormComponent implements OnDestroy, OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
this.captchaVersion$ = this.googleRecaptchaService.captchaVersion();
this.captchaMode$ = this.googleRecaptchaService.captchaMode();
const validators: ValidatorFn[] = [ const validators: ValidatorFn[] = [
Validators.required, Validators.required,
Validators.email, Validators.email,
@@ -191,7 +189,7 @@ export class RegisterEmailFormComponent implements OnDestroy, OnInit {
register(tokenV2?) { register(tokenV2?) {
if (!this.form.invalid) { if (!this.form.invalid) {
if (this.registrationVerification) { if (this.registrationVerification) {
this.subscriptions.push(combineLatest([this.captchaVersion(), this.captchaMode()]).pipe( this.subscriptions.push(combineLatest([this.captchaVersion$, this.captchaMode$]).pipe(
switchMap(([captchaVersion, captchaMode]) => { switchMap(([captchaVersion, captchaMode]) => {
if (captchaVersion === 'v3') { if (captchaVersion === 'v3') {
return this.googleRecaptchaService.getRecaptchaToken('register_email'); return this.googleRecaptchaService.getRecaptchaToken('register_email');
@@ -254,7 +252,7 @@ export class RegisterEmailFormComponent implements OnDestroy, OnInit {
*/ */
disableUntilCheckedFcn(): Observable<boolean> { disableUntilCheckedFcn(): Observable<boolean> {
const checked$ = this.checkboxCheckedSubject$.asObservable(); const checked$ = this.checkboxCheckedSubject$.asObservable();
return combineLatest([this.captchaVersion(), this.captchaMode(), checked$]).pipe( return combineLatest([this.captchaVersion$, this.captchaMode$, checked$]).pipe(
// disable if checkbox is not checked or if reCaptcha is not in v2 checkbox mode // disable if checkbox is not checked or if reCaptcha is not in v2 checkbox mode
switchMap(([captchaVersion, captchaMode, checked]) => captchaVersion === 'v2' && captchaMode === 'checkbox' ? of(!checked) : of(false)), switchMap(([captchaVersion, captchaMode, checked]) => captchaVersion === 'v2' && captchaMode === 'checkbox' ? of(!checked) : of(false)),
startWith(true), startWith(true),

View File

@@ -1,15 +1,15 @@
<div class="mt-3" @fadeInOut> <div class="mt-3" @fadeInOut>
@if (isListOfEPerson) { @if (isListOfEPerson) {
<ds-eperson-search-box (search)="onSearch($event)"></ds-eperson-search-box> <ds-eperson-search-box (search)="onSearch($event)"></ds-eperson-search-box>
} } @else {
@if (!isListOfEPerson) {
<ds-group-search-box (search)="onSearch($event)"></ds-group-search-box> <ds-group-search-box (search)="onSearch($event)"></ds-group-search-box>
} }
@if ((getList() | async)?.payload?.totalElements > 0) { @let list = (list$ | async);
@if (list && list.totalElements > 0) {
<ds-pagination <ds-pagination
[paginationOptions]="paginationOptions" [paginationOptions]="paginationOptions"
[collectionSize]="(getList() | async)?.payload?.totalElements" [collectionSize]="list.totalElements"
[retainScrollPosition]="true" [retainScrollPosition]="true"
[hideGear]="true"> [hideGear]="true">
<div class="table-responsive"> <div class="table-responsive">
@@ -22,11 +22,10 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@for (entry of (getList() | async)?.payload?.page; track entry) { @for (entry of list.page; track entry) {
<tr <tr [class.table-primary]="(entrySelectedId$ | async) === entry.id">
[class.table-primary]="isSelected(entry) | async"> <td>{{ entry.id }}</td>
<td>{{entry.id}}</td> <td>{{ dsoNameService.getName(entry) }}</td>
<td>{{dsoNameService.getName(entry)}}</td>
<td class="text-center"> <td class="text-center">
<button class="btn btn-sm btn-outline-primary" (click)="emitSelect(entry)"> <button class="btn btn-sm btn-outline-primary" (click)="emitSelect(entry)">
{{'resource-policies.form.eperson-group-list.select.btn' | translate}} {{'resource-policies.form.eperson-group-list.select.btn' | translate}}

View File

@@ -13,7 +13,7 @@ import {
} from '@angular/core/testing'; } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { cold } from 'jasmine-marbles'; import { hot } from 'jasmine-marbles';
import uniqueId from 'lodash/uniqueId'; import uniqueId from 'lodash/uniqueId';
import { of as observableOf } from 'rxjs'; import { of as observableOf } from 'rxjs';
@@ -49,14 +49,13 @@ const mockDataServiceMap: LazyDataServicesMap = new Map([
[GROUP.value, () => import('../../core/eperson/group-data.service').then(m => m.GroupDataService)], [GROUP.value, () => import('../../core/eperson/group-data.service').then(m => m.GroupDataService)],
]); ]);
describe('EpersonGroupListComponent test suite', () => { describe('EpersonGroupListComponent', () => {
let comp: EpersonGroupListComponent; let comp: EpersonGroupListComponent;
let compAsAny: any; let compAsAny: any;
let fixture: ComponentFixture<EpersonGroupListComponent>; let fixture: ComponentFixture<EpersonGroupListComponent>;
let de;
let groupService: any; let groupService: any;
let epersonService: any; let epersonService: any;
let paginationService; let paginationService: PaginationServiceStub;
const paginationOptions: PaginationComponentOptions = new PaginationComponentOptions(); const paginationOptions: PaginationComponentOptions = new PaginationComponentOptions();
paginationOptions.id = uniqueId('eperson-group-list-pagination-test'); paginationOptions.id = uniqueId('eperson-group-list-pagination-test');
@@ -129,7 +128,6 @@ describe('EpersonGroupListComponent test suite', () => {
})); }));
describe('', () => { describe('', () => {
let testComp: TestComponent;
let testFixture: ComponentFixture<TestComponent>; let testFixture: ComponentFixture<TestComponent>;
// synchronous beforeEach // synchronous beforeEach
@@ -139,7 +137,6 @@ describe('EpersonGroupListComponent test suite', () => {
<ds-eperson-group-list [isListOfEPerson]="isListOfEPerson" [initSelected]="initSelected"></ds-eperson-group-list>`; <ds-eperson-group-list [isListOfEPerson]="isListOfEPerson" [initSelected]="initSelected"></ds-eperson-group-list>`;
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>; testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
testComp = testFixture.componentInstance;
}); });
afterEach(() => { afterEach(() => {
@@ -167,7 +164,6 @@ describe('EpersonGroupListComponent test suite', () => {
afterEach(() => { afterEach(() => {
comp = null; comp = null;
compAsAny = null; compAsAny = null;
de = null;
fixture.destroy(); fixture.destroy();
}); });
@@ -181,29 +177,25 @@ describe('EpersonGroupListComponent test suite', () => {
}); });
})); }));
it('should init entrySelectedId', fakeAsync(() => { it('should init entrySelectedId', fakeAsync(async () => {
spyOn(comp, 'updateList'); spyOn(comp, 'updateList');
comp.initSelected = EPersonMock.id; comp.initSelected = EPersonMock.id;
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { await fixture.whenStable();
expect(compAsAny.entrySelectedId.value).toBe(EPersonMock.id); expect(comp.entrySelectedId$.value).toBe(EPersonMock.id);
});
})); }));
it('should init the list of eperson', fakeAsync(() => { it('should init the list of eperson', fakeAsync(async () => {
epersonService.searchByScope.and.returnValue(observableOf(epersonPaginatedListRD)); epersonService.searchByScope.and.returnValue(observableOf(epersonPaginatedListRD));
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { await fixture.whenStable();
expect(compAsAny.list$.value).toEqual(epersonPaginatedListRD); expect(comp.list$).toBeObservable(hot('(a|)', {
expect(comp.getList()).toBeObservable(cold('a', { a: epersonPaginatedList,
a: epersonPaginatedListRD, }));
}));
});
})); }));
it('should emit select event', () => { it('should emit select event', () => {
@@ -211,23 +203,13 @@ describe('EpersonGroupListComponent test suite', () => {
comp.emitSelect(EPersonMock); comp.emitSelect(EPersonMock);
expect(comp.select.emit).toHaveBeenCalled(); expect(comp.select.emit).toHaveBeenCalled();
expect(compAsAny.entrySelectedId.value).toBe(EPersonMock.id); expect(comp.entrySelectedId$.value).toBe(EPersonMock.id);
}); });
it('should return true when entry is selected', () => { it('should return the entrySelectedId$ value', () => {
compAsAny.entrySelectedId.next(EPersonMock.id); comp.entrySelectedId$.next(EPersonMock.id);
expect(comp.isSelected(EPersonMock)).toBeObservable(cold('a', { expect(comp.entrySelectedId$.value).toBe(EPersonMock.id);
a: true,
}));
});
it('should return false when entry is not selected', () => {
compAsAny.entrySelectedId.next('');
expect(comp.isSelected(EPersonMock)).toBeObservable(cold('a', {
a: false,
}));
}); });
}); });
@@ -245,7 +227,6 @@ describe('EpersonGroupListComponent test suite', () => {
afterEach(() => { afterEach(() => {
comp = null; comp = null;
compAsAny = null; compAsAny = null;
de = null;
fixture.destroy(); fixture.destroy();
}); });
@@ -260,27 +241,24 @@ describe('EpersonGroupListComponent test suite', () => {
})); }));
it('should init entrySelectedId', fakeAsync(() => { it('should init entrySelectedId', fakeAsync(async () => {
spyOn(comp, 'updateList'); spyOn(comp, 'updateList');
comp.initSelected = GroupMock.id; comp.initSelected = GroupMock.id;
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { await fixture.whenStable();
expect(compAsAny.entrySelectedId.value).toBe(GroupMock.id); expect(comp.entrySelectedId$.value).toBe(GroupMock.id);
});
})); }));
it('should init the list of group', fakeAsync(() => { it('should init the list of group', fakeAsync(async () => {
groupService.searchGroups.and.returnValue(observableOf(groupPaginatedListRD)); groupService.searchGroups.and.returnValue(observableOf(groupPaginatedListRD));
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { await fixture.whenStable();
expect(compAsAny.list$.value).toEqual(groupPaginatedListRD); expect(comp.list$).toBeObservable(hot('(a|)', {
expect(comp.getList()).toBeObservable(cold('a', { a: groupPaginatedList,
a: groupPaginatedListRD, }));
}));
});
})); }));
it('should emit select event', () => { it('should emit select event', () => {
@@ -288,27 +266,16 @@ describe('EpersonGroupListComponent test suite', () => {
comp.emitSelect(GroupMock); comp.emitSelect(GroupMock);
expect(comp.select.emit).toHaveBeenCalled(); expect(comp.select.emit).toHaveBeenCalled();
expect(compAsAny.entrySelectedId.value).toBe(GroupMock.id); expect(comp.entrySelectedId$.value).toBe(GroupMock.id);
}); });
it('should return true when entry is selected', () => { it('should return the entrySelectedId$ value', () => {
compAsAny.entrySelectedId.next(EPersonMock.id); comp.entrySelectedId$.next(GroupMock.id);
expect(comp.isSelected(EPersonMock)).toBeObservable(cold('a', { expect(comp.entrySelectedId$.value).toBe(GroupMock.id);
a: true,
}));
});
it('should return false when entry is not selected', () => {
compAsAny.entrySelectedId.next('');
expect(comp.isSelected(EPersonMock)).toBeObservable(cold('a', {
a: false,
}));
}); });
it('should update list on search triggered', () => { it('should update list on search triggered', () => {
const options: PaginationComponentOptions = comp.paginationOptions;
const event: SearchEvent = { const event: SearchEvent = {
scope: 'metadata', scope: 'metadata',
query: 'test', query: 'test',
@@ -316,7 +283,7 @@ describe('EpersonGroupListComponent test suite', () => {
spyOn(comp, 'updateList'); spyOn(comp, 'updateList');
comp.onSearch(event); comp.onSearch(event);
expect(compAsAny.updateList).toHaveBeenCalledWith('metadata', 'test'); expect(comp.updateList).toHaveBeenCalledWith('metadata', 'test');
}); });
}); });
}); });

View File

@@ -14,9 +14,8 @@ import uniqueId from 'lodash/uniqueId';
import { import {
BehaviorSubject, BehaviorSubject,
Observable, Observable,
Subscription,
} from 'rxjs'; } from 'rxjs';
import { map } from 'rxjs/operators'; import { switchMap } from 'rxjs/operators';
import { import {
APP_DATA_SERVICES_MAP, APP_DATA_SERVICES_MAP,
@@ -25,21 +24,21 @@ import {
import { DSONameService } from '../../core/breadcrumbs/dso-name.service'; import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
import { FindListOptions } from '../../core/data/find-list-options.model'; import { FindListOptions } from '../../core/data/find-list-options.model';
import { PaginatedList } from '../../core/data/paginated-list.model'; import { PaginatedList } from '../../core/data/paginated-list.model';
import { RemoteData } from '../../core/data/remote-data';
import { EPersonDataService } from '../../core/eperson/eperson-data.service'; import { EPersonDataService } from '../../core/eperson/eperson-data.service';
import { GroupDataService } from '../../core/eperson/group-data.service'; import { GroupDataService } from '../../core/eperson/group-data.service';
import { EPerson } from '../../core/eperson/models/eperson.model';
import { EPERSON } from '../../core/eperson/models/eperson.resource-type'; import { EPERSON } from '../../core/eperson/models/eperson.resource-type';
import { Group } from '../../core/eperson/models/group.model';
import { GROUP } from '../../core/eperson/models/group.resource-type'; import { GROUP } from '../../core/eperson/models/group.resource-type';
import { lazyDataService } from '../../core/lazy-data-service'; import { lazyDataService } from '../../core/lazy-data-service';
import { PaginationService } from '../../core/pagination/pagination.service'; import { PaginationService } from '../../core/pagination/pagination.service';
import { DSpaceObject } from '../../core/shared/dspace-object.model'; import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { getFirstCompletedRemoteData } from '../../core/shared/operators'; import {
getFirstCompletedRemoteData,
getRemoteDataPayload,
} from '../../core/shared/operators';
import { ResourceType } from '../../core/shared/resource-type'; import { ResourceType } from '../../core/shared/resource-type';
import { fadeInOut } from '../animations/fade'; import { fadeInOut } from '../animations/fade';
import {
hasValue,
isNotEmpty,
} from '../empty.util';
import { PaginationComponent } from '../pagination/pagination.component'; import { PaginationComponent } from '../pagination/pagination.component';
import { PaginationComponentOptions } from '../pagination/pagination-component-options.model'; import { PaginationComponentOptions } from '../pagination/pagination-component-options.model';
import { SearchEvent } from './eperson-group-list-event-type'; import { SearchEvent } from './eperson-group-list-event-type';
@@ -101,21 +100,13 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy {
/** /**
* A list of eperson or group * A list of eperson or group
*/ */
private list$: BehaviorSubject<RemoteData<PaginatedList<DSpaceObject>>> = new BehaviorSubject<RemoteData<PaginatedList<DSpaceObject>>>({} as any); list$: Observable<PaginatedList<EPerson | Group>>;
/** /**
* The eperson or group's id selected * The eperson or group's id selected
* @type {string} * @type {string}
*/ */
private entrySelectedId: BehaviorSubject<string> = new BehaviorSubject<string>(''); entrySelectedId$: BehaviorSubject<string> = new BehaviorSubject('');
/**
* Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array}
*/
private subs: Subscription[] = [];
private pageConfigSub: Subscription;
/** /**
* Initialize instance variables and inject the properly UpdateDataServiceImpl * Initialize instance variables and inject the properly UpdateDataServiceImpl
@@ -143,7 +134,7 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy {
this.paginationOptions.pageSize = 5; this.paginationOptions.pageSize = 5;
if (this.initSelected) { if (this.initSelected) {
this.entrySelectedId.next(this.initSelected); this.entrySelectedId$.next(this.initSelected);
} }
this.updateList(this.currentSearchScope, this.currentSearchQuery); this.updateList(this.currentSearchScope, this.currentSearchQuery);
@@ -158,28 +149,9 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy {
*/ */
emitSelect(entry: DSpaceObject): void { emitSelect(entry: DSpaceObject): void {
this.select.emit(entry); this.select.emit(entry);
this.entrySelectedId.next(entry.id); this.entrySelectedId$.next(entry.id);
} }
/**
* Return the list of eperson or group
*/
getList(): Observable<RemoteData<PaginatedList<DSpaceObject>>> {
return this.list$.asObservable();
}
/**
* Return a boolean representing if a table row is selected
*
* @return {boolean}
*/
isSelected(entry: DSpaceObject): Observable<boolean> {
return this.entrySelectedId.asObservable().pipe(
map((selectedId) => isNotEmpty(selectedId) && selectedId === entry.id),
);
}
/** /**
* Method called on search * Method called on search
*/ */
@@ -194,38 +166,26 @@ export class EpersonGroupListComponent implements OnInit, OnDestroy {
* Retrieve a paginate list of eperson or group * Retrieve a paginate list of eperson or group
*/ */
updateList(scope: string, query: string): void { updateList(scope: string, query: string): void {
if (hasValue(this.pageConfigSub)) { this.list$ = this.paginationService.getCurrentPagination(this.paginationOptions.id, this.paginationOptions).pipe(
this.pageConfigSub.unsubscribe(); switchMap((paginationOptions) => {
} const options: FindListOptions = Object.assign(new FindListOptions(), {
this.pageConfigSub = this.paginationService.getCurrentPagination(this.paginationOptions.id, this.paginationOptions)
.subscribe((paginationOptions) => {
const options: FindListOptions = Object.assign({}, new FindListOptions(), {
elementsPerPage: paginationOptions.pageSize, elementsPerPage: paginationOptions.pageSize,
currentPage: paginationOptions.currentPage, currentPage: paginationOptions.currentPage,
}); });
const search$: Observable<RemoteData<PaginatedList<DSpaceObject>>> = this.isListOfEPerson ? return this.isListOfEPerson ?
(this.dataService as EPersonDataService).searchByScope(scope, query, options) : (this.dataService as EPersonDataService).searchByScope(scope, query, options) :
(this.dataService as GroupDataService).searchGroups(query, options); (this.dataService as GroupDataService).searchGroups(query, options);
}),
this.subs.push(search$.pipe(getFirstCompletedRemoteData()) getFirstCompletedRemoteData(),
.subscribe((list: RemoteData<PaginatedList<DSpaceObject>>) => { getRemoteDataPayload(),
if (hasValue(this.list$)) { );
this.list$.next(list);
}
}),
);
});
} }
/** /**
* Unsubscribe from all subscriptions * Unsubscribe from all subscriptions
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.list$ = null;
this.subs
.filter((subscription) => hasValue(subscription))
.forEach((subscription) => subscription.unsubscribe());
this.paginationService.clearPagination(this.paginationOptions.id); this.paginationService.clearPagination(this.paginationOptions.id);
} }

View File

@@ -147,10 +147,10 @@ export class HostWindowService {
} }
isXsOrSm(): Observable<boolean> { isXsOrSm(): Observable<boolean> {
return observableCombineLatest( return observableCombineLatest([
this.isXs(), this.isXs(),
this.isSm(), this.isSm(),
).pipe( ]).pipe(
map(([isXs, isSm]) => isXs || isSm), map(([isXs, isSm]) => isXs || isSm),
distinctUntilChanged(), distinctUntilChanged(),
); );

View File

@@ -51,7 +51,7 @@
</div> </div>
} }
<ng-content></ng-content> <ng-content></ng-content>
@if (shouldShowBottomPager | async) { @if (showBottomPager$ | async) {
<div> <div>
@if (showPaginator) { @if (showPaginator) {
<div class="pagination justify-content-center clearfix bottom"> <div class="pagination justify-content-center clearfix bottom">

View File

@@ -226,6 +226,11 @@ export class PaginationComponent implements OnChanges, OnDestroy, OnInit {
public showingDetails$: Observable<PaginationDetails>; public showingDetails$: Observable<PaginationDetails>;
/**
* Whether the current pagination should show a bottom pages
*/
showBottomPager$: Observable<boolean>;
/** /**
* Array to track all subscriptions and unsubscribe them onDestroy * Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array} * @type {Array}
@@ -255,7 +260,7 @@ export class PaginationComponent implements OnChanges, OnDestroy, OnInit {
} }
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
if (changes.collectionSize.currentValue !== changes.collectionSize.previousValue) { if (hasValue(changes.collectionSize)) {
this.showingDetails$ = this.getShowingDetails(this.collectionSize); this.showingDetails$ = this.getShowingDetails(this.collectionSize);
} }
} }
@@ -294,6 +299,7 @@ export class PaginationComponent implements OnChanges, OnDestroy, OnInit {
this.sortField$ = this.paginationService.getCurrentSort(this.id, sortOptions).pipe( this.sortField$ = this.paginationService.getCurrentSort(this.id, sortOptions).pipe(
map((currentSort) => currentSort.field), map((currentSort) => currentSort.field),
); );
this.showBottomPager$ = this.shouldShowBottomPager;
} }
constructor( constructor(

View File

@@ -2,12 +2,12 @@
#sectionAdd="ngbDropdown" #sectionAdd="ngbDropdown"
placement="bottom-right" placement="bottom-right"
class="d-inline-block" class="d-inline-block"
[ngClass]="{'w-100': windowService.isXs()}"> [ngClass]="{'w-100': isXs$}">
@if (hasSections$ | async) { @if (hasSections$ | async) {
<button class="btn btn-outline-primary dropdown-toggle" <button class="btn btn-outline-primary dropdown-toggle"
id="sectionControls" id="sectionControls"
[dsBtnDisabled]="(hasSections$ | async) !== true" [dsBtnDisabled]="(hasSections$ | async) !== true"
[ngClass]="{'w-100': (windowService.isXs() | async)}" [ngClass]="{'w-100': (isXs$ | async)}"
ngbDropdownToggle> ngbDropdownToggle>
{{ 'submission.sections.general.add-more' | translate }} <i class="fa fa-plus" aria-hidden="true"></i> {{ 'submission.sections.general.add-more' | translate }} <i class="fa fa-plus" aria-hidden="true"></i>
</button> </button>
@@ -15,7 +15,7 @@
<div ngbDropdownMenu <div ngbDropdownMenu
class="sections-dropdown-menu" class="sections-dropdown-menu"
aria-labelledby="sectionControls" aria-labelledby="sectionControls"
[ngClass]="{'w-100': (windowService.isXs() | async)}"> [ngClass]="{'w-100': (isXs$ | async)}">
@if ((hasSections$ | async) !== true) { @if ((hasSections$ | async) !== true) {
<button class="dropdown-item disabled"> <button class="dropdown-item disabled">
{{ 'submission.sections.general.no-sections' | translate }} {{ 'submission.sections.general.no-sections' | translate }}

View File

@@ -51,6 +51,11 @@ export class SubmissionFormSectionAddComponent implements OnInit {
*/ */
public hasSections$: Observable<boolean>; public hasSections$: Observable<boolean>;
/**
* A boolean representing whether it's a small screen
*/
isXs$: Observable<boolean>;
/** /**
* Initialize instance variables * Initialize instance variables
* *
@@ -71,6 +76,7 @@ export class SubmissionFormSectionAddComponent implements OnInit {
this.hasSections$ = this.sectionList$.pipe( this.hasSections$ = this.sectionList$.pipe(
map((list: SectionDataObject[]) => list.length > 0), map((list: SectionDataObject[]) => list.length > 0),
); );
this.isXs$ = this.windowService.isXs();
} }
/** /**

View File

@@ -1,5 +1,5 @@
<div class="container-fluid"> <div class="container-fluid">
@if ((isLoading() | async) !== true) { @if ((isLoading$ | async) !== true) {
<div class="submission-form-header mb-3 d-flex flex-wrap position-sticky"> <div class="submission-form-header mb-3 d-flex flex-wrap position-sticky">
@if ((uploadEnabled$ | async)) { @if ((uploadEnabled$ | async)) {
<div class="w-100"> <div class="w-100">
@@ -30,7 +30,7 @@
} }
<div class="submission-form-content"> <div class="submission-form-content">
@if ((isLoading() | async)) { @if ((isLoading$ | async)) {
<ds-loading message="Loading..."></ds-loading> <ds-loading message="Loading..."></ds-loading>
} }
@for (object of $any(submissionSections | async); track object) { @for (object of $any(submissionSections | async); track object) {
@@ -40,7 +40,7 @@
</ds-submission-section-container> </ds-submission-section-container>
} }
</div> </div>
@if ((isLoading() | async) !== true) { @if ((isLoading$ | async) !== true) {
<div class="submission-form-footer mt-3 mb-3 position-sticky"> <div class="submission-form-footer mt-3 mb-3 position-sticky">
<ds-submission-form-footer [submissionId]="submissionId"></ds-submission-form-footer> <ds-submission-form-footer [submissionId]="submissionId"></ds-submission-form-footer>
</div> </div>

View File

@@ -147,7 +147,7 @@ describe('SubmissionFormComponent Component', () => {
expect(compAsAny.submissionSections).toBeUndefined(); expect(compAsAny.submissionSections).toBeUndefined();
expect(compAsAny.subs).toEqual([]); expect(compAsAny.subs).toEqual([]);
expect(submissionServiceStub.startAutoSave).not.toHaveBeenCalled(); expect(submissionServiceStub.startAutoSave).not.toHaveBeenCalled();
expect(comp.loading).toBeObservable(cold('(a|)', { a: true })); expect(comp.isLoading$).toBeObservable(cold('(a|)', { a: true }));
done(); done();
}); });

View File

@@ -124,7 +124,7 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy {
* A boolean representing if a submission form is pending * A boolean representing if a submission form is pending
* @type {Observable<boolean>} * @type {Observable<boolean>}
*/ */
public loading: Observable<boolean> = observableOf(true); public isLoading$: Observable<boolean> = observableOf(true);
/** /**
* Emits true when the submission config has bitstream uploading enabled in submission * Emits true when the submission config has bitstream uploading enabled in submission
@@ -196,7 +196,7 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy {
this.uploadEnabled$ = this.sectionsService.isSectionTypeAvailable(this.submissionId, SectionsType.Upload); this.uploadEnabled$ = this.sectionsService.isSectionTypeAvailable(this.submissionId, SectionsType.Upload);
// check if is submission loading // check if is submission loading
this.loading = this.submissionService.getSubmissionObject(this.submissionId).pipe( this.isLoading$ = this.submissionService.getSubmissionObject(this.submissionId).pipe(
filter(() => this.isActive), filter(() => this.isActive),
map((submission: SubmissionObjectEntry) => submission.isLoading), map((submission: SubmissionObjectEntry) => submission.isLoading),
map((isLoading: boolean) => isLoading), map((isLoading: boolean) => isLoading),
@@ -302,16 +302,6 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy {
} }
} }
/**
* Check if submission form is loading
*/
isLoading(): Observable<boolean> {
return this.loading;
}
/**
* Check if submission form is loading
*/
protected getSectionsList(): Observable<any> { protected getSectionsList(): Observable<any> {
return this.submissionService.getSubmissionSections(this.submissionId).pipe( return this.submissionService.getSubmissionSections(this.submissionId).pipe(
filter((sections: SectionDataObject[]) => isNotEmpty(sections)), filter((sections: SectionDataObject[]) => isNotEmpty(sections)),

View File

@@ -120,32 +120,28 @@
} }
} }
@if (getCcLicenseLink$()) { @if (ccLicenseLink$) {
<ng-container *ngVar="getCcLicenseLink$() | async as licenseLink"> @let licenseLink = (ccLicenseLink$ | async);
@if (!licenseLink) { @if (licenseLink) {
<div
class="mt-2 p-4 bg-light text-dark">
<div> <div>
<ds-loading></ds-loading> {{ 'submission.sections.ccLicense.link' | translate }}
</div> </div>
} <a class="license-link" href="{{ licenseLink }}" target="_blank" rel="noopener noreferrer">
@if (licenseLink) { {{ licenseLink }}
<div </a>
class="mt-2 p-4 bg-light text-dark"> <div class="m-2">
<div> <div (click)="setAccepted(!accepted)">
{{ 'submission.sections.ccLicense.link' | translate }} <input type="checkbox"
</div> class="me-2"
<a class="license-link" href="{{ licenseLink }}" target="_blank" rel="noopener noreferrer"> title="accepted"
{{ licenseLink }} [checked]="accepted">
</a> <span> {{ 'submission.sections.ccLicense.confirmation' | translate }}</span>
<div class="m-2">
<div (click)="setAccepted(!accepted)">
<input type="checkbox"
class="me-2"
title="accepted"
[checked]="accepted">
<span> {{ 'submission.sections.ccLicense.confirmation' | translate }}</span>
</div>
</div> </div>
</div> </div>
} </div>
</ng-container> } @else {
<ds-loading></ds-loading>
}
} }

View File

@@ -3,6 +3,9 @@ import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
Inject, Inject,
OnChanges,
OnInit,
SimpleChanges,
} from '@angular/core'; } from '@angular/core';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { import {
@@ -42,9 +45,12 @@ import {
import { WorkspaceitemSectionCcLicenseObject } from '../../../core/submission/models/workspaceitem-section-cc-license.model'; import { WorkspaceitemSectionCcLicenseObject } from '../../../core/submission/models/workspaceitem-section-cc-license.model';
import { SubmissionCcLicenseDataService } from '../../../core/submission/submission-cc-license-data.service'; import { SubmissionCcLicenseDataService } from '../../../core/submission/submission-cc-license-data.service';
import { SubmissionCcLicenseUrlDataService } from '../../../core/submission/submission-cc-license-url-data.service'; import { SubmissionCcLicenseUrlDataService } from '../../../core/submission/submission-cc-license-url-data.service';
import { BtnDisabledDirective } from '../../../shared/btn-disabled.directive';
import { DsSelectComponent } from '../../../shared/ds-select/ds-select.component'; import { DsSelectComponent } from '../../../shared/ds-select/ds-select.component';
import { isNotEmpty } from '../../../shared/empty.util'; import {
hasNoValue,
hasValue,
isNotEmpty,
} from '../../../shared/empty.util';
import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component'; import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component';
import { VarDirective } from '../../../shared/utils/var.directive'; import { VarDirective } from '../../../shared/utils/var.directive';
import { SectionModelComponent } from '../models/section.model'; import { SectionModelComponent } from '../models/section.model';
@@ -68,11 +74,10 @@ import { SectionsType } from '../sections-type';
NgbDropdownModule, NgbDropdownModule,
FormsModule, FormsModule,
InfiniteScrollModule, InfiniteScrollModule,
BtnDisabledDirective,
], ],
standalone: true, standalone: true,
}) })
export class SubmissionSectionCcLicensesComponent extends SectionModelComponent { export class SubmissionSectionCcLicensesComponent extends SectionModelComponent implements OnChanges, OnInit {
/** /**
* The form id * The form id
@@ -148,6 +153,8 @@ export class SubmissionSectionCcLicensesComponent extends SectionModelComponent
return this.data.accepted; return this.data.accepted;
} }
ccLicenseLink$: Observable<string>;
constructor( constructor(
protected modalService: NgbModal, protected modalService: NgbModal,
protected sectionService: SectionsService, protected sectionService: SectionsService,
@@ -167,6 +174,19 @@ export class SubmissionSectionCcLicensesComponent extends SectionModelComponent
); );
} }
ngOnInit(): void {
super.ngOnInit();
if (hasNoValue(this.ccLicenseLink$)) {
this.ccLicenseLink$ = this.getCcLicenseLink$();
}
}
ngOnChanges(changes: SimpleChanges): void {
if (hasValue(changes.sectionData) || hasValue(changes.submissionCcLicenses)) {
this.ccLicenseLink$ = this.getCcLicenseLink$();
}
}
/** /**
* The data of this section. * The data of this section.
*/ */
@@ -191,6 +211,7 @@ export class SubmissionSectionCcLicensesComponent extends SectionModelComponent
}, },
uri: undefined, uri: undefined,
}); });
this.ccLicenseLink$ = this.getCcLicenseLink$();
} }
/** /**
@@ -222,6 +243,7 @@ export class SubmissionSectionCcLicensesComponent extends SectionModelComponent
}, },
accepted: false, accepted: false,
}); });
this.ccLicenseLink$ = this.getCcLicenseLink$();
} }
/** /**

View File

@@ -1,20 +1,14 @@
<!-- @let identifierData = (identifierData$ | async);
Template for the identifiers submission section component @if (identifierData && identifierData.identifiers) {
@author Kim Shepherd <div>
--> <span>{{'submission.sections.identifiers.info' | translate}}</span>
<!-- Main identifier data --> <ul>
<ng-container *ngVar="(getIdentifierData() | async) as identifierData"> @for (identifier of identifierData.identifiers; track identifier) {
@if (identifierData && identifierData.identifiers) { @if (identifierData.displayTypes.includes(identifier.identifierType) && identifier.value) {
<div> <li>{{'submission.sections.identifiers.' + identifier.identifierType + '_label' | translate}}
<span>{{'submission.sections.identifiers.info' | translate}}</span> {{identifier.value}}</li>
<ul>
@for (identifier of identifierData.identifiers; track identifier) {
@if (identifierData.displayTypes.includes(identifier.identifierType) && identifier.value) {
<li>{{'submission.sections.identifiers.' + identifier.identifierType + '_label' | translate}}
{{identifier.value}}</li>
}
} }
</ul> }
</div> </ul>
} </div>
</ng-container> }

View File

@@ -12,11 +12,9 @@ import {
import { import {
Observable, Observable,
of as observableOf, of as observableOf,
Subscription,
} from 'rxjs'; } from 'rxjs';
import { WorkspaceitemSectionIdentifiersObject } from '../../../core/submission/models/workspaceitem-section-identifiers.model'; import { WorkspaceitemSectionIdentifiersObject } from '../../../core/submission/models/workspaceitem-section-identifiers.model';
import { AlertType } from '../../../shared/alert/alert-type';
import { VarDirective } from '../../../shared/utils/var.directive'; import { VarDirective } from '../../../shared/utils/var.directive';
import { SubmissionService } from '../../submission.service'; import { SubmissionService } from '../../submission.service';
import { SectionModelComponent } from '../models/section.model'; import { SectionModelComponent } from '../models/section.model';
@@ -43,11 +41,6 @@ import { SectionsService } from '../sections.service';
}) })
export class SubmissionSectionIdentifiersComponent extends SectionModelComponent implements OnInit { export class SubmissionSectionIdentifiersComponent extends SectionModelComponent implements OnInit {
/**
* The Alert categories.
* @type {AlertType}
*/
public AlertTypeEnum = AlertType;
/** /**
* Variable to track if the section is loading. * Variable to track if the section is loading.
@@ -59,14 +52,7 @@ export class SubmissionSectionIdentifiersComponent extends SectionModelComponent
* Observable identifierData subject * Observable identifierData subject
* @type {Observable<WorkspaceitemSectionIdentifiersObject>} * @type {Observable<WorkspaceitemSectionIdentifiersObject>}
*/ */
public identifierData$: Observable<WorkspaceitemSectionIdentifiersObject> = new Observable<WorkspaceitemSectionIdentifiersObject>(); public identifierData$: Observable<WorkspaceitemSectionIdentifiersObject>;
/**
* Array to track all subscriptions and unsubscribe them onDestroy
* @type {Array}
*/
protected subs: Subscription[] = [];
public subbedIdentifierData: WorkspaceitemSectionIdentifiersObject;
/** /**
* Initialize instance variables. * Initialize instance variables.
@@ -87,10 +73,6 @@ export class SubmissionSectionIdentifiersComponent extends SectionModelComponent
super(injectedCollectionId, injectedSectionData, injectedSubmissionId); super(injectedCollectionId, injectedSectionData, injectedSubmissionId);
} }
ngOnInit(): void {
super.ngOnInit();
}
/** /**
* Initialize all instance variables and retrieve configuration. * Initialize all instance variables and retrieve configuration.
*/ */
@@ -99,13 +81,6 @@ export class SubmissionSectionIdentifiersComponent extends SectionModelComponent
this.identifierData$ = this.getIdentifierData(); this.identifierData$ = this.getIdentifierData();
} }
/**
* Check if identifier section has read-only visibility
*/
isReadOnly(): boolean {
return true;
}
/** /**
* Unsubscribe from all subscriptions, if needed. * Unsubscribe from all subscriptions, if needed.
*/ */