mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge pull request #1936 from atmire/w2p-95335_created-ListableNotificationObjectComponent_contribute-7.2
Created `ListableNotificationObject`
This commit is contained in:
@@ -21,6 +21,7 @@ const effects = [
|
|||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
NavbarSectionComponent,
|
NavbarSectionComponent,
|
||||||
|
ExpandableNavbarSectionComponent,
|
||||||
ThemedExpandableNavbarSectionComponent,
|
ThemedExpandableNavbarSectionComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -34,11 +35,9 @@ const ENTRY_COMPONENTS = [
|
|||||||
CoreModule.forRoot()
|
CoreModule.forRoot()
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
...ENTRY_COMPONENTS,
|
||||||
NavbarComponent,
|
NavbarComponent,
|
||||||
ThemedNavbarComponent,
|
ThemedNavbarComponent,
|
||||||
NavbarSectionComponent,
|
|
||||||
ExpandableNavbarSectionComponent,
|
|
||||||
ThemedExpandableNavbarSectionComponent,
|
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
exports: [
|
exports: [
|
||||||
|
@@ -53,8 +53,9 @@ export class AuthorizedCollectionSelectorComponent extends DSOSelectorComponent
|
|||||||
* Perform a search for authorized collections with the current query and page
|
* Perform a search for authorized collections with the current query and page
|
||||||
* @param query Query to search objects for
|
* @param query Query to search objects for
|
||||||
* @param page Page to retrieve
|
* @param page Page to retrieve
|
||||||
|
* @param useCache Whether or not to use the cache
|
||||||
*/
|
*/
|
||||||
search(query: string, page: number): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> {
|
search(query: string, page: number, useCache: boolean = true): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> {
|
||||||
let searchListService$: Observable<RemoteData<PaginatedList<Collection>>> = null;
|
let searchListService$: Observable<RemoteData<PaginatedList<Collection>>> = null;
|
||||||
const findOptions: FindListOptions = {
|
const findOptions: FindListOptions = {
|
||||||
currentPage: page,
|
currentPage: page,
|
||||||
@@ -69,7 +70,7 @@ export class AuthorizedCollectionSelectorComponent extends DSOSelectorComponent
|
|||||||
findOptions);
|
findOptions);
|
||||||
} else {
|
} else {
|
||||||
searchListService$ = this.collectionDataService
|
searchListService$ = this.collectionDataService
|
||||||
.getAuthorizedCollection(query, findOptions, true, false, followLink('parentCommunity'));
|
.getAuthorizedCollection(query, findOptions, useCache, false, followLink('parentCommunity'));
|
||||||
}
|
}
|
||||||
return searchListService$.pipe(
|
return searchListService$.pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
|
@@ -21,12 +21,12 @@
|
|||||||
</button>
|
</button>
|
||||||
<button *ngFor="let listEntry of (listEntries$ | async)"
|
<button *ngFor="let listEntry of (listEntries$ | async)"
|
||||||
class="list-group-item list-group-item-action border-0 list-entry"
|
class="list-group-item list-group-item-action border-0 list-entry"
|
||||||
[ngClass]="{'bg-primary': listEntry.indexableObject.id === currentDSOId}"
|
[ngClass]="{'bg-primary': listEntry['id'] === currentDSOId}"
|
||||||
title="{{ getName(listEntry) }}"
|
title="{{ getName(listEntry) }}"
|
||||||
dsHoverClass="ds-hover"
|
dsHoverClass="ds-hover"
|
||||||
(click)="onSelect.emit(listEntry.indexableObject)" #listEntryElement>
|
(click)="onClick(listEntry)" #listEntryElement>
|
||||||
<ds-listable-object-component-loader [object]="listEntry" [viewMode]="viewMode"
|
<ds-listable-object-component-loader [object]="listEntry" [viewMode]="viewMode"
|
||||||
[linkType]=linkTypes.None [context]="getContext(listEntry.indexableObject.id)"></ds-listable-object-component-loader>
|
[linkType]=linkTypes.None [context]="getContext(listEntry['id'])"></ds-listable-object-component-loader>
|
||||||
</button>
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<button *ngIf="loading"
|
<button *ngIf="loading"
|
||||||
|
@@ -35,6 +35,14 @@ import { RemoteData } from '../../../core/data/remote-data';
|
|||||||
import { NotificationsService } from '../../notifications/notifications.service';
|
import { NotificationsService } from '../../notifications/notifications.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import {
|
||||||
|
ListableNotificationObject
|
||||||
|
} from '../../object-list/listable-notification-object/listable-notification-object.model';
|
||||||
|
import { ListableObject } from '../../object-collection/shared/listable-object.model';
|
||||||
|
import { NotificationType } from '../../notifications/models/notification-type';
|
||||||
|
import {
|
||||||
|
LISTABLE_NOTIFICATION_OBJECT
|
||||||
|
} from '../../object-list/listable-notification-object/listable-notification-object.resource-type';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-dso-selector',
|
selector: 'ds-dso-selector',
|
||||||
@@ -82,7 +90,7 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* List with search results of DSpace objects for the current query
|
* List with search results of DSpace objects for the current query
|
||||||
*/
|
*/
|
||||||
listEntries$: BehaviorSubject<SearchResult<DSpaceObject>[]> = new BehaviorSubject(null);
|
listEntries$: BehaviorSubject<ListableObject[]> = new BehaviorSubject(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current page to load
|
* The current page to load
|
||||||
@@ -116,11 +124,6 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
linkTypes = CollectionElementLinkType;
|
linkTypes = CollectionElementLinkType;
|
||||||
|
|
||||||
/**
|
|
||||||
* Track whether the element has the mouse over it
|
|
||||||
*/
|
|
||||||
isMouseOver = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array to track all subscriptions and unsubscribe them onDestroy
|
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||||
* @type {Array}
|
* @type {Array}
|
||||||
@@ -182,24 +185,28 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
).subscribe((rd) => {
|
).subscribe((rd: RemoteData<PaginatedList<SearchResult<DSpaceObject>>>) => {
|
||||||
this.loading = false;
|
this.updateList(rd);
|
||||||
if (rd.hasSucceeded) {
|
|
||||||
const currentEntries = this.listEntries$.getValue();
|
|
||||||
if (hasNoValue(currentEntries)) {
|
|
||||||
this.listEntries$.next(rd.payload.page);
|
|
||||||
} else {
|
|
||||||
this.listEntries$.next([...currentEntries, ...rd.payload.page]);
|
|
||||||
}
|
|
||||||
// Check if there are more pages available after the current one
|
|
||||||
this.hasNextPage = rd.payload.totalElements > this.listEntries$.getValue().length;
|
|
||||||
} else {
|
|
||||||
this.listEntries$.next(null);
|
|
||||||
this.hasNextPage = false;
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateList(rd: RemoteData<PaginatedList<SearchResult<DSpaceObject>>>) {
|
||||||
|
this.loading = false;
|
||||||
|
const currentEntries = this.listEntries$.getValue();
|
||||||
|
if (rd.hasSucceeded) {
|
||||||
|
if (hasNoValue(currentEntries)) {
|
||||||
|
this.listEntries$.next(rd.payload.page);
|
||||||
|
} else {
|
||||||
|
this.listEntries$.next([...currentEntries, ...rd.payload.page]);
|
||||||
|
}
|
||||||
|
// Check if there are more pages available after the current one
|
||||||
|
this.hasNextPage = rd.payload.totalElements > this.listEntries$.getValue().length;
|
||||||
|
} else {
|
||||||
|
this.listEntries$.next([...(hasNoValue(currentEntries) ? [] : this.listEntries$.getValue()), new ListableNotificationObject(NotificationType.Error, 'dso-selector.results-could-not-be-retrieved', LISTABLE_NOTIFICATION_OBJECT.value)]);
|
||||||
|
this.hasNextPage = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a query to send for retrieving the current DSO
|
* Get a query to send for retrieving the current DSO
|
||||||
*/
|
*/
|
||||||
@@ -211,8 +218,9 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
|
|||||||
* Perform a search for the current query and page
|
* Perform a search for the current query and page
|
||||||
* @param query Query to search objects for
|
* @param query Query to search objects for
|
||||||
* @param page Page to retrieve
|
* @param page Page to retrieve
|
||||||
|
* @param useCache Whether or not to use the cache
|
||||||
*/
|
*/
|
||||||
search(query: string, page: number): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> {
|
search(query: string, page: number, useCache: boolean = true): Observable<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> {
|
||||||
return this.searchService.search(
|
return this.searchService.search(
|
||||||
new PaginatedSearchOptions({
|
new PaginatedSearchOptions({
|
||||||
query: query,
|
query: query,
|
||||||
@@ -220,7 +228,9 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
|
|||||||
pagination: Object.assign({}, this.defaultPagination, {
|
pagination: Object.assign({}, this.defaultPagination, {
|
||||||
currentPage: page
|
currentPage: page
|
||||||
})
|
})
|
||||||
})
|
}),
|
||||||
|
null,
|
||||||
|
useCache,
|
||||||
).pipe(
|
).pipe(
|
||||||
getFirstCompletedRemoteData()
|
getFirstCompletedRemoteData()
|
||||||
);
|
);
|
||||||
@@ -262,7 +272,28 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
|
|||||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
getName(searchResult: SearchResult<DSpaceObject>): string {
|
/**
|
||||||
return this.dsoNameService.getName(searchResult.indexableObject);
|
* Handles the user clicks on the {@link ListableObject}s. When the {@link listableObject} is a
|
||||||
|
* {@link ListableObject} it will retry the error when the user clicks it. Otherwise it will emit the {@link onSelect}.
|
||||||
|
*
|
||||||
|
* @param listableObject The {@link ListableObject} to evaluate
|
||||||
|
*/
|
||||||
|
onClick(listableObject: ListableObject): void {
|
||||||
|
if (hasValue((listableObject as SearchResult<DSpaceObject>).indexableObject)) {
|
||||||
|
this.onSelect.emit((listableObject as SearchResult<DSpaceObject>).indexableObject);
|
||||||
|
} else {
|
||||||
|
this.listEntries$.value.pop();
|
||||||
|
this.hasNextPage = true;
|
||||||
|
this.search(this.input.value ? this.input.value : '', this.currentPage$.value, false).pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
).subscribe((rd: RemoteData<PaginatedList<SearchResult<DSpaceObject>>>) => {
|
||||||
|
this.updateList(rd);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getName(listableObject: ListableObject): string {
|
||||||
|
return hasValue((listableObject as SearchResult<DSpaceObject>).indexableObject) ?
|
||||||
|
this.dsoNameService.getName((listableObject as SearchResult<DSpaceObject>).indexableObject) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1 @@
|
|||||||
|
<div class="alert d-block {{ object?.notificationType }} m-0">{{ object?.message | translate }}</div>
|
@@ -0,0 +1,43 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { ListableNotificationObjectComponent } from './listable-notification-object.component';
|
||||||
|
import { NotificationType } from '../../notifications/models/notification-type';
|
||||||
|
import { ListableNotificationObject } from './listable-notification-object.model';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
describe('ListableNotificationObjectComponent', () => {
|
||||||
|
let component: ListableNotificationObjectComponent;
|
||||||
|
let fixture: ComponentFixture<ListableNotificationObjectComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ListableNotificationObjectComponent,
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ListableNotificationObjectComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ui', () => {
|
||||||
|
it('should display the given error message', () => {
|
||||||
|
component.object = new ListableNotificationObject(NotificationType.Error, 'test error message');
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
const listableNotificationObject: Element = fixture.debugElement.query(By.css('.alert')).nativeElement;
|
||||||
|
expect(listableNotificationObject.className).toContain(NotificationType.Error);
|
||||||
|
expect(listableNotificationObject.innerHTML).toBe('test error message');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
fixture.debugElement.nativeElement.remove();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,21 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import {
|
||||||
|
AbstractListableElementComponent
|
||||||
|
} from '../../object-collection/shared/object-collection-element/abstract-listable-element.component';
|
||||||
|
import { ListableNotificationObject } from './listable-notification-object.model';
|
||||||
|
import { listableObjectComponent } from '../../object-collection/shared/listable-object/listable-object.decorator';
|
||||||
|
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||||
|
import { LISTABLE_NOTIFICATION_OBJECT } from './listable-notification-object.resource-type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The component for displaying a notifications inside an object list
|
||||||
|
*/
|
||||||
|
@listableObjectComponent(ListableNotificationObject, ViewMode.ListElement)
|
||||||
|
@listableObjectComponent(LISTABLE_NOTIFICATION_OBJECT.value, ViewMode.ListElement)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-listable-notification-object',
|
||||||
|
templateUrl: './listable-notification-object.component.html',
|
||||||
|
styleUrls: ['./listable-notification-object.component.scss'],
|
||||||
|
})
|
||||||
|
export class ListableNotificationObjectComponent extends AbstractListableElementComponent<ListableNotificationObject> {
|
||||||
|
}
|
@@ -0,0 +1,36 @@
|
|||||||
|
import { ListableObject } from '../../object-collection/shared/listable-object.model';
|
||||||
|
import { typedObject } from '../../../core/cache/builders/build-decorators';
|
||||||
|
import { TypedObject } from '../../../core/cache/typed-object.model';
|
||||||
|
import { LISTABLE_NOTIFICATION_OBJECT } from './listable-notification-object.resource-type';
|
||||||
|
import { GenericConstructor } from '../../../core/shared/generic-constructor';
|
||||||
|
import { NotificationType } from '../../notifications/models/notification-type';
|
||||||
|
import { ResourceType } from '../../../core/shared/resource-type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Object representing a notification message inside a list of objects
|
||||||
|
*/
|
||||||
|
@typedObject
|
||||||
|
export class ListableNotificationObject extends ListableObject implements TypedObject {
|
||||||
|
|
||||||
|
static type: ResourceType = LISTABLE_NOTIFICATION_OBJECT;
|
||||||
|
type: ResourceType = LISTABLE_NOTIFICATION_OBJECT;
|
||||||
|
|
||||||
|
protected renderTypes: string[];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public notificationType: NotificationType = NotificationType.Error,
|
||||||
|
public message: string = 'listable-notification-object.default-message',
|
||||||
|
...renderTypes: string[]
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this.renderTypes = renderTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method that returns as which type of object this object should be rendered.
|
||||||
|
*/
|
||||||
|
getRenderTypes(): (string | GenericConstructor<ListableObject>)[] {
|
||||||
|
return [...this.renderTypes, this.constructor as GenericConstructor<ListableObject>];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
import { ResourceType } from '../../../core/shared/resource-type';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The resource type for {@link ListableNotificationObject}
|
||||||
|
*
|
||||||
|
* Needs to be in a separate file to prevent circular
|
||||||
|
* dependencies in webpack.
|
||||||
|
*/
|
||||||
|
export const LISTABLE_NOTIFICATION_OBJECT = new ResourceType('listable-notification-object');
|
@@ -323,6 +323,9 @@ import {
|
|||||||
} from '../item-page/simple/field-components/specific-field/title/item-page-title-field.component';
|
} from '../item-page/simple/field-components/specific-field/title/item-page-title-field.component';
|
||||||
import { MarkdownPipe } from './utils/markdown.pipe';
|
import { MarkdownPipe } from './utils/markdown.pipe';
|
||||||
import { GoogleRecaptchaModule } from '../core/google-recaptcha/google-recaptcha.module';
|
import { GoogleRecaptchaModule } from '../core/google-recaptcha/google-recaptcha.module';
|
||||||
|
import {
|
||||||
|
ListableNotificationObjectComponent
|
||||||
|
} from './object-list/listable-notification-object/listable-notification-object.component';
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@@ -510,6 +513,7 @@ const COMPONENTS = [
|
|||||||
ScopeSelectorModalComponent,
|
ScopeSelectorModalComponent,
|
||||||
ItemPageTitleFieldComponent,
|
ItemPageTitleFieldComponent,
|
||||||
ThemedSearchNavbarComponent,
|
ThemedSearchNavbarComponent,
|
||||||
|
ListableNotificationObjectComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
@@ -575,7 +579,8 @@ const ENTRY_COMPONENTS = [
|
|||||||
OnClickMenuItemComponent,
|
OnClickMenuItemComponent,
|
||||||
TextMenuItemComponent,
|
TextMenuItemComponent,
|
||||||
ScopeSelectorModalComponent,
|
ScopeSelectorModalComponent,
|
||||||
ExternalLinkMenuItemComponent
|
ExternalLinkMenuItemComponent,
|
||||||
|
ListableNotificationObjectComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
const SHARED_ITEM_PAGE_COMPONENTS = [
|
const SHARED_ITEM_PAGE_COMPONENTS = [
|
||||||
|
@@ -1410,6 +1410,8 @@
|
|||||||
|
|
||||||
"dso-selector.claim.item.create-from-scratch": "Create a new one",
|
"dso-selector.claim.item.create-from-scratch": "Create a new one",
|
||||||
|
|
||||||
|
"dso-selector.results-could-not-be-retrieved": "Something went wrong, please refresh again ↻",
|
||||||
|
|
||||||
"confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}",
|
"confirmation-modal.export-metadata.header": "Export metadata for {{ dsoName }}",
|
||||||
|
|
||||||
"confirmation-modal.export-metadata.info": "Are you sure you want to export metadata for {{ dsoName }}",
|
"confirmation-modal.export-metadata.info": "Are you sure you want to export metadata for {{ dsoName }}",
|
||||||
@@ -4826,4 +4828,5 @@
|
|||||||
"person.orcid.registry.auth": "ORCID Authorizations",
|
"person.orcid.registry.auth": "ORCID Authorizations",
|
||||||
"home.recent-submissions.head": "Recent Submissions",
|
"home.recent-submissions.head": "Recent Submissions",
|
||||||
|
|
||||||
|
"listable-notification-object.default-message": "This object couldn't be retrieved",
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user