mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-18 15:33:04 +00:00
add tabulatable loader and related configuration
This commit is contained in:
@@ -12,7 +12,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</li>
|
</li>
|
||||||
<li [ngbNavItem]="'logs'">
|
<li [ngbNavItem]="'logs'" (click)="activateTableMode()">
|
||||||
<a ngbNavLink>{{'admin-notify-dashboard.logs' | translate}}</a>
|
<a ngbNavLink>{{'admin-notify-dashboard.logs' | translate}}</a>
|
||||||
<ng-template ngbNavContent>
|
<ng-template ngbNavContent>
|
||||||
<div id="logs">
|
<div id="logs">
|
||||||
|
@@ -14,6 +14,8 @@ import { SEARCH_CONFIG_SERVICE } from '../../my-dspace-page/my-dspace-page.compo
|
|||||||
import { AdminNotifySearchConfigurationService } from './config/admin-notify-search-configuration.service';
|
import { AdminNotifySearchConfigurationService } from './config/admin-notify-search-configuration.service';
|
||||||
import { AdminNotifySearchFilterService } from './config/admin-notify-filter-service';
|
import { AdminNotifySearchFilterService } from './config/admin-notify-filter-service';
|
||||||
import { AdminNotifySearchFilterConfig } from './config/admin-notify-search-filter-config';
|
import { AdminNotifySearchFilterConfig } from './config/admin-notify-search-filter-config';
|
||||||
|
import { ViewMode } from "../../core/shared/view-mode.model";
|
||||||
|
import { Router } from "@angular/router";
|
||||||
|
|
||||||
export const FILTER_SEARCH: InjectionToken<SearchFilterService> = new InjectionToken<SearchFilterService>('searchFilterService');
|
export const FILTER_SEARCH: InjectionToken<SearchFilterService> = new InjectionToken<SearchFilterService>('searchFilterService');
|
||||||
|
|
||||||
@@ -45,7 +47,8 @@ export class AdminNotifyDashboardComponent implements OnInit{
|
|||||||
id: 'single-result-options',
|
id: 'single-result-options',
|
||||||
pageSize: 1
|
pageSize: 1
|
||||||
});
|
});
|
||||||
constructor(private searchService: SearchService) {}
|
constructor(private searchService: SearchService,
|
||||||
|
private router: Router) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
const mertricsRowsConfigurations = this.metricsConfig
|
const mertricsRowsConfigurations = this.metricsConfig
|
||||||
@@ -99,4 +102,28 @@ export class AdminNotifyDashboardComponent implements OnInit{
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activate Table view mode for search result rendering
|
||||||
|
*/
|
||||||
|
activateTableMode() {
|
||||||
|
this.searchService.setViewMode(ViewMode.Table, this.getSearchLinkParts());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} The base path to the search page, or the current page when inPlaceSearch is true
|
||||||
|
*/
|
||||||
|
public getSearchLink(): string {
|
||||||
|
return this.searchService.getSearchLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string[]} The base path to the search page, or the current page when inPlaceSearch is true, split in separate pieces
|
||||||
|
*/
|
||||||
|
public getSearchLinkParts(): string[] {
|
||||||
|
if (this.searchService) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this.getSearchLink().split('/');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,34 @@
|
|||||||
<div class="d-flex bg-light w-100 align-items-center">
|
<div class="d-flex bg-light w-100 align-items-center">
|
||||||
<div class="p-2 text-truncate">{{indexableObject.queueTimeout ?? 'n/a'}}</div>
|
<div class="table-responsive">
|
||||||
<div class="p-2 text-truncate">{{indexableObject.source ?? 'n/a'}}</div>
|
<table id="formats" class="table table-striped table-hover">
|
||||||
<div class="p-2 text-truncate">{{indexableObject.target ?? 'n/a'}}</div>
|
<thead>
|
||||||
<div class="p-2 text-truncate">{{indexableObject.coarNotifyType ?? 'n/a'}}</div>
|
<tr>
|
||||||
<div class="p-2 text-truncate">{{indexableObject.queueStatusLabel ?? 'n/a'}}</div>
|
<th scope="col">Timestamp</th>
|
||||||
|
<th scope="col">Source</th>
|
||||||
|
<th scope="col">Target</th>
|
||||||
|
<th scope="col">Type</th>
|
||||||
|
<th scope="col">Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let object of indexableObjects">
|
||||||
|
<td>
|
||||||
|
<div>{{object.queueTimeout}}</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>{{object.source}}</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>{{object.target}}</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>{{object.coarNotifyType}}</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div>{{object.queueStatusLabel}}</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,24 +1,26 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import {
|
|
||||||
listableObjectComponent
|
|
||||||
} from '../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
|
||||||
import { AdminNotifySearchResult } from '../models/admin-notify-message-search-result.model';
|
import { AdminNotifySearchResult } from '../models/admin-notify-message-search-result.model';
|
||||||
import { ViewMode } from '../../../core/shared/view-mode.model';
|
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||||
import { Context } from '../../../core/shared/context.model';
|
import { Context } from '../../../core/shared/context.model';
|
||||||
import {
|
|
||||||
SearchResultListElementComponent
|
|
||||||
} from '../../../shared/object-list/search-result-list-element/search-result-list-element.component';
|
|
||||||
import { AdminNotifyMessage } from '../models/admin-notify-message.model';
|
import { AdminNotifyMessage } from '../models/admin-notify-message.model';
|
||||||
|
import {
|
||||||
|
tabulatableObjectsComponent
|
||||||
|
} from "../../../shared/object-collection/shared/tabulatable-objects/tabulatable-objects.decorator";
|
||||||
|
import {
|
||||||
|
TabulatableResultListElementsComponent
|
||||||
|
} from "../../../shared/object-list/search-result-list-element/tabulatable-search-result/tabulatable-result-list-elements.component";
|
||||||
|
import { PaginatedList } from "../../../core/data/paginated-list.model";
|
||||||
|
|
||||||
@listableObjectComponent(AdminNotifySearchResult, ViewMode.ListElement, Context.CoarNotify)
|
@tabulatableObjectsComponent(AdminNotifySearchResult, ViewMode.Table, Context.CoarNotify)
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-admin-notify-search-result',
|
selector: 'ds-admin-notify-search-result',
|
||||||
templateUrl: './admin-notify-search-result.component.html',
|
templateUrl: './admin-notify-search-result.component.html',
|
||||||
styleUrls: ['./admin-notify-search-result.component.scss']
|
styleUrls: ['./admin-notify-search-result.component.scss']
|
||||||
})
|
})
|
||||||
export class AdminNotifySearchResultComponent extends SearchResultListElementComponent<AdminNotifySearchResult, AdminNotifyMessage> implements OnInit{
|
export class AdminNotifySearchResultComponent extends TabulatableResultListElementsComponent<PaginatedList<AdminNotifyMessage>, AdminNotifyMessage> implements OnInit{
|
||||||
indexableObject: AdminNotifyMessage;
|
public indexableObjects: AdminNotifyMessage[];
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.indexableObject = this.object.indexableObject;
|
this.indexableObjects = this.objects.page.map(object => object.indexableObject);
|
||||||
|
console.log(this.objects.page)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -56,6 +56,12 @@ export class AdminNotifyMessage extends DSpaceObject {
|
|||||||
@autoserialize
|
@autoserialize
|
||||||
queueStatus: number;
|
queueStatus: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The status of the queue
|
||||||
|
*/
|
||||||
|
@autoserialize
|
||||||
|
indexableObject: AdminNotifyMessage;
|
||||||
|
|
||||||
@deserialize
|
@deserialize
|
||||||
_links: {
|
_links: {
|
||||||
self: {
|
self: {
|
||||||
|
@@ -7,4 +7,5 @@ export enum ViewMode {
|
|||||||
GridElement = 'grid',
|
GridElement = 'grid',
|
||||||
DetailedListElement = 'detailed',
|
DetailedListElement = 'detailed',
|
||||||
StandalonePage = 'standalone',
|
StandalonePage = 'standalone',
|
||||||
|
Table = 'table',
|
||||||
}
|
}
|
||||||
|
@@ -58,3 +58,20 @@
|
|||||||
</ds-object-detail>
|
</ds-object-detail>
|
||||||
|
|
||||||
|
|
||||||
|
<ds-object-table
|
||||||
|
[config]="config"
|
||||||
|
[sortConfig]="sortConfig"
|
||||||
|
[objects]="objects"
|
||||||
|
[hideGear]="hideGear"
|
||||||
|
[linkType]="linkType"
|
||||||
|
[context]="context"
|
||||||
|
[hidePaginationDetail]="hidePaginationDetail"
|
||||||
|
[showPaginator]="showPaginator"
|
||||||
|
[showThumbnails]="showThumbnails"
|
||||||
|
(paginationChange)="onPaginationChange($event)"
|
||||||
|
(pageChange)="onPageChange($event)"
|
||||||
|
(pageSizeChange)="onPageSizeChange($event)"
|
||||||
|
(sortDirectionChange)="onSortDirectionChange($event)"
|
||||||
|
(sortFieldChange)="onSortFieldChange($event)"
|
||||||
|
*ngIf="(currentMode$ | async) === viewModeEnum.Table">
|
||||||
|
</ds-object-table>
|
||||||
|
@@ -154,7 +154,6 @@ export class ListableObjectComponentLoaderComponent implements OnInit, OnChanges
|
|||||||
}
|
}
|
||||||
|
|
||||||
private instantiateComponent(object: ListableObject, changes?: SimpleChanges): void {
|
private instantiateComponent(object: ListableObject, changes?: SimpleChanges): void {
|
||||||
|
|
||||||
const component = this.getComponent(object.getRenderTypes(), this.viewMode, this.context);
|
const component = this.getComponent(object.getRenderTypes(), this.viewMode, this.context);
|
||||||
|
|
||||||
const viewContainerRef = this.listableObjectDirective.viewContainerRef;
|
const viewContainerRef = this.listableObjectDirective.viewContainerRef;
|
||||||
|
@@ -0,0 +1,194 @@
|
|||||||
|
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||||
|
import { Context } from '../../../../core/shared/context.model';
|
||||||
|
import { hasNoValue, hasValue, isNotEmpty } from '../../../empty.util';
|
||||||
|
import { GenericConstructor } from '../../../../core/shared/generic-constructor';
|
||||||
|
import { ListableObject } from '../listable-object.model';
|
||||||
|
import { environment } from '../../../../../environments/environment';
|
||||||
|
import { ThemeConfig } from '../../../../../config/theme.config';
|
||||||
|
import { InjectionToken } from '@angular/core';
|
||||||
|
|
||||||
|
export const DEFAULT_VIEW_MODE = ViewMode.ListElement;
|
||||||
|
export const DEFAULT_CONTEXT = Context.Any;
|
||||||
|
export const DEFAULT_THEME = '*';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A class used to compare two matches and their relevancy to determine which of the two gains priority over the other
|
||||||
|
*
|
||||||
|
* "level" represents the index of the first default value that was used to find the match with:
|
||||||
|
* ViewMode being index 0, Context index 1 and theme index 2. Examples:
|
||||||
|
* - If a default value was used for context, but not view-mode and theme, the "level" will be 1
|
||||||
|
* - If a default value was used for view-mode and context, but not for theme, the "level" will be 0
|
||||||
|
* - If no default value was used for any of the fields, the "level" will be 3
|
||||||
|
*
|
||||||
|
* "relevancy" represents the amount of values that didn't require a default value to fall back on. Examples:
|
||||||
|
* - If a default value was used for theme, but not view-mode and context, the "relevancy" will be 2
|
||||||
|
* - If a default value was used for view-mode and context, but not for theme, the "relevancy" will be 1
|
||||||
|
* - If a default value was used for all fields, the "relevancy" will be 0
|
||||||
|
* - If no default value was used for any of the fields, the "relevancy" will be 3
|
||||||
|
*
|
||||||
|
* To determine which of two MatchRelevancies is the most relevant, we compare "level" and "relevancy" in that order.
|
||||||
|
* If any of the two is higher than the other, that match is most relevant. Examples:
|
||||||
|
* - { level: 1, relevancy: 1 } is more relevant than { level: 0, relevancy: 2 }
|
||||||
|
* - { level: 1, relevancy: 1 } is less relevant than { level: 1, relevancy: 2 }
|
||||||
|
* - { level: 1, relevancy: 1 } is more relevant than { level: 1, relevancy: 0 }
|
||||||
|
* - { level: 1, relevancy: 1 } is less relevant than { level: 2, relevancy: 0 }
|
||||||
|
* - { level: 1, relevancy: 1 } is more relevant than null
|
||||||
|
*/
|
||||||
|
class MatchRelevancy {
|
||||||
|
constructor(public match: any,
|
||||||
|
public level: number,
|
||||||
|
public relevancy: number) {
|
||||||
|
}
|
||||||
|
|
||||||
|
isMoreRelevantThan(otherMatch: MatchRelevancy): boolean {
|
||||||
|
if (hasNoValue(otherMatch)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (otherMatch.level > this.level) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (otherMatch.level === this.level && otherMatch.relevancy > this.relevancy) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLessRelevantThan(otherMatch: MatchRelevancy): boolean {
|
||||||
|
return !this.isMoreRelevantThan(otherMatch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory to allow us to inject getThemeConfigFor so we can mock it in tests
|
||||||
|
*/
|
||||||
|
export const GET_THEME_CONFIG_FOR_FACTORY = new InjectionToken<(str) => ThemeConfig>('getThemeConfigFor', {
|
||||||
|
providedIn: 'root',
|
||||||
|
factory: () => getThemeConfigFor
|
||||||
|
});
|
||||||
|
|
||||||
|
const map = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decorator used for rendering a listable object
|
||||||
|
* @param objectType The object type or entity type the component represents
|
||||||
|
* @param viewMode The view mode the component represents
|
||||||
|
* @param context The optional context the component represents
|
||||||
|
* @param theme The optional theme for the component
|
||||||
|
*/
|
||||||
|
export function tabulatableObjectsComponent(objectsType: string | GenericConstructor<ListableObject>, viewMode: ViewMode, context: Context = DEFAULT_CONTEXT, theme = DEFAULT_THEME) {
|
||||||
|
return function decorator(component: any) {
|
||||||
|
if (hasNoValue(objectsType)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hasNoValue(map.get(objectsType))) {
|
||||||
|
map.set(objectsType, new Map());
|
||||||
|
}
|
||||||
|
if (hasNoValue(map.get(objectsType).get(viewMode))) {
|
||||||
|
map.get(objectsType).set(viewMode, new Map());
|
||||||
|
}
|
||||||
|
if (hasNoValue(map.get(objectsType).get(viewMode).get(context))) {
|
||||||
|
map.get(objectsType).get(viewMode).set(context, new Map());
|
||||||
|
}
|
||||||
|
map.get(objectsType).get(viewMode).get(context).set(theme, component);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter to retrieve the matching listable object component
|
||||||
|
*
|
||||||
|
* Looping over the provided types, it'll attempt to find the best match depending on the {@link MatchRelevancy} returned by getMatch()
|
||||||
|
* The most relevant match between types is kept and eventually returned
|
||||||
|
*
|
||||||
|
* @param types The types of which one should match the listable component
|
||||||
|
* @param viewMode The view mode that should match the components
|
||||||
|
* @param context The context that should match the components
|
||||||
|
* @param theme The theme that should match the components
|
||||||
|
*/
|
||||||
|
export function getTabulatableObjectsComponent(types: (string | GenericConstructor<ListableObject>)[], viewMode: ViewMode, context: Context = DEFAULT_CONTEXT, theme: string = DEFAULT_THEME) {
|
||||||
|
let currentBestMatch: MatchRelevancy = null;
|
||||||
|
for (const type of types) {
|
||||||
|
const typeMap = map.get(type);
|
||||||
|
if (hasValue(typeMap)) {
|
||||||
|
const match = getMatch(typeMap, [viewMode, context, theme], [DEFAULT_VIEW_MODE, DEFAULT_CONTEXT, DEFAULT_THEME]);
|
||||||
|
if (hasNoValue(currentBestMatch) || currentBestMatch.isLessRelevantThan(match)) {
|
||||||
|
currentBestMatch = match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hasValue(currentBestMatch) ? currentBestMatch.match : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find an object within a nested map, matching the provided keys as best as possible, falling back on defaults wherever
|
||||||
|
* needed.
|
||||||
|
*
|
||||||
|
* Starting off with a Map, it loops over the provided keys, going deeper into the map until it finds a value
|
||||||
|
* If at some point, no value is found, it'll attempt to use the default value for that index instead
|
||||||
|
* If the default value exists, the index is stored in the "level"
|
||||||
|
* If no default value exists, 1 is added to "relevancy"
|
||||||
|
* See {@link MatchRelevancy} what these represent
|
||||||
|
*
|
||||||
|
* @param typeMap a multi-dimensional map
|
||||||
|
* @param keys the keys of the multi-dimensional map to loop over. Each key represents a level within the map
|
||||||
|
* @param defaults the default values to use for each level, in case no value is found for the key at that index
|
||||||
|
* @returns matchAndLevel a {@link MatchRelevancy} object containing the match and its level of relevancy
|
||||||
|
*/
|
||||||
|
function getMatch(typeMap: Map<any, any>, keys: any[], defaults: any[]): MatchRelevancy {
|
||||||
|
let currentMap = typeMap;
|
||||||
|
let level = -1;
|
||||||
|
let relevancy = 0;
|
||||||
|
for (let i = 0; i < keys.length; i++) {
|
||||||
|
// If we're currently checking the theme, resolve it first to take extended themes into account
|
||||||
|
let currentMatch = defaults[i] === DEFAULT_THEME ? resolveTheme(currentMap, keys[i]) : currentMap.get(keys[i]);
|
||||||
|
if (hasNoValue(currentMatch)) {
|
||||||
|
currentMatch = currentMap.get(defaults[i]);
|
||||||
|
if (level === -1) {
|
||||||
|
level = i;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
relevancy++;
|
||||||
|
}
|
||||||
|
if (hasValue(currentMatch)) {
|
||||||
|
if (currentMatch instanceof Map) {
|
||||||
|
currentMap = currentMatch as Map<any, any>;
|
||||||
|
} else {
|
||||||
|
return new MatchRelevancy(currentMatch, level > -1 ? level : i + 1, relevancy);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches for a ThemeConfig by its name;
|
||||||
|
*/
|
||||||
|
export const getThemeConfigFor = (themeName: string): ThemeConfig => {
|
||||||
|
return environment.themes.find(theme => theme.name === themeName);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a match in the given map for the given theme name, taking theme extension into account
|
||||||
|
*
|
||||||
|
* @param contextMap A map of theme names to components
|
||||||
|
* @param themeName The name of the theme to check
|
||||||
|
* @param checkedThemeNames The list of theme names that are already checked
|
||||||
|
*/
|
||||||
|
export const resolveTheme = (contextMap: Map<any, any>, themeName: string, checkedThemeNames: string[] = []): any => {
|
||||||
|
const match = contextMap.get(themeName);
|
||||||
|
if (hasValue(match)) {
|
||||||
|
return match;
|
||||||
|
} else {
|
||||||
|
const cfg = getThemeConfigFor(themeName);
|
||||||
|
if (hasValue(cfg) && isNotEmpty(cfg.extends)) {
|
||||||
|
const nextTheme = cfg.extends;
|
||||||
|
const nextCheckedThemeNames = [...checkedThemeNames, themeName];
|
||||||
|
if (checkedThemeNames.includes(nextTheme)) {
|
||||||
|
throw new Error('Theme extension cycle detected: ' + [...nextCheckedThemeNames, nextTheme].join(' -> '));
|
||||||
|
} else {
|
||||||
|
return resolveTheme(contextMap, nextTheme, nextCheckedThemeNames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@@ -0,0 +1,11 @@
|
|||||||
|
import { Directive, ViewContainerRef } from '@angular/core';
|
||||||
|
|
||||||
|
@Directive({
|
||||||
|
selector: '[dsTabulatableObjects]',
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Directive used as a hook to know where to inject the dynamic listable object component
|
||||||
|
*/
|
||||||
|
export class TabulatableObjectsDirective {
|
||||||
|
constructor(public viewContainerRef: ViewContainerRef) { }
|
||||||
|
}
|
@@ -0,0 +1,22 @@
|
|||||||
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
|
import {
|
||||||
|
AbstractTabulatableElementComponent
|
||||||
|
} from "../../../object-collection/shared/objects-collection-tabulatable/objects-collection-tabulatable.component";
|
||||||
|
import { DSpaceObject } from "../../../../core/shared/dspace-object.model";
|
||||||
|
import { TruncatableService } from "../../../truncatable/truncatable.service";
|
||||||
|
import { DSONameService } from "../../../../core/breadcrumbs/dso-name.service";
|
||||||
|
import { APP_CONFIG, AppConfig } from "../../../../../config/app-config.interface";
|
||||||
|
import { PaginatedList } from "../../../../core/data/paginated-list.model";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-search-result-list-element',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
export class TabulatableResultListElementsComponent<T extends PaginatedList<K>, K extends DSpaceObject> extends AbstractTabulatableElementComponent<T> {
|
||||||
|
public constructor(protected truncatableService: TruncatableService,
|
||||||
|
public dsoNameService: DSONameService,
|
||||||
|
@Inject(APP_CONFIG) protected appConfig?: AppConfig) {
|
||||||
|
super(dsoNameService);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
32
src/app/shared/object-table/object-table.component.html
Normal file
32
src/app/shared/object-table/object-table.component.html
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<ds-pagination
|
||||||
|
[paginationOptions]="config"
|
||||||
|
[pageInfoState]="objects?.payload"
|
||||||
|
[collectionSize]="objects?.payload?.totalElements"
|
||||||
|
[sortOptions]="sortConfig"
|
||||||
|
[hideGear]="hideGear"
|
||||||
|
[objects]="objects"
|
||||||
|
[hidePagerWhenSinglePage]="hidePagerWhenSinglePage"
|
||||||
|
[hidePaginationDetail]="hidePaginationDetail"
|
||||||
|
[showPaginator]="showPaginator"
|
||||||
|
(pageChange)="onPageChange($event)"
|
||||||
|
(pageSizeChange)="onPageSizeChange($event)"
|
||||||
|
(sortDirectionChange)="onSortDirectionChange($event)"
|
||||||
|
(sortFieldChange)="onSortFieldChange($event)"
|
||||||
|
(paginationChange)="onPaginationChange($event)"
|
||||||
|
(prev)="goPrev()"
|
||||||
|
(next)="goNext()"
|
||||||
|
>
|
||||||
|
<div class="row" *ngIf="objects?.hasSucceeded">
|
||||||
|
<div @fadeIn>
|
||||||
|
<ds-tabulatable-objects-loader [objects]="objects.payload"
|
||||||
|
[context]="context"
|
||||||
|
[showThumbnails]="showThumbnails"
|
||||||
|
[linkType]="linkType">
|
||||||
|
</ds-tabulatable-objects-loader>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ds-error *ngIf="objects.hasFailed" message="{{'error.objects' | translate}}"></ds-error>
|
||||||
|
<ds-themed-loading *ngIf="objects.isLoading" message="{{'loading.objects' | translate}}"></ds-themed-loading>
|
||||||
|
</ds-pagination>
|
||||||
|
|
||||||
|
|
23
src/app/shared/object-table/object-table.component.spec.ts
Normal file
23
src/app/shared/object-table/object-table.component.spec.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ObjectTableComponent } from './object-table.component';
|
||||||
|
|
||||||
|
describe('ObjectTableComponent', () => {
|
||||||
|
let component: ObjectTableComponent;
|
||||||
|
let fixture: ComponentFixture<ObjectTableComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ ObjectTableComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ObjectTableComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
209
src/app/shared/object-table/object-table.component.ts
Normal file
209
src/app/shared/object-table/object-table.component.ts
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core';
|
||||||
|
import { ViewMode } from "../../core/shared/view-mode.model";
|
||||||
|
import { PaginationComponentOptions } from "../pagination/pagination-component-options.model";
|
||||||
|
import { SortDirection, SortOptions } from "../../core/cache/models/sort-options.model";
|
||||||
|
import { CollectionElementLinkType } from "../object-collection/collection-element-link.type";
|
||||||
|
import { Context } from "../../core/shared/context.model";
|
||||||
|
import { BehaviorSubject} from "rxjs";
|
||||||
|
import { RemoteData } from "../../core/data/remote-data";
|
||||||
|
import { PaginatedList } from "../../core/data/paginated-list.model";
|
||||||
|
import { ListableObject } from "../object-collection/shared/listable-object.model";
|
||||||
|
import { fadeIn } from "../animations/fade";
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
changeDetection: ChangeDetectionStrategy.Default,
|
||||||
|
encapsulation: ViewEncapsulation.Emulated,
|
||||||
|
selector: 'ds-object-table',
|
||||||
|
templateUrl: './object-table.component.html',
|
||||||
|
styleUrls: ['./object-table.component.scss'],
|
||||||
|
animations: [fadeIn]
|
||||||
|
})
|
||||||
|
export class ObjectTableComponent {
|
||||||
|
/**
|
||||||
|
* The view mode of this component
|
||||||
|
*/
|
||||||
|
viewMode = ViewMode.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current pagination configuration
|
||||||
|
*/
|
||||||
|
@Input() config: PaginationComponentOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current sort configuration
|
||||||
|
*/
|
||||||
|
@Input() sortConfig: SortOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the pagination should be rendered as simple previous and next buttons instead of the normal pagination
|
||||||
|
*/
|
||||||
|
@Input() showPaginator = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to show the thumbnail preview
|
||||||
|
*/
|
||||||
|
@Input() showThumbnails;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The whether or not the gear is hidden
|
||||||
|
*/
|
||||||
|
@Input() hideGear = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the pager is visible when there is only a single page of results
|
||||||
|
*/
|
||||||
|
@Input() hidePagerWhenSinglePage = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The link type of the listable elements
|
||||||
|
*/
|
||||||
|
@Input() linkType: CollectionElementLinkType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The context of the listable elements
|
||||||
|
*/
|
||||||
|
@Input() context: Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Option for hiding the pagination detail
|
||||||
|
*/
|
||||||
|
@Input() hidePaginationDetail = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Behavior subject to output the current listable objects
|
||||||
|
*/
|
||||||
|
private _objects$: BehaviorSubject<RemoteData<PaginatedList<ListableObject>>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setter to make sure the observable is turned into an observable
|
||||||
|
* @param objects The new objects to output
|
||||||
|
*/
|
||||||
|
@Input() set objects(objects: RemoteData<PaginatedList<ListableObject>>) {
|
||||||
|
this._objects$.next(objects);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter to return the current objects
|
||||||
|
*/
|
||||||
|
get objects() {
|
||||||
|
return this._objects$.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when the page is changed.
|
||||||
|
* Event's payload equals to the newly selected page.
|
||||||
|
*/
|
||||||
|
@Output() change: EventEmitter<{
|
||||||
|
pagination: PaginationComponentOptions,
|
||||||
|
sort: SortOptions
|
||||||
|
}> = new EventEmitter<{
|
||||||
|
pagination: PaginationComponentOptions,
|
||||||
|
sort: SortOptions
|
||||||
|
}>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when the page is changed.
|
||||||
|
* Event's payload equals to the newly selected page.
|
||||||
|
*/
|
||||||
|
@Output() pageChange: EventEmitter<number> = new EventEmitter<number>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when the page wsize is changed.
|
||||||
|
* Event's payload equals to the newly selected page size.
|
||||||
|
*/
|
||||||
|
@Output() pageSizeChange: EventEmitter<number> = new EventEmitter<number>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when the sort direction is changed.
|
||||||
|
* Event's payload equals to the newly selected sort direction.
|
||||||
|
*/
|
||||||
|
@Output() sortDirectionChange: EventEmitter<SortDirection> = new EventEmitter<SortDirection>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when on of the pagination parameters changes
|
||||||
|
*/
|
||||||
|
@Output() paginationChange: EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event fired when the sort field is changed.
|
||||||
|
* Event's payload equals to the newly selected sort field.
|
||||||
|
*/
|
||||||
|
@Output() sortFieldChange: EventEmitter<string> = new EventEmitter<string>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If showPaginator is set to true, emit when the previous button is clicked
|
||||||
|
*/
|
||||||
|
@Output() prev = new EventEmitter<boolean>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If showPaginator is set to true, emit when the next button is clicked
|
||||||
|
*/
|
||||||
|
@Output() next = new EventEmitter<boolean>();
|
||||||
|
|
||||||
|
data: any = {};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._objects$ = new BehaviorSubject(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the instance variables
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
console.log('table rendered')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits the current page when it changes
|
||||||
|
* @param event The new page
|
||||||
|
*/
|
||||||
|
onPageChange(event) {
|
||||||
|
this.pageChange.emit(event);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Emits the current page size when it changes
|
||||||
|
* @param event The new page size
|
||||||
|
*/
|
||||||
|
onPageSizeChange(event) {
|
||||||
|
this.pageSizeChange.emit(event);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Emits the current sort direction when it changes
|
||||||
|
* @param event The new sort direction
|
||||||
|
*/
|
||||||
|
onSortDirectionChange(event) {
|
||||||
|
this.sortDirectionChange.emit(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits the current sort field when it changes
|
||||||
|
* @param event The new sort field
|
||||||
|
*/
|
||||||
|
onSortFieldChange(event) {
|
||||||
|
this.sortFieldChange.emit(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emits the current pagination when it changes
|
||||||
|
* @param event The new pagination
|
||||||
|
*/
|
||||||
|
onPaginationChange(event) {
|
||||||
|
this.paginationChange.emit(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to the previous page
|
||||||
|
*/
|
||||||
|
goPrev() {
|
||||||
|
this.prev.emit(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to the next page
|
||||||
|
*/
|
||||||
|
goNext() {
|
||||||
|
this.next.emit(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -286,6 +286,17 @@ import { SplitPipe } from './utils/split.pipe';
|
|||||||
import { ThemedUserMenuComponent } from './auth-nav-menu/user-menu/themed-user-menu.component';
|
import { ThemedUserMenuComponent } from './auth-nav-menu/user-menu/themed-user-menu.component';
|
||||||
import { ThemedLangSwitchComponent } from './lang-switch/themed-lang-switch.component';
|
import { ThemedLangSwitchComponent } from './lang-switch/themed-lang-switch.component';
|
||||||
import { NotificationBoxComponent } from './notification-box/notification-box.component';
|
import { NotificationBoxComponent } from './notification-box/notification-box.component';
|
||||||
|
import { ObjectTableComponent } from './object-table/object-table.component';
|
||||||
|
import { TabulatableObjectsLoaderComponent } from './object-collection/shared/tabulatable-objects/tabulatable-objects-loader.component';
|
||||||
|
import {
|
||||||
|
TabulatableObjectsDirective
|
||||||
|
} from "./object-collection/shared/tabulatable-objects/tabulatable-objects.directive";
|
||||||
|
import {
|
||||||
|
AbstractTabulatableElementComponent
|
||||||
|
} from "./object-collection/shared/objects-collection-tabulatable/objects-collection-tabulatable.component";
|
||||||
|
import {
|
||||||
|
TabulatableResultListElementsComponent
|
||||||
|
} from "./object-list/search-result-list-element/tabulatable-search-result/tabulatable-result-list-elements.component";
|
||||||
|
|
||||||
const MODULES = [
|
const MODULES = [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@@ -349,7 +360,9 @@ const COMPONENTS = [
|
|||||||
ThemedObjectListComponent,
|
ThemedObjectListComponent,
|
||||||
ObjectDetailComponent,
|
ObjectDetailComponent,
|
||||||
ObjectGridComponent,
|
ObjectGridComponent,
|
||||||
|
ObjectTableComponent,
|
||||||
AbstractListableElementComponent,
|
AbstractListableElementComponent,
|
||||||
|
AbstractTabulatableElementComponent,
|
||||||
ObjectCollectionComponent,
|
ObjectCollectionComponent,
|
||||||
PaginationComponent,
|
PaginationComponent,
|
||||||
RSSComponent,
|
RSSComponent,
|
||||||
@@ -415,6 +428,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
CollectionListElementComponent,
|
CollectionListElementComponent,
|
||||||
CommunityListElementComponent,
|
CommunityListElementComponent,
|
||||||
SearchResultListElementComponent,
|
SearchResultListElementComponent,
|
||||||
|
TabulatableResultListElementsComponent,
|
||||||
CommunitySearchResultListElementComponent,
|
CommunitySearchResultListElementComponent,
|
||||||
CollectionSearchResultListElementComponent,
|
CollectionSearchResultListElementComponent,
|
||||||
CollectionGridElementComponent,
|
CollectionGridElementComponent,
|
||||||
@@ -471,7 +485,8 @@ const ENTRY_COMPONENTS = [
|
|||||||
EpersonGroupListComponent,
|
EpersonGroupListComponent,
|
||||||
EpersonSearchBoxComponent,
|
EpersonSearchBoxComponent,
|
||||||
GroupSearchBoxComponent,
|
GroupSearchBoxComponent,
|
||||||
NotificationBoxComponent
|
NotificationBoxComponent,
|
||||||
|
TabulatableObjectsLoaderComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
const PROVIDERS = [
|
const PROVIDERS = [
|
||||||
@@ -490,6 +505,7 @@ const DIRECTIVES = [
|
|||||||
RoleDirective,
|
RoleDirective,
|
||||||
MetadataRepresentationDirective,
|
MetadataRepresentationDirective,
|
||||||
ListableObjectDirective,
|
ListableObjectDirective,
|
||||||
|
TabulatableObjectsDirective,
|
||||||
ClaimedTaskActionsDirective,
|
ClaimedTaskActionsDirective,
|
||||||
FileValueAccessorDirective,
|
FileValueAccessorDirective,
|
||||||
FileValidator,
|
FileValidator,
|
||||||
|
Reference in New Issue
Block a user