mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge branch 'main' into w2p-97184_theme-feedback_contribute-main
This commit is contained in:
50
.github/workflows/build.yml
vendored
50
.github/workflows/build.yml
vendored
@@ -68,7 +68,7 @@ jobs:
|
|||||||
# https://github.com/actions/cache/blob/main/examples.md#node---yarn
|
# https://github.com/actions/cache/blob/main/examples.md#node---yarn
|
||||||
- name: Get Yarn cache directory
|
- name: Get Yarn cache directory
|
||||||
id: yarn-cache-dir-path
|
id: yarn-cache-dir-path
|
||||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||||
- name: Cache Yarn dependencies
|
- name: Cache Yarn dependencies
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
@@ -93,12 +93,16 @@ jobs:
|
|||||||
- name: Run specs (unit tests)
|
- name: Run specs (unit tests)
|
||||||
run: yarn run test:headless
|
run: yarn run test:headless
|
||||||
|
|
||||||
|
# Upload code coverage report to artifact (for one version of Node only),
|
||||||
|
# so that it can be shared with the 'codecov' job (see below)
|
||||||
# NOTE: Angular CLI only supports code coverage for specs. See https://github.com/angular/angular-cli/issues/6286
|
# NOTE: Angular CLI only supports code coverage for specs. See https://github.com/angular/angular-cli/issues/6286
|
||||||
# Upload coverage reports to Codecov (for one version of Node only)
|
- name: Upload code coverage report to Artifact
|
||||||
# https://github.com/codecov/codecov-action
|
uses: actions/upload-artifact@v3
|
||||||
- name: Upload coverage to Codecov.io
|
if: matrix.node-version == '18.x'
|
||||||
uses: codecov/codecov-action@v3
|
with:
|
||||||
if: matrix.node-version == '16.x'
|
name: dspace-angular coverage report
|
||||||
|
path: 'coverage/dspace-angular/lcov.info'
|
||||||
|
retention-days: 14
|
||||||
|
|
||||||
# Using docker-compose start backend using CI configuration
|
# Using docker-compose start backend using CI configuration
|
||||||
# and load assetstore from a cached copy
|
# and load assetstore from a cached copy
|
||||||
@@ -112,11 +116,10 @@ jobs:
|
|||||||
# https://github.com/cypress-io/github-action
|
# https://github.com/cypress-io/github-action
|
||||||
# (NOTE: to run these e2e tests locally, just use 'ng e2e')
|
# (NOTE: to run these e2e tests locally, just use 'ng e2e')
|
||||||
- name: Run e2e tests (integration tests)
|
- name: Run e2e tests (integration tests)
|
||||||
uses: cypress-io/github-action@v4
|
uses: cypress-io/github-action@v5
|
||||||
with:
|
with:
|
||||||
# Run tests in Chrome, headless mode
|
# Run tests in Chrome, headless mode (default)
|
||||||
browser: chrome
|
browser: chrome
|
||||||
headless: true
|
|
||||||
# Start app before running tests (will be stopped automatically after tests finish)
|
# Start app before running tests (will be stopped automatically after tests finish)
|
||||||
start: yarn run serve:ssr
|
start: yarn run serve:ssr
|
||||||
# Wait for backend & frontend to be available
|
# Wait for backend & frontend to be available
|
||||||
@@ -176,3 +179,32 @@ jobs:
|
|||||||
|
|
||||||
- name: Shutdown Docker containers
|
- name: Shutdown Docker containers
|
||||||
run: docker-compose -f ./docker/docker-compose-ci.yml down
|
run: docker-compose -f ./docker/docker-compose-ci.yml down
|
||||||
|
|
||||||
|
# Codecov upload is a separate job in order to allow us to restart this separate from the entire build/test
|
||||||
|
# job above. This is necessary because Codecov uploads seem to randomly fail at times.
|
||||||
|
# See https://community.codecov.com/t/upload-issues-unable-to-locate-build-via-github-actions-api/3954
|
||||||
|
codecov:
|
||||||
|
# Must run after 'tests' job above
|
||||||
|
needs: tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# Download artifacts from previous 'tests' job
|
||||||
|
- name: Download coverage artifacts
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
|
||||||
|
# Now attempt upload to Codecov using its action.
|
||||||
|
# NOTE: We use a retry action to retry the Codecov upload if it fails the first time.
|
||||||
|
#
|
||||||
|
# Retry action: https://github.com/marketplace/actions/retry-action
|
||||||
|
# Codecov action: https://github.com/codecov/codecov-action
|
||||||
|
- name: Upload coverage to Codecov.io
|
||||||
|
uses: Wandalen/wretry.action@v1.0.36
|
||||||
|
with:
|
||||||
|
action: codecov/codecov-action@v3
|
||||||
|
# Try upload 5 times max
|
||||||
|
attempt_limit: 5
|
||||||
|
# Run again in 30 seconds
|
||||||
|
attempt_delay: 30000
|
||||||
|
2
.github/workflows/issue_opened.yml
vendored
2
.github/workflows/issue_opened.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
# Only add to project board if issue is flagged as "needs triage" or has no labels
|
# Only add to project board if issue is flagged as "needs triage" or has no labels
|
||||||
# NOTE: By default we flag new issues as "needs triage" in our issue template
|
# NOTE: By default we flag new issues as "needs triage" in our issue template
|
||||||
if: (contains(github.event.issue.labels.*.name, 'needs triage') || join(github.event.issue.labels.*.name) == '')
|
if: (contains(github.event.issue.labels.*.name, 'needs triage') || join(github.event.issue.labels.*.name) == '')
|
||||||
uses: actions/add-to-project@v0.3.0
|
uses: actions/add-to-project@v0.5.0
|
||||||
# Note, the authentication token below is an ORG level Secret.
|
# Note, the authentication token below is an ORG level Secret.
|
||||||
# It must be created/recreated manually via a personal access token with admin:org, project, public_repo permissions
|
# It must be created/recreated manually via a personal access token with admin:org, project, public_repo permissions
|
||||||
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token
|
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token#permissions-for-the-github_token
|
||||||
|
2
.github/workflows/label_merge_conflicts.yml
vendored
2
.github/workflows/label_merge_conflicts.yml
vendored
@@ -23,7 +23,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# See: https://github.com/prince-chrismc/label-merge-conflicts-action
|
# See: https://github.com/prince-chrismc/label-merge-conflicts-action
|
||||||
- name: Auto-label PRs with merge conflicts
|
- name: Auto-label PRs with merge conflicts
|
||||||
uses: prince-chrismc/label-merge-conflicts-action@v2
|
uses: prince-chrismc/label-merge-conflicts-action@v3
|
||||||
# Add "merge conflict" label if a merge conflict is detected. Remove it when resolved.
|
# Add "merge conflict" label if a merge conflict is detected. Remove it when resolved.
|
||||||
# Note, the authentication token is created automatically
|
# Note, the authentication token is created automatically
|
||||||
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token
|
# See: https://docs.github.com/en/actions/configuring-and-managing-workflows/authenticating-with-the-github_token
|
||||||
|
@@ -107,14 +107,14 @@ services:
|
|||||||
- '-c'
|
- '-c'
|
||||||
- |
|
- |
|
||||||
init-var-solr
|
init-var-solr
|
||||||
precreate-core authority /opt/solr/server/solr/configsets/dspace/authority
|
precreate-core authority /opt/solr/server/solr/configsets/authority
|
||||||
cp -r /opt/solr/server/solr/configsets/dspace/authority/* authority
|
cp -r /opt/solr/server/solr/configsets/authority/* authority
|
||||||
precreate-core oai /opt/solr/server/solr/configsets/dspace/oai
|
precreate-core oai /opt/solr/server/solr/configsets/oai
|
||||||
cp -r /opt/solr/server/solr/configsets/dspace/oai/* oai
|
cp -r /opt/solr/server/solr/configsets/oai/* oai
|
||||||
precreate-core search /opt/solr/server/solr/configsets/dspace/search
|
precreate-core search /opt/solr/server/solr/configsets/search
|
||||||
cp -r /opt/solr/server/solr/configsets/dspace/search/* search
|
cp -r /opt/solr/server/solr/configsets/search/* search
|
||||||
precreate-core statistics /opt/solr/server/solr/configsets/dspace/statistics
|
precreate-core statistics /opt/solr/server/solr/configsets/statistics
|
||||||
cp -r /opt/solr/server/solr/configsets/dspace/statistics/* statistics
|
cp -r /opt/solr/server/solr/configsets/statistics/* statistics
|
||||||
exec solr -f
|
exec solr -f
|
||||||
volumes:
|
volumes:
|
||||||
assetstore:
|
assetstore:
|
||||||
|
@@ -96,11 +96,17 @@ export class BatchImportPageComponent {
|
|||||||
if (isNotEmpty(rd.payload)) {
|
if (isNotEmpty(rd.payload)) {
|
||||||
this.router.navigateByUrl(getProcessDetailRoute(rd.payload.processId));
|
this.router.navigateByUrl(getProcessDetailRoute(rd.payload.processId));
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if (rd.statusCode === 413) {
|
||||||
|
const title = this.translate.get('process.new.notification.error.title');
|
||||||
|
const content = this.translate.get('process.new.notification.error.max-upload.content');
|
||||||
|
this.notificationsService.error(title, content);
|
||||||
} else {
|
} else {
|
||||||
const title = this.translate.get('process.new.notification.error.title');
|
const title = this.translate.get('process.new.notification.error.title');
|
||||||
const content = this.translate.get('process.new.notification.error.content');
|
const content = this.translate.get('process.new.notification.error.content');
|
||||||
this.notificationsService.error(title, content);
|
this.notificationsService.error(title, content);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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({
|
||||||
|
@@ -246,10 +246,10 @@ export abstract class BaseItemDataService extends IdentifiableDataService<Item>
|
|||||||
* Get the endpoint to move the item
|
* Get the endpoint to move the item
|
||||||
* @param itemId
|
* @param itemId
|
||||||
*/
|
*/
|
||||||
public getMoveItemEndpoint(itemId: string): Observable<string> {
|
public getMoveItemEndpoint(itemId: string, inheritPolicies: boolean): Observable<string> {
|
||||||
return this.halService.getEndpoint(this.linkPath).pipe(
|
return this.halService.getEndpoint(this.linkPath).pipe(
|
||||||
map((endpoint: string) => this.getIDHref(endpoint, itemId)),
|
map((endpoint: string) => this.getIDHref(endpoint, itemId)),
|
||||||
map((endpoint: string) => `${endpoint}/owningCollection`),
|
map((endpoint: string) => `${endpoint}/owningCollection?inheritPolicies=${inheritPolicies}`)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,14 +258,14 @@ export abstract class BaseItemDataService extends IdentifiableDataService<Item>
|
|||||||
* @param itemId
|
* @param itemId
|
||||||
* @param collection
|
* @param collection
|
||||||
*/
|
*/
|
||||||
public moveToCollection(itemId: string, collection: Collection): Observable<RemoteData<any>> {
|
public moveToCollection(itemId: string, collection: Collection, inheritPolicies: boolean): Observable<RemoteData<any>> {
|
||||||
const options: HttpOptions = Object.create({});
|
const options: HttpOptions = Object.create({});
|
||||||
let headers = new HttpHeaders();
|
let headers = new HttpHeaders();
|
||||||
headers = headers.append('Content-Type', 'text/uri-list');
|
headers = headers.append('Content-Type', 'text/uri-list');
|
||||||
options.headers = headers;
|
options.headers = headers;
|
||||||
|
|
||||||
const requestId = this.requestService.generateRequestId();
|
const requestId = this.requestService.generateRequestId();
|
||||||
const hrefObs = this.getMoveItemEndpoint(itemId);
|
const hrefObs = this.getMoveItemEndpoint(itemId, inheritPolicies);
|
||||||
|
|
||||||
hrefObs.pipe(
|
hrefObs.pipe(
|
||||||
find((href: string) => hasValue(href)),
|
find((href: string) => hasValue(href)),
|
||||||
|
@@ -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';
|
||||||
|
@@ -134,9 +134,10 @@ describe('ItemMoveComponent', () => {
|
|||||||
});
|
});
|
||||||
comp.selectedCollectionName = 'selected-collection-id';
|
comp.selectedCollectionName = 'selected-collection-id';
|
||||||
comp.selectedCollection = collection1;
|
comp.selectedCollection = collection1;
|
||||||
|
comp.inheritPolicies = false;
|
||||||
comp.moveToCollection();
|
comp.moveToCollection();
|
||||||
|
|
||||||
expect(itemDataService.moveToCollection).toHaveBeenCalledWith('item-id', collection1);
|
expect(itemDataService.moveToCollection).toHaveBeenCalledWith('item-id', collection1, false);
|
||||||
});
|
});
|
||||||
it('should call notificationsService success message on success', () => {
|
it('should call notificationsService success message on success', () => {
|
||||||
comp.moveToCollection();
|
comp.moveToCollection();
|
||||||
|
@@ -104,7 +104,7 @@ export class ItemMoveComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
moveToCollection() {
|
moveToCollection() {
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
const move$ = this.itemDataService.moveToCollection(this.item.id, this.selectedCollection)
|
const move$ = this.itemDataService.moveToCollection(this.item.id, this.selectedCollection, this.inheritPolicies)
|
||||||
.pipe(getFirstCompletedRemoteData());
|
.pipe(getFirstCompletedRemoteData());
|
||||||
|
|
||||||
move$.subscribe((response: RemoteData<any>) => {
|
move$.subscribe((response: RemoteData<any>) => {
|
||||||
|
@@ -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) => {
|
|
||||||
let srclang: string = media.bitstream.name.slice(-6, -4).toLowerCase();
|
|
||||||
capInfos.push(new CaptionInfo(
|
capInfos.push(new CaptionInfo(
|
||||||
media.bitstream._links.content.href,
|
media._links.content.href,
|
||||||
srclang,
|
srclang,
|
||||||
languageHelper[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'];
|
||||||
|
|
||||||
|
@@ -9,7 +9,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
DsoEditMenuExpandableSectionComponent
|
DsoEditMenuExpandableSectionComponent
|
||||||
} from '../dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component';
|
} from '../dso-page/dso-edit-menu/dso-edit-expandable-menu-section/dso-edit-menu-expandable-section.component';
|
||||||
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbDropdownModule, NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
const COMPONENTS = [
|
const COMPONENTS = [
|
||||||
DsoEditMenuComponent,
|
DsoEditMenuComponent,
|
||||||
@@ -25,6 +25,7 @@ const MODULES = [
|
|||||||
RouterModule,
|
RouterModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
NgbTooltipModule,
|
NgbTooltipModule,
|
||||||
|
NgbDropdownModule,
|
||||||
];
|
];
|
||||||
const PROVIDERS = [
|
const PROVIDERS = [
|
||||||
|
|
||||||
|
@@ -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 { SearchResult } from '../../../search/models/search-result.model';
|
import { SearchResult } from '../../../search/models/search-result.model';
|
||||||
import { Context } from 'src/app/core/shared/context.model';
|
import { Context } from 'src/app/core/shared/context.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() context: Context;
|
@Input() context: Context;
|
||||||
|
|
||||||
@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);
|
||||||
|
@@ -3257,6 +3257,8 @@
|
|||||||
|
|
||||||
"process.new.notification.error.content": "An error occurred while creating this process",
|
"process.new.notification.error.content": "An error occurred while creating this process",
|
||||||
|
|
||||||
|
"process.new.notification.error.max-upload.content": "The file exceeds the maximum upload size",
|
||||||
|
|
||||||
"process.new.header": "Create a new process",
|
"process.new.header": "Create a new process",
|
||||||
|
|
||||||
"process.new.title": "Create a new process",
|
"process.new.title": "Create a new process",
|
||||||
|
@@ -752,8 +752,8 @@
|
|||||||
// "admin.access-control.groups.form.tooltip.editGroup.addEpeople": "To add or remove an EPerson to/from this group, either click the 'Browse All' button or use the search bar below to search for users (use the dropdown to the left of the search bar to choose whether to search by metadata or by email). Then click the plus icon for each user you wish to add in the list below, or the trash can icon for each user you wish to remove. The list below may have several pages: use the page controls below the list to navigate to the next pages. Once you are ready, save your changes by clicking the 'Save' button in the top section.",
|
// "admin.access-control.groups.form.tooltip.editGroup.addEpeople": "To add or remove an EPerson to/from this group, either click the 'Browse All' button or use the search bar below to search for users (use the dropdown to the left of the search bar to choose whether to search by metadata or by email). Then click the plus icon for each user you wish to add in the list below, or the trash can icon for each user you wish to remove. The list below may have several pages: use the page controls below the list to navigate to the next pages. Once you are ready, save your changes by clicking the 'Save' button in the top section.",
|
||||||
"admin.access-control.groups.form.tooltip.editGroup.addEpeople": "Para agregar o remover una persona en este grupo, pulse el botón ‘Examinar todo’ o utilice la barra de búsqueda de abajo para buscar los usuario (Use el desplegable que se encuentra a la derecha de la barra de búsqueda para seleccionar entre buscar por metadato o por correo electrónico). Posteriormente pulse el botón con el icono más por cada usuario que desea agregar a la lista, o el icono de papelera por cada usuario que desea remover. La lista puede contener varias páginas: utilice los controles de paginación debajo de la lista, para navegar a las siguientes páginas. Cuando haya finalizado, guarde sus cambios pulsando el botón ‘Guardar’ ubicado en la parte superior de la sección.",
|
"admin.access-control.groups.form.tooltip.editGroup.addEpeople": "Para agregar o remover una persona en este grupo, pulse el botón ‘Examinar todo’ o utilice la barra de búsqueda de abajo para buscar los usuario (Use el desplegable que se encuentra a la derecha de la barra de búsqueda para seleccionar entre buscar por metadato o por correo electrónico). Posteriormente pulse el botón con el icono más por cada usuario que desea agregar a la lista, o el icono de papelera por cada usuario que desea remover. La lista puede contener varias páginas: utilice los controles de paginación debajo de la lista, para navegar a las siguientes páginas. Cuando haya finalizado, guarde sus cambios pulsando el botón ‘Guardar’ ubicado en la parte superior de la sección.",
|
||||||
|
|
||||||
// "admin.access-control.groups.form.tooltip.editGroup.addSubgroups": "To add or remove a Subgroup to/from this group, either click the 'Browse All' button or use the search bar below to search for subgroups. Then click the plus icon for each subgroup you wish to add in the list below, or the trash can icon for each subgroup you wish to remove. The list below may have several pages: use the page controls below the list to navigate to the next pages. Once you are ready, save your changes by clicking the 'Save' button in the top section.",
|
// "admin.access-control.groups.form.tooltip.editGroup.addSubgroups": "To add or remove a Subgroup to/from this group, either click the 'Browse All' button or use the search bar below to search for users. Then click the plus icon for each user you wish to add in the list below, or the trash can icon for each user you wish to remove. The list below may have several pages: use the page controls below the list to navigate to the next pages. Once you are ready, save your changes by clicking the 'Save' button in the top section.",
|
||||||
"admin.access-control.groups.form.tooltip.editGroup.addSubgroups": "Para agregar o remover un sub-grupo en este grupo, pulse el botón ‘Examinar todo’ o utilice la barra de búsqueda de abajo para buscar los subgrupos- Posteriormente pulse el botón con el icono más por cada sub-grupo que desea agregar a la lista, o el icono de papelera por cada sub-grupo que desea remover. La lista puede contener varias páginas: utilice los controles de paginación debajo de la lista, para navegar a las siguientes páginas. Cuando haya finalizado, guarde sus cambios pulsando el botón ‘Guardar’ ubicado en la parte superior de la sección.",
|
"admin.access-control.groups.form.tooltip.editGroup.addSubgroups": "Para agregar o remover un sub-grupo en este grupo, pulse el botón 'Examinar todo' o utilice la barra de búsqueda de abajo para buscar los subgrupos- Posteriormente pulse el botón con el icono más por cada sub-grupo que desea agregar a la lista, o el icono de papelera por cada sub-grupo que desea remover. La lista puede contener varias páginas: utilice los controles de paginación debajo de la lista, para navegar a las siguientes páginas. Cuando haya finalizado, guarde sus cambios pulsando el botón ‘Guardar’ ubicado en la parte superior de la sección.",
|
||||||
|
|
||||||
// "admin.search.breadcrumbs": "Administrative Search",
|
// "admin.search.breadcrumbs": "Administrative Search",
|
||||||
"admin.search.breadcrumbs": "Búsqueda administrativa",
|
"admin.search.breadcrumbs": "Búsqueda administrativa",
|
||||||
@@ -1891,12 +1891,10 @@
|
|||||||
|
|
||||||
|
|
||||||
// "comcol-role.edit.scorereviewers.name": "Score Reviewers",
|
// "comcol-role.edit.scorereviewers.name": "Score Reviewers",
|
||||||
// TODO New key - Add a translation
|
"comcol-role.edit.scorereviewers.name": "Revisores de puntuación",
|
||||||
"comcol-role.edit.scorereviewers.name": "Score Reviewers",
|
|
||||||
|
|
||||||
// "comcol-role.edit.scorereviewers.description": "Reviewers are able to give a score to incoming submissions, this will define whether the submission will be rejected or not.",
|
// "comcol-role.edit.scorereviewers.description": "Reviewers are able to give a score to incoming submissions, this will define whether the submission will be rejected or not.",
|
||||||
// TODO New key - Add a translation
|
"comcol-role.edit.scorereviewers.description": "Los revisores pueden dar una puntuación a un envío entrante, esto definirá si el envío será rechazado o no.",
|
||||||
"comcol-role.edit.scorereviewers.description": "Reviewers are able to give a score to incoming submissions, this will define whether the submission will be rejected or not.",
|
|
||||||
|
|
||||||
// "community.form.abstract": "Short Description",
|
// "community.form.abstract": "Short Description",
|
||||||
"community.form.abstract": "Breve descripción",
|
"community.form.abstract": "Breve descripción",
|
||||||
@@ -2588,6 +2586,9 @@
|
|||||||
// "form.submit": "Save",
|
// "form.submit": "Save",
|
||||||
"form.submit": "Guardar",
|
"form.submit": "Guardar",
|
||||||
|
|
||||||
|
// "form.create": "Create",
|
||||||
|
"form.create": "Crear",
|
||||||
|
|
||||||
// "form.repeatable.sort.tip": "Drop the item in the new position",
|
// "form.repeatable.sort.tip": "Drop the item in the new position",
|
||||||
"form.repeatable.sort.tip": "Suelte el ítem en la nueva posición",
|
"form.repeatable.sort.tip": "Suelte el ítem en la nueva posición",
|
||||||
|
|
||||||
@@ -3953,8 +3954,8 @@
|
|||||||
// "itemtemplate.edit.metadata.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button",
|
// "itemtemplate.edit.metadata.notifications.discarded.content": "Your changes were discarded. To reinstate your changes click the 'Undo' button",
|
||||||
"itemtemplate.edit.metadata.notifications.discarded.content": "Sus cambios fueron descartados. Para recuperar sus cambios pulse el botón 'Deshacer'",
|
"itemtemplate.edit.metadata.notifications.discarded.content": "Sus cambios fueron descartados. Para recuperar sus cambios pulse el botón 'Deshacer'",
|
||||||
|
|
||||||
// "itemtemplate.edit.metadata.notifications.discarded.title": "Changed discarded",
|
// "itemtemplate.edit.metadata.notifications.discarded.title": "Changes discarded",
|
||||||
"itemtemplate.edit.metadata.notifications.discarded.title": "Cambio descartado",
|
"itemtemplate.edit.metadata.notifications.discarded.title": "Cambios descartados",
|
||||||
|
|
||||||
// "itemtemplate.edit.metadata.notifications.error.title": "An error occurred",
|
// "itemtemplate.edit.metadata.notifications.error.title": "An error occurred",
|
||||||
"itemtemplate.edit.metadata.notifications.error.title": "Ocurrió un error",
|
"itemtemplate.edit.metadata.notifications.error.title": "Ocurrió un error",
|
||||||
@@ -3968,8 +3969,8 @@
|
|||||||
// "itemtemplate.edit.metadata.notifications.outdated.content": "The item template you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts",
|
// "itemtemplate.edit.metadata.notifications.outdated.content": "The item template you're currently working on has been changed by another user. Your current changes are discarded to prevent conflicts",
|
||||||
"itemtemplate.edit.metadata.notifications.outdated.content": "La plantilla de ítem en la que usted esta trabajando actualmente ha sido cambiada por otro usuario. Sus cambios han sido descartados para evitar conflictos",
|
"itemtemplate.edit.metadata.notifications.outdated.content": "La plantilla de ítem en la que usted esta trabajando actualmente ha sido cambiada por otro usuario. Sus cambios han sido descartados para evitar conflictos",
|
||||||
|
|
||||||
// "itemtemplate.edit.metadata.notifications.outdated.title": "Changed outdated",
|
// "itemtemplate.edit.metadata.notifications.outdated.title": "Changes outdated",
|
||||||
"itemtemplate.edit.metadata.notifications.outdated.title": "Cambiado obsoleto",
|
"itemtemplate.edit.metadata.notifications.outdated.title": "Cambios obsoletos",
|
||||||
|
|
||||||
// "itemtemplate.edit.metadata.notifications.saved.content": "Your changes to this item template's metadata were saved.",
|
// "itemtemplate.edit.metadata.notifications.saved.content": "Your changes to this item template's metadata were saved.",
|
||||||
"itemtemplate.edit.metadata.notifications.saved.content": "Sus cambios a los metadatos de esta plantilla de ítem han sido guardados.",
|
"itemtemplate.edit.metadata.notifications.saved.content": "Sus cambios a los metadatos de esta plantilla de ítem han sido guardados.",
|
||||||
@@ -5211,7 +5212,6 @@
|
|||||||
|
|
||||||
// "register-page.registration.google-recaptcha.must-accept-cookies": "In order to register you must accept the <b>Registration and Password recovery</b> (Google reCaptcha) cookies.",
|
// "register-page.registration.google-recaptcha.must-accept-cookies": "In order to register you must accept the <b>Registration and Password recovery</b> (Google reCaptcha) cookies.",
|
||||||
"register-page.registration.google-recaptcha.must-accept-cookies": "Para registrarse debe aceptar las cookies de <b>Registro y recuperación de contraseña</b> (Google reCaptcha).",
|
"register-page.registration.google-recaptcha.must-accept-cookies": "Para registrarse debe aceptar las cookies de <b>Registro y recuperación de contraseña</b> (Google reCaptcha).",
|
||||||
|
|
||||||
// "register-page.registration.error.maildomain": "This email address is not on the list of domains who can register. Allowed domains are {{ domains }}",
|
// "register-page.registration.error.maildomain": "This email address is not on the list of domains who can register. Allowed domains are {{ domains }}",
|
||||||
"register-page.registration.error.maildomain": "Este correo electrónico no esta en la lista de dominios que pueden registrarse. Los dominios permitidos son {{ domains }}",
|
"register-page.registration.error.maildomain": "Este correo electrónico no esta en la lista de dominios que pueden registrarse. Los dominios permitidos son {{ domains }}",
|
||||||
|
|
||||||
@@ -5226,7 +5226,6 @@
|
|||||||
|
|
||||||
// "register-page.registration.google-recaptcha.notification.message.expired": "Verification expired. Please verify again.",
|
// "register-page.registration.google-recaptcha.notification.message.expired": "Verification expired. Please verify again.",
|
||||||
"register-page.registration.google-recaptcha.notification.message.expired": "Verificación caducada. Verifique de nuevo.",
|
"register-page.registration.google-recaptcha.notification.message.expired": "Verificación caducada. Verifique de nuevo.",
|
||||||
|
|
||||||
// "register-page.registration.info.maildomain": "Accounts can be registered for mail addresses of the domains",
|
// "register-page.registration.info.maildomain": "Accounts can be registered for mail addresses of the domains",
|
||||||
"register-page.registration.info.maildomain": "Las cuentas pueden registrarse para las direcciones de correo de los dominios",
|
"register-page.registration.info.maildomain": "Las cuentas pueden registrarse para las direcciones de correo de los dominios",
|
||||||
|
|
||||||
@@ -5943,6 +5942,8 @@
|
|||||||
// "statistics.table.header.views": "Views",
|
// "statistics.table.header.views": "Views",
|
||||||
"statistics.table.header.views": "Visitas",
|
"statistics.table.header.views": "Visitas",
|
||||||
|
|
||||||
|
// "statistics.table.no-name": "(object name could not be loaded)",
|
||||||
|
"statistics.table.no-name": "(el nombre del objeto no pudo ser cargado)",
|
||||||
|
|
||||||
|
|
||||||
// "submission.edit.breadcrumbs": "Edit Submission",
|
// "submission.edit.breadcrumbs": "Edit Submission",
|
||||||
@@ -6936,8 +6937,8 @@
|
|||||||
// "submission.workflow.generic.delete": "Delete",
|
// "submission.workflow.generic.delete": "Delete",
|
||||||
"submission.workflow.generic.delete": "Eliminar",
|
"submission.workflow.generic.delete": "Eliminar",
|
||||||
|
|
||||||
// "submission.workflow.generic.delete-help": "If you would to discard this item, select \"Delete\". You will then be asked to confirm it.",
|
// "submission.workflow.generic.delete-help": "Select this option to discard this item. You will then be asked to confirm it.",
|
||||||
"submission.workflow.generic.delete-help": "Si desea descartar este ítem, seleccione \"Eliminar\". Luego se le pedirá que lo confirme.",
|
"submission.workflow.generic.delete-help": "Seleccione esta opción para descartar este ítem. A contunuación se le pedirá confirmación.",
|
||||||
|
|
||||||
// "submission.workflow.generic.edit": "Edit",
|
// "submission.workflow.generic.edit": "Edit",
|
||||||
"submission.workflow.generic.edit": "Editar",
|
"submission.workflow.generic.edit": "Editar",
|
||||||
@@ -7089,7 +7090,7 @@
|
|||||||
// "subscriptions.modal.new-subscription-form.type.content": "Content",
|
// "subscriptions.modal.new-subscription-form.type.content": "Content",
|
||||||
"subscriptions.modal.new-subscription-form.type.content": "Contenido",
|
"subscriptions.modal.new-subscription-form.type.content": "Contenido",
|
||||||
|
|
||||||
// "subscriptions.modal.new-subscription-form.frequency.D": "Diario",
|
// "subscriptions.modal.new-subscription-form.frequency.D": "Daily",
|
||||||
"subscriptions.modal.new-subscription-form.frequency.D": "Diario",
|
"subscriptions.modal.new-subscription-form.frequency.D": "Diario",
|
||||||
|
|
||||||
// "subscriptions.modal.new-subscription-form.frequency.W": "Weekly",
|
// "subscriptions.modal.new-subscription-form.frequency.W": "Weekly",
|
||||||
@@ -7345,8 +7346,8 @@
|
|||||||
"workflow-item.selectrevieweraction.button.confirm": "Confirmar",
|
"workflow-item.selectrevieweraction.button.confirm": "Confirmar",
|
||||||
|
|
||||||
|
|
||||||
// "workflow-item.scorereviewaction.notification.success.title": "Revisar evaluación",
|
// "workflow-item.scorereviewaction.notification.success.title": "Rating review",
|
||||||
"workflow-item.scorereviewaction.notification.success.title": "Revisar evaluación",
|
"workflow-item.scorereviewaction.notification.success.title": "Revisión de evaluación",
|
||||||
|
|
||||||
// "workflow-item.scorereviewaction.notification.success.content": "The rating for this item workflow item has been successfully submitted",
|
// "workflow-item.scorereviewaction.notification.success.content": "The rating for this item workflow item has been successfully submitted",
|
||||||
"workflow-item.scorereviewaction.notification.success.content": "La evaluación para este ítem ha sido enviada exitosamente",
|
"workflow-item.scorereviewaction.notification.success.content": "La evaluación para este ítem ha sido enviada exitosamente",
|
||||||
|
@@ -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