mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge branch 'main' into w2p-101198_in-place-links_contribute-main
This commit is contained in:
@@ -17,6 +17,7 @@ export const AuthActionTypes = {
|
|||||||
AUTHENTICATED_SUCCESS: type('dspace/auth/AUTHENTICATED_SUCCESS'),
|
AUTHENTICATED_SUCCESS: type('dspace/auth/AUTHENTICATED_SUCCESS'),
|
||||||
CHECK_AUTHENTICATION_TOKEN: type('dspace/auth/CHECK_AUTHENTICATION_TOKEN'),
|
CHECK_AUTHENTICATION_TOKEN: type('dspace/auth/CHECK_AUTHENTICATION_TOKEN'),
|
||||||
CHECK_AUTHENTICATION_TOKEN_COOKIE: type('dspace/auth/CHECK_AUTHENTICATION_TOKEN_COOKIE'),
|
CHECK_AUTHENTICATION_TOKEN_COOKIE: type('dspace/auth/CHECK_AUTHENTICATION_TOKEN_COOKIE'),
|
||||||
|
SET_AUTH_COOKIE_STATUS: type('dspace/auth/SET_AUTH_COOKIE_STATUS'),
|
||||||
RETRIEVE_AUTH_METHODS: type('dspace/auth/RETRIEVE_AUTH_METHODS'),
|
RETRIEVE_AUTH_METHODS: type('dspace/auth/RETRIEVE_AUTH_METHODS'),
|
||||||
RETRIEVE_AUTH_METHODS_SUCCESS: type('dspace/auth/RETRIEVE_AUTH_METHODS_SUCCESS'),
|
RETRIEVE_AUTH_METHODS_SUCCESS: type('dspace/auth/RETRIEVE_AUTH_METHODS_SUCCESS'),
|
||||||
RETRIEVE_AUTH_METHODS_ERROR: type('dspace/auth/RETRIEVE_AUTH_METHODS_ERROR'),
|
RETRIEVE_AUTH_METHODS_ERROR: type('dspace/auth/RETRIEVE_AUTH_METHODS_ERROR'),
|
||||||
@@ -150,6 +151,19 @@ export class CheckAuthenticationTokenCookieAction implements Action {
|
|||||||
public type: string = AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE;
|
public type: string = AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the authentication cookie status to flag an external authentication response.
|
||||||
|
*/
|
||||||
|
export class SetAuthCookieStatus implements Action {
|
||||||
|
public type: string = AuthActionTypes.SET_AUTH_COOKIE_STATUS;
|
||||||
|
|
||||||
|
payload = false;
|
||||||
|
|
||||||
|
constructor(exists: boolean) {
|
||||||
|
this.payload = exists;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign out.
|
* Sign out.
|
||||||
* @class LogOutAction
|
* @class LogOutAction
|
||||||
@@ -425,6 +439,7 @@ export type AuthActions
|
|||||||
| AuthenticationSuccessAction
|
| AuthenticationSuccessAction
|
||||||
| CheckAuthenticationTokenAction
|
| CheckAuthenticationTokenAction
|
||||||
| CheckAuthenticationTokenCookieAction
|
| CheckAuthenticationTokenCookieAction
|
||||||
|
| SetAuthCookieStatus
|
||||||
| RedirectWhenAuthenticationIsRequiredAction
|
| RedirectWhenAuthenticationIsRequiredAction
|
||||||
| RedirectWhenTokenExpiredAction
|
| RedirectWhenTokenExpiredAction
|
||||||
| AddAuthenticationMessageAction
|
| AddAuthenticationMessageAction
|
||||||
|
@@ -214,12 +214,15 @@ describe('AuthEffects', () => {
|
|||||||
authenticated: true
|
authenticated: true
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
spyOn((authEffects as any).authService, 'setExternalAuthStatus');
|
||||||
actions = hot('--a-', { a: { type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE } });
|
actions = hot('--a-', { a: { type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN_COOKIE } });
|
||||||
|
|
||||||
const expected = cold('--b-', { b: new RetrieveTokenAction() });
|
const expected = cold('--b-', { b: new RetrieveTokenAction() });
|
||||||
|
|
||||||
expect(authEffects.checkTokenCookie$).toBeObservable(expected);
|
expect(authEffects.checkTokenCookie$).toBeObservable(expected);
|
||||||
authEffects.checkTokenCookie$.subscribe(() => {
|
authEffects.checkTokenCookie$.subscribe(() => {
|
||||||
|
expect(authServiceStub.setExternalAuthStatus).toHaveBeenCalled();
|
||||||
|
expect(authServiceStub.isExternalAuthentication).toBeTrue();
|
||||||
expect((authEffects as any).authorizationsService.invalidateAuthorizationsRequestCache).toHaveBeenCalled();
|
expect((authEffects as any).authorizationsService.invalidateAuthorizationsRequestCache).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -153,6 +153,7 @@ export class AuthEffects {
|
|||||||
return this.authService.checkAuthenticationCookie().pipe(
|
return this.authService.checkAuthenticationCookie().pipe(
|
||||||
map((response: AuthStatus) => {
|
map((response: AuthStatus) => {
|
||||||
if (response.authenticated) {
|
if (response.authenticated) {
|
||||||
|
this.authService.setExternalAuthStatus(true);
|
||||||
this.authorizationsService.invalidateAuthorizationsRequestCache();
|
this.authorizationsService.invalidateAuthorizationsRequestCache();
|
||||||
return new RetrieveTokenAction();
|
return new RetrieveTokenAction();
|
||||||
} else {
|
} else {
|
||||||
|
@@ -8,6 +8,7 @@ import {
|
|||||||
AuthenticationErrorAction,
|
AuthenticationErrorAction,
|
||||||
AuthenticationSuccessAction,
|
AuthenticationSuccessAction,
|
||||||
CheckAuthenticationTokenAction,
|
CheckAuthenticationTokenAction,
|
||||||
|
SetAuthCookieStatus,
|
||||||
CheckAuthenticationTokenCookieAction,
|
CheckAuthenticationTokenCookieAction,
|
||||||
LogOutAction,
|
LogOutAction,
|
||||||
LogOutErrorAction,
|
LogOutErrorAction,
|
||||||
@@ -219,6 +220,28 @@ describe('authReducer', () => {
|
|||||||
expect(newState).toEqual(state);
|
expect(newState).toEqual(state);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set the authentication cookie status in response to a SET_AUTH_COOKIE_STATUS action', () => {
|
||||||
|
initialState = {
|
||||||
|
authenticated: true,
|
||||||
|
loaded: false,
|
||||||
|
blocking: false,
|
||||||
|
loading: true,
|
||||||
|
externalAuth: false,
|
||||||
|
idle: false
|
||||||
|
};
|
||||||
|
const action = new SetAuthCookieStatus(true);
|
||||||
|
const newState = authReducer(initialState, action);
|
||||||
|
state = {
|
||||||
|
authenticated: true,
|
||||||
|
loaded: false,
|
||||||
|
blocking: false,
|
||||||
|
loading: true,
|
||||||
|
externalAuth: true,
|
||||||
|
idle: false
|
||||||
|
};
|
||||||
|
expect(newState).toEqual(state);
|
||||||
|
});
|
||||||
|
|
||||||
it('should properly set the state, in response to a LOG_OUT action', () => {
|
it('should properly set the state, in response to a LOG_OUT action', () => {
|
||||||
initialState = {
|
initialState = {
|
||||||
authenticated: true,
|
authenticated: true,
|
||||||
|
@@ -10,7 +10,7 @@ import {
|
|||||||
RedirectWhenTokenExpiredAction,
|
RedirectWhenTokenExpiredAction,
|
||||||
RefreshTokenSuccessAction,
|
RefreshTokenSuccessAction,
|
||||||
RetrieveAuthenticatedEpersonSuccessAction,
|
RetrieveAuthenticatedEpersonSuccessAction,
|
||||||
RetrieveAuthMethodsSuccessAction,
|
RetrieveAuthMethodsSuccessAction, SetAuthCookieStatus,
|
||||||
SetRedirectUrlAction
|
SetRedirectUrlAction
|
||||||
} from './auth.actions';
|
} from './auth.actions';
|
||||||
// import models
|
// import models
|
||||||
@@ -59,6 +59,8 @@ export interface AuthState {
|
|||||||
// all authentication Methods enabled at the backend
|
// all authentication Methods enabled at the backend
|
||||||
authMethods?: AuthMethod[];
|
authMethods?: AuthMethod[];
|
||||||
|
|
||||||
|
externalAuth?: boolean,
|
||||||
|
|
||||||
// true when the current user is idle
|
// true when the current user is idle
|
||||||
idle: boolean;
|
idle: boolean;
|
||||||
|
|
||||||
@@ -73,6 +75,7 @@ const initialState: AuthState = {
|
|||||||
blocking: true,
|
blocking: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
authMethods: [],
|
authMethods: [],
|
||||||
|
externalAuth: false,
|
||||||
idle: false
|
idle: false
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -104,6 +107,11 @@ export function authReducer(state: any = initialState, action: AuthActions): Aut
|
|||||||
loading: true,
|
loading: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
case AuthActionTypes.SET_AUTH_COOKIE_STATUS:
|
||||||
|
return Object.assign({}, state, {
|
||||||
|
externalAuth: (action as SetAuthCookieStatus).payload
|
||||||
|
});
|
||||||
|
|
||||||
case AuthActionTypes.AUTHENTICATED_ERROR:
|
case AuthActionTypes.AUTHENTICATED_ERROR:
|
||||||
case AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON_ERROR:
|
case AuthActionTypes.RETRIEVE_AUTHENTICATED_EPERSON_ERROR:
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
|
@@ -25,7 +25,7 @@ import {
|
|||||||
import { CookieService } from '../services/cookie.service';
|
import { CookieService } from '../services/cookie.service';
|
||||||
import {
|
import {
|
||||||
getAuthenticatedUserId,
|
getAuthenticatedUserId,
|
||||||
getAuthenticationToken,
|
getAuthenticationToken, getExternalAuthCookieStatus,
|
||||||
getRedirectUrl,
|
getRedirectUrl,
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
isAuthenticatedLoaded,
|
isAuthenticatedLoaded,
|
||||||
@@ -36,7 +36,7 @@ import { AppState } from '../../app.reducer';
|
|||||||
import {
|
import {
|
||||||
CheckAuthenticationTokenAction,
|
CheckAuthenticationTokenAction,
|
||||||
RefreshTokenAction,
|
RefreshTokenAction,
|
||||||
ResetAuthenticationMessagesAction,
|
ResetAuthenticationMessagesAction, SetAuthCookieStatus,
|
||||||
SetRedirectUrlAction,
|
SetRedirectUrlAction,
|
||||||
SetUserAsIdleAction,
|
SetUserAsIdleAction,
|
||||||
UnsetUserAsIdleAction
|
UnsetUserAsIdleAction
|
||||||
@@ -156,6 +156,24 @@ export class AuthService {
|
|||||||
return this.store.pipe(select(isAuthenticatedLoaded));
|
return this.store.pipe(select(isAuthenticatedLoaded));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to set the external authentication status when authenticating via an
|
||||||
|
* external authentication system (e.g. Shibboleth).
|
||||||
|
* @param external
|
||||||
|
*/
|
||||||
|
public setExternalAuthStatus(external: boolean) {
|
||||||
|
this.store.dispatch(new SetAuthCookieStatus(external));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if an external authentication system (e.g. Shibboleth) is being used
|
||||||
|
* for authentication. Returns false otherwise.
|
||||||
|
*/
|
||||||
|
public isExternalAuthentication(): Observable<boolean> {
|
||||||
|
return this.store.pipe(
|
||||||
|
select(getExternalAuthCookieStatus));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the href link to authenticated user
|
* Returns the href link to authenticated user
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
|
@@ -116,6 +116,8 @@ const _getRedirectUrl = (state: AuthState) => state.redirectUrl;
|
|||||||
|
|
||||||
const _getAuthenticationMethods = (state: AuthState) => state.authMethods;
|
const _getAuthenticationMethods = (state: AuthState) => state.authMethods;
|
||||||
|
|
||||||
|
const _getExternalAuthCookieStatus = (state: AuthState) => state.externalAuth;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the user is idle.
|
* Returns true if the user is idle.
|
||||||
* @function _isIdle
|
* @function _isIdle
|
||||||
@@ -178,6 +180,16 @@ export const isAuthenticated = createSelector(getAuthState, _isAuthenticated);
|
|||||||
*/
|
*/
|
||||||
export const isAuthenticatedLoaded = createSelector(getAuthState, _isAuthenticatedLoaded);
|
export const isAuthenticatedLoaded = createSelector(getAuthState, _isAuthenticatedLoaded);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the authentication cookie status. Expect to be true when external authentication
|
||||||
|
* is used.
|
||||||
|
* @function getExternalAuthCookieStatus
|
||||||
|
* @param {AuthState} state
|
||||||
|
* @param {any} props
|
||||||
|
* @return {boolean}
|
||||||
|
*/
|
||||||
|
export const getExternalAuthCookieStatus = createSelector(getAuthState, _getExternalAuthCookieStatus);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the authentication request is loading.
|
* Returns true if the authentication request is loading.
|
||||||
* @function isAuthenticationLoading
|
* @function isAuthenticationLoading
|
||||||
|
@@ -176,6 +176,7 @@ import { VocabularyEntryDetailsDataService } from './submission/vocabularies/voc
|
|||||||
import { IdentifierData } from '../shared/object-list/identifier-data/identifier-data.model';
|
import { IdentifierData } from '../shared/object-list/identifier-data/identifier-data.model';
|
||||||
import { Subscription } from '../shared/subscriptions/models/subscription.model';
|
import { Subscription } from '../shared/subscriptions/models/subscription.model';
|
||||||
import { SupervisionOrderDataService } from './supervision-order/supervision-order-data.service';
|
import { SupervisionOrderDataService } from './supervision-order/supervision-order-data.service';
|
||||||
|
import { ItemRequest } from './shared/item-request.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When not in production, endpoint responses can be mocked for testing purposes
|
* When not in production, endpoint responses can be mocked for testing purposes
|
||||||
@@ -369,6 +370,7 @@ export const models =
|
|||||||
AccessStatusObject,
|
AccessStatusObject,
|
||||||
IdentifierData,
|
IdentifierData,
|
||||||
Subscription,
|
Subscription,
|
||||||
|
ItemRequest,
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -7,7 +7,7 @@ import { FooterComponent } from './footer.component';
|
|||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-themed-footer',
|
selector: 'ds-themed-footer',
|
||||||
styleUrls: ['footer.component.scss'],
|
styleUrls: [],
|
||||||
templateUrl: '../shared/theme-support/themed.component.html',
|
templateUrl: '../shared/theme-support/themed.component.html',
|
||||||
})
|
})
|
||||||
export class ThemedFooterComponent extends ThemedComponent<FooterComponent> {
|
export class ThemedFooterComponent extends ThemedComponent<FooterComponent> {
|
||||||
@@ -20,6 +20,6 @@ export class ThemedFooterComponent extends ThemedComponent<FooterComponent> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected importUnthemedComponent(): Promise<any> {
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
return import(`./footer.component`);
|
return import('./footer.component');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,11 +3,11 @@ import { ThemedComponent } from '../shared/theme-support/themed.component';
|
|||||||
import { HeaderNavbarWrapperComponent } from './header-navbar-wrapper.component';
|
import { HeaderNavbarWrapperComponent } from './header-navbar-wrapper.component';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Themed wrapper for BreadcrumbsComponent
|
* Themed wrapper for {@link HeaderNavbarWrapperComponent}
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-themed-header-navbar-wrapper',
|
selector: 'ds-themed-header-navbar-wrapper',
|
||||||
styleUrls: ['./themed-header-navbar-wrapper.component.scss'],
|
styleUrls: [],
|
||||||
templateUrl: '../shared/theme-support/themed.component.html',
|
templateUrl: '../shared/theme-support/themed.component.html',
|
||||||
})
|
})
|
||||||
export class ThemedHeaderNavbarWrapperComponent extends ThemedComponent<HeaderNavbarWrapperComponent> {
|
export class ThemedHeaderNavbarWrapperComponent extends ThemedComponent<HeaderNavbarWrapperComponent> {
|
||||||
@@ -20,6 +20,6 @@ export class ThemedHeaderNavbarWrapperComponent extends ThemedComponent<HeaderNa
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected importUnthemedComponent(): Promise<any> {
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
return import(`./header-navbar-wrapper.component`);
|
return import('./header-navbar-wrapper.component');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,8 +8,6 @@ import { Component } from '@angular/core';
|
|||||||
templateUrl: '../shared/theme-support/themed.component.html',
|
templateUrl: '../shared/theme-support/themed.component.html',
|
||||||
})
|
})
|
||||||
export class ThemedHomePageComponent extends ThemedComponent<HomePageComponent> {
|
export class ThemedHomePageComponent extends ThemedComponent<HomePageComponent> {
|
||||||
protected inAndOutputNames: (keyof HomePageComponent & keyof this)[];
|
|
||||||
|
|
||||||
|
|
||||||
protected getComponentName(): string {
|
protected getComponentName(): string {
|
||||||
return 'HomePageComponent';
|
return 'HomePageComponent';
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnChanges, OnInit } from '@angular/core';
|
||||||
import { NgxGalleryImage, NgxGalleryOptions } from '@kolkov/ngx-gallery';
|
import { NgxGalleryImage, NgxGalleryOptions } from '@kolkov/ngx-gallery';
|
||||||
import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
|
import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
|
||||||
import { NgxGalleryAnimation } from '@kolkov/ngx-gallery';
|
import { NgxGalleryAnimation } from '@kolkov/ngx-gallery';
|
||||||
@@ -13,15 +13,16 @@ import { AuthService } from '../../../core/auth/auth.service';
|
|||||||
templateUrl: './media-viewer-image.component.html',
|
templateUrl: './media-viewer-image.component.html',
|
||||||
styleUrls: ['./media-viewer-image.component.scss'],
|
styleUrls: ['./media-viewer-image.component.scss'],
|
||||||
})
|
})
|
||||||
export class MediaViewerImageComponent implements OnInit {
|
export class MediaViewerImageComponent implements OnChanges, OnInit {
|
||||||
@Input() images: MediaViewerItem[];
|
@Input() images: MediaViewerItem[];
|
||||||
@Input() preview?: boolean;
|
@Input() preview?: boolean;
|
||||||
@Input() image?: string;
|
@Input() image?: string;
|
||||||
|
|
||||||
thumbnailPlaceholder = './assets/images/replacement_image.svg';
|
thumbnailPlaceholder = './assets/images/replacement_image.svg';
|
||||||
|
|
||||||
galleryOptions: NgxGalleryOptions[];
|
galleryOptions: NgxGalleryOptions[] = [];
|
||||||
galleryImages: NgxGalleryImage[];
|
|
||||||
|
galleryImages: NgxGalleryImage[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the current user is authenticated
|
* Whether or not the current user is authenticated
|
||||||
@@ -33,11 +34,7 @@ export class MediaViewerImageComponent implements OnInit {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
ngOnChanges(): void {
|
||||||
* Thi method sets up the gallery settings and data
|
|
||||||
*/
|
|
||||||
ngOnInit(): void {
|
|
||||||
this.isAuthenticated$ = this.authService.isAuthenticated();
|
|
||||||
this.galleryOptions = [
|
this.galleryOptions = [
|
||||||
{
|
{
|
||||||
preview: this.preview !== undefined ? this.preview : true,
|
preview: this.preview !== undefined ? this.preview : true,
|
||||||
@@ -53,7 +50,6 @@ export class MediaViewerImageComponent implements OnInit {
|
|||||||
previewFullscreen: true,
|
previewFullscreen: true,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (this.image) {
|
if (this.image) {
|
||||||
this.galleryImages = [
|
this.galleryImages = [
|
||||||
{
|
{
|
||||||
@@ -67,6 +63,11 @@ export class MediaViewerImageComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.isAuthenticated$ = this.authService.isAuthenticated();
|
||||||
|
this.ngOnChanges();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method convert an array of MediaViewerItem into NgxGalleryImage array
|
* This method convert an array of MediaViewerItem into NgxGalleryImage array
|
||||||
* @param medias input NgxGalleryImage array
|
* @param medias input NgxGalleryImage array
|
||||||
|
@@ -1,23 +1,22 @@
|
|||||||
<video
|
<video
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
#media
|
[src]="medias[currentIndex].bitstream._links.content.href"
|
||||||
[src]="filteredMedias[currentIndex].bitstream._links.content.href"
|
|
||||||
id="singleVideo"
|
id="singleVideo"
|
||||||
[poster]="
|
[poster]="
|
||||||
filteredMedias[currentIndex].thumbnail ||
|
medias[currentIndex].thumbnail ||
|
||||||
replacements[filteredMedias[currentIndex].format]
|
replacements[medias[currentIndex].format]
|
||||||
"
|
"
|
||||||
preload="none"
|
preload="none"
|
||||||
controls
|
controls
|
||||||
>
|
>
|
||||||
<ng-container *ngIf="getMediaCap(filteredMedias[currentIndex].bitstream.name) as capInfos">
|
<ng-container *ngIf="getMediaCap(medias[currentIndex].bitstream.name, captions) as capInfos">
|
||||||
<ng-container *ngFor="let capInfo of capInfos">
|
<ng-container *ngFor="let capInfo of capInfos">
|
||||||
<track [src]="capInfo.src" [label]="capInfo.langLabel" [srclang]="capInfo.srclang" />
|
<track [src]="capInfo.src" [label]="capInfo.langLabel" [srclang]="capInfo.srclang" />
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
</video>
|
</video>
|
||||||
<div class="buttons" *ngIf="filteredMedias?.length > 1">
|
<div class="buttons" *ngIf="medias?.length > 1">
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary previous"
|
class="btn btn-primary previous"
|
||||||
[disabled]="currentIndex === 0"
|
[disabled]="currentIndex === 0"
|
||||||
@@ -28,7 +27,7 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary next"
|
class="btn btn-primary next"
|
||||||
[disabled]="currentIndex === filteredMedias.length - 1"
|
[disabled]="currentIndex === medias.length - 1"
|
||||||
(click)="nextMedia()"
|
(click)="nextMedia()"
|
||||||
>
|
>
|
||||||
{{ "media-viewer.next" | translate }}
|
{{ "media-viewer.next" | translate }}
|
||||||
@@ -44,7 +43,7 @@
|
|||||||
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
|
<div ngbDropdownMenu aria-labelledby="dropdownBasic1">
|
||||||
<button
|
<button
|
||||||
ngbDropdownItem
|
ngbDropdownItem
|
||||||
*ngFor="let item of filteredMedias; index as indexOfelement"
|
*ngFor="let item of medias; index as indexOfelement"
|
||||||
class="list-element"
|
class="list-element"
|
||||||
(click)="selectedMedia(indexOfelement)"
|
(click)="selectedMedia(indexOfelement)"
|
||||||
>
|
>
|
||||||
|
@@ -83,7 +83,6 @@ describe('MediaViewerVideoComponent', () => {
|
|||||||
fixture = TestBed.createComponent(MediaViewerVideoComponent);
|
fixture = TestBed.createComponent(MediaViewerVideoComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
component.medias = mockMediaViewerItem;
|
component.medias = mockMediaViewerItem;
|
||||||
component.filteredMedias = mockMediaViewerItem;
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -94,7 +93,6 @@ describe('MediaViewerVideoComponent', () => {
|
|||||||
describe('should show controller buttons when the having mode then one video', () => {
|
describe('should show controller buttons when the having mode then one video', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
component.medias = mockMediaViewerItems;
|
component.medias = mockMediaViewerItems;
|
||||||
component.filteredMedias = mockMediaViewerItems;
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
|
import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
|
||||||
import { languageHelper } from './language-helper';
|
import { languageHelper } from './language-helper';
|
||||||
import { CaptionInfo} from './caption-info';
|
import { CaptionInfo } from './caption-info';
|
||||||
|
import { Bitstream } from 'src/app/core/shared/bitstream.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a video viewer and playlist for the media viewer
|
* This component renders a video viewer and playlist for the media viewer
|
||||||
@@ -11,12 +12,13 @@ import { CaptionInfo} from './caption-info';
|
|||||||
templateUrl: './media-viewer-video.component.html',
|
templateUrl: './media-viewer-video.component.html',
|
||||||
styleUrls: ['./media-viewer-video.component.scss'],
|
styleUrls: ['./media-viewer-video.component.scss'],
|
||||||
})
|
})
|
||||||
export class MediaViewerVideoComponent implements OnInit {
|
export class MediaViewerVideoComponent {
|
||||||
@Input() medias: MediaViewerItem[];
|
@Input() medias: MediaViewerItem[];
|
||||||
|
|
||||||
filteredMedias: MediaViewerItem[];
|
@Input() captions: Bitstream[] = [];
|
||||||
|
|
||||||
|
isCollapsed = false;
|
||||||
|
|
||||||
isCollapsed: boolean;
|
|
||||||
currentIndex = 0;
|
currentIndex = 0;
|
||||||
|
|
||||||
replacements = {
|
replacements = {
|
||||||
@@ -24,11 +26,6 @@ export class MediaViewerVideoComponent implements OnInit {
|
|||||||
audio: './assets/images/replacement_audio.svg',
|
audio: './assets/images/replacement_audio.svg',
|
||||||
};
|
};
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.isCollapsed = false;
|
|
||||||
this.filteredMedias = this.medias.filter((media) => media.format === 'audio' || media.format === 'video');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method check if there is caption file for the media
|
* This method check if there is caption file for the media
|
||||||
* The caption file name is the media name plus "-" following two letter
|
* The caption file name is the media name plus "-" following two letter
|
||||||
@@ -39,29 +36,24 @@ export class MediaViewerVideoComponent implements OnInit {
|
|||||||
* Two letter language code reference
|
* Two letter language code reference
|
||||||
* https://www.w3schools.com/tags/ref_language_codes.asp
|
* https://www.w3schools.com/tags/ref_language_codes.asp
|
||||||
*/
|
*/
|
||||||
getMediaCap(name: string): CaptionInfo[] {
|
getMediaCap(name: string, captions: Bitstream[]): CaptionInfo[] {
|
||||||
let filteredCapMedias: MediaViewerItem[];
|
const capInfos: CaptionInfo[] = [];
|
||||||
let capInfos: CaptionInfo[] = [];
|
const filteredCapMedias: Bitstream[] = captions
|
||||||
filteredCapMedias = this.medias
|
.filter((media: Bitstream) => media.name.substring(0, (media.name.length - 7)).toLowerCase() === name.toLowerCase());
|
||||||
.filter((media) => media.mimetype === 'text/vtt')
|
|
||||||
.filter((media) => media.bitstream.name.substring(0, (media.bitstream.name.length - 7) ).toLowerCase() === name.toLowerCase());
|
|
||||||
|
|
||||||
if (filteredCapMedias) {
|
for (const media of filteredCapMedias) {
|
||||||
filteredCapMedias
|
let srclang: string = media.name.slice(-6, -4).toLowerCase();
|
||||||
.forEach((media, index) => {
|
capInfos.push(new CaptionInfo(
|
||||||
let srclang: string = media.bitstream.name.slice(-6, -4).toLowerCase();
|
media._links.content.href,
|
||||||
capInfos.push(new CaptionInfo(
|
srclang,
|
||||||
media.bitstream._links.content.href,
|
languageHelper[srclang],
|
||||||
srclang,
|
));
|
||||||
languageHelper[srclang]
|
|
||||||
));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return capInfos;
|
return capInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method sets the reviced index into currentIndex
|
* This method sets the received index into currentIndex
|
||||||
* @param index Selected index
|
* @param index Selected index
|
||||||
*/
|
*/
|
||||||
selectedMedia(index: number) {
|
selectedMedia(index: number) {
|
||||||
@@ -69,14 +61,14 @@ export class MediaViewerVideoComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method increade the number of the currentIndex
|
* This method increases the number of the currentIndex
|
||||||
*/
|
*/
|
||||||
nextMedia() {
|
nextMedia() {
|
||||||
this.currentIndex++;
|
this.currentIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method decrese the number of the currentIndex
|
* This method decreases the number of the currentIndex
|
||||||
*/
|
*/
|
||||||
prevMedia() {
|
prevMedia() {
|
||||||
this.currentIndex--;
|
this.currentIndex--;
|
||||||
|
@@ -2,6 +2,7 @@ import { Component, Input } from '@angular/core';
|
|||||||
import { ThemedComponent } from '../../../shared/theme-support/themed.component';
|
import { ThemedComponent } from '../../../shared/theme-support/themed.component';
|
||||||
import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
|
import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
|
||||||
import { MediaViewerVideoComponent } from './media-viewer-video.component';
|
import { MediaViewerVideoComponent } from './media-viewer-video.component';
|
||||||
|
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Themed wrapper for {@link MediaViewerVideoComponent}.
|
* Themed wrapper for {@link MediaViewerVideoComponent}.
|
||||||
@@ -15,8 +16,11 @@ export class ThemedMediaViewerVideoComponent extends ThemedComponent<MediaViewer
|
|||||||
|
|
||||||
@Input() medias: MediaViewerItem[];
|
@Input() medias: MediaViewerItem[];
|
||||||
|
|
||||||
|
@Input() captions: Bitstream[];
|
||||||
|
|
||||||
protected inAndOutputNames: (keyof MediaViewerVideoComponent & keyof this)[] = [
|
protected inAndOutputNames: (keyof MediaViewerVideoComponent & keyof this)[] = [
|
||||||
'medias',
|
'medias',
|
||||||
|
'captions',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected getComponentName(): string {
|
protected getComponentName(): string {
|
||||||
|
@@ -5,32 +5,23 @@
|
|||||||
[showMessage]="false"
|
[showMessage]="false"
|
||||||
></ds-themed-loading>
|
></ds-themed-loading>
|
||||||
<div class="media-viewer" *ngIf="!isLoading">
|
<div class="media-viewer" *ngIf="!isLoading">
|
||||||
<ng-container *ngIf="mediaList.length > 0">
|
<ng-container *ngIf="mediaList.length > 0; else showThumbnail">
|
||||||
<ng-container *ngIf="videoOptions">
|
<ng-container *ngVar="mediaOptions.video && ['audio', 'video'].includes(mediaList[0]?.format) as showVideo">
|
||||||
<ng-container
|
<ng-container *ngVar="mediaOptions.image && mediaList[0]?.format === 'image' as showImage">
|
||||||
*ngIf="
|
<ds-themed-media-viewer-video *ngIf="showVideo" [medias]="mediaList" [captions]="captions$ | async"></ds-themed-media-viewer-video>
|
||||||
mediaList[0]?.format === 'video' || mediaList[0]?.format === 'audio'
|
<ds-themed-media-viewer-image *ngIf="showImage" [images]="mediaList"></ds-themed-media-viewer-image>
|
||||||
"
|
<ng-container *ngIf="showImage || showVideo; else showThumbnail"></ng-container>
|
||||||
>
|
|
||||||
<ds-themed-media-viewer-video [medias]="mediaList"></ds-themed-media-viewer-video>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="mediaList[0]?.format === 'image'">
|
|
||||||
<ds-themed-media-viewer-image [images]="mediaList"></ds-themed-media-viewer-image>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container
|
|
||||||
*ngIf="
|
|
||||||
((mediaList[0]?.format !== 'image') &&
|
|
||||||
(!videoOptions || mediaList[0]?.format !== 'video') &&
|
|
||||||
(!videoOptions || mediaList[0]?.format !== 'audio')) ||
|
|
||||||
mediaList.length === 0
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<ds-themed-media-viewer-image
|
|
||||||
[image]="mediaList[0]?.thumbnail || thumbnailPlaceholder"
|
|
||||||
[preview]="false"
|
|
||||||
></ds-themed-media-viewer-image>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
<ng-template #showThumbnail>
|
||||||
|
<ds-themed-media-viewer-image *ngIf="mediaOptions.image && mediaOptions.video"
|
||||||
|
[image]="(thumbnailsRD$ | async)?.payload?.page[0]?._links.content.href || thumbnailPlaceholder"
|
||||||
|
[preview]="false"
|
||||||
|
></ds-themed-media-viewer-image>
|
||||||
|
<ds-thumbnail *ngIf="!(mediaOptions.image && mediaOptions.video)"
|
||||||
|
[thumbnail]="(thumbnailsRD$ | async)?.payload?.page[0]">
|
||||||
|
</ds-thumbnail>
|
||||||
|
</ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@@ -61,7 +61,7 @@ describe('MediaViewerComponent', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
return TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
loader: {
|
loader: {
|
||||||
@@ -94,7 +94,10 @@ describe('MediaViewerComponent', () => {
|
|||||||
describe('when the bitstreams are loading', () => {
|
describe('when the bitstreams are loading', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
comp.mediaList$.next([mockMediaViewerItem]);
|
comp.mediaList$.next([mockMediaViewerItem]);
|
||||||
comp.videoOptions = true;
|
comp.mediaOptions = {
|
||||||
|
image: true,
|
||||||
|
video: true,
|
||||||
|
};
|
||||||
comp.isLoading = true;
|
comp.isLoading = true;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
@@ -118,7 +121,10 @@ describe('MediaViewerComponent', () => {
|
|||||||
describe('when the bitstreams loading is failed', () => {
|
describe('when the bitstreams loading is failed', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
comp.mediaList$.next([]);
|
comp.mediaList$.next([]);
|
||||||
comp.videoOptions = true;
|
comp.mediaOptions = {
|
||||||
|
image: true,
|
||||||
|
video: true,
|
||||||
|
};
|
||||||
comp.isLoading = false;
|
comp.isLoading = false;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import { filter, take } from 'rxjs/operators';
|
import { filter, take } from 'rxjs/operators';
|
||||||
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
||||||
@@ -11,6 +11,9 @@ import { MediaViewerItem } from '../../core/shared/media-viewer-item.model';
|
|||||||
import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators';
|
import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { followLink } from '../../shared/utils/follow-link-config.model';
|
import { followLink } from '../../shared/utils/follow-link-config.model';
|
||||||
|
import { MediaViewerConfig } from '../../../config/media-viewer-config.interface';
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
|
import { Subscription } from 'rxjs/internal/Subscription';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders the media viewers
|
* This component renders the media viewers
|
||||||
@@ -20,51 +23,71 @@ import { followLink } from '../../shared/utils/follow-link-config.model';
|
|||||||
templateUrl: './media-viewer.component.html',
|
templateUrl: './media-viewer.component.html',
|
||||||
styleUrls: ['./media-viewer.component.scss'],
|
styleUrls: ['./media-viewer.component.scss'],
|
||||||
})
|
})
|
||||||
export class MediaViewerComponent implements OnInit {
|
export class MediaViewerComponent implements OnDestroy, OnInit {
|
||||||
@Input() item: Item;
|
@Input() item: Item;
|
||||||
@Input() videoOptions: boolean;
|
|
||||||
|
|
||||||
mediaList$: BehaviorSubject<MediaViewerItem[]>;
|
@Input() mediaOptions: MediaViewerConfig = environment.mediaViewer;
|
||||||
|
|
||||||
isLoading: boolean;
|
mediaList$: BehaviorSubject<MediaViewerItem[]> = new BehaviorSubject([]);
|
||||||
|
|
||||||
|
captions$: BehaviorSubject<Bitstream[]> = new BehaviorSubject([]);
|
||||||
|
|
||||||
|
isLoading = true;
|
||||||
|
|
||||||
thumbnailPlaceholder = './assets/images/replacement_document.svg';
|
thumbnailPlaceholder = './assets/images/replacement_document.svg';
|
||||||
|
|
||||||
constructor(protected bitstreamDataService: BitstreamDataService) {}
|
thumbnailsRD$: Observable<RemoteData<PaginatedList<Bitstream>>>;
|
||||||
|
|
||||||
|
subs: Subscription[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected bitstreamDataService: BitstreamDataService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.subs.forEach((subscription: Subscription) => subscription.unsubscribe());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This metod loads all the Bitstreams and Thumbnails and contert it to media item
|
* This method loads all the Bitstreams and Thumbnails and converts it to {@link MediaViewerItem}s
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.mediaList$ = new BehaviorSubject([]);
|
const types: string[] = [
|
||||||
this.isLoading = true;
|
...(this.mediaOptions.image ? ['image'] : []),
|
||||||
this.loadRemoteData('ORIGINAL').subscribe((bitstreamsRD) => {
|
...(this.mediaOptions.video ? ['audio', 'video'] : []),
|
||||||
|
];
|
||||||
|
this.thumbnailsRD$ = this.loadRemoteData('THUMBNAIL');
|
||||||
|
this.subs.push(this.loadRemoteData('ORIGINAL').subscribe((bitstreamsRD: RemoteData<PaginatedList<Bitstream>>) => {
|
||||||
if (bitstreamsRD.payload.page.length === 0) {
|
if (bitstreamsRD.payload.page.length === 0) {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.mediaList$.next([]);
|
this.mediaList$.next([]);
|
||||||
} else {
|
} else {
|
||||||
this.loadRemoteData('THUMBNAIL').subscribe((thumbnailsRD) => {
|
this.subs.push(this.thumbnailsRD$.subscribe((thumbnailsRD: RemoteData<PaginatedList<Bitstream>>) => {
|
||||||
for (
|
for (
|
||||||
let index = 0;
|
let index = 0;
|
||||||
index < bitstreamsRD.payload.page.length;
|
index < bitstreamsRD.payload.page.length;
|
||||||
index++
|
index++
|
||||||
) {
|
) {
|
||||||
bitstreamsRD.payload.page[index].format
|
this.subs.push(bitstreamsRD.payload.page[index].format
|
||||||
.pipe(getFirstSucceededRemoteDataPayload())
|
.pipe(getFirstSucceededRemoteDataPayload())
|
||||||
.subscribe((format) => {
|
.subscribe((format: BitstreamFormat) => {
|
||||||
const current = this.mediaList$.getValue();
|
|
||||||
const mediaItem = this.createMediaViewerItem(
|
const mediaItem = this.createMediaViewerItem(
|
||||||
bitstreamsRD.payload.page[index],
|
bitstreamsRD.payload.page[index],
|
||||||
format,
|
format,
|
||||||
thumbnailsRD.payload && thumbnailsRD.payload.page[index]
|
thumbnailsRD.payload && thumbnailsRD.payload.page[index]
|
||||||
);
|
);
|
||||||
this.mediaList$.next([...current, mediaItem]);
|
if (types.includes(mediaItem.format)) {
|
||||||
});
|
this.mediaList$.next([...this.mediaList$.getValue(), mediaItem]);
|
||||||
|
} else if (format.mimetype === 'text/vtt') {
|
||||||
|
this.captions$.next([...this.captions$.getValue(), bitstreamsRD.payload.page[index]]);
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,16 +117,12 @@ export class MediaViewerComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method create MediaViewerItem from incoming bitstreams
|
* This method creates a {@link MediaViewerItem} from incoming {@link Bitstream}s
|
||||||
* @param original original remote data bitstream
|
* @param original original bitstream
|
||||||
* @param format original bitstream format
|
* @param format original bitstream format
|
||||||
* @param thumbnail trunbnail remote data bitstream
|
* @param thumbnail thumbnail bitstream
|
||||||
*/
|
*/
|
||||||
createMediaViewerItem(
|
createMediaViewerItem(original: Bitstream, format: BitstreamFormat, thumbnail: Bitstream): MediaViewerItem {
|
||||||
original: Bitstream,
|
|
||||||
format: BitstreamFormat,
|
|
||||||
thumbnail: Bitstream
|
|
||||||
): MediaViewerItem {
|
|
||||||
const mediaItem = new MediaViewerItem();
|
const mediaItem = new MediaViewerItem();
|
||||||
mediaItem.bitstream = original;
|
mediaItem.bitstream = original;
|
||||||
mediaItem.format = format.mimetype.split('/')[0];
|
mediaItem.format = format.mimetype.split('/')[0];
|
||||||
|
@@ -2,6 +2,7 @@ import { Component, Input } from '@angular/core';
|
|||||||
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
||||||
import { MediaViewerComponent } from './media-viewer.component';
|
import { MediaViewerComponent } from './media-viewer.component';
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
import { MediaViewerConfig } from '../../../config/media-viewer-config.interface';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Themed wrapper for {@link MediaViewerComponent}.
|
* Themed wrapper for {@link MediaViewerComponent}.
|
||||||
@@ -14,11 +15,11 @@ import { Item } from '../../core/shared/item.model';
|
|||||||
export class ThemedMediaViewerComponent extends ThemedComponent<MediaViewerComponent> {
|
export class ThemedMediaViewerComponent extends ThemedComponent<MediaViewerComponent> {
|
||||||
|
|
||||||
@Input() item: Item;
|
@Input() item: Item;
|
||||||
@Input() videoOptions: boolean;
|
@Input() mediaOptions: MediaViewerConfig;
|
||||||
|
|
||||||
protected inAndOutputNames: (keyof MediaViewerComponent & keyof this)[] = [
|
protected inAndOutputNames: (keyof MediaViewerComponent & keyof this)[] = [
|
||||||
'item',
|
'item',
|
||||||
'videoOptions',
|
'mediaOptions',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected getComponentName(): string {
|
protected getComponentName(): string {
|
||||||
|
@@ -15,13 +15,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 col-md-4">
|
<div class="col-xs-12 col-md-4">
|
||||||
<ng-container *ngIf="!mediaViewer.image">
|
<ng-container *ngIf="!(mediaViewer.image || mediaViewer.video)">
|
||||||
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
|
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
|
||||||
<ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
|
<ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
|
||||||
</ds-metadata-field-wrapper>
|
</ds-metadata-field-wrapper>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div *ngIf="mediaViewer.image" class="mb-2">
|
<div *ngIf="mediaViewer.image || mediaViewer.video" class="mb-2">
|
||||||
<ds-themed-media-viewer [item]="object" [videoOptions]="mediaViewer.video"></ds-themed-media-viewer>
|
<ds-themed-media-viewer [item]="object"></ds-themed-media-viewer>
|
||||||
</div>
|
</div>
|
||||||
<ds-themed-item-page-file-section [item]="object"></ds-themed-item-page-file-section>
|
<ds-themed-item-page-file-section [item]="object"></ds-themed-item-page-file-section>
|
||||||
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
||||||
|
@@ -16,13 +16,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 col-md-4">
|
<div class="col-xs-12 col-md-4">
|
||||||
<ng-container *ngIf="!mediaViewer.image">
|
<ng-container *ngIf="!(mediaViewer.image || mediaViewer.video)">
|
||||||
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
|
<ds-metadata-field-wrapper [hideIfNoTextContent]="false">
|
||||||
<ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
|
<ds-thumbnail [thumbnail]="object?.thumbnail | async"></ds-thumbnail>
|
||||||
</ds-metadata-field-wrapper>
|
</ds-metadata-field-wrapper>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div *ngIf="mediaViewer.image" class="mb-2">
|
<div *ngIf="mediaViewer.image || mediaViewer.video" class="mb-2">
|
||||||
<ds-themed-media-viewer [item]="object" [videoOptions]="mediaViewer.video"></ds-themed-media-viewer>
|
<ds-themed-media-viewer [item]="object"></ds-themed-media-viewer>
|
||||||
</div>
|
</div>
|
||||||
<ds-themed-item-page-file-section [item]="object"></ds-themed-item-page-file-section>
|
<ds-themed-item-page-file-section [item]="object"></ds-themed-item-page-file-section>
|
||||||
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
||||||
|
@@ -19,7 +19,7 @@ export class ThemedMetadataRepresentationListComponent extends ThemedComponent<M
|
|||||||
|
|
||||||
@Input() label: string;
|
@Input() label: string;
|
||||||
|
|
||||||
@Input() incrementBy = 10;
|
@Input() incrementBy: number;
|
||||||
|
|
||||||
protected getComponentName(): string {
|
protected getComponentName(): string {
|
||||||
return 'MetadataRepresentationListComponent';
|
return 'MetadataRepresentationListComponent';
|
||||||
|
@@ -11,7 +11,6 @@ import { MyDSpacePageComponent } from './my-dspace-page.component';
|
|||||||
templateUrl: './../shared/theme-support/themed.component.html'
|
templateUrl: './../shared/theme-support/themed.component.html'
|
||||||
})
|
})
|
||||||
export class ThemedMyDSpacePageComponent extends ThemedComponent<MyDSpacePageComponent> {
|
export class ThemedMyDSpacePageComponent extends ThemedComponent<MyDSpacePageComponent> {
|
||||||
protected inAndOutputNames: (keyof MyDSpacePageComponent & keyof this)[];
|
|
||||||
|
|
||||||
protected getComponentName(): string {
|
protected getComponentName(): string {
|
||||||
return 'MyDSpacePageComponent';
|
return 'MyDSpacePageComponent';
|
||||||
|
@@ -28,19 +28,18 @@ export class ThemedConfigurationSearchPageComponent extends ThemedComponent<Conf
|
|||||||
/**
|
/**
|
||||||
* True when the search component should show results on the current page
|
* True when the search component should show results on the current page
|
||||||
*/
|
*/
|
||||||
@Input() inPlaceSearch = true;
|
@Input() inPlaceSearch: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the search bar should be visible
|
* Whether or not the search bar should be visible
|
||||||
*/
|
*/
|
||||||
@Input()
|
@Input() searchEnabled: boolean;
|
||||||
searchEnabled = true;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The width of the sidebar (bootstrap columns)
|
* The width of the sidebar (bootstrap columns)
|
||||||
*/
|
*/
|
||||||
@Input()
|
@Input()
|
||||||
sideBarWidth = 3;
|
sideBarWidth: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The currently applied configuration (determines title of search)
|
* The currently applied configuration (determines title of search)
|
||||||
@@ -66,7 +65,7 @@ export class ThemedConfigurationSearchPageComponent extends ThemedComponent<Conf
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected importUnthemedComponent(): Promise<any> {
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
return import(`./configuration-search-page.component`);
|
return import('./configuration-search-page.component');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -11,11 +11,11 @@ export class ThemedCollectionDropdownComponent extends ThemedComponent<Collectio
|
|||||||
|
|
||||||
@Input() entityType: string;
|
@Input() entityType: string;
|
||||||
|
|
||||||
@Output() searchComplete = new EventEmitter<any>();
|
@Output() searchComplete: EventEmitter<any> = new EventEmitter();
|
||||||
|
|
||||||
@Output() theOnlySelectable = new EventEmitter<CollectionListEntry>();
|
@Output() theOnlySelectable: EventEmitter<CollectionListEntry> = new EventEmitter();
|
||||||
|
|
||||||
@Output() selectionChange = new EventEmitter<CollectionListEntry>();
|
@Output() selectionChange = new EventEmitter();
|
||||||
|
|
||||||
protected inAndOutputNames: (keyof CollectionDropdownComponent & keyof this)[] = ['entityType', 'searchComplete', 'theOnlySelectable', 'selectionChange'];
|
protected inAndOutputNames: (keyof CollectionDropdownComponent & keyof this)[] = ['entityType', 'searchComplete', 'theOnlySelectable', 'selectionChange'];
|
||||||
|
|
||||||
|
@@ -4,27 +4,14 @@ import { RelationshipOptions } from '../../../models/relationship-options.model'
|
|||||||
import { ListableObject } from '../../../../../object-collection/shared/listable-object.model';
|
import { ListableObject } from '../../../../../object-collection/shared/listable-object.model';
|
||||||
import { Context } from '../../../../../../core/shared/context.model';
|
import { Context } from '../../../../../../core/shared/context.model';
|
||||||
import { Item } from '../../../../../../core/shared/item.model';
|
import { Item } from '../../../../../../core/shared/item.model';
|
||||||
import { SEARCH_CONFIG_SERVICE } from '../../../../../../my-dspace-page/my-dspace-page.component';
|
|
||||||
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
|
|
||||||
import { Collection } from '../../../../../../core/shared/collection.model';
|
import { Collection } from '../../../../../../core/shared/collection.model';
|
||||||
import { ExternalSource } from '../../../../../../core/shared/external-source.model';
|
import { ExternalSource } from '../../../../../../core/shared/external-source.model';
|
||||||
import { DsDynamicLookupRelationExternalSourceTabComponent } from './dynamic-lookup-relation-external-source-tab.component';
|
import { DsDynamicLookupRelationExternalSourceTabComponent } from './dynamic-lookup-relation-external-source-tab.component';
|
||||||
import { fadeIn, fadeInOut } from '../../../../../animations/fade';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-themed-dynamic-lookup-relation-external-source-tab',
|
selector: 'ds-themed-dynamic-lookup-relation-external-source-tab',
|
||||||
styleUrls: [],
|
styleUrls: [],
|
||||||
templateUrl: '../../../../../theme-support/themed.component.html',
|
templateUrl: '../../../../../theme-support/themed.component.html',
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: SEARCH_CONFIG_SERVICE,
|
|
||||||
useClass: SearchConfigurationService
|
|
||||||
}
|
|
||||||
],
|
|
||||||
animations: [
|
|
||||||
fadeIn,
|
|
||||||
fadeInOut
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
export class ThemedDynamicLookupRelationExternalSourceTabComponent extends ThemedComponent<DsDynamicLookupRelationExternalSourceTabComponent> {
|
export class ThemedDynamicLookupRelationExternalSourceTabComponent extends ThemedComponent<DsDynamicLookupRelationExternalSourceTabComponent> {
|
||||||
protected inAndOutputNames: (keyof DsDynamicLookupRelationExternalSourceTabComponent & keyof this)[] = ['label', 'listId',
|
protected inAndOutputNames: (keyof DsDynamicLookupRelationExternalSourceTabComponent & keyof this)[] = ['label', 'listId',
|
||||||
@@ -44,7 +31,7 @@ export class ThemedDynamicLookupRelationExternalSourceTabComponent extends Theme
|
|||||||
|
|
||||||
@Input() repeatable: boolean;
|
@Input() repeatable: boolean;
|
||||||
|
|
||||||
@Output() importedObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() importedObject: EventEmitter<ListableObject> = new EventEmitter();
|
||||||
|
|
||||||
@Input() externalSource: ExternalSource;
|
@Input() externalSource: ExternalSource;
|
||||||
|
|
||||||
|
@@ -10,19 +10,11 @@ import { Item } from '../../../../../../core/shared/item.model';
|
|||||||
import { SearchResult } from '../../../../../search/models/search-result.model';
|
import { SearchResult } from '../../../../../search/models/search-result.model';
|
||||||
import { SearchObjects } from '../../../../../search/models/search-objects.model';
|
import { SearchObjects } from '../../../../../search/models/search-objects.model';
|
||||||
import { DSpaceObject } from '../../../../../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../../../../../core/shared/dspace-object.model';
|
||||||
import { SEARCH_CONFIG_SERVICE } from '../../../../../../my-dspace-page/my-dspace-page.component';
|
|
||||||
import { SearchConfigurationService } from '../../../../../../core/shared/search/search-configuration.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-themed-dynamic-lookup-relation-search-tab',
|
selector: 'ds-themed-dynamic-lookup-relation-search-tab',
|
||||||
styleUrls: [],
|
styleUrls: [],
|
||||||
templateUrl: '../../../../../theme-support/themed.component.html',
|
templateUrl: '../../../../../theme-support/themed.component.html',
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: SEARCH_CONFIG_SERVICE,
|
|
||||||
useClass: SearchConfigurationService
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
export class ThemedDynamicLookupRelationSearchTabComponent extends ThemedComponent<DsDynamicLookupRelationSearchTabComponent> {
|
export class ThemedDynamicLookupRelationSearchTabComponent extends ThemedComponent<DsDynamicLookupRelationSearchTabComponent> {
|
||||||
protected inAndOutputNames: (keyof DsDynamicLookupRelationSearchTabComponent & keyof this)[] = ['relationship', 'listId',
|
protected inAndOutputNames: (keyof DsDynamicLookupRelationSearchTabComponent & keyof this)[] = ['relationship', 'listId',
|
||||||
@@ -51,11 +43,11 @@ export class ThemedDynamicLookupRelationSearchTabComponent extends ThemedCompone
|
|||||||
|
|
||||||
@Input() isEditRelationship: boolean;
|
@Input() isEditRelationship: boolean;
|
||||||
|
|
||||||
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter();
|
||||||
|
|
||||||
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter();
|
||||||
|
|
||||||
@Output() resultFound: EventEmitter<SearchObjects<DSpaceObject>> = new EventEmitter<SearchObjects<DSpaceObject>>();
|
@Output() resultFound: EventEmitter<SearchObjects<DSpaceObject>> = new EventEmitter();
|
||||||
|
|
||||||
protected getComponentName(): string {
|
protected getComponentName(): string {
|
||||||
return 'DsDynamicLookupRelationSearchTabComponent';
|
return 'DsDynamicLookupRelationSearchTabComponent';
|
||||||
|
@@ -14,8 +14,8 @@ import { ThemeService } from '../theme-support/theme.service';
|
|||||||
export class ThemedLoadingComponent extends ThemedComponent<LoadingComponent> {
|
export class ThemedLoadingComponent extends ThemedComponent<LoadingComponent> {
|
||||||
|
|
||||||
@Input() message: string;
|
@Input() message: string;
|
||||||
@Input() showMessage = true;
|
@Input() showMessage: boolean;
|
||||||
@Input() spinner = false;
|
@Input() spinner: boolean;
|
||||||
|
|
||||||
protected inAndOutputNames: (keyof LoadingComponent & keyof this)[] = ['message', 'showMessage', 'spinner'];
|
protected inAndOutputNames: (keyof LoadingComponent & keyof this)[] = ['message', 'showMessage', 'spinner'];
|
||||||
|
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
import { ChangeDetectorRef, Component, ComponentFactoryResolver, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { ThemedComponent } from '../../../theme-support/themed.component';
|
import { ThemedComponent } from '../../../theme-support/themed.component';
|
||||||
import { ItemListPreviewComponent } from './item-list-preview.component';
|
import { ItemListPreviewComponent } from './item-list-preview.component';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type';
|
import { MyDspaceItemStatusType } from '../../../object-collection/shared/mydspace-item-status/my-dspace-item-status-type';
|
||||||
import { SearchResult } from '../../../search/models/search-result.model';
|
import { SearchResult } from '../../../search/models/search-result.model';
|
||||||
import { WorkflowItem } from 'src/app/core/submission/models/workflowitem.model';
|
import { WorkflowItem } from 'src/app/core/submission/models/workflowitem.model';
|
||||||
import { ThemeService } from 'src/app/shared/theme-support/theme.service';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Themed wrapper for ItemListPreviewComponent
|
* Themed wrapper for ItemListPreviewComponent
|
||||||
@@ -24,22 +23,10 @@ export class ThemedItemListPreviewComponent extends ThemedComponent<ItemListPrev
|
|||||||
|
|
||||||
@Input() status: MyDspaceItemStatusType;
|
@Input() status: MyDspaceItemStatusType;
|
||||||
|
|
||||||
@Input() showSubmitter = false;
|
@Input() showSubmitter: boolean;
|
||||||
|
|
||||||
@Input() workflowItem: WorkflowItem;
|
@Input() workflowItem: WorkflowItem;
|
||||||
|
|
||||||
constructor(
|
|
||||||
protected resolver: ComponentFactoryResolver,
|
|
||||||
protected cdr: ChangeDetectorRef,
|
|
||||||
protected themeService: ThemeService,
|
|
||||||
) {
|
|
||||||
super(resolver, cdr, themeService);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
super.ngOnInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getComponentName(): string {
|
protected getComponentName(): string {
|
||||||
return 'ItemListPreviewComponent';
|
return 'ItemListPreviewComponent';
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||||
import { ObjectListComponent } from './object-list.component';
|
import { ObjectListComponent } from './object-list.component';
|
||||||
import { ThemedComponent } from '../theme-support/themed.component';
|
import { ThemedComponent } from '../theme-support/themed.component';
|
||||||
import {ViewMode} from '../../core/shared/view-mode.model';
|
|
||||||
import {PaginationComponentOptions} from '../pagination/pagination-component-options.model';
|
import {PaginationComponentOptions} from '../pagination/pagination-component-options.model';
|
||||||
import {SortDirection, SortOptions} from '../../core/cache/models/sort-options.model';
|
import {SortDirection, SortOptions} from '../../core/cache/models/sort-options.model';
|
||||||
import {CollectionElementLinkType} from '../object-collection/collection-element-link.type';
|
import {CollectionElementLinkType} from '../object-collection/collection-element-link.type';
|
||||||
@@ -19,10 +18,6 @@ import {ListableObject} from '../object-collection/shared/listable-object.model'
|
|||||||
templateUrl: '../theme-support/themed.component.html',
|
templateUrl: '../theme-support/themed.component.html',
|
||||||
})
|
})
|
||||||
export class ThemedObjectListComponent extends ThemedComponent<ObjectListComponent> {
|
export class ThemedObjectListComponent extends ThemedComponent<ObjectListComponent> {
|
||||||
/**
|
|
||||||
* The view mode of the this component
|
|
||||||
*/
|
|
||||||
viewMode = ViewMode.ListElement;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current pagination configuration
|
* The current pagination configuration
|
||||||
@@ -37,18 +32,20 @@ export class ThemedObjectListComponent extends ThemedComponent<ObjectListCompone
|
|||||||
/**
|
/**
|
||||||
* Whether or not the list elements have a border
|
* Whether or not the list elements have a border
|
||||||
*/
|
*/
|
||||||
@Input() hasBorder = false;
|
@Input() hasBorder: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The whether or not the gear is hidden
|
* The whether or not the gear is hidden
|
||||||
*/
|
*/
|
||||||
@Input() hideGear = false;
|
@Input() hideGear: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the pager is visible when there is only a single page of results
|
* Whether or not the pager is visible when there is only a single page of results
|
||||||
*/
|
*/
|
||||||
@Input() hidePagerWhenSinglePage = true;
|
@Input() hidePagerWhenSinglePage: boolean;
|
||||||
@Input() selectable = false;
|
|
||||||
|
@Input() selectable: boolean;
|
||||||
|
|
||||||
@Input() selectionConfig: { repeatable: boolean, listId: string };
|
@Input() selectionConfig: { repeatable: boolean, listId: string };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,12 +61,12 @@ export class ThemedObjectListComponent extends ThemedComponent<ObjectListCompone
|
|||||||
/**
|
/**
|
||||||
* Option for hiding the pagination detail
|
* Option for hiding the pagination detail
|
||||||
*/
|
*/
|
||||||
@Input() hidePaginationDetail = false;
|
@Input() hidePaginationDetail: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not to add an import button to the object
|
* Whether or not to add an import button to the object
|
||||||
*/
|
*/
|
||||||
@Input() importable = false;
|
@Input() importable: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Config used for the import button
|
* Config used for the import button
|
||||||
@@ -79,42 +76,24 @@ export class ThemedObjectListComponent extends ThemedComponent<ObjectListCompone
|
|||||||
/**
|
/**
|
||||||
* Whether or not the pagination should be rendered as simple previous and next buttons instead of the normal pagination
|
* Whether or not the pagination should be rendered as simple previous and next buttons instead of the normal pagination
|
||||||
*/
|
*/
|
||||||
@Input() showPaginator = true;
|
@Input() showPaginator: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emit when one of the listed object has changed.
|
* Emit when one of the listed object has changed.
|
||||||
*/
|
*/
|
||||||
@Output() contentChange = new EventEmitter<any>();
|
@Output() contentChange: EventEmitter<any> = new EventEmitter();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If showPaginator is set to true, emit when the previous button is clicked
|
* If showPaginator is set to true, emit when the previous button is clicked
|
||||||
*/
|
*/
|
||||||
@Output() prev = new EventEmitter<boolean>();
|
@Output() prev: EventEmitter<boolean> = new EventEmitter();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If showPaginator is set to true, emit when the next button is clicked
|
* If showPaginator is set to true, emit when the next button is clicked
|
||||||
*/
|
*/
|
||||||
@Output() next = new EventEmitter<boolean>();
|
@Output() next: EventEmitter<boolean> = new EventEmitter();
|
||||||
|
|
||||||
/**
|
@Input() objects: RemoteData<PaginatedList<ListableObject>>;
|
||||||
* The current listable objects
|
|
||||||
*/
|
|
||||||
private _objects: RemoteData<PaginatedList<ListableObject>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setter for the objects
|
|
||||||
* @param objects The new objects
|
|
||||||
*/
|
|
||||||
@Input() set objects(objects: RemoteData<PaginatedList<ListableObject>>) {
|
|
||||||
this._objects = objects;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Getter to return the current objects
|
|
||||||
*/
|
|
||||||
get objects() {
|
|
||||||
return this._objects;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An event fired when the page is changed.
|
* An event fired when the page is changed.
|
||||||
@@ -123,48 +102,45 @@ export class ThemedObjectListComponent extends ThemedComponent<ObjectListCompone
|
|||||||
@Output() change: EventEmitter<{
|
@Output() change: EventEmitter<{
|
||||||
pagination: PaginationComponentOptions,
|
pagination: PaginationComponentOptions,
|
||||||
sort: SortOptions
|
sort: SortOptions
|
||||||
}> = new EventEmitter<{
|
}> = new EventEmitter();
|
||||||
pagination: PaginationComponentOptions,
|
|
||||||
sort: SortOptions
|
|
||||||
}>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An event fired when the page is changed.
|
* An event fired when the page is changed.
|
||||||
* Event's payload equals to the newly selected page.
|
* Event's payload equals to the newly selected page.
|
||||||
*/
|
*/
|
||||||
@Output() pageChange: EventEmitter<number> = new EventEmitter<number>();
|
@Output() pageChange: EventEmitter<number> = new EventEmitter();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An event fired when the page wsize is changed.
|
* An event fired when the page wsize is changed.
|
||||||
* Event's payload equals to the newly selected page size.
|
* Event's payload equals to the newly selected page size.
|
||||||
*/
|
*/
|
||||||
@Output() pageSizeChange: EventEmitter<number> = new EventEmitter<number>();
|
@Output() pageSizeChange: EventEmitter<number> = new EventEmitter();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An event fired when the sort direction is changed.
|
* An event fired when the sort direction is changed.
|
||||||
* Event's payload equals to the newly selected sort direction.
|
* Event's payload equals to the newly selected sort direction.
|
||||||
*/
|
*/
|
||||||
@Output() sortDirectionChange: EventEmitter<SortDirection> = new EventEmitter<SortDirection>();
|
@Output() sortDirectionChange: EventEmitter<SortDirection> = new EventEmitter();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An event fired when on of the pagination parameters changes
|
* An event fired when on of the pagination parameters changes
|
||||||
*/
|
*/
|
||||||
@Output() paginationChange: EventEmitter<any> = new EventEmitter<any>();
|
@Output() paginationChange: EventEmitter<any> = new EventEmitter();
|
||||||
|
|
||||||
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter();
|
||||||
|
|
||||||
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send an import event to the parent component
|
* Send an import event to the parent component
|
||||||
*/
|
*/
|
||||||
@Output() importObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() importObject: EventEmitter<ListableObject> = new EventEmitter();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An event fired when the sort field is changed.
|
* An event fired when the sort field is changed.
|
||||||
* Event's payload equals to the newly selected sort field.
|
* Event's payload equals to the newly selected sort field.
|
||||||
*/
|
*/
|
||||||
@Output() sortFieldChange: EventEmitter<string> = new EventEmitter<string>();
|
@Output() sortFieldChange: EventEmitter<string> = new EventEmitter();
|
||||||
|
|
||||||
inAndOutputNames: (keyof ObjectListComponent & keyof this)[] = [
|
inAndOutputNames: (keyof ObjectListComponent & keyof this)[] = [
|
||||||
'config',
|
'config',
|
||||||
|
@@ -29,7 +29,7 @@ export class ThemedSearchResultsComponent extends ThemedComponent<SearchResultsC
|
|||||||
|
|
||||||
@Input() searchConfig: PaginatedSearchOptions;
|
@Input() searchConfig: PaginatedSearchOptions;
|
||||||
|
|
||||||
@Input() showCsvExport = false;
|
@Input() showCsvExport: boolean;
|
||||||
|
|
||||||
@Input() sortConfig: SortOptions;
|
@Input() sortConfig: SortOptions;
|
||||||
|
|
||||||
@@ -37,21 +37,21 @@ export class ThemedSearchResultsComponent extends ThemedComponent<SearchResultsC
|
|||||||
|
|
||||||
@Input() configuration: string;
|
@Input() configuration: string;
|
||||||
|
|
||||||
@Input() disableHeader = false;
|
@Input() disableHeader: boolean;
|
||||||
|
|
||||||
@Input() selectable = false;
|
@Input() selectable: boolean;
|
||||||
|
|
||||||
@Input() context: Context;
|
@Input() context: Context;
|
||||||
|
|
||||||
@Input() hidePaginationDetail = false;
|
@Input() hidePaginationDetail: boolean;
|
||||||
|
|
||||||
@Input() selectionConfig: SelectionConfig = null;
|
@Input() selectionConfig: SelectionConfig;
|
||||||
|
|
||||||
@Output() contentChange: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() contentChange: EventEmitter<ListableObject> = new EventEmitter();
|
||||||
|
|
||||||
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter();
|
||||||
|
|
||||||
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter();
|
||||||
|
|
||||||
protected getComponentName(): string {
|
protected getComponentName(): string {
|
||||||
return 'SearchResultsComponent';
|
return 'SearchResultsComponent';
|
||||||
|
@@ -136,7 +136,7 @@ export class SearchComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* List of available view mode
|
* List of available view mode
|
||||||
*/
|
*/
|
||||||
@Input() useUniquePageId: false;
|
@Input() useUniquePageId: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of available view mode
|
* List of available view mode
|
||||||
|
@@ -11,7 +11,7 @@ import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
|||||||
import { ListableObject } from '../object-collection/shared/listable-object.model';
|
import { ListableObject } from '../object-collection/shared/listable-object.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Themed wrapper for SearchComponent
|
* Themed wrapper for {@link SearchComponent}
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-themed-search',
|
selector: 'ds-themed-search',
|
||||||
@@ -21,53 +21,53 @@ import { ListableObject } from '../object-collection/shared/listable-object.mode
|
|||||||
export class ThemedSearchComponent extends ThemedComponent<SearchComponent> {
|
export class ThemedSearchComponent extends ThemedComponent<SearchComponent> {
|
||||||
protected inAndOutputNames: (keyof SearchComponent & keyof this)[] = ['configurationList', 'context', 'configuration', 'fixedFilterQuery', 'useCachedVersionIfAvailable', 'inPlaceSearch', 'linkType', 'paginationId', 'searchEnabled', 'sideBarWidth', 'searchFormPlaceholder', 'selectable', 'selectionConfig', 'showCsvExport', 'showSidebar', 'showViewModes', 'useUniquePageId', 'viewModeList', 'showScopeSelector', 'resultFound', 'deselectObject', 'selectObject', 'trackStatistics', 'query'];
|
protected inAndOutputNames: (keyof SearchComponent & keyof this)[] = ['configurationList', 'context', 'configuration', 'fixedFilterQuery', 'useCachedVersionIfAvailable', 'inPlaceSearch', 'linkType', 'paginationId', 'searchEnabled', 'sideBarWidth', 'searchFormPlaceholder', 'selectable', 'selectionConfig', 'showCsvExport', 'showSidebar', 'showViewModes', 'useUniquePageId', 'viewModeList', 'showScopeSelector', 'resultFound', 'deselectObject', 'selectObject', 'trackStatistics', 'query'];
|
||||||
|
|
||||||
@Input() configurationList: SearchConfigurationOption[] = [];
|
@Input() configurationList: SearchConfigurationOption[];
|
||||||
|
|
||||||
@Input() context: Context = Context.Search;
|
@Input() context: Context;
|
||||||
|
|
||||||
@Input() configuration = 'default';
|
@Input() configuration: string;
|
||||||
|
|
||||||
@Input() fixedFilterQuery: string;
|
@Input() fixedFilterQuery: string;
|
||||||
|
|
||||||
@Input() useCachedVersionIfAvailable = true;
|
@Input() useCachedVersionIfAvailable: boolean;
|
||||||
|
|
||||||
@Input() inPlaceSearch = true;
|
@Input() inPlaceSearch: boolean;
|
||||||
|
|
||||||
@Input() linkType: CollectionElementLinkType;
|
@Input() linkType: CollectionElementLinkType;
|
||||||
|
|
||||||
@Input() paginationId = 'spc';
|
@Input() paginationId: string;
|
||||||
|
|
||||||
@Input() searchEnabled = true;
|
@Input() searchEnabled: boolean;
|
||||||
|
|
||||||
@Input() sideBarWidth = 3;
|
@Input() sideBarWidth: number;
|
||||||
|
|
||||||
@Input() searchFormPlaceholder = 'search.search-form.placeholder';
|
@Input() searchFormPlaceholder: string;
|
||||||
|
|
||||||
@Input() selectable = false;
|
@Input() selectable: boolean;
|
||||||
|
|
||||||
@Input() selectionConfig: SelectionConfig;
|
@Input() selectionConfig: SelectionConfig;
|
||||||
|
|
||||||
@Input() showCsvExport = false;
|
@Input() showCsvExport: boolean;
|
||||||
|
|
||||||
@Input() showSidebar = true;
|
@Input() showSidebar: boolean;
|
||||||
|
|
||||||
@Input() showViewModes = true;
|
@Input() showViewModes: boolean;
|
||||||
|
|
||||||
@Input() useUniquePageId: false;
|
@Input() useUniquePageId: boolean;
|
||||||
|
|
||||||
@Input() viewModeList: ViewMode[];
|
@Input() viewModeList: ViewMode[];
|
||||||
|
|
||||||
@Input() showScopeSelector = true;
|
@Input() showScopeSelector: boolean;
|
||||||
|
|
||||||
@Input() trackStatistics = false;
|
@Input() trackStatistics: boolean;
|
||||||
|
|
||||||
@Input() query: string;
|
@Input() query: string;
|
||||||
|
|
||||||
@Output() resultFound: EventEmitter<SearchObjects<DSpaceObject>> = new EventEmitter<SearchObjects<DSpaceObject>>();
|
@Output() resultFound: EventEmitter<SearchObjects<DSpaceObject>> = new EventEmitter();
|
||||||
|
|
||||||
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() deselectObject: EventEmitter<ListableObject> = new EventEmitter();
|
||||||
|
|
||||||
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter();
|
||||||
|
|
||||||
protected getComponentName(): string {
|
protected getComponentName(): string {
|
||||||
return 'SearchComponent';
|
return 'SearchComponent';
|
||||||
|
@@ -17,6 +17,7 @@ export class AuthServiceStub {
|
|||||||
token: AuthTokenInfo = new AuthTokenInfo('token_test');
|
token: AuthTokenInfo = new AuthTokenInfo('token_test');
|
||||||
impersonating: string;
|
impersonating: string;
|
||||||
private _tokenExpired = false;
|
private _tokenExpired = false;
|
||||||
|
private _isExternalAuth = false;
|
||||||
private redirectUrl;
|
private redirectUrl;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -122,6 +123,13 @@ export class AuthServiceStub {
|
|||||||
checkAuthenticationCookie() {
|
checkAuthenticationCookie() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
setExternalAuthStatus(externalCookie: boolean) {
|
||||||
|
this._isExternalAuth = externalCookie;
|
||||||
|
}
|
||||||
|
|
||||||
|
isExternalAuthentication(): Observable<boolean> {
|
||||||
|
return observableOf(this._isExternalAuth);
|
||||||
|
}
|
||||||
|
|
||||||
retrieveAuthMethodsFromAuthStatus(status: AuthStatus) {
|
retrieveAuthMethodsFromAuthStatus(status: AuthStatus) {
|
||||||
return observableOf(authMethodsMock);
|
return observableOf(authMethodsMock);
|
||||||
|
@@ -26,16 +26,21 @@ import { AuthService } from '../../app/core/auth/auth.service';
|
|||||||
import { ThemeService } from '../../app/shared/theme-support/theme.service';
|
import { ThemeService } from '../../app/shared/theme-support/theme.service';
|
||||||
import { StoreAction, StoreActionTypes } from '../../app/store.actions';
|
import { StoreAction, StoreActionTypes } from '../../app/store.actions';
|
||||||
import { coreSelector } from '../../app/core/core.selectors';
|
import { coreSelector } from '../../app/core/core.selectors';
|
||||||
import { find, map } from 'rxjs/operators';
|
import { filter, find, map } from 'rxjs/operators';
|
||||||
import { isNotEmpty } from '../../app/shared/empty.util';
|
import { isNotEmpty } from '../../app/shared/empty.util';
|
||||||
import { logStartupMessage } from '../../../startup-message';
|
import { logStartupMessage } from '../../../startup-message';
|
||||||
import { MenuService } from '../../app/shared/menu/menu.service';
|
import { MenuService } from '../../app/shared/menu/menu.service';
|
||||||
|
import { RootDataService } from '../../app/core/data/root-data.service';
|
||||||
|
import { firstValueFrom, Subscription } from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs client-side initialization.
|
* Performs client-side initialization.
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BrowserInitService extends InitService {
|
export class BrowserInitService extends InitService {
|
||||||
|
|
||||||
|
sub: Subscription;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected store: Store<AppState>,
|
protected store: Store<AppState>,
|
||||||
protected correlationIdService: CorrelationIdService,
|
protected correlationIdService: CorrelationIdService,
|
||||||
@@ -51,6 +56,7 @@ export class BrowserInitService extends InitService {
|
|||||||
protected authService: AuthService,
|
protected authService: AuthService,
|
||||||
protected themeService: ThemeService,
|
protected themeService: ThemeService,
|
||||||
protected menuService: MenuService,
|
protected menuService: MenuService,
|
||||||
|
private rootDataService: RootDataService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
store,
|
store,
|
||||||
@@ -80,6 +86,7 @@ export class BrowserInitService extends InitService {
|
|||||||
return async () => {
|
return async () => {
|
||||||
await this.loadAppState();
|
await this.loadAppState();
|
||||||
this.checkAuthenticationToken();
|
this.checkAuthenticationToken();
|
||||||
|
this.externalAuthCheck();
|
||||||
this.initCorrelationId();
|
this.initCorrelationId();
|
||||||
|
|
||||||
this.checkEnvironment();
|
this.checkEnvironment();
|
||||||
@@ -134,4 +141,35 @@ export class BrowserInitService extends InitService {
|
|||||||
protected initGoogleAnalytics() {
|
protected initGoogleAnalytics() {
|
||||||
this.googleAnalyticsService.addTrackingIdToPage();
|
this.googleAnalyticsService.addTrackingIdToPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* During an external authentication flow invalidate the SSR transferState
|
||||||
|
* data in the cache. This allows the app to fetch fresh content.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private externalAuthCheck() {
|
||||||
|
|
||||||
|
this.sub = this.authService.isExternalAuthentication().pipe(
|
||||||
|
filter((externalAuth: boolean) => externalAuth)
|
||||||
|
).subscribe(() => {
|
||||||
|
// Clear the transferState data.
|
||||||
|
this.rootDataService.invalidateRootCache();
|
||||||
|
this.authService.setExternalAuthStatus(false);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.closeAuthCheckSubscription();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe the external authentication subscription
|
||||||
|
* when authentication is no longer blocking.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private closeAuthCheckSubscription() {
|
||||||
|
firstValueFrom(this.authenticationReady$()).then(() => {
|
||||||
|
this.sub.unsubscribe();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user