mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +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 = [
|
||||
// put only entry components that use custom decorator
|
||||
NavbarSectionComponent,
|
||||
ExpandableNavbarSectionComponent,
|
||||
ThemedExpandableNavbarSectionComponent,
|
||||
];
|
||||
|
||||
@@ -34,11 +35,9 @@ const ENTRY_COMPONENTS = [
|
||||
CoreModule.forRoot()
|
||||
],
|
||||
declarations: [
|
||||
...ENTRY_COMPONENTS,
|
||||
NavbarComponent,
|
||||
ThemedNavbarComponent,
|
||||
NavbarSectionComponent,
|
||||
ExpandableNavbarSectionComponent,
|
||||
ThemedExpandableNavbarSectionComponent,
|
||||
],
|
||||
providers: [],
|
||||
exports: [
|
||||
|
@@ -53,8 +53,9 @@ export class AuthorizedCollectionSelectorComponent extends DSOSelectorComponent
|
||||
* Perform a search for authorized collections with the current query and page
|
||||
* @param query Query to search objects for
|
||||
* @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;
|
||||
const findOptions: FindListOptions = {
|
||||
currentPage: page,
|
||||
@@ -69,7 +70,7 @@ export class AuthorizedCollectionSelectorComponent extends DSOSelectorComponent
|
||||
findOptions);
|
||||
} else {
|
||||
searchListService$ = this.collectionDataService
|
||||
.getAuthorizedCollection(query, findOptions, true, false, followLink('parentCommunity'));
|
||||
.getAuthorizedCollection(query, findOptions, useCache, false, followLink('parentCommunity'));
|
||||
}
|
||||
return searchListService$.pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
|
@@ -21,12 +21,12 @@
|
||||
</button>
|
||||
<button *ngFor="let listEntry of (listEntries$ | async)"
|
||||
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) }}"
|
||||
dsHoverClass="ds-hover"
|
||||
(click)="onSelect.emit(listEntry.indexableObject)" #listEntryElement>
|
||||
(click)="onClick(listEntry)" #listEntryElement>
|
||||
<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>
|
||||
</ng-container>
|
||||
<button *ngIf="loading"
|
||||
|
@@ -35,6 +35,14 @@ import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { NotificationsService } from '../../notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
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({
|
||||
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
|
||||
*/
|
||||
listEntries$: BehaviorSubject<SearchResult<DSpaceObject>[]> = new BehaviorSubject(null);
|
||||
listEntries$: BehaviorSubject<ListableObject[]> = new BehaviorSubject(null);
|
||||
|
||||
/**
|
||||
* The current page to load
|
||||
@@ -116,11 +124,6 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
|
||||
*/
|
||||
linkTypes = CollectionElementLinkType;
|
||||
|
||||
/**
|
||||
* Track whether the element has the mouse over it
|
||||
*/
|
||||
isMouseOver = false;
|
||||
|
||||
/**
|
||||
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||
* @type {Array}
|
||||
@@ -182,24 +185,28 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
|
||||
})
|
||||
);
|
||||
})
|
||||
).subscribe((rd) => {
|
||||
this.loading = false;
|
||||
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;
|
||||
}
|
||||
).subscribe((rd: RemoteData<PaginatedList<SearchResult<DSpaceObject>>>) => {
|
||||
this.updateList(rd);
|
||||
}));
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
@@ -211,8 +218,9 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
|
||||
* Perform a search for the current query and page
|
||||
* @param query Query to search objects for
|
||||
* @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(
|
||||
new PaginatedSearchOptions({
|
||||
query: query,
|
||||
@@ -220,7 +228,9 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
|
||||
pagination: Object.assign({}, this.defaultPagination, {
|
||||
currentPage: page
|
||||
})
|
||||
})
|
||||
}),
|
||||
null,
|
||||
useCache,
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData()
|
||||
);
|
||||
@@ -262,7 +272,28 @@ export class DSOSelectorComponent implements OnInit, OnDestroy {
|
||||
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';
|
||||
import { MarkdownPipe } from './utils/markdown.pipe';
|
||||
import { GoogleRecaptchaModule } from '../core/google-recaptcha/google-recaptcha.module';
|
||||
import {
|
||||
ListableNotificationObjectComponent
|
||||
} from './object-list/listable-notification-object/listable-notification-object.component';
|
||||
|
||||
const MODULES = [
|
||||
CommonModule,
|
||||
@@ -510,6 +513,7 @@ const COMPONENTS = [
|
||||
ScopeSelectorModalComponent,
|
||||
ItemPageTitleFieldComponent,
|
||||
ThemedSearchNavbarComponent,
|
||||
ListableNotificationObjectComponent,
|
||||
];
|
||||
|
||||
const ENTRY_COMPONENTS = [
|
||||
@@ -575,7 +579,8 @@ const ENTRY_COMPONENTS = [
|
||||
OnClickMenuItemComponent,
|
||||
TextMenuItemComponent,
|
||||
ScopeSelectorModalComponent,
|
||||
ExternalLinkMenuItemComponent
|
||||
ExternalLinkMenuItemComponent,
|
||||
ListableNotificationObjectComponent,
|
||||
];
|
||||
|
||||
const SHARED_ITEM_PAGE_COMPONENTS = [
|
||||
|
@@ -1410,6 +1410,8 @@
|
||||
|
||||
"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.info": "Are you sure you want to export metadata for {{ dsoName }}",
|
||||
@@ -4826,4 +4828,5 @@
|
||||
"person.orcid.registry.auth": "ORCID Authorizations",
|
||||
"home.recent-submissions.head": "Recent Submissions",
|
||||
|
||||
"listable-notification-object.default-message": "This object couldn't be retrieved",
|
||||
}
|
||||
|
Reference in New Issue
Block a user