mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge remote-tracking branch '4Science-bitbucket/main' into CST-5337
This commit is contained in:
@@ -6,7 +6,8 @@
|
|||||||
"eslint-plugin-import",
|
"eslint-plugin-import",
|
||||||
"eslint-plugin-jsdoc",
|
"eslint-plugin-jsdoc",
|
||||||
"eslint-plugin-deprecation",
|
"eslint-plugin-deprecation",
|
||||||
"eslint-plugin-unused-imports"
|
"unused-imports",
|
||||||
|
"eslint-plugin-lodash"
|
||||||
],
|
],
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
||||||
@@ -202,7 +203,13 @@
|
|||||||
"deprecation/deprecation": "warn",
|
"deprecation/deprecation": "warn",
|
||||||
|
|
||||||
"import/order": "off",
|
"import/order": "off",
|
||||||
"import/no-deprecated": "warn"
|
"import/no-deprecated": "warn",
|
||||||
|
"import/no-namespace": "error",
|
||||||
|
"unused-imports/no-unused-imports": "error",
|
||||||
|
"lodash/import-scope": [
|
||||||
|
"error",
|
||||||
|
"method"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
16
.gitattributes
vendored
16
.gitattributes
vendored
@@ -1,2 +1,16 @@
|
|||||||
# Auto detect text files and perform LF normalization
|
# By default, auto detect text files and perform LF normalization
|
||||||
|
# This ensures code is always checked in with LF line endings
|
||||||
* text=auto
|
* text=auto
|
||||||
|
|
||||||
|
# JS and TS files must always use LF for Angular tools to work
|
||||||
|
# Some Angular tools expect LF line endings, even on Windows.
|
||||||
|
# This ensures Windows always checks out these files with LF line endings
|
||||||
|
# We've copied many of these rules from https://github.com/angular/angular-cli/
|
||||||
|
*.js eol=lf
|
||||||
|
*.ts eol=lf
|
||||||
|
*.json eol=lf
|
||||||
|
*.json5 eol=lf
|
||||||
|
*.css eol=lf
|
||||||
|
*.scss eol=lf
|
||||||
|
*.html eol=lf
|
||||||
|
*.svg eol=lf
|
17
.github/workflows/build.yml
vendored
17
.github/workflows/build.yml
vendored
@@ -6,6 +6,9 @@ name: Build
|
|||||||
# Run this Build for all pushes / PRs to current branch
|
# Run this Build for all pushes / PRs to current branch
|
||||||
on: [push, pull_request]
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
tests:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -29,11 +32,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# https://github.com/actions/checkout
|
# https://github.com/actions/checkout
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
# https://github.com/actions/setup-node
|
# https://github.com/actions/setup-node
|
||||||
- name: Install Node.js ${{ matrix.node-version }}
|
- name: Install Node.js ${{ matrix.node-version }}
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
@@ -58,7 +61,7 @@ jobs:
|
|||||||
id: yarn-cache-dir-path
|
id: yarn-cache-dir-path
|
||||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||||
- name: Cache Yarn dependencies
|
- name: Cache Yarn dependencies
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
# Cache entire Yarn cache directory (see previous step)
|
# Cache entire Yarn cache directory (see previous step)
|
||||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||||
@@ -85,7 +88,7 @@ jobs:
|
|||||||
# Upload coverage reports to Codecov (for one version of Node only)
|
# Upload coverage reports to Codecov (for one version of Node only)
|
||||||
# https://github.com/codecov/codecov-action
|
# https://github.com/codecov/codecov-action
|
||||||
- name: Upload coverage to Codecov.io
|
- name: Upload coverage to Codecov.io
|
||||||
uses: codecov/codecov-action@v2
|
uses: codecov/codecov-action@v3
|
||||||
if: matrix.node-version == '16.x'
|
if: matrix.node-version == '16.x'
|
||||||
|
|
||||||
# Using docker-compose start backend using CI configuration
|
# Using docker-compose start backend using CI configuration
|
||||||
@@ -100,7 +103,7 @@ 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@v2
|
uses: cypress-io/github-action@v4
|
||||||
with:
|
with:
|
||||||
# Run tests in Chrome, headless mode
|
# Run tests in Chrome, headless mode
|
||||||
browser: chrome
|
browser: chrome
|
||||||
@@ -116,7 +119,7 @@ jobs:
|
|||||||
# Cypress always creates a video of all e2e tests (whether they succeeded or failed)
|
# Cypress always creates a video of all e2e tests (whether they succeeded or failed)
|
||||||
# Save those in an Artifact
|
# Save those in an Artifact
|
||||||
- name: Upload e2e test videos to Artifacts
|
- name: Upload e2e test videos to Artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: e2e-test-videos
|
name: e2e-test-videos
|
||||||
@@ -125,7 +128,7 @@ jobs:
|
|||||||
# If e2e tests fail, Cypress creates a screenshot of what happened
|
# If e2e tests fail, Cypress creates a screenshot of what happened
|
||||||
# Save those in an Artifact
|
# Save those in an Artifact
|
||||||
- name: Upload e2e test failure screenshots to Artifacts
|
- name: Upload e2e test failure screenshots to Artifacts
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
if: failure()
|
if: failure()
|
||||||
with:
|
with:
|
||||||
name: e2e-test-screenshots
|
name: e2e-test-screenshots
|
||||||
|
13
.github/workflows/docker.yml
vendored
13
.github/workflows/docker.yml
vendored
@@ -12,6 +12,9 @@ on:
|
|||||||
- 'dspace-**'
|
- 'dspace-**'
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read # to fetch code (actions/checkout)
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker:
|
||||||
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace-angular'
|
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace-angular'
|
||||||
@@ -39,11 +42,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# https://github.com/actions/checkout
|
# https://github.com/actions/checkout
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
# https://github.com/docker/setup-buildx-action
|
# https://github.com/docker/setup-buildx-action
|
||||||
- name: Setup Docker Buildx
|
- name: Setup Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
|
|
||||||
# https://github.com/docker/setup-qemu-action
|
# https://github.com/docker/setup-qemu-action
|
||||||
- name: Set up QEMU emulation to build for multiple architectures
|
- name: Set up QEMU emulation to build for multiple architectures
|
||||||
@@ -53,7 +56,7 @@ jobs:
|
|||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
# Only login if not a PR, as PRs only trigger a Docker build and not a push
|
# Only login if not a PR, as PRs only trigger a Docker build and not a push
|
||||||
if: github.event_name != 'pull_request'
|
if: github.event_name != 'pull_request'
|
||||||
uses: docker/login-action@v1
|
uses: docker/login-action@v2
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||||
@@ -65,7 +68,7 @@ jobs:
|
|||||||
# Get Metadata for docker_build step below
|
# Get Metadata for docker_build step below
|
||||||
- name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-angular' image
|
- name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-angular' image
|
||||||
id: meta_build
|
id: meta_build
|
||||||
uses: docker/metadata-action@v3
|
uses: docker/metadata-action@v4
|
||||||
with:
|
with:
|
||||||
images: dspace/dspace-angular
|
images: dspace/dspace-angular
|
||||||
tags: ${{ env.IMAGE_TAGS }}
|
tags: ${{ env.IMAGE_TAGS }}
|
||||||
@@ -74,7 +77,7 @@ jobs:
|
|||||||
# https://github.com/docker/build-push-action
|
# https://github.com/docker/build-push-action
|
||||||
- name: Build and push 'dspace-angular' image
|
- name: Build and push 'dspace-angular' image
|
||||||
id: docker_build
|
id: docker_build
|
||||||
uses: docker/build-push-action@v2
|
uses: docker/build-push-action@v3
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
|
17
.github/workflows/issue_opened.yml
vendored
17
.github/workflows/issue_opened.yml
vendored
@@ -5,25 +5,22 @@ on:
|
|||||||
issues:
|
issues:
|
||||||
types: [opened]
|
types: [opened]
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
jobs:
|
jobs:
|
||||||
automation:
|
automation:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
# Add the new issue to a project board, if it needs triage
|
# Add the new issue to a project board, if it needs triage
|
||||||
# See https://github.com/marketplace/actions/create-project-card-action
|
# See https://github.com/actions/add-to-project
|
||||||
- name: Add issue to project board
|
- name: Add issue to triage board
|
||||||
# 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: technote-space/create-project-card-action@v1
|
uses: actions/add-to-project@v0.3.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 "public_repo" and "admin:org" 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
|
||||||
# This is necessary because the "DSpace Backlog" project is an org level project (i.e. not repo specific)
|
# This is necessary because the "DSpace Backlog" project is an org level project (i.e. not repo specific)
|
||||||
with:
|
with:
|
||||||
GITHUB_TOKEN: ${{ secrets.ORG_PROJECT_TOKEN }}
|
github-token: ${{ secrets.TRIAGE_PROJECT_TOKEN }}
|
||||||
PROJECT: DSpace Backlog
|
project-url: https://github.com/orgs/DSpace/projects/24
|
||||||
COLUMN: Triage
|
|
||||||
CHECK_ORG_PROJECT: true
|
|
||||||
# Ignore errors
|
|
||||||
continue-on-error: true
|
|
||||||
|
27
.github/workflows/label_merge_conflicts.yml
vendored
27
.github/workflows/label_merge_conflicts.yml
vendored
@@ -5,21 +5,32 @@ name: Check for merge conflicts
|
|||||||
# NOTE: This means merge conflicts are only checked for when a PR is merged to main.
|
# NOTE: This means merge conflicts are only checked for when a PR is merged to main.
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches: [ main ]
|
||||||
- main
|
# So that the `conflict_label_name` is removed if conflicts are resolved,
|
||||||
|
# we allow this to run for `pull_request_target` so that github secrets are available.
|
||||||
|
pull_request_target:
|
||||||
|
types: [ synchronize ]
|
||||||
|
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
triage:
|
triage:
|
||||||
|
# Ensure this job never runs on forked repos. It's only executed for 'dspace/dspace-angular'
|
||||||
|
if: github.repository == 'dspace/dspace-angular'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
# See: https://github.com/mschilde/auto-label-merge-conflicts/
|
# 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: mschilde/auto-label-merge-conflicts@v2.0
|
uses: prince-chrismc/label-merge-conflicts-action@v2
|
||||||
# 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
|
||||||
with:
|
with:
|
||||||
CONFLICT_LABEL_NAME: 'merge conflict'
|
conflict_label_name: 'merge conflict'
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
# Ignore errors
|
conflict_comment: |
|
||||||
continue-on-error: true
|
Hi @${author},
|
||||||
|
Conflicts have been detected against the base branch.
|
||||||
|
Please [resolve these conflicts](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/addressing-merge-conflicts/about-merge-conflicts) as soon as you can. Thanks!
|
@@ -179,7 +179,7 @@ If needing to update default configurations values for production, update local
|
|||||||
|
|
||||||
- Update `environment.production.ts` file in `src/environment/` for a `production` environment;
|
- Update `environment.production.ts` file in `src/environment/` for a `production` environment;
|
||||||
|
|
||||||
The environment object is provided for use as import in code and is extended with he runtime configuration on bootstrap of the application.
|
The environment object is provided for use as import in code and is extended with the runtime configuration on bootstrap of the application.
|
||||||
|
|
||||||
> Take caution moving runtime configs into the buildtime configuration. They will be overwritten by what is defined in the runtime config on bootstrap.
|
> Take caution moving runtime configs into the buildtime configuration. They will be overwritten by what is defined in the runtime config on bootstrap.
|
||||||
|
|
||||||
@@ -351,7 +351,7 @@ Documentation
|
|||||||
|
|
||||||
Official DSpace documentation is available in the DSpace wiki at https://wiki.lyrasis.org/display/DSDOC7x/
|
Official DSpace documentation is available in the DSpace wiki at https://wiki.lyrasis.org/display/DSDOC7x/
|
||||||
|
|
||||||
Some UI specific configuration documentation is also found in the [`./docs`](docs) folder of htis codebase.
|
Some UI specific configuration documentation is also found in the [`./docs`](docs) folder of this codebase.
|
||||||
|
|
||||||
### Building code documentation
|
### Building code documentation
|
||||||
|
|
||||||
|
@@ -14,6 +14,8 @@ ui:
|
|||||||
rateLimiter:
|
rateLimiter:
|
||||||
windowMs: 60000 # 1 minute
|
windowMs: 60000 # 1 minute
|
||||||
max: 500 # limit each IP to 500 requests per windowMs
|
max: 500 # limit each IP to 500 requests per windowMs
|
||||||
|
# Trust X-FORWARDED-* headers from proxies (default = true)
|
||||||
|
useProxies: true
|
||||||
|
|
||||||
# The REST API server settings
|
# The REST API server settings
|
||||||
# NOTE: these settings define which (publicly available) REST API to use. They are usually
|
# NOTE: these settings define which (publicly available) REST API to use. They are usually
|
||||||
@@ -150,12 +152,24 @@ languages:
|
|||||||
- code: fi
|
- code: fi
|
||||||
label: Suomi
|
label: Suomi
|
||||||
active: true
|
active: true
|
||||||
|
- code: sv
|
||||||
|
label: Svenska
|
||||||
|
active: true
|
||||||
- code: tr
|
- code: tr
|
||||||
label: Türkçe
|
label: Türkçe
|
||||||
active: true
|
active: true
|
||||||
|
- code: kk
|
||||||
|
label: Қазақ
|
||||||
|
active: true
|
||||||
- code: bn
|
- code: bn
|
||||||
label: বাংলা
|
label: বাংলা
|
||||||
active: true
|
active: true
|
||||||
|
- code: hi
|
||||||
|
label: हिंदी
|
||||||
|
active: true
|
||||||
|
- code: el
|
||||||
|
label: Ελληνικά
|
||||||
|
active: true
|
||||||
|
|
||||||
# Browse-By Pages
|
# Browse-By Pages
|
||||||
browseBy:
|
browseBy:
|
||||||
@@ -165,6 +179,27 @@ browseBy:
|
|||||||
fiveYearLimit: 30
|
fiveYearLimit: 30
|
||||||
# The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items)
|
# The absolute lowest year to display in the dropdown (only used when no lowest date can be found for all items)
|
||||||
defaultLowerLimit: 1900
|
defaultLowerLimit: 1900
|
||||||
|
# If true, thumbnail images for items will be added to BOTH search and browse result lists.
|
||||||
|
showThumbnails: true
|
||||||
|
# The number of entries in a paginated browse results list.
|
||||||
|
# Rounded to the nearest size in the list of selectable sizes on the
|
||||||
|
# settings menu.
|
||||||
|
pageSize: 20
|
||||||
|
|
||||||
|
communityList:
|
||||||
|
# No. of communities to list per expansion (show more)
|
||||||
|
pageSize: 20
|
||||||
|
|
||||||
|
homePage:
|
||||||
|
recentSubmissions:
|
||||||
|
# The number of item showing in recent submission components
|
||||||
|
pageSize: 5
|
||||||
|
# Sort record of recent submission
|
||||||
|
sortField: 'dc.date.accessioned'
|
||||||
|
topLevelCommunityList:
|
||||||
|
# No. of communities to list per page on the home page
|
||||||
|
# This will always round to the nearest number from the list of page sizes. e.g. if you set it to 7 it'll use 10
|
||||||
|
pageSize: 5
|
||||||
|
|
||||||
# Item Config
|
# Item Config
|
||||||
item:
|
item:
|
||||||
@@ -240,7 +275,7 @@ themes:
|
|||||||
|
|
||||||
# The default bundles that should always be displayed as suggestions when you upload a new bundle
|
# The default bundles that should always be displayed as suggestions when you upload a new bundle
|
||||||
bundle:
|
bundle:
|
||||||
- standardBundles: [ ORIGINAL, THUMBNAIL, LICENSE ]
|
standardBundles: [ ORIGINAL, THUMBNAIL, LICENSE ]
|
||||||
|
|
||||||
# Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with 'image' or 'video').
|
# Whether to enable media viewer for image and/or video Bitstreams (i.e. Bitstreams whose MIME type starts with 'image' or 'video').
|
||||||
# For images, this enables a gallery viewer where you can zoom or page through images.
|
# For images, this enables a gallery viewer where you can zoom or page through images.
|
||||||
@@ -248,3 +283,16 @@ bundle:
|
|||||||
mediaViewer:
|
mediaViewer:
|
||||||
image: false
|
image: false
|
||||||
video: false
|
video: false
|
||||||
|
|
||||||
|
# Whether the end user agreement is required before users use the repository.
|
||||||
|
# If enabled, the user will be required to accept the agreement before they can use the repository.
|
||||||
|
# And whether the privacy statement should exist or not.
|
||||||
|
info:
|
||||||
|
enableEndUserAgreement: true
|
||||||
|
enablePrivacyStatement: true
|
||||||
|
|
||||||
|
# Whether to enable Markdown (https://commonmark.org/) and MathJax (https://www.mathjax.org/)
|
||||||
|
# display in supported metadata fields. By default, only dc.description.abstract is supported.
|
||||||
|
markdown:
|
||||||
|
enabled: false
|
||||||
|
mathjax: false
|
1
cypress/.gitignore
vendored
1
cypress/.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
screenshots/
|
screenshots/
|
||||||
videos/
|
videos/
|
||||||
|
downloads/
|
||||||
|
@@ -4,10 +4,11 @@ import { testA11y } from 'cypress/support/utils';
|
|||||||
|
|
||||||
describe('My DSpace page', () => {
|
describe('My DSpace page', () => {
|
||||||
it('should display recent submissions and pass accessibility tests', () => {
|
it('should display recent submissions and pass accessibility tests', () => {
|
||||||
cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
|
||||||
|
|
||||||
cy.visit('/mydspace');
|
cy.visit('/mydspace');
|
||||||
|
|
||||||
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
|
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||||
|
|
||||||
cy.get('ds-my-dspace-page').should('exist');
|
cy.get('ds-my-dspace-page').should('exist');
|
||||||
|
|
||||||
// At least one recent submission should be displayed
|
// At least one recent submission should be displayed
|
||||||
@@ -36,10 +37,11 @@ describe('My DSpace page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should have a working detailed view that passes accessibility tests', () => {
|
it('should have a working detailed view that passes accessibility tests', () => {
|
||||||
cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
|
||||||
|
|
||||||
cy.visit('/mydspace');
|
cy.visit('/mydspace');
|
||||||
|
|
||||||
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
|
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||||
|
|
||||||
cy.get('ds-my-dspace-page').should('exist');
|
cy.get('ds-my-dspace-page').should('exist');
|
||||||
|
|
||||||
// Click button in sidebar to display detailed view
|
// Click button in sidebar to display detailed view
|
||||||
@@ -61,9 +63,11 @@ describe('My DSpace page', () => {
|
|||||||
|
|
||||||
// NOTE: Deleting existing submissions is exercised by submission.spec.ts
|
// NOTE: Deleting existing submissions is exercised by submission.spec.ts
|
||||||
it('should let you start a new submission & edit in-progress submissions', () => {
|
it('should let you start a new submission & edit in-progress submissions', () => {
|
||||||
cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
|
||||||
cy.visit('/mydspace');
|
cy.visit('/mydspace');
|
||||||
|
|
||||||
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
|
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||||
|
|
||||||
// Open the New Submission dropdown
|
// Open the New Submission dropdown
|
||||||
cy.get('button[data-test="submission-dropdown"]').click();
|
cy.get('button[data-test="submission-dropdown"]').click();
|
||||||
// Click on the "Item" type in that dropdown
|
// Click on the "Item" type in that dropdown
|
||||||
@@ -131,9 +135,11 @@ describe('My DSpace page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should let you import from external sources', () => {
|
it('should let you import from external sources', () => {
|
||||||
cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
|
||||||
cy.visit('/mydspace');
|
cy.visit('/mydspace');
|
||||||
|
|
||||||
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
|
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||||
|
|
||||||
// Open the New Import dropdown
|
// Open the New Import dropdown
|
||||||
cy.get('button[data-test="import-dropdown"]').click();
|
cy.get('button[data-test="import-dropdown"]').click();
|
||||||
// Click on the "Item" type in that dropdown
|
// Click on the "Item" type in that dropdown
|
||||||
|
@@ -6,11 +6,12 @@ describe('New Submission page', () => {
|
|||||||
// NOTE: We already test that new submissions can be started from MyDSpace in my-dspace.spec.ts
|
// NOTE: We already test that new submissions can be started from MyDSpace in my-dspace.spec.ts
|
||||||
|
|
||||||
it('should create a new submission when using /submit path & pass accessibility', () => {
|
it('should create a new submission when using /submit path & pass accessibility', () => {
|
||||||
cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
|
||||||
|
|
||||||
// Test that calling /submit with collection & entityType will create a new submission
|
// Test that calling /submit with collection & entityType will create a new submission
|
||||||
cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none');
|
cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none');
|
||||||
|
|
||||||
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
|
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||||
|
|
||||||
// Should redirect to /workspaceitems, as we've started a new submission
|
// Should redirect to /workspaceitems, as we've started a new submission
|
||||||
cy.url().should('include', '/workspaceitems');
|
cy.url().should('include', '/workspaceitems');
|
||||||
|
|
||||||
@@ -33,11 +34,12 @@ describe('New Submission page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should block submission & show errors if required fields are missing', () => {
|
it('should block submission & show errors if required fields are missing', () => {
|
||||||
cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
|
||||||
|
|
||||||
// Create a new submission
|
// Create a new submission
|
||||||
cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none');
|
cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none');
|
||||||
|
|
||||||
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
|
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||||
|
|
||||||
// Attempt an immediate deposit without filling out any fields
|
// Attempt an immediate deposit without filling out any fields
|
||||||
cy.get('button#deposit').click();
|
cy.get('button#deposit').click();
|
||||||
|
|
||||||
@@ -92,11 +94,12 @@ describe('New Submission page', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should allow for deposit if all required fields completed & file uploaded', () => {
|
it('should allow for deposit if all required fields completed & file uploaded', () => {
|
||||||
cy.login(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
|
||||||
|
|
||||||
// Create a new submission
|
// Create a new submission
|
||||||
cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none');
|
cy.visit('/submit?collection=' + TEST_SUBMIT_COLLECTION_UUID + '&entityType=none');
|
||||||
|
|
||||||
|
// This page is restricted, so we will be shown the login form. Fill it out & submit.
|
||||||
|
cy.loginViaForm(TEST_SUBMIT_USER, TEST_SUBMIT_USER_PASSWORD);
|
||||||
|
|
||||||
// Fill out all required fields (Title, Date)
|
// Fill out all required fields (Title, Date)
|
||||||
cy.get('input#dc_title').type('DSpace logo uploaded via e2e tests');
|
cy.get('input#dc_title').type('DSpace logo uploaded via e2e tests');
|
||||||
cy.get('input#dc_date_issued_year').type('2022');
|
cy.get('input#dc_date_issued_year').type('2022');
|
||||||
|
@@ -19,6 +19,14 @@ declare global {
|
|||||||
* @param password password to login as
|
* @param password password to login as
|
||||||
*/
|
*/
|
||||||
login(email: string, password: string): typeof login;
|
login(email: string, password: string): typeof login;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login via form before accessing the next page. Useful to fill out login
|
||||||
|
* form when a cy.visit() call is to an a page which requires authentication.
|
||||||
|
* @param email email to login as
|
||||||
|
* @param password password to login as
|
||||||
|
*/
|
||||||
|
loginViaForm(email: string, password: string): typeof loginViaForm;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,6 +34,8 @@ declare global {
|
|||||||
/**
|
/**
|
||||||
* Login user via REST API directly, and pass authentication token to UI via
|
* Login user via REST API directly, and pass authentication token to UI via
|
||||||
* the UI's dsAuthInfo cookie.
|
* the UI's dsAuthInfo cookie.
|
||||||
|
* WARNING: WHILE THIS METHOD WORKS, OCCASIONALLY RANDOM AUTHENTICATION ERRORS OCCUR.
|
||||||
|
* At this time "loginViaForm()" seems more consistent/stable.
|
||||||
* @param email email to login as
|
* @param email email to login as
|
||||||
* @param password password to login as
|
* @param password password to login as
|
||||||
*/
|
*/
|
||||||
@@ -81,3 +91,20 @@ function login(email: string, password: string): void {
|
|||||||
}
|
}
|
||||||
// Add as a Cypress command (i.e. assign to 'cy.login')
|
// Add as a Cypress command (i.e. assign to 'cy.login')
|
||||||
Cypress.Commands.add('login', login);
|
Cypress.Commands.add('login', login);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login user via displayed login form
|
||||||
|
* @param email email to login as
|
||||||
|
* @param password password to login as
|
||||||
|
*/
|
||||||
|
function loginViaForm(email: string, password: string): void {
|
||||||
|
// Enter email
|
||||||
|
cy.get('ds-log-in [data-test="email"]').type(email);
|
||||||
|
// Enter password
|
||||||
|
cy.get('ds-log-in [data-test="password"]').type(password);
|
||||||
|
// Click login button
|
||||||
|
cy.get('ds-log-in [data-test="login-button"]').click();
|
||||||
|
}
|
||||||
|
// Add as a Cypress command (i.e. assign to 'cy.loginViaForm')
|
||||||
|
Cypress.Commands.add('loginViaForm', loginViaForm);
|
@@ -24,7 +24,7 @@ import 'cypress-axe';
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Pre-agree to all Klaro cookies by setting the klaro-anonymous cookie
|
// Pre-agree to all Klaro cookies by setting the klaro-anonymous cookie
|
||||||
// This just ensures it doesn't get in the way of matching other objects in the page.
|
// This just ensures it doesn't get in the way of matching other objects in the page.
|
||||||
cy.setCookie('klaro-anonymous', '{%22authentication%22:true%2C%22preferences%22:true%2C%22acknowledgement%22:true%2C%22google-analytics%22:true}');
|
cy.setCookie('klaro-anonymous', '{%22authentication%22:true%2C%22preferences%22:true%2C%22acknowledgement%22:true%2C%22google-analytics%22:true%2C%22google-recaptcha%22:true}');
|
||||||
});
|
});
|
||||||
|
|
||||||
// For better stability between tests, we visit "about:blank" (i.e. blank page) after each test.
|
// For better stability between tests, we visit "about:blank" (i.e. blank page) after each test.
|
||||||
|
24
package.json
24
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dspace-angular",
|
"name": "dspace-angular",
|
||||||
"version": "0.0.0",
|
"version": "7.5.0-next",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"config:watch": "nodemon",
|
"config:watch": "nodemon",
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
"start:prod": "yarn run build:prod && cross-env NODE_ENV=production yarn run serve:ssr",
|
"start:prod": "yarn run build:prod && cross-env NODE_ENV=production yarn run serve:ssr",
|
||||||
"start:mirador:prod": "yarn run build:mirador && yarn run start:prod",
|
"start:mirador:prod": "yarn run build:mirador && yarn run start:prod",
|
||||||
"preserve": "yarn base-href",
|
"preserve": "yarn base-href",
|
||||||
"serve": "ng serve --configuration development",
|
"serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts",
|
||||||
"serve:ssr": "node dist/server/main",
|
"serve:ssr": "node dist/server/main",
|
||||||
"analyze": "webpack-bundle-analyzer dist/browser/stats.json",
|
"analyze": "webpack-bundle-analyzer dist/browser/stats.json",
|
||||||
"build": "ng build --configuration development",
|
"build": "ng build --configuration development",
|
||||||
@@ -78,6 +78,7 @@
|
|||||||
"@nguniversal/express-engine": "^13.0.2",
|
"@nguniversal/express-engine": "^13.0.2",
|
||||||
"@ngx-translate/core": "^13.0.0",
|
"@ngx-translate/core": "^13.0.0",
|
||||||
"@nicky-lenaers/ngx-scroll-to": "^9.0.0",
|
"@nicky-lenaers/ngx-scroll-to": "^9.0.0",
|
||||||
|
"@types/grecaptcha": "^3.0.4",
|
||||||
"angular-idle-preload": "3.0.0",
|
"angular-idle-preload": "3.0.0",
|
||||||
"angulartics2": "^12.0.0",
|
"angulartics2": "^12.0.0",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
@@ -88,6 +89,8 @@
|
|||||||
"compression": "^1.7.4",
|
"compression": "^1.7.4",
|
||||||
"cookie-parser": "1.4.5",
|
"cookie-parser": "1.4.5",
|
||||||
"core-js": "^3.7.0",
|
"core-js": "^3.7.0",
|
||||||
|
"date-fns": "^2.29.3",
|
||||||
|
"date-fns-tz": "^1.3.7",
|
||||||
"deepmerge": "^4.2.2",
|
"deepmerge": "^4.2.2",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-rate-limit": "^5.1.3",
|
"express-rate-limit": "^5.1.3",
|
||||||
@@ -102,20 +105,21 @@
|
|||||||
"json5": "^2.1.3",
|
"json5": "^2.1.3",
|
||||||
"jsonschema": "1.4.0",
|
"jsonschema": "1.4.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"klaro": "^0.7.10",
|
"klaro": "^0.7.18",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"markdown-it": "^13.0.1",
|
||||||
|
"markdown-it-mathjax3": "^4.3.1",
|
||||||
"mirador": "^3.3.0",
|
"mirador": "^3.3.0",
|
||||||
"mirador-dl-plugin": "^0.13.0",
|
"mirador-dl-plugin": "^0.13.0",
|
||||||
"mirador-share-plugin": "^0.11.0",
|
"mirador-share-plugin": "^0.11.0",
|
||||||
"moment": "^2.29.2",
|
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"ng-mocks": "^13.1.1",
|
"ng-mocks": "^13.1.1",
|
||||||
"ng2-file-upload": "1.4.0",
|
"ng2-file-upload": "1.4.0",
|
||||||
"ng2-nouislider": "^1.8.3",
|
"ng2-nouislider": "^1.8.3",
|
||||||
"ngx-infinite-scroll": "^10.0.1",
|
"ngx-infinite-scroll": "^10.0.1",
|
||||||
"ngx-moment": "^5.0.0",
|
|
||||||
"ngx-pagination": "5.0.0",
|
"ngx-pagination": "5.0.0",
|
||||||
"ngx-sortablejs": "^11.1.0",
|
"ngx-sortablejs": "^11.1.0",
|
||||||
|
"ngx-ui-switch": "^11.0.1",
|
||||||
"nouislider": "^14.6.3",
|
"nouislider": "^14.6.3",
|
||||||
"pem": "1.14.4",
|
"pem": "1.14.4",
|
||||||
"postcss-cli": "^9.1.0",
|
"postcss-cli": "^9.1.0",
|
||||||
@@ -123,13 +127,13 @@
|
|||||||
"react-copy-to-clipboard": "^5.0.1",
|
"react-copy-to-clipboard": "^5.0.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "^7.5.5",
|
"rxjs": "^7.5.5",
|
||||||
|
"sanitize-html": "^2.7.2",
|
||||||
"sortablejs": "1.13.0",
|
"sortablejs": "1.13.0",
|
||||||
"tslib": "^2.0.0",
|
"tslib": "^2.0.0",
|
||||||
"url-parse": "^1.5.6",
|
"url-parse": "^1.5.6",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
"webfontloader": "1.6.28",
|
"webfontloader": "1.6.28",
|
||||||
"zone.js": "~0.11.5",
|
"zone.js": "~0.11.5"
|
||||||
"ngx-ui-switch": "^11.0.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-builders/custom-webpack": "~13.1.0",
|
"@angular-builders/custom-webpack": "~13.1.0",
|
||||||
@@ -155,16 +159,17 @@
|
|||||||
"@types/js-cookie": "2.2.6",
|
"@types/js-cookie": "2.2.6",
|
||||||
"@types/lodash": "^4.14.165",
|
"@types/lodash": "^4.14.165",
|
||||||
"@types/node": "^14.14.9",
|
"@types/node": "^14.14.9",
|
||||||
|
"@types/sanitize-html": "^2.6.2",
|
||||||
"@typescript-eslint/eslint-plugin": "5.11.0",
|
"@typescript-eslint/eslint-plugin": "5.11.0",
|
||||||
"@typescript-eslint/parser": "5.11.0",
|
"@typescript-eslint/parser": "5.11.0",
|
||||||
"axe-core": "^4.3.3",
|
"axe-core": "^4.4.3",
|
||||||
"compression-webpack-plugin": "^9.2.0",
|
"compression-webpack-plugin": "^9.2.0",
|
||||||
"copy-webpack-plugin": "^6.4.1",
|
"copy-webpack-plugin": "^6.4.1",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"css-loader": "^6.2.0",
|
"css-loader": "^6.2.0",
|
||||||
"css-minimizer-webpack-plugin": "^3.4.1",
|
"css-minimizer-webpack-plugin": "^3.4.1",
|
||||||
"cssnano": "^5.0.6",
|
"cssnano": "^5.0.6",
|
||||||
"cypress": "9.5.1",
|
"cypress": "9.7.0",
|
||||||
"cypress-axe": "^0.14.0",
|
"cypress-axe": "^0.14.0",
|
||||||
"debug-loader": "^0.0.1",
|
"debug-loader": "^0.0.1",
|
||||||
"deep-freeze": "0.0.1",
|
"deep-freeze": "0.0.1",
|
||||||
@@ -173,6 +178,7 @@
|
|||||||
"eslint-plugin-deprecation": "^1.3.2",
|
"eslint-plugin-deprecation": "^1.3.2",
|
||||||
"eslint-plugin-import": "^2.25.4",
|
"eslint-plugin-import": "^2.25.4",
|
||||||
"eslint-plugin-jsdoc": "^38.0.6",
|
"eslint-plugin-jsdoc": "^38.0.6",
|
||||||
|
"eslint-plugin-lodash": "^7.4.0",
|
||||||
"eslint-plugin-unused-imports": "^2.0.0",
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
"express-static-gzip": "^2.1.5",
|
"express-static-gzip": "^2.1.5",
|
||||||
"fork-ts-checker-webpack-plugin": "^6.0.3",
|
"fork-ts-checker-webpack-plugin": "^6.0.3",
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import * as fs from 'fs';
|
import { existsSync, writeFileSync } from 'fs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
import { AppConfig } from '../src/config/app-config.interface';
|
import { AppConfig } from '../src/config/app-config.interface';
|
||||||
@@ -16,7 +16,7 @@ const appConfig: AppConfig = buildAppConfig();
|
|||||||
|
|
||||||
const angularJsonPath = join(process.cwd(), 'angular.json');
|
const angularJsonPath = join(process.cwd(), 'angular.json');
|
||||||
|
|
||||||
if (!fs.existsSync(angularJsonPath)) {
|
if (!existsSync(angularJsonPath)) {
|
||||||
console.error(`Error:\n${angularJsonPath} does not exist\n`);
|
console.error(`Error:\n${angularJsonPath} does not exist\n`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -30,7 +30,7 @@ try {
|
|||||||
|
|
||||||
angularJson.projects['dspace-angular'].architect.build.options.baseHref = baseHref;
|
angularJson.projects['dspace-angular'].architect.build.options.baseHref = baseHref;
|
||||||
|
|
||||||
fs.writeFileSync(angularJsonPath, JSON.stringify(angularJson, null, 2) + '\n');
|
writeFileSync(angularJsonPath, JSON.stringify(angularJson, null, 2) + '\n');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import * as fs from 'fs';
|
import { existsSync, writeFileSync } from 'fs';
|
||||||
import * as yaml from 'js-yaml';
|
import { dump } from 'js-yaml';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,7 +18,7 @@ if (args[0] === undefined) {
|
|||||||
|
|
||||||
const envFullPath = join(process.cwd(), args[0]);
|
const envFullPath = join(process.cwd(), args[0]);
|
||||||
|
|
||||||
if (!fs.existsSync(envFullPath)) {
|
if (!existsSync(envFullPath)) {
|
||||||
console.error(`Error:\n${envFullPath} does not exist\n`);
|
console.error(`Error:\n${envFullPath} does not exist\n`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -26,10 +26,10 @@ if (!fs.existsSync(envFullPath)) {
|
|||||||
try {
|
try {
|
||||||
const env = require(envFullPath).environment;
|
const env = require(envFullPath).environment;
|
||||||
|
|
||||||
const config = yaml.dump(env);
|
const config = dump(env);
|
||||||
if (args[1]) {
|
if (args[1]) {
|
||||||
const ymlFullPath = join(process.cwd(), args[1]);
|
const ymlFullPath = join(process.cwd(), args[1]);
|
||||||
fs.writeFileSync(ymlFullPath, config);
|
writeFileSync(ymlFullPath, config);
|
||||||
} else {
|
} else {
|
||||||
console.log(config);
|
console.log(config);
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import * as child from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
|
|
||||||
import { AppConfig } from '../src/config/app-config.interface';
|
import { AppConfig } from '../src/config/app-config.interface';
|
||||||
import { buildAppConfig } from '../src/config/config.server';
|
import { buildAppConfig } from '../src/config/config.server';
|
||||||
@@ -7,8 +7,9 @@ const appConfig: AppConfig = buildAppConfig();
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls `ng serve` with the following arguments configured for the UI in the app config: host, port, nameSpace, ssl
|
* Calls `ng serve` with the following arguments configured for the UI in the app config: host, port, nameSpace, ssl
|
||||||
|
* Any CLI arguments given to this script are patched through to `ng serve` as well.
|
||||||
*/
|
*/
|
||||||
child.spawn(
|
spawn(
|
||||||
`ng serve --host ${appConfig.ui.host} --port ${appConfig.ui.port} --serve-path ${appConfig.ui.nameSpace} --ssl ${appConfig.ui.ssl}`,
|
`ng serve --host ${appConfig.ui.host} --port ${appConfig.ui.port} --serve-path ${appConfig.ui.nameSpace} --ssl ${appConfig.ui.ssl} ${process.argv.slice(2).join(' ')} --configuration development`,
|
||||||
{ stdio: 'inherit', shell: true }
|
{ stdio: 'inherit', shell: true }
|
||||||
);
|
);
|
||||||
|
0
scripts/sync-i18n-files.ts
Executable file → Normal file
0
scripts/sync-i18n-files.ts
Executable file → Normal file
@@ -1,5 +1,5 @@
|
|||||||
import * as http from 'http';
|
import { request } from 'http';
|
||||||
import * as https from 'https';
|
import { request as https_request } from 'https';
|
||||||
|
|
||||||
import { AppConfig } from '../src/config/app-config.interface';
|
import { AppConfig } from '../src/config/app-config.interface';
|
||||||
import { buildAppConfig } from '../src/config/config.server';
|
import { buildAppConfig } from '../src/config/config.server';
|
||||||
@@ -20,9 +20,15 @@ console.log(`...Testing connection to REST API at ${restUrl}...\n`);
|
|||||||
|
|
||||||
// If SSL enabled, test via HTTPS, else via HTTP
|
// If SSL enabled, test via HTTPS, else via HTTP
|
||||||
if (appConfig.rest.ssl) {
|
if (appConfig.rest.ssl) {
|
||||||
const req = https.request(restUrl, (res) => {
|
const req = https_request(restUrl, (res) => {
|
||||||
console.log(`RESPONSE: ${res.statusCode} ${res.statusMessage} \n`);
|
console.log(`RESPONSE: ${res.statusCode} ${res.statusMessage} \n`);
|
||||||
res.on('data', (data) => {
|
// We will keep reading data until the 'end' event fires.
|
||||||
|
// This ensures we don't just read the first chunk.
|
||||||
|
let data = '';
|
||||||
|
res.on('data', (chunk) => {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
res.on('end', () => {
|
||||||
checkJSONResponse(data);
|
checkJSONResponse(data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -33,9 +39,15 @@ if (appConfig.rest.ssl) {
|
|||||||
|
|
||||||
req.end();
|
req.end();
|
||||||
} else {
|
} else {
|
||||||
const req = http.request(restUrl, (res) => {
|
const req = request(restUrl, (res) => {
|
||||||
console.log(`RESPONSE: ${res.statusCode} ${res.statusMessage} \n`);
|
console.log(`RESPONSE: ${res.statusCode} ${res.statusMessage} \n`);
|
||||||
res.on('data', (data) => {
|
// We will keep reading data until the 'end' event fires.
|
||||||
|
// This ensures we don't just read the first chunk.
|
||||||
|
let data = '';
|
||||||
|
res.on('data', (chunk) => {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
res.on('end', () => {
|
||||||
checkJSONResponse(data);
|
checkJSONResponse(data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
24
server.ts
24
server.ts
@@ -19,14 +19,17 @@ import 'zone.js/node';
|
|||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
import 'rxjs';
|
import 'rxjs';
|
||||||
|
|
||||||
import axios from 'axios';
|
/* eslint-disable import/no-namespace */
|
||||||
import * as pem from 'pem';
|
|
||||||
import * as https from 'https';
|
|
||||||
import * as morgan from 'morgan';
|
import * as morgan from 'morgan';
|
||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
import * as bodyParser from 'body-parser';
|
|
||||||
import * as compression from 'compression';
|
import * as compression from 'compression';
|
||||||
import * as expressStaticGzip from 'express-static-gzip';
|
import * as expressStaticGzip from 'express-static-gzip';
|
||||||
|
/* eslint-enable import/no-namespace */
|
||||||
|
|
||||||
|
import axios from 'axios';
|
||||||
|
import { createCertificate } from 'pem';
|
||||||
|
import { createServer } from 'https';
|
||||||
|
import { json } from 'body-parser';
|
||||||
|
|
||||||
import { existsSync, readFileSync } from 'fs';
|
import { existsSync, readFileSync } from 'fs';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
@@ -48,6 +51,7 @@ import { ServerAppModule } from './src/main.server';
|
|||||||
import { buildAppConfig } from './src/config/config.server';
|
import { buildAppConfig } from './src/config/config.server';
|
||||||
import { APP_CONFIG, AppConfig } from './src/config/app-config.interface';
|
import { APP_CONFIG, AppConfig } from './src/config/app-config.interface';
|
||||||
import { extendEnvironmentWithAppConfig } from './src/config/config.util';
|
import { extendEnvironmentWithAppConfig } from './src/config/config.util';
|
||||||
|
import { logStartupMessage } from './startup-message';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set path for the browser application's dist folder
|
* Set path for the browser application's dist folder
|
||||||
@@ -75,6 +79,10 @@ export function app() {
|
|||||||
*/
|
*/
|
||||||
const server = express();
|
const server = express();
|
||||||
|
|
||||||
|
// Tell Express to trust X-FORWARDED-* headers from proxies
|
||||||
|
// See https://expressjs.com/en/guide/behind-proxies.html
|
||||||
|
server.set('trust proxy', environment.ui.useProxies);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If production mode is enabled in the environment file:
|
* If production mode is enabled in the environment file:
|
||||||
* - Enable Angular's production mode
|
* - Enable Angular's production mode
|
||||||
@@ -105,7 +113,7 @@ export function app() {
|
|||||||
* Add parser for request bodies
|
* Add parser for request bodies
|
||||||
* See [morgan](https://github.com/expressjs/body-parser)
|
* See [morgan](https://github.com/expressjs/body-parser)
|
||||||
*/
|
*/
|
||||||
server.use(bodyParser.json());
|
server.use(json());
|
||||||
|
|
||||||
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
|
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
|
||||||
server.engine('html', (_, options, callback) =>
|
server.engine('html', (_, options, callback) =>
|
||||||
@@ -261,7 +269,7 @@ function serverStarted() {
|
|||||||
* @param keys SSL credentials
|
* @param keys SSL credentials
|
||||||
*/
|
*/
|
||||||
function createHttpsServer(keys) {
|
function createHttpsServer(keys) {
|
||||||
https.createServer({
|
createServer({
|
||||||
key: keys.serviceKey,
|
key: keys.serviceKey,
|
||||||
cert: keys.certificate
|
cert: keys.certificate
|
||||||
}, app).listen(environment.ui.port, environment.ui.host, () => {
|
}, app).listen(environment.ui.port, environment.ui.host, () => {
|
||||||
@@ -281,6 +289,8 @@ function run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
|
logStartupMessage(environment);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If SSL is enabled
|
* If SSL is enabled
|
||||||
* - Read credentials from configuration files
|
* - Read credentials from configuration files
|
||||||
@@ -313,7 +323,7 @@ function start() {
|
|||||||
|
|
||||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // lgtm[js/disabling-certificate-validation]
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // lgtm[js/disabling-certificate-validation]
|
||||||
|
|
||||||
pem.createCertificate({
|
createCertificate({
|
||||||
days: 1,
|
days: 1,
|
||||||
selfSigned: true
|
selfSigned: true
|
||||||
}, (error, keys) => {
|
}, (error, keys) => {
|
||||||
|
@@ -45,7 +45,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<ds-loading *ngIf="searching$ | async"></ds-loading>
|
<ds-themed-loading *ngIf="searching$ | async"></ds-themed-loading>
|
||||||
<ds-pagination
|
<ds-pagination
|
||||||
*ngIf="(pageInfoState$ | async)?.totalElements > 0 && !(searching$ | async)"
|
*ngIf="(pageInfoState$ | async)?.totalElements > 0 && !(searching$ | async)"
|
||||||
[paginationOptions]="config"
|
[paginationOptions]="config"
|
||||||
|
@@ -238,7 +238,6 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
|
|||||||
this.epersonService.deleteEPerson(ePerson).pipe(getFirstCompletedRemoteData()).subscribe((restResponse: RemoteData<NoContent>) => {
|
this.epersonService.deleteEPerson(ePerson).pipe(getFirstCompletedRemoteData()).subscribe((restResponse: RemoteData<NoContent>) => {
|
||||||
if (restResponse.hasSucceeded) {
|
if (restResponse.hasSucceeded) {
|
||||||
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', {name: ePerson.name}));
|
this.notificationsService.success(this.translateService.get(this.labelPrefix + 'notification.deleted.success', {name: ePerson.name}));
|
||||||
this.reset();
|
|
||||||
} else {
|
} else {
|
||||||
this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + ePerson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage);
|
this.notificationsService.error('Error occured when trying to delete EPerson with id: ' + ePerson.id + ' with code: ' + restResponse.statusCode + ' and message: ' + restResponse.errorMessage);
|
||||||
}
|
}
|
||||||
|
@@ -36,12 +36,12 @@
|
|||||||
</button>
|
</button>
|
||||||
</ds-form>
|
</ds-form>
|
||||||
|
|
||||||
<ds-loading [showMessage]="false" *ngIf="!formGroup"></ds-loading>
|
<ds-themed-loading [showMessage]="false" *ngIf="!formGroup"></ds-themed-loading>
|
||||||
|
|
||||||
<div *ngIf="epersonService.getActiveEPerson() | async">
|
<div *ngIf="epersonService.getActiveEPerson() | async">
|
||||||
<h5>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h5>
|
<h5>{{messagePrefix + '.groupsEPersonIsMemberOf' | translate}}</h5>
|
||||||
|
|
||||||
<ds-loading [showMessage]="false" *ngIf="!(groups | async)"></ds-loading>
|
<ds-themed-loading [showMessage]="false" *ngIf="!(groups | async)"></ds-themed-loading>
|
||||||
|
|
||||||
<ds-pagination
|
<ds-pagination
|
||||||
*ngIf="(groups | async)?.payload?.totalElements > 0"
|
*ngIf="(groups | async)?.payload?.totalElements > 0"
|
||||||
|
@@ -177,7 +177,7 @@ describe('EPersonFormComponent', () => {
|
|||||||
|
|
||||||
});
|
});
|
||||||
groupsDataService = jasmine.createSpyObj('groupsDataService', {
|
groupsDataService = jasmine.createSpyObj('groupsDataService', {
|
||||||
findAllByHref: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
findListByHref: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
||||||
getGroupRegistryRouterLink: ''
|
getGroupRegistryRouterLink: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -265,7 +265,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
||||||
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
||||||
if (eperson != null) {
|
if (eperson != null) {
|
||||||
this.groups = this.groupsDataService.findAllByHref(eperson._links.groups.href, {
|
this.groups = this.groupsDataService.findListByHref(eperson._links.groups.href, {
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
elementsPerPage: this.config.pageSize
|
elementsPerPage: this.config.pageSize
|
||||||
});
|
});
|
||||||
@@ -297,7 +297,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
}),
|
}),
|
||||||
switchMap(([eperson, findListOptions]) => {
|
switchMap(([eperson, findListOptions]) => {
|
||||||
if (eperson != null) {
|
if (eperson != null) {
|
||||||
return this.groupsDataService.findAllByHref(eperson._links.groups.href, findListOptions, true, true, followLink('object'));
|
return this.groupsDataService.findListByHref(eperson._links.groups.href, findListOptions, true, true, followLink('object'));
|
||||||
}
|
}
|
||||||
return observableOf(undefined);
|
return observableOf(undefined);
|
||||||
})
|
})
|
||||||
@@ -554,7 +554,7 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
private updateGroups(options) {
|
private updateGroups(options) {
|
||||||
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
|
||||||
this.groups = this.groupsDataService.findAllByHref(eperson._links.groups.href, options);
|
this.groups = this.groupsDataService.findListByHref(eperson._links.groups.href, options);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -53,7 +53,7 @@ describe('MembersListComponent', () => {
|
|||||||
activeGroup: activeGroup,
|
activeGroup: activeGroup,
|
||||||
epersonMembers: epersonMembers,
|
epersonMembers: epersonMembers,
|
||||||
subgroupMembers: subgroupMembers,
|
subgroupMembers: subgroupMembers,
|
||||||
findAllByHref(href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
findListByHref(href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
return createSuccessfulRemoteDataObject$(buildPaginatedList<EPerson>(new PageInfo(), groupsDataServiceStub.getEPersonMembers()));
|
return createSuccessfulRemoteDataObject$(buildPaginatedList<EPerson>(new PageInfo(), groupsDataServiceStub.getEPersonMembers()));
|
||||||
},
|
},
|
||||||
searchByScope(scope: string, query: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
searchByScope(scope: string, query: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
|
@@ -10,7 +10,7 @@ import {
|
|||||||
combineLatest as observableCombineLatest,
|
combineLatest as observableCombineLatest,
|
||||||
ObservedValueOf,
|
ObservedValueOf,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { map, mergeMap, switchMap, take } from 'rxjs/operators';
|
import { defaultIfEmpty, map, mergeMap, switchMap, take } from 'rxjs/operators';
|
||||||
import {buildPaginatedList, PaginatedList} from '../../../../core/data/paginated-list.model';
|
import {buildPaginatedList, PaginatedList} from '../../../../core/data/paginated-list.model';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
import { EPersonDataService } from '../../../../core/eperson/eperson-data.service';
|
||||||
@@ -129,7 +129,7 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
this.subs.set(SubKey.MembersDTO,
|
this.subs.set(SubKey.MembersDTO,
|
||||||
this.paginationService.getCurrentPagination(this.config.id, this.config).pipe(
|
this.paginationService.getCurrentPagination(this.config.id, this.config).pipe(
|
||||||
switchMap((currentPagination) => {
|
switchMap((currentPagination) => {
|
||||||
return this.ePersonDataService.findAllByHref(this.groupBeingEdited._links.epersons.href, {
|
return this.ePersonDataService.findListByHref(this.groupBeingEdited._links.epersons.href, {
|
||||||
currentPage: currentPagination.currentPage,
|
currentPage: currentPagination.currentPage,
|
||||||
elementsPerPage: currentPagination.pageSize
|
elementsPerPage: currentPagination.pageSize
|
||||||
}
|
}
|
||||||
@@ -144,7 +144,7 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
switchMap((epersonListRD: RemoteData<PaginatedList<EPerson>>) => {
|
switchMap((epersonListRD: RemoteData<PaginatedList<EPerson>>) => {
|
||||||
const dtos$ = observableCombineLatest(...epersonListRD.payload.page.map((member: EPerson) => {
|
const dtos$ = observableCombineLatest([...epersonListRD.payload.page.map((member: EPerson) => {
|
||||||
const dto$: Observable<EpersonDtoModel> = observableCombineLatest(
|
const dto$: Observable<EpersonDtoModel> = observableCombineLatest(
|
||||||
this.isMemberOfGroup(member), (isMember: ObservedValueOf<Observable<boolean>>) => {
|
this.isMemberOfGroup(member), (isMember: ObservedValueOf<Observable<boolean>>) => {
|
||||||
const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel();
|
const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel();
|
||||||
@@ -153,8 +153,8 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
return epersonDtoModel;
|
return epersonDtoModel;
|
||||||
});
|
});
|
||||||
return dto$;
|
return dto$;
|
||||||
}));
|
})]);
|
||||||
return dtos$.pipe(map((dtos: EpersonDtoModel[]) => {
|
return dtos$.pipe(defaultIfEmpty([]), map((dtos: EpersonDtoModel[]) => {
|
||||||
return buildPaginatedList(epersonListRD.payload.pageInfo, dtos);
|
return buildPaginatedList(epersonListRD.payload.pageInfo, dtos);
|
||||||
}));
|
}));
|
||||||
}))
|
}))
|
||||||
@@ -171,10 +171,10 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
return this.groupDataService.getActiveGroup().pipe(take(1),
|
return this.groupDataService.getActiveGroup().pipe(take(1),
|
||||||
mergeMap((group: Group) => {
|
mergeMap((group: Group) => {
|
||||||
if (group != null) {
|
if (group != null) {
|
||||||
return this.ePersonDataService.findAllByHref(group._links.epersons.href, {
|
return this.ePersonDataService.findListByHref(group._links.epersons.href, {
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
elementsPerPage: 9999
|
elementsPerPage: 9999
|
||||||
}, false)
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
getFirstSucceededRemoteData(),
|
getFirstSucceededRemoteData(),
|
||||||
getRemoteDataPayload(),
|
getRemoteDataPayload(),
|
||||||
@@ -209,7 +209,6 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
if (activeGroup != null) {
|
if (activeGroup != null) {
|
||||||
const response = this.groupDataService.deleteMemberFromGroup(activeGroup, ePerson.eperson);
|
const response = this.groupDataService.deleteMemberFromGroup(activeGroup, ePerson.eperson);
|
||||||
this.showNotifications('deleteMember', response, ePerson.eperson.name, activeGroup);
|
this.showNotifications('deleteMember', response, ePerson.eperson.name, activeGroup);
|
||||||
this.search({ scope: this.currentSearchScope, query: this.currentSearchQuery });
|
|
||||||
} else {
|
} else {
|
||||||
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.noActiveGroup'));
|
||||||
}
|
}
|
||||||
@@ -274,7 +273,7 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
switchMap((epersonListRD: RemoteData<PaginatedList<EPerson>>) => {
|
switchMap((epersonListRD: RemoteData<PaginatedList<EPerson>>) => {
|
||||||
const dtos$ = observableCombineLatest(...epersonListRD.payload.page.map((member: EPerson) => {
|
const dtos$ = observableCombineLatest([...epersonListRD.payload.page.map((member: EPerson) => {
|
||||||
const dto$: Observable<EpersonDtoModel> = observableCombineLatest(
|
const dto$: Observable<EpersonDtoModel> = observableCombineLatest(
|
||||||
this.isMemberOfGroup(member), (isMember: ObservedValueOf<Observable<boolean>>) => {
|
this.isMemberOfGroup(member), (isMember: ObservedValueOf<Observable<boolean>>) => {
|
||||||
const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel();
|
const epersonDtoModel: EpersonDtoModel = new EpersonDtoModel();
|
||||||
@@ -283,8 +282,8 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
return epersonDtoModel;
|
return epersonDtoModel;
|
||||||
});
|
});
|
||||||
return dto$;
|
return dto$;
|
||||||
}));
|
})]);
|
||||||
return dtos$.pipe(map((dtos: EpersonDtoModel[]) => {
|
return dtos$.pipe(defaultIfEmpty([]), map((dtos: EpersonDtoModel[]) => {
|
||||||
return buildPaginatedList(epersonListRD.payload.pageInfo, dtos);
|
return buildPaginatedList(epersonListRD.payload.pageInfo, dtos);
|
||||||
}));
|
}));
|
||||||
}))
|
}))
|
||||||
@@ -315,7 +314,6 @@ export class MembersListComponent implements OnInit, OnDestroy {
|
|||||||
response.pipe(getFirstCompletedRemoteData()).subscribe((rd: RemoteData<any>) => {
|
response.pipe(getFirstCompletedRemoteData()).subscribe((rd: RemoteData<any>) => {
|
||||||
if (rd.hasSucceeded) {
|
if (rd.hasSucceeded) {
|
||||||
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.success.' + messageSuffix, { name: nameObject }));
|
this.notificationsService.success(this.translateService.get(this.messagePrefix + '.notification.success.' + messageSuffix, { name: nameObject }));
|
||||||
this.ePersonDataService.clearLinkRequests(activeGroup._links.epersons.href);
|
|
||||||
} else {
|
} else {
|
||||||
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.' + messageSuffix, { name: nameObject }));
|
this.notificationsService.error(this.translateService.get(this.messagePrefix + '.notification.failure.' + messageSuffix, { name: nameObject }));
|
||||||
}
|
}
|
||||||
|
@@ -65,7 +65,7 @@ describe('SubgroupsListComponent', () => {
|
|||||||
getSubgroups(): Group {
|
getSubgroups(): Group {
|
||||||
return this.activeGroup;
|
return this.activeGroup;
|
||||||
},
|
},
|
||||||
findAllByHref(href: string): Observable<RemoteData<PaginatedList<Group>>> {
|
findListByHref(href: string): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
return this.subgroups$.pipe(
|
return this.subgroups$.pipe(
|
||||||
map((currentGroups: Group[]) => {
|
map((currentGroups: Group[]) => {
|
||||||
return createSuccessfulRemoteDataObject(buildPaginatedList<Group>(new PageInfo(), currentGroups));
|
return createSuccessfulRemoteDataObject(buildPaginatedList<Group>(new PageInfo(), currentGroups));
|
||||||
|
@@ -115,7 +115,7 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
|
|||||||
this.subs.set(
|
this.subs.set(
|
||||||
SubKey.Members,
|
SubKey.Members,
|
||||||
this.paginationService.getCurrentPagination(this.config.id, this.config).pipe(
|
this.paginationService.getCurrentPagination(this.config.id, this.config).pipe(
|
||||||
switchMap((config) => this.groupDataService.findAllByHref(this.groupBeingEdited._links.subgroups.href, {
|
switchMap((config) => this.groupDataService.findListByHref(this.groupBeingEdited._links.subgroups.href, {
|
||||||
currentPage: config.currentPage,
|
currentPage: config.currentPage,
|
||||||
elementsPerPage: config.pageSize
|
elementsPerPage: config.pageSize
|
||||||
},
|
},
|
||||||
@@ -139,7 +139,7 @@ export class SubgroupsListComponent implements OnInit, OnDestroy {
|
|||||||
if (activeGroup.uuid === possibleSubgroup.uuid) {
|
if (activeGroup.uuid === possibleSubgroup.uuid) {
|
||||||
return observableOf(false);
|
return observableOf(false);
|
||||||
} else {
|
} else {
|
||||||
return this.groupDataService.findAllByHref(activeGroup._links.subgroups.href, {
|
return this.groupDataService.findListByHref(activeGroup._links.subgroups.href, {
|
||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
elementsPerPage: 9999
|
elementsPerPage: 9999
|
||||||
})
|
})
|
||||||
|
@@ -33,7 +33,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<ds-loading *ngIf="loading$ | async"></ds-loading>
|
<ds-themed-loading *ngIf="loading$ | async"></ds-themed-loading>
|
||||||
<ds-pagination
|
<ds-pagination
|
||||||
*ngIf="(pageInfoState$ | async)?.totalElements > 0 && !(loading$ | async)"
|
*ngIf="(pageInfoState$ | async)?.totalElements > 0 && !(loading$ | async)"
|
||||||
[paginationOptions]="config"
|
[paginationOptions]="config"
|
||||||
|
@@ -69,7 +69,7 @@ describe('GroupRegistryComponent', () => {
|
|||||||
mockGroups = [GroupMock, GroupMock2];
|
mockGroups = [GroupMock, GroupMock2];
|
||||||
mockEPeople = [EPersonMock, EPersonMock2];
|
mockEPeople = [EPersonMock, EPersonMock2];
|
||||||
ePersonDataServiceStub = {
|
ePersonDataServiceStub = {
|
||||||
findAllByHref(href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
findListByHref(href: string): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
switch (href) {
|
switch (href) {
|
||||||
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/epersons':
|
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/epersons':
|
||||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo({
|
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo({
|
||||||
@@ -97,7 +97,7 @@ describe('GroupRegistryComponent', () => {
|
|||||||
};
|
};
|
||||||
groupsDataServiceStub = {
|
groupsDataServiceStub = {
|
||||||
allGroups: mockGroups,
|
allGroups: mockGroups,
|
||||||
findAllByHref(href: string): Observable<RemoteData<PaginatedList<Group>>> {
|
findListByHref(href: string): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
switch (href) {
|
switch (href) {
|
||||||
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/groups':
|
case 'https://dspace.4science.it/dspace-spring-rest/api/eperson/groups/testgroupid2/groups':
|
||||||
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo({
|
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo({
|
||||||
|
@@ -5,11 +5,12 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
combineLatest as observableCombineLatest,
|
combineLatest as observableCombineLatest,
|
||||||
|
EMPTY,
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf,
|
of as observableOf,
|
||||||
Subscription
|
Subscription
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { catchError, map, switchMap, tap } from 'rxjs/operators';
|
import { catchError, defaultIfEmpty, map, switchMap, tap } from 'rxjs/operators';
|
||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
||||||
@@ -144,7 +145,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
return this.authorizationService.isAuthorized(FeatureID.AdministratorOf).pipe(
|
return this.authorizationService.isAuthorized(FeatureID.AdministratorOf).pipe(
|
||||||
switchMap((isSiteAdmin: boolean) => {
|
switchMap((isSiteAdmin: boolean) => {
|
||||||
return observableCombineLatest(groups.page.map((group: Group) => {
|
return observableCombineLatest([...groups.page.map((group: Group) => {
|
||||||
if (hasValue(group) && !this.deletedGroupsIds.includes(group.id)) {
|
if (hasValue(group) && !this.deletedGroupsIds.includes(group.id)) {
|
||||||
return observableCombineLatest([
|
return observableCombineLatest([
|
||||||
this.authorizationService.isAuthorized(FeatureID.CanDelete, group.self),
|
this.authorizationService.isAuthorized(FeatureID.CanDelete, group.self),
|
||||||
@@ -165,8 +166,10 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
return EMPTY;
|
||||||
}
|
}
|
||||||
})).pipe(map((dtos: GroupDtoModel[]) => {
|
})]).pipe(defaultIfEmpty([]), map((dtos: GroupDtoModel[]) => {
|
||||||
return buildPaginatedList(groups.pageInfo, dtos);
|
return buildPaginatedList(groups.pageInfo, dtos);
|
||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
@@ -213,7 +216,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
|||||||
* @param group
|
* @param group
|
||||||
*/
|
*/
|
||||||
getMembers(group: Group): Observable<RemoteData<PaginatedList<EPerson>>> {
|
getMembers(group: Group): Observable<RemoteData<PaginatedList<EPerson>>> {
|
||||||
return this.ePersonDataService.findAllByHref(group._links.epersons.href).pipe(getFirstSucceededRemoteData());
|
return this.ePersonDataService.findListByHref(group._links.epersons.href).pipe(getFirstSucceededRemoteData());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -221,7 +224,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
|
|||||||
* @param group
|
* @param group
|
||||||
*/
|
*/
|
||||||
getSubgroups(group: Group): Observable<RemoteData<PaginatedList<Group>>> {
|
getSubgroups(group: Group): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
return this.groupService.findAllByHref(group._links.subgroups.href).pipe(getFirstSucceededRemoteData());
|
return this.groupService.findListByHref(group._links.subgroups.href).pipe(getFirstSucceededRemoteData());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -0,0 +1,35 @@
|
|||||||
|
<div class="container">
|
||||||
|
<h2 id="header">{{'admin.batch-import.page.header' | translate}}</h2>
|
||||||
|
<p>{{'admin.batch-import.page.help' | translate}}</p>
|
||||||
|
<p *ngIf="dso">
|
||||||
|
selected collection: <b>{{getDspaceObjectName()}}</b>
|
||||||
|
<a href="javascript:void(0)" (click)="removeDspaceObject()">{{'admin.batch-import.page.remove' | translate}}</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<button class="btn btn-primary" (click)="this.selectCollection();">{{'admin.metadata-import.page.button.select-collection' | translate}}</button>
|
||||||
|
</p>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="validateOnly" [(ngModel)]="validateOnly">
|
||||||
|
<label class="form-check-label" for="validateOnly">
|
||||||
|
{{'admin.metadata-import.page.validateOnly' | translate}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<small id="validateOnlyHelpBlock" class="form-text text-muted">
|
||||||
|
{{'admin.batch-import.page.validateOnly.hint' | translate}}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ds-file-dropzone-no-uploader
|
||||||
|
(onFileAdded)="setFile($event)"
|
||||||
|
[dropMessageLabel]="'admin.batch-import.page.dropMsg'"
|
||||||
|
[dropMessageLabelReplacement]="'admin.batch-import.page.dropMsgReplace'">
|
||||||
|
</ds-file-dropzone-no-uploader>
|
||||||
|
|
||||||
|
<div class="space-children-mr">
|
||||||
|
<button class="btn btn-secondary" id="backButton"
|
||||||
|
(click)="this.onReturn();">{{'admin.metadata-import.page.button.return' | translate}}</button>
|
||||||
|
<button class="btn btn-primary" id="proceedButton"
|
||||||
|
(click)="this.importMetadata();">{{'admin.metadata-import.page.button.proceed' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1,151 @@
|
|||||||
|
import { ComponentFixture, fakeAsync, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
|
import { BatchImportPageComponent } from './batch-import-page.component';
|
||||||
|
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
|
||||||
|
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { FileValueAccessorDirective } from '../../shared/utils/file-value-accessor.directive';
|
||||||
|
import { FileValidator } from '../../shared/utils/require-file.validator';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import {
|
||||||
|
BATCH_IMPORT_SCRIPT_NAME,
|
||||||
|
ScriptDataService
|
||||||
|
} from '../../core/data/processes/script-data.service';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { Location } from '@angular/common';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { ProcessParameter } from '../../process-page/processes/process-parameter.model';
|
||||||
|
|
||||||
|
describe('BatchImportPageComponent', () => {
|
||||||
|
let component: BatchImportPageComponent;
|
||||||
|
let fixture: ComponentFixture<BatchImportPageComponent>;
|
||||||
|
|
||||||
|
let notificationService: NotificationsServiceStub;
|
||||||
|
let scriptService: any;
|
||||||
|
let router;
|
||||||
|
let locationStub;
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
notificationService = new NotificationsServiceStub();
|
||||||
|
scriptService = jasmine.createSpyObj('scriptService',
|
||||||
|
{
|
||||||
|
invoke: createSuccessfulRemoteDataObject$({ processId: '46' })
|
||||||
|
}
|
||||||
|
);
|
||||||
|
router = jasmine.createSpyObj('router', {
|
||||||
|
navigateByUrl: jasmine.createSpy('navigateByUrl')
|
||||||
|
});
|
||||||
|
locationStub = jasmine.createSpyObj('location', {
|
||||||
|
back: jasmine.createSpy('back')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(waitForAsync(() => {
|
||||||
|
init();
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
RouterTestingModule.withRoutes([])
|
||||||
|
],
|
||||||
|
declarations: [BatchImportPageComponent, FileValueAccessorDirective, FileValidator],
|
||||||
|
providers: [
|
||||||
|
{ provide: NotificationsService, useValue: notificationService },
|
||||||
|
{ provide: ScriptDataService, useValue: scriptService },
|
||||||
|
{ provide: Router, useValue: router },
|
||||||
|
{ provide: Location, useValue: locationStub },
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(BatchImportPageComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if back button is pressed', () => {
|
||||||
|
beforeEach(fakeAsync(() => {
|
||||||
|
const proceed = fixture.debugElement.query(By.css('#backButton')).nativeElement;
|
||||||
|
proceed.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
it('should do location.back', () => {
|
||||||
|
expect(locationStub.back).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if file is set', () => {
|
||||||
|
let fileMock: File;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fileMock = new File([''], 'filename.zip', { type: 'application/zip' });
|
||||||
|
component.setFile(fileMock);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if proceed button is pressed without validate only', () => {
|
||||||
|
beforeEach(fakeAsync(() => {
|
||||||
|
component.validateOnly = false;
|
||||||
|
const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement;
|
||||||
|
proceed.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
it('metadata-import script is invoked with --zip fileName and the mockFile', () => {
|
||||||
|
const parameterValues: ProcessParameter[] = [
|
||||||
|
Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' }),
|
||||||
|
];
|
||||||
|
parameterValues.push(Object.assign(new ProcessParameter(), { name: '--add' }));
|
||||||
|
expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [fileMock]);
|
||||||
|
});
|
||||||
|
it('success notification is shown', () => {
|
||||||
|
expect(notificationService.success).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
it('redirected to process page', () => {
|
||||||
|
expect(router.navigateByUrl).toHaveBeenCalledWith('/processes/46');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if proceed button is pressed with validate only', () => {
|
||||||
|
beforeEach(fakeAsync(() => {
|
||||||
|
component.validateOnly = true;
|
||||||
|
const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement;
|
||||||
|
proceed.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
it('metadata-import script is invoked with --zip fileName and the mockFile and -v validate-only', () => {
|
||||||
|
const parameterValues: ProcessParameter[] = [
|
||||||
|
Object.assign(new ProcessParameter(), { name: '--zip', value: 'filename.zip' }),
|
||||||
|
Object.assign(new ProcessParameter(), { name: '--add' }),
|
||||||
|
Object.assign(new ProcessParameter(), { name: '-v', value: true }),
|
||||||
|
];
|
||||||
|
expect(scriptService.invoke).toHaveBeenCalledWith(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [fileMock]);
|
||||||
|
});
|
||||||
|
it('success notification is shown', () => {
|
||||||
|
expect(notificationService.success).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
it('redirected to process page', () => {
|
||||||
|
expect(router.navigateByUrl).toHaveBeenCalledWith('/processes/46');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('if proceed is pressed; but script invoke fails', () => {
|
||||||
|
beforeEach(fakeAsync(() => {
|
||||||
|
jasmine.getEnv().allowRespy(true);
|
||||||
|
spyOn(scriptService, 'invoke').and.returnValue(createFailedRemoteDataObject$('Error', 500));
|
||||||
|
const proceed = fixture.debugElement.query(By.css('#proceedButton')).nativeElement;
|
||||||
|
proceed.click();
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
it('error notification is shown', () => {
|
||||||
|
expect(notificationService.error).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,124 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Location } from '@angular/common';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { BATCH_IMPORT_SCRIPT_NAME, ScriptDataService } from '../../core/data/processes/script-data.service';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { ProcessParameter } from '../../process-page/processes/process-parameter.model';
|
||||||
|
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
import { Process } from '../../process-page/processes/process.model';
|
||||||
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
|
import { getProcessDetailRoute } from '../../process-page/process-page-routing.paths';
|
||||||
|
import {
|
||||||
|
ImportBatchSelectorComponent
|
||||||
|
} from '../../shared/dso-selector/modal-wrappers/import-batch-selector/import-batch-selector.component';
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-batch-import-page',
|
||||||
|
templateUrl: './batch-import-page.component.html'
|
||||||
|
})
|
||||||
|
export class BatchImportPageComponent {
|
||||||
|
/**
|
||||||
|
* The current value of the file
|
||||||
|
*/
|
||||||
|
fileObject: File;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The validate only flag
|
||||||
|
*/
|
||||||
|
validateOnly = true;
|
||||||
|
/**
|
||||||
|
* dso object for community or collection
|
||||||
|
*/
|
||||||
|
dso: DSpaceObject = null;
|
||||||
|
|
||||||
|
public constructor(private location: Location,
|
||||||
|
protected translate: TranslateService,
|
||||||
|
protected notificationsService: NotificationsService,
|
||||||
|
private scriptDataService: ScriptDataService,
|
||||||
|
private router: Router,
|
||||||
|
private modalService: NgbModal,
|
||||||
|
private dsoNameService: DSONameService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set file
|
||||||
|
* @param file
|
||||||
|
*/
|
||||||
|
setFile(file) {
|
||||||
|
this.fileObject = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When return button is pressed go to previous location
|
||||||
|
*/
|
||||||
|
public onReturn() {
|
||||||
|
this.location.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
public selectCollection() {
|
||||||
|
const modalRef = this.modalService.open(ImportBatchSelectorComponent);
|
||||||
|
modalRef.componentInstance.response.pipe(take(1)).subscribe((dso) => {
|
||||||
|
this.dso = dso || null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts import-metadata script with --zip fileName (and the selected file)
|
||||||
|
*/
|
||||||
|
public importMetadata() {
|
||||||
|
if (this.fileObject == null) {
|
||||||
|
this.notificationsService.error(this.translate.get('admin.metadata-import.page.error.addFile'));
|
||||||
|
} else {
|
||||||
|
const parameterValues: ProcessParameter[] = [
|
||||||
|
Object.assign(new ProcessParameter(), { name: '--zip', value: this.fileObject.name }),
|
||||||
|
Object.assign(new ProcessParameter(), { name: '--add' })
|
||||||
|
];
|
||||||
|
if (this.dso) {
|
||||||
|
parameterValues.push(Object.assign(new ProcessParameter(), { name: '--collection', value: this.dso.uuid }));
|
||||||
|
}
|
||||||
|
if (this.validateOnly) {
|
||||||
|
parameterValues.push(Object.assign(new ProcessParameter(), { name: '-v', value: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scriptDataService.invoke(BATCH_IMPORT_SCRIPT_NAME, parameterValues, [this.fileObject]).pipe(
|
||||||
|
getFirstCompletedRemoteData(),
|
||||||
|
).subscribe((rd: RemoteData<Process>) => {
|
||||||
|
if (rd.hasSucceeded) {
|
||||||
|
const title = this.translate.get('process.new.notification.success.title');
|
||||||
|
const content = this.translate.get('process.new.notification.success.content');
|
||||||
|
this.notificationsService.success(title, content);
|
||||||
|
if (isNotEmpty(rd.payload)) {
|
||||||
|
this.router.navigateByUrl(getProcessDetailRoute(rd.payload.processId));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const title = this.translate.get('process.new.notification.error.title');
|
||||||
|
const content = this.translate.get('process.new.notification.error.content');
|
||||||
|
this.notificationsService.error(title, content);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return selected dspace object name
|
||||||
|
*/
|
||||||
|
getDspaceObjectName(): string {
|
||||||
|
if (this.dso) {
|
||||||
|
return this.dsoNameService.getName(this.dso);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove selected dso object
|
||||||
|
*/
|
||||||
|
removeDspaceObject(): void {
|
||||||
|
this.dso = null;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,12 +1,8 @@
|
|||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { map, switchMap } from 'rxjs/operators';
|
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
|
||||||
import { METADATA_IMPORT_SCRIPT_NAME, ScriptDataService } from '../../core/data/processes/script-data.service';
|
import { METADATA_IMPORT_SCRIPT_NAME, ScriptDataService } from '../../core/data/processes/script-data.service';
|
||||||
import { EPerson } from '../../core/eperson/models/eperson.model';
|
|
||||||
import { ProcessParameter } from '../../process-page/processes/process-parameter.model';
|
import { ProcessParameter } from '../../process-page/processes/process-parameter.model';
|
||||||
import { isNotEmpty } from '../../shared/empty.util';
|
import { isNotEmpty } from '../../shared/empty.util';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
@@ -13,32 +13,34 @@
|
|||||||
[paginationOptions]="pageConfig"
|
[paginationOptions]="pageConfig"
|
||||||
[pageInfoState]="(bitstreamFormats | async)?.payload"
|
[pageInfoState]="(bitstreamFormats | async)?.payload"
|
||||||
[collectionSize]="(bitstreamFormats | async)?.payload?.totalElements"
|
[collectionSize]="(bitstreamFormats | async)?.payload?.totalElements"
|
||||||
[hideGear]="true"
|
[hideGear]="false"
|
||||||
[hidePagerWhenSinglePage]="true">
|
[hidePagerWhenSinglePage]="true">
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table id="formats" class="table table-striped table-hover">
|
<table id="formats" class="table table-striped table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col"></th>
|
<th scope="col"></th>
|
||||||
<th scope="col">{{'admin.registries.bitstream-formats.table.name' | translate}}</th>
|
<th scope="col">{{'admin.registries.bitstream-formats.table.id' | translate}}</th>
|
||||||
<th scope="col">{{'admin.registries.bitstream-formats.table.mimetype' | translate}}</th>
|
<th scope="col">{{'admin.registries.bitstream-formats.table.name' | translate}}</th>
|
||||||
<th scope="col">{{'admin.registries.bitstream-formats.table.supportLevel.head' | translate}}</th>
|
<th scope="col">{{'admin.registries.bitstream-formats.table.mimetype' | translate}}</th>
|
||||||
</tr>
|
<th scope="col">{{'admin.registries.bitstream-formats.table.supportLevel.head' | translate}}</th>
|
||||||
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let bitstreamFormat of (bitstreamFormats | async)?.payload?.page">
|
<tr *ngFor="let bitstreamFormat of (bitstreamFormats | async)?.payload?.page">
|
||||||
<td>
|
<td>
|
||||||
<label>
|
<label>
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
[checked]="isSelected(bitstreamFormat) | async"
|
[checked]="isSelected(bitstreamFormat) | async"
|
||||||
(change)="selectBitStreamFormat(bitstreamFormat, $event)"
|
(change)="selectBitStreamFormat(bitstreamFormat, $event)"
|
||||||
>
|
>
|
||||||
</label>
|
</label>
|
||||||
</td>
|
</td>
|
||||||
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.shortDescription}}</a></td>
|
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.id}}</a></td>
|
||||||
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.mimetype}} <span *ngIf="bitstreamFormat.internal">({{'admin.registries.bitstream-formats.table.internal' | translate}})</span></a></td>
|
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.shortDescription}}</a></td>
|
||||||
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{'admin.registries.bitstream-formats.table.supportLevel.'+bitstreamFormat.supportLevel | translate}}</a></td>
|
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.mimetype}} <span *ngIf="bitstreamFormat.internal">({{'admin.registries.bitstream-formats.table.internal' | translate}})</span></a></td>
|
||||||
</tr>
|
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{'admin.registries.bitstream-formats.table.supportLevel.'+bitstreamFormat.supportLevel | translate}}</a></td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -20,14 +20,12 @@ import { TestScheduler } from 'rxjs/testing';
|
|||||||
import {
|
import {
|
||||||
createNoContentRemoteDataObject$,
|
createNoContentRemoteDataObject$,
|
||||||
createSuccessfulRemoteDataObject,
|
createSuccessfulRemoteDataObject,
|
||||||
createSuccessfulRemoteDataObject$
|
createSuccessfulRemoteDataObject$,
|
||||||
|
createFailedRemoteDataObject$
|
||||||
} from '../../../shared/remote-data.utils';
|
} from '../../../shared/remote-data.utils';
|
||||||
import { createPaginatedList } from '../../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../../shared/testing/utils.test';
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
|
||||||
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
|
||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||||
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
|
||||||
|
|
||||||
describe('BitstreamFormatsComponent', () => {
|
describe('BitstreamFormatsComponent', () => {
|
||||||
let comp: BitstreamFormatsComponent;
|
let comp: BitstreamFormatsComponent;
|
||||||
@@ -85,10 +83,6 @@ describe('BitstreamFormatsComponent', () => {
|
|||||||
];
|
];
|
||||||
const mockFormatsRD = createSuccessfulRemoteDataObject(createPaginatedList(mockFormatsList));
|
const mockFormatsRD = createSuccessfulRemoteDataObject(createPaginatedList(mockFormatsList));
|
||||||
|
|
||||||
const pagination = Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 });
|
|
||||||
const sort = new SortOptions('score', SortDirection.DESC);
|
|
||||||
const findlistOptions = Object.assign(new FindListOptions(), { currentPage: 1, elementsPerPage: 20 });
|
|
||||||
|
|
||||||
const initAsync = () => {
|
const initAsync = () => {
|
||||||
notificationsServiceStub = new NotificationsServiceStub();
|
notificationsServiceStub = new NotificationsServiceStub();
|
||||||
|
|
||||||
@@ -135,16 +129,19 @@ describe('BitstreamFormatsComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should contain the correct formats', () => {
|
it('should contain the correct formats', () => {
|
||||||
const unknownName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(1) td:nth-child(2)')).nativeElement;
|
const unknownName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(1) td:nth-child(3)')).nativeElement;
|
||||||
expect(unknownName.textContent).toBe('Unknown');
|
expect(unknownName.textContent).toBe('Unknown');
|
||||||
|
|
||||||
const licenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(2) td:nth-child(2)')).nativeElement;
|
const UUID: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(1) td:nth-child(2)')).nativeElement;
|
||||||
|
expect(UUID.textContent).toBe('test-uuid-1');
|
||||||
|
|
||||||
|
const licenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(2) td:nth-child(3)')).nativeElement;
|
||||||
expect(licenseName.textContent).toBe('License');
|
expect(licenseName.textContent).toBe('License');
|
||||||
|
|
||||||
const ccLicenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(3) td:nth-child(2)')).nativeElement;
|
const ccLicenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(3) td:nth-child(3)')).nativeElement;
|
||||||
expect(ccLicenseName.textContent).toBe('CC License');
|
expect(ccLicenseName.textContent).toBe('CC License');
|
||||||
|
|
||||||
const adobeName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(4) td:nth-child(2)')).nativeElement;
|
const adobeName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(4) td:nth-child(3)')).nativeElement;
|
||||||
expect(adobeName.textContent).toBe('Adobe PDF');
|
expect(adobeName.textContent).toBe('Adobe PDF');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -246,7 +243,7 @@ describe('BitstreamFormatsComponent', () => {
|
|||||||
));
|
));
|
||||||
|
|
||||||
beforeEach(initBeforeEach);
|
beforeEach(initBeforeEach);
|
||||||
it('should clear bitstream formats ', () => {
|
it('should clear bitstream formats and show a success notification', () => {
|
||||||
comp.deleteFormats();
|
comp.deleteFormats();
|
||||||
|
|
||||||
expect(bitstreamFormatService.clearBitStreamFormatRequests).toHaveBeenCalled();
|
expect(bitstreamFormatService.clearBitStreamFormatRequests).toHaveBeenCalled();
|
||||||
@@ -275,7 +272,7 @@ describe('BitstreamFormatsComponent', () => {
|
|||||||
selectBitstreamFormat: {},
|
selectBitstreamFormat: {},
|
||||||
deselectBitstreamFormat: {},
|
deselectBitstreamFormat: {},
|
||||||
deselectAllBitstreamFormats: {},
|
deselectAllBitstreamFormats: {},
|
||||||
delete: observableOf(false),
|
delete: createFailedRemoteDataObject$(),
|
||||||
clearBitStreamFormatRequests: observableOf('cleared')
|
clearBitStreamFormatRequests: observableOf('cleared')
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -295,7 +292,7 @@ describe('BitstreamFormatsComponent', () => {
|
|||||||
));
|
));
|
||||||
|
|
||||||
beforeEach(initBeforeEach);
|
beforeEach(initBeforeEach);
|
||||||
it('should clear bitstream formats ', () => {
|
it('should clear bitstream formats and show an error notification', () => {
|
||||||
comp.deleteFormats();
|
comp.deleteFormats();
|
||||||
|
|
||||||
expect(bitstreamFormatService.clearBitStreamFormatRequests).toHaveBeenCalled();
|
expect(bitstreamFormatService.clearBitStreamFormatRequests).toHaveBeenCalled();
|
||||||
|
@@ -1,18 +1,18 @@
|
|||||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { combineLatest as observableCombineLatest, Observable, zip } from 'rxjs';
|
import { combineLatest as observableCombineLatest, Observable} from 'rxjs';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
|
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
|
||||||
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
|
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
|
||||||
import { map, switchMap, take } from 'rxjs/operators';
|
import { map, mergeMap, switchMap, take, toArray } from 'rxjs/operators';
|
||||||
import { hasValue } from '../../../shared/empty.util';
|
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { NoContent } from '../../../core/shared/NoContent.model';
|
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
||||||
|
import { getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a list of bitstream formats
|
* This component renders a list of bitstream formats
|
||||||
@@ -28,21 +28,14 @@ export class BitstreamFormatsComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
bitstreamFormats: Observable<RemoteData<PaginatedList<BitstreamFormat>>>;
|
bitstreamFormats: Observable<RemoteData<PaginatedList<BitstreamFormat>>>;
|
||||||
|
|
||||||
/**
|
|
||||||
* The current pagination configuration for the page used by the FindAll method
|
|
||||||
* Currently simply renders all bitstream formats
|
|
||||||
*/
|
|
||||||
config: FindListOptions = Object.assign(new FindListOptions(), {
|
|
||||||
elementsPerPage: 20
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current pagination configuration for the page
|
* The current pagination configuration for the page
|
||||||
* Currently simply renders all bitstream formats
|
* Currently simply renders all bitstream formats
|
||||||
*/
|
*/
|
||||||
pageConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
pageConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||||
id: 'rbp',
|
id: 'rbp',
|
||||||
pageSize: 20
|
pageSize: 20,
|
||||||
|
pageSizeOptions: [20, 40, 60, 80, 100]
|
||||||
});
|
});
|
||||||
|
|
||||||
constructor(private notificationsService: NotificationsService,
|
constructor(private notificationsService: NotificationsService,
|
||||||
@@ -50,7 +43,7 @@ export class BitstreamFormatsComponent implements OnInit, OnDestroy {
|
|||||||
private translateService: TranslateService,
|
private translateService: TranslateService,
|
||||||
private bitstreamFormatService: BitstreamFormatDataService,
|
private bitstreamFormatService: BitstreamFormatDataService,
|
||||||
private paginationService: PaginationService,
|
private paginationService: PaginationService,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -58,31 +51,39 @@ export class BitstreamFormatsComponent implements OnInit, OnDestroy {
|
|||||||
* Deletes the currently selected formats from the registry and updates the presented list
|
* Deletes the currently selected formats from the registry and updates the presented list
|
||||||
*/
|
*/
|
||||||
deleteFormats() {
|
deleteFormats() {
|
||||||
this.bitstreamFormatService.clearBitStreamFormatRequests().subscribe();
|
this.bitstreamFormatService.clearBitStreamFormatRequests();
|
||||||
this.bitstreamFormatService.getSelectedBitstreamFormats().pipe(take(1)).subscribe(
|
this.bitstreamFormatService.getSelectedBitstreamFormats().pipe(
|
||||||
(formats) => {
|
take(1),
|
||||||
const tasks$ = [];
|
// emit all formats in the array one at a time
|
||||||
for (const format of formats) {
|
mergeMap((formats: BitstreamFormat[]) => formats),
|
||||||
if (hasValue(format.id)) {
|
// delete each format
|
||||||
tasks$.push(this.bitstreamFormatService.delete(format.id).pipe(map((response: RemoteData<NoContent>) => response.hasSucceeded)));
|
mergeMap((format: BitstreamFormat) => this.bitstreamFormatService.delete(format.id).pipe(
|
||||||
}
|
// wait for each response to come back
|
||||||
}
|
getFirstCompletedRemoteData(),
|
||||||
zip(...tasks$).subscribe((results: boolean[]) => {
|
// return a boolean to indicate whether a response succeeded
|
||||||
const successResponses = results.filter((result: boolean) => result);
|
map((response: RemoteData<NoContent>) => response.hasSucceeded),
|
||||||
const failedResponses = results.filter((result: boolean) => !result);
|
)),
|
||||||
if (successResponses.length > 0) {
|
// wait for all responses to come in and return them as a single array
|
||||||
this.showNotification(true, successResponses.length);
|
toArray()
|
||||||
}
|
).subscribe((results: boolean[]) => {
|
||||||
if (failedResponses.length > 0) {
|
// Count the number of succeeded and failed deletions
|
||||||
this.showNotification(false, failedResponses.length);
|
const successResponses = results.filter((result: boolean) => result);
|
||||||
}
|
const failedResponses = results.filter((result: boolean) => !result);
|
||||||
|
|
||||||
this.deselectAll();
|
// Show a notification indicating the number of succeeded and failed deletions
|
||||||
|
if (successResponses.length > 0) {
|
||||||
this.paginationService.resetPage(this.pageConfig.id);
|
this.showNotification(true, successResponses.length);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
);
|
if (failedResponses.length > 0) {
|
||||||
|
this.showNotification(false, failedResponses.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset the selection
|
||||||
|
this.deselectAll();
|
||||||
|
|
||||||
|
// reload the page
|
||||||
|
this.paginationService.resetPage(this.pageConfig.id);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -140,7 +141,7 @@ export class BitstreamFormatsComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
|
||||||
this.bitstreamFormats = this.paginationService.getFindListOptions(this.pageConfig.id, this.config).pipe(
|
this.bitstreamFormats = this.paginationService.getFindListOptions(this.pageConfig.id, this.pageConfig).pipe(
|
||||||
switchMap((findListOptions: FindListOptions) => {
|
switchMap((findListOptions: FindListOptions) => {
|
||||||
return this.bitstreamFormatService.findAll(findListOptions);
|
return this.bitstreamFormatService.findAll(findListOptions);
|
||||||
})
|
})
|
||||||
|
@@ -19,10 +19,7 @@ import { RestResponse } from '../../../core/cache/response.models';
|
|||||||
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
|
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
|
||||||
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
|
||||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||||
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
|
||||||
|
|
||||||
describe('MetadataRegistryComponent', () => {
|
describe('MetadataRegistryComponent', () => {
|
||||||
let comp: MetadataRegistryComponent;
|
let comp: MetadataRegistryComponent;
|
||||||
|
@@ -25,6 +25,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th></th>
|
||||||
|
<th scope="col">{{'admin.registries.schema.fields.table.id' | translate}}</th>
|
||||||
<th scope="col">{{'admin.registries.schema.fields.table.field' | translate}}</th>
|
<th scope="col">{{'admin.registries.schema.fields.table.field' | translate}}</th>
|
||||||
<th scope="col">{{'admin.registries.schema.fields.table.scopenote' | translate}}</th>
|
<th scope="col">{{'admin.registries.schema.fields.table.scopenote' | translate}}</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -39,6 +40,7 @@
|
|||||||
(change)="selectMetadataField(field, $event)">
|
(change)="selectMetadataField(field, $event)">
|
||||||
</label>
|
</label>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="selectable-row" (click)="editField(field)">{{field.id}}</td>
|
||||||
<td class="selectable-row" (click)="editField(field)">{{schema?.prefix}}.{{field.element}}<label *ngIf="field.qualifier">.</label>{{field.qualifier}}</td>
|
<td class="selectable-row" (click)="editField(field)">{{schema?.prefix}}.{{field.element}}<label *ngIf="field.qualifier">.</label>{{field.qualifier}}</td>
|
||||||
<td class="selectable-row" (click)="editField(field)">{{field.scopeNote}}</td>
|
<td class="selectable-row" (click)="editField(field)">{{field.scopeNote}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@@ -23,11 +23,8 @@ import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
|
|||||||
import { MetadataField } from '../../../core/metadata/metadata-field.model';
|
import { MetadataField } from '../../../core/metadata/metadata-field.model';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
import { VarDirective } from '../../../shared/utils/var.directive';
|
import { VarDirective } from '../../../shared/utils/var.directive';
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
|
||||||
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
|
||||||
import { PaginationService } from '../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||||
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
|
||||||
|
|
||||||
describe('MetadataSchemaComponent', () => {
|
describe('MetadataSchemaComponent', () => {
|
||||||
let comp: MetadataSchemaComponent;
|
let comp: MetadataSchemaComponent;
|
||||||
@@ -169,10 +166,10 @@ describe('MetadataSchemaComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should contain the correct fields', () => {
|
it('should contain the correct fields', () => {
|
||||||
const editorField: HTMLElement = fixture.debugElement.query(By.css('#metadata-fields tr:nth-child(1) td:nth-child(2)')).nativeElement;
|
const editorField: HTMLElement = fixture.debugElement.query(By.css('#metadata-fields tr:nth-child(1) td:nth-child(3)')).nativeElement;
|
||||||
expect(editorField.textContent).toBe('mock.contributor.editor');
|
expect(editorField.textContent).toBe('mock.contributor.editor');
|
||||||
|
|
||||||
const illustratorField: HTMLElement = fixture.debugElement.query(By.css('#metadata-fields tr:nth-child(2) td:nth-child(2)')).nativeElement;
|
const illustratorField: HTMLElement = fixture.debugElement.query(By.css('#metadata-fields tr:nth-child(2) td:nth-child(3)')).nativeElement;
|
||||||
expect(illustratorField.textContent).toBe('mock.contributor.illustrator');
|
expect(illustratorField.textContent).toBe('mock.contributor.illustrator');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -7,6 +7,7 @@ import { AdminWorkflowPageComponent } from './admin-workflow-page/admin-workflow
|
|||||||
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
|
import { I18nBreadcrumbsService } from '../core/breadcrumbs/i18n-breadcrumbs.service';
|
||||||
import { AdminCurationTasksComponent } from './admin-curation-tasks/admin-curation-tasks.component';
|
import { AdminCurationTasksComponent } from './admin-curation-tasks/admin-curation-tasks.component';
|
||||||
import { REGISTRIES_MODULE_PATH, NOTIFICATIONS_MODULE_PATH } from './admin-routing-paths';
|
import { REGISTRIES_MODULE_PATH, NOTIFICATIONS_MODULE_PATH } from './admin-routing-paths';
|
||||||
|
import { BatchImportPageComponent } from './admin-import-batch-page/batch-import-page.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -45,6 +46,12 @@ import { REGISTRIES_MODULE_PATH, NOTIFICATIONS_MODULE_PATH } from './admin-routi
|
|||||||
component: MetadataImportPageComponent,
|
component: MetadataImportPageComponent,
|
||||||
data: { title: 'admin.metadata-import.title', breadcrumbKey: 'admin.metadata-import' }
|
data: { title: 'admin.metadata-import.title', breadcrumbKey: 'admin.metadata-import' }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'batch-import',
|
||||||
|
resolve: { breadcrumb: I18nBreadcrumbResolver },
|
||||||
|
component: BatchImportPageComponent,
|
||||||
|
data: { title: 'admin.batch-import.title', breadcrumbKey: 'admin.batch-import' }
|
||||||
|
},
|
||||||
])
|
])
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
@@ -14,6 +14,14 @@ import { By } from '@angular/platform-browser';
|
|||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { getCollectionEditRoute } from '../../../../../collection-page/collection-page-routing-paths';
|
import { getCollectionEditRoute } from '../../../../../collection-page/collection-page-routing-paths';
|
||||||
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
||||||
|
import { AuthService } from '../../../../../core/auth/auth.service';
|
||||||
|
import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub';
|
||||||
|
import { FileService } from '../../../../../core/shared/file.service';
|
||||||
|
import { FileServiceStub } from '../../../../../shared/testing/file-service.stub';
|
||||||
|
import { AuthorizationDataService } from '../../../../../core/data/feature-authorization/authorization-data.service';
|
||||||
|
import { AuthorizationDataServiceStub } from '../../../../../shared/testing/authorization-service.stub';
|
||||||
|
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
|
||||||
|
import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock';
|
||||||
|
|
||||||
describe('CollectionAdminSearchResultGridElementComponent', () => {
|
describe('CollectionAdminSearchResultGridElementComponent', () => {
|
||||||
let component: CollectionAdminSearchResultGridElementComponent;
|
let component: CollectionAdminSearchResultGridElementComponent;
|
||||||
@@ -45,7 +53,11 @@ describe('CollectionAdminSearchResultGridElementComponent', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: TruncatableService, useValue: mockTruncatableService },
|
{ provide: TruncatableService, useValue: mockTruncatableService },
|
||||||
{ provide: BitstreamDataService, useValue: {} },
|
{ provide: BitstreamDataService, useValue: {} },
|
||||||
{ provide: LinkService, useValue: linkService }
|
{ provide: LinkService, useValue: linkService },
|
||||||
|
{ provide: AuthService, useClass: AuthServiceStub },
|
||||||
|
{ provide: FileService, useClass: FileServiceStub },
|
||||||
|
{ provide: AuthorizationDataService, useClass: AuthorizationDataServiceStub },
|
||||||
|
{ provide: ThemeService, useValue: getMockThemeService() },
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
@@ -16,6 +16,14 @@ import { CommunitySearchResult } from '../../../../../shared/object-collection/s
|
|||||||
import { Community } from '../../../../../core/shared/community.model';
|
import { Community } from '../../../../../core/shared/community.model';
|
||||||
import { getCommunityEditRoute } from '../../../../../community-page/community-page-routing-paths';
|
import { getCommunityEditRoute } from '../../../../../community-page/community-page-routing-paths';
|
||||||
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
||||||
|
import { AuthService } from '../../../../../core/auth/auth.service';
|
||||||
|
import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub';
|
||||||
|
import { FileService } from '../../../../../core/shared/file.service';
|
||||||
|
import { FileServiceStub } from '../../../../../shared/testing/file-service.stub';
|
||||||
|
import { AuthorizationDataService } from '../../../../../core/data/feature-authorization/authorization-data.service';
|
||||||
|
import { AuthorizationDataServiceStub } from '../../../../../shared/testing/authorization-service.stub';
|
||||||
|
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
|
||||||
|
import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock';
|
||||||
|
|
||||||
describe('CommunityAdminSearchResultGridElementComponent', () => {
|
describe('CommunityAdminSearchResultGridElementComponent', () => {
|
||||||
let component: CommunityAdminSearchResultGridElementComponent;
|
let component: CommunityAdminSearchResultGridElementComponent;
|
||||||
@@ -47,7 +55,11 @@ describe('CommunityAdminSearchResultGridElementComponent', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: TruncatableService, useValue: mockTruncatableService },
|
{ provide: TruncatableService, useValue: mockTruncatableService },
|
||||||
{ provide: BitstreamDataService, useValue: {} },
|
{ provide: BitstreamDataService, useValue: {} },
|
||||||
{ provide: LinkService, useValue: linkService }
|
{ provide: LinkService, useValue: linkService },
|
||||||
|
{ provide: AuthService, useClass: AuthServiceStub },
|
||||||
|
{ provide: FileService, useClass: FileServiceStub },
|
||||||
|
{ provide: AuthorizationDataService, useClass: AuthorizationDataServiceStub },
|
||||||
|
{ provide: ThemeService, useValue: getMockThemeService() },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
})
|
})
|
||||||
|
@@ -20,6 +20,12 @@ import { getMockThemeService } from '../../../../../shared/mocks/theme-service.m
|
|||||||
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
|
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
|
||||||
import { AccessStatusDataService } from '../../../../../core/data/access-status-data.service';
|
import { AccessStatusDataService } from '../../../../../core/data/access-status-data.service';
|
||||||
import { AccessStatusObject } from '../../../../../shared/object-list/access-status-badge/access-status.model';
|
import { AccessStatusObject } from '../../../../../shared/object-list/access-status-badge/access-status.model';
|
||||||
|
import { AuthService } from '../../../../../core/auth/auth.service';
|
||||||
|
import { AuthServiceStub } from '../../../../../shared/testing/auth-service.stub';
|
||||||
|
import { FileService } from '../../../../../core/shared/file.service';
|
||||||
|
import { FileServiceStub } from '../../../../../shared/testing/file-service.stub';
|
||||||
|
import { AuthorizationDataService } from '../../../../../core/data/feature-authorization/authorization-data.service';
|
||||||
|
import { AuthorizationDataServiceStub } from '../../../../../shared/testing/authorization-service.stub';
|
||||||
|
|
||||||
describe('ItemAdminSearchResultGridElementComponent', () => {
|
describe('ItemAdminSearchResultGridElementComponent', () => {
|
||||||
let component: ItemAdminSearchResultGridElementComponent;
|
let component: ItemAdminSearchResultGridElementComponent;
|
||||||
@@ -64,6 +70,9 @@ describe('ItemAdminSearchResultGridElementComponent', () => {
|
|||||||
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
||||||
{ provide: ThemeService, useValue: mockThemeService },
|
{ provide: ThemeService, useValue: mockThemeService },
|
||||||
{ provide: AccessStatusDataService, useValue: mockAccessStatusDataService },
|
{ provide: AccessStatusDataService, useValue: mockAccessStatusDataService },
|
||||||
|
{ provide: AuthService, useClass: AuthServiceStub },
|
||||||
|
{ provide: FileService, useClass: FileServiceStub },
|
||||||
|
{ provide: AuthorizationDataService, useClass: AuthorizationDataServiceStub },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
})
|
})
|
||||||
|
@@ -13,6 +13,8 @@ import { RouterTestingModule } from '@angular/router/testing';
|
|||||||
import { getCollectionEditRoute } from '../../../../../collection-page/collection-page-routing-paths';
|
import { getCollectionEditRoute } from '../../../../../collection-page/collection-page-routing-paths';
|
||||||
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
||||||
|
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../../../../environments/environment';
|
||||||
|
|
||||||
describe('CollectionAdminSearchResultListElementComponent', () => {
|
describe('CollectionAdminSearchResultListElementComponent', () => {
|
||||||
let component: CollectionAdminSearchResultListElementComponent;
|
let component: CollectionAdminSearchResultListElementComponent;
|
||||||
@@ -36,7 +38,8 @@ describe('CollectionAdminSearchResultListElementComponent', () => {
|
|||||||
],
|
],
|
||||||
declarations: [CollectionAdminSearchResultListElementComponent],
|
declarations: [CollectionAdminSearchResultListElementComponent],
|
||||||
providers: [{ provide: TruncatableService, useValue: {} },
|
providers: [{ provide: TruncatableService, useValue: {} },
|
||||||
{ provide: DSONameService, useClass: DSONameServiceMock }],
|
{ provide: DSONameService, useClass: DSONameServiceMock },
|
||||||
|
{ provide: APP_CONFIG, useValue: environment }],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
@@ -13,6 +13,8 @@ import { Community } from '../../../../../core/shared/community.model';
|
|||||||
import { getCommunityEditRoute } from '../../../../../community-page/community-page-routing-paths';
|
import { getCommunityEditRoute } from '../../../../../community-page/community-page-routing-paths';
|
||||||
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
||||||
|
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../../../../environments/environment';
|
||||||
|
|
||||||
describe('CommunityAdminSearchResultListElementComponent', () => {
|
describe('CommunityAdminSearchResultListElementComponent', () => {
|
||||||
let component: CommunityAdminSearchResultListElementComponent;
|
let component: CommunityAdminSearchResultListElementComponent;
|
||||||
@@ -36,7 +38,8 @@ describe('CommunityAdminSearchResultListElementComponent', () => {
|
|||||||
],
|
],
|
||||||
declarations: [CommunityAdminSearchResultListElementComponent],
|
declarations: [CommunityAdminSearchResultListElementComponent],
|
||||||
providers: [{ provide: TruncatableService, useValue: {} },
|
providers: [{ provide: TruncatableService, useValue: {} },
|
||||||
{ provide: DSONameService, useClass: DSONameServiceMock }],
|
{ provide: DSONameService, useClass: DSONameServiceMock },
|
||||||
|
{ provide: APP_CONFIG, useValue: environment }],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
@@ -10,6 +10,8 @@ import { ItemAdminSearchResultListElementComponent } from './item-admin-search-r
|
|||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
||||||
|
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../../../../environments/environment';
|
||||||
|
|
||||||
describe('ItemAdminSearchResultListElementComponent', () => {
|
describe('ItemAdminSearchResultListElementComponent', () => {
|
||||||
let component: ItemAdminSearchResultListElementComponent;
|
let component: ItemAdminSearchResultListElementComponent;
|
||||||
@@ -33,7 +35,8 @@ describe('ItemAdminSearchResultListElementComponent', () => {
|
|||||||
],
|
],
|
||||||
declarations: [ItemAdminSearchResultListElementComponent],
|
declarations: [ItemAdminSearchResultListElementComponent],
|
||||||
providers: [{ provide: TruncatableService, useValue: {} },
|
providers: [{ provide: TruncatableService, useValue: {} },
|
||||||
{ provide: DSONameService, useClass: DSONameServiceMock }],
|
{ provide: DSONameService, useClass: DSONameServiceMock },
|
||||||
|
{ provide: APP_CONFIG, useValue: environment }],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
@@ -2,7 +2,7 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
|||||||
|
|
||||||
import { MenuService } from '../../../shared/menu/menu.service';
|
import { MenuService } from '../../../shared/menu/menu.service';
|
||||||
import { MenuServiceStub } from '../../../shared/testing/menu-service.stub';
|
import { MenuServiceStub } from '../../../shared/testing/menu-service.stub';
|
||||||
import { CSSVariableService } from '../../../shared/sass-helper/sass-helper.service';
|
import { CSSVariableService } from '../../../shared/sass-helper/css-variable.service';
|
||||||
import { CSSVariableServiceStub } from '../../../shared/testing/css-variable-service.stub';
|
import { CSSVariableServiceStub } from '../../../shared/testing/css-variable-service.stub';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<nav @slideHorizontal class="navbar navbar-dark p-0"
|
<nav class="navbar navbar-dark p-0"
|
||||||
[ngClass]="{'active': sidebarOpen, 'inactive': sidebarClosed}"
|
[ngClass]="{'active': sidebarOpen, 'inactive': sidebarClosed}"
|
||||||
[@slideSidebar]="{
|
[@slideSidebar]="{
|
||||||
value: (!(sidebarExpanded | async) ? 'collapsed' : 'expanded'),
|
value: (!(sidebarExpanded | async) ? 'collapsed' : 'expanded'),
|
||||||
|
@@ -6,7 +6,7 @@ import { ScriptDataService } from '../../core/data/processes/script-data.service
|
|||||||
import { AdminSidebarComponent } from './admin-sidebar.component';
|
import { AdminSidebarComponent } from './admin-sidebar.component';
|
||||||
import { MenuService } from '../../shared/menu/menu.service';
|
import { MenuService } from '../../shared/menu/menu.service';
|
||||||
import { MenuServiceStub } from '../../shared/testing/menu-service.stub';
|
import { MenuServiceStub } from '../../shared/testing/menu-service.stub';
|
||||||
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
|
import { CSSVariableService } from '../../shared/sass-helper/css-variable.service';
|
||||||
import { CSSVariableServiceStub } from '../../shared/testing/css-variable-service.stub';
|
import { CSSVariableServiceStub } from '../../shared/testing/css-variable-service.stub';
|
||||||
import { AuthServiceStub } from '../../shared/testing/auth-service.stub';
|
import { AuthServiceStub } from '../../shared/testing/auth-service.stub';
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
@@ -16,10 +16,11 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
|||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||||
import { FeatureID } from '../../core/data/feature-authorization/feature-id';
|
|
||||||
import createSpy = jasmine.createSpy;
|
import createSpy = jasmine.createSpy;
|
||||||
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||||
|
import { getMockThemeService } from '../../shared/mocks/theme-service.mock';
|
||||||
|
|
||||||
describe('AdminSidebarComponent', () => {
|
describe('AdminSidebarComponent', () => {
|
||||||
let comp: AdminSidebarComponent;
|
let comp: AdminSidebarComponent;
|
||||||
@@ -60,6 +61,7 @@ describe('AdminSidebarComponent', () => {
|
|||||||
declarations: [AdminSidebarComponent],
|
declarations: [AdminSidebarComponent],
|
||||||
providers: [
|
providers: [
|
||||||
Injector,
|
Injector,
|
||||||
|
{ provide: ThemeService, useValue: getMockThemeService() },
|
||||||
{ provide: MenuService, useValue: menuService },
|
{ provide: MenuService, useValue: menuService },
|
||||||
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
|
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
|
||||||
{ provide: AuthService, useClass: AuthServiceStub },
|
{ provide: AuthService, useClass: AuthServiceStub },
|
||||||
|
@@ -2,13 +2,14 @@ import { Component, HostListener, Injector, OnInit } from '@angular/core';
|
|||||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||||
import { debounceTime, distinctUntilChanged, first, map, withLatestFrom } from 'rxjs/operators';
|
import { debounceTime, distinctUntilChanged, first, map, withLatestFrom } from 'rxjs/operators';
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { slideHorizontal, slideSidebar } from '../../shared/animations/slide';
|
import { slideSidebar } from '../../shared/animations/slide';
|
||||||
import { MenuComponent } from '../../shared/menu/menu.component';
|
import { MenuComponent } from '../../shared/menu/menu.component';
|
||||||
import { MenuService } from '../../shared/menu/menu.service';
|
import { MenuService } from '../../shared/menu/menu.service';
|
||||||
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
|
import { CSSVariableService } from '../../shared/sass-helper/css-variable.service';
|
||||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||||
import { MenuID } from '../../shared/menu/menu-id.model';
|
import { MenuID } from '../../shared/menu/menu-id.model';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component representing the admin sidebar
|
* Component representing the admin sidebar
|
||||||
@@ -17,7 +18,7 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
selector: 'ds-admin-sidebar',
|
selector: 'ds-admin-sidebar',
|
||||||
templateUrl: './admin-sidebar.component.html',
|
templateUrl: './admin-sidebar.component.html',
|
||||||
styleUrls: ['./admin-sidebar.component.scss'],
|
styleUrls: ['./admin-sidebar.component.scss'],
|
||||||
animations: [slideHorizontal, slideSidebar]
|
animations: [slideSidebar]
|
||||||
})
|
})
|
||||||
export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||||
/**
|
/**
|
||||||
@@ -56,9 +57,10 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
private variableService: CSSVariableService,
|
private variableService: CSSVariableService,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
public authorizationService: AuthorizationDataService,
|
public authorizationService: AuthorizationDataService,
|
||||||
public route: ActivatedRoute
|
public route: ActivatedRoute,
|
||||||
|
protected themeService: ThemeService
|
||||||
) {
|
) {
|
||||||
super(menuService, injector, authorizationService, route);
|
super(menuService, injector, authorizationService, route, themeService);
|
||||||
this.inFocus$ = new BehaviorSubject(false);
|
this.inFocus$ = new BehaviorSubject(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,7 +69,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
this.sidebarWidth = this.variableService.getVariable('sidebarItemsWidth');
|
this.sidebarWidth = this.variableService.getVariable('--ds-sidebar-items-width');
|
||||||
this.authService.isAuthenticated()
|
this.authService.isAuthenticated()
|
||||||
.subscribe((loggedIn: boolean) => {
|
.subscribe((loggedIn: boolean) => {
|
||||||
if (loggedIn) {
|
if (loggedIn) {
|
||||||
|
@@ -3,7 +3,7 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
|||||||
import { ExpandableAdminSidebarSectionComponent } from './expandable-admin-sidebar-section.component';
|
import { ExpandableAdminSidebarSectionComponent } from './expandable-admin-sidebar-section.component';
|
||||||
import { MenuService } from '../../../shared/menu/menu.service';
|
import { MenuService } from '../../../shared/menu/menu.service';
|
||||||
import { MenuServiceStub } from '../../../shared/testing/menu-service.stub';
|
import { MenuServiceStub } from '../../../shared/testing/menu-service.stub';
|
||||||
import { CSSVariableService } from '../../../shared/sass-helper/sass-helper.service';
|
import { CSSVariableService } from '../../../shared/sass-helper/css-variable.service';
|
||||||
import { CSSVariableServiceStub } from '../../../shared/testing/css-variable-service.stub';
|
import { CSSVariableServiceStub } from '../../../shared/testing/css-variable-service.stub';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
|
@@ -2,7 +2,7 @@ import { Component, Inject, Injector, OnInit } from '@angular/core';
|
|||||||
import { rotate } from '../../../shared/animations/rotate';
|
import { rotate } from '../../../shared/animations/rotate';
|
||||||
import { AdminSidebarSectionComponent } from '../admin-sidebar-section/admin-sidebar-section.component';
|
import { AdminSidebarSectionComponent } from '../admin-sidebar-section/admin-sidebar-section.component';
|
||||||
import { slide } from '../../../shared/animations/slide';
|
import { slide } from '../../../shared/animations/slide';
|
||||||
import { CSSVariableService } from '../../../shared/sass-helper/sass-helper.service';
|
import { CSSVariableService } from '../../../shared/sass-helper/css-variable.service';
|
||||||
import { bgColor } from '../../../shared/animations/bgColor';
|
import { bgColor } from '../../../shared/animations/bgColor';
|
||||||
import { MenuService } from '../../../shared/menu/menu.service';
|
import { MenuService } from '../../../shared/menu/menu.service';
|
||||||
import { combineLatest as combineLatestObservable, Observable } from 'rxjs';
|
import { combineLatest as combineLatestObservable, Observable } from 'rxjs';
|
||||||
@@ -65,7 +65,7 @@ export class ExpandableAdminSidebarSectionComponent extends AdminSidebarSectionC
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
this.sidebarActiveBg = this.variableService.getVariable('adminSidebarActiveBg');
|
this.sidebarActiveBg = this.variableService.getVariable('--ds-admin-sidebar-active-bg');
|
||||||
this.sidebarCollapsed = this.menuService.isMenuCollapsed(this.menuID);
|
this.sidebarCollapsed = this.menuService.isMenuCollapsed(this.menuID);
|
||||||
this.sidebarPreviewCollapsed = this.menuService.isMenuPreviewCollapsed(this.menuID);
|
this.sidebarPreviewCollapsed = this.menuService.isMenuPreviewCollapsed(this.menuID);
|
||||||
this.expanded = combineLatestObservable(this.active, this.sidebarCollapsed, this.sidebarPreviewCollapsed)
|
this.expanded = combineLatestObservable(this.active, this.sidebarCollapsed, this.sidebarPreviewCollapsed)
|
||||||
|
@@ -18,6 +18,8 @@ import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-
|
|||||||
import { getMockLinkService } from '../../../../../shared/mocks/link-service.mock';
|
import { getMockLinkService } from '../../../../../shared/mocks/link-service.mock';
|
||||||
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
import { DSONameServiceMock } from '../../../../../shared/mocks/dso-name.service.mock';
|
||||||
|
import { APP_CONFIG } from '../../../../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../../../../environments/environment';
|
||||||
|
|
||||||
describe('WorkflowItemAdminWorkflowListElementComponent', () => {
|
describe('WorkflowItemAdminWorkflowListElementComponent', () => {
|
||||||
let component: WorkflowItemSearchResultAdminWorkflowListElementComponent;
|
let component: WorkflowItemSearchResultAdminWorkflowListElementComponent;
|
||||||
@@ -51,7 +53,8 @@ describe('WorkflowItemAdminWorkflowListElementComponent', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: TruncatableService, useValue: mockTruncatableService },
|
{ provide: TruncatableService, useValue: mockTruncatableService },
|
||||||
{ provide: LinkService, useValue: linkService },
|
{ provide: LinkService, useValue: linkService },
|
||||||
{ provide: DSONameService, useClass: DSONameServiceMock }
|
{ provide: DSONameService, useClass: DSONameServiceMock },
|
||||||
|
{ provide: APP_CONFIG, useValue: environment }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
})
|
})
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||||
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||||
import { Context } from '../../../../../core/shared/context.model';
|
import { Context } from '../../../../../core/shared/context.model';
|
||||||
@@ -13,6 +13,7 @@ import { SearchResultListElementComponent } from '../../../../../shared/object-l
|
|||||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||||
import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
||||||
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { APP_CONFIG, AppConfig } from '../../../../../../config/app-config.interface';
|
||||||
|
|
||||||
@listableObjectComponent(WorkflowItemSearchResult, ViewMode.ListElement, Context.AdminWorkflowSearch)
|
@listableObjectComponent(WorkflowItemSearchResult, ViewMode.ListElement, Context.AdminWorkflowSearch)
|
||||||
@Component({
|
@Component({
|
||||||
@@ -32,9 +33,10 @@ export class WorkflowItemSearchResultAdminWorkflowListElementComponent extends S
|
|||||||
|
|
||||||
constructor(private linkService: LinkService,
|
constructor(private linkService: LinkService,
|
||||||
protected truncatableService: TruncatableService,
|
protected truncatableService: TruncatableService,
|
||||||
protected dsoNameService: DSONameService
|
protected dsoNameService: DSONameService,
|
||||||
|
@Inject(APP_CONFIG) protected appConfig: AppConfig
|
||||||
) {
|
) {
|
||||||
super(truncatableService, dsoNameService);
|
super(truncatableService, dsoNameService, appConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -9,6 +9,7 @@ import { AdminWorkflowModuleModule } from './admin-workflow-page/admin-workflow.
|
|||||||
import { AdminSearchModule } from './admin-search-page/admin-search.module';
|
import { AdminSearchModule } from './admin-search-page/admin-search.module';
|
||||||
import { AdminSidebarSectionComponent } from './admin-sidebar/admin-sidebar-section/admin-sidebar-section.component';
|
import { AdminSidebarSectionComponent } from './admin-sidebar/admin-sidebar-section/admin-sidebar-section.component';
|
||||||
import { ExpandableAdminSidebarSectionComponent } from './admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component';
|
import { ExpandableAdminSidebarSectionComponent } from './admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component';
|
||||||
|
import { BatchImportPageComponent } from './admin-import-batch-page/batch-import-page.component';
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
@@ -28,7 +29,8 @@ const ENTRY_COMPONENTS = [
|
|||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AdminCurationTasksComponent,
|
AdminCurationTasksComponent,
|
||||||
MetadataImportPageComponent
|
MetadataImportPageComponent,
|
||||||
|
BatchImportPageComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class AdminModule {
|
export class AdminModule {
|
||||||
|
@@ -1,10 +1,9 @@
|
|||||||
import { Store, StoreModule } from '@ngrx/store';
|
import { Store, StoreModule } from '@ngrx/store';
|
||||||
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
import { CommonModule, DOCUMENT } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
import { Angulartics2GoogleAnalytics } from 'angulartics2';
|
|
||||||
|
|
||||||
// Load the implementations that should be tested
|
// Load the implementations that should be tested
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
@@ -19,7 +18,7 @@ import { AngularticsProviderMock } from './shared/mocks/angulartics-provider.ser
|
|||||||
import { AuthServiceMock } from './shared/mocks/auth.service.mock';
|
import { AuthServiceMock } from './shared/mocks/auth.service.mock';
|
||||||
import { AuthService } from './core/auth/auth.service';
|
import { AuthService } from './core/auth/auth.service';
|
||||||
import { MenuService } from './shared/menu/menu.service';
|
import { MenuService } from './shared/menu/menu.service';
|
||||||
import { CSSVariableService } from './shared/sass-helper/sass-helper.service';
|
import { CSSVariableService } from './shared/sass-helper/css-variable.service';
|
||||||
import { CSSVariableServiceStub } from './shared/testing/css-variable-service.stub';
|
import { CSSVariableServiceStub } from './shared/testing/css-variable-service.stub';
|
||||||
import { MenuServiceStub } from './shared/testing/menu-service.stub';
|
import { MenuServiceStub } from './shared/testing/menu-service.stub';
|
||||||
import { HostWindowService } from './shared/host-window.service';
|
import { HostWindowService } from './shared/host-window.service';
|
||||||
@@ -32,7 +31,6 @@ import { storeModuleConfig } from './app.reducer';
|
|||||||
import { LocaleService } from './core/locale/locale.service';
|
import { LocaleService } from './core/locale/locale.service';
|
||||||
import { authReducer } from './core/auth/auth.reducer';
|
import { authReducer } from './core/auth/auth.reducer';
|
||||||
import { provideMockStore } from '@ngrx/store/testing';
|
import { provideMockStore } from '@ngrx/store/testing';
|
||||||
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
|
||||||
import { ThemeService } from './shared/theme-support/theme.service';
|
import { ThemeService } from './shared/theme-support/theme.service';
|
||||||
import { getMockThemeService } from './shared/mocks/theme-service.mock';
|
import { getMockThemeService } from './shared/mocks/theme-service.mock';
|
||||||
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
||||||
@@ -46,16 +44,16 @@ const initialState = {
|
|||||||
core: { auth: { loading: false } }
|
core: { auth: { loading: false } }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getMockLocaleService(): LocaleService {
|
||||||
|
return jasmine.createSpyObj('LocaleService', {
|
||||||
|
setCurrentLanguageCode: jasmine.createSpy('setCurrentLanguageCode')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
describe('App component', () => {
|
describe('App component', () => {
|
||||||
|
|
||||||
let breadcrumbsServiceSpy;
|
let breadcrumbsServiceSpy;
|
||||||
|
|
||||||
function getMockLocaleService(): LocaleService {
|
|
||||||
return jasmine.createSpyObj('LocaleService', {
|
|
||||||
setCurrentLanguageCode: jasmine.createSpy('setCurrentLanguageCode')
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const getDefaultTestBedConf = () => {
|
const getDefaultTestBedConf = () => {
|
||||||
breadcrumbsServiceSpy = jasmine.createSpyObj(['listenForRouteChanges']);
|
breadcrumbsServiceSpy = jasmine.createSpyObj(['listenForRouteChanges']);
|
||||||
|
|
||||||
@@ -74,7 +72,6 @@ describe('App component', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: NativeWindowService, useValue: new NativeWindowRef() },
|
{ provide: NativeWindowService, useValue: new NativeWindowRef() },
|
||||||
{ provide: MetadataService, useValue: new MetadataServiceMock() },
|
{ provide: MetadataService, useValue: new MetadataServiceMock() },
|
||||||
{ provide: Angulartics2GoogleAnalytics, useValue: new AngularticsProviderMock() },
|
|
||||||
{ provide: Angulartics2DSpace, useValue: new AngularticsProviderMock() },
|
{ provide: Angulartics2DSpace, useValue: new AngularticsProviderMock() },
|
||||||
{ provide: AuthService, useValue: new AuthServiceMock() },
|
{ provide: AuthService, useValue: new AuthServiceMock() },
|
||||||
{ provide: Router, useValue: new RouterMock() },
|
{ provide: Router, useValue: new RouterMock() },
|
||||||
@@ -130,66 +127,4 @@ describe('App component', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('the constructor', () => {
|
|
||||||
it('should call breadcrumbsService.listenForRouteChanges', () => {
|
|
||||||
expect(breadcrumbsServiceSpy.listenForRouteChanges).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when GoogleAnalyticsService is provided', () => {
|
|
||||||
let googleAnalyticsSpy;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// NOTE: Cannot override providers once components have been compiled, so TestBed needs to be reset
|
|
||||||
TestBed.resetTestingModule();
|
|
||||||
TestBed.configureTestingModule(getDefaultTestBedConf());
|
|
||||||
googleAnalyticsSpy = jasmine.createSpyObj('googleAnalyticsService', [
|
|
||||||
'addTrackingIdToPage',
|
|
||||||
]);
|
|
||||||
TestBed.overrideProvider(GoogleAnalyticsService, {useValue: googleAnalyticsSpy});
|
|
||||||
fixture = TestBed.createComponent(AppComponent);
|
|
||||||
comp = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create component', () => {
|
|
||||||
expect(comp).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('the constructor', () => {
|
|
||||||
it('should call googleAnalyticsService.addTrackingIdToPage()', () => {
|
|
||||||
expect(googleAnalyticsSpy.addTrackingIdToPage).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when ThemeService returns a custom theme', () => {
|
|
||||||
let document;
|
|
||||||
let headSpy;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
// NOTE: Cannot override providers once components have been compiled, so TestBed needs to be reset
|
|
||||||
TestBed.resetTestingModule();
|
|
||||||
TestBed.configureTestingModule(getDefaultTestBedConf());
|
|
||||||
TestBed.overrideProvider(ThemeService, {useValue: getMockThemeService('custom')});
|
|
||||||
document = TestBed.inject(DOCUMENT);
|
|
||||||
headSpy = jasmine.createSpyObj('head', ['appendChild', 'getElementsByClassName']);
|
|
||||||
headSpy.getElementsByClassName.and.returnValue([]);
|
|
||||||
spyOn(document, 'getElementsByTagName').and.returnValue([headSpy]);
|
|
||||||
fixture = TestBed.createComponent(AppComponent);
|
|
||||||
comp = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should append a link element with the correct attributes to the head element', () => {
|
|
||||||
const link = document.createElement('link');
|
|
||||||
link.setAttribute('rel', 'stylesheet');
|
|
||||||
link.setAttribute('type', 'text/css');
|
|
||||||
link.setAttribute('class', 'theme-css');
|
|
||||||
link.setAttribute('href', 'custom-theme.css');
|
|
||||||
|
|
||||||
expect(headSpy.appendChild).toHaveBeenCalledWith(link);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { distinctUntilChanged, filter, switchMap, take, withLatestFrom } from 'rxjs/operators';
|
import { distinctUntilChanged, take, withLatestFrom } from 'rxjs/operators';
|
||||||
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
||||||
import {
|
import {
|
||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
@@ -7,49 +7,30 @@ import {
|
|||||||
HostListener,
|
HostListener,
|
||||||
Inject,
|
Inject,
|
||||||
OnInit,
|
OnInit,
|
||||||
Optional,
|
|
||||||
PLATFORM_ID,
|
PLATFORM_ID,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
|
||||||
ActivationEnd,
|
|
||||||
NavigationCancel,
|
NavigationCancel,
|
||||||
NavigationEnd,
|
NavigationEnd,
|
||||||
NavigationStart, ResolveEnd,
|
NavigationStart,
|
||||||
Router,
|
Router,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
|
||||||
import { isEqual } from 'lodash';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import { BehaviorSubject, Observable, of } from 'rxjs';
|
|
||||||
import { select, Store } from '@ngrx/store';
|
import { select, Store } from '@ngrx/store';
|
||||||
import { NgbModal, NgbModalConfig } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal, NgbModalConfig } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Angulartics2GoogleAnalytics } from 'angulartics2';
|
|
||||||
|
|
||||||
import { MetadataService } from './core/metadata/metadata.service';
|
|
||||||
import { HostWindowResizeAction } from './shared/host-window.actions';
|
import { HostWindowResizeAction } from './shared/host-window.actions';
|
||||||
import { HostWindowState } from './shared/search/host-window.reducer';
|
import { HostWindowState } from './shared/search/host-window.reducer';
|
||||||
import { NativeWindowRef, NativeWindowService } from './core/services/window.service';
|
import { NativeWindowRef, NativeWindowService } from './core/services/window.service';
|
||||||
import { isAuthenticationBlocking } from './core/auth/selectors';
|
import { isAuthenticationBlocking } from './core/auth/selectors';
|
||||||
import { AuthService } from './core/auth/auth.service';
|
import { AuthService } from './core/auth/auth.service';
|
||||||
import { CSSVariableService } from './shared/sass-helper/sass-helper.service';
|
import { CSSVariableService } from './shared/sass-helper/css-variable.service';
|
||||||
import { MenuService } from './shared/menu/menu.service';
|
|
||||||
import { HostWindowService } from './shared/host-window.service';
|
|
||||||
import { HeadTagConfig, ThemeConfig } from '../config/theme.model';
|
|
||||||
import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider';
|
|
||||||
import { environment } from '../environments/environment';
|
import { environment } from '../environments/environment';
|
||||||
import { models } from './core/core.module';
|
import { models } from './core/core.module';
|
||||||
import { LocaleService } from './core/locale/locale.service';
|
|
||||||
import { hasNoValue, hasValue, isNotEmpty } from './shared/empty.util';
|
|
||||||
import { KlaroService } from './shared/cookies/klaro.service';
|
|
||||||
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
|
||||||
import { ThemeService } from './shared/theme-support/theme.service';
|
import { ThemeService } from './shared/theme-support/theme.service';
|
||||||
import { BASE_THEME_NAME } from './shared/theme-support/theme.constants';
|
|
||||||
import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
|
||||||
import { IdleModalComponent } from './shared/idle-modal/idle-modal.component';
|
import { IdleModalComponent } from './shared/idle-modal/idle-modal.component';
|
||||||
import { getDefaultThemeConfig } from '../config/config.util';
|
import { distinctNext } from './core/shared/distinct-next';
|
||||||
import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface';
|
|
||||||
import { ModalBeforeDismiss } from './shared/interfaces/modal-before-dismiss.interface';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-app',
|
selector: 'ds-app',
|
||||||
@@ -58,11 +39,6 @@ import { ModalBeforeDismiss } from './shared/interfaces/modal-before-dismiss.int
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit, AfterViewInit {
|
export class AppComponent implements OnInit, AfterViewInit {
|
||||||
sidebarVisible: Observable<boolean>;
|
|
||||||
slideSidebarOver: Observable<boolean>;
|
|
||||||
collapsedSidebarWidth: Observable<string>;
|
|
||||||
totalSidebarWidth: Observable<string>;
|
|
||||||
theme: Observable<ThemeConfig> = of({} as any);
|
|
||||||
notificationOptions;
|
notificationOptions;
|
||||||
models;
|
models;
|
||||||
|
|
||||||
@@ -79,9 +55,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
/**
|
/**
|
||||||
* Whether or not the theme is in the process of being swapped
|
* Whether or not the theme is in the process of being swapped
|
||||||
*/
|
*/
|
||||||
isThemeLoading$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
isThemeLoading$: Observable<boolean>;
|
||||||
|
|
||||||
isThemeCSSLoading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the idle modal is is currently open
|
* Whether or not the idle modal is is currently open
|
||||||
@@ -92,78 +66,26 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
||||||
@Inject(DOCUMENT) private document: any,
|
@Inject(DOCUMENT) private document: any,
|
||||||
@Inject(PLATFORM_ID) private platformId: any,
|
@Inject(PLATFORM_ID) private platformId: any,
|
||||||
@Inject(APP_CONFIG) private appConfig: AppConfig,
|
|
||||||
private themeService: ThemeService,
|
private themeService: ThemeService,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private store: Store<HostWindowState>,
|
private store: Store<HostWindowState>,
|
||||||
private metadata: MetadataService,
|
|
||||||
private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics,
|
|
||||||
private angulartics2DSpace: Angulartics2DSpace,
|
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private cssService: CSSVariableService,
|
private cssService: CSSVariableService,
|
||||||
private menuService: MenuService,
|
|
||||||
private windowService: HostWindowService,
|
|
||||||
private localeService: LocaleService,
|
|
||||||
private breadcrumbsService: BreadcrumbsService,
|
|
||||||
private modalService: NgbModal,
|
private modalService: NgbModal,
|
||||||
private modalConfig: NgbModalConfig,
|
private modalConfig: NgbModalConfig,
|
||||||
@Optional() private cookiesService: KlaroService,
|
|
||||||
@Optional() private googleAnalyticsService: GoogleAnalyticsService,
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
if (!isEqual(environment, this.appConfig)) {
|
|
||||||
throw new Error('environment does not match app config!');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.notificationOptions = environment.notifications;
|
this.notificationOptions = environment.notifications;
|
||||||
|
|
||||||
/* Use models object so all decorators are actually called */
|
/* Use models object so all decorators are actually called */
|
||||||
this.models = models;
|
this.models = models;
|
||||||
|
|
||||||
this.themeService.getThemeName$().subscribe((themeName: string) => {
|
|
||||||
if (isPlatformBrowser(this.platformId)) {
|
|
||||||
// the theme css will never download server side, so this should only happen on the browser
|
|
||||||
this.distinctNext(this.isThemeCSSLoading$, true);
|
|
||||||
}
|
|
||||||
if (hasValue(themeName)) {
|
|
||||||
this.loadGlobalThemeConfig(themeName);
|
|
||||||
} else {
|
|
||||||
const defaultThemeConfig = getDefaultThemeConfig();
|
|
||||||
if (hasValue(defaultThemeConfig)) {
|
|
||||||
this.loadGlobalThemeConfig(defaultThemeConfig.name);
|
|
||||||
} else {
|
|
||||||
this.loadGlobalThemeConfig(BASE_THEME_NAME);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isPlatformBrowser(this.platformId)) {
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
this.authService.trackTokenExpiration();
|
|
||||||
this.trackIdleModal();
|
this.trackIdleModal();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load all the languages that are defined as active from the config file
|
this.isThemeLoading$ = this.themeService.isThemeLoading$;
|
||||||
translate.addLangs(environment.languages.filter((LangConfig) => LangConfig.active === true).map((a) => a.code));
|
|
||||||
|
|
||||||
// Load the default language from the config file
|
|
||||||
// translate.setDefaultLang(environment.defaultLanguage);
|
|
||||||
|
|
||||||
// set the current language code
|
|
||||||
this.localeService.setCurrentLanguageCode();
|
|
||||||
|
|
||||||
// analytics
|
|
||||||
if (hasValue(googleAnalyticsService)) {
|
|
||||||
googleAnalyticsService.addTrackingIdToPage();
|
|
||||||
}
|
|
||||||
angulartics2DSpace.startTracking();
|
|
||||||
|
|
||||||
metadata.listenForRouteChange();
|
|
||||||
breadcrumbsService.listenForRouteChanges();
|
|
||||||
|
|
||||||
if (environment.debug) {
|
|
||||||
console.info(environment);
|
|
||||||
}
|
|
||||||
this.storeCSSVariables();
|
this.storeCSSVariables();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,88 +100,32 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.isAuthBlocking$ = this.store.pipe(select(isAuthenticationBlocking)).pipe(
|
this.isAuthBlocking$ = this.store.pipe(
|
||||||
|
select(isAuthenticationBlocking),
|
||||||
distinctUntilChanged()
|
distinctUntilChanged()
|
||||||
);
|
);
|
||||||
this.isAuthBlocking$
|
|
||||||
.pipe(
|
|
||||||
filter((isBlocking: boolean) => isBlocking === false),
|
|
||||||
take(1)
|
|
||||||
).subscribe(() => this.initializeKlaro());
|
|
||||||
|
|
||||||
const env: string = environment.production ? 'Production' : 'Development';
|
|
||||||
const color: string = environment.production ? 'red' : 'green';
|
|
||||||
console.info(`Environment: %c${env}`, `color: ${color}; font-weight: bold;`);
|
|
||||||
this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight);
|
this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
private storeCSSVariables() {
|
private storeCSSVariables() {
|
||||||
this.cssService.addCSSVariable('xlMin', '1200px');
|
this.cssService.clearCSSVariables();
|
||||||
this.cssService.addCSSVariable('mdMin', '768px');
|
this.cssService.addCSSVariables(this.cssService.getCSSVariablesFromStylesheets(this.document));
|
||||||
this.cssService.addCSSVariable('lgMin', '576px');
|
|
||||||
this.cssService.addCSSVariable('smMin', '0');
|
|
||||||
this.cssService.addCSSVariable('adminSidebarActiveBg', '#0f1b28');
|
|
||||||
this.cssService.addCSSVariable('sidebarItemsWidth', '250px');
|
|
||||||
this.cssService.addCSSVariable('collapsedSidebarWidth', '53.234px');
|
|
||||||
this.cssService.addCSSVariable('totalSidebarWidth', '303.234px');
|
|
||||||
// const vars = variables.locals || {};
|
|
||||||
// Object.keys(vars).forEach((name: string) => {
|
|
||||||
// this.cssService.addCSSVariable(name, vars[name]);
|
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit() {
|
ngAfterViewInit() {
|
||||||
let updatingTheme = false;
|
|
||||||
let snapshot: ActivatedRouteSnapshot;
|
|
||||||
|
|
||||||
this.router.events.subscribe((event) => {
|
this.router.events.subscribe((event) => {
|
||||||
if (event instanceof NavigationStart) {
|
if (event instanceof NavigationStart) {
|
||||||
updatingTheme = false;
|
distinctNext(this.isRouteLoading$, true);
|
||||||
this.distinctNext(this.isRouteLoading$, true);
|
} else if (
|
||||||
} else if (event instanceof ResolveEnd) {
|
event instanceof NavigationEnd ||
|
||||||
// this is the earliest point where we have all the information we need
|
event instanceof NavigationCancel
|
||||||
// to update the theme, but this event is not emitted on first load
|
) {
|
||||||
this.updateTheme(event.urlAfterRedirects, event.state.root);
|
distinctNext(this.isRouteLoading$, false);
|
||||||
updatingTheme = true;
|
|
||||||
} else if (!updatingTheme && event instanceof ActivationEnd) {
|
|
||||||
// if there was no ResolveEnd, keep track of the snapshot...
|
|
||||||
snapshot = event.snapshot;
|
|
||||||
} else if (event instanceof NavigationEnd) {
|
|
||||||
if (!updatingTheme) {
|
|
||||||
// ...and use it to update the theme on NavigationEnd instead
|
|
||||||
this.updateTheme(event.urlAfterRedirects, snapshot);
|
|
||||||
updatingTheme = true;
|
|
||||||
}
|
|
||||||
this.distinctNext(this.isRouteLoading$, false);
|
|
||||||
} else if (event instanceof NavigationCancel) {
|
|
||||||
if (!updatingTheme) {
|
|
||||||
this.distinctNext(this.isThemeLoading$, false);
|
|
||||||
}
|
|
||||||
this.distinctNext(this.isRouteLoading$, false);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the theme according to the current route, if applicable.
|
|
||||||
* @param urlAfterRedirects the current URL after redirects
|
|
||||||
* @param snapshot the current route snapshot
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private updateTheme(urlAfterRedirects: string, snapshot: ActivatedRouteSnapshot): void {
|
|
||||||
this.themeService.updateThemeOnRouteChange$(urlAfterRedirects, snapshot).pipe(
|
|
||||||
switchMap((changed) => {
|
|
||||||
if (changed) {
|
|
||||||
return this.isThemeCSSLoading$;
|
|
||||||
} else {
|
|
||||||
return [false];
|
|
||||||
}
|
|
||||||
})
|
|
||||||
).subscribe((changed) => {
|
|
||||||
this.distinctNext(this.isThemeLoading$, changed);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
public onResize(event): void {
|
public onResize(event): void {
|
||||||
this.dispatchWindowSize(event.target.innerWidth, event.target.innerHeight);
|
this.dispatchWindowSize(event.target.innerWidth, event.target.innerHeight);
|
||||||
@@ -271,125 +137,6 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private initializeKlaro() {
|
|
||||||
if (hasValue(this.cookiesService)) {
|
|
||||||
this.cookiesService.initialize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private loadGlobalThemeConfig(themeName: string): void {
|
|
||||||
this.setThemeCss(themeName);
|
|
||||||
this.setHeadTags(themeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the theme css file in <head>
|
|
||||||
*
|
|
||||||
* @param themeName The name of the new theme
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
private setThemeCss(themeName: string): void {
|
|
||||||
const head = this.document.getElementsByTagName('head')[0];
|
|
||||||
if (hasNoValue(head)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Array.from to ensure we end up with an array, not an HTMLCollection, which would be
|
|
||||||
// automatically updated if we add nodes later
|
|
||||||
const currentThemeLinks = Array.from(head.getElementsByClassName('theme-css'));
|
|
||||||
const link = this.document.createElement('link');
|
|
||||||
link.setAttribute('rel', 'stylesheet');
|
|
||||||
link.setAttribute('type', 'text/css');
|
|
||||||
link.setAttribute('class', 'theme-css');
|
|
||||||
link.setAttribute('href', `${encodeURIComponent(themeName)}-theme.css`);
|
|
||||||
// wait for the new css to download before removing the old one to prevent a
|
|
||||||
// flash of unstyled content
|
|
||||||
link.onload = () => {
|
|
||||||
if (isNotEmpty(currentThemeLinks)) {
|
|
||||||
currentThemeLinks.forEach((currentThemeLink: any) => {
|
|
||||||
if (hasValue(currentThemeLink)) {
|
|
||||||
currentThemeLink.remove();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// the fact that this callback is used, proves we're on the browser.
|
|
||||||
this.distinctNext(this.isThemeCSSLoading$, false);
|
|
||||||
};
|
|
||||||
head.appendChild(link);
|
|
||||||
}
|
|
||||||
|
|
||||||
private setHeadTags(themeName: string): void {
|
|
||||||
const head = this.document.getElementsByTagName('head')[0];
|
|
||||||
if (hasNoValue(head)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear head tags
|
|
||||||
const currentHeadTags = Array.from(head.getElementsByClassName('theme-head-tag'));
|
|
||||||
if (hasValue(currentHeadTags)) {
|
|
||||||
currentHeadTags.forEach((currentHeadTag: any) => currentHeadTag.remove());
|
|
||||||
}
|
|
||||||
|
|
||||||
// create new head tags (not yet added to DOM)
|
|
||||||
const headTagFragment = this.document.createDocumentFragment();
|
|
||||||
this.createHeadTags(themeName)
|
|
||||||
.forEach(newHeadTag => headTagFragment.appendChild(newHeadTag));
|
|
||||||
|
|
||||||
// add new head tags to DOM
|
|
||||||
head.appendChild(headTagFragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
private createHeadTags(themeName: string): HTMLElement[] {
|
|
||||||
const themeConfig = this.themeService.getThemeConfigFor(themeName);
|
|
||||||
const headTagConfigs = themeConfig?.headTags;
|
|
||||||
|
|
||||||
if (hasNoValue(headTagConfigs)) {
|
|
||||||
const parentThemeName = themeConfig?.extends;
|
|
||||||
if (hasValue(parentThemeName)) {
|
|
||||||
// inherit the head tags of the parent theme
|
|
||||||
return this.createHeadTags(parentThemeName);
|
|
||||||
}
|
|
||||||
const defaultThemeConfig = getDefaultThemeConfig();
|
|
||||||
const defaultThemeName = defaultThemeConfig.name;
|
|
||||||
if (
|
|
||||||
hasNoValue(defaultThemeName) ||
|
|
||||||
themeName === defaultThemeName ||
|
|
||||||
themeName === BASE_THEME_NAME
|
|
||||||
) {
|
|
||||||
// last resort, use fallback favicon.ico
|
|
||||||
return [
|
|
||||||
this.createHeadTag({
|
|
||||||
'tagName': 'link',
|
|
||||||
'attributes': {
|
|
||||||
'rel': 'icon',
|
|
||||||
'href': 'assets/images/favicon.ico',
|
|
||||||
'sizes': 'any',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// inherit the head tags of the default theme
|
|
||||||
return this.createHeadTags(defaultThemeConfig.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return headTagConfigs.map(this.createHeadTag.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private createHeadTag(headTagConfig: HeadTagConfig): HTMLElement {
|
|
||||||
const tag = this.document.createElement(headTagConfig.tagName);
|
|
||||||
|
|
||||||
if (hasValue(headTagConfig.attributes)) {
|
|
||||||
Object.entries(headTagConfig.attributes)
|
|
||||||
.forEach(([key, value]) => tag.setAttribute(key, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'class' attribute should always be 'theme-head-tag' for removal
|
|
||||||
tag.setAttribute('class', 'theme-head-tag');
|
|
||||||
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
|
|
||||||
private trackIdleModal() {
|
private trackIdleModal() {
|
||||||
const isIdle$ = this.authService.isUserIdle();
|
const isIdle$ = this.authService.isUserIdle();
|
||||||
const isAuthenticated$ = this.authService.isAuthenticated();
|
const isAuthenticated$ = this.authService.isAuthenticated();
|
||||||
@@ -409,16 +156,4 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Use nextValue to update a given BehaviorSubject, only if it differs from its current value
|
|
||||||
*
|
|
||||||
* @param bs a BehaviorSubject
|
|
||||||
* @param nextValue the next value for that BehaviorSubject
|
|
||||||
* @protected
|
|
||||||
*/
|
|
||||||
protected distinctNext<T>(bs: BehaviorSubject<T>, nextValue: T): void {
|
|
||||||
if (bs.getValue() !== nextValue) {
|
|
||||||
bs.next(nextValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
25
src/app/app.module.ts
Executable file → Normal file
25
src/app/app.module.ts
Executable file → Normal file
@@ -1,18 +1,14 @@
|
|||||||
import { APP_BASE_HREF, CommonModule, DOCUMENT } from '@angular/common';
|
import { APP_BASE_HREF, CommonModule, DOCUMENT } from '@angular/common';
|
||||||
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
|
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
|
||||||
import { APP_INITIALIZER, NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { AbstractControl } from '@angular/forms';
|
import { AbstractControl } from '@angular/forms';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { EffectsModule } from '@ngrx/effects';
|
import { EffectsModule } from '@ngrx/effects';
|
||||||
import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store';
|
import { RouterStateSerializer, StoreRouterConnectingModule } from '@ngrx/router-store';
|
||||||
import { MetaReducer, Store, StoreModule, USER_PROVIDED_META_REDUCERS } from '@ngrx/store';
|
import { MetaReducer, StoreModule, USER_PROVIDED_META_REDUCERS } from '@ngrx/store';
|
||||||
import {
|
import { DYNAMIC_ERROR_MESSAGES_MATCHER, DYNAMIC_MATCHER_PROVIDERS, DynamicErrorMessagesMatcher } from '@ng-dynamic-forms/core';
|
||||||
DYNAMIC_ERROR_MESSAGES_MATCHER,
|
|
||||||
DYNAMIC_MATCHER_PROVIDERS,
|
|
||||||
DynamicErrorMessagesMatcher
|
|
||||||
} from '@ng-dynamic-forms/core';
|
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to';
|
import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to';
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
@@ -20,7 +16,6 @@ import { AppComponent } from './app.component';
|
|||||||
import { appEffects } from './app.effects';
|
import { appEffects } from './app.effects';
|
||||||
import { appMetaReducers, debugMetaReducers } from './app.metareducers';
|
import { appMetaReducers, debugMetaReducers } from './app.metareducers';
|
||||||
import { appReducers, AppState, storeModuleConfig } from './app.reducer';
|
import { appReducers, AppState, storeModuleConfig } from './app.reducer';
|
||||||
import { CheckAuthenticationTokenAction } from './core/auth/auth.actions';
|
|
||||||
import { CoreModule } from './core/core.module';
|
import { CoreModule } from './core/core.module';
|
||||||
import { ClientCookieService } from './core/services/client-cookie.service';
|
import { ClientCookieService } from './core/services/client-cookie.service';
|
||||||
import { NavbarModule } from './navbar/navbar.module';
|
import { NavbarModule } from './navbar/navbar.module';
|
||||||
@@ -32,7 +27,6 @@ import { LocaleInterceptor } from './core/locale/locale.interceptor';
|
|||||||
import { XsrfInterceptor } from './core/xsrf/xsrf.interceptor';
|
import { XsrfInterceptor } from './core/xsrf/xsrf.interceptor';
|
||||||
import { LogInterceptor } from './core/log/log.interceptor';
|
import { LogInterceptor } from './core/log/log.interceptor';
|
||||||
import { EagerThemesModule } from '../themes/eager-themes.module';
|
import { EagerThemesModule } from '../themes/eager-themes.module';
|
||||||
|
|
||||||
import { APP_CONFIG, AppConfig } from '../config/app-config.interface';
|
import { APP_CONFIG, AppConfig } from '../config/app-config.interface';
|
||||||
import { NgxMaskModule } from 'ngx-mask';
|
import { NgxMaskModule } from 'ngx-mask';
|
||||||
import { StoreDevModules } from '../config/store/devtools';
|
import { StoreDevModules } from '../config/store/devtools';
|
||||||
@@ -80,10 +74,6 @@ const IMPORTS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const PROVIDERS = [
|
const PROVIDERS = [
|
||||||
{
|
|
||||||
provide: APP_CONFIG,
|
|
||||||
useFactory: getConfig
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: APP_BASE_HREF,
|
provide: APP_BASE_HREF,
|
||||||
useFactory: getBaseHref,
|
useFactory: getBaseHref,
|
||||||
@@ -99,15 +89,6 @@ const PROVIDERS = [
|
|||||||
useClass: DSpaceRouterStateSerializer
|
useClass: DSpaceRouterStateSerializer
|
||||||
},
|
},
|
||||||
ClientCookieService,
|
ClientCookieService,
|
||||||
// Check the authentication token when the app initializes
|
|
||||||
{
|
|
||||||
provide: APP_INITIALIZER,
|
|
||||||
useFactory: (store: Store<AppState>,) => {
|
|
||||||
return () => store.dispatch(new CheckAuthenticationTokenAction());
|
|
||||||
},
|
|
||||||
deps: [Store],
|
|
||||||
multi: true
|
|
||||||
},
|
|
||||||
// register AuthInterceptor as HttpInterceptor
|
// register AuthInterceptor as HttpInterceptor
|
||||||
{
|
{
|
||||||
provide: HTTP_INTERCEPTORS,
|
provide: HTTP_INTERCEPTORS,
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import * as fromRouter from '@ngrx/router-store';
|
import { routerReducer, RouterReducerState } from '@ngrx/router-store';
|
||||||
import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store';
|
import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store';
|
||||||
import {
|
import {
|
||||||
ePeopleRegistryReducer,
|
ePeopleRegistryReducer,
|
||||||
@@ -35,7 +35,7 @@ import {
|
|||||||
ObjectSelectionListState,
|
ObjectSelectionListState,
|
||||||
objectSelectionReducer
|
objectSelectionReducer
|
||||||
} from './shared/object-select/object-select.reducer';
|
} from './shared/object-select/object-select.reducer';
|
||||||
import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer';
|
import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/css-variable.reducer';
|
||||||
|
|
||||||
import { hostWindowReducer, HostWindowState } from './shared/search/host-window.reducer';
|
import { hostWindowReducer, HostWindowState } from './shared/search/host-window.reducer';
|
||||||
import {
|
import {
|
||||||
@@ -53,7 +53,7 @@ import { MenusState } from './shared/menu/menus-state.model';
|
|||||||
import { correlationIdReducer } from './correlation-id/correlation-id.reducer';
|
import { correlationIdReducer } from './correlation-id/correlation-id.reducer';
|
||||||
|
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
router: fromRouter.RouterReducerState;
|
router: RouterReducerState;
|
||||||
hostWindow: HostWindowState;
|
hostWindow: HostWindowState;
|
||||||
forms: FormState;
|
forms: FormState;
|
||||||
metadataRegistry: MetadataRegistryState;
|
metadataRegistry: MetadataRegistryState;
|
||||||
@@ -75,7 +75,7 @@ export interface AppState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const appReducers: ActionReducerMap<AppState> = {
|
export const appReducers: ActionReducerMap<AppState> = {
|
||||||
router: fromRouter.routerReducer,
|
router: routerReducer,
|
||||||
hostWindow: hostWindowReducer,
|
hostWindow: hostWindowReducer,
|
||||||
forms: formReducer,
|
forms: formReducer,
|
||||||
metadataRegistry: metadataRegistryReducer,
|
metadataRegistry: metadataRegistryReducer,
|
||||||
|
@@ -27,7 +27,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ds-error *ngIf="bitstreamRD?.hasFailed" message="{{'error.bitstream' | translate}}"></ds-error>
|
<ds-error *ngIf="bitstreamRD?.hasFailed" message="{{'error.bitstream' | translate}}"></ds-error>
|
||||||
<ds-loading *ngIf="!bitstreamRD || !formatsRD || bitstreamRD?.isLoading || formatsRD?.isLoading"
|
<ds-themed-loading *ngIf="!bitstreamRD || !formatsRD || bitstreamRD?.isLoading || formatsRD?.isLoading"
|
||||||
message="{{'loading.bitstream' | translate}}"></ds-loading>
|
message="{{'loading.bitstream' | translate}}"></ds-themed-loading>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@@ -26,7 +26,7 @@ import {
|
|||||||
import { FormGroup } from '@angular/forms';
|
import { FormGroup } from '@angular/forms';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { DynamicCustomSwitchModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
|
import { DynamicCustomSwitchModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
|
||||||
import { cloneDeep } from 'lodash';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
||||||
import {
|
import {
|
||||||
getAllSucceededRemoteDataPayload,
|
getAllSucceededRemoteDataPayload,
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { LegacyBitstreamUrlResolver } from './legacy-bitstream-url.resolver';
|
import { LegacyBitstreamUrlResolver } from './legacy-bitstream-url.resolver';
|
||||||
import { of as observableOf, EMPTY } from 'rxjs';
|
import { EMPTY } from 'rxjs';
|
||||||
import { BitstreamDataService } from '../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../core/data/bitstream-data.service';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
@@ -18,11 +18,10 @@ import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-
|
|||||||
import { toRemoteData } from '../browse-by-metadata-page/browse-by-metadata-page.component.spec';
|
import { toRemoteData } from '../browse-by-metadata-page/browse-by-metadata-page.component.spec';
|
||||||
import { VarDirective } from '../../shared/utils/var.directive';
|
import { VarDirective } from '../../shared/utils/var.directive';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
||||||
import { FindListOptions } from '../../core/data/find-list-options.model';
|
import { APP_CONFIG } from '../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
|
|
||||||
describe('BrowseByDatePageComponent', () => {
|
describe('BrowseByDatePageComponent', () => {
|
||||||
let comp: BrowseByDatePageComponent;
|
let comp: BrowseByDatePageComponent;
|
||||||
@@ -83,7 +82,8 @@ describe('BrowseByDatePageComponent', () => {
|
|||||||
{ provide: DSpaceObjectDataService, useValue: mockDsoService },
|
{ provide: DSpaceObjectDataService, useValue: mockDsoService },
|
||||||
{ provide: Router, useValue: new RouterMock() },
|
{ provide: Router, useValue: new RouterMock() },
|
||||||
{ provide: PaginationService, useValue: paginationService },
|
{ provide: PaginationService, useValue: paginationService },
|
||||||
{ provide: ChangeDetectorRef, useValue: mockCdRef }
|
{ provide: ChangeDetectorRef, useValue: mockCdRef },
|
||||||
|
{ provide: APP_CONFIG, useValue: environment }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
@@ -1,9 +1,8 @@
|
|||||||
import { ChangeDetectorRef, Component } from '@angular/core';
|
import { ChangeDetectorRef, Component, Inject } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
BrowseByMetadataPageComponent,
|
BrowseByMetadataPageComponent,
|
||||||
browseParamsToOptions
|
browseParamsToOptions, getBrowseSearchOptions
|
||||||
} from '../browse-by-metadata-page/browse-by-metadata-page.component';
|
} from '../browse-by-metadata-page/browse-by-metadata-page.component';
|
||||||
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
|
|
||||||
import { combineLatest as observableCombineLatest } from 'rxjs';
|
import { combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
@@ -12,13 +11,12 @@ import { ActivatedRoute, Params, Router } from '@angular/router';
|
|||||||
import { BrowseService } from '../../core/browse/browse.service';
|
import { BrowseService } from '../../core/browse/browse.service';
|
||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
||||||
import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
|
||||||
import { environment } from '../../../environments/environment';
|
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/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 { isValidDate } from '../../shared/date.util';
|
import { isValidDate } from '../../shared/date.util';
|
||||||
|
import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-browse-by-date-page',
|
selector: 'ds-browse-by-date-page',
|
||||||
@@ -30,7 +28,6 @@ import { isValidDate } from '../../shared/date.util';
|
|||||||
* A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields.
|
* A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields.
|
||||||
* An example would be 'dateissued' for 'dc.date.issued'
|
* An example would be 'dateissued' for 'dc.date.issued'
|
||||||
*/
|
*/
|
||||||
@rendersBrowseBy(BrowseByDataType.Date)
|
|
||||||
export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -43,14 +40,16 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
protected dsoService: DSpaceObjectDataService,
|
protected dsoService: DSpaceObjectDataService,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
protected paginationService: PaginationService,
|
protected paginationService: PaginationService,
|
||||||
protected cdRef: ChangeDetectorRef) {
|
protected cdRef: ChangeDetectorRef,
|
||||||
super(route, browseService, dsoService, paginationService, router);
|
@Inject(APP_CONFIG) public appConfig: AppConfig) {
|
||||||
|
super(route, browseService, dsoService, paginationService, router, appConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
const sortConfig = new SortOptions('default', SortDirection.ASC);
|
const sortConfig = new SortOptions('default', SortDirection.ASC);
|
||||||
this.startsWithType = StartsWithType.date;
|
this.startsWithType = StartsWithType.date;
|
||||||
this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig));
|
// include the thumbnail configuration in browse search options
|
||||||
|
this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig, this.fetchThumbnails));
|
||||||
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
||||||
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
|
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
@@ -63,7 +62,7 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
const metadataKeys = params.browseDefinition ? params.browseDefinition.metadataKeys : this.defaultMetadataKeys;
|
const metadataKeys = params.browseDefinition ? params.browseDefinition.metadataKeys : this.defaultMetadataKeys;
|
||||||
this.browseId = params.id || this.defaultBrowseId;
|
this.browseId = params.id || this.defaultBrowseId;
|
||||||
this.startsWith = +params.startsWith || params.startsWith;
|
this.startsWith = +params.startsWith || params.startsWith;
|
||||||
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId);
|
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId, this.fetchThumbnails);
|
||||||
this.updatePageWithItems(searchOptions, this.value, undefined);
|
this.updatePageWithItems(searchOptions, this.value, undefined);
|
||||||
this.updateParent(params.scope);
|
this.updateParent(params.scope);
|
||||||
this.updateStartsWithOptions(this.browseId, metadataKeys, params.scope);
|
this.updateStartsWithOptions(this.browseId, metadataKeys, params.scope);
|
||||||
@@ -83,7 +82,7 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
updateStartsWithOptions(definition: string, metadataKeys: string[], scope?: string) {
|
updateStartsWithOptions(definition: string, metadataKeys: string[], scope?: string) {
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
this.browseService.getFirstItemFor(definition, scope).subscribe((firstItemRD: RemoteData<Item>) => {
|
this.browseService.getFirstItemFor(definition, scope).subscribe((firstItemRD: RemoteData<Item>) => {
|
||||||
let lowerLimit = environment.browseBy.defaultLowerLimit;
|
let lowerLimit = this.appConfig.browseBy.defaultLowerLimit;
|
||||||
if (hasValue(firstItemRD.payload)) {
|
if (hasValue(firstItemRD.payload)) {
|
||||||
const date = firstItemRD.payload.firstMetadataValue(metadataKeys);
|
const date = firstItemRD.payload.firstMetadataValue(metadataKeys);
|
||||||
if (isNotEmpty(date) && isValidDate(date)) {
|
if (isNotEmpty(date) && isValidDate(date)) {
|
||||||
@@ -94,8 +93,8 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
}
|
}
|
||||||
const options = [];
|
const options = [];
|
||||||
const currentYear = new Date().getUTCFullYear();
|
const currentYear = new Date().getUTCFullYear();
|
||||||
const oneYearBreak = Math.floor((currentYear - environment.browseBy.oneYearLimit) / 5) * 5;
|
const oneYearBreak = Math.floor((currentYear - this.appConfig.browseBy.oneYearLimit) / 5) * 5;
|
||||||
const fiveYearBreak = Math.floor((currentYear - environment.browseBy.fiveYearLimit) / 10) * 10;
|
const fiveYearBreak = Math.floor((currentYear - this.appConfig.browseBy.fiveYearLimit) / 10) * 10;
|
||||||
if (lowerLimit <= fiveYearBreak) {
|
if (lowerLimit <= fiveYearBreak) {
|
||||||
lowerLimit -= 10;
|
lowerLimit -= 10;
|
||||||
} else if (lowerLimit <= oneYearBreak) {
|
} else if (lowerLimit <= oneYearBreak) {
|
||||||
|
@@ -0,0 +1,29 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
||||||
|
import { BrowseByDatePageComponent } from './browse-by-date-page.component';
|
||||||
|
import {BrowseByDataType, rendersBrowseBy} from '../browse-by-switcher/browse-by-decorator';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for BrowseByDatePageComponent
|
||||||
|
* */
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-browse-by-metadata-page',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: '../../shared/theme-support/themed.component.html',
|
||||||
|
})
|
||||||
|
|
||||||
|
@rendersBrowseBy(BrowseByDataType.Date)
|
||||||
|
export class ThemedBrowseByDatePageComponent
|
||||||
|
extends ThemedComponent<BrowseByDatePageComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'BrowseByDatePageComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../../themes/${themeName}/app/browse-by/browse-by-date-page/browse-by-date-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./browse-by-date-page.component`);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,6 @@
|
|||||||
import { first } from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
import { BrowseByGuard } from './browse-by-guard';
|
import { BrowseByGuard } from './browse-by-guard';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { BrowseDefinitionDataService } from '../core/browse/browse-definition-data.service';
|
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||||
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
||||||
import { BrowseByDataType } from './browse-by-switcher/browse-by-decorator';
|
import { BrowseByDataType } from './browse-by-switcher/browse-by-decorator';
|
||||||
|
@@ -6,10 +6,10 @@
|
|||||||
<ds-comcol-page-header [name]="parentContext.name">
|
<ds-comcol-page-header [name]="parentContext.name">
|
||||||
</ds-comcol-page-header>
|
</ds-comcol-page-header>
|
||||||
<!-- Handle -->
|
<!-- Handle -->
|
||||||
<ds-comcol-page-handle
|
<ds-themed-comcol-page-handle
|
||||||
[content]="parentContext.handle"
|
[content]="parentContext.handle"
|
||||||
[title]="parentContext.type+'.page.handle'" >
|
[title]="parentContext.type+'.page.handle'" >
|
||||||
</ds-comcol-page-handle>
|
</ds-themed-comcol-page-handle>
|
||||||
<!-- Introductory text -->
|
<!-- Introductory text -->
|
||||||
<ds-comcol-page-content [content]="parentContext.introductoryText" [hasInnerHtml]="true">
|
<ds-comcol-page-content [content]="parentContext.introductoryText" [hasInnerHtml]="true">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
(prev)="goPrev()"
|
(prev)="goPrev()"
|
||||||
(next)="goNext()">
|
(next)="goNext()">
|
||||||
</ds-browse-by>
|
</ds-browse-by>
|
||||||
<ds-loading *ngIf="!startsWithOptions" message="{{'loading.browse-by-page' | translate}}"></ds-loading>
|
<ds-themed-loading *ngIf="!startsWithOptions" message="{{'loading.browse-by-page' | translate}}"></ds-themed-loading>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<ng-container *ngVar="(parent$ | async) as parent">
|
<ng-container *ngVar="(parent$ | async) as parent">
|
||||||
|
@@ -1,4 +1,8 @@
|
|||||||
import { BrowseByMetadataPageComponent, browseParamsToOptions } from './browse-by-metadata-page.component';
|
import {
|
||||||
|
BrowseByMetadataPageComponent,
|
||||||
|
browseParamsToOptions,
|
||||||
|
getBrowseSearchOptions
|
||||||
|
} from './browse-by-metadata-page.component';
|
||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { BrowseService } from '../../core/browse/browse.service';
|
import { BrowseService } from '../../core/browse/browse.service';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
@@ -14,7 +18,7 @@ import { RemoteData } from '../../core/data/remote-data';
|
|||||||
import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model';
|
import { buildPaginatedList, PaginatedList } from '../../core/data/paginated-list.model';
|
||||||
import { PageInfo } from '../../core/shared/page-info.model';
|
import { PageInfo } from '../../core/shared/page-info.model';
|
||||||
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
|
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection } from '../../core/cache/models/sort-options.model';
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
@@ -25,6 +29,7 @@ import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.util
|
|||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
||||||
|
import { APP_CONFIG } from '../../../config/app-config.interface';
|
||||||
|
|
||||||
describe('BrowseByMetadataPageComponent', () => {
|
describe('BrowseByMetadataPageComponent', () => {
|
||||||
let comp: BrowseByMetadataPageComponent;
|
let comp: BrowseByMetadataPageComponent;
|
||||||
@@ -43,6 +48,13 @@ describe('BrowseByMetadataPageComponent', () => {
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const environmentMock = {
|
||||||
|
browseBy: {
|
||||||
|
showThumbnails: true,
|
||||||
|
pageSize: 10
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const mockEntries = [
|
const mockEntries = [
|
||||||
{
|
{
|
||||||
type: BrowseEntry.type,
|
type: BrowseEntry.type,
|
||||||
@@ -97,7 +109,8 @@ describe('BrowseByMetadataPageComponent', () => {
|
|||||||
{ provide: BrowseService, useValue: mockBrowseService },
|
{ provide: BrowseService, useValue: mockBrowseService },
|
||||||
{ provide: DSpaceObjectDataService, useValue: mockDsoService },
|
{ provide: DSpaceObjectDataService, useValue: mockDsoService },
|
||||||
{ provide: PaginationService, useValue: paginationService },
|
{ provide: PaginationService, useValue: paginationService },
|
||||||
{ provide: Router, useValue: new RouterMock() }
|
{ provide: Router, useValue: new RouterMock() },
|
||||||
|
{ provide: APP_CONFIG, useValue: environmentMock }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
@@ -118,6 +131,10 @@ describe('BrowseByMetadataPageComponent', () => {
|
|||||||
expect(comp.items$).toBeUndefined();
|
expect(comp.items$).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set embed thumbnail property to true', () => {
|
||||||
|
expect(comp.fetchThumbnails).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
describe('when a value is provided', () => {
|
describe('when a value is provided', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const paramsWithValue = {
|
const paramsWithValue = {
|
||||||
@@ -145,14 +162,14 @@ describe('BrowseByMetadataPageComponent', () => {
|
|||||||
};
|
};
|
||||||
const paginationOptions = Object.assign(new PaginationComponentOptions(), {
|
const paginationOptions = Object.assign(new PaginationComponentOptions(), {
|
||||||
currentPage: 5,
|
currentPage: 5,
|
||||||
pageSize: 10,
|
pageSize: comp.appConfig.browseBy.pageSize,
|
||||||
});
|
});
|
||||||
const sortOptions = {
|
const sortOptions = {
|
||||||
direction: SortDirection.ASC,
|
direction: SortDirection.ASC,
|
||||||
field: 'fake-field',
|
field: 'fake-field',
|
||||||
};
|
};
|
||||||
|
|
||||||
result = browseParamsToOptions(paramsScope, paginationOptions, sortOptions, 'author');
|
result = browseParamsToOptions(paramsScope, paginationOptions, sortOptions, 'author', comp.fetchThumbnails);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return BrowseEntrySearchOptions with the correct properties', () => {
|
it('should return BrowseEntrySearchOptions with the correct properties', () => {
|
||||||
@@ -163,6 +180,36 @@ describe('BrowseByMetadataPageComponent', () => {
|
|||||||
expect(result.sort.direction).toEqual(SortDirection.ASC);
|
expect(result.sort.direction).toEqual(SortDirection.ASC);
|
||||||
expect(result.sort.field).toEqual('fake-field');
|
expect(result.sort.field).toEqual('fake-field');
|
||||||
expect(result.scope).toEqual('fake-scope');
|
expect(result.scope).toEqual('fake-scope');
|
||||||
|
expect(result.fetchThumbnail).toBeTrue();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('calling getBrowseSearchOptions', () => {
|
||||||
|
let result: BrowseEntrySearchOptions;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const paramsScope = {
|
||||||
|
scope: 'fake-scope'
|
||||||
|
};
|
||||||
|
const paginationOptions = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
currentPage: 5,
|
||||||
|
pageSize: comp.appConfig.browseBy.pageSize,
|
||||||
|
});
|
||||||
|
const sortOptions = {
|
||||||
|
direction: SortDirection.ASC,
|
||||||
|
field: 'fake-field',
|
||||||
|
};
|
||||||
|
|
||||||
|
result = getBrowseSearchOptions('title', paginationOptions, sortOptions, comp.fetchThumbnails);
|
||||||
|
});
|
||||||
|
it('should return BrowseEntrySearchOptions with the correct properties', () => {
|
||||||
|
|
||||||
|
expect(result.metadataDefinition).toEqual('title');
|
||||||
|
expect(result.pagination.currentPage).toEqual(5);
|
||||||
|
expect(result.pagination.pageSize).toEqual(10);
|
||||||
|
expect(result.sort.direction).toEqual(SortDirection.ASC);
|
||||||
|
expect(result.sort.field).toEqual('fake-field');
|
||||||
|
expect(result.fetchThumbnail).toBeTrue();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs';
|
import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs';
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, Inject, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
@@ -14,9 +14,11 @@ import { getFirstSucceededRemoteData } from '../../core/shared/operators';
|
|||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
||||||
import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
|
||||||
|
|
||||||
|
export const BBM_PAGINATION_ID = 'bbm';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-browse-by-metadata-page',
|
selector: 'ds-browse-by-metadata-page',
|
||||||
@@ -24,12 +26,12 @@ import { map } from 'rxjs/operators';
|
|||||||
templateUrl: './browse-by-metadata-page.component.html'
|
templateUrl: './browse-by-metadata-page.component.html'
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* Component for browsing (items) by metadata definition
|
* Component for browsing (items) by metadata definition.
|
||||||
* A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields.
|
* A metadata definition (a.k.a. browse id) is a short term used to describe one
|
||||||
* An example would be 'author' for 'dc.contributor.*'
|
* or multiple metadata fields. An example would be 'author' for
|
||||||
|
* 'dc.contributor.*'
|
||||||
*/
|
*/
|
||||||
@rendersBrowseBy(BrowseByDataType.Metadata)
|
export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
||||||
export class BrowseByMetadataPageComponent implements OnInit {
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of browse-entries to display
|
* The list of browse-entries to display
|
||||||
@@ -49,11 +51,7 @@ export class BrowseByMetadataPageComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* The pagination config used to display the values
|
* The pagination config used to display the values
|
||||||
*/
|
*/
|
||||||
paginationConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
paginationConfig: PaginationComponentOptions;
|
||||||
id: 'bbm',
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: 20
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The pagination observable
|
* The pagination observable
|
||||||
@@ -93,7 +91,7 @@ export class BrowseByMetadataPageComponent implements OnInit {
|
|||||||
startsWithOptions;
|
startsWithOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The value we're browing items for
|
* The value we're browsing items for
|
||||||
* - When the value is not empty, we're browsing items
|
* - When the value is not empty, we're browsing items
|
||||||
* - When the value is empty, we're browsing browse-entries (values for the given metadata definition)
|
* - When the value is empty, we're browsing browse-entries (values for the given metadata definition)
|
||||||
*/
|
*/
|
||||||
@@ -109,16 +107,31 @@ export class BrowseByMetadataPageComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
startsWith: string;
|
startsWith: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether to request embedded thumbnail.
|
||||||
|
*/
|
||||||
|
fetchThumbnails: boolean;
|
||||||
|
|
||||||
public constructor(protected route: ActivatedRoute,
|
public constructor(protected route: ActivatedRoute,
|
||||||
protected browseService: BrowseService,
|
protected browseService: BrowseService,
|
||||||
protected dsoService: DSpaceObjectDataService,
|
protected dsoService: DSpaceObjectDataService,
|
||||||
protected paginationService: PaginationService,
|
protected paginationService: PaginationService,
|
||||||
protected router: Router) {
|
protected router: Router,
|
||||||
}
|
@Inject(APP_CONFIG) public appConfig: AppConfig) {
|
||||||
|
|
||||||
|
this.fetchThumbnails = this.appConfig.browseBy.showThumbnails;
|
||||||
|
this.paginationConfig = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: BBM_PAGINATION_ID,
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: this.appConfig.browseBy.pageSize,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
|
||||||
const sortConfig = new SortOptions('default', SortDirection.ASC);
|
const sortConfig = new SortOptions('default', SortDirection.ASC);
|
||||||
this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig));
|
this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig));
|
||||||
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
||||||
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
|
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
@@ -131,15 +144,16 @@ export class BrowseByMetadataPageComponent implements OnInit {
|
|||||||
this.authority = params.authority;
|
this.authority = params.authority;
|
||||||
this.value = +params.value || params.value || '';
|
this.value = +params.value || params.value || '';
|
||||||
this.startsWith = +params.startsWith || params.startsWith;
|
this.startsWith = +params.startsWith || params.startsWith;
|
||||||
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId);
|
|
||||||
if (isNotEmpty(this.value)) {
|
if (isNotEmpty(this.value)) {
|
||||||
this.updatePageWithItems(searchOptions, this.value, this.authority);
|
this.updatePageWithItems(
|
||||||
|
browseParamsToOptions(params, currentPage, currentSort, this.browseId, this.fetchThumbnails), this.value, this.authority);
|
||||||
} else {
|
} else {
|
||||||
this.updatePage(searchOptions);
|
this.updatePage(browseParamsToOptions(params, currentPage, currentSort, this.browseId, false));
|
||||||
}
|
}
|
||||||
this.updateParent(params.scope);
|
this.updateParent(params.scope);
|
||||||
}));
|
}));
|
||||||
this.updateStartsWithTextOptions();
|
this.updateStartsWithTextOptions();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -226,22 +240,44 @@ export class BrowseByMetadataPageComponent implements OnInit {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates browse entry search options.
|
||||||
|
* @param defaultBrowseId the metadata definition to fetch entries or items for
|
||||||
|
* @param paginationConfig the required pagination configuration
|
||||||
|
* @param sortConfig the required sort configuration
|
||||||
|
* @param fetchThumbnails optional boolean for fetching thumbnails
|
||||||
|
* @returns BrowseEntrySearchOptions instance
|
||||||
|
*/
|
||||||
|
export function getBrowseSearchOptions(defaultBrowseId: string,
|
||||||
|
paginationConfig: PaginationComponentOptions,
|
||||||
|
sortConfig: SortOptions,
|
||||||
|
fetchThumbnails?: boolean) {
|
||||||
|
if (!hasValue(fetchThumbnails)) {
|
||||||
|
fetchThumbnails = false;
|
||||||
|
}
|
||||||
|
return new BrowseEntrySearchOptions(defaultBrowseId, paginationConfig, sortConfig, null,
|
||||||
|
null, fetchThumbnails);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Function to transform query and url parameters into searchOptions used to fetch browse entries or items
|
* Function to transform query and url parameters into searchOptions used to fetch browse entries or items
|
||||||
* @param params URL and query parameters
|
* @param params URL and query parameters
|
||||||
* @param paginationConfig Pagination configuration
|
* @param paginationConfig Pagination configuration
|
||||||
* @param sortConfig Sorting configuration
|
* @param sortConfig Sorting configuration
|
||||||
* @param metadata Optional metadata definition to fetch browse entries/items for
|
* @param metadata Optional metadata definition to fetch browse entries/items for
|
||||||
|
* @param fetchThumbnail Optional parameter for requesting thumbnail images
|
||||||
*/
|
*/
|
||||||
export function browseParamsToOptions(params: any,
|
export function browseParamsToOptions(params: any,
|
||||||
paginationConfig: PaginationComponentOptions,
|
paginationConfig: PaginationComponentOptions,
|
||||||
sortConfig: SortOptions,
|
sortConfig: SortOptions,
|
||||||
metadata?: string): BrowseEntrySearchOptions {
|
metadata?: string,
|
||||||
|
fetchThumbnail?: boolean): BrowseEntrySearchOptions {
|
||||||
return new BrowseEntrySearchOptions(
|
return new BrowseEntrySearchOptions(
|
||||||
metadata,
|
metadata,
|
||||||
paginationConfig,
|
paginationConfig,
|
||||||
sortConfig,
|
sortConfig,
|
||||||
+params.startsWith || params.startsWith,
|
+params.startsWith || params.startsWith,
|
||||||
params.scope
|
params.scope,
|
||||||
|
fetchThumbnail
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,29 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
||||||
|
import { BrowseByMetadataPageComponent } from './browse-by-metadata-page.component';
|
||||||
|
import {BrowseByDataType, rendersBrowseBy} from '../browse-by-switcher/browse-by-decorator';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for BrowseByMetadataPageComponent
|
||||||
|
**/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-browse-by-metadata-page',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: '../../shared/theme-support/themed.component.html',
|
||||||
|
})
|
||||||
|
|
||||||
|
@rendersBrowseBy(BrowseByDataType.Metadata)
|
||||||
|
export class ThemedBrowseByMetadataPageComponent
|
||||||
|
extends ThemedComponent<BrowseByMetadataPageComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'BrowseByMetadataPageComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../../themes/${themeName}/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./browse-by-metadata-page.component`);
|
||||||
|
}
|
||||||
|
}
|
@@ -1,6 +1,10 @@
|
|||||||
import { hasNoValue } from '../../shared/empty.util';
|
import { hasNoValue } from '../../shared/empty.util';
|
||||||
import { InjectionToken } from '@angular/core';
|
import { InjectionToken } from '@angular/core';
|
||||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||||
|
import {
|
||||||
|
DEFAULT_THEME,
|
||||||
|
resolveTheme
|
||||||
|
} from '../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||||
|
|
||||||
export enum BrowseByDataType {
|
export enum BrowseByDataType {
|
||||||
Title = 'title',
|
Title = 'title',
|
||||||
@@ -10,7 +14,7 @@ export enum BrowseByDataType {
|
|||||||
|
|
||||||
export const DEFAULT_BROWSE_BY_TYPE = BrowseByDataType.Metadata;
|
export const DEFAULT_BROWSE_BY_TYPE = BrowseByDataType.Metadata;
|
||||||
|
|
||||||
export const BROWSE_BY_COMPONENT_FACTORY = new InjectionToken<(browseByType) => GenericConstructor<any>>('getComponentByBrowseByType', {
|
export const BROWSE_BY_COMPONENT_FACTORY = new InjectionToken<(browseByType, theme) => GenericConstructor<any>>('getComponentByBrowseByType', {
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
factory: () => getComponentByBrowseByType
|
factory: () => getComponentByBrowseByType
|
||||||
});
|
});
|
||||||
@@ -20,13 +24,17 @@ const map = new Map();
|
|||||||
/**
|
/**
|
||||||
* Decorator used for rendering Browse-By pages by type
|
* Decorator used for rendering Browse-By pages by type
|
||||||
* @param browseByType The type of page
|
* @param browseByType The type of page
|
||||||
|
* @param theme The optional theme for the component
|
||||||
*/
|
*/
|
||||||
export function rendersBrowseBy(browseByType: BrowseByDataType) {
|
export function rendersBrowseBy(browseByType: BrowseByDataType, theme = DEFAULT_THEME) {
|
||||||
return function decorator(component: any) {
|
return function decorator(component: any) {
|
||||||
if (hasNoValue(map.get(browseByType))) {
|
if (hasNoValue(map.get(browseByType))) {
|
||||||
map.set(browseByType, component);
|
map.set(browseByType, new Map());
|
||||||
|
}
|
||||||
|
if (hasNoValue(map.get(browseByType).get(theme))) {
|
||||||
|
map.get(browseByType).set(theme, component);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`There can't be more than one component to render Browse-By of type "${browseByType}"`);
|
throw new Error(`There can't be more than one component to render Browse-By of type "${browseByType}" and theme "${theme}"`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -34,11 +42,16 @@ export function rendersBrowseBy(browseByType: BrowseByDataType) {
|
|||||||
/**
|
/**
|
||||||
* Get the component used for rendering a Browse-By page by type
|
* Get the component used for rendering a Browse-By page by type
|
||||||
* @param browseByType The type of page
|
* @param browseByType The type of page
|
||||||
|
* @param theme the theme to match
|
||||||
*/
|
*/
|
||||||
export function getComponentByBrowseByType(browseByType) {
|
export function getComponentByBrowseByType(browseByType, theme) {
|
||||||
const comp = map.get(browseByType);
|
let themeMap = map.get(browseByType);
|
||||||
|
if (hasNoValue(themeMap)) {
|
||||||
|
themeMap = map.get(DEFAULT_BROWSE_BY_TYPE);
|
||||||
|
}
|
||||||
|
const comp = resolveTheme(themeMap, theme);
|
||||||
if (hasNoValue(comp)) {
|
if (hasNoValue(comp)) {
|
||||||
map.get(DEFAULT_BROWSE_BY_TYPE);
|
return themeMap.get(DEFAULT_THEME);
|
||||||
}
|
}
|
||||||
return comp;
|
return comp;
|
||||||
}
|
}
|
||||||
|
@@ -4,7 +4,8 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
|
|||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { BROWSE_BY_COMPONENT_FACTORY, BrowseByDataType } from './browse-by-decorator';
|
import { BROWSE_BY_COMPONENT_FACTORY, BrowseByDataType } from './browse-by-decorator';
|
||||||
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
||||||
import { BehaviorSubject, of as observableOf } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||||
|
|
||||||
describe('BrowseBySwitcherComponent', () => {
|
describe('BrowseBySwitcherComponent', () => {
|
||||||
let comp: BrowseBySwitcherComponent;
|
let comp: BrowseBySwitcherComponent;
|
||||||
@@ -44,11 +45,20 @@ describe('BrowseBySwitcherComponent', () => {
|
|||||||
data
|
data
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let themeService: ThemeService;
|
||||||
|
let themeName: string;
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
|
themeName = 'dspace';
|
||||||
|
themeService = jasmine.createSpyObj('themeService', {
|
||||||
|
getThemeName: themeName,
|
||||||
|
});
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [BrowseBySwitcherComponent],
|
declarations: [BrowseBySwitcherComponent],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||||
|
{ provide: ThemeService, useValue: themeService },
|
||||||
{ provide: BROWSE_BY_COMPONENT_FACTORY, useValue: jasmine.createSpy('getComponentByBrowseByType').and.returnValue(null) }
|
{ provide: BROWSE_BY_COMPONENT_FACTORY, useValue: jasmine.createSpy('getComponentByBrowseByType').and.returnValue(null) }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@@ -68,7 +78,7 @@ describe('BrowseBySwitcherComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it(`should call getComponentByBrowseByType with type "${type.dataType}"`, () => {
|
it(`should call getComponentByBrowseByType with type "${type.dataType}"`, () => {
|
||||||
expect((comp as any).getComponentByBrowseByType).toHaveBeenCalledWith(type.dataType);
|
expect((comp as any).getComponentByBrowseByType).toHaveBeenCalledWith(type.dataType, themeName);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -5,6 +5,7 @@ import { map } from 'rxjs/operators';
|
|||||||
import { BROWSE_BY_COMPONENT_FACTORY } from './browse-by-decorator';
|
import { BROWSE_BY_COMPONENT_FACTORY } from './browse-by-decorator';
|
||||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||||
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
||||||
|
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-browse-by-switcher',
|
selector: 'ds-browse-by-switcher',
|
||||||
@@ -21,7 +22,8 @@ export class BrowseBySwitcherComponent implements OnInit {
|
|||||||
browseByComponent: Observable<any>;
|
browseByComponent: Observable<any>;
|
||||||
|
|
||||||
public constructor(protected route: ActivatedRoute,
|
public constructor(protected route: ActivatedRoute,
|
||||||
@Inject(BROWSE_BY_COMPONENT_FACTORY) private getComponentByBrowseByType: (browseByType) => GenericConstructor<any>) {
|
protected themeService: ThemeService,
|
||||||
|
@Inject(BROWSE_BY_COMPONENT_FACTORY) private getComponentByBrowseByType: (browseByType, theme) => GenericConstructor<any>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,7 +31,7 @@ export class BrowseBySwitcherComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.browseByComponent = this.route.data.pipe(
|
this.browseByComponent = this.route.data.pipe(
|
||||||
map((data: { browseDefinition: BrowseDefinition }) => this.getComponentByBrowseByType(data.browseDefinition.dataType))
|
map((data: { browseDefinition: BrowseDefinition }) => this.getComponentByBrowseByType(data.browseDefinition.dataType, this.themeService.getThemeName()))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -18,11 +18,11 @@ import { BrowseService } from '../../core/browse/browse.service';
|
|||||||
import { RouterMock } from '../../shared/mocks/router.mock';
|
import { RouterMock } from '../../shared/mocks/router.mock';
|
||||||
import { VarDirective } from '../../shared/utils/var.directive';
|
import { VarDirective } from '../../shared/utils/var.directive';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
||||||
import { FindListOptions } from '../../core/data/find-list-options.model';
|
import { APP_CONFIG } from '../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
|
|
||||||
|
|
||||||
describe('BrowseByTitlePageComponent', () => {
|
describe('BrowseByTitlePageComponent', () => {
|
||||||
let comp: BrowseByTitlePageComponent;
|
let comp: BrowseByTitlePageComponent;
|
||||||
@@ -77,7 +77,8 @@ describe('BrowseByTitlePageComponent', () => {
|
|||||||
{ provide: BrowseService, useValue: mockBrowseService },
|
{ provide: BrowseService, useValue: mockBrowseService },
|
||||||
{ provide: DSpaceObjectDataService, useValue: mockDsoService },
|
{ provide: DSpaceObjectDataService, useValue: mockDsoService },
|
||||||
{ provide: PaginationService, useValue: paginationService },
|
{ provide: PaginationService, useValue: paginationService },
|
||||||
{ provide: Router, useValue: new RouterMock() }
|
{ provide: Router, useValue: new RouterMock() },
|
||||||
|
{ provide: APP_CONFIG, useValue: environment }
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
|
@@ -1,19 +1,18 @@
|
|||||||
import { combineLatest as observableCombineLatest } from 'rxjs';
|
import { combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
import { Component } from '@angular/core';
|
import { Component, Inject } from '@angular/core';
|
||||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import {
|
import {
|
||||||
BrowseByMetadataPageComponent,
|
BrowseByMetadataPageComponent,
|
||||||
browseParamsToOptions
|
browseParamsToOptions, getBrowseSearchOptions
|
||||||
} from '../browse-by-metadata-page/browse-by-metadata-page.component';
|
} from '../browse-by-metadata-page/browse-by-metadata-page.component';
|
||||||
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
|
|
||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
import { BrowseService } from '../../core/browse/browse.service';
|
import { BrowseService } from '../../core/browse/browse.service';
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
import { BrowseByDataType, rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
|
import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-browse-by-title-page',
|
selector: 'ds-browse-by-title-page',
|
||||||
@@ -23,20 +22,21 @@ import { PaginationComponentOptions } from '../../shared/pagination/pagination-c
|
|||||||
/**
|
/**
|
||||||
* Component for browsing items by title (dc.title)
|
* Component for browsing items by title (dc.title)
|
||||||
*/
|
*/
|
||||||
@rendersBrowseBy(BrowseByDataType.Title)
|
|
||||||
export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
|
export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
|
||||||
|
|
||||||
public constructor(protected route: ActivatedRoute,
|
public constructor(protected route: ActivatedRoute,
|
||||||
protected browseService: BrowseService,
|
protected browseService: BrowseService,
|
||||||
protected dsoService: DSpaceObjectDataService,
|
protected dsoService: DSpaceObjectDataService,
|
||||||
protected paginationService: PaginationService,
|
protected paginationService: PaginationService,
|
||||||
protected router: Router) {
|
protected router: Router,
|
||||||
super(route, browseService, dsoService, paginationService, router);
|
@Inject(APP_CONFIG) public appConfig: AppConfig) {
|
||||||
|
super(route, browseService, dsoService, paginationService, router, appConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
const sortConfig = new SortOptions('dc.title', SortDirection.ASC);
|
const sortConfig = new SortOptions('dc.title', SortDirection.ASC);
|
||||||
this.updatePage(new BrowseEntrySearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig));
|
// include the thumbnail configuration in browse search options
|
||||||
|
this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig, this.fetchThumbnails));
|
||||||
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
||||||
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
|
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
@@ -47,7 +47,7 @@ export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
||||||
this.startsWith = +params.startsWith || params.startsWith;
|
this.startsWith = +params.startsWith || params.startsWith;
|
||||||
this.browseId = params.id || this.defaultBrowseId;
|
this.browseId = params.id || this.defaultBrowseId;
|
||||||
this.updatePageWithItems(browseParamsToOptions(params, currentPage, currentSort, this.browseId), undefined, undefined);
|
this.updatePageWithItems(browseParamsToOptions(params, currentPage, currentSort, this.browseId, this.fetchThumbnails), undefined, undefined);
|
||||||
this.updateParent(params.scope);
|
this.updateParent(params.scope);
|
||||||
}));
|
}));
|
||||||
this.updateStartsWithTextOptions();
|
this.updateStartsWithTextOptions();
|
||||||
|
@@ -0,0 +1,29 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
||||||
|
import { BrowseByTitlePageComponent } from './browse-by-title-page.component';
|
||||||
|
import {BrowseByDataType, rendersBrowseBy} from '../browse-by-switcher/browse-by-decorator';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Themed wrapper for BrowseByTitlePageComponent
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-browse-by-title-page',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: '../../shared/theme-support/themed.component.html',
|
||||||
|
})
|
||||||
|
|
||||||
|
@rendersBrowseBy(BrowseByDataType.Title)
|
||||||
|
export class ThemedBrowseByTitlePageComponent
|
||||||
|
extends ThemedComponent<BrowseByTitlePageComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'BrowseByTitlePageComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../../themes/${themeName}/app/browse-by/browse-by-title-page/browse-by-title-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import(`./browse-by-title-page.component`);
|
||||||
|
}
|
||||||
|
}
|
@@ -7,12 +7,20 @@ import { BrowseByDatePageComponent } from './browse-by-date-page/browse-by-date-
|
|||||||
import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switcher.component';
|
import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switcher.component';
|
||||||
import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component';
|
import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component';
|
||||||
import { ComcolModule } from '../shared/comcol/comcol.module';
|
import { ComcolModule } from '../shared/comcol/comcol.module';
|
||||||
|
import { ThemedBrowseByMetadataPageComponent } from './browse-by-metadata-page/themed-browse-by-metadata-page.component';
|
||||||
|
import { ThemedBrowseByDatePageComponent } from './browse-by-date-page/themed-browse-by-date-page.component';
|
||||||
|
import { ThemedBrowseByTitlePageComponent } from './browse-by-title-page/themed-browse-by-title-page.component';
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
BrowseByTitlePageComponent,
|
BrowseByTitlePageComponent,
|
||||||
BrowseByMetadataPageComponent,
|
BrowseByMetadataPageComponent,
|
||||||
BrowseByDatePageComponent
|
BrowseByDatePageComponent,
|
||||||
|
|
||||||
|
ThemedBrowseByMetadataPageComponent,
|
||||||
|
ThemedBrowseByDatePageComponent,
|
||||||
|
ThemedBrowseByTitlePageComponent,
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -16,7 +16,7 @@ import { CommunityDataService } from '../../core/data/community-data.service';
|
|||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { RequestService } from '../../core/data/request.service';
|
import { RequestService } from '../../core/data/request.service';
|
||||||
import { ObjectCacheService } from '../../core/cache/object-cache.service';
|
import { ObjectCacheService } from '../../core/cache/object-cache.service';
|
||||||
import { EntityTypeService } from '../../core/data/entity-type.service';
|
import { EntityTypeDataService } from '../../core/data/entity-type-data.service';
|
||||||
import { ItemType } from '../../core/shared/item-relationships/item-type.model';
|
import { ItemType } from '../../core/shared/item-relationships/item-type.model';
|
||||||
import { MetadataValue } from '../../core/shared/metadata.models';
|
import { MetadataValue } from '../../core/shared/metadata.models';
|
||||||
import { getFirstSucceededRemoteListPayload } from '../../core/shared/operators';
|
import { getFirstSucceededRemoteListPayload } from '../../core/shared/operators';
|
||||||
@@ -61,7 +61,7 @@ export class CollectionFormComponent extends ComColFormComponent<Collection> imp
|
|||||||
protected dsoService: CommunityDataService,
|
protected dsoService: CommunityDataService,
|
||||||
protected requestService: RequestService,
|
protected requestService: RequestService,
|
||||||
protected objectCache: ObjectCacheService,
|
protected objectCache: ObjectCacheService,
|
||||||
protected entityTypeService: EntityTypeService) {
|
protected entityTypeService: EntityTypeDataService) {
|
||||||
super(formService, translate, notificationsService, authService, requestService, objectCache);
|
super(formService, translate, notificationsService, authService, requestService, objectCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,7 +16,7 @@ import { Collection } from '../../core/shared/collection.model';
|
|||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/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 { ChangeDetectionStrategy, EventEmitter } from '@angular/core';
|
import { EventEmitter } from '@angular/core';
|
||||||
import { HostWindowService } from '../../shared/host-window.service';
|
import { HostWindowService } from '../../shared/host-window.service';
|
||||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
|
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
@@ -41,7 +41,7 @@ import {
|
|||||||
} from '../../shared/remote-data.utils';
|
} from '../../shared/remote-data.utils';
|
||||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||||
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service';
|
||||||
import { MyDSpacePageComponent, SEARCH_CONFIG_SERVICE } from '../../my-dspace-page/my-dspace-page.component';
|
import { SEARCH_CONFIG_SERVICE } from '../../my-dspace-page/my-dspace-page.component';
|
||||||
import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-service.stub';
|
import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-service.stub';
|
||||||
import { GroupDataService } from '../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../core/eperson/group-data.service';
|
||||||
import { LinkHeadService } from '../../core/services/link-head.service';
|
import { LinkHeadService } from '../../core/services/link-head.service';
|
||||||
@@ -94,7 +94,7 @@ describe('CollectionItemMapperComponent', () => {
|
|||||||
const emptyList = createSuccessfulRemoteDataObject(createPaginatedList([]));
|
const emptyList = createSuccessfulRemoteDataObject(createPaginatedList([]));
|
||||||
const itemDataServiceStub = {
|
const itemDataServiceStub = {
|
||||||
mapToCollection: () => createSuccessfulRemoteDataObject$({}),
|
mapToCollection: () => createSuccessfulRemoteDataObject$({}),
|
||||||
findAllByHref: () => observableOf(emptyList)
|
findListByHref: () => observableOf(emptyList),
|
||||||
};
|
};
|
||||||
const activatedRouteStub = {
|
const activatedRouteStub = {
|
||||||
parent: {
|
parent: {
|
||||||
@@ -152,7 +152,7 @@ describe('CollectionItemMapperComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const groupDataService = jasmine.createSpyObj('groupsDataService', {
|
const groupDataService = jasmine.createSpyObj('groupsDataService', {
|
||||||
findAllByHref: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
findListByHref: createSuccessfulRemoteDataObject$(createPaginatedList([])),
|
||||||
getGroupRegistryRouterLink: '',
|
getGroupRegistryRouterLink: '',
|
||||||
getUUIDFromString: '',
|
getUUIDFromString: '',
|
||||||
});
|
});
|
||||||
|
@@ -143,7 +143,7 @@ export class CollectionItemMapperComponent implements OnInit {
|
|||||||
if (shouldUpdate === true) {
|
if (shouldUpdate === true) {
|
||||||
this.shouldUpdate$.next(false);
|
this.shouldUpdate$.next(false);
|
||||||
}
|
}
|
||||||
return this.itemDataService.findAllByHref(collectionRD.payload._links.mappedItems.href, Object.assign(options, {
|
return this.itemDataService.findListByHref(collectionRD.payload._links.mappedItems.href, Object.assign(options, {
|
||||||
sort: this.defaultSortOptions
|
sort: this.defaultSortOptions
|
||||||
}),!shouldUpdate, false, followLink('owningCollection')).pipe(
|
}),!shouldUpdate, false, followLink('owningCollection')).pipe(
|
||||||
getAllSucceededRemoteData()
|
getAllSucceededRemoteData()
|
||||||
|
@@ -6,7 +6,7 @@ import { CreateCollectionPageComponent } from './create-collection-page/create-c
|
|||||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||||
import { CreateCollectionPageGuard } from './create-collection-page/create-collection-page.guard';
|
import { CreateCollectionPageGuard } from './create-collection-page/create-collection-page.guard';
|
||||||
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
|
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
|
||||||
import { EditItemTemplatePageComponent } from './edit-item-template-page/edit-item-template-page.component';
|
import { ThemedEditItemTemplatePageComponent } from './edit-item-template-page/themed-edit-item-template-page.component';
|
||||||
import { ItemTemplatePageResolver } from './edit-item-template-page/item-template-page.resolver';
|
import { ItemTemplatePageResolver } from './edit-item-template-page/item-template-page.resolver';
|
||||||
import { CollectionBreadcrumbResolver } from '../core/breadcrumbs/collection-breadcrumb.resolver';
|
import { CollectionBreadcrumbResolver } from '../core/breadcrumbs/collection-breadcrumb.resolver';
|
||||||
import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service';
|
import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service';
|
||||||
@@ -52,7 +52,7 @@ import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: ITEMTEMPLATE_PATH,
|
path: ITEMTEMPLATE_PATH,
|
||||||
component: EditItemTemplatePageComponent,
|
component: ThemedEditItemTemplatePageComponent,
|
||||||
canActivate: [AuthenticatedGuard],
|
canActivate: [AuthenticatedGuard],
|
||||||
resolve: {
|
resolve: {
|
||||||
item: ItemTemplatePageResolver,
|
item: ItemTemplatePageResolver,
|
||||||
|
@@ -17,10 +17,10 @@
|
|||||||
</ds-comcol-page-logo>
|
</ds-comcol-page-logo>
|
||||||
|
|
||||||
<!-- Handle -->
|
<!-- Handle -->
|
||||||
<ds-comcol-page-handle
|
<ds-themed-comcol-page-handle
|
||||||
[content]="collection.handle"
|
[content]="collection.handle"
|
||||||
[title]="'collection.page.handle'" >
|
[title]="'collection.page.handle'" >
|
||||||
</ds-comcol-page-handle>
|
</ds-themed-comcol-page-handle>
|
||||||
<!-- Introductory text -->
|
<!-- Introductory text -->
|
||||||
<ds-comcol-page-content
|
<ds-comcol-page-content
|
||||||
[content]="collection.introductoryText"
|
[content]="collection.introductoryText"
|
||||||
@@ -56,8 +56,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<ds-error *ngIf="itemRD?.hasFailed"
|
<ds-error *ngIf="itemRD?.hasFailed"
|
||||||
message="{{'error.recent-submissions' | translate}}"></ds-error>
|
message="{{'error.recent-submissions' | translate}}"></ds-error>
|
||||||
<ds-loading *ngIf="!itemRD || itemRD.isLoading"
|
<ds-themed-loading *ngIf="!itemRD || itemRD.isLoading"
|
||||||
message="{{'loading.recent-submissions' | translate}}"></ds-loading>
|
message="{{'loading.recent-submissions' | translate}}"></ds-themed-loading>
|
||||||
<div *ngIf="!itemRD?.isLoading && itemRD?.payload?.page.length === 0" class="alert alert-info w-100" role="alert">
|
<div *ngIf="!itemRD?.isLoading && itemRD?.payload?.page.length === 0" class="alert alert-info w-100" role="alert">
|
||||||
{{'collection.page.browse.recent.empty' | translate}}
|
{{'collection.page.browse.recent.empty' | translate}}
|
||||||
</div>
|
</div>
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ds-error *ngIf="collectionRD?.hasFailed"
|
<ds-error *ngIf="collectionRD?.hasFailed"
|
||||||
message="{{'error.collection' | translate}}"></ds-error>
|
message="{{'error.collection' | translate}}"></ds-error>
|
||||||
<ds-loading *ngIf="collectionRD?.isLoading"
|
<ds-themed-loading *ngIf="collectionRD?.isLoading"
|
||||||
message="{{'loading.collection' | translate}}"></ds-loading>
|
message="{{'loading.collection' | translate}}"></ds-themed-loading>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -28,6 +28,7 @@ import { AuthorizationDataService } from '../core/data/feature-authorization/aut
|
|||||||
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
||||||
import { getCollectionPageRoute } from './collection-page-routing-paths';
|
import { getCollectionPageRoute } from './collection-page-routing-paths';
|
||||||
import { redirectOn4xx } from '../core/shared/authorized.operators';
|
import { redirectOn4xx } from '../core/shared/authorized.operators';
|
||||||
|
import { BROWSE_LINKS_TO_FOLLOW } from '../core/browse/browse.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-collection-page',
|
selector: 'ds-collection-page',
|
||||||
@@ -74,6 +75,7 @@ export class CollectionPageComponent implements OnInit {
|
|||||||
this.paginationConfig.pageSize = 5;
|
this.paginationConfig.pageSize = 5;
|
||||||
this.paginationConfig.currentPage = 1;
|
this.paginationConfig.currentPage = 1;
|
||||||
this.sortConfig = new SortOptions('dc.date.accessioned', SortDirection.DESC);
|
this.sortConfig = new SortOptions('dc.date.accessioned', SortDirection.DESC);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@@ -102,13 +104,14 @@ export class CollectionPageComponent implements OnInit {
|
|||||||
getFirstSucceededRemoteData(),
|
getFirstSucceededRemoteData(),
|
||||||
map((rd) => rd.payload.id),
|
map((rd) => rd.payload.id),
|
||||||
switchMap((id: string) => {
|
switchMap((id: string) => {
|
||||||
return this.searchService.search(
|
return this.searchService.search<Item>(
|
||||||
new PaginatedSearchOptions({
|
new PaginatedSearchOptions({
|
||||||
scope: id,
|
scope: id,
|
||||||
pagination: currentPagination,
|
pagination: currentPagination,
|
||||||
sort: currentSort,
|
sort: currentSort,
|
||||||
dsoTypes: [DSpaceObjectType.ITEM]
|
dsoTypes: [DSpaceObjectType.ITEM]
|
||||||
})).pipe(toDSpaceObjectListRD()) as Observable<RemoteData<PaginatedList<Item>>>;
|
}), null, true, true, ...BROWSE_LINKS_TO_FOLLOW)
|
||||||
|
.pipe(toDSpaceObjectListRD()) as Observable<RemoteData<PaginatedList<Item>>>;
|
||||||
}),
|
}),
|
||||||
startWith(undefined) // Make sure switching pages shows loading component
|
startWith(undefined) // Make sure switching pages shows loading component
|
||||||
)
|
)
|
||||||
|
@@ -8,6 +8,7 @@ import { CollectionPageRoutingModule } from './collection-page-routing.module';
|
|||||||
import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component';
|
import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component';
|
||||||
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
|
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
|
||||||
import { EditItemTemplatePageComponent } from './edit-item-template-page/edit-item-template-page.component';
|
import { EditItemTemplatePageComponent } from './edit-item-template-page/edit-item-template-page.component';
|
||||||
|
import { ThemedEditItemTemplatePageComponent } from './edit-item-template-page/themed-edit-item-template-page.component';
|
||||||
import { EditItemPageModule } from '../item-page/edit-item-page/edit-item-page.module';
|
import { EditItemPageModule } from '../item-page/edit-item-page/edit-item-page.module';
|
||||||
import { CollectionItemMapperComponent } from './collection-item-mapper/collection-item-mapper.component';
|
import { CollectionItemMapperComponent } from './collection-item-mapper/collection-item-mapper.component';
|
||||||
import { SearchService } from '../core/shared/search/search.service';
|
import { SearchService } from '../core/shared/search/search.service';
|
||||||
@@ -32,6 +33,7 @@ import { ComcolModule } from '../shared/comcol/comcol.module';
|
|||||||
CreateCollectionPageComponent,
|
CreateCollectionPageComponent,
|
||||||
DeleteCollectionPageComponent,
|
DeleteCollectionPageComponent,
|
||||||
EditItemTemplatePageComponent,
|
EditItemTemplatePageComponent,
|
||||||
|
ThemedEditItemTemplatePageComponent,
|
||||||
CollectionItemMapperComponent
|
CollectionItemMapperComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
@@ -17,7 +17,7 @@ export const COLLECTION_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Collection>[] = [
|
|||||||
followLink('parentCommunity', {},
|
followLink('parentCommunity', {},
|
||||||
followLink('parentCommunity')
|
followLink('parentCommunity')
|
||||||
),
|
),
|
||||||
followLink('logo')
|
followLink('logo'),
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -13,7 +13,7 @@ import { Item } from '../../../core/shared/item.model';
|
|||||||
import { ItemTemplateDataService } from '../../../core/data/item-template-data.service';
|
import { ItemTemplateDataService } from '../../../core/data/item-template-data.service';
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { RequestService } from '../../../core/data/request.service';
|
import { RequestService } from '../../../core/data/request.service';
|
||||||
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
import { getCollectionItemTemplateRoute } from '../../collection-page-routing-paths';
|
import { getCollectionItemTemplateRoute } from '../../collection-page-routing-paths';
|
||||||
|
|
||||||
describe('CollectionMetadataComponent', () => {
|
describe('CollectionMetadataComponent', () => {
|
||||||
@@ -39,8 +39,8 @@ describe('CollectionMetadataComponent', () => {
|
|||||||
|
|
||||||
const itemTemplateServiceStub = jasmine.createSpyObj('itemTemplateService', {
|
const itemTemplateServiceStub = jasmine.createSpyObj('itemTemplateService', {
|
||||||
findByCollectionID: createSuccessfulRemoteDataObject$(template),
|
findByCollectionID: createSuccessfulRemoteDataObject$(template),
|
||||||
create: createSuccessfulRemoteDataObject$(template),
|
createByCollectionID: createSuccessfulRemoteDataObject$(template),
|
||||||
deleteByCollectionID: observableOf(true),
|
delete: observableOf(true),
|
||||||
getCollectionEndpoint: observableOf(collectionTemplateHref),
|
getCollectionEndpoint: observableOf(collectionTemplateHref),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -91,12 +91,12 @@ describe('CollectionMetadataComponent', () => {
|
|||||||
|
|
||||||
describe('deleteItemTemplate', () => {
|
describe('deleteItemTemplate', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(itemTemplateService.deleteByCollectionID as jasmine.Spy).and.returnValue(observableOf(true));
|
(itemTemplateService.delete as jasmine.Spy).and.returnValue(createSuccessfulRemoteDataObject$({}));
|
||||||
comp.deleteItemTemplate();
|
comp.deleteItemTemplate();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call ItemTemplateService.deleteByCollectionID', () => {
|
it('should call ItemTemplateService.delete', () => {
|
||||||
expect(itemTemplateService.deleteByCollectionID).toHaveBeenCalledWith(template, 'collection-id');
|
expect(itemTemplateService.delete).toHaveBeenCalledWith(template.uuid);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when delete returns a success', () => {
|
describe('when delete returns a success', () => {
|
||||||
@@ -107,7 +107,7 @@ describe('CollectionMetadataComponent', () => {
|
|||||||
|
|
||||||
describe('when delete returns a failure', () => {
|
describe('when delete returns a failure', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
(itemTemplateService.deleteByCollectionID as jasmine.Spy).and.returnValue(observableOf(false));
|
(itemTemplateService.delete as jasmine.Spy).and.returnValue(createFailedRemoteDataObject$());
|
||||||
comp.deleteItemTemplate();
|
comp.deleteItemTemplate();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -7,12 +7,14 @@ import { ItemTemplateDataService } from '../../../core/data/item-template-data.s
|
|||||||
import { combineLatest as combineLatestObservable, Observable } from 'rxjs';
|
import { combineLatest as combineLatestObservable, Observable } from 'rxjs';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators';
|
import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../../core/shared/operators';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { RequestService } from '../../../core/data/request.service';
|
import { RequestService } from '../../../core/data/request.service';
|
||||||
import { getCollectionItemTemplateRoute } from '../../collection-page-routing-paths';
|
import { getCollectionItemTemplateRoute } from '../../collection-page-routing-paths';
|
||||||
|
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||||
|
import { hasValue } from '../../../shared/empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for editing a collection's metadata
|
* Component for editing a collection's metadata
|
||||||
@@ -65,7 +67,7 @@ export class CollectionMetadataComponent extends ComcolMetadataComponent<Collect
|
|||||||
getFirstSucceededRemoteDataPayload(),
|
getFirstSucceededRemoteDataPayload(),
|
||||||
);
|
);
|
||||||
const template$ = collection$.pipe(
|
const template$ = collection$.pipe(
|
||||||
switchMap((collection: Collection) => this.itemTemplateService.create(new Item(), collection.uuid).pipe(
|
switchMap((collection: Collection) => this.itemTemplateService.createByCollectionID(new Item(), collection.uuid).pipe(
|
||||||
getFirstSucceededRemoteDataPayload(),
|
getFirstSucceededRemoteDataPayload(),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
@@ -83,18 +85,15 @@ export class CollectionMetadataComponent extends ComcolMetadataComponent<Collect
|
|||||||
* Delete the item template from the collection
|
* Delete the item template from the collection
|
||||||
*/
|
*/
|
||||||
deleteItemTemplate() {
|
deleteItemTemplate() {
|
||||||
const collection$ = this.dsoRD$.pipe(
|
this.dsoRD$.pipe(
|
||||||
getFirstSucceededRemoteDataPayload(),
|
getFirstSucceededRemoteDataPayload(),
|
||||||
);
|
switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid)),
|
||||||
const template$ = collection$.pipe(
|
getFirstSucceededRemoteDataPayload(),
|
||||||
switchMap((collection: Collection) => this.itemTemplateService.findByCollectionID(collection.uuid).pipe(
|
switchMap((template) => {
|
||||||
getFirstSucceededRemoteDataPayload(),
|
return this.itemTemplateService.delete(template.uuid);
|
||||||
)),
|
}),
|
||||||
);
|
getFirstCompletedRemoteData(),
|
||||||
combineLatestObservable(collection$, template$).pipe(
|
map((response: RemoteData<NoContent>) => hasValue(response) && response.hasSucceeded),
|
||||||
switchMap(([collection, template]) => {
|
|
||||||
return this.itemTemplateService.deleteByCollectionID(template, collection.uuid);
|
|
||||||
})
|
|
||||||
).subscribe((success: boolean) => {
|
).subscribe((success: boolean) => {
|
||||||
if (success) {
|
if (success) {
|
||||||
this.notificationsService.success(null, this.translate.get('collection.edit.template.notifications.delete.success'));
|
this.notificationsService.success(null, this.translate.get('collection.edit.template.notifications.delete.success'));
|
||||||
|
@@ -11,11 +11,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="font-weight-bold">{{'collection.source.controls.harvest.last' | translate}}</span>
|
<span class="font-weight-bold">{{'collection.source.controls.harvest.last' | translate}}</span>
|
||||||
<span>{{contentSource?.message ? contentSource?.message : 'collection.source.controls.harvest.no-information'|translate }}</span>
|
<span>{{contentSource?.lastHarvested ? contentSource?.lastHarvested : 'collection.source.controls.harvest.no-information'|translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="font-weight-bold">{{'collection.source.controls.harvest.message' | translate}}</span>
|
<span class="font-weight-bold">{{'collection.source.controls.harvest.message' | translate}}</span>
|
||||||
<span>{{contentSource?.lastHarvested ? contentSource?.lastHarvested : 'collection.source.controls.harvest.no-information'|translate }}</span>
|
<span>{{contentSource?.message ? contentSource?.message: 'collection.source.controls.harvest.no-information'|translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button *ngIf="!(testConfigRunning$ |async)" class="btn btn-secondary"
|
<button *ngIf="!(testConfigRunning$ |async)" class="btn btn-secondary"
|
||||||
|
@@ -25,7 +25,7 @@
|
|||||||
<label class="form-check-label"
|
<label class="form-check-label"
|
||||||
for="externalSourceCheck">{{ 'collection.edit.tabs.source.external' | translate }}</label>
|
for="externalSourceCheck">{{ 'collection.edit.tabs.source.external' | translate }}</label>
|
||||||
</div>
|
</div>
|
||||||
<ds-loading *ngIf="!contentSource" [message]="'loading.content-source' | translate"></ds-loading>
|
<ds-themed-loading *ngIf="!contentSource" [message]="'loading.content-source' | translate"></ds-themed-loading>
|
||||||
<h4 *ngIf="contentSource && (contentSource?.harvestType !== harvestTypeNone)">{{ 'collection.edit.tabs.source.form.head' | translate }}</h4>
|
<h4 *ngIf="contentSource && (contentSource?.harvestType !== harvestTypeNone)">{{ 'collection.edit.tabs.source.form.head' | translate }}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
@@ -8,8 +8,7 @@ import {
|
|||||||
DynamicInputModel,
|
DynamicInputModel,
|
||||||
DynamicOptionControlModel,
|
DynamicOptionControlModel,
|
||||||
DynamicRadioGroupModel,
|
DynamicRadioGroupModel,
|
||||||
DynamicSelectModel,
|
DynamicSelectModel
|
||||||
DynamicTextAreaModel
|
|
||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
import { Location } from '@angular/common';
|
import { Location } from '@angular/common';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
@@ -23,7 +22,7 @@ import { RemoteData } from '../../../core/data/remote-data';
|
|||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { first, map, switchMap, take } from 'rxjs/operators';
|
import { first, map, switchMap, take } from 'rxjs/operators';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { cloneDeep } from 'lodash';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||||
import { getFirstSucceededRemoteData, getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
import { getFirstSucceededRemoteData, getFirstCompletedRemoteData } from '../../../core/shared/operators';
|
||||||
import { MetadataConfig } from '../../../core/shared/metadata-config.model';
|
import { MetadataConfig } from '../../../core/shared/metadata-config.model';
|
||||||
|
@@ -3,10 +3,10 @@
|
|||||||
<div class="col-12" *ngVar="(itemRD$ | async) as itemRD">
|
<div class="col-12" *ngVar="(itemRD$ | async) as itemRD">
|
||||||
<ng-container *ngIf="itemRD?.hasSucceeded">
|
<ng-container *ngIf="itemRD?.hasSucceeded">
|
||||||
<h2 class="border-bottom">{{ 'collection.edit.template.head' | translate:{ collection: collection?.name } }}</h2>
|
<h2 class="border-bottom">{{ 'collection.edit.template.head' | translate:{ collection: collection?.name } }}</h2>
|
||||||
<ds-item-metadata [updateService]="itemTemplateService" [item]="itemRD?.payload"></ds-item-metadata>
|
<ds-themed-item-metadata [updateService]="itemTemplateService" [item]="itemRD?.payload"></ds-themed-item-metadata>
|
||||||
<button [routerLink]="getCollectionEditUrl(collection)" class="btn btn-outline-secondary">{{ 'collection.edit.template.cancel' | translate }}</button>
|
<button [routerLink]="getCollectionEditUrl(collection)" class="btn btn-outline-secondary">{{ 'collection.edit.template.cancel' | translate }}</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ds-loading *ngIf="itemRD?.isLoading" [message]="'collection.edit.template.loading' | translate"></ds-loading>
|
<ds-themed-loading *ngIf="itemRD?.isLoading" [message]="'collection.edit.template.loading' | translate"></ds-themed-loading>
|
||||||
<ds-alert *ngIf="itemRD?.hasFailed" [type]="AlertTypeEnum.Error" [content]="'collection.edit.template.error' | translate"></ds-alert>
|
<ds-alert *ngIf="itemRD?.hasFailed" [type]="AlertTypeEnum.Error" [content]="'collection.edit.template.error' | translate"></ds-alert>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,25 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
||||||
|
import { EditItemTemplatePageComponent } from './edit-item-template-page.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-themed-edit-item-template-page',
|
||||||
|
styleUrls: [],
|
||||||
|
templateUrl: '../../shared/theme-support/themed.component.html',
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component for editing the item template of a collection
|
||||||
|
*/
|
||||||
|
export class ThemedEditItemTemplatePageComponent extends ThemedComponent<EditItemTemplatePageComponent> {
|
||||||
|
protected getComponentName(): string {
|
||||||
|
return 'EditItemTemplatePageComponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
|
return import(`../../../themes/${themeName}/app/collection-page/edit-item-template-page/edit-item-template-page.component`);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected importUnthemedComponent(): Promise<any> {
|
||||||
|
return import('./edit-item-template-page.component');
|
||||||
|
}
|
||||||
|
}
|
@@ -15,6 +15,8 @@ import { Collection } from '../core/shared/collection.model';
|
|||||||
import { PageInfo } from '../core/shared/page-info.model';
|
import { PageInfo } from '../core/shared/page-info.model';
|
||||||
import { FlatNode } from './flat-node.model';
|
import { FlatNode } from './flat-node.model';
|
||||||
import { FindListOptions } from '../core/data/find-list-options.model';
|
import { FindListOptions } from '../core/data/find-list-options.model';
|
||||||
|
import { APP_CONFIG } from 'src/config/app-config.interface';
|
||||||
|
import { environment } from 'src/environments/environment.test';
|
||||||
|
|
||||||
describe('CommunityListService', () => {
|
describe('CommunityListService', () => {
|
||||||
let store: StoreMock<AppState>;
|
let store: StoreMock<AppState>;
|
||||||
@@ -191,13 +193,14 @@ describe('CommunityListService', () => {
|
|||||||
};
|
};
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [CommunityListService,
|
providers: [CommunityListService,
|
||||||
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
{ provide: CollectionDataService, useValue: collectionDataServiceStub },
|
{ provide: CollectionDataService, useValue: collectionDataServiceStub },
|
||||||
{ provide: CommunityDataService, useValue: communityDataServiceStub },
|
{ provide: CommunityDataService, useValue: communityDataServiceStub },
|
||||||
{ provide: Store, useValue: StoreMock },
|
{ provide: Store, useValue: StoreMock },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
store = TestBed.inject(Store as any);
|
store = TestBed.inject(Store as any);
|
||||||
service = new CommunityListService(communityDataServiceStub, collectionDataServiceStub, store);
|
service = new CommunityListService(environment, communityDataServiceStub, collectionDataServiceStub, store);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create', inject([CommunityListService], (serviceIn: CommunityListService) => {
|
it('should create', inject([CommunityListService], (serviceIn: CommunityListService) => {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable max-classes-per-file */
|
/* eslint-disable max-classes-per-file */
|
||||||
import { Injectable } from '@angular/core';
|
import { Inject, Injectable } from '@angular/core';
|
||||||
import { createSelector, Store } from '@ngrx/store';
|
import { createSelector, Store } from '@ngrx/store';
|
||||||
|
|
||||||
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
|
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
|
||||||
@@ -23,6 +23,7 @@ import { followLink } from '../shared/utils/follow-link-config.model';
|
|||||||
import { FlatNode } from './flat-node.model';
|
import { FlatNode } from './flat-node.model';
|
||||||
import { ShowMoreFlatNode } from './show-more-flat-node.model';
|
import { ShowMoreFlatNode } from './show-more-flat-node.model';
|
||||||
import { FindListOptions } from '../core/data/find-list-options.model';
|
import { FindListOptions } from '../core/data/find-list-options.model';
|
||||||
|
import { AppConfig, APP_CONFIG } from 'src/config/app-config.interface';
|
||||||
|
|
||||||
// Helper method to combine an flatten an array of observables of flatNode arrays
|
// Helper method to combine an flatten an array of observables of flatNode arrays
|
||||||
export const combineAndFlatten = (obsList: Observable<FlatNode[]>[]): Observable<FlatNode[]> =>
|
export const combineAndFlatten = (obsList: Observable<FlatNode[]>[]): Observable<FlatNode[]> =>
|
||||||
@@ -80,8 +81,6 @@ const communityListStateSelector = (state: AppState) => state.communityList;
|
|||||||
const expandedNodesSelector = createSelector(communityListStateSelector, (communityList: CommunityListState) => communityList.expandedNodes);
|
const expandedNodesSelector = createSelector(communityListStateSelector, (communityList: CommunityListState) => communityList.expandedNodes);
|
||||||
const loadingNodeSelector = createSelector(communityListStateSelector, (communityList: CommunityListState) => communityList.loadingNode);
|
const loadingNodeSelector = createSelector(communityListStateSelector, (communityList: CommunityListState) => communityList.loadingNode);
|
||||||
|
|
||||||
export const MAX_COMCOLS_PER_PAGE = 20;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service class for the community list, responsible for the creating of the flat list used by communityList dataSource
|
* Service class for the community list, responsible for the creating of the flat list used by communityList dataSource
|
||||||
* and connection to the store to retrieve and save the state of the community list
|
* and connection to the store to retrieve and save the state of the community list
|
||||||
@@ -89,8 +88,15 @@ export const MAX_COMCOLS_PER_PAGE = 20;
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class CommunityListService {
|
export class CommunityListService {
|
||||||
|
|
||||||
constructor(private communityDataService: CommunityDataService, private collectionDataService: CollectionDataService,
|
private pageSize: number;
|
||||||
private store: Store<any>) {
|
|
||||||
|
constructor(
|
||||||
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
|
private communityDataService: CommunityDataService,
|
||||||
|
private collectionDataService: CollectionDataService,
|
||||||
|
private store: Store<any>
|
||||||
|
) {
|
||||||
|
this.pageSize = appConfig.communityList.pageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
private configOnePage: FindListOptions = Object.assign(new FindListOptions(), {
|
private configOnePage: FindListOptions = Object.assign(new FindListOptions(), {
|
||||||
@@ -145,7 +151,7 @@ export class CommunityListService {
|
|||||||
private getTopCommunities(options: FindListOptions): Observable<PaginatedList<Community>> {
|
private getTopCommunities(options: FindListOptions): Observable<PaginatedList<Community>> {
|
||||||
return this.communityDataService.findTop({
|
return this.communityDataService.findTop({
|
||||||
currentPage: options.currentPage,
|
currentPage: options.currentPage,
|
||||||
elementsPerPage: MAX_COMCOLS_PER_PAGE,
|
elementsPerPage: this.pageSize,
|
||||||
sort: {
|
sort: {
|
||||||
field: options.sort.field,
|
field: options.sort.field,
|
||||||
direction: options.sort.direction
|
direction: options.sort.direction
|
||||||
@@ -216,7 +222,7 @@ export class CommunityListService {
|
|||||||
let subcoms = [];
|
let subcoms = [];
|
||||||
for (let i = 1; i <= currentCommunityPage; i++) {
|
for (let i = 1; i <= currentCommunityPage; i++) {
|
||||||
const nextSetOfSubcommunitiesPage = this.communityDataService.findByParent(community.uuid, {
|
const nextSetOfSubcommunitiesPage = this.communityDataService.findByParent(community.uuid, {
|
||||||
elementsPerPage: MAX_COMCOLS_PER_PAGE,
|
elementsPerPage: this.pageSize,
|
||||||
currentPage: i
|
currentPage: i
|
||||||
},
|
},
|
||||||
followLink('subcommunities', { findListOptions: this.configOnePage }),
|
followLink('subcommunities', { findListOptions: this.configOnePage }),
|
||||||
@@ -241,7 +247,7 @@ export class CommunityListService {
|
|||||||
let collections = [];
|
let collections = [];
|
||||||
for (let i = 1; i <= currentCollectionPage; i++) {
|
for (let i = 1; i <= currentCollectionPage; i++) {
|
||||||
const nextSetOfCollectionsPage = this.collectionDataService.findByParent(community.uuid, {
|
const nextSetOfCollectionsPage = this.collectionDataService.findByParent(community.uuid, {
|
||||||
elementsPerPage: MAX_COMCOLS_PER_PAGE,
|
elementsPerPage: this.pageSize,
|
||||||
currentPage: i
|
currentPage: i
|
||||||
})
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<ds-loading *ngIf="(dataSource.loading$ | async) && !loadingNode" class="ds-loading"></ds-loading>
|
<ds-themed-loading *ngIf="(dataSource.loading$ | async) && !loadingNode" class="ds-themed-loading"></ds-themed-loading>
|
||||||
<cdk-tree [dataSource]="dataSource" [treeControl]="treeControl">
|
<cdk-tree [dataSource]="dataSource" [treeControl]="treeControl">
|
||||||
<!-- This is the tree node template for show more node -->
|
<!-- This is the tree node template for show more node -->
|
||||||
<cdk-tree-node *cdkTreeNodeDef="let node; when: isShowMore" cdkTreeNodePadding
|
<cdk-tree-node *cdkTreeNodeDef="let node; when: isShowMore" cdkTreeNodePadding
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
class="btn btn-outline-primary btn-sm" role="button">
|
class="btn btn-outline-primary btn-sm" role="button">
|
||||||
<i class="fas fa-angle-down"></i> {{ 'communityList.showMore' | translate }}
|
<i class="fas fa-angle-down"></i> {{ 'communityList.showMore' | translate }}
|
||||||
</a>
|
</a>
|
||||||
<ds-loading *ngIf="node===loadingNode && dataSource.loading$ | async" class="ds-loading"></ds-loading>
|
<ds-themed-loading *ngIf="node===loadingNode && dataSource.loading$ | async" class="ds-themed-loading"></ds-themed-loading>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-muted" cdkTreeNodePadding>
|
<div class="text-muted" cdkTreeNodePadding>
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
||||||
aria-hidden="true"></span>
|
aria-hidden="true"></span>
|
||||||
</button>
|
</button>
|
||||||
<ds-loading class="ds-loading"></ds-loading>
|
<ds-themed-loading class="ds-themed-loading"></ds-themed-loading>
|
||||||
</div>
|
</div>
|
||||||
</cdk-tree-node>
|
</cdk-tree-node>
|
||||||
<!-- This is the tree node template for leaf nodes (collections and (sub)coms without children) -->
|
<!-- This is the tree node template for leaf nodes (collections and (sub)coms without children) -->
|
||||||
|
@@ -10,8 +10,8 @@
|
|||||||
<ds-comcol-page-logo *ngIf="logoRD$" [logo]="(logoRD$ | async)?.payload" [alternateText]="'Community Logo'">
|
<ds-comcol-page-logo *ngIf="logoRD$" [logo]="(logoRD$ | async)?.payload" [alternateText]="'Community Logo'">
|
||||||
</ds-comcol-page-logo>
|
</ds-comcol-page-logo>
|
||||||
<!-- Handle -->
|
<!-- Handle -->
|
||||||
<ds-comcol-page-handle [content]="communityPayload.handle" [title]="'community.page.handle'">
|
<ds-themed-comcol-page-handle [content]="communityPayload.handle" [title]="'community.page.handle'">
|
||||||
</ds-comcol-page-handle>
|
</ds-themed-comcol-page-handle>
|
||||||
<!-- Introductory text -->
|
<!-- Introductory text -->
|
||||||
<ds-comcol-page-content [content]="communityPayload.introductoryText" [hasInnerHtml]="true">
|
<ds-comcol-page-content [content]="communityPayload.introductoryText" [hasInnerHtml]="true">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
@@ -25,12 +25,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<section class="comcol-page-browse-section">
|
<section class="comcol-page-browse-section">
|
||||||
|
|
||||||
<!-- Browse-By Links -->
|
<!-- Browse-By Links -->
|
||||||
<ds-themed-comcol-page-browse-by [id]="communityPayload.id" [contentType]="communityPayload.type">
|
<ds-themed-comcol-page-browse-by [id]="communityPayload.id" [contentType]="communityPayload.type">
|
||||||
</ds-themed-comcol-page-browse-by>
|
</ds-themed-comcol-page-browse-by>
|
||||||
|
|
||||||
<ds-community-page-sub-community-list [community]="communityPayload"></ds-community-page-sub-community-list>
|
<ds-themed-community-page-sub-community-list [community]="communityPayload"></ds-themed-community-page-sub-community-list>
|
||||||
<ds-community-page-sub-collection-list [community]="communityPayload"></ds-community-page-sub-collection-list>
|
<ds-themed-community-page-sub-collection-list [community]="communityPayload"></ds-themed-community-page-sub-collection-list>
|
||||||
</section>
|
</section>
|
||||||
<footer *ngIf="communityPayload.copyrightText" class="border-top my-5 pt-4">
|
<footer *ngIf="communityPayload.copyrightText" class="border-top my-5 pt-4">
|
||||||
<!-- Copyright -->
|
<!-- Copyright -->
|
||||||
@@ -41,5 +42,5 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ds-error *ngIf="communityRD?.hasFailed" message="{{'error.community' | translate}}"></ds-error>
|
<ds-error *ngIf="communityRD?.hasFailed" message="{{'error.community' | translate}}"></ds-error>
|
||||||
<ds-loading *ngIf="communityRD?.isLoading" message="{{'loading.community' | translate}}"></ds-loading>
|
<ds-themed-loading *ngIf="communityRD?.isLoading" message="{{'loading.community' | translate}}"></ds-themed-loading>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -13,10 +13,18 @@ import { StatisticsModule } from '../statistics/statistics.module';
|
|||||||
import { CommunityFormModule } from './community-form/community-form.module';
|
import { CommunityFormModule } from './community-form/community-form.module';
|
||||||
import { ThemedCommunityPageComponent } from './themed-community-page.component';
|
import { ThemedCommunityPageComponent } from './themed-community-page.component';
|
||||||
import { ComcolModule } from '../shared/comcol/comcol.module';
|
import { ComcolModule } from '../shared/comcol/comcol.module';
|
||||||
|
import {
|
||||||
|
ThemedCommunityPageSubCommunityListComponent
|
||||||
|
} from './sub-community-list/themed-community-page-sub-community-list.component';
|
||||||
|
import {
|
||||||
|
ThemedCollectionPageSubCollectionListComponent
|
||||||
|
} from './sub-collection-list/themed-community-page-sub-collection-list.component';
|
||||||
|
|
||||||
const DECLARATIONS = [CommunityPageComponent,
|
const DECLARATIONS = [CommunityPageComponent,
|
||||||
ThemedCommunityPageComponent,
|
ThemedCommunityPageComponent,
|
||||||
|
ThemedCommunityPageSubCommunityListComponent,
|
||||||
CommunityPageSubCollectionListComponent,
|
CommunityPageSubCollectionListComponent,
|
||||||
|
ThemedCollectionPageSubCollectionListComponent,
|
||||||
CommunityPageSubCommunityListComponent,
|
CommunityPageSubCommunityListComponent,
|
||||||
CreateCommunityPageComponent,
|
CreateCommunityPageComponent,
|
||||||
DeleteCommunityPageComponent];
|
DeleteCommunityPageComponent];
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user