mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-08 18:44:14 +00:00
Merge remote-tracking branch 'origin/main' into CST-4506_item_embargo
This commit is contained in:
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -29,11 +29,11 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
# https://github.com/actions/checkout
|
# https://github.com/actions/checkout
|
||||||
- name: Checkout codebase
|
- name: Checkout codebase
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
# 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@v1
|
uses: actions/setup-node@v2
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
@@ -82,7 +82,7 @@ jobs:
|
|||||||
# Upload coverage reports to Codecov (for Node v12 only)
|
# Upload coverage reports to Codecov (for Node v12 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@v1
|
uses: codecov/codecov-action@v2
|
||||||
if: matrix.node-version == '12.x'
|
if: matrix.node-version == '12.x'
|
||||||
|
|
||||||
# Using docker-compose start backend using CI configuration
|
# Using docker-compose start backend using CI configuration
|
||||||
|
78
.github/workflows/docker.yml
vendored
Normal file
78
.github/workflows/docker.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# DSpace Docker image build for hub.docker.com
|
||||||
|
name: Docker images
|
||||||
|
|
||||||
|
# Run this Build for all pushes to 'main' or maintenance branches, or tagged releases.
|
||||||
|
# Also run for PRs to ensure PR doesn't break Docker build process
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- 'dspace-**'
|
||||||
|
tags:
|
||||||
|
- 'dspace-**'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
# 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
|
||||||
|
env:
|
||||||
|
# Define tags to use for Docker images based on Git tags/branches (for docker/metadata-action)
|
||||||
|
# For a new commit on default branch (main), use the literal tag 'dspace-7_x' on Docker image.
|
||||||
|
# For a new commit on other branches, use the branch name as the tag for Docker image.
|
||||||
|
# For a new tag, copy that tag name as the tag for Docker image.
|
||||||
|
IMAGE_TAGS: |
|
||||||
|
type=raw,value=dspace-7_x,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }}
|
||||||
|
type=ref,event=branch,enable=${{ !endsWith(github.ref, github.event.repository.default_branch) }}
|
||||||
|
type=ref,event=tag
|
||||||
|
# Define default tag "flavor" for docker/metadata-action per
|
||||||
|
# https://github.com/docker/metadata-action#flavor-input
|
||||||
|
# We turn off 'latest' tag by default.
|
||||||
|
TAGS_FLAVOR: |
|
||||||
|
latest=false
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# https://github.com/actions/checkout
|
||||||
|
- name: Checkout codebase
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
# https://github.com/docker/setup-buildx-action
|
||||||
|
- name: Setup Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
|
||||||
|
# https://github.com/docker/login-action
|
||||||
|
- name: Login to DockerHub
|
||||||
|
# Only login if not a PR, as PRs only trigger a Docker build and not a push
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_ACCESS_TOKEN }}
|
||||||
|
|
||||||
|
###############################################
|
||||||
|
# Build/Push the 'dspace/dspace-angular' image
|
||||||
|
###############################################
|
||||||
|
# https://github.com/docker/metadata-action
|
||||||
|
# Get Metadata for docker_build step below
|
||||||
|
- name: Sync metadata (tags, labels) from GitHub to Docker for 'dspace-angular' image
|
||||||
|
id: meta_build
|
||||||
|
uses: docker/metadata-action@v3
|
||||||
|
with:
|
||||||
|
images: dspace/dspace-angular
|
||||||
|
tags: ${{ env.IMAGE_TAGS }}
|
||||||
|
flavor: ${{ env.TAGS_FLAVOR }}
|
||||||
|
|
||||||
|
# https://github.com/docker/build-push-action
|
||||||
|
- name: Build and push 'dspace-angular' image
|
||||||
|
id: docker_build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile
|
||||||
|
# For pull requests, we run the Docker build (to ensure no PR changes break the build),
|
||||||
|
# but we ONLY do an image push to DockerHub if it's NOT a PR
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
# Use tags / labels provided by 'docker/metadata-action' above
|
||||||
|
tags: ${{ steps.meta_build.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta_build.outputs.labels }}
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -3,5 +3,6 @@
|
|||||||
"i18n-ally.localesPaths": [
|
"i18n-ally.localesPaths": [
|
||||||
"src/assets/i18n",
|
"src/assets/i18n",
|
||||||
"src/app/core/locale"
|
"src/app/core/locale"
|
||||||
]
|
],
|
||||||
|
"typescript.tsdk": "node_modules\\typescript\\lib"
|
||||||
}
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
# This image will be published as dspace/dspace-angular
|
# This image will be published as dspace/dspace-angular
|
||||||
# See https://dspace-labs.github.io/DSpace-Docker-Images/ for usage details
|
# See https://github.com/DSpace/dspace-angular/tree/main/docker for usage details
|
||||||
|
|
||||||
FROM node:12-alpine
|
FROM node:14-alpine
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ADD . /app/
|
ADD . /app/
|
||||||
EXPOSE 4000
|
EXPOSE 4000
|
||||||
|
20
LICENSE
20
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
DSpace source code BSD License:
|
BSD 3-Clause License
|
||||||
|
|
||||||
Copyright (c) 2002-2021, LYRASIS. All rights reserved.
|
Copyright (c) 2002-2021, LYRASIS. All rights reserved.
|
||||||
|
|
||||||
@@ -13,13 +13,12 @@ notice, this list of conditions and the following disclaimer.
|
|||||||
notice, this list of conditions and the following disclaimer in the
|
notice, this list of conditions and the following disclaimer in the
|
||||||
documentation and/or other materials provided with the distribution.
|
documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
- Neither the name DuraSpace nor the name of the DSpace Foundation
|
- Neither the name of the copyright holder nor the names of its
|
||||||
nor the names of its contributors may be used to endorse or promote
|
contributors may be used to endorse or promote products derived from
|
||||||
products derived from this software without specific prior written
|
this software without specific prior written permission.
|
||||||
permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
@@ -29,11 +28,4 @@ OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|||||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
|
||||||
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||||
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
DAMAGE.
|
DAMAGE.
|
||||||
|
|
||||||
|
|
||||||
DSpace uses third-party libraries which may be distributed under
|
|
||||||
different licenses to the above. Information about these licenses
|
|
||||||
is detailed in the LICENSES_THIRD_PARTY file at the root of the source
|
|
||||||
tree. You must agree to the terms of these licenses, in addition to
|
|
||||||
the above DSpace source code license, in order to use this software.
|
|
28
NOTICE
Normal file
28
NOTICE
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
Licenses of Third-Party Libraries
|
||||||
|
=================================
|
||||||
|
|
||||||
|
DSpace uses third-party libraries which may be distributed under
|
||||||
|
different licenses than specified in our LICENSE file. Information
|
||||||
|
about these licenses is detailed in the LICENSES_THIRD_PARTY file at
|
||||||
|
the root of the source tree. You must agree to the terms of these
|
||||||
|
licenses, in addition to the DSpace source code license, in order to
|
||||||
|
use this software.
|
||||||
|
|
||||||
|
Licensing Notices
|
||||||
|
=================
|
||||||
|
|
||||||
|
[July 2019] DuraSpace joined with LYRASIS (another 501(c)3 organization) in July 2019.
|
||||||
|
LYRASIS holds the copyrights of DuraSpace.
|
||||||
|
|
||||||
|
[July 2009] Fedora Commons joined with the DSpace Foundation and began operating under
|
||||||
|
the new name DuraSpace in July 2009. DuraSpace holds the copyrights of
|
||||||
|
the DSpace Foundation, Inc.
|
||||||
|
|
||||||
|
[July 2007] The DSpace Foundation, Inc. is a 501(c)3 corporation established in July 2007
|
||||||
|
with a mission to promote and advance the dspace platform enabling management,
|
||||||
|
access and preservation of digital works. The Foundation was able to transfer
|
||||||
|
the legal copyright from Hewlett-Packard Company (HP) and Massachusetts
|
||||||
|
Institute of Technology (MIT) to the DSpace Foundation in October 2007. Many
|
||||||
|
of the files in the source code may contain a copyright statement stating HP
|
||||||
|
and MIT possess the copyright, in these instances please note that the copy
|
||||||
|
right has transferred to the DSpace foundation, and subsequently to DuraSpace.
|
10
README.md
10
README.md
@@ -230,7 +230,9 @@ E2E tests (aka integration tests) use [Cypress.io](https://www.cypress.io/). Con
|
|||||||
|
|
||||||
The test files can be found in the `./cypress/integration/` folder.
|
The test files can be found in the `./cypress/integration/` folder.
|
||||||
|
|
||||||
Before you can run e2e tests, you MUST have a running backend (i.e. REST API). By default, the e2e tests look for this at http://localhost:8080/server/ or whatever `rest` backend is defined in your `environment.prod.ts` or `environment.common.ts`. You may override this using env variables, see [Configuring](#configuring).
|
Before you can run e2e tests, two things are required:
|
||||||
|
1. You MUST have a running backend (i.e. REST API). By default, the e2e tests look for this at http://localhost:8080/server/ or whatever `rest` backend is defined in your `environment.prod.ts` or `environment.common.ts`. You may override this using env variables, see [Configuring](#configuring).
|
||||||
|
2. Your backend MUST include our Entities Test Data set. Some tests run against a (currently hardcoded) Community/Collection/Item UUID. These UUIDs are all valid for our Entities Test Data set. The Entities Test Data set may be installed easily via Docker, see https://github.com/DSpace/DSpace/tree/main/dspace/src/main/docker-compose#ingest-option-2-ingest-entities-test-data
|
||||||
|
|
||||||
Run `ng e2e` to kick off the tests. This will start Cypress and allow you to select the browser you wish to use, as well as whether you wish to run all tests or an individual test file. Once you click run on test(s), this opens the [Cypress Test Runner](https://docs.cypress.io/guides/core-concepts/test-runner) to run your test(s) and show you the results.
|
Run `ng e2e` to kick off the tests. This will start Cypress and allow you to select the browser you wish to use, as well as whether you wish to run all tests or an individual test file. Once you click run on test(s), this opens the [Cypress Test Runner](https://docs.cypress.io/guides/core-concepts/test-runner) to run your test(s) and show you the results.
|
||||||
|
|
||||||
@@ -449,4 +451,8 @@ DSpace uses GitHub to track issues:
|
|||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
This project's source code is made available under the DSpace BSD License: http://www.dspace.org/license
|
DSpace source code is freely available under a standard [BSD 3-Clause license](https://opensource.org/licenses/BSD-3-Clause).
|
||||||
|
The full license is available in the [LICENSE](LICENSE) file or online at http://www.dspace.org/license/
|
||||||
|
|
||||||
|
DSpace uses third-party libraries which may be distributed under different licenses. Those licenses are listed
|
||||||
|
in the [LICENSES_THIRD_PARTY](LICENSES_THIRD_PARTY) file.
|
||||||
|
@@ -177,16 +177,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputPath": "dist/server",
|
"outputPath": "dist/server",
|
||||||
"main": "src/main.server.ts",
|
"main": "server.ts",
|
||||||
"tsConfig": "tsconfig.server.json"
|
"tsConfig": "tsconfig.server.json"
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"sourceMap": false,
|
"sourceMap": false,
|
||||||
"optimization": {
|
"optimization": true
|
||||||
"scripts": false,
|
|
||||||
"styles": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@@ -5,5 +5,6 @@
|
|||||||
"screenshotsFolder": "cypress/screenshots",
|
"screenshotsFolder": "cypress/screenshots",
|
||||||
"pluginsFile": "cypress/plugins/index.ts",
|
"pluginsFile": "cypress/plugins/index.ts",
|
||||||
"fixturesFolder": "cypress/fixtures",
|
"fixturesFolder": "cypress/fixtures",
|
||||||
"baseUrl": "http://localhost:4000"
|
"baseUrl": "http://localhost:4000",
|
||||||
|
"retries": 2
|
||||||
}
|
}
|
@@ -4,6 +4,20 @@
|
|||||||
:warning: **NOT PRODUCTION READY** The below Docker Compose resources are not guaranteed "production ready" at this time. They have been built for development/testing only. Therefore, DSpace Docker images may not be fully secured or up-to-date. While you are welcome to base your own images on these DSpace images/resources, these should not be used "as is" in any production scenario.
|
:warning: **NOT PRODUCTION READY** The below Docker Compose resources are not guaranteed "production ready" at this time. They have been built for development/testing only. Therefore, DSpace Docker images may not be fully secured or up-to-date. While you are welcome to base your own images on these DSpace images/resources, these should not be used "as is" in any production scenario.
|
||||||
***
|
***
|
||||||
|
|
||||||
|
## 'Dockerfile' in root directory
|
||||||
|
This Dockerfile is used to build a *development* DSpace 7 Angular UI image, published as 'dspace/dspace-angular'
|
||||||
|
|
||||||
|
```
|
||||||
|
docker build -t dspace/dspace-angular:dspace-7_x .
|
||||||
|
```
|
||||||
|
|
||||||
|
This image is built *automatically* after each commit is made to the `main` branch.
|
||||||
|
|
||||||
|
Admins to our DockerHub repo can manually publish with the following command.
|
||||||
|
```
|
||||||
|
docker push dspace/dspace-angular:dspace-7_x
|
||||||
|
```
|
||||||
|
|
||||||
## docker directory
|
## docker directory
|
||||||
- docker-compose.yml
|
- docker-compose.yml
|
||||||
- Starts DSpace Angular with Docker Compose from the current branch. This file assumes that a DSpace 7 REST instance will also be started in Docker.
|
- Starts DSpace Angular with Docker Compose from the current branch. This file assumes that a DSpace 7 REST instance will also be started in Docker.
|
||||||
|
@@ -64,7 +64,7 @@ services:
|
|||||||
dspacesolr:
|
dspacesolr:
|
||||||
container_name: dspacesolr
|
container_name: dspacesolr
|
||||||
# Uses official Solr image at https://hub.docker.com/_/solr/
|
# Uses official Solr image at https://hub.docker.com/_/solr/
|
||||||
image: solr:8.8
|
image: solr:8.11-slim
|
||||||
# Needs main 'dspace' container to start first to guarantee access to solr_configs
|
# Needs main 'dspace' container to start first to guarantee access to solr_configs
|
||||||
depends_on:
|
depends_on:
|
||||||
- dspace
|
- dspace
|
||||||
|
@@ -62,7 +62,7 @@ services:
|
|||||||
dspacesolr:
|
dspacesolr:
|
||||||
container_name: dspacesolr
|
container_name: dspacesolr
|
||||||
# Uses official Solr image at https://hub.docker.com/_/solr/
|
# Uses official Solr image at https://hub.docker.com/_/solr/
|
||||||
image: solr:8.8
|
image: solr:8.11-slim
|
||||||
# Needs main 'dspace' container to start first to guarantee access to solr_configs
|
# Needs main 'dspace' container to start first to guarantee access to solr_configs
|
||||||
depends_on:
|
depends_on:
|
||||||
- dspace
|
- dspace
|
||||||
|
@@ -20,7 +20,7 @@ services:
|
|||||||
DSPACE_NAMESPACE: /
|
DSPACE_NAMESPACE: /
|
||||||
DSPACE_PORT: '4000'
|
DSPACE_PORT: '4000'
|
||||||
DSPACE_SSL: "false"
|
DSPACE_SSL: "false"
|
||||||
image: dspace/dspace-angular:latest
|
image: dspace/dspace-angular:dspace-7_x
|
||||||
build:
|
build:
|
||||||
context: ..
|
context: ..
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
100
package.json
100
package.json
@@ -26,16 +26,14 @@
|
|||||||
"build": "ng build",
|
"build": "ng build",
|
||||||
"build:stats": "ng build --stats-json",
|
"build:stats": "ng build --stats-json",
|
||||||
"build:prod": "yarn run build:ssr",
|
"build:prod": "yarn run build:ssr",
|
||||||
"build:ssr": "yarn run build:client-and-server-bundles && yarn run compile:server",
|
"build:ssr": "ng build --configuration production && ng run dspace-angular:server:production",
|
||||||
"build:client-and-server-bundles": "ng build --prod && ng run dspace-angular:server:production --bundleDependencies true",
|
|
||||||
"test:watch": "npm-run-all --parallel config:test:watch test",
|
"test:watch": "npm-run-all --parallel config:test:watch test",
|
||||||
"test": "ng test --sourceMap=true --watch=true",
|
"test": "ng test --sourceMap=true --watch=true",
|
||||||
"test:headless": "ng test --watch=false --sourceMap=true --browsers=ChromeHeadless --code-coverage",
|
"test:headless": "ng test --watch=false --sourceMap=true --browsers=ChromeHeadless --code-coverage",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
"lint-fix": "ng lint --fix=true",
|
"lint-fix": "ng lint --fix=true",
|
||||||
"e2e": "ng e2e",
|
"e2e": "ng e2e",
|
||||||
"compile:server": "webpack --config webpack.server.config.js --progress --color",
|
"serve:ssr": "node dist/server/main",
|
||||||
"serve:ssr": "node dist/server",
|
|
||||||
"clean:coverage": "rimraf coverage",
|
"clean:coverage": "rimraf coverage",
|
||||||
"clean:dist": "rimraf dist",
|
"clean:dist": "rimraf dist",
|
||||||
"clean:doc": "rimraf doc",
|
"clean:doc": "rimraf doc",
|
||||||
@@ -65,26 +63,26 @@
|
|||||||
"webdriver-manager": "^12.1.8"
|
"webdriver-manager": "^12.1.8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "~10.2.3",
|
"@angular/animations": "~11.2.14",
|
||||||
"@angular/cdk": "^10.2.6",
|
"@angular/cdk": "^11.2.13",
|
||||||
"@angular/common": "~10.2.3",
|
"@angular/common": "~11.2.14",
|
||||||
"@angular/compiler": "~10.2.3",
|
"@angular/compiler": "~11.2.14",
|
||||||
"@angular/core": "~10.2.3",
|
"@angular/core": "~11.2.14",
|
||||||
"@angular/forms": "~10.2.3",
|
"@angular/forms": "~11.2.14",
|
||||||
"@angular/localize": "10.2.3",
|
"@angular/localize": "11.2.14",
|
||||||
"@angular/platform-browser": "~10.2.3",
|
"@angular/platform-browser": "~11.2.14",
|
||||||
"@angular/platform-browser-dynamic": "~10.2.3",
|
"@angular/platform-browser-dynamic": "~11.2.14",
|
||||||
"@angular/platform-server": "~10.2.3",
|
"@angular/platform-server": "~11.2.14",
|
||||||
"@angular/router": "~10.2.3",
|
"@angular/router": "~11.2.14",
|
||||||
"@angularclass/bootloader": "1.0.1",
|
"@angularclass/bootloader": "1.0.1",
|
||||||
"@kolkov/ngx-gallery": "^1.2.3",
|
"@kolkov/ngx-gallery": "^1.2.3",
|
||||||
"@ng-bootstrap/ng-bootstrap": "7.0.0",
|
"@ng-bootstrap/ng-bootstrap": "9.1.3",
|
||||||
"@ng-dynamic-forms/core": "^12.0.0",
|
"@ng-dynamic-forms/core": "^13.0.0",
|
||||||
"@ng-dynamic-forms/ui-ng-bootstrap": "^12.0.0",
|
"@ng-dynamic-forms/ui-ng-bootstrap": "^13.0.0",
|
||||||
"@ngrx/effects": "^10.0.1",
|
"@ngrx/effects": "^11.1.1",
|
||||||
"@ngrx/router-store": "^10.0.1",
|
"@ngrx/router-store": "^11.1.1",
|
||||||
"@ngrx/store": "^10.0.1",
|
"@ngrx/store": "^11.1.1",
|
||||||
"@nguniversal/express-engine": "10.1.0",
|
"@nguniversal/express-engine": "11.2.1",
|
||||||
"@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",
|
||||||
"angular-idle-preload": "3.0.0",
|
"angular-idle-preload": "3.0.0",
|
||||||
@@ -94,9 +92,9 @@
|
|||||||
"caniuse-lite": "^1.0.30001165",
|
"caniuse-lite": "^1.0.30001165",
|
||||||
"cerialize": "0.1.18",
|
"cerialize": "0.1.18",
|
||||||
"cli-progress": "^3.8.0",
|
"cli-progress": "^3.8.0",
|
||||||
|
"compression": "^1.7.4",
|
||||||
"cookie-parser": "1.4.5",
|
"cookie-parser": "1.4.5",
|
||||||
"core-js": "^3.7.0",
|
"core-js": "^3.7.0",
|
||||||
"debug-loader": "^0.0.1",
|
|
||||||
"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",
|
||||||
@@ -105,78 +103,78 @@
|
|||||||
"filesize": "^6.1.0",
|
"filesize": "^6.1.0",
|
||||||
"font-awesome": "4.7.0",
|
"font-awesome": "4.7.0",
|
||||||
"https": "1.0.0",
|
"https": "1.0.0",
|
||||||
|
"http-proxy-middleware": "^1.0.5",
|
||||||
"js-cookie": "2.2.1",
|
"js-cookie": "2.2.1",
|
||||||
"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.10",
|
||||||
"mirador": "^3.0.0",
|
"lodash": "^4.17.21",
|
||||||
|
"mirador": "^3.3.0",
|
||||||
"mirador-dl-plugin": "^0.13.0",
|
"mirador-dl-plugin": "^0.13.0",
|
||||||
"mirador-share-plugin": "^0.10.0",
|
"mirador-share-plugin": "^0.11.0",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"ng-mocks": "10.5.4",
|
"ng-mocks": "11.11.2",
|
||||||
"ng2-file-upload": "1.4.0",
|
"ng2-file-upload": "1.4.0",
|
||||||
"ng2-nouislider": "^1.8.2",
|
"ng2-nouislider": "^1.8.3",
|
||||||
"ngx-infinite-scroll": "^10.0.1",
|
"ngx-infinite-scroll": "^10.0.1",
|
||||||
"ngx-moment": "^5.0.0",
|
"ngx-moment": "^5.0.0",
|
||||||
"ngx-pagination": "5.0.0",
|
"ngx-pagination": "5.0.0",
|
||||||
"ngx-sortablejs": "^10.0.0",
|
"ngx-sortablejs": "^11.1.0",
|
||||||
"nouislider": "^14.6.3",
|
"nouislider": "^14.6.3",
|
||||||
"pem": "1.14.4",
|
"pem": "1.14.4",
|
||||||
"postcss-cli": "^8.3.0",
|
"postcss-cli": "^8.3.0",
|
||||||
"react": "^16.14.0",
|
|
||||||
"react-dom": "^16.14.0",
|
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "^6.6.3",
|
"rxjs": "^6.6.3",
|
||||||
"rxjs-spy": "^7.5.3",
|
|
||||||
"sass-resources-loader": "^2.1.1",
|
|
||||||
"sortablejs": "1.13.0",
|
"sortablejs": "1.13.0",
|
||||||
"tslib": "^2.0.0",
|
"tslib": "^2.0.0",
|
||||||
|
"url-parse": "^1.5.3",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
"webfontloader": "1.6.28",
|
"webfontloader": "1.6.28",
|
||||||
"zone.js": "^0.10.3"
|
"zone.js": "^0.10.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-builders/custom-webpack": "10.0.1",
|
"@angular-builders/custom-webpack": "10.0.1",
|
||||||
"@angular-devkit/build-angular": "~0.1002.0",
|
"@angular-devkit/build-angular": "~0.1102.15",
|
||||||
"@angular/cli": "~10.2.0",
|
"@angular/cli": "~11.2.15",
|
||||||
"@angular/compiler-cli": "~10.2.3",
|
"@angular/compiler-cli": "~11.2.14",
|
||||||
"@angular/language-service": "~10.2.3",
|
"@angular/language-service": "~11.2.14",
|
||||||
"@cypress/schematic": "^1.5.0",
|
"@cypress/schematic": "^1.5.0",
|
||||||
"@fortawesome/fontawesome-free": "^5.5.0",
|
"@fortawesome/fontawesome-free": "^5.5.0",
|
||||||
"@ngrx/store-devtools": "^10.0.1",
|
"@ngrx/store-devtools": "^11.1.1",
|
||||||
"@ngtools/webpack": "10.2.0",
|
"@ngtools/webpack": "10.2.3",
|
||||||
"@nguniversal/builders": "~10.1.0",
|
"@nguniversal/builders": "~11.2.1",
|
||||||
"@types/deep-freeze": "0.1.2",
|
"@types/deep-freeze": "0.1.2",
|
||||||
"@types/express": "^4.17.9",
|
"@types/express": "^4.17.9",
|
||||||
"@types/file-saver": "^2.0.1",
|
"@types/file-saver": "^2.0.1",
|
||||||
"@types/jasmine": "^3.6.2",
|
"@types/jasmine": "~3.6.0",
|
||||||
"@types/jasminewd2": "~2.0.8",
|
"@types/jasminewd2": "~2.0.8",
|
||||||
"@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",
|
||||||
"axe-core": "^4.3.3",
|
"axe-core": "^4.3.3",
|
||||||
"codelyzer": "^6.0.1",
|
"codelyzer": "^6.0.0",
|
||||||
"compression-webpack-plugin": "^3.0.1",
|
"compression-webpack-plugin": "^3.0.1",
|
||||||
"copy-webpack-plugin": "^6.4.1",
|
"copy-webpack-plugin": "^6.4.1",
|
||||||
"css-loader": "3.4.0",
|
"css-loader": "3.4.0",
|
||||||
"cssnano": "^4.1.10",
|
"cssnano": "^4.1.10",
|
||||||
"cypress": "8.6.0",
|
"cypress": "8.6.0",
|
||||||
"cypress-axe": "^0.13.0",
|
"cypress-axe": "^0.13.0",
|
||||||
|
"debug-loader": "^0.0.1",
|
||||||
"deep-freeze": "0.0.1",
|
"deep-freeze": "0.0.1",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"fork-ts-checker-webpack-plugin": "^6.0.3",
|
"fork-ts-checker-webpack-plugin": "^6.0.3",
|
||||||
"html-loader": "^1.3.2",
|
"html-loader": "^1.3.2",
|
||||||
"html-webpack-plugin": "^4.5.0",
|
"html-webpack-plugin": "^4.5.0",
|
||||||
"http-proxy-middleware": "^1.0.5",
|
"jasmine-core": "~3.6.0",
|
||||||
"jasmine-core": "^3.6.0",
|
|
||||||
"jasmine-marbles": "0.6.0",
|
"jasmine-marbles": "0.6.0",
|
||||||
"jasmine-spec-reporter": "^6.0.0",
|
"jasmine-spec-reporter": "~5.0.0",
|
||||||
"karma": "^5.2.3",
|
"karma": "^5.2.3",
|
||||||
"karma-chrome-launcher": "^3.1.0",
|
"karma-chrome-launcher": "~3.1.0",
|
||||||
"karma-coverage-istanbul-reporter": "~3.0.2",
|
"karma-coverage-istanbul-reporter": "~3.0.2",
|
||||||
"karma-jasmine": "^4.0.1",
|
"karma-jasmine": "~4.0.0",
|
||||||
"karma-jasmine-html-reporter": "^1.5.4",
|
"karma-jasmine-html-reporter": "^1.5.0",
|
||||||
"karma-mocha-reporter": "2.2.5",
|
"karma-mocha-reporter": "2.2.5",
|
||||||
"nodemon": "^2.0.2",
|
"nodemon": "^2.0.2",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
@@ -189,17 +187,21 @@
|
|||||||
"protractor": "^7.0.0",
|
"protractor": "^7.0.0",
|
||||||
"protractor-istanbul-plugin": "2.0.0",
|
"protractor-istanbul-plugin": "2.0.0",
|
||||||
"raw-loader": "0.5.1",
|
"raw-loader": "0.5.1",
|
||||||
|
"react": "^16.14.0",
|
||||||
|
"react-dom": "^16.14.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
|
"rxjs-spy": "^7.5.3",
|
||||||
|
"sass-resources-loader": "^2.1.1",
|
||||||
"script-ext-html-webpack-plugin": "2.1.5",
|
"script-ext-html-webpack-plugin": "2.1.5",
|
||||||
"string-replace-loader": "^2.3.0",
|
"string-replace-loader": "^2.3.0",
|
||||||
"terser-webpack-plugin": "^2.3.1",
|
"terser-webpack-plugin": "^2.3.1",
|
||||||
"ts-loader": "^5.2.0",
|
"ts-loader": "^5.2.0",
|
||||||
"ts-node": "^8.8.1",
|
"ts-node": "^8.10.2",
|
||||||
"tslint": "^6.1.3",
|
"tslint": "^6.1.3",
|
||||||
"typescript": "~4.0.5",
|
"typescript": "~4.0.5",
|
||||||
"webpack": "^4.44.2",
|
"webpack": "^4.44.2",
|
||||||
"webpack-bundle-analyzer": "^4.4.0",
|
"webpack-bundle-analyzer": "^4.4.0",
|
||||||
"webpack-cli": "^4.2.0",
|
"webpack-cli": "^4.2.0",
|
||||||
"webpack-node-externals": "1.7.2"
|
"webpack-dev-server": "^4.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
93
server.ts
93
server.ts
@@ -30,6 +30,7 @@ import { join } from 'path';
|
|||||||
|
|
||||||
import { enableProdMode } from '@angular/core';
|
import { enableProdMode } from '@angular/core';
|
||||||
import { existsSync } from 'fs';
|
import { existsSync } from 'fs';
|
||||||
|
import { ngExpressEngine } from '@nguniversal/express-engine';
|
||||||
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
|
import { REQUEST, RESPONSE } from '@nguniversal/express-engine/tokens';
|
||||||
import { environment } from './src/environments/environment';
|
import { environment } from './src/environments/environment';
|
||||||
import { createProxyMiddleware } from 'http-proxy-middleware';
|
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||||
@@ -37,6 +38,8 @@ import { hasValue, hasNoValue } from './src/app/shared/empty.util';
|
|||||||
import { APP_BASE_HREF } from '@angular/common';
|
import { APP_BASE_HREF } from '@angular/common';
|
||||||
import { UIServerConfig } from './src/config/ui-server-config.interface';
|
import { UIServerConfig } from './src/config/ui-server-config.interface';
|
||||||
|
|
||||||
|
import { ServerAppModule } from './src/main.server';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Set path for the browser application's dist folder
|
* Set path for the browser application's dist folder
|
||||||
*/
|
*/
|
||||||
@@ -46,9 +49,6 @@ const IIIF_VIEWER = join(process.cwd(), 'dist/iiif');
|
|||||||
|
|
||||||
const indexHtml = existsSync(join(DIST_FOLDER, 'index.html')) ? 'index.html' : 'index';
|
const indexHtml = existsSync(join(DIST_FOLDER, 'index.html')) ? 'index.html' : 'index';
|
||||||
|
|
||||||
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
|
|
||||||
const { ServerAppModule, ngExpressEngine } = require('./dist/server/main');
|
|
||||||
|
|
||||||
const cookieParser = require('cookie-parser');
|
const cookieParser = require('cookie-parser');
|
||||||
|
|
||||||
// The Express app is exported so that it can be used by serverless Functions.
|
// The Express app is exported so that it can be used by serverless Functions.
|
||||||
@@ -59,7 +59,6 @@ export function app() {
|
|||||||
*/
|
*/
|
||||||
const server = express();
|
const server = express();
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* 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
|
||||||
@@ -227,47 +226,59 @@ function run() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
function start() {
|
||||||
* If SSL is enabled
|
/*
|
||||||
* - Read credentials from configuration files
|
* If SSL is enabled
|
||||||
* - Call script to start an HTTPS server with these credentials
|
* - Read credentials from configuration files
|
||||||
* When SSL is disabled
|
* - Call script to start an HTTPS server with these credentials
|
||||||
* - Start an HTTP server on the configured port and host
|
* When SSL is disabled
|
||||||
*/
|
* - Start an HTTP server on the configured port and host
|
||||||
if (environment.ui.ssl) {
|
*/
|
||||||
let serviceKey;
|
if (environment.ui.ssl) {
|
||||||
try {
|
let serviceKey;
|
||||||
serviceKey = fs.readFileSync('./config/ssl/key.pem');
|
try {
|
||||||
} catch (e) {
|
serviceKey = fs.readFileSync('./config/ssl/key.pem');
|
||||||
console.warn('Service key not found at ./config/ssl/key.pem');
|
} catch (e) {
|
||||||
}
|
console.warn('Service key not found at ./config/ssl/key.pem');
|
||||||
|
}
|
||||||
|
|
||||||
let certificate;
|
let certificate;
|
||||||
try {
|
try {
|
||||||
certificate = fs.readFileSync('./config/ssl/cert.pem');
|
certificate = fs.readFileSync('./config/ssl/cert.pem');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Certificate not found at ./config/ssl/key.pem');
|
console.warn('Certificate not found at ./config/ssl/key.pem');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serviceKey && certificate) {
|
if (serviceKey && certificate) {
|
||||||
createHttpsServer({
|
createHttpsServer({
|
||||||
serviceKey: serviceKey,
|
serviceKey: serviceKey,
|
||||||
certificate: certificate
|
certificate: certificate
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
console.warn('Disabling certificate validation and proceeding with a self-signed certificate. If this is a production server, it is recommended that you configure a valid certificate instead.');
|
||||||
|
|
||||||
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // lgtm[js/disabling-certificate-validation]
|
||||||
|
|
||||||
|
pem.createCertificate({
|
||||||
|
days: 1,
|
||||||
|
selfSigned: true
|
||||||
|
}, (error, keys) => {
|
||||||
|
createHttpsServer(keys);
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn('Disabling certificate validation and proceeding with a self-signed certificate. If this is a production server, it is recommended that you configure a valid certificate instead.');
|
run();
|
||||||
|
|
||||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // lgtm[js/disabling-certificate-validation]
|
|
||||||
|
|
||||||
pem.createCertificate({
|
|
||||||
days: 1,
|
|
||||||
selfSigned: true
|
|
||||||
}, (error, keys) => {
|
|
||||||
createHttpsServer(keys);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
run();
|
|
||||||
|
// Webpack will replace 'require' with '__webpack_require__'
|
||||||
|
// '__non_webpack_require__' is a proxy to Node 'require'
|
||||||
|
// The below code is to ensure that the server is run only when not requiring the bundle.
|
||||||
|
declare const __non_webpack_require__: NodeRequire;
|
||||||
|
const mainModule = __non_webpack_require__.main;
|
||||||
|
const moduleFilename = (mainModule && mainModule.filename) || '';
|
||||||
|
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
|
||||||
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
export * from './src/main.server';
|
export * from './src/main.server';
|
||||||
|
@@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule, FormArray, FormControl, FormGroup,Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS } from '@angular/forms';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
@@ -34,6 +34,7 @@ import { TranslateLoaderMock } from '../../../shared/testing/translate-loader.mo
|
|||||||
import { RouterMock } from '../../../shared/mocks/router.mock';
|
import { RouterMock } from '../../../shared/mocks/router.mock';
|
||||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service.stub';
|
||||||
import { Operation } from 'fast-json-patch';
|
import { Operation } from 'fast-json-patch';
|
||||||
|
import { ValidateGroupExists } from './validators/group-exists.validator';
|
||||||
|
|
||||||
describe('GroupFormComponent', () => {
|
describe('GroupFormComponent', () => {
|
||||||
let component: GroupFormComponent;
|
let component: GroupFormComponent;
|
||||||
@@ -117,7 +118,69 @@ describe('GroupFormComponent', () => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
builderService = getMockFormBuilderService();
|
builderService = Object.assign(getMockFormBuilderService(),{
|
||||||
|
createFormGroup(formModel, options = null) {
|
||||||
|
const controls = {};
|
||||||
|
formModel.forEach( model => {
|
||||||
|
model.parent = parent;
|
||||||
|
const controlModel = model;
|
||||||
|
const controlState = { value: controlModel.value, disabled: controlModel.disabled };
|
||||||
|
const controlOptions = this.createAbstractControlOptions(controlModel.validators, controlModel.asyncValidators, controlModel.updateOn);
|
||||||
|
controls[model.id] = new FormControl(controlState, controlOptions);
|
||||||
|
});
|
||||||
|
return new FormGroup(controls, options);
|
||||||
|
},
|
||||||
|
createAbstractControlOptions(validatorsConfig = null, asyncValidatorsConfig = null, updateOn = null) {
|
||||||
|
return {
|
||||||
|
validators: validatorsConfig !== null ? this.getValidators(validatorsConfig) : null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
getValidators(validatorsConfig) {
|
||||||
|
return this.getValidatorFns(validatorsConfig);
|
||||||
|
},
|
||||||
|
getValidatorFns(validatorsConfig, validatorsToken = this._NG_VALIDATORS) {
|
||||||
|
let validatorFns = [];
|
||||||
|
if (this.isObject(validatorsConfig)) {
|
||||||
|
validatorFns = Object.keys(validatorsConfig).map(validatorConfigKey => {
|
||||||
|
const validatorConfigValue = validatorsConfig[validatorConfigKey];
|
||||||
|
if (this.isValidatorDescriptor(validatorConfigValue)) {
|
||||||
|
const descriptor = validatorConfigValue;
|
||||||
|
return this.getValidatorFn(descriptor.name, descriptor.args, validatorsToken);
|
||||||
|
}
|
||||||
|
return this.getValidatorFn(validatorConfigKey, validatorConfigValue, validatorsToken);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return validatorFns;
|
||||||
|
},
|
||||||
|
getValidatorFn(validatorName, validatorArgs = null, validatorsToken = this._NG_VALIDATORS) {
|
||||||
|
let validatorFn;
|
||||||
|
if (Validators.hasOwnProperty(validatorName)) { // Built-in Angular Validators
|
||||||
|
validatorFn = Validators[validatorName];
|
||||||
|
} else { // Custom Validators
|
||||||
|
if (this._DYNAMIC_VALIDATORS && this._DYNAMIC_VALIDATORS.has(validatorName)) {
|
||||||
|
validatorFn = this._DYNAMIC_VALIDATORS.get(validatorName);
|
||||||
|
} else if (validatorsToken) {
|
||||||
|
validatorFn = validatorsToken.find(validator => validator.name === validatorName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (validatorFn === undefined) { // throw when no validator could be resolved
|
||||||
|
throw new Error(`validator '${validatorName}' is not provided via NG_VALIDATORS, NG_ASYNC_VALIDATORS or DYNAMIC_FORM_VALIDATORS`);
|
||||||
|
}
|
||||||
|
if (validatorArgs !== null) {
|
||||||
|
return validatorFn(validatorArgs);
|
||||||
|
}
|
||||||
|
return validatorFn;
|
||||||
|
},
|
||||||
|
isValidatorDescriptor(value) {
|
||||||
|
if (this.isObject(value)) {
|
||||||
|
return value.hasOwnProperty('name') && value.hasOwnProperty('args');
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
isObject(value) {
|
||||||
|
return typeof value === 'object' && value !== null;
|
||||||
|
}
|
||||||
|
});
|
||||||
translateService = getMockTranslateService();
|
translateService = getMockTranslateService();
|
||||||
router = new RouterMock();
|
router = new RouterMock();
|
||||||
notificationService = new NotificationsServiceStub();
|
notificationService = new NotificationsServiceStub();
|
||||||
@@ -217,4 +280,72 @@ describe('GroupFormComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('check form validation', () => {
|
||||||
|
let groupCommunity;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
groupName = 'testName';
|
||||||
|
groupCommunity = 'testgroupCommunity';
|
||||||
|
groupDescription = 'testgroupDescription';
|
||||||
|
|
||||||
|
expected = Object.assign(new Group(), {
|
||||||
|
name: groupName,
|
||||||
|
metadata: {
|
||||||
|
'dc.description': [
|
||||||
|
{
|
||||||
|
value: groupDescription
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
spyOn(component.submitForm, 'emit');
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
component.initialisePage();
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
describe('groupName, groupCommunity and groupDescription should be required', () => {
|
||||||
|
it('form should be invalid because the groupName is required', waitForAsync(() => {
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
expect(component.formGroup.controls.groupName.valid).toBeFalse();
|
||||||
|
expect(component.formGroup.controls.groupName.errors.required).toBeTrue();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('after inserting information groupName,groupCommunity and groupDescription not required', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
component.formGroup.controls.groupName.setValue('test');
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
it('groupName should be valid because the groupName is set', waitForAsync(() => {
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
expect(component.formGroup.controls.groupName.valid).toBeTrue();
|
||||||
|
expect(component.formGroup.controls.groupName.errors).toBeNull();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('after already utilized groupName', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const groupsDataServiceStubWithGroup = Object.assign(groupsDataServiceStub,{
|
||||||
|
searchGroups(query: string): Observable<RemoteData<PaginatedList<Group>>> {
|
||||||
|
return createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), [expected]));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
component.formGroup.controls.groupName.setValue('testName');
|
||||||
|
component.formGroup.controls.groupName.setAsyncValidators(ValidateGroupExists.createValidator(groupsDataServiceStubWithGroup));
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('groupName should not be valid because groupName is already taken', waitForAsync(() => {
|
||||||
|
fixture.whenStable().then(() => {
|
||||||
|
expect(component.formGroup.controls.groupName.valid).toBeFalse();
|
||||||
|
expect(component.formGroup.controls.groupName.errors.groupExists).toBeTruthy();
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, EventEmitter, HostListener, OnDestroy, OnInit, Output } from '@angular/core';
|
import { Component, EventEmitter, HostListener, OnDestroy, OnInit, Output, ChangeDetectorRef } from '@angular/core';
|
||||||
import { FormGroup } from '@angular/forms';
|
import { FormGroup } from '@angular/forms';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
of as observableOf,
|
of as observableOf,
|
||||||
Subscription,
|
Subscription,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { catchError, map, switchMap, take, filter } from 'rxjs/operators';
|
import { catchError, map, switchMap, take, filter, debounceTime } from 'rxjs/operators';
|
||||||
import { getCollectionEditRolesRoute } from '../../../collection-page/collection-page-routing-paths';
|
import { getCollectionEditRolesRoute } from '../../../collection-page/collection-page-routing-paths';
|
||||||
import { getCommunityEditRolesRoute } from '../../../community-page/community-page-routing-paths';
|
import { getCommunityEditRolesRoute } from '../../../community-page/community-page-routing-paths';
|
||||||
import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../../core/data/dspace-object-data.service';
|
||||||
@@ -45,6 +45,7 @@ import { NotificationsService } from '../../../shared/notifications/notification
|
|||||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||||
import { NoContent } from '../../../core/shared/NoContent.model';
|
import { NoContent } from '../../../core/shared/NoContent.model';
|
||||||
import { Operation } from 'fast-json-patch';
|
import { Operation } from 'fast-json-patch';
|
||||||
|
import { ValidateGroupExists } from './validators/group-exists.validator';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-group-form',
|
selector: 'ds-group-form',
|
||||||
@@ -126,6 +127,12 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
public AlertTypeEnum = AlertType;
|
public AlertTypeEnum = AlertType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription to email field value change
|
||||||
|
*/
|
||||||
|
groupNameValueChangeSubscribe: Subscription;
|
||||||
|
|
||||||
|
|
||||||
constructor(public groupDataService: GroupDataService,
|
constructor(public groupDataService: GroupDataService,
|
||||||
private ePersonDataService: EPersonDataService,
|
private ePersonDataService: EPersonDataService,
|
||||||
private dSpaceObjectDataService: DSpaceObjectDataService,
|
private dSpaceObjectDataService: DSpaceObjectDataService,
|
||||||
@@ -136,7 +143,8 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
protected router: Router,
|
protected router: Router,
|
||||||
private authorizationService: AuthorizationDataService,
|
private authorizationService: AuthorizationDataService,
|
||||||
private modalService: NgbModal,
|
private modalService: NgbModal,
|
||||||
public requestService: RequestService) {
|
public requestService: RequestService,
|
||||||
|
protected changeDetectorRef: ChangeDetectorRef) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@@ -192,6 +200,14 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
this.groupDescription,
|
this.groupDescription,
|
||||||
];
|
];
|
||||||
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
this.formGroup = this.formBuilderService.createFormGroup(this.formModel);
|
||||||
|
|
||||||
|
if (!!this.formGroup.controls.groupName) {
|
||||||
|
this.formGroup.controls.groupName.setAsyncValidators(ValidateGroupExists.createValidator(this.groupDataService));
|
||||||
|
this.groupNameValueChangeSubscribe = this.groupName.valueChanges.pipe(debounceTime(300)).subscribe(() => {
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
observableCombineLatest(
|
observableCombineLatest(
|
||||||
this.groupDataService.getActiveGroup(),
|
this.groupDataService.getActiveGroup(),
|
||||||
@@ -201,6 +217,10 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
).subscribe(([activeGroup, canEdit, linkedObject]) => {
|
).subscribe(([activeGroup, canEdit, linkedObject]) => {
|
||||||
|
|
||||||
if (activeGroup != null) {
|
if (activeGroup != null) {
|
||||||
|
|
||||||
|
// Disable group name exists validator
|
||||||
|
this.formGroup.controls.groupName.clearAsyncValidators();
|
||||||
|
|
||||||
this.groupBeingEdited = activeGroup;
|
this.groupBeingEdited = activeGroup;
|
||||||
|
|
||||||
if (linkedObject?.name) {
|
if (linkedObject?.name) {
|
||||||
@@ -436,6 +456,11 @@ export class GroupFormComponent implements OnInit, OnDestroy {
|
|||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.groupDataService.cancelEditGroup();
|
this.groupDataService.cancelEditGroup();
|
||||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
||||||
|
|
||||||
|
if ( hasValue(this.groupNameValueChangeSubscribe) ) {
|
||||||
|
this.groupNameValueChangeSubscribe.unsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -0,0 +1,33 @@
|
|||||||
|
import { AbstractControl, ValidationErrors } from '@angular/forms';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { map} from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
|
import { getFirstSucceededRemoteListPayload } from '../../../../core/shared/operators';
|
||||||
|
import { Group } from '../../../../core/eperson/models/group.model';
|
||||||
|
|
||||||
|
export class ValidateGroupExists {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method will create the validator with the groupDataService requested from component
|
||||||
|
* @param groupDataService the service with DI in the component that this validator is being utilized.
|
||||||
|
* @return Observable<ValidationErrors | null>
|
||||||
|
*/
|
||||||
|
static createValidator(groupDataService: GroupDataService) {
|
||||||
|
return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
|
||||||
|
return groupDataService.searchGroups(control.value, {
|
||||||
|
currentPage: 1,
|
||||||
|
elementsPerPage: 100
|
||||||
|
})
|
||||||
|
.pipe(
|
||||||
|
getFirstSucceededRemoteListPayload(),
|
||||||
|
map( (groups: Group[]) => {
|
||||||
|
return groups.filter(group => group.name === control.value);
|
||||||
|
}),
|
||||||
|
map( (groups: Group[]) => {
|
||||||
|
return groups.length > 0 ? { groupExists: true } : null;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -36,7 +36,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
export class AdminSearchModule {
|
export class AdminSearchModule {
|
||||||
/**
|
/**
|
||||||
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
||||||
* which are not loaded during CSR otherwise
|
* which are not loaded during SSR otherwise
|
||||||
*/
|
*/
|
||||||
static withEntryComponents() {
|
static withEntryComponents() {
|
||||||
return {
|
return {
|
||||||
|
@@ -28,7 +28,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
export class AdminWorkflowModuleModule {
|
export class AdminWorkflowModuleModule {
|
||||||
/**
|
/**
|
||||||
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
||||||
* which are not loaded during CSR otherwise
|
* which are not loaded during SSR otherwise
|
||||||
*/
|
*/
|
||||||
static withEntryComponents() {
|
static withEntryComponents() {
|
||||||
return {
|
return {
|
||||||
|
@@ -34,7 +34,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
export class AdminModule {
|
export class AdminModule {
|
||||||
/**
|
/**
|
||||||
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
||||||
* which are not loaded during CSR otherwise
|
* which are not loaded during SSR otherwise
|
||||||
*/
|
*/
|
||||||
static withEntryComponents() {
|
static withEntryComponents() {
|
||||||
return {
|
return {
|
||||||
|
@@ -202,8 +202,8 @@ import { GroupAdministratorGuard } from './core/data/feature-authorization/featu
|
|||||||
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent },
|
{ path: '**', pathMatch: 'full', component: ThemedPageNotFoundComponent },
|
||||||
]}
|
]}
|
||||||
],{
|
],{
|
||||||
onSameUrlNavigation: 'reload',
|
onSameUrlNavigation: 'reload',
|
||||||
})
|
})
|
||||||
],
|
],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
})
|
})
|
||||||
|
@@ -171,7 +171,8 @@ describe('App component', () => {
|
|||||||
TestBed.configureTestingModule(getDefaultTestBedConf());
|
TestBed.configureTestingModule(getDefaultTestBedConf());
|
||||||
TestBed.overrideProvider(ThemeService, {useValue: getMockThemeService('custom')});
|
TestBed.overrideProvider(ThemeService, {useValue: getMockThemeService('custom')});
|
||||||
document = TestBed.inject(DOCUMENT);
|
document = TestBed.inject(DOCUMENT);
|
||||||
headSpy = jasmine.createSpyObj('head', ['appendChild']);
|
headSpy = jasmine.createSpyObj('head', ['appendChild', 'getElementsByClassName']);
|
||||||
|
headSpy.getElementsByClassName.and.returnValue([]);
|
||||||
spyOn(document, 'getElementsByTagName').and.returnValue([headSpy]);
|
spyOn(document, 'getElementsByTagName').and.returnValue([headSpy]);
|
||||||
fixture = TestBed.createComponent(AppComponent);
|
fixture = TestBed.createComponent(AppComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
|
@@ -31,12 +31,12 @@ import { AuthService } from './core/auth/auth.service';
|
|||||||
import { CSSVariableService } from './shared/sass-helper/sass-helper.service';
|
import { CSSVariableService } from './shared/sass-helper/sass-helper.service';
|
||||||
import { MenuService } from './shared/menu/menu.service';
|
import { MenuService } from './shared/menu/menu.service';
|
||||||
import { HostWindowService } from './shared/host-window.service';
|
import { HostWindowService } from './shared/host-window.service';
|
||||||
import { ThemeConfig } from '../config/theme.model';
|
import { HeadTagConfig, ThemeConfig } from '../config/theme.model';
|
||||||
import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider';
|
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 { LocaleService } from './core/locale/locale.service';
|
||||||
import { hasValue, isNotEmpty } from './shared/empty.util';
|
import { hasNoValue, hasValue, isNotEmpty } from './shared/empty.util';
|
||||||
import { KlaroService } from './shared/cookies/klaro.service';
|
import { KlaroService } from './shared/cookies/klaro.service';
|
||||||
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
||||||
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
||||||
@@ -115,11 +115,11 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
this.isThemeCSSLoading$.next(true);
|
this.isThemeCSSLoading$.next(true);
|
||||||
}
|
}
|
||||||
if (hasValue(themeName)) {
|
if (hasValue(themeName)) {
|
||||||
this.setThemeCss(themeName);
|
this.loadGlobalThemeConfig(themeName);
|
||||||
} else if (hasValue(DEFAULT_THEME_CONFIG)) {
|
} else if (hasValue(DEFAULT_THEME_CONFIG)) {
|
||||||
this.setThemeCss(DEFAULT_THEME_CONFIG.name);
|
this.loadGlobalThemeConfig(DEFAULT_THEME_CONFIG.name);
|
||||||
} else {
|
} else {
|
||||||
this.setThemeCss(BASE_THEME_NAME);
|
this.loadGlobalThemeConfig(BASE_THEME_NAME);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -233,6 +233,11 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private loadGlobalThemeConfig(themeName: string): void {
|
||||||
|
this.setThemeCss(themeName);
|
||||||
|
this.setHeadTags(themeName);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the theme css file in <head>
|
* Update the theme css file in <head>
|
||||||
*
|
*
|
||||||
@@ -241,9 +246,13 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
*/
|
*/
|
||||||
private setThemeCss(themeName: string): void {
|
private setThemeCss(themeName: string): void {
|
||||||
const head = this.document.getElementsByTagName('head')[0];
|
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
|
// Array.from to ensure we end up with an array, not an HTMLCollection, which would be
|
||||||
// automatically updated if we add nodes later
|
// automatically updated if we add nodes later
|
||||||
const currentThemeLinks = Array.from(this.document.getElementsByClassName('theme-css'));
|
const currentThemeLinks = Array.from(head.getElementsByClassName('theme-css'));
|
||||||
const link = this.document.createElement('link');
|
const link = this.document.createElement('link');
|
||||||
link.setAttribute('rel', 'stylesheet');
|
link.setAttribute('rel', 'stylesheet');
|
||||||
link.setAttribute('type', 'text/css');
|
link.setAttribute('type', 'text/css');
|
||||||
@@ -265,6 +274,78 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
head.appendChild(link);
|
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 defaultThemeName = DEFAULT_THEME_CONFIG.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(DEFAULT_THEME_CONFIG.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();
|
||||||
|
@@ -10,11 +10,11 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<ng-template #breadcrumb let-text="text" let-url="url">
|
<ng-template #breadcrumb let-text="text" let-url="url">
|
||||||
<li class="breadcrumb-item"><a [routerLink]="url">{{text | translate}}</a></li>
|
<li class="breadcrumb-item"><div class="breadcrumb-item-limiter"><a [routerLink]="url" class="text-truncate">{{text | translate}}</a></div></li>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #activeBreadcrumb let-text="text">
|
<ng-template #activeBreadcrumb let-text="text">
|
||||||
<li class="breadcrumb-item active" aria-current="page">{{text | translate}}</li>
|
<li class="breadcrumb-item active" aria-current="page"><div class="breadcrumb-item-limiter"><div class="text-truncate">{{text | translate}}</div></div></li>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
@@ -10,6 +10,19 @@
|
|||||||
background-color: var(--ds-breadcrumb-bg);
|
background-color: var(--ds-breadcrumb-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
li.breadcrumb-item {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-item-limiter {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: var(--ds-breadcrumb-max-length);
|
||||||
|
> * {
|
||||||
|
max-width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
li.breadcrumb-item > a {
|
li.breadcrumb-item > a {
|
||||||
color: var(--ds-breadcrumb-link-color) !important;
|
color: var(--ds-breadcrumb-link-color) !important;
|
||||||
}
|
}
|
||||||
@@ -18,5 +31,6 @@ li.breadcrumb-item.active {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.breadcrumb-item+ .breadcrumb-item::before {
|
.breadcrumb-item+ .breadcrumb-item::before {
|
||||||
|
display: block;
|
||||||
content: quote("•") !important;
|
content: quote("•") !important;
|
||||||
}
|
}
|
||||||
|
@@ -8,7 +8,7 @@ import { By } from '@angular/platform-browser';
|
|||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
import { TranslateLoaderMock } from '../shared/testing/translate-loader.mock';
|
import { TranslateLoaderMock } from '../shared/testing/translate-loader.mock';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { DebugElement } from '@angular/core';
|
import { DebugElement } from '@angular/core';
|
||||||
|
|
||||||
describe('BreadcrumbsComponent', () => {
|
describe('BreadcrumbsComponent', () => {
|
||||||
@@ -72,7 +72,7 @@ describe('BreadcrumbsComponent', () => {
|
|||||||
expect(breadcrumbs.length).toBe(3);
|
expect(breadcrumbs.length).toBe(3);
|
||||||
expectBreadcrumb(breadcrumbs[0], 'home.breadcrumbs', '/');
|
expectBreadcrumb(breadcrumbs[0], 'home.breadcrumbs', '/');
|
||||||
expectBreadcrumb(breadcrumbs[1], 'bc 1', '/example.com');
|
expectBreadcrumb(breadcrumbs[1], 'bc 1', '/example.com');
|
||||||
expectBreadcrumb(breadcrumbs[2], 'bc 2', null);
|
expectBreadcrumb(breadcrumbs[2].query(By.css('.text-truncate')), 'bc 2', null);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { Breadcrumb } from './breadcrumb/breadcrumb.model';
|
import { Breadcrumb } from './breadcrumb/breadcrumb.model';
|
||||||
import { BreadcrumbsService } from './breadcrumbs.service';
|
import { BreadcrumbsService } from './breadcrumbs.service';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component representing the breadcrumbs of a page
|
* Component representing the breadcrumbs of a page
|
||||||
|
@@ -31,7 +31,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
export class BrowseByModule {
|
export class BrowseByModule {
|
||||||
/**
|
/**
|
||||||
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
||||||
* which are not loaded during CSR otherwise
|
* which are not loaded during SSR otherwise
|
||||||
*/
|
*/
|
||||||
static withEntryComponents() {
|
static withEntryComponents() {
|
||||||
return {
|
return {
|
||||||
|
@@ -5,9 +5,10 @@
|
|||||||
<p [innerHTML]="'collection.edit.item-mapper.collection' | translate:{ name: (collectionName$ |async) }" id="collection-name"></p>
|
<p [innerHTML]="'collection.edit.item-mapper.collection' | translate:{ name: (collectionName$ |async) }" id="collection-name"></p>
|
||||||
<p>{{'collection.edit.item-mapper.description' | translate}}</p>
|
<p>{{'collection.edit.item-mapper.description' | translate}}</p>
|
||||||
|
|
||||||
<ngb-tabset (tabChange)="tabChange($event)" [destroyOnHide]="true" #tabs="ngbTabset">
|
<ul ngbNav (navChange)="tabChange($event)" [destroyOnHide]="true" #tabs="ngbNav" class="nav-tabs">
|
||||||
<ngb-tab title="{{'collection.edit.item-mapper.tabs.browse' | translate}}" id="browseTab">
|
<li [ngbNavItem]="'browseTab'">
|
||||||
<ng-template ngbTabContent>
|
<a ngbNavLink>{{'collection.edit.item-mapper.tabs.browse' | translate}}</a>
|
||||||
|
<ng-template ngbNavContent>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<ds-item-select class="mt-2"
|
<ds-item-select class="mt-2"
|
||||||
[key]="'browse'"
|
[key]="'browse'"
|
||||||
@@ -21,9 +22,10 @@
|
|||||||
(cancel)="onCancel()"></ds-item-select>
|
(cancel)="onCancel()"></ds-item-select>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ngb-tab>
|
</li>
|
||||||
<ngb-tab title="{{'collection.edit.item-mapper.tabs.map' | translate}}" id="mapTab">
|
<li [ngbNavItem]="'mapTab'">
|
||||||
<ng-template ngbTabContent>
|
<a ngbNavLink>{{'collection.edit.item-mapper.tabs.map' | translate}}</a>
|
||||||
|
<ng-template ngbNavContent>
|
||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
<div class="col-12 col-lg-6">
|
<div class="col-12 col-lg-6">
|
||||||
<ds-search-form id="search-form"
|
<ds-search-form id="search-form"
|
||||||
@@ -52,8 +54,9 @@
|
|||||||
{{'collection.edit.item-mapper.no-search' | translate}}
|
{{'collection.edit.item-mapper.no-search' | translate}}
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ngb-tab>
|
</li>
|
||||||
</ngb-tabset>
|
</ul>
|
||||||
|
<div [ngbNavOutlet]="tabs"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -27,7 +27,7 @@ import { ItemSelectComponent } from '../../shared/object-select/item-select/item
|
|||||||
import { ObjectSelectService } from '../../shared/object-select/object-select.service';
|
import { ObjectSelectService } from '../../shared/object-select/object-select.service';
|
||||||
import { ObjectSelectServiceStub } from '../../shared/testing/object-select-service.stub';
|
import { ObjectSelectServiceStub } from '../../shared/testing/object-select-service.stub';
|
||||||
import { VarDirective } from '../../shared/utils/var.directive';
|
import { VarDirective } from '../../shared/utils/var.directive';
|
||||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { RouteService } from '../../core/services/route.service';
|
import { RouteService } from '../../core/services/route.service';
|
||||||
import { ErrorComponent } from '../../shared/error/error.component';
|
import { ErrorComponent } from '../../shared/error/error.component';
|
||||||
import { LoadingComponent } from '../../shared/loading/loading.component';
|
import { LoadingComponent } from '../../shared/loading/loading.component';
|
||||||
|
@@ -11,18 +11,16 @@ import {
|
|||||||
import { filter, map, switchMap, tap } from 'rxjs/operators';
|
import { filter, map, switchMap, tap } from 'rxjs/operators';
|
||||||
import { hasValue, hasValueOperator } from '../../../../shared/empty.util';
|
import { hasValue, hasValueOperator } from '../../../../shared/empty.util';
|
||||||
import { ProcessStatus } from '../../../../process-page/processes/process-status.model';
|
import { ProcessStatus } from '../../../../process-page/processes/process-status.model';
|
||||||
import { Subscription } from 'rxjs/internal/Subscription';
|
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||||
import { RequestService } from '../../../../core/data/request.service';
|
import { RequestService } from '../../../../core/data/request.service';
|
||||||
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../../shared/notifications/notifications.service';
|
||||||
import { Collection } from '../../../../core/shared/collection.model';
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
import { CollectionDataService } from '../../../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../../../core/data/collection-data.service';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
|
||||||
import { Process } from '../../../../process-page/processes/process.model';
|
import { Process } from '../../../../process-page/processes/process.model';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
||||||
import { ContentSourceSetSerializer } from '../../../../core/shared/content-source-set-serializer';
|
import { ContentSourceSetSerializer } from '../../../../core/shared/content-source-set-serializer';
|
||||||
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that contains the controls to run, reset and test the harvest
|
* Component that contains the controls to run, reset and test the harvest
|
||||||
|
@@ -1,9 +1,8 @@
|
|||||||
import { Subscription } from 'rxjs/internal/Subscription';
|
|
||||||
import { FindListOptions } from '../core/data/request.models';
|
import { FindListOptions } from '../core/data/request.models';
|
||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
import { CommunityListService, FlatNode } from './community-list-service';
|
import { CommunityListService, FlatNode } from './community-list-service';
|
||||||
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
|
import { CollectionViewer, DataSource } from '@angular/cdk/collections';
|
||||||
import { BehaviorSubject, Observable, } from 'rxjs';
|
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||||
import { finalize } from 'rxjs/operators';
|
import { finalize } from 'rxjs/operators';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
3
src/app/core/cache/builders/link.service.ts
vendored
3
src/app/core/cache/builders/link.service.ts
vendored
@@ -10,8 +10,7 @@ import {
|
|||||||
LinkDefinition
|
LinkDefinition
|
||||||
} from './build-decorators';
|
} from './build-decorators';
|
||||||
import { RemoteData } from '../../data/remote-data';
|
import { RemoteData } from '../../data/remote-data';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { EMPTY, Observable } from 'rxjs';
|
||||||
import { EMPTY } from 'rxjs';
|
|
||||||
import { ResourceType } from '../../shared/resource-type';
|
import { ResourceType } from '../../shared/resource-type';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -7,7 +7,7 @@ import { PostRequest } from './request.models';
|
|||||||
import { Registration } from '../shared/registration.model';
|
import { Registration } from '../shared/registration.model';
|
||||||
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
||||||
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils';
|
||||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
|
|
||||||
describe('EpersonRegistrationService', () => {
|
describe('EpersonRegistrationService', () => {
|
||||||
|
@@ -14,7 +14,7 @@ import { FindListOptions } from './request.models';
|
|||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { dataService } from '../cache/builders/build-decorators';
|
import { dataService } from '../cache/builders/build-decorators';
|
||||||
import { RemoteData } from './remote-data';
|
import { RemoteData } from './remote-data';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { PaginatedList } from './paginated-list.model';
|
import { PaginatedList } from './paginated-list.model';
|
||||||
import { ITEM_TYPE } from '../shared/item-relationships/item-type.resource-type';
|
import { ITEM_TYPE } from '../shared/item-relationships/item-type.resource-type';
|
||||||
import { LICENSE } from '../shared/license.resource-type';
|
import { LICENSE } from '../shared/license.resource-type';
|
||||||
|
@@ -10,7 +10,6 @@ import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
|
|||||||
import { difference } from '../../shared/object.util';
|
import { difference } from '../../shared/object.util';
|
||||||
import { isNumeric } from 'rxjs/internal-compatibility';
|
import { isNumeric } from 'rxjs/internal-compatibility';
|
||||||
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
|
@@ -7,7 +7,7 @@ import { HALLink } from './hal-link.model';
|
|||||||
import { ResourceType } from './resource-type';
|
import { ResourceType } from './resource-type';
|
||||||
import { RemoteData } from '../data/remote-data';
|
import { RemoteData } from '../data/remote-data';
|
||||||
import { PaginatedList } from '../data/paginated-list.model';
|
import { PaginatedList } from '../data/paginated-list.model';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { ITEM_TYPE } from './item-relationships/item-type.resource-type';
|
import { ITEM_TYPE } from './item-relationships/item-type.resource-type';
|
||||||
import { ItemType } from './item-relationships/item-type.model';
|
import { ItemType } from './item-relationships/item-type.model';
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@ import { map, take } from 'rxjs/operators';
|
|||||||
import { NativeWindowRef, NativeWindowService } from '../services/window.service';
|
import { NativeWindowRef, NativeWindowService } from '../services/window.service';
|
||||||
import { URLCombiner } from '../url-combiner/url-combiner';
|
import { URLCombiner } from '../url-combiner/url-combiner';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides utility methods to save files on the client-side.
|
* Provides utility methods to save files on the client-side.
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import * as uuidv4 from 'uuid/v4';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { autoserialize, Serialize, Deserialize } from 'cerialize';
|
import { autoserialize, Serialize, Deserialize } from 'cerialize';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
/* tslint:disable:max-classes-per-file */
|
/* tslint:disable:max-classes-per-file */
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { isUndefined } from '../../shared/empty.util';
|
import { isUndefined } from '../../shared/empty.util';
|
||||||
import * as uuidv4 from 'uuid/v4';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { MetadataMap, MetadataValue, MetadataValueFilter, MetadatumViewModel } from './metadata.models';
|
import { MetadataMap, MetadataValue, MetadataValueFilter, MetadatumViewModel } from './metadata.models';
|
||||||
import { Metadata } from './metadata.utils';
|
import { Metadata } from './metadata.utils';
|
||||||
|
|
||||||
|
@@ -26,7 +26,7 @@ import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../../s
|
|||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
import { SearchConfig } from './search-filters/search-config.model';
|
import { SearchConfig } from './search-filters/search-config.model';
|
||||||
import { SearchService } from './search.service';
|
import { SearchService } from './search.service';
|
||||||
import { of } from 'rxjs/internal/observable/of';
|
import { of } from 'rxjs';
|
||||||
import { PaginationService } from '../../pagination/pagination.service';
|
import { PaginationService } from '../../pagination/pagination.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import * as uuidv4 from 'uuid/v4';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UUIDService {
|
export class UUIDService {
|
||||||
|
@@ -8,7 +8,7 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv
|
|||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { CoreState } from '../core.reducers';
|
import { CoreState } from '../core.reducers';
|
||||||
import { ClaimedTaskDataService } from './claimed-task-data.service';
|
import { ClaimedTaskDataService } from './claimed-task-data.service';
|
||||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { FindListOptions } from '../data/request.models';
|
import { FindListOptions } from '../data/request.models';
|
||||||
import { RequestParam } from '../cache/models/request-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { getTestScheduler } from 'jasmine-marbles';
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
|
@@ -10,7 +10,7 @@ import { CoreState } from '../core.reducers';
|
|||||||
import { PoolTaskDataService } from './pool-task-data.service';
|
import { PoolTaskDataService } from './pool-task-data.service';
|
||||||
import { getTestScheduler } from 'jasmine-marbles';
|
import { getTestScheduler } from 'jasmine-marbles';
|
||||||
import { TestScheduler } from 'rxjs/testing';
|
import { TestScheduler } from 'rxjs/testing';
|
||||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { FindListOptions } from '../data/request.models';
|
import { FindListOptions } from '../data/request.models';
|
||||||
import { RequestParam } from '../cache/models/request-param.model';
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
|
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
|
||||||
|
@@ -17,7 +17,7 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
|
|||||||
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
|
import { DSOChangeAnalyzer } from '../data/dso-change-analyzer.service';
|
||||||
import { ChangeAnalyzer } from '../data/change-analyzer';
|
import { ChangeAnalyzer } from '../data/change-analyzer';
|
||||||
import { compare, Operation } from 'fast-json-patch';
|
import { compare, Operation } from 'fast-json-patch';
|
||||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
|
import { HttpOptions } from '../dspace-rest/dspace-rest.service';
|
||||||
import { getMockRemoteDataBuildService } from '../../shared/mocks/remote-data-build.service.mock';
|
import { getMockRemoteDataBuildService } from '../../shared/mocks/remote-data-build.service.mock';
|
||||||
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
|
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
|
||||||
|
@@ -8,11 +8,10 @@ import {
|
|||||||
HttpResponse,
|
HttpResponse,
|
||||||
HttpXsrfTokenExtractor
|
HttpXsrfTokenExtractor
|
||||||
} from '@angular/common/http';
|
} from '@angular/common/http';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable, throwError } from 'rxjs';
|
||||||
import { tap, catchError } from 'rxjs/operators';
|
import { tap, catchError } from 'rxjs/operators';
|
||||||
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
import { RESTURLCombiner } from '../url-combiner/rest-url-combiner';
|
||||||
import { CookieService } from '../services/cookie.service';
|
import { CookieService } from '../services/cookie.service';
|
||||||
import { throwError } from 'rxjs';
|
|
||||||
|
|
||||||
// Name of XSRF header we may send in requests to backend (this is a standard name defined by Angular)
|
// Name of XSRF header we may send in requests to backend (this is a standard name defined by Angular)
|
||||||
export const XSRF_REQUEST_HEADER = 'X-XSRF-TOKEN';
|
export const XSRF_REQUEST_HEADER = 'X-XSRF-TOKEN';
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable [id]="dso.id">
|
<ds-truncatable [id]="dso.id">
|
||||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
||||||
[routerLink]="[itemPageRoute]" class="lead item-list-title"
|
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out"
|
||||||
[innerHTML]="dsoTitle"></a>
|
[innerHTML]="dsoTitle"></a>
|
||||||
<span *ngIf="linkType == linkTypes.None"
|
<span *ngIf="linkType == linkTypes.None"
|
||||||
class="lead item-list-title"
|
class="lead item-list-title dont-break-out"
|
||||||
[innerHTML]="dsoTitle"></span>
|
[innerHTML]="dsoTitle"></span>
|
||||||
<span class="text-muted">
|
<span class="text-muted">
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="1">
|
<ds-truncatable-part [id]="dso.id" [minLines]="1">
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable [id]="dso.id">
|
<ds-truncatable [id]="dso.id">
|
||||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
||||||
[routerLink]="[itemPageRoute]" class="lead item-list-title"
|
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out"
|
||||||
[innerHTML]="dsoTitle"></a>
|
[innerHTML]="dsoTitle"></a>
|
||||||
<span *ngIf="linkType == linkTypes.None"
|
<span *ngIf="linkType == linkTypes.None"
|
||||||
class="lead item-list-title"
|
class="lead item-list-title dont-break-out"
|
||||||
[innerHTML]="dsoTitle"></span>
|
[innerHTML]="dsoTitle"></span>
|
||||||
<span class="text-muted">
|
<span class="text-muted">
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="1">
|
<ds-truncatable-part [id]="dso.id" [minLines]="1">
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<ds-truncatable [id]="dso.id">
|
<ds-truncatable [id]="dso.id">
|
||||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
||||||
[routerLink]="[itemPageRoute]" class="lead item-list-title"
|
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out"
|
||||||
[innerHTML]="dsoTitle"></a>
|
[innerHTML]="dsoTitle"></a>
|
||||||
<span *ngIf="linkType == linkTypes.None"
|
<span *ngIf="linkType == linkTypes.None"
|
||||||
class="lead item-list-title"
|
class="lead item-list-title dont-break-out"
|
||||||
[innerHTML]="dsoTitle"></span>
|
[innerHTML]="dsoTitle"></span>
|
||||||
<span class="text-muted">
|
<span class="text-muted">
|
||||||
<ds-truncatable-part [id]="dso.id" [minLines]="1">
|
<ds-truncatable-part [id]="dso.id" [minLines]="1">
|
||||||
|
@@ -54,7 +54,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
export class JournalEntitiesModule {
|
export class JournalEntitiesModule {
|
||||||
/**
|
/**
|
||||||
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
||||||
* which are not loaded during CSR otherwise
|
* which are not loaded during SSR otherwise
|
||||||
*/
|
*/
|
||||||
static withEntryComponents() {
|
static withEntryComponents() {
|
||||||
return {
|
return {
|
||||||
|
@@ -1,10 +1,10 @@
|
|||||||
<ds-truncatable [id]="dso.id">
|
<ds-truncatable [id]="dso.id">
|
||||||
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
<ds-type-badge *ngIf="showLabel" [object]="dso"></ds-type-badge>
|
||||||
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
<a *ngIf="linkType != linkTypes.None" [target]="(linkType == linkTypes.ExternalLink) ? '_blank' : '_self'" rel="noopener noreferrer"
|
||||||
[routerLink]="[itemPageRoute]" class="lead item-list-title"
|
[routerLink]="[itemPageRoute]" class="lead item-list-title dont-break-out"
|
||||||
[innerHTML]="dsoTitle"></a>
|
[innerHTML]="dsoTitle"></a>
|
||||||
<span *ngIf="linkType == linkTypes.None"
|
<span *ngIf="linkType == linkTypes.None"
|
||||||
class="lead item-list-title"
|
class="lead item-list-title dont-break-out"
|
||||||
[innerHTML]="dsoTitle"></span>
|
[innerHTML]="dsoTitle"></span>
|
||||||
<!--<span class="text-muted">-->
|
<!--<span class="text-muted">-->
|
||||||
<!--<ds-truncatable-part [id]="dso.id" [minLines]="1">-->
|
<!--<ds-truncatable-part [id]="dso.id" [minLines]="1">-->
|
||||||
|
@@ -74,7 +74,7 @@ const COMPONENTS = [
|
|||||||
export class ResearchEntitiesModule {
|
export class ResearchEntitiesModule {
|
||||||
/**
|
/**
|
||||||
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
||||||
* which are not loaded during CSR otherwise
|
* which are not loaded during SSR otherwise
|
||||||
*/
|
*/
|
||||||
static withEntryComponents() {
|
static withEntryComponents() {
|
||||||
return {
|
return {
|
||||||
|
@@ -1,18 +1,24 @@
|
|||||||
<ng-template #bitstreamView>
|
<ng-template #bitstreamView>
|
||||||
<div class="{{columnSizes.columns[0].buildClasses()}} row-element d-flex">
|
<div class="{{columnSizes.columns[0].buildClasses()}} row-element d-flex">
|
||||||
<ng-content select="[slot=drag-handle]"></ng-content>
|
<ng-content select="[slot=drag-handle]"></ng-content>
|
||||||
<div class="float-left d-flex align-items-center">
|
<div class="float-left d-flex align-items-center overflow-hidden">
|
||||||
{{ bitstreamName }}
|
<span class="text-truncate">
|
||||||
|
{{ bitstreamName }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="{{columnSizes.columns[1].buildClasses()}} row-element d-flex align-items-center">
|
<div class="{{columnSizes.columns[1].buildClasses()}} row-element d-flex align-items-center">
|
||||||
<div class="w-100">
|
<div class="w-100">
|
||||||
|
<span class="text-truncate">
|
||||||
{{ bitstream?.firstMetadataValue('dc.description') }}
|
{{ bitstream?.firstMetadataValue('dc.description') }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="{{columnSizes.columns[2].buildClasses()}} row-element d-flex align-items-center">
|
<div class="{{columnSizes.columns[2].buildClasses()}} row-element d-flex align-items-center">
|
||||||
<div class="text-center w-100">
|
<div class="text-center w-100">
|
||||||
{{ (format$ | async)?.shortDescription }}
|
<span class="text-truncate">
|
||||||
|
{{ (format$ | async)?.shortDescription }}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="{{columnSizes.columns[3].buildClasses()}} row-element d-flex align-items-center">
|
<div class="{{columnSizes.columns[3].buildClasses()}} row-element d-flex align-items-center">
|
||||||
|
@@ -5,9 +5,10 @@
|
|||||||
<p [innerHTML]="'item.edit.item-mapper.item' | translate:{ name: (itemName$ | async) }" id="item-name"></p>
|
<p [innerHTML]="'item.edit.item-mapper.item' | translate:{ name: (itemName$ | async) }" id="item-name"></p>
|
||||||
<p>{{'item.edit.item-mapper.description' | translate}}</p>
|
<p>{{'item.edit.item-mapper.description' | translate}}</p>
|
||||||
|
|
||||||
<ngb-tabset (tabChange)="tabChange($event)" [destroyOnHide]="true" #tabs="ngbTabset">
|
<ul ngbNav (navChange)="tabChange($event)" [destroyOnHide]="true" #tabs="ngbNav" class="nav-tabs">
|
||||||
<ngb-tab title="{{'item.edit.item-mapper.tabs.browse' | translate}}" id="browseTab">
|
<li [ngbNavItem]="'browseTab'">
|
||||||
<ng-template ngbTabContent>
|
<a ngbNavLink>{{'item.edit.item-mapper.tabs.browse' | translate}}</a>
|
||||||
|
<ng-template ngbNavContent>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<ds-collection-select class="mt-2"
|
<ds-collection-select class="mt-2"
|
||||||
[key]="'browse'"
|
[key]="'browse'"
|
||||||
@@ -20,9 +21,10 @@
|
|||||||
(cancel)="onCancel()"></ds-collection-select>
|
(cancel)="onCancel()"></ds-collection-select>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ngb-tab>
|
</li>
|
||||||
<ngb-tab title="{{'item.edit.item-mapper.tabs.map' | translate}}" id="mapTab">
|
<li [ngbNavItem]="'mapTab'">
|
||||||
<ng-template ngbTabContent>
|
<a ngbNavLink>{{'item.edit.item-mapper.tabs.map' | translate}}</a>
|
||||||
|
<ng-template ngbNavContent>
|
||||||
<div class="row mt-2">
|
<div class="row mt-2">
|
||||||
<div class="col-12 col-lg-6">
|
<div class="col-12 col-lg-6">
|
||||||
<ds-search-form id="search-form"
|
<ds-search-form id="search-form"
|
||||||
@@ -50,8 +52,9 @@
|
|||||||
{{'item.edit.item-mapper.no-search' | translate}}
|
{{'item.edit.item-mapper.no-search' | translate}}
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ngb-tab>
|
</li>
|
||||||
</ngb-tabset>
|
</ul>
|
||||||
|
<div [ngbNavOutlet]="tabs"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -7,7 +7,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
||||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||||
|
@@ -26,7 +26,7 @@
|
|||||||
<td class="w-100">
|
<td class="w-100">
|
||||||
<div class="value-field">
|
<div class="value-field">
|
||||||
<div *ngIf="!(editable | async)">
|
<div *ngIf="!(editable | async)">
|
||||||
<span>{{metadata?.value}}</span>
|
<span class="dont-break-out">{{metadata?.value}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="(editable | async)" class="field-container">
|
<div *ngIf="(editable | async)" class="field-container">
|
||||||
<textarea class="form-control" type="textarea" attr.aria-labelledby="fieldValue" [(ngModel)]="metadata.value" [dsDebounce]
|
<textarea class="form-control" type="textarea" attr.aria-labelledby="fieldValue" [(ngModel)]="metadata.value" [dsDebounce]
|
||||||
|
@@ -3,7 +3,7 @@ import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
|
|||||||
import { LinkService } from '../../../../core/cache/builders/link.service';
|
import { LinkService } from '../../../../core/cache/builders/link.service';
|
||||||
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
|
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
|
||||||
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
||||||
import { combineLatest as observableCombineLatest, from as observableFrom, Observable } from 'rxjs';
|
import { combineLatest as observableCombineLatest, from as observableFrom, BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
FieldUpdate,
|
FieldUpdate,
|
||||||
FieldUpdates,
|
FieldUpdates,
|
||||||
@@ -30,8 +30,6 @@ import { followLink } from '../../../../shared/utils/follow-link-config.model';
|
|||||||
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { Collection } from '../../../../core/shared/collection.model';
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
|
||||||
import { Subscription } from 'rxjs/internal/Subscription';
|
|
||||||
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
|
||||||
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||||
import { RelationshipTypeService } from '../../../../core/data/relationship-type.service';
|
import { RelationshipTypeService } from '../../../../core/data/relationship-type.service';
|
||||||
|
@@ -6,9 +6,8 @@ import {
|
|||||||
FieldUpdates,
|
FieldUpdates,
|
||||||
RelationshipIdentifiable,
|
RelationshipIdentifiable,
|
||||||
} from '../../../core/data/object-updates/object-updates.reducer';
|
} from '../../../core/data/object-updates/object-updates.reducer';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
|
||||||
import { map, startWith, switchMap, take } from 'rxjs/operators';
|
import { map, startWith, switchMap, take } from 'rxjs/operators';
|
||||||
import { combineLatest as observableCombineLatest, of as observableOf, zip as observableZip } from 'rxjs';
|
import { combineLatest as observableCombineLatest, of as observableOf, zip as observableZip, Observable } from 'rxjs';
|
||||||
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
import { followLink } from '../../../shared/utils/follow-link-config.model';
|
||||||
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
|
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
|
||||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<ds-metadata-field-wrapper [label]="label | translate">
|
<ds-metadata-field-wrapper [label]="label | translate">
|
||||||
<a *ngFor="let mdValue of mdValues; let last=last;" [href]="mdValue.value">
|
<a class="dont-break-out" *ngFor="let mdValue of mdValues; let last=last;" [href]="mdValue.value">
|
||||||
{{ linktext || mdValue.value }}<span *ngIf="!last" [innerHTML]="separator"></span>
|
{{ linktext || mdValue.value }}<span *ngIf="!last" [innerHTML]="separator"></span>
|
||||||
</a>
|
</a>
|
||||||
</ds-metadata-field-wrapper>
|
</ds-metadata-field-wrapper>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<ds-metadata-field-wrapper [label]="label | translate">
|
<ds-metadata-field-wrapper [label]="label | translate">
|
||||||
<span *ngFor="let mdValue of mdValues; let last=last;">
|
<span class="dont-break-out" *ngFor="let mdValue of mdValues; let last=last;">
|
||||||
{{mdValue.value}}<span *ngIf="!last" [innerHTML]="separator"></span>
|
{{mdValue.value}}<span *ngIf="!last" [innerHTML]="separator"></span>
|
||||||
</span>
|
</span>
|
||||||
</ds-metadata-field-wrapper>
|
</ds-metadata-field-wrapper>
|
||||||
|
@@ -33,7 +33,7 @@ import { NgxGalleryModule } from '@kolkov/ngx-gallery';
|
|||||||
import { MiradorViewerComponent } from './mirador-viewer/mirador-viewer.component';
|
import { MiradorViewerComponent } from './mirador-viewer/mirador-viewer.component';
|
||||||
import { VersionPageComponent } from './version-page/version-page/version-page.component';
|
import { VersionPageComponent } from './version-page/version-page/version-page.component';
|
||||||
import { VersionedItemComponent } from './simple/item-types/versioned-item/versioned-item.component';
|
import { VersionedItemComponent } from './simple/item-types/versioned-item/versioned-item.component';
|
||||||
import { ThemedFileSectionComponent} from './simple/field-components/file-section/themed-file-section.component';
|
import { ThemedFileSectionComponent } from './simple/field-components/file-section/themed-file-section.component';
|
||||||
|
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
@@ -91,7 +91,7 @@ const DECLARATIONS = [
|
|||||||
export class ItemPageModule {
|
export class ItemPageModule {
|
||||||
/**
|
/**
|
||||||
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
||||||
* which are not loaded during CSR otherwise
|
* which are not loaded during SSR otherwise
|
||||||
*/
|
*/
|
||||||
static withEntryComponents() {
|
static withEntryComponents() {
|
||||||
return {
|
return {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { NgxGalleryOptions } from '@kolkov/ngx-gallery';
|
import { NgxGalleryOptions } from '@kolkov/ngx-gallery';
|
||||||
import { Bitstream } from '../../../core/shared/bitstream.model';
|
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||||
import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
|
import { MediaViewerItem } from '../../../core/shared/media-viewer-item.model';
|
||||||
@@ -55,7 +55,7 @@ describe('MediaViewerImageComponent', () => {
|
|||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports:[],
|
imports:[],
|
||||||
declarations: [MediaViewerImageComponent],
|
declarations: [MediaViewerImageComponent],
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
@@ -17,7 +17,7 @@ describe('MediaViewerVideoComponent', () => {
|
|||||||
let component: MediaViewerVideoComponent;
|
let component: MediaViewerVideoComponent;
|
||||||
let fixture: ComponentFixture<MediaViewerVideoComponent>;
|
let fixture: ComponentFixture<MediaViewerVideoComponent>;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { Bitstream } from '../../core/shared/bitstream.model';
|
import { Bitstream } from '../../core/shared/bitstream.model';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||||
@@ -60,7 +60,7 @@ describe('MediaViewerComponent', () => {
|
|||||||
{ bitstream: mockBitstream, format: 'image', thumbnail: null }
|
{ bitstream: mockBitstream, format: 'image', thumbnail: null }
|
||||||
);
|
);
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
|
@@ -3,9 +3,8 @@ import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
|||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable, of } from 'rxjs';
|
||||||
import { map, take } from 'rxjs/operators';
|
import { map, take } from 'rxjs/operators';
|
||||||
import { of } from 'rxjs';
|
|
||||||
import { isPlatformBrowser } from '@angular/common';
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
import { MiradorViewerService } from './mirador-viewer.service';
|
import { MiradorViewerService } from './mirador-viewer.service';
|
||||||
import { HostWindowService, WidthCategory } from '../../shared/host-window.service';
|
import { HostWindowService, WidthCategory } from '../../shared/host-window.service';
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Injectable, isDevMode } from '@angular/core';
|
import { Injectable, isDevMode } from '@angular/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../core/shared/operators';
|
import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../../core/shared/operators';
|
||||||
import { last, map, switchMap } from 'rxjs/operators';
|
import { last, map, switchMap } from 'rxjs/operators';
|
||||||
|
@@ -4,7 +4,7 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
|||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../../../../core/cache/builders/remote-data-build.service';
|
||||||
import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
|
import { ObjectCacheService } from '../../../../core/cache/object-cache.service';
|
||||||
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../../../core/data/bitstream-data.service';
|
||||||
|
@@ -1,17 +1,21 @@
|
|||||||
<ngb-tabset *ngIf="relationTypes.length > 1" [destroyOnHide]="true" #tabs="ngbTabset" [activeId]="activeTab$ | async" (tabChange)="onTabChange($event)">
|
<ng-container *ngIf="relationTypes.length > 1">
|
||||||
<ngb-tab *ngFor="let relationType of relationTypes" title="{{'item.page.relationships.' + relationType.label | translate}}" [id]="relationType.filter">
|
<ul ngbNav #tabs="ngbNav" [destroyOnHide]="true" [activeId]="activeTab$ | async" (navChange)="onTabChange($event)" class="nav-tabs">
|
||||||
<ng-template ngbTabContent>
|
<li *ngFor="let relationType of relationTypes" [ngbNavItem]="relationType.filter">
|
||||||
<div class="mt-4">
|
<a ngbNavLink>{{'item.page.relationships.' + relationType.label | translate}}</a>
|
||||||
<ds-related-entities-search [item]="item"
|
<ng-template ngbNavContent>
|
||||||
[relationType]="relationType.filter"
|
<div class="mt-4">
|
||||||
[configuration]="relationType.configuration"
|
<ds-related-entities-search [item]="item"
|
||||||
[searchEnabled]="searchEnabled"
|
[relationType]="relationType.filter"
|
||||||
[sideBarWidth]="sideBarWidth">
|
[configuration]="relationType.configuration"
|
||||||
</ds-related-entities-search>
|
[searchEnabled]="searchEnabled"
|
||||||
</div>
|
[sideBarWidth]="sideBarWidth">
|
||||||
</ng-template>
|
</ds-related-entities-search>
|
||||||
</ngb-tab>
|
</div>
|
||||||
</ngb-tabset>
|
</ng-template>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div [ngbNavOutlet]="tabs"></div>
|
||||||
|
</ng-container>
|
||||||
<div *ngIf="relationTypes.length === 1" class="mt-4">
|
<div *ngIf="relationTypes.length === 1" class="mt-4">
|
||||||
<ds-related-entities-search *ngVar="relationTypes[0] as relationType" [item]="item"
|
<ds-related-entities-search *ngVar="relationTypes[0] as relationType" [item]="item"
|
||||||
[relationType]="relationType.filter"
|
[relationType]="relationType.filter"
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Component, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { Component, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
@@ -75,7 +75,7 @@ describe('MyDSpaceNewExternalDropdownComponent test', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('With only one Entity', () => {
|
describe('With only one Entity', () => {
|
||||||
beforeEach(async(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@@ -126,7 +126,7 @@ describe('MyDSpaceNewExternalDropdownComponent test', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('With more than one Entity', () => {
|
describe('With more than one Entity', () => {
|
||||||
beforeEach(async(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { Component, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { Component, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@@ -79,7 +79,7 @@ describe('MyDSpaceNewSubmissionDropdownComponent test', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
describe('With only one Entity', () => {
|
describe('With only one Entity', () => {
|
||||||
beforeEach(async(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@@ -130,7 +130,7 @@ describe('MyDSpaceNewSubmissionDropdownComponent test', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('With more than one Entity', () => {
|
describe('With more than one Entity', () => {
|
||||||
beforeEach(async(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
<ds-search-sidebar *ngIf="!(isXsOrSm$ | async)" class="col-3 sidebar-md-sticky"
|
<ds-search-sidebar *ngIf="!(isXsOrSm$ | async)" class="col-3 sidebar-md-sticky"
|
||||||
id="search-sidebar"
|
id="search-sidebar"
|
||||||
[configurationList]="(configurationList$ | async)"
|
[configurationList]="(configurationList$ | async)"
|
||||||
[resultCount]="(resultsRD$ | async)?.payload.totalElements"
|
[resultCount]="(resultsRD$ | async)?.payload?.totalElements"
|
||||||
[viewModeList]="viewModeList"
|
[viewModeList]="viewModeList"
|
||||||
[searchOptions]="(searchOptions$ | async)"
|
[searchOptions]="(searchOptions$ | async)"
|
||||||
[sortOptions]="(sortOptions$ | async)"
|
[sortOptions]="(sortOptions$ | async)"
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
<ds-search-sidebar *ngIf="(isXsOrSm$ | async)" class="col-12"
|
<ds-search-sidebar *ngIf="(isXsOrSm$ | async)" class="col-12"
|
||||||
id="search-sidebar-sm"
|
id="search-sidebar-sm"
|
||||||
[configurationList]="(configurationList$ | async)"
|
[configurationList]="(configurationList$ | async)"
|
||||||
[resultCount]="(resultsRD$ | async)?.payload.totalElements"
|
[resultCount]="(resultsRD$ | async)?.payload?.totalElements"
|
||||||
(toggleSidebar)="closeSidebar()"
|
(toggleSidebar)="closeSidebar()"
|
||||||
[ngClass]="{'active': !(isSidebarCollapsed() | async)}"
|
[ngClass]="{'active': !(isSidebarCollapsed() | async)}"
|
||||||
[searchOptions]="(searchOptions$ | async)"
|
[searchOptions]="(searchOptions$ | async)"
|
||||||
|
@@ -19,7 +19,7 @@ import { PaginatedSearchOptions } from '../shared/search/paginated-search-option
|
|||||||
import { SearchService } from '../core/shared/search/search.service';
|
import { SearchService } from '../core/shared/search/search.service';
|
||||||
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
import { getFirstSucceededRemoteData } from '../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||||
import { MyDSpaceResponseParsingService } from '../core/data/mydspace-response-parsing.service';
|
import { MyDSpaceResponseParsingService } from '../core/data/mydspace-response-parsing.service';
|
||||||
import { SearchConfigurationOption } from '../shared/search/search-switch-configuration/search-configuration-option.model';
|
import { SearchConfigurationOption } from '../shared/search/search-switch-configuration/search-configuration-option.model';
|
||||||
import { RoleType } from '../core/roles/role-types';
|
import { RoleType } from '../core/roles/role-types';
|
||||||
@@ -30,7 +30,7 @@ import { MyDSpaceRequest } from '../core/data/request.models';
|
|||||||
import { SearchResult } from '../shared/search/search-result.model';
|
import { SearchResult } from '../shared/search/search-result.model';
|
||||||
import { Context } from '../core/shared/context.model';
|
import { Context } from '../core/shared/context.model';
|
||||||
import { SortOptions } from '../core/cache/models/sort-options.model';
|
import { SortOptions } from '../core/cache/models/sort-options.model';
|
||||||
import { RouteService } from '../core/services/route.service';
|
import { SearchObjects } from '../shared/search/search-objects.model';
|
||||||
|
|
||||||
export const MYDSPACE_ROUTE = '/mydspace';
|
export const MYDSPACE_ROUTE = '/mydspace';
|
||||||
export const SEARCH_CONFIG_SERVICE: InjectionToken<SearchConfigurationService> = new InjectionToken<SearchConfigurationService>('searchConfigurationService');
|
export const SEARCH_CONFIG_SERVICE: InjectionToken<SearchConfigurationService> = new InjectionToken<SearchConfigurationService>('searchConfigurationService');
|
||||||
@@ -111,8 +111,7 @@ export class MyDSpacePageComponent implements OnInit {
|
|||||||
constructor(private service: SearchService,
|
constructor(private service: SearchService,
|
||||||
private sidebarService: SidebarService,
|
private sidebarService: SidebarService,
|
||||||
private windowService: HostWindowService,
|
private windowService: HostWindowService,
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: MyDSpaceConfigurationService,
|
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: MyDSpaceConfigurationService) {
|
||||||
private routeService: RouteService) {
|
|
||||||
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
||||||
this.service.setServiceOptions(MyDSpaceResponseParsingService, MyDSpaceRequest);
|
this.service.setServiceOptions(MyDSpaceResponseParsingService, MyDSpaceRequest);
|
||||||
}
|
}
|
||||||
@@ -134,8 +133,8 @@ export class MyDSpacePageComponent implements OnInit {
|
|||||||
this.searchOptions$ = this.searchConfigService.paginatedSearchOptions;
|
this.searchOptions$ = this.searchConfigService.paginatedSearchOptions;
|
||||||
this.sub = this.searchOptions$.pipe(
|
this.sub = this.searchOptions$.pipe(
|
||||||
tap(() => this.resultsRD$.next(null)),
|
tap(() => this.resultsRD$.next(null)),
|
||||||
switchMap((options: PaginatedSearchOptions) => this.service.search(options).pipe(getFirstSucceededRemoteData())))
|
switchMap((options: PaginatedSearchOptions) => this.service.search(options).pipe(getFirstCompletedRemoteData())))
|
||||||
.subscribe((results) => {
|
.subscribe((results: RemoteData<SearchObjects<DSpaceObject>>) => {
|
||||||
this.resultsRD$.next(results);
|
this.resultsRD$.next(results);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -10,5 +10,5 @@
|
|||||||
</ds-viewable-collection>
|
</ds-viewable-collection>
|
||||||
</div>
|
</div>
|
||||||
<ds-loading *ngIf="isLoading()" message="{{'loading.mydspace-results' | translate}}"></ds-loading>
|
<ds-loading *ngIf="isLoading()" message="{{'loading.mydspace-results' | translate}}"></ds-loading>
|
||||||
<ds-error *ngIf="searchResults?.hasFailed && (!searchResults?.errorMessage || searchResults?.statusCode != 400)" message="{{'error.search-results' | translate}}"></ds-error>
|
<ds-error *ngIf="showError()" message="{{errorMessageLabel() | translate}}"></ds-error>
|
||||||
<h3 *ngIf="searchResults?.payload?.page.length == 0" class="text-center text-muted" ><span>{{'mydspace.results.no-results' | translate}}</span></h3>
|
<h3 *ngIf="searchResults?.payload?.page.length == 0" class="text-center text-muted" ><span>{{'mydspace.results.no-results' | translate}}</span></h3>
|
||||||
|
@@ -40,9 +40,19 @@ describe('MyDSpaceResultsComponent', () => {
|
|||||||
expect(fixture.debugElement.query(By.css('a'))).toBeNull();
|
expect(fixture.debugElement.query(By.css('a'))).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display error message if error is != 400', () => {
|
it('should display error message if error is 500', () => {
|
||||||
(comp as any).searchResults = { hasFailed: true, error: { statusCode: 500 } };
|
(comp as any).searchResults = { hasFailed: true, statusCode: 500 };
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
expect(comp.showError()).toBeTrue();
|
||||||
|
expect(comp.errorMessageLabel()).toBe('error.search-results');
|
||||||
|
expect(fixture.debugElement.query(By.css('ds-error'))).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display error message if error is 422', () => {
|
||||||
|
(comp as any).searchResults = { hasFailed: true, statusCode: 422 };
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(comp.showError()).toBeTrue();
|
||||||
|
expect(comp.errorMessageLabel()).toBe('error.invalid-search-query');
|
||||||
expect(fixture.debugElement.query(By.css('ds-error'))).not.toBeNull();
|
expect(fixture.debugElement.query(By.css('ds-error'))).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -58,4 +58,12 @@ export class MyDSpaceResultsComponent {
|
|||||||
isLoading() {
|
isLoading() {
|
||||||
return !this.searchResults || isEmpty(this.searchResults) || this.searchResults.isLoading;
|
return !this.searchResults || isEmpty(this.searchResults) || this.searchResults.isLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showError(): boolean {
|
||||||
|
return this.searchResults?.hasFailed && (!this.searchResults?.errorMessage || this.searchResults?.statusCode !== 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
errorMessageLabel(): string {
|
||||||
|
return (this.searchResults?.statusCode === 422) ? 'error.invalid-search-query' : 'error.search-results';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -50,7 +50,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
export class MyDspaceSearchModule {
|
export class MyDspaceSearchModule {
|
||||||
/**
|
/**
|
||||||
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
||||||
* which are not loaded during CSR otherwise
|
* which are not loaded during SSR otherwise
|
||||||
*/
|
*/
|
||||||
static withEntryComponents() {
|
static withEntryComponents() {
|
||||||
return {
|
return {
|
||||||
|
@@ -58,7 +58,7 @@ const ENTRY_COMPONENTS = [
|
|||||||
export class NavbarModule {
|
export class NavbarModule {
|
||||||
/**
|
/**
|
||||||
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
* NOTE: this method allows to resolve issue with components that using a custom decorator
|
||||||
* which are not loaded during CSR otherwise
|
* which are not loaded during SSR otherwise
|
||||||
*/
|
*/
|
||||||
static withEntryComponents() {
|
static withEntryComponents() {
|
||||||
return {
|
return {
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<h2 class="flex-grow-1">{{'process.detail.title' | translate:{id: process?.processId, name: process?.scriptName} }}</h2>
|
<h2 class="flex-grow-1">{{'process.detail.title' | translate:{id: process?.processId, name: process?.scriptName} }}</h2>
|
||||||
<div>
|
<div>
|
||||||
<a class="btn btn-light" [routerLink]="'/processes/new'" [queryParams]="{id: process?.processId}">{{'process.detail.create' | translate}}</a>
|
<button class="btn btn-lg btn-success " routerLink="/processes/new" [queryParams]="{id: process?.processId}"><i class="fas fa-plus pr-2"></i>{{'process.detail.create' | translate}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ds-process-detail-field id="process-name" [title]="'process.detail.script'">
|
<ds-process-detail-field id="process-name" [title]="'process.detail.script'">
|
||||||
@@ -23,11 +23,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ds-process-detail-field *ngIf="process && process.startTime" id="process-start-time" [title]="'process.detail.start-time' | translate">
|
<ds-process-detail-field *ngIf="process && process.startTime" id="process-start-time" [title]="'process.detail.start-time' | translate">
|
||||||
<div>{{ process.startTime }}</div>
|
<div>{{ process.startTime | date:dateFormat:'UTC' }}</div>
|
||||||
</ds-process-detail-field>
|
</ds-process-detail-field>
|
||||||
|
|
||||||
<ds-process-detail-field *ngIf="process && process.endTime" id="process-end-time" [title]="'process.detail.end-time' | translate">
|
<ds-process-detail-field *ngIf="process && process.endTime" id="process-end-time" [title]="'process.detail.end-time' | translate">
|
||||||
<div>{{ process.endTime }}</div>
|
<div>{{ process.endTime | date:dateFormat:'UTC' }}</div>
|
||||||
</ds-process-detail-field>
|
</ds-process-detail-field>
|
||||||
|
|
||||||
<ds-process-detail-field *ngIf="process && process.processStatus" id="process-status" [title]="'process.detail.status' | translate">
|
<ds-process-detail-field *ngIf="process && process.processStatus" id="process-status" [title]="'process.detail.status' | translate">
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
</ds-process-detail-field>
|
</ds-process-detail-field>
|
||||||
|
|
||||||
<ds-process-detail-field *ngIf="isProcessFinished(process)" id="process-output" [title]="'process.detail.output'">
|
<ds-process-detail-field *ngIf="isProcessFinished(process)" id="process-output" [title]="'process.detail.output'">
|
||||||
<button *ngIf="!showOutputLogs && process?._links?.output?.href != undefined" id="showOutputButton" class="btn btn-light" (click)="showProcessOutputLogs()">
|
<button *ngIf="!showOutputLogs && process?._links?.output?.href != undefined" id="showOutputButton" class="btn btn-primary" (click)="showProcessOutputLogs()">
|
||||||
{{ 'process.detail.logs.button' | translate }}
|
{{ 'process.detail.logs.button' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<ds-loading *ngIf="retrievingOutputLogs$ | async" class="ds-loading" message="{{ 'process.detail.logs.loading' | translate }}"></ds-loading>
|
<ds-loading *ngIf="retrievingOutputLogs$ | async" class="ds-loading" message="{{ 'process.detail.logs.loading' | translate }}"></ds-loading>
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</ds-process-detail-field>
|
</ds-process-detail-field>
|
||||||
|
|
||||||
<div>
|
<div style="text-align: right;">
|
||||||
<a class="btn btn-light mt-3" [routerLink]="'/processes'">{{'process.detail.back' | translate}}</a>
|
<a class="btn btn-outline-secondary mt-3" [routerLink]="'/processes'">{{'process.detail.back' | translate}}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -66,6 +66,11 @@ export class ProcessDetailComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
retrievingOutputLogs$: BehaviorSubject<boolean>;
|
retrievingOutputLogs$: BehaviorSubject<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Date format to use for start and end time of processes
|
||||||
|
*/
|
||||||
|
dateFormat = 'yyyy-MM-dd HH:mm:ss ZZZZ';
|
||||||
|
|
||||||
constructor(protected route: ActivatedRoute,
|
constructor(protected route: ActivatedRoute,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
protected processService: ProcessDataService,
|
protected processService: ProcessDataService,
|
||||||
|
@@ -26,8 +26,8 @@
|
|||||||
<td><a [routerLink]="['/processes/', process.processId]">{{process.processId}}</a></td>
|
<td><a [routerLink]="['/processes/', process.processId]">{{process.processId}}</a></td>
|
||||||
<td><a [routerLink]="['/processes/', process.processId]">{{process.scriptName}}</a></td>
|
<td><a [routerLink]="['/processes/', process.processId]">{{process.scriptName}}</a></td>
|
||||||
<td *ngVar="(getEpersonName(process.userId) | async) as ePersonName">{{ePersonName}}</td>
|
<td *ngVar="(getEpersonName(process.userId) | async) as ePersonName">{{ePersonName}}</td>
|
||||||
<td>{{process.startTime | date:dateFormat}}</td>
|
<td>{{process.startTime | date:dateFormat:'UTC'}}</td>
|
||||||
<td>{{process.endTime | date:dateFormat}}</td>
|
<td>{{process.endTime | date:dateFormat:'UTC'}}</td>
|
||||||
<td>{{process.processStatus}}</td>
|
<td>{{process.processStatus}}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@@ -12,12 +12,9 @@ import { By } from '@angular/platform-browser';
|
|||||||
import { ProcessStatus } from '../processes/process-status.model';
|
import { ProcessStatus } from '../processes/process-status.model';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../shared/testing/utils.test';
|
||||||
import { of as observableOf } from 'rxjs';
|
|
||||||
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 { FindListOptions } from '../../core/data/request.models';
|
|
||||||
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
||||||
|
import { DatePipe } from '@angular/common';
|
||||||
|
|
||||||
describe('ProcessOverviewComponent', () => {
|
describe('ProcessOverviewComponent', () => {
|
||||||
let component: ProcessOverviewComponent;
|
let component: ProcessOverviewComponent;
|
||||||
@@ -30,27 +27,29 @@ describe('ProcessOverviewComponent', () => {
|
|||||||
let processes: Process[];
|
let processes: Process[];
|
||||||
let ePerson: EPerson;
|
let ePerson: EPerson;
|
||||||
|
|
||||||
|
const pipe = new DatePipe('en-US');
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
processes = [
|
processes = [
|
||||||
Object.assign(new Process(), {
|
Object.assign(new Process(), {
|
||||||
processId: 1,
|
processId: 1,
|
||||||
scriptName: 'script-name',
|
scriptName: 'script-name',
|
||||||
startTime: '2020-03-19',
|
startTime: '2020-03-19 00:30:00',
|
||||||
endTime: '2020-03-19',
|
endTime: '2020-03-19 23:30:00',
|
||||||
processStatus: ProcessStatus.COMPLETED
|
processStatus: ProcessStatus.COMPLETED
|
||||||
}),
|
}),
|
||||||
Object.assign(new Process(), {
|
Object.assign(new Process(), {
|
||||||
processId: 2,
|
processId: 2,
|
||||||
scriptName: 'script-name',
|
scriptName: 'script-name',
|
||||||
startTime: '2020-03-20',
|
startTime: '2020-03-20 00:30:00',
|
||||||
endTime: '2020-03-20',
|
endTime: '2020-03-20 23:30:00',
|
||||||
processStatus: ProcessStatus.FAILED
|
processStatus: ProcessStatus.FAILED
|
||||||
}),
|
}),
|
||||||
Object.assign(new Process(), {
|
Object.assign(new Process(), {
|
||||||
processId: 3,
|
processId: 3,
|
||||||
scriptName: 'another-script-name',
|
scriptName: 'another-script-name',
|
||||||
startTime: '2020-03-21',
|
startTime: '2020-03-21 00:30:00',
|
||||||
endTime: '2020-03-21',
|
endTime: '2020-03-21 23:30:00',
|
||||||
processStatus: ProcessStatus.RUNNING
|
processStatus: ProcessStatus.RUNNING
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
@@ -135,14 +134,14 @@ describe('ProcessOverviewComponent', () => {
|
|||||||
it('should display the start time in the fourth column', () => {
|
it('should display the start time in the fourth column', () => {
|
||||||
rowElements.forEach((rowElement, index) => {
|
rowElements.forEach((rowElement, index) => {
|
||||||
const el = rowElement.query(By.css('td:nth-child(4)')).nativeElement;
|
const el = rowElement.query(By.css('td:nth-child(4)')).nativeElement;
|
||||||
expect(el.textContent).toContain(processes[index].startTime);
|
expect(el.textContent).toContain(pipe.transform(processes[index].startTime, component.dateFormat, 'UTC'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display the end time in the fifth column', () => {
|
it('should display the end time in the fifth column', () => {
|
||||||
rowElements.forEach((rowElement, index) => {
|
rowElements.forEach((rowElement, index) => {
|
||||||
const el = rowElement.query(By.css('td:nth-child(5)')).nativeElement;
|
const el = rowElement.query(By.css('td:nth-child(5)')).nativeElement;
|
||||||
expect(el.textContent).toContain(processes[index].endTime);
|
expect(el.textContent).toContain(pipe.transform(processes[index].endTime, component.dateFormat, 'UTC'));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -23,7 +23,7 @@ export class ProcessPageResolver implements Resolve<RemoteData<Process>> {
|
|||||||
* or an error if something went wrong
|
* or an error if something went wrong
|
||||||
*/
|
*/
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Process>> {
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Process>> {
|
||||||
return this.processService.findById(route.params.id, true, false, followLink('script')).pipe(
|
return this.processService.findById(route.params.id, false, true, followLink('script')).pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { map, switchMap } from 'rxjs/operators';
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
import { ItemRequest } from '../../core/shared/item-request.model';
|
import { ItemRequest } from '../../core/shared/item-request.model';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload,
|
getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload,
|
||||||
redirectOn4xx
|
redirectOn4xx
|
||||||
|
@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { map, switchMap } from 'rxjs/operators';
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
import { ItemRequest } from '../../core/shared/item-request.model';
|
import { ItemRequest } from '../../core/shared/item-request.model';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
getFirstCompletedRemoteData,
|
getFirstCompletedRemoteData,
|
||||||
getFirstSucceededRemoteDataPayload,
|
getFirstSucceededRemoteDataPayload,
|
||||||
|
@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { map, switchMap } from 'rxjs/operators';
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
import { ItemRequest } from '../../core/shared/item-request.model';
|
import { ItemRequest } from '../../core/shared/item-request.model';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import {
|
import {
|
||||||
getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload,
|
getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload,
|
||||||
redirectOn4xx
|
redirectOn4xx
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { ItemRequest } from '../core/shared/item-request.model';
|
import { ItemRequest } from '../core/shared/item-request.model';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { ItemRequestDataService } from '../core/data/item-request-data.service';
|
import { ItemRequestDataService } from '../core/data/item-request-data.service';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||||
|
@@ -8,7 +8,7 @@ import { pushInOut } from '../shared/animations/push';
|
|||||||
import { HostWindowService } from '../shared/host-window.service';
|
import { HostWindowService } from '../shared/host-window.service';
|
||||||
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||||
import { hasValue, isEmpty } from '../shared/empty.util';
|
import { hasValue, isEmpty } from '../shared/empty.util';
|
||||||
import { getFirstSucceededRemoteData } from '../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||||
import { RouteService } from '../core/services/route.service';
|
import { RouteService } from '../core/services/route.service';
|
||||||
import { SEARCH_CONFIG_SERVICE } from '../my-dspace-page/my-dspace-page.component';
|
import { SEARCH_CONFIG_SERVICE } from '../my-dspace-page/my-dspace-page.component';
|
||||||
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
||||||
@@ -126,12 +126,12 @@ export class SearchComponent implements OnInit {
|
|||||||
this.searchOptions$ = this.getSearchOptions();
|
this.searchOptions$ = this.getSearchOptions();
|
||||||
this.sub = this.searchOptions$.pipe(
|
this.sub = this.searchOptions$.pipe(
|
||||||
switchMap((options) => this.service.search(
|
switchMap((options) => this.service.search(
|
||||||
options, undefined, true, true, followLink<Item>('thumbnail', { isOptional: true })
|
options, undefined, false, true, followLink<Item>('thumbnail', { isOptional: true })
|
||||||
).pipe(getFirstSucceededRemoteData(), startWith(undefined))
|
).pipe(getFirstCompletedRemoteData(), startWith(undefined))
|
||||||
)
|
)
|
||||||
).subscribe((results) => {
|
).subscribe((results) => {
|
||||||
this.resultsRD$.next(results);
|
this.resultsRD$.next(results);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isEmpty(this.configuration$)) {
|
if (isEmpty(this.configuration$)) {
|
||||||
this.configuration$ = this.searchConfigService.getCurrentConfiguration('default');
|
this.configuration$ = this.searchConfigService.getCurrentConfiguration('default');
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { FileService } from '../../core/shared/file.service';
|
import { FileService } from '../../core/shared/file.service';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
@@ -77,7 +77,7 @@ describe('BitstreamDownloadPageComponent', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('init', () => {
|
describe('init', () => {
|
||||||
beforeEach(async(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
init();
|
init();
|
||||||
initTestbed();
|
initTestbed();
|
||||||
}));
|
}));
|
||||||
@@ -93,7 +93,7 @@ describe('BitstreamDownloadPageComponent', () => {
|
|||||||
|
|
||||||
describe('bitstream retrieval', () => {
|
describe('bitstream retrieval', () => {
|
||||||
describe('when the user is authorized and not logged in', () => {
|
describe('when the user is authorized and not logged in', () => {
|
||||||
beforeEach(async(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
init();
|
init();
|
||||||
(authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false));
|
(authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false));
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ describe('BitstreamDownloadPageComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('when the user is authorized and logged in', () => {
|
describe('when the user is authorized and logged in', () => {
|
||||||
beforeEach(async(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
init();
|
init();
|
||||||
initTestbed();
|
initTestbed();
|
||||||
}));
|
}));
|
||||||
@@ -123,7 +123,7 @@ describe('BitstreamDownloadPageComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('when the user is not authorized and logged in', () => {
|
describe('when the user is not authorized and logged in', () => {
|
||||||
beforeEach(async(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
init();
|
init();
|
||||||
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));
|
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));
|
||||||
initTestbed();
|
initTestbed();
|
||||||
@@ -138,7 +138,7 @@ describe('BitstreamDownloadPageComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('when the user is not authorized and not logged in', () => {
|
describe('when the user is not authorized and not logged in', () => {
|
||||||
beforeEach(async(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
init();
|
init();
|
||||||
(authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false));
|
(authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false));
|
||||||
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));
|
(authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false));
|
||||||
|
@@ -7,10 +7,9 @@ import { Bitstream } from '../../core/shared/bitstream.model';
|
|||||||
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';
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs';
|
import { combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
|
||||||
import { getBitstreamDownloadRoute, getForbiddenRoute } from '../../app-routing-paths';
|
import { getBitstreamDownloadRoute, getForbiddenRoute } from '../../app-routing-paths';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Subscription } from 'rxjs/internal/Subscription';
|
|
||||||
import { EPerson } from '../../core/eperson/models/eperson.model';
|
import { EPerson } from '../../core/eperson/models/eperson.model';
|
||||||
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
import { ItemRequestDataService } from '../../core/data/item-request-data.service';
|
import { ItemRequestDataService } from '../../core/data/item-request-data.service';
|
||||||
|
@@ -25,7 +25,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul class="list-unstyled">
|
<ul class="list-unstyled">
|
||||||
<li *ngFor="let object of objects?.payload?.page" class="mt-4 mb-4">
|
<li *ngFor="let object of objects?.payload?.page" class="mt-4 mb-4">
|
||||||
<ds-listable-object-component-loader [object]="object"></ds-listable-object-component-loader>
|
<ds-listable-object-component-loader [object]="object" [viewMode]="viewMode"></ds-listable-object-component-loader>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div>
|
<div>
|
||||||
|
@@ -2,7 +2,7 @@ import { BrowseByComponent } from './browse-by.component';
|
|||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { SharedModule } from '../shared.module';
|
import { SharedModule } from '../shared.module';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
@@ -18,17 +18,31 @@ import { PaginationComponentOptions } from '../pagination/pagination-component-o
|
|||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../remote-data.utils';
|
||||||
import { storeModuleConfig } from '../../app.reducer';
|
import { storeModuleConfig } from '../../app.reducer';
|
||||||
import { FindListOptions } from '../../core/data/request.models';
|
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { PaginationServiceStub } from '../testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../testing/pagination-service.stub';
|
||||||
|
import { ListableObjectComponentLoaderComponent } from '../object-collection/shared/listable-object/listable-object-component-loader.component';
|
||||||
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
|
import { BrowseEntryListElementComponent } from '../object-list/browse-entry-list-element/browse-entry-list-element.component';
|
||||||
|
import {
|
||||||
|
DEFAULT_CONTEXT,
|
||||||
|
listableObjectComponent,
|
||||||
|
} from '../object-collection/shared/listable-object/listable-object.decorator';
|
||||||
|
import { BrowseEntry } from '../../core/shared/browse-entry.model';
|
||||||
|
import { ITEM } from '../../core/shared/item.resource-type';
|
||||||
import { ThemeService } from '../theme-support/theme.service';
|
import { ThemeService } from '../theme-support/theme.service';
|
||||||
|
import SpyObj = jasmine.SpyObj;
|
||||||
|
|
||||||
|
@listableObjectComponent(BrowseEntry, ViewMode.ListElement, DEFAULT_CONTEXT, 'custom')
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-browse-entry-list-element',
|
||||||
|
template: ''
|
||||||
|
})
|
||||||
|
class MockThemedBrowseEntryListElementComponent {}
|
||||||
|
|
||||||
describe('BrowseByComponent', () => {
|
describe('BrowseByComponent', () => {
|
||||||
let comp: BrowseByComponent;
|
let comp: BrowseByComponent;
|
||||||
let fixture: ComponentFixture<BrowseByComponent>;
|
let fixture: ComponentFixture<BrowseByComponent>;
|
||||||
|
|
||||||
let themeService: ThemeService;
|
|
||||||
|
|
||||||
const mockItems = [
|
const mockItems = [
|
||||||
Object.assign(new Item(), {
|
Object.assign(new Item(), {
|
||||||
id: 'fakeId-1',
|
id: 'fakeId-1',
|
||||||
@@ -59,9 +73,12 @@ describe('BrowseByComponent', () => {
|
|||||||
});
|
});
|
||||||
const paginationService = new PaginationServiceStub(paginationConfig);
|
const paginationService = new PaginationServiceStub(paginationConfig);
|
||||||
|
|
||||||
|
let themeService: SpyObj<ThemeService>;
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
themeService = jasmine.createSpyObj('themeService', {
|
themeService = jasmine.createSpyObj('themeService', {
|
||||||
getThemeName: 'dspace',
|
getThemeName: 'dspace',
|
||||||
|
getThemeName$: observableOf('dspace'),
|
||||||
});
|
});
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -82,6 +99,7 @@ describe('BrowseByComponent', () => {
|
|||||||
declarations: [],
|
declarations: [],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: PaginationService, useValue: paginationService},
|
{provide: PaginationService, useValue: paginationService},
|
||||||
|
{provide: MockThemedBrowseEntryListElementComponent},
|
||||||
{ provide: ThemeService, useValue: themeService },
|
{ provide: ThemeService, useValue: themeService },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@@ -170,4 +188,67 @@ describe('BrowseByComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when enableArrows is true and browseEntries are provided', () => {
|
||||||
|
let browseEntries;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
browseEntries = [
|
||||||
|
Object.assign(new BrowseEntry(), {
|
||||||
|
type: ITEM,
|
||||||
|
authority: 'authority key 1',
|
||||||
|
value: 'browse entry 1',
|
||||||
|
language: null,
|
||||||
|
count: 1,
|
||||||
|
}),
|
||||||
|
Object.assign(new BrowseEntry(), {
|
||||||
|
type: ITEM,
|
||||||
|
authority: null,
|
||||||
|
value: 'browse entry 2',
|
||||||
|
language: null,
|
||||||
|
count: 4,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
comp.enableArrows = true;
|
||||||
|
comp.objects$ = createSuccessfulRemoteDataObject$(buildPaginatedList(new PageInfo(), browseEntries));
|
||||||
|
comp.paginationConfig = paginationConfig;
|
||||||
|
comp.sortConfig = Object.assign(new SortOptions('dc.title', SortDirection.ASC));
|
||||||
|
// NOTE: do NOT trigger change detection until the theme is set, such that the theme can be picked up as well
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when theme is base', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
themeService.getThemeName.and.returnValue('base');
|
||||||
|
themeService.getThemeName$.and.returnValue(observableOf('base'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use the base component to render browse entries', () => {
|
||||||
|
const componentLoaders = fixture.debugElement.queryAll(By.directive(ListableObjectComponentLoaderComponent));
|
||||||
|
expect(componentLoaders.length).toEqual(browseEntries.length);
|
||||||
|
componentLoaders.forEach((componentLoader) => {
|
||||||
|
const browseEntry = componentLoader.query(By.css('ds-browse-entry-list-element'));
|
||||||
|
expect(browseEntry.componentInstance).toBeInstanceOf(BrowseEntryListElementComponent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when theme is custom', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
themeService.getThemeName.and.returnValue('custom');
|
||||||
|
themeService.getThemeName$.and.returnValue(observableOf('custom'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use the themed component to render browse entries', () => {
|
||||||
|
const componentLoaders = fixture.debugElement.queryAll(By.directive(ListableObjectComponentLoaderComponent));
|
||||||
|
expect(componentLoaders.length).toEqual(browseEntries.length);
|
||||||
|
componentLoaders.forEach((componentLoader) => {
|
||||||
|
const browseEntry = componentLoader.query(By.css('ds-browse-entry-list-element'));
|
||||||
|
expect(browseEntry.componentInstance).toBeInstanceOf(MockThemedBrowseEntryListElementComponent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -8,6 +8,7 @@ import { Observable } from 'rxjs';
|
|||||||
import { ListableObject } from '../object-collection/shared/listable-object.model';
|
import { ListableObject } from '../object-collection/shared/listable-object.model';
|
||||||
import { getStartsWithComponent, StartsWithType } from '../starts-with/starts-with-decorator';
|
import { getStartsWithComponent, StartsWithType } from '../starts-with/starts-with-decorator';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-browse-by',
|
selector: 'ds-browse-by',
|
||||||
@@ -22,6 +23,12 @@ import { PaginationService } from '../../core/pagination/pagination.service';
|
|||||||
* Component to display a browse-by page for any ListableObject
|
* Component to display a browse-by page for any ListableObject
|
||||||
*/
|
*/
|
||||||
export class BrowseByComponent implements OnInit {
|
export class BrowseByComponent implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewMode that should be passed to {@link ListableObjectComponentLoaderComponent}.
|
||||||
|
*/
|
||||||
|
viewMode: ViewMode = ViewMode.ListElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The i18n message to display as title
|
* The i18n message to display as title
|
||||||
*/
|
*/
|
||||||
|
@@ -8,7 +8,7 @@ import { ChipsItem } from './models/chips-item.model';
|
|||||||
import { UploaderService } from '../uploader/uploader.service';
|
import { UploaderService } from '../uploader/uploader.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Options } from 'sortablejs';
|
import { Options } from 'sortablejs';
|
||||||
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-chips',
|
selector: 'ds-chips',
|
||||||
|
@@ -11,8 +11,8 @@
|
|||||||
aria-labelledby="dropdownMenuButton"
|
aria-labelledby="dropdownMenuButton"
|
||||||
(scroll)="onScroll($event)"
|
(scroll)="onScroll($event)"
|
||||||
infiniteScroll
|
infiniteScroll
|
||||||
[infiniteScrollDistance]="5"
|
[infiniteScrollDistance]="1.5"
|
||||||
[infiniteScrollThrottle]="300"
|
[infiniteScrollThrottle]="0"
|
||||||
[infiniteScrollUpDistance]="1.5"
|
[infiniteScrollUpDistance]="1.5"
|
||||||
[fromRoot]="true"
|
[fromRoot]="true"
|
||||||
[scrollWindow]="false"
|
[scrollWindow]="false"
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<button class="dropdown-item disabled" *ngIf="searchListCollection?.length == 0 && !(isLoading | async)">
|
<button class="dropdown-item disabled" *ngIf="searchListCollection?.length == 0 && !(isLoading | async)">
|
||||||
{{'submission.sections.general.no-collection' | translate}}
|
{{'submission.sections.general.no-collection' | translate}}
|
||||||
</button>
|
</button>
|
||||||
<ng-container *ngIf="searchListCollection?.length > 0 && !(isLoading | async)">
|
<ng-container *ngIf="searchListCollection?.length > 0">
|
||||||
<button *ngFor="let listItem of searchListCollection"
|
<button *ngFor="let listItem of searchListCollection"
|
||||||
class="dropdown-item collection-item"
|
class="dropdown-item collection-item"
|
||||||
title="{{ listItem.collection.name }}"
|
title="{{ listItem.collection.name }}"
|
||||||
|
@@ -223,20 +223,20 @@ export class CollectionDropdownComponent implements OnInit, OnDestroy {
|
|||||||
switchMap((collectionsRD: RemoteData<PaginatedList<Collection>>) => {
|
switchMap((collectionsRD: RemoteData<PaginatedList<Collection>>) => {
|
||||||
this.searchComplete.emit();
|
this.searchComplete.emit();
|
||||||
if (collectionsRD.hasSucceeded && collectionsRD.payload.totalElements > 0) {
|
if (collectionsRD.hasSucceeded && collectionsRD.payload.totalElements > 0) {
|
||||||
if ( (this.searchListCollection.length + findOptions.elementsPerPage) >= collectionsRD.payload.totalElements ) {
|
if (this.searchListCollection.length >= collectionsRD.payload.totalElements) {
|
||||||
this.hasNextPage = false;
|
this.hasNextPage = false;
|
||||||
this.emitSelectionEvents(collectionsRD);
|
|
||||||
return observableFrom(collectionsRD.payload.page).pipe(
|
|
||||||
mergeMap((collection: Collection) => collection.parentCommunity.pipe(
|
|
||||||
getFirstSucceededRemoteDataPayload(),
|
|
||||||
map((community: Community) => ({
|
|
||||||
communities: [{ id: community.id, name: community.name }],
|
|
||||||
collection: { id: collection.id, uuid: collection.id, name: collection.name }
|
|
||||||
})
|
|
||||||
))),
|
|
||||||
reduce((acc: any, value: any) => [...acc, value], []),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
this.emitSelectionEvents(collectionsRD);
|
||||||
|
return observableFrom(collectionsRD.payload.page).pipe(
|
||||||
|
mergeMap((collection: Collection) => collection.parentCommunity.pipe(
|
||||||
|
getFirstSucceededRemoteDataPayload(),
|
||||||
|
map((community: Community) => ({
|
||||||
|
communities: [{ id: community.id, name: community.name }],
|
||||||
|
collection: { id: collection.id, uuid: collection.id, name: collection.name }
|
||||||
|
})
|
||||||
|
))),
|
||||||
|
reduce((acc: any, value: any) => [...acc, value], []),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
this.hasNextPage = false;
|
this.hasNextPage = false;
|
||||||
return observableOf([]);
|
return observableOf([]);
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs';
|
||||||
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@@ -1,11 +1,10 @@
|
|||||||
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||||
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../../../core/data/feature-authorization/authorization-data.service';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable, of } from 'rxjs';
|
||||||
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
import { FeatureID } from '../../../core/data/feature-authorization/feature-id';
|
||||||
import { VersionHistoryDataService } from '../../../core/data/version-history-data.service';
|
import { VersionHistoryDataService } from '../../../core/data/version-history-data.service';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { map, startWith, switchMap } from 'rxjs/operators';
|
import { map, startWith, switchMap } from 'rxjs/operators';
|
||||||
import { of } from 'rxjs';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-dso-page-version-button',
|
selector: 'ds-dso-page-version-button',
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
class="form-control"
|
class="form-control"
|
||||||
(click)="$event.stopPropagation();"
|
(click)="$event.stopPropagation();"
|
||||||
placeholder="{{'dso-selector.placeholder' | translate: { type: typesString } }}"
|
placeholder="{{'dso-selector.placeholder' | translate: { type: typesString } }}"
|
||||||
[formControl]="input" dsAutoFocus (keyup.enter)="selectSingleResult()">
|
[formControl]="input" ngbAutofocus (keyup.enter)="selectSingleResult()">
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<div class="scrollable-menu list-group">
|
<div class="scrollable-menu list-group">
|
||||||
|
@@ -1,9 +1,8 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { map, switchMap } from 'rxjs/operators';
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
import { of as observableOf } from 'rxjs';
|
|
||||||
import { METADATA_EXPORT_SCRIPT_NAME, ScriptDataService } from '../../../../core/data/processes/script-data.service';
|
import { METADATA_EXPORT_SCRIPT_NAME, ScriptDataService } from '../../../../core/data/processes/script-data.service';
|
||||||
import { Collection } from '../../../../core/shared/collection.model';
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
import { Community } from '../../../../core/shared/community.model';
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user