mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-16 22:43:03 +00:00
Merge remote-tracking branch 'remotes/origin/master' into shibboleth
# Conflicts: # resources/i18n/de.json5 # resources/i18n/en.json5 # src/app/shared/shared.module.ts
This commit is contained in:
97
README.md
97
README.md
@@ -3,13 +3,12 @@
|
|||||||
dspace-angular
|
dspace-angular
|
||||||
==============
|
==============
|
||||||
|
|
||||||
> The next UI for DSpace, based on Angular Universal.
|
> The next UI for DSpace 7, based on Angular Universal.
|
||||||
|
|
||||||
This project is currently in pre-alpha.
|
This project is currently under active development. For more information on the DSpace 7 release see the [DSpace 7.0 Release Status wiki page](https://wiki.lyrasis.org/display/DSPACE/DSpace+Release+7.0+Status)
|
||||||
|
|
||||||
You can find additional information on the [wiki](https://wiki.duraspace.org/display/DSPACE/DSpace+7+-+Angular+UI) or [the project board (waffle.io)](https://waffle.io/DSpace/dspace-angular).
|
You can find additional information on the DSpace 7 Angular UI on the [wiki](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+-+Angular+UI+Development).
|
||||||
|
|
||||||
If you're looking for the 2016 Angular 2 DSpace UI prototype, you can find it [here](https://github.com/DSpace-Labs/angular2-ui-prototype)
|
|
||||||
|
|
||||||
Quick start
|
Quick start
|
||||||
-----------
|
-----------
|
||||||
@@ -32,8 +31,6 @@ yarn start
|
|||||||
|
|
||||||
Then go to [http://localhost:3000](http://localhost:3000) in your browser
|
Then go to [http://localhost:3000](http://localhost:3000) in your browser
|
||||||
|
|
||||||
NOTE: currently there's not much to see at that URL. We really do need your help. If you're interested in jumping in, and you've made it this far, please look at the [the project board (waffle.io)](https://waffle.io/DSpace/dspace-angular), grab a card, and get to work. Thanks!
|
|
||||||
|
|
||||||
Not sure where to start? watch the training videos linked in the [Introduction to the technology](#introduction-to-the-technology) section below.
|
Not sure where to start? watch the training videos linked in the [Introduction to the technology](#introduction-to-the-technology) section below.
|
||||||
|
|
||||||
Table of Contents
|
Table of Contents
|
||||||
@@ -42,24 +39,27 @@ Table of Contents
|
|||||||
- [Introduction to the technology](#introduction-to-the-technology)
|
- [Introduction to the technology](#introduction-to-the-technology)
|
||||||
- [Requirements](#requirements)
|
- [Requirements](#requirements)
|
||||||
- [Installing](#installing)
|
- [Installing](#installing)
|
||||||
- [Configuring](#configuring)
|
- [Configuring](#configuring)
|
||||||
- [Running the app](#running-the-app)
|
- [Running the app](#running-the-app)
|
||||||
- [Running in production mode](#running-in-production-mode)
|
- [Running in production mode](#running-in-production-mode)
|
||||||
|
- [Deploy](#deploy)
|
||||||
|
- [Running the application with Docker](#running-the-application-with-docker)
|
||||||
- [Cleaning](#cleaning)
|
- [Cleaning](#cleaning)
|
||||||
- [Testing](#testing)
|
- [Testing](#testing)
|
||||||
|
- [Test a Pull Request](#test-a-pull-request)
|
||||||
- [Documentation](#documentation)
|
- [Documentation](#documentation)
|
||||||
- [Other commands](#other-commands)
|
- [Other commands](#other-commands)
|
||||||
- [Recommended Editors/IDEs](#recommended-editorsides)
|
- [Recommended Editors/IDEs](#recommended-editorsides)
|
||||||
- [Collaborating](#collaborating)
|
- [Collaborating](#collaborating)
|
||||||
- [File Structure](#file-structure)
|
- [File Structure](#file-structure)
|
||||||
- [3rd Party Library Installation](#3rd-party-library-installation)
|
- [Managing Dependencies (via yarn)](#managing-dependencies-via-yarn)
|
||||||
- [Frequently asked questions](#frequently-asked-questions)
|
- [Frequently asked questions](#frequently-asked-questions)
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
Introduction to the technology
|
Introduction to the technology
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
You can find more information on the technologies used in this project (Angular 2, Typescript, Angular Universal, RxJS, etc) on the [DuraSpace wiki](https://wiki.duraspace.org/display/DSPACE/DSpace+7+UI+Technology+Stack)
|
You can find more information on the technologies used in this project (Angular.io, Typescript, Angular Universal, RxJS, etc) on the [LYRASIS wiki](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+UI+Technology+Stack)
|
||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
------------
|
------------
|
||||||
@@ -75,8 +75,7 @@ Installing
|
|||||||
- `yarn run global` to install the required global dependencies
|
- `yarn run global` to install the required global dependencies
|
||||||
- `yarn install` to install the local dependencies
|
- `yarn install` to install the local dependencies
|
||||||
|
|
||||||
Configuring
|
### Configuring
|
||||||
-----------
|
|
||||||
|
|
||||||
Default configuration file is located in `config/` folder.
|
Default configuration file is located in `config/` folder.
|
||||||
|
|
||||||
@@ -98,8 +97,7 @@ Running the app
|
|||||||
|
|
||||||
After you have installed all dependencies you can now run the app. Run `yarn run watch` to start a local server which will watch for changes, rebuild the code, and reload the server for you. You can visit it at `http://localhost:3000`.
|
After you have installed all dependencies you can now run the app. Run `yarn run watch` to start a local server which will watch for changes, rebuild the code, and reload the server for you. You can visit it at `http://localhost:3000`.
|
||||||
|
|
||||||
Running in production mode
|
### Running in production mode
|
||||||
--------------------------
|
|
||||||
|
|
||||||
When building for production we're using Ahead of Time (AoT) compilation. With AoT, the browser downloads a pre-compiled version of the application, so it can render the application immediately, without waiting to compile the app first. The compiler is roughly half the size of Angular itself, so omitting it dramatically reduces the application payload.
|
When building for production we're using Ahead of Time (AoT) compilation. With AoT, the browser downloads a pre-compiled version of the application, so it can render the application immediately, without waiting to compile the app first. The compiler is roughly half the size of Angular itself, so omitting it dramatically reduces the application payload.
|
||||||
|
|
||||||
@@ -117,6 +115,19 @@ yarn run build:prod
|
|||||||
|
|
||||||
This will build the application and put the result in the `dist` folder
|
This will build the application and put the result in the `dist` folder
|
||||||
|
|
||||||
|
### Deploy
|
||||||
|
```bash
|
||||||
|
# deploy production in standalone pm2 container
|
||||||
|
yarn run deploy
|
||||||
|
|
||||||
|
# remove production from standalone pm2 container
|
||||||
|
yarn run undeploy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running the application with Docker
|
||||||
|
See [Docker Runtime Options](docker/README.md)
|
||||||
|
|
||||||
|
|
||||||
Cleaning
|
Cleaning
|
||||||
--------
|
--------
|
||||||
|
|
||||||
@@ -131,10 +142,6 @@ yarn run clean:prod
|
|||||||
yarn run clean:dist
|
yarn run clean:dist
|
||||||
```
|
```
|
||||||
|
|
||||||
Running the application with Docker
|
|
||||||
-----------------------------------
|
|
||||||
See [Docker Runtime Options](docker/README.md)
|
|
||||||
|
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
-------
|
-------
|
||||||
@@ -189,21 +196,14 @@ To run all the tests (e.g.: to run tests with Continuous Integration software) y
|
|||||||
Documentation
|
Documentation
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
See [`./docs`](docs) for further documentation.
|
||||||
|
|
||||||
|
### Building code documentation
|
||||||
|
|
||||||
To build the code documentation we use [TYPEDOC](http://typedoc.org). TYPEDOC is a documentation generator for TypeScript projects. It extracts informations from properly formatted comments that can be written within the code files. Follow the instructions [here](http://typedoc.org/guides/doccomments/) to know how to make those comments.
|
To build the code documentation we use [TYPEDOC](http://typedoc.org). TYPEDOC is a documentation generator for TypeScript projects. It extracts informations from properly formatted comments that can be written within the code files. Follow the instructions [here](http://typedoc.org/guides/doccomments/) to know how to make those comments.
|
||||||
|
|
||||||
Run:`yarn run docs` to produce the documentation that will be available in the 'doc' folder.
|
Run:`yarn run docs` to produce the documentation that will be available in the 'doc' folder.
|
||||||
|
|
||||||
Deploy
|
|
||||||
------
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# deploy production in standalone pm2 container
|
|
||||||
yarn run deploy
|
|
||||||
|
|
||||||
# remove production from standalone pm2 container
|
|
||||||
yarn run undeploy
|
|
||||||
```
|
|
||||||
|
|
||||||
Other commands
|
Other commands
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
@@ -229,7 +229,7 @@ To get the most out of TypeScript, you'll need a TypeScript-aware editor. We've
|
|||||||
Collaborating
|
Collaborating
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
See [the guide on the wiki](https://wiki.duraspace.org/display/DSPACE/DSpace+7+-+Angular+2+UI#DSpace7-Angular2UI-Howtocontribute)
|
See [the guide on the wiki](https://wiki.lyrasis.org/display/DSPACE/DSpace+7+-+Angular+UI+Development#DSpace7-AngularUIDevelopment-Howtocontribute)
|
||||||
|
|
||||||
File Structure
|
File Structure
|
||||||
--------------
|
--------------
|
||||||
@@ -335,10 +335,20 @@ dspace-angular
|
|||||||
└── yarn.lock * Yarn lockfile (https://yarnpkg.com/en/docs/yarn-lock)
|
└── yarn.lock * Yarn lockfile (https://yarnpkg.com/en/docs/yarn-lock)
|
||||||
```
|
```
|
||||||
|
|
||||||
3rd Party Library Installation
|
Managing Dependencies (via yarn)
|
||||||
------------------------------
|
-------------
|
||||||
|
|
||||||
Install your library via `yarn add lib-name --save` and import it in your code. `--save` will add it to `package.json`.
|
This project makes use of [`yarn`](https://yarnpkg.com/en/) to ensure that the exact same dependency versions are used every time you install it.
|
||||||
|
|
||||||
|
* `yarn` creates a [`yarn.lock`](https://yarnpkg.com/en/docs/yarn-lock) to track those versions. That file is updated automatically by whenever dependencies are added/updated/removed via yarn.
|
||||||
|
* **Adding new dependencies**: To install/add a new dependency (third party library), use [`yarn add`](https://yarnpkg.com/en/docs/cli/add). For example: `yarn add some-lib`.
|
||||||
|
* If you are adding a new build tool dependency (to `devDependencies`), use `yarn add some-lib --dev`
|
||||||
|
* **Upgrading existing dependencies**: To upgrade existing dependencies, you can use [`yarn upgrade`](https://yarnpkg.com/en/docs/cli/upgrade). For example: `yarn upgrade some-lib` or `yarn upgrade some-lib@version`
|
||||||
|
* **Removing dependencies**: If a dependency is no longer needed, or replaced, use [`yarn remove`](https://yarnpkg.com/en/docs/cli/remove) to remove it.
|
||||||
|
|
||||||
|
As you can see above, using `yarn` commandline tools means that you should never need to modify the `package.json` manually. *We recommend always using `yarn` to keep dependencies updated / in sync.*
|
||||||
|
|
||||||
|
### Adding Typings for libraries
|
||||||
|
|
||||||
If the library does not include typings, you can install them using yarn:
|
If the library does not include typings, you can install them using yarn:
|
||||||
|
|
||||||
@@ -370,24 +380,6 @@ If you're importing a module that uses CommonJS you need to import as
|
|||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
```
|
```
|
||||||
|
|
||||||
Managing Dependencies (via yarn)
|
|
||||||
-------------
|
|
||||||
|
|
||||||
This project makes use of [`yarn`](https://yarnpkg.com/en/) to ensure that the exact same dependency versions are used every time you install it.
|
|
||||||
|
|
||||||
* `yarn` creates a [`yarn.lock`](https://yarnpkg.com/en/docs/yarn-lock) to track those versions. That file is updated automatically by whenever dependencies are added/updated/removed via yarn.
|
|
||||||
* **Adding new dependencies**: To install/add a new dependency (third party library), use [`yarn add`](https://yarnpkg.com/en/docs/cli/add). For example: `yarn add some-lib`.
|
|
||||||
* If you are adding a new build tool dependency (to `devDependencies`), use `yarn add some-lib --dev`
|
|
||||||
* **Upgrading existing dependencies**: To upgrade existing dependencies, you can use [`yarn upgrade`](https://yarnpkg.com/en/docs/cli/upgrade). For example: `yarn upgrade some-lib` or `yarn upgrade some-lib@version`
|
|
||||||
* **Removing dependencies**: If a dependency is no longer needed, or replaced, use [`yarn remove`](https://yarnpkg.com/en/docs/cli/remove) to remove it.
|
|
||||||
|
|
||||||
As you can see above, using `yarn` commandline tools means that you should never need to modify the `package.json` manually. *We recommend always using `yarn` to keep dependencies updated / in sync.*
|
|
||||||
|
|
||||||
Further Documentation
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
See [`./docs`](docs) for further documentation.
|
|
||||||
|
|
||||||
Frequently asked questions
|
Frequently asked questions
|
||||||
--------------------------
|
--------------------------
|
||||||
|
|
||||||
@@ -411,5 +403,4 @@ Frequently asked questions
|
|||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
This project's source code is made available under the DSpace BSD License: http://www.dspace.org/license
|
||||||
http://www.dspace.org/license
|
|
||||||
|
@@ -141,6 +141,10 @@ module.exports = {
|
|||||||
code: 'nl',
|
code: 'nl',
|
||||||
label: 'Nederlands',
|
label: 'Nederlands',
|
||||||
active: false,
|
active: false,
|
||||||
|
}, {
|
||||||
|
code: 'pt',
|
||||||
|
label: 'Português',
|
||||||
|
active: true,
|
||||||
}],
|
}],
|
||||||
// Browse-By Pages
|
// Browse-By Pages
|
||||||
browseBy: {
|
browseBy: {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
dspace.dir=/dspace
|
dspace.dir=/dspace
|
||||||
db.url=jdbc:postgresql://dspacedb:5432/dspace
|
db.url=jdbc:postgresql://dspacedb:5432/dspace
|
||||||
dspace.hostname=dspace
|
dspace.hostname=dspace
|
||||||
dspace.baseUrl=http://localhost:8080
|
dspace.baseUrl=http://localhost:8080/server
|
||||||
dspace.name=DSpace Started with Docker Compose
|
dspace.name=DSpace Started with Docker Compose
|
||||||
solr.server=http://dspacesolr:8983/solr
|
solr.server=http://dspacesolr:8983/solr
|
||||||
|
@@ -2,7 +2,8 @@ import { browser, element, by } from 'protractor';
|
|||||||
|
|
||||||
export class ProtractorPage {
|
export class ProtractorPage {
|
||||||
navigateTo() {
|
navigateTo() {
|
||||||
return browser.get('/');
|
return browser.get('/')
|
||||||
|
.then(() => browser.waitForAngular());
|
||||||
}
|
}
|
||||||
|
|
||||||
getPageTitleText() {
|
getPageTitleText() {
|
||||||
|
@@ -15,7 +15,11 @@ module.exports = function (config) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
var configuration = {
|
var configuration = {
|
||||||
|
client: {
|
||||||
|
jasmine: {
|
||||||
|
random: false
|
||||||
|
}
|
||||||
|
},
|
||||||
// base path that will be used to resolve all patterns (e.g. files, exclude)
|
// base path that will be used to resolve all patterns (e.g. files, exclude)
|
||||||
basePath: '',
|
basePath: '',
|
||||||
|
|
||||||
|
11
package.json
11
package.json
@@ -70,10 +70,12 @@
|
|||||||
"docs": "typedoc --options typedoc.json ./src/",
|
"docs": "typedoc --options typedoc.json ./src/",
|
||||||
"coverage": "http-server -c-1 -o -p 9875 ./coverage",
|
"coverage": "http-server -c-1 -o -p 9875 ./coverage",
|
||||||
"postinstall": "yarn run patch-protractor",
|
"postinstall": "yarn run patch-protractor",
|
||||||
"patch-protractor": "ncp node_modules/webdriver-manager node_modules/protractor/node_modules/webdriver-manager"
|
"patch-protractor": "ncp node_modules/webdriver-manager node_modules/protractor/node_modules/webdriver-manager",
|
||||||
|
"sync-i18n": "node ./scripts/sync-i18n-files.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^6.1.4",
|
"@angular/animations": "^6.1.4",
|
||||||
|
"@angular/cdk": "^6.4.7",
|
||||||
"@angular/cli": "^6.1.5",
|
"@angular/cli": "^6.1.5",
|
||||||
"@angular/common": "^6.1.4",
|
"@angular/common": "^6.1.4",
|
||||||
"@angular/core": "^6.1.4",
|
"@angular/core": "^6.1.4",
|
||||||
@@ -138,6 +140,7 @@
|
|||||||
"text-mask-core": "5.0.1",
|
"text-mask-core": "5.0.1",
|
||||||
"ts-loader": "^5.2.1",
|
"ts-loader": "^5.2.1",
|
||||||
"ts-md5": "^1.2.4",
|
"ts-md5": "^1.2.4",
|
||||||
|
"url-parse": "^1.4.7",
|
||||||
"uuid": "^3.2.1",
|
"uuid": "^3.2.1",
|
||||||
"webfontloader": "1.6.28",
|
"webfontloader": "1.6.28",
|
||||||
"webpack-cli": "^3.1.0",
|
"webpack-cli": "^3.1.0",
|
||||||
@@ -174,7 +177,9 @@
|
|||||||
"angular2-template-loader": "0.6.2",
|
"angular2-template-loader": "0.6.2",
|
||||||
"autoprefixer": "^9.1.3",
|
"autoprefixer": "^9.1.3",
|
||||||
"caniuse-lite": "^1.0.30000697",
|
"caniuse-lite": "^1.0.30000697",
|
||||||
|
"cli-progress": "^3.3.1",
|
||||||
"codelyzer": "^4.4.4",
|
"codelyzer": "^4.4.4",
|
||||||
|
"commander": "^3.0.2",
|
||||||
"compression-webpack-plugin": "^1.1.6",
|
"compression-webpack-plugin": "^1.1.6",
|
||||||
"copy-webpack-plugin": "^4.4.1",
|
"copy-webpack-plugin": "^4.4.1",
|
||||||
"copyfiles": "^2.1.1",
|
"copyfiles": "^2.1.1",
|
||||||
@@ -225,7 +230,7 @@
|
|||||||
"rollup-plugin-node-globals": "1.2.1",
|
"rollup-plugin-node-globals": "1.2.1",
|
||||||
"rollup-plugin-node-resolve": "^3.0.3",
|
"rollup-plugin-node-resolve": "^3.0.3",
|
||||||
"rollup-plugin-terser": "^2.0.2",
|
"rollup-plugin-terser": "^2.0.2",
|
||||||
"sass-loader": "7.1.0",
|
"sass-loader": "^7.1.0",
|
||||||
"script-ext-html-webpack-plugin": "2.0.1",
|
"script-ext-html-webpack-plugin": "2.0.1",
|
||||||
"source-map": "0.7.3",
|
"source-map": "0.7.3",
|
||||||
"source-map-loader": "0.2.4",
|
"source-map-loader": "0.2.4",
|
||||||
@@ -236,7 +241,7 @@
|
|||||||
"tslint": "5.11.0",
|
"tslint": "5.11.0",
|
||||||
"typedoc": "^0.9.0",
|
"typedoc": "^0.9.0",
|
||||||
"typescript": "^2.9.1",
|
"typescript": "^2.9.1",
|
||||||
"webdriver-manager": "^12.1.6",
|
"webdriver-manager": "^12.1.7",
|
||||||
"webpack": "^4.17.1",
|
"webpack": "^4.17.1",
|
||||||
"webpack-bundle-analyzer": "^3.3.2",
|
"webpack-bundle-analyzer": "^3.3.2",
|
||||||
"webpack-dev-middleware": "3.2.0",
|
"webpack-dev-middleware": "3.2.0",
|
||||||
|
3
resources/fonts/README.md
Normal file
3
resources/fonts/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Supported font formats
|
||||||
|
|
||||||
|
DSpace supports EOT, TTF, OTF, SVG, WOFF and WOFF2 fonts.
|
3220
resources/i18n/ar.json5
Normal file
3220
resources/i18n/ar.json5
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1610
resources/i18n/es.json5
Normal file
1610
resources/i18n/es.json5
Normal file
File diff suppressed because it is too large
Load Diff
3220
resources/i18n/fi.json5
Normal file
3220
resources/i18n/fi.json5
Normal file
File diff suppressed because it is too large
Load Diff
3220
resources/i18n/fr.json5
Normal file
3220
resources/i18n/fr.json5
Normal file
File diff suppressed because it is too large
Load Diff
3220
resources/i18n/ja.json5
Normal file
3220
resources/i18n/ja.json5
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3220
resources/i18n/pl.json5
Normal file
3220
resources/i18n/pl.json5
Normal file
File diff suppressed because it is too large
Load Diff
2457
resources/i18n/pt.json5
Normal file
2457
resources/i18n/pt.json5
Normal file
File diff suppressed because it is too large
Load Diff
3220
resources/i18n/sw.json5
Normal file
3220
resources/i18n/sw.json5
Normal file
File diff suppressed because it is too large
Load Diff
3220
resources/i18n/tr.json5
Normal file
3220
resources/i18n/tr.json5
Normal file
File diff suppressed because it is too large
Load Diff
342
scripts/sync-i18n-files.js
Executable file
342
scripts/sync-i18n-files.js
Executable file
@@ -0,0 +1,342 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
const commander = require('commander');
|
||||||
|
const fs = require('fs');
|
||||||
|
const JSON5 = require('json5');
|
||||||
|
const _cliProgress = require('cli-progress');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const {projectRoot} = require('../webpack/helpers');
|
||||||
|
|
||||||
|
const program = new commander.Command();
|
||||||
|
program.version('1.0.0', '-v, --version');
|
||||||
|
|
||||||
|
const NEW_MESSAGE_TODO = '// TODO New key - Add a translation';
|
||||||
|
const MESSAGE_CHANGED_TODO = '// TODO Source message changed - Revise the translation';
|
||||||
|
const COMMENTS_CHANGED_TODO = '// TODO Source comments changed - Revise the translation';
|
||||||
|
|
||||||
|
const DEFAULT_SOURCE_FILE_LOCATION = 'resources/i18n/en.json5';
|
||||||
|
const LANGUAGE_FILES_LOCATION = 'resources/i18n';
|
||||||
|
|
||||||
|
parseCliInput();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the CLI input given by the user
|
||||||
|
* If no parameters are set (standard usage) -> source file is default (set to DEFAULT_SOURCE_FILE_LOCATION) and all
|
||||||
|
* other language files in the LANGUAGE_FILES_LOCATION are synced with this one in-place
|
||||||
|
* (replaced with newly synced file)
|
||||||
|
* If only target-file -t is set -> either -i in-place or -o output-file must be set
|
||||||
|
* Source file can be set with -s if it should be something else than DEFAULT_SOURCE_FILE_LOCATION
|
||||||
|
*
|
||||||
|
* If any of the paths to files/dirs given by user are not valid, an error message is printed and script gets aborted
|
||||||
|
*/
|
||||||
|
function parseCliInput() {
|
||||||
|
program
|
||||||
|
.option('-d, --output-dir <output-dir>', 'output dir when running script on all language files; mutually exclusive with -o')
|
||||||
|
.option('-t, --target-file <target>', 'target file we compare with and where completed output ends up if -o is not configured and -i is')
|
||||||
|
.option('-i, --edit-in-place', 'edit-in-place; store output straight in target file; mutually exclusive with -o')
|
||||||
|
.option('-s, --source-file <source>', 'source file to be parsed for translation', projectRoot(DEFAULT_SOURCE_FILE_LOCATION))
|
||||||
|
.option('-o, --output-file <output>', 'where output of script ends up; mutually exclusive with -i')
|
||||||
|
.usage('([-d <output-dir>] [-s <source-file>]) || (-t <target-file> (-i | -o <output>) [-s <source-file>])')
|
||||||
|
.parse(process.argv);
|
||||||
|
|
||||||
|
if (!program.targetFile) {
|
||||||
|
fs.readdirSync(projectRoot(LANGUAGE_FILES_LOCATION)).forEach(file => {
|
||||||
|
if (!program.sourceFile.toString().endsWith(file)) {
|
||||||
|
const targetFileLocation = projectRoot(LANGUAGE_FILES_LOCATION + "/" + file);
|
||||||
|
console.log('Syncing file at: ' + targetFileLocation + ' with source file at: ' + program.sourceFile);
|
||||||
|
if (program.outputDir) {
|
||||||
|
if (!fs.existsSync(program.outputDir)) {
|
||||||
|
fs.mkdirSync(program.outputDir);
|
||||||
|
}
|
||||||
|
const outputFileLocation = program.outputDir + "/" + file;
|
||||||
|
console.log('Output location: ' + outputFileLocation);
|
||||||
|
syncFileWithSource(targetFileLocation, outputFileLocation);
|
||||||
|
} else {
|
||||||
|
console.log('Replacing in target location');
|
||||||
|
syncFileWithSource(targetFileLocation, targetFileLocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (program.targetFile && !checkIfPathToFileIsValid(program.targetFile)) {
|
||||||
|
console.error('Directory path of target file is not valid.');
|
||||||
|
console.log(program.outputHelp());
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (program.targetFile && checkIfFileExists(program.targetFile) && !(program.editInPlace || program.outputFile)) {
|
||||||
|
console.error('This target file already exists, if you want to overwrite this add option -i, or add an -o output location');
|
||||||
|
console.log(program.outputHelp());
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (!checkIfFileExists(program.sourceFile)) {
|
||||||
|
console.error('Path of source file is not valid.');
|
||||||
|
console.log(program.outputHelp());
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (program.outputFile && !checkIfPathToFileIsValid(program.outputFile)) {
|
||||||
|
console.error('Directory path of output file is not valid.');
|
||||||
|
console.log(program.outputHelp());
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
syncFileWithSource(program.targetFile, getOutputFileLocationIfExistsElseTargetFileLocation(program.targetFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates chunk lists for both the source and the target files (for example en.json5 and nl.json5 respectively)
|
||||||
|
* > Creates output chunks by comparing the source chunk with corresponding target chunk (based on key of translation)
|
||||||
|
* > Writes the output chunks to a new valid lang.json5 file, either replacing the target file (-i in-place)
|
||||||
|
* or sending it to an output file specified by the user
|
||||||
|
* @param pathToTargetFile Valid path to target file to generate target chunks from
|
||||||
|
* @param pathToOutputFile Valid path to output file to write output chunks to
|
||||||
|
*/
|
||||||
|
function syncFileWithSource(pathToTargetFile, pathToOutputFile) {
|
||||||
|
const progressBar = new _cliProgress.SingleBar({}, _cliProgress.Presets.shades_classic);
|
||||||
|
progressBar.start(100, 0);
|
||||||
|
|
||||||
|
const sourceLines = [];
|
||||||
|
const targetLines = [];
|
||||||
|
const existingTargetFile = readFileIfExists(pathToTargetFile);
|
||||||
|
existingTargetFile.toString().split("\n").forEach((function (line) {
|
||||||
|
targetLines.push(line.trim());
|
||||||
|
}));
|
||||||
|
progressBar.update(10);
|
||||||
|
const sourceFile = readFileIfExists(program.sourceFile);
|
||||||
|
sourceFile.toString().split("\n").forEach((function (line) {
|
||||||
|
sourceLines.push(line.trim());
|
||||||
|
}));
|
||||||
|
progressBar.update(20);
|
||||||
|
const sourceChunks = createChunks(sourceLines, progressBar, false);
|
||||||
|
const targetChunks = createChunks(targetLines, progressBar, true);
|
||||||
|
|
||||||
|
const outputChunks = compareChunksAndCreateOutput(sourceChunks, targetChunks, progressBar);
|
||||||
|
|
||||||
|
const file = fs.createWriteStream(pathToOutputFile);
|
||||||
|
file.on('error', function (err) {
|
||||||
|
console.error('Something went wrong writing to output file at: ' + pathToOutputFile + err)
|
||||||
|
});
|
||||||
|
file.on('open', function() {
|
||||||
|
file.write("{\n");
|
||||||
|
outputChunks.forEach(function (chunk) {
|
||||||
|
progressBar.increment();
|
||||||
|
chunk.split("\n").forEach(function (line) {
|
||||||
|
file.write(" " + line + "\n");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
file.write("\n}");
|
||||||
|
file.end();
|
||||||
|
});
|
||||||
|
file.on('finish', function() {
|
||||||
|
const osName = process.platform;
|
||||||
|
if (osName.startsWith("win")) {
|
||||||
|
replaceLineEndingsToCRLF(pathToOutputFile);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
progressBar.update(100);
|
||||||
|
progressBar.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For each of the source chunks:
|
||||||
|
* - Determine if it's a new key-value => Add it to output, with source comments, source key-value commented, a message indicating it's new and the source-key value uncommented
|
||||||
|
* - If it's not new, compare it with the corresponding target chunk and log the differences, see createNewChunkComparingSourceAndTarget
|
||||||
|
* @param sourceChunks All the source chunks, split per key-value pair group
|
||||||
|
* @param targetChunks All the target chunks, split per key-value pair group
|
||||||
|
* @param progressBar The progressbar for the CLI
|
||||||
|
* @return {Array} All the output chunks, split per key-value pair group
|
||||||
|
*/
|
||||||
|
function compareChunksAndCreateOutput(sourceChunks, targetChunks, progressBar) {
|
||||||
|
const outputChunks = [];
|
||||||
|
sourceChunks.map((sourceChunk) => {
|
||||||
|
progressBar.increment();
|
||||||
|
if (sourceChunk.trim().length !== 0) {
|
||||||
|
let newChunk = [];
|
||||||
|
const sourceList = sourceChunk.split("\n");
|
||||||
|
const keyValueSource = sourceList[sourceList.length - 1];
|
||||||
|
const keySource = getSubStringBeforeLastString(keyValueSource, ":");
|
||||||
|
const commentSource = getSubStringBeforeLastString(sourceChunk, keyValueSource);
|
||||||
|
|
||||||
|
const correspondingTargetChunk = targetChunks.find((targetChunk) => {
|
||||||
|
return targetChunk.includes(keySource);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create new chunk with: the source comments, the commented source key-value, the todos and either the old target key-value pair or if it's a new pair, the source key-value pair
|
||||||
|
newChunk.push(removeWhiteLines(commentSource));
|
||||||
|
newChunk.push("// " + keyValueSource);
|
||||||
|
if (correspondingTargetChunk === undefined) {
|
||||||
|
newChunk.push(NEW_MESSAGE_TODO);
|
||||||
|
newChunk.push(keyValueSource);
|
||||||
|
} else {
|
||||||
|
createNewChunkComparingSourceAndTarget(correspondingTargetChunk, sourceChunk, commentSource, keyValueSource, newChunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
outputChunks.push(newChunk.filter(Boolean).join("\n"));
|
||||||
|
} else {
|
||||||
|
outputChunks.push(sourceChunk);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return outputChunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a corresponding target chunk is found:
|
||||||
|
* - If old key value is not found in comments > Assumed it is new key
|
||||||
|
* - If the target comments do not contain the source comments (because they have changed since last time) => Add comments changed message
|
||||||
|
* - If the key-value in the target comments is not the same as the source key-value (because it changes since last time) => Add message changed message
|
||||||
|
* - Add the old todos if they haven't been added already
|
||||||
|
* - End with the original target key-value
|
||||||
|
*/
|
||||||
|
function createNewChunkComparingSourceAndTarget(correspondingTargetChunk, sourceChunk, commentSource, keyValueSource, newChunk) {
|
||||||
|
let commentsOfSourceHaveChanged = false;
|
||||||
|
let messageOfSourceHasChanged = false;
|
||||||
|
|
||||||
|
const targetList = correspondingTargetChunk.split("\n");
|
||||||
|
const oldKeyValueInTargetComments = getSubStringWithRegex(correspondingTargetChunk, "\\s*\\/\\/\\s*\".*");
|
||||||
|
const keyValueTarget = targetList[targetList.length - 1];
|
||||||
|
|
||||||
|
if (oldKeyValueInTargetComments != null) {
|
||||||
|
const oldKeyValueUncommented = getSubStringWithRegex(oldKeyValueInTargetComments[0], "\".*")[0];
|
||||||
|
|
||||||
|
if (!(_.isEmpty(correspondingTargetChunk) && _.isEmpty(commentSource)) && !removeWhiteLines(correspondingTargetChunk).includes(removeWhiteLines(commentSource.trim()))) {
|
||||||
|
commentsOfSourceHaveChanged = true;
|
||||||
|
newChunk.push(COMMENTS_CHANGED_TODO);
|
||||||
|
}
|
||||||
|
const parsedOldKey = JSON5.stringify("{" + oldKeyValueUncommented + "}");
|
||||||
|
const parsedSourceKey = JSON5.stringify("{" + keyValueSource + "}");
|
||||||
|
if (!_.isEqual(parsedOldKey, parsedSourceKey)) {
|
||||||
|
messageOfSourceHasChanged = true;
|
||||||
|
newChunk.push(MESSAGE_CHANGED_TODO);
|
||||||
|
}
|
||||||
|
addOldTodosIfNeeded(targetList, newChunk, commentsOfSourceHaveChanged, messageOfSourceHasChanged);
|
||||||
|
}
|
||||||
|
newChunk.push(keyValueTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds old todos found in target comments if they've not been added already
|
||||||
|
function addOldTodosIfNeeded(targetList, newChunk, commentsOfSourceHaveChanged, messageOfSourceHasChanged) {
|
||||||
|
targetList.map((targetLine) => {
|
||||||
|
const foundTODO = getSubStringWithRegex(targetLine, "\\s*//\\s*TODO.*");
|
||||||
|
if (foundTODO != null) {
|
||||||
|
const todo = foundTODO[0];
|
||||||
|
if (!((todo.includes(COMMENTS_CHANGED_TODO) && commentsOfSourceHaveChanged)
|
||||||
|
|| (todo.includes(MESSAGE_CHANGED_TODO) && messageOfSourceHasChanged))) {
|
||||||
|
newChunk.push(todo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates chunks from an array of lines, each chunk contains either an empty line or a grouping of comments with their corresponding key-value pair
|
||||||
|
* @param lines Array of lines, to be grouped into chunks
|
||||||
|
* @param progressBar Progressbar of the CLI
|
||||||
|
* @return {Array} Array of chunks, grouped by key-value and their corresponding comments or an empty line
|
||||||
|
*/
|
||||||
|
function createChunks(lines, progressBar, creatingTarget) {
|
||||||
|
const chunks = [];
|
||||||
|
let nextChunk = [];
|
||||||
|
let onMultiLineComment = false;
|
||||||
|
lines.map((line) => {
|
||||||
|
progressBar.increment();
|
||||||
|
if (line.length === 0) {
|
||||||
|
chunks.push(line);
|
||||||
|
}
|
||||||
|
if (isOneLineCommentLine(line)) {
|
||||||
|
nextChunk.push(line);
|
||||||
|
}
|
||||||
|
if (onMultiLineComment) {
|
||||||
|
nextChunk.push(line);
|
||||||
|
if (isEndOfMultiLineComment(line)) {
|
||||||
|
onMultiLineComment = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isStartOfMultiLineComment(line)) {
|
||||||
|
nextChunk.push(line);
|
||||||
|
onMultiLineComment = true;
|
||||||
|
}
|
||||||
|
if (isKeyValuePair(line)) {
|
||||||
|
nextChunk.push(line);
|
||||||
|
const newMessageLineIfExists = nextChunk.find((lineInChunk) => lineInChunk.trim().startsWith(NEW_MESSAGE_TODO));
|
||||||
|
if (newMessageLineIfExists === undefined || !creatingTarget) {
|
||||||
|
chunks.push(nextChunk.join("\n"));
|
||||||
|
}
|
||||||
|
nextChunk = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
|
|
||||||
|
function readFileIfExists(pathToFile) {
|
||||||
|
if (checkIfFileExists(pathToFile)) {
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(pathToFile, 'utf8');
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error:', e.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isOneLineCommentLine(line) {
|
||||||
|
return (line.startsWith("//"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isStartOfMultiLineComment(line) {
|
||||||
|
return (line.startsWith("/*"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEndOfMultiLineComment(line) {
|
||||||
|
return (line.endsWith("*/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function isKeyValuePair(line) {
|
||||||
|
return (line.startsWith("\""));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getSubStringWithRegex(string, regex) {
|
||||||
|
return string.match(regex);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSubStringBeforeLastString(string, char) {
|
||||||
|
const lastCharIndex = string.lastIndexOf(char);
|
||||||
|
return string.substr(0, lastCharIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getOutputFileLocationIfExistsElseTargetFileLocation(targetLocation) {
|
||||||
|
if (program.outputFile) {
|
||||||
|
return program.outputFile;
|
||||||
|
}
|
||||||
|
return targetLocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkIfPathToFileIsValid(pathToCheck) {
|
||||||
|
if (!pathToCheck.includes("/")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return checkIfFileExists(getPathOfDirectory(pathToCheck));
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkIfFileExists(pathToCheck) {
|
||||||
|
return fs.existsSync(pathToCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPathOfDirectory(pathToCheck) {
|
||||||
|
return getSubStringBeforeLastString(pathToCheck, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeWhiteLines(string) {
|
||||||
|
return string.replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces UNIX \n LF line endings to windows \r\n CRLF line endings.
|
||||||
|
* @param filePath Path to file whose line endings are being converted
|
||||||
|
*/
|
||||||
|
function replaceLineEndingsToCRLF(filePath) {
|
||||||
|
const data = readFileIfExists(filePath);
|
||||||
|
const result = data.replace(/\n/g,"\r\n");
|
||||||
|
fs.writeFileSync(filePath, result, 'utf8');
|
||||||
|
}
|
@@ -5,7 +5,7 @@ import { PaginatedList } from '../../../core/data/paginated-list';
|
|||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
|
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
|
||||||
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
|
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
|
||||||
import { FindAllOptions } from '../../../core/data/request.models';
|
import { FindListOptions } from '../../../core/data/request.models';
|
||||||
import { map, switchMap, take } from 'rxjs/operators';
|
import { map, switchMap, take } from 'rxjs/operators';
|
||||||
import { hasValue } from '../../../shared/empty.util';
|
import { hasValue } from '../../../shared/empty.util';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
@@ -35,7 +35,7 @@ export class BitstreamFormatsComponent implements OnInit {
|
|||||||
* The current pagination configuration for the page used by the FindAll method
|
* The current pagination configuration for the page used by the FindAll method
|
||||||
* Currently simply renders all bitstream formats
|
* Currently simply renders all bitstream formats
|
||||||
*/
|
*/
|
||||||
config: FindAllOptions = Object.assign(new FindAllOptions(), {
|
config: FindListOptions = Object.assign(new FindListOptions(), {
|
||||||
elementsPerPage: 20
|
elementsPerPage: 20
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -145,7 +145,7 @@ export class BitstreamFormatsComponent implements OnInit {
|
|||||||
* @param event The page change event
|
* @param event The page change event
|
||||||
*/
|
*/
|
||||||
onPageChange(event) {
|
onPageChange(event) {
|
||||||
this.config = Object.assign(new FindAllOptions(), this.config, {
|
this.config = Object.assign(new FindListOptions(), this.config, {
|
||||||
currentPage: event,
|
currentPage: event,
|
||||||
});
|
});
|
||||||
this.pageConfig.currentPage = event;
|
this.pageConfig.currentPage = event;
|
||||||
|
@@ -13,7 +13,6 @@ import { combineLatest as combineLatestObservable } from 'rxjs';
|
|||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { OnClickMenuItemModel } from '../../shared/menu/menu-item/models/onclick.model';
|
import { OnClickMenuItemModel } from '../../shared/menu/menu-item/models/onclick.model';
|
||||||
import { CreateCommunityParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component';
|
import { CreateCommunityParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-community-parent-selector/create-community-parent-selector.component';
|
||||||
import { CreateItemParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component';
|
|
||||||
import { CreateCollectionParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component';
|
import { CreateCollectionParentSelectorComponent } from '../../shared/dso-selector/modal-wrappers/create-collection-parent-selector/create-collection-parent-selector.component';
|
||||||
import { EditItemSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component';
|
import { EditItemSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-item-selector/edit-item-selector.component';
|
||||||
import { EditCommunitySelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component';
|
import { EditCommunitySelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-community-selector/edit-community-selector.component';
|
||||||
|
@@ -11,7 +11,6 @@ import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.serv
|
|||||||
import { MockRouter } from '../../shared/mocks/mock-router';
|
import { MockRouter } from '../../shared/mocks/mock-router';
|
||||||
import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
|
||||||
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
|
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
@@ -82,7 +82,8 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
const date = firstItemRD.payload.firstMetadataValue(metadataField);
|
const date = firstItemRD.payload.firstMetadataValue(metadataField);
|
||||||
if (hasValue(date)) {
|
if (hasValue(date)) {
|
||||||
const dateObj = new Date(date);
|
const dateObj = new Date(date);
|
||||||
lowerLimit = dateObj.getFullYear();
|
// TODO: it appears that getFullYear (based on local time) is sometimes unreliable. Switching to UTC.
|
||||||
|
lowerLimit = dateObj.getUTCFullYear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const options = [];
|
const options = [];
|
||||||
|
@@ -1,29 +1,23 @@
|
|||||||
import { CollectionItemMapperComponent } from './collection-item-mapper.component';
|
import { CollectionItemMapperComponent } from './collection-item-mapper.component';
|
||||||
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
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 { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
import { SearchFormComponent } from '../../shared/search-form/search-form.component';
|
import { SearchFormComponent } from '../../shared/search-form/search-form.component';
|
||||||
import { SearchPageModule } from '../../+search-page/search-page.module';
|
|
||||||
import { ObjectCollectionComponent } from '../../shared/object-collection/object-collection.component';
|
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
|
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
|
||||||
import { RouterStub } from '../../shared/testing/router-stub';
|
import { RouterStub } from '../../shared/testing/router-stub';
|
||||||
import { SearchConfigurationService } from '../../+search-page/search-service/search-configuration.service';
|
|
||||||
import { SearchService } from '../../+search-page/search-service/search.service';
|
|
||||||
import { SearchServiceStub } from '../../shared/testing/search-service-stub';
|
import { SearchServiceStub } from '../../shared/testing/search-service-stub';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { NotificationsServiceStub } from '../../shared/testing/notifications-service-stub';
|
import { NotificationsServiceStub } from '../../shared/testing/notifications-service-stub';
|
||||||
import { ItemDataService } from '../../core/data/item-data.service';
|
import { ItemDataService } from '../../core/data/item-data.service';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model';
|
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
import { EventEmitter, NgModule } from '@angular/core';
|
import { EventEmitter } from '@angular/core';
|
||||||
import { HostWindowService } from '../../shared/host-window.service';
|
import { HostWindowService } from '../../shared/host-window.service';
|
||||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service-stub';
|
import { HostWindowServiceStub } from '../../shared/testing/host-window-service-stub';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
@@ -36,13 +30,14 @@ 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 { Observable } from 'rxjs/internal/Observable';
|
|
||||||
import { of as observableOf, of } from 'rxjs/internal/observable/of';
|
import { of as observableOf, of } from 'rxjs/internal/observable/of';
|
||||||
import { RestResponse } from '../../core/cache/response.models';
|
import { RestResponse } from '../../core/cache/response.models';
|
||||||
import { SearchFixedFilterService } from '../../+search-page/search-filters/search-filter/search-fixed-filter.service';
|
|
||||||
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';
|
||||||
|
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
||||||
|
import { SearchService } from '../../core/shared/search/search.service';
|
||||||
|
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
||||||
|
|
||||||
describe('CollectionItemMapperComponent', () => {
|
describe('CollectionItemMapperComponent', () => {
|
||||||
let comp: CollectionItemMapperComponent;
|
let comp: CollectionItemMapperComponent;
|
||||||
@@ -135,7 +130,6 @@ describe('CollectionItemMapperComponent', () => {
|
|||||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
|
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
|
||||||
{ provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() },
|
{ provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() },
|
||||||
{ provide: RouteService, useValue: routeServiceStub },
|
{ provide: RouteService, useValue: routeServiceStub },
|
||||||
{ provide: SearchFixedFilterService, useValue: fixedFilterServiceStub }
|
|
||||||
]
|
]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
@@ -5,12 +5,9 @@ import { fadeIn, fadeInOut } from '../../shared/animations/fade';
|
|||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
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 { SearchConfigurationService } from '../../+search-page/search-service/search-configuration.service';
|
|
||||||
import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model';
|
|
||||||
import { PaginatedList } from '../../core/data/paginated-list';
|
import { PaginatedList } from '../../core/data/paginated-list';
|
||||||
import { map, startWith, switchMap, take, tap } from 'rxjs/operators';
|
import { map, startWith, switchMap, take } from 'rxjs/operators';
|
||||||
import { getRemoteDataPayload, getSucceededRemoteData, toDSpaceObjectListRD } from '../../core/shared/operators';
|
import { getRemoteDataPayload, getSucceededRemoteData, toDSpaceObjectListRD } from '../../core/shared/operators';
|
||||||
import { SearchService } from '../../+search-page/search-service/search.service';
|
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model';
|
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.model';
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
@@ -22,6 +19,9 @@ import { isNotEmpty } from '../../shared/empty.util';
|
|||||||
import { RestResponse } from '../../core/cache/response.models';
|
import { RestResponse } from '../../core/cache/response.models';
|
||||||
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
||||||
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 { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
||||||
|
import { PaginatedSearchOptions } from '../../shared/search/paginated-search-options.model';
|
||||||
|
import { SearchService } from '../../core/shared/search/search.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-collection-item-mapper',
|
selector: 'ds-collection-item-mapper',
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
*ngVar="(collectionRD$ | async) as collectionRD">
|
*ngVar="(collectionRD$ | async) as collectionRD">
|
||||||
<div *ngIf="collectionRD?.hasSucceeded" @fadeInOut>
|
<div *ngIf="collectionRD?.hasSucceeded" @fadeInOut>
|
||||||
<div *ngIf="collectionRD?.payload as collection">
|
<div *ngIf="collectionRD?.payload as collection">
|
||||||
|
<ds-view-tracker [object]="collection"></ds-view-tracker>
|
||||||
<header class="comcol-header border-bottom mb-4 pb-4">
|
<header class="comcol-header border-bottom mb-4 pb-4">
|
||||||
<!-- Collection logo -->
|
<!-- Collection logo -->
|
||||||
<ds-comcol-page-logo *ngIf="logoRD$"
|
<ds-comcol-page-logo *ngIf="logoRD$"
|
||||||
|
@@ -2,8 +2,8 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
|||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { BehaviorSubject, of as observableOf, Observable, Subject } from 'rxjs';
|
import { BehaviorSubject, of as observableOf, Observable, Subject } from 'rxjs';
|
||||||
import { filter, flatMap, map, startWith, switchMap, take, tap } from 'rxjs/operators';
|
import { filter, flatMap, map, startWith, switchMap, take, tap } from 'rxjs/operators';
|
||||||
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
||||||
import { SearchService } from '../+search-page/search-service/search.service';
|
import { SearchService } from '../core/shared/search/search.service';
|
||||||
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 { PaginatedList } from '../core/data/paginated-list';
|
import { PaginatedList } from '../core/data/paginated-list';
|
||||||
|
@@ -9,15 +9,16 @@ import { CreateCollectionPageComponent } from './create-collection-page/create-c
|
|||||||
import { CollectionFormComponent } from './collection-form/collection-form.component';
|
import { CollectionFormComponent } from './collection-form/collection-form.component';
|
||||||
import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component';
|
import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component';
|
||||||
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
|
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
|
||||||
import { SearchService } from '../+search-page/search-service/search.service';
|
|
||||||
import { CollectionItemMapperComponent } from './collection-item-mapper/collection-item-mapper.component';
|
import { CollectionItemMapperComponent } from './collection-item-mapper/collection-item-mapper.component';
|
||||||
import { SearchFixedFilterService } from '../+search-page/search-filters/search-filter/search-fixed-filter.service';
|
import { SearchService } from '../core/shared/search/search.service';
|
||||||
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
CollectionPageRoutingModule
|
CollectionPageRoutingModule,
|
||||||
|
StatisticsModule.forRoot()
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
CollectionPageComponent,
|
CollectionPageComponent,
|
||||||
@@ -29,7 +30,6 @@ import { SearchFixedFilterService } from '../+search-page/search-filters/search-
|
|||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SearchService,
|
SearchService,
|
||||||
SearchFixedFilterService
|
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class CollectionPageModule {
|
export class CollectionPageModule {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
<div class="container" *ngVar="(communityRD$ | async) as communityRD">
|
<div class="container" *ngVar="(communityRD$ | async) as communityRD">
|
||||||
<div class="community-page" *ngIf="communityRD?.hasSucceeded" @fadeInOut>
|
<div class="community-page" *ngIf="communityRD?.hasSucceeded" @fadeInOut>
|
||||||
<div *ngIf="communityRD?.payload; let communityPayload">
|
<div *ngIf="communityRD?.payload; let communityPayload">
|
||||||
|
<ds-view-tracker [object]="communityPayload"></ds-view-tracker>
|
||||||
<header class="comcol-header border-bottom mb-4 pb-4">
|
<header class="comcol-header border-bottom mb-4 pb-4">
|
||||||
<!-- Community logo -->
|
<!-- Community logo -->
|
||||||
<ds-comcol-page-logo *ngIf="logoRD$" [logo]="(logoRD$ | async)?.payload" [alternateText]="'Community Logo'">
|
<ds-comcol-page-logo *ngIf="logoRD$" [logo]="(logoRD$ | async)?.payload" [alternateText]="'Community Logo'">
|
||||||
|
@@ -6,17 +6,19 @@ import { SharedModule } from '../shared/shared.module';
|
|||||||
import { CommunityPageComponent } from './community-page.component';
|
import { CommunityPageComponent } from './community-page.component';
|
||||||
import { CommunityPageSubCollectionListComponent } from './sub-collection-list/community-page-sub-collection-list.component';
|
import { CommunityPageSubCollectionListComponent } from './sub-collection-list/community-page-sub-collection-list.component';
|
||||||
import { CommunityPageRoutingModule } from './community-page-routing.module';
|
import { CommunityPageRoutingModule } from './community-page-routing.module';
|
||||||
import {CommunityPageSubCommunityListComponent} from './sub-community-list/community-page-sub-community-list.component';
|
import { CommunityPageSubCommunityListComponent } from './sub-community-list/community-page-sub-community-list.component';
|
||||||
import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component';
|
import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component';
|
||||||
import { CommunityFormComponent } from './community-form/community-form.component';
|
import { CommunityFormComponent } from './community-form/community-form.component';
|
||||||
import { EditCommunityPageComponent } from './edit-community-page/edit-community-page.component';
|
import { EditCommunityPageComponent } from './edit-community-page/edit-community-page.component';
|
||||||
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
|
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
|
||||||
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
CommunityPageRoutingModule
|
CommunityPageRoutingModule,
|
||||||
|
StatisticsModule.forRoot()
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
CommunityPageComponent,
|
CommunityPageComponent,
|
||||||
|
@@ -2,12 +2,25 @@ import { NgModule } from '@angular/core';
|
|||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { HomePageComponent } from './home-page.component';
|
import { HomePageComponent } from './home-page.component';
|
||||||
|
import { HomePageResolver } from './home-page.resolver';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forChild([
|
RouterModule.forChild([
|
||||||
{ path: '', component: HomePageComponent, pathMatch: 'full', data: { title: 'home.title' } }
|
{
|
||||||
|
path: '',
|
||||||
|
component: HomePageComponent,
|
||||||
|
pathMatch: 'full',
|
||||||
|
data: {title: 'home.title'},
|
||||||
|
resolve: {
|
||||||
|
site: HomePageResolver
|
||||||
|
}
|
||||||
|
}
|
||||||
])
|
])
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
HomePageResolver
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class HomePageRoutingModule { }
|
export class HomePageRoutingModule {
|
||||||
|
}
|
||||||
|
@@ -1,5 +1,8 @@
|
|||||||
<ds-home-news></ds-home-news>
|
<ds-home-news></ds-home-news>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<ng-container *ngIf="(site$ | async) as site">
|
||||||
|
<ds-view-tracker [object]="site"></ds-view-tracker>
|
||||||
|
</ng-container>
|
||||||
<ds-search-form [inPlaceSearch]="false"></ds-search-form>
|
<ds-search-form [inPlaceSearch]="false"></ds-search-form>
|
||||||
<ds-top-level-community-list></ds-top-level-community-list>
|
<ds-top-level-community-list></ds-top-level-community-list>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,9 +1,26 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { Site } from '../core/shared/site.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-home-page',
|
selector: 'ds-home-page',
|
||||||
styleUrls: ['./home-page.component.scss'],
|
styleUrls: ['./home-page.component.scss'],
|
||||||
templateUrl: './home-page.component.html'
|
templateUrl: './home-page.component.html'
|
||||||
})
|
})
|
||||||
export class HomePageComponent {
|
export class HomePageComponent implements OnInit {
|
||||||
|
|
||||||
|
site$:Observable<Site>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route:ActivatedRoute,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit():void {
|
||||||
|
this.site$ = this.route.data.pipe(
|
||||||
|
map((data) => data.site as Site),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,12 +6,14 @@ import { HomePageRoutingModule } from './home-page-routing.module';
|
|||||||
|
|
||||||
import { HomePageComponent } from './home-page.component';
|
import { HomePageComponent } from './home-page.component';
|
||||||
import { TopLevelCommunityListComponent } from './top-level-community-list/top-level-community-list.component';
|
import { TopLevelCommunityListComponent } from './top-level-community-list/top-level-community-list.component';
|
||||||
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
HomePageRoutingModule
|
HomePageRoutingModule,
|
||||||
|
StatisticsModule.forRoot()
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
HomePageComponent,
|
HomePageComponent,
|
||||||
|
25
src/app/+home-page/home-page.resolver.ts
Normal file
25
src/app/+home-page/home-page.resolver.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||||
|
import { SiteDataService } from '../core/data/site-data.service';
|
||||||
|
import { Site } from '../core/shared/site.model';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { take } from 'rxjs/operators';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The class that resolve the Site object for a route
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class HomePageResolver implements Resolve<Site> {
|
||||||
|
constructor(private siteService:SiteDataService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method for resolving a site object
|
||||||
|
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||||
|
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||||
|
* @returns Observable<Site> Emits the found Site object, or an error if something went wrong
|
||||||
|
*/
|
||||||
|
resolve(route:ActivatedRouteSnapshot, state:RouterStateSnapshot):Observable<Site> | Promise<Site> | Site {
|
||||||
|
return this.siteService.find().pipe(take(1));
|
||||||
|
}
|
||||||
|
}
|
@@ -1,15 +1,12 @@
|
|||||||
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
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 { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { ItemCollectionMapperComponent } from './item-collection-mapper.component';
|
import { ItemCollectionMapperComponent } from './item-collection-mapper.component';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { SearchConfigurationService } from '../../../+search-page/search-service/search-configuration.service';
|
|
||||||
import { SearchService } from '../../../+search-page/search-service/search.service';
|
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model';
|
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
||||||
import { RouterStub } from '../../../shared/testing/router-stub';
|
import { RouterStub } from '../../../shared/testing/router-stub';
|
||||||
@@ -19,7 +16,6 @@ import { SearchServiceStub } from '../../../shared/testing/search-service-stub';
|
|||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { SharedModule } from '../../../shared/shared.module';
|
|
||||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
|
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
|
||||||
import { HostWindowService } from '../../../shared/host-window.service';
|
import { HostWindowService } from '../../../shared/host-window.service';
|
||||||
@@ -28,7 +24,6 @@ import { By } from '@angular/platform-browser';
|
|||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
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 { Observable } from 'rxjs/internal/Observable';
|
|
||||||
import { of } from 'rxjs/internal/observable/of';
|
import { of } from 'rxjs/internal/observable/of';
|
||||||
import { RestResponse } from '../../../core/cache/response.models';
|
import { RestResponse } from '../../../core/cache/response.models';
|
||||||
import { CollectionSelectComponent } from '../../../shared/object-select/collection-select/collection-select.component';
|
import { CollectionSelectComponent } from '../../../shared/object-select/collection-select/collection-select.component';
|
||||||
@@ -39,6 +34,9 @@ import { SearchFormComponent } from '../../../shared/search-form/search-form.com
|
|||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
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';
|
||||||
|
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
||||||
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
|
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
||||||
|
|
||||||
describe('ItemCollectionMapperComponent', () => {
|
describe('ItemCollectionMapperComponent', () => {
|
||||||
let comp: ItemCollectionMapperComponent;
|
let comp: ItemCollectionMapperComponent;
|
||||||
|
@@ -2,15 +2,12 @@ import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
|||||||
|
|
||||||
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core';
|
||||||
import { fadeIn, fadeInOut } from '../../../shared/animations/fade';
|
import { fadeIn, fadeInOut } from '../../../shared/animations/fade';
|
||||||
import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model';
|
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { getRemoteDataPayload, getSucceededRemoteData, toDSpaceObjectListRD } from '../../../core/shared/operators';
|
import { getRemoteDataPayload, getSucceededRemoteData, toDSpaceObjectListRD } from '../../../core/shared/operators';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { SearchService } from '../../../+search-page/search-service/search.service';
|
|
||||||
import { SearchConfigurationService } from '../../../+search-page/search-service/search-configuration.service';
|
|
||||||
import { map, startWith, switchMap, take } from 'rxjs/operators';
|
import { map, startWith, switchMap, take } from 'rxjs/operators';
|
||||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
@@ -19,6 +16,9 @@ import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model'
|
|||||||
import { isNotEmpty } from '../../../shared/empty.util';
|
import { isNotEmpty } from '../../../shared/empty.util';
|
||||||
import { RestResponse } from '../../../core/cache/response.models';
|
import { RestResponse } from '../../../core/cache/response.models';
|
||||||
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
||||||
|
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
||||||
|
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
||||||
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-collection-mapper',
|
selector: 'ds-item-collection-mapper',
|
||||||
|
@@ -9,7 +9,6 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
import { ItemMoveComponent } from './item-move.component';
|
import { ItemMoveComponent } from './item-move.component';
|
||||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
|
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
import { SearchService } from '../../../+search-page/search-service/search.service';
|
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { ItemDataService } from '../../../core/data/item-data.service';
|
import { ItemDataService } from '../../../core/data/item-data.service';
|
||||||
@@ -18,6 +17,7 @@ import { PaginatedList } from '../../../core/data/paginated-list';
|
|||||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
import { RestResponse } from '../../../core/cache/response.models';
|
import { RestResponse } from '../../../core/cache/response.models';
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
|
|
||||||
describe('ItemMoveComponent', () => {
|
describe('ItemMoveComponent', () => {
|
||||||
let comp: ItemMoveComponent;
|
let comp: ItemMoveComponent;
|
||||||
|
@@ -1,12 +1,9 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { SearchService } from '../../../+search-page/search-service/search.service';
|
|
||||||
import { first, map } from 'rxjs/operators';
|
import { first, map } from 'rxjs/operators';
|
||||||
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
||||||
import { SearchOptions } from '../../../+search-page/search-options.model';
|
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
import { SearchResult } from '../../../+search-page/search-result.model';
|
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
@@ -17,9 +14,10 @@ import { getItemEditPath } from '../../item-page-routing.module';
|
|||||||
import { Observable, of as observableOf } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { RestResponse } from '../../../core/cache/response.models';
|
import { RestResponse } from '../../../core/cache/response.models';
|
||||||
import { Collection } from '../../../core/shared/collection.model';
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
import { tap } from 'rxjs/internal/operators/tap';
|
|
||||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model';
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
|
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
||||||
|
import { SearchResult } from '../../../shared/search/search-result.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-move',
|
selector: 'ds-item-move',
|
||||||
|
@@ -97,7 +97,7 @@ describe('EditRelationshipListComponent', () => {
|
|||||||
|
|
||||||
relationshipService = jasmine.createSpyObj('relationshipService',
|
relationshipService = jasmine.createSpyObj('relationshipService',
|
||||||
{
|
{
|
||||||
getRelatedItemsByLabel: observableOf([author1, author2]),
|
getRelatedItemsByLabel: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [author1, author2]))),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -4,8 +4,10 @@ import { Observable } from 'rxjs/internal/Observable';
|
|||||||
import { FieldUpdate, FieldUpdates } from '../../../../core/data/object-updates/object-updates.reducer';
|
import { FieldUpdate, FieldUpdates } from '../../../../core/data/object-updates/object-updates.reducer';
|
||||||
import { RelationshipService } from '../../../../core/data/relationship.service';
|
import { RelationshipService } from '../../../../core/data/relationship.service';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
import { hasValue } from '../../../../shared/empty.util';
|
import { hasValue } from '../../../../shared/empty.util';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-edit-relationship-list',
|
selector: 'ds-edit-relationship-list',
|
||||||
@@ -63,7 +65,7 @@ export class EditRelationshipListComponent implements OnInit, OnChanges {
|
|||||||
* Transform the item's relationships of a specific type into related items
|
* Transform the item's relationships of a specific type into related items
|
||||||
* @param label The relationship type's label
|
* @param label The relationship type's label
|
||||||
*/
|
*/
|
||||||
public getRelatedItemsByLabel(label: string): Observable<Item[]> {
|
public getRelatedItemsByLabel(label: string): Observable<RemoteData<PaginatedList<Item>>> {
|
||||||
return this.relationshipService.getRelatedItemsByLabel(this.item, label);
|
return this.relationshipService.getRelatedItemsByLabel(this.item, label);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +75,7 @@ export class EditRelationshipListComponent implements OnInit, OnChanges {
|
|||||||
*/
|
*/
|
||||||
public getUpdatesByLabel(label: string): Observable<FieldUpdates> {
|
public getUpdatesByLabel(label: string): Observable<FieldUpdates> {
|
||||||
return this.getRelatedItemsByLabel(label).pipe(
|
return this.getRelatedItemsByLabel(label).pipe(
|
||||||
switchMap((items: Item[]) => this.objectUpdatesService.getFieldUpdatesExclusive(this.url, items))
|
switchMap((itemsRD) => this.objectUpdatesService.getFieldUpdatesExclusive(this.url, itemsRD.payload.page))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<div class="row" *ngIf="item">
|
<div class="row" *ngIf="item">
|
||||||
<div class="col-10 relationship">
|
<div class="col-10 relationship">
|
||||||
<ds-item-type-switcher [object]="item" [viewMode]="viewMode"></ds-item-type-switcher>
|
<ds-listable-object-component-loader [object]="item" [viewMode]="viewMode"></ds-listable-object-component-loader>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-2">
|
<div class="col-2">
|
||||||
<div class="btn-group relationship-action-buttons">
|
<div class="btn-group relationship-action-buttons">
|
||||||
|
@@ -5,7 +5,6 @@ import { ObjectUpdatesService } from '../../../../core/data/object-updates/objec
|
|||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { EditRelationshipComponent } from './edit-relationship.component';
|
import { EditRelationshipComponent } from './edit-relationship.component';
|
||||||
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
||||||
import { ResourceType } from '../../../../core/shared/resource-type';
|
|
||||||
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
@@ -4,7 +4,7 @@ import { cloneDeep } from 'lodash';
|
|||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
|
||||||
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
|
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
|
||||||
import { ItemViewMode } from '../../../../shared/items/item-type-decorator';
|
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
// tslint:disable-next-line:component-selector
|
// tslint:disable-next-line:component-selector
|
||||||
@@ -31,7 +31,7 @@ export class EditRelationshipComponent implements OnChanges {
|
|||||||
/**
|
/**
|
||||||
* The view-mode we're currently on
|
* The view-mode we're currently on
|
||||||
*/
|
*/
|
||||||
viewMode = ItemViewMode.Element;
|
viewMode = ViewMode.ListElement;
|
||||||
|
|
||||||
constructor(private objectUpdatesService: ObjectUpdatesService) {
|
constructor(private objectUpdatesService: ObjectUpdatesService) {
|
||||||
}
|
}
|
||||||
|
@@ -156,7 +156,9 @@ describe('ItemRelationshipsComponent', () => {
|
|||||||
getRelatedItemsByLabel: observableOf([author1, author2]),
|
getRelatedItemsByLabel: observableOf([author1, author2]),
|
||||||
getItemRelationshipsArray: observableOf(relationships),
|
getItemRelationshipsArray: observableOf(relationships),
|
||||||
deleteRelationship: observableOf(new RestResponse(true, 200, 'OK')),
|
deleteRelationship: observableOf(new RestResponse(true, 200, 'OK')),
|
||||||
getItemResolvedRelatedItemsAndRelationships: observableCombineLatest(observableOf([author1, author2]), observableOf([item, item]), observableOf(relationships))
|
getItemResolvedRelatedItemsAndRelationships: observableCombineLatest(observableOf([author1, author2]), observableOf([item, item]), observableOf(relationships)),
|
||||||
|
getRelationshipsByRelatedItemIds: observableOf(relationships),
|
||||||
|
getRelationshipTypeLabelsByItem: observableOf([relationshipType.leftwardType])
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -2,8 +2,8 @@ import { ChangeDetectorRef, Component, Inject, OnDestroy } from '@angular/core';
|
|||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
|
import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { filter, map, switchMap, take, tap } from 'rxjs/operators';
|
import { filter, flatMap, map, switchMap, take, tap } from 'rxjs/operators';
|
||||||
import { combineLatest as observableCombineLatest, zip as observableZip } from 'rxjs';
|
import { zip as observableZip } from 'rxjs';
|
||||||
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';
|
||||||
import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
|
import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
|
||||||
@@ -21,7 +21,6 @@ import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
|||||||
import { getSucceededRemoteData } from '../../../core/shared/operators';
|
import { getSucceededRemoteData } from '../../../core/shared/operators';
|
||||||
import { RequestService } from '../../../core/data/request.service';
|
import { RequestService } from '../../../core/data/request.service';
|
||||||
import { Subscription } from 'rxjs/internal/Subscription';
|
import { Subscription } from 'rxjs/internal/Subscription';
|
||||||
import { getRelationsByRelatedItemIds } from '../../simple/item-types/shared/item-relationships-utils';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-relationships',
|
selector: 'ds-item-relationships',
|
||||||
@@ -65,7 +64,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
this.relationLabels$ = this.relationshipService.getItemRelationshipLabels(this.item);
|
this.relationLabels$ = this.relationshipService.getRelationshipTypeLabelsByItem(this.item);
|
||||||
this.initializeItemUpdate();
|
this.initializeItemUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,8 +112,9 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
|
|||||||
);
|
);
|
||||||
// Get all the relationships that should be removed
|
// Get all the relationships that should be removed
|
||||||
const removedRelationships$ = removedItemIds$.pipe(
|
const removedRelationships$ = removedItemIds$.pipe(
|
||||||
getRelationsByRelatedItemIds(this.item, this.relationshipService)
|
flatMap((uuids) => this.relationshipService.getRelationshipsByRelatedItemIds(this.item, uuids))
|
||||||
);
|
);
|
||||||
|
// const removedRelationships$ = removedItemIds$.pipe(flatMap((uuids: string[]) => this.relationshipService.getRelationshipsByRelatedItemIds(this.item, uuids)));
|
||||||
// Request a delete for every relationship found in the observable created above
|
// Request a delete for every relationship found in the observable created above
|
||||||
removedRelationships$.pipe(
|
removedRelationships$.pipe(
|
||||||
take(1),
|
take(1),
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
<div class="container" *ngVar="(itemRD$ | async) as itemRD">
|
<div class="container" *ngVar="(itemRD$ | async) as itemRD">
|
||||||
<div class="item-page" *ngIf="itemRD?.hasSucceeded" @fadeInOut>
|
<div class="item-page" *ngIf="itemRD?.hasSucceeded" @fadeInOut>
|
||||||
<div *ngIf="itemRD?.payload as item">
|
<div *ngIf="itemRD?.payload as item">
|
||||||
|
<ds-view-tracker [object]="item"></ds-view-tracker>
|
||||||
<ds-item-page-title-field [item]="item"></ds-item-page-title-field>
|
<ds-item-page-title-field [item]="item"></ds-item-page-title-field>
|
||||||
<div class="simple-view-link my-3">
|
<div class="simple-view-link my-3">
|
||||||
<a class="btn btn-outline-primary" [routerLink]="['/items/' + item.id]">
|
<a class="btn btn-outline-primary" [routerLink]="['/items/' + item.id]">
|
||||||
|
@@ -23,7 +23,7 @@ import {
|
|||||||
} from '../../shared/testing/utils';
|
} from '../../shared/testing/utils';
|
||||||
|
|
||||||
const mockItem: Item = Object.assign(new Item(), {
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
||||||
metadata: {
|
metadata: {
|
||||||
'dc.title': [
|
'dc.title': [
|
||||||
{
|
{
|
||||||
|
@@ -26,6 +26,9 @@ import { MetadataRepresentationListComponent } from './simple/metadata-represent
|
|||||||
import { RelatedEntitiesSearchComponent } from './simple/related-entities/related-entities-search/related-entities-search.component';
|
import { RelatedEntitiesSearchComponent } from './simple/related-entities/related-entities-search/related-entities-search.component';
|
||||||
import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.component';
|
import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.component';
|
||||||
import { MetadataFieldWrapperComponent } from './field-components/metadata-field-wrapper/metadata-field-wrapper.component';
|
import { MetadataFieldWrapperComponent } from './field-components/metadata-field-wrapper/metadata-field-wrapper.component';
|
||||||
|
import { TabbedRelatedEntitiesSearchComponent } from './simple/related-entities/tabbed-related-entities-search/tabbed-related-entities-search.component';
|
||||||
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
|
import { AbstractIncrementalListComponent } from './simple/abstract-incremental-list/abstract-incremental-list.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -33,7 +36,8 @@ import { MetadataFieldWrapperComponent } from './field-components/metadata-field
|
|||||||
SharedModule,
|
SharedModule,
|
||||||
ItemPageRoutingModule,
|
ItemPageRoutingModule,
|
||||||
EditItemPageModule,
|
EditItemPageModule,
|
||||||
SearchPageModule
|
SearchPageModule,
|
||||||
|
StatisticsModule.forRoot()
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
ItemPageComponent,
|
ItemPageComponent,
|
||||||
@@ -53,7 +57,9 @@ import { MetadataFieldWrapperComponent } from './field-components/metadata-field
|
|||||||
ItemComponent,
|
ItemComponent,
|
||||||
GenericItemPageFieldComponent,
|
GenericItemPageFieldComponent,
|
||||||
MetadataRepresentationListComponent,
|
MetadataRepresentationListComponent,
|
||||||
RelatedEntitiesSearchComponent
|
RelatedEntitiesSearchComponent,
|
||||||
|
TabbedRelatedEntitiesSearchComponent,
|
||||||
|
AbstractIncrementalListComponent
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ItemComponent,
|
ItemComponent,
|
||||||
@@ -63,7 +69,8 @@ import { MetadataFieldWrapperComponent } from './field-components/metadata-field
|
|||||||
RelatedEntitiesSearchComponent,
|
RelatedEntitiesSearchComponent,
|
||||||
RelatedItemsComponent,
|
RelatedItemsComponent,
|
||||||
MetadataRepresentationListComponent,
|
MetadataRepresentationListComponent,
|
||||||
ItemPageTitleFieldComponent
|
ItemPageTitleFieldComponent,
|
||||||
|
TabbedRelatedEntitiesSearchComponent
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
PublicationComponent
|
PublicationComponent
|
||||||
|
@@ -0,0 +1,73 @@
|
|||||||
|
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
import { Subscription } from 'rxjs/internal/Subscription';
|
||||||
|
import { hasValue, isNotEmpty } from '../../../shared/empty.util';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-abstract-incremental-list',
|
||||||
|
template: ``,
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* An abstract component for displaying an incremental list of objects
|
||||||
|
*/
|
||||||
|
export class AbstractIncrementalListComponent<T> implements OnInit, OnDestroy {
|
||||||
|
/**
|
||||||
|
* The amount to increment the list by
|
||||||
|
* Define this amount in the child component overriding this component
|
||||||
|
*/
|
||||||
|
incrementBy: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All pages of objects to display as an array
|
||||||
|
*/
|
||||||
|
objects: T[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of open subscriptions
|
||||||
|
*/
|
||||||
|
subscriptions: Subscription[];
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.objects = [];
|
||||||
|
this.subscriptions = [];
|
||||||
|
this.increase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specific page
|
||||||
|
* > Override this method to return a specific page
|
||||||
|
* @param page The page to fetch
|
||||||
|
*/
|
||||||
|
getPage(page: number): T {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Increase the amount displayed
|
||||||
|
*/
|
||||||
|
increase() {
|
||||||
|
const page = this.getPage(this.objects.length + 1);
|
||||||
|
if (hasValue(page)) {
|
||||||
|
this.objects.push(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decrease the amount displayed
|
||||||
|
*/
|
||||||
|
decrease() {
|
||||||
|
if (this.objects.length > 1) {
|
||||||
|
this.objects.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from any open subscriptions
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (isNotEmpty(this.subscriptions)) {
|
||||||
|
this.subscriptions.forEach((sub: Subscription) => {
|
||||||
|
sub.unsubscribe();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -10,7 +10,7 @@ import { ItemPageFieldComponent } from '../item-page-field.component';
|
|||||||
/**
|
/**
|
||||||
* This component can be used to represent metadata on a simple item page.
|
* This component can be used to represent metadata on a simple item page.
|
||||||
* It is the most generic way of displaying metadata values
|
* It is the most generic way of displaying metadata values
|
||||||
* It expects 4 parameters: The item, a seperator, the metadata keys and an i18n key
|
* It expects 4 parameters: The item, a separator, the metadata keys and an i18n key
|
||||||
*/
|
*/
|
||||||
export class GenericItemPageFieldComponent extends ItemPageFieldComponent {
|
export class GenericItemPageFieldComponent extends ItemPageFieldComponent {
|
||||||
|
|
||||||
|
@@ -4,12 +4,9 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
import { MockTranslateLoader } from '../../../../shared/mocks/mock-translate-loader';
|
import { MockTranslateLoader } from '../../../../shared/mocks/mock-translate-loader';
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { PageInfo } from '../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
|
||||||
import { ItemPageFieldComponent } from './item-page-field.component';
|
import { ItemPageFieldComponent } from './item-page-field.component';
|
||||||
import { MetadataValuesComponent } from '../../../field-components/metadata-values/metadata-values.component';
|
import { MetadataValuesComponent } from '../../../field-components/metadata-values/metadata-values.component';
|
||||||
import { of as observableOf } from 'rxjs';
|
|
||||||
import { MetadataMap, MetadataValue } from '../../../../core/shared/metadata.models';
|
import { MetadataMap, MetadataValue } from '../../../../core/shared/metadata.models';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
|
||||||
|
|
||||||
@@ -53,7 +50,7 @@ describe('ItemPageFieldComponent', () => {
|
|||||||
|
|
||||||
export function mockItemWithMetadataFieldAndValue(field: string, value: string): Item {
|
export function mockItemWithMetadataFieldAndValue(field: string, value: string): Item {
|
||||||
const item = Object.assign(new Item(), {
|
const item = Object.assign(new Item(), {
|
||||||
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
||||||
metadata: new MetadataMap()
|
metadata: new MetadataMap()
|
||||||
});
|
});
|
||||||
item.metadata[field] = [{
|
item.metadata[field] = [{
|
||||||
|
@@ -1,7 +1,8 @@
|
|||||||
<div class="container" *ngVar="(itemRD$ | async) as itemRD">
|
<div class="container" *ngVar="(itemRD$ | async) as itemRD">
|
||||||
<div class="item-page" *ngIf="itemRD?.hasSucceeded" @fadeInOut>
|
<div class="item-page" *ngIf="itemRD?.hasSucceeded" @fadeInOut>
|
||||||
<div *ngIf="itemRD?.payload as item">
|
<div *ngIf="itemRD?.payload as item">
|
||||||
<ds-item-type-switcher [object]="item" [viewMode]="viewMode"></ds-item-type-switcher>
|
<ds-view-tracker [object]="item"></ds-view-tracker>
|
||||||
|
<ds-listable-object-component-loader [object]="item" [viewMode]="viewMode"></ds-listable-object-component-loader>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ds-error *ngIf="itemRD?.hasFailed" message="{{'error.item' | translate}}"></ds-error>
|
<ds-error *ngIf="itemRD?.hasFailed" message="{{'error.item' | translate}}"></ds-error>
|
||||||
|
@@ -22,7 +22,7 @@ import {
|
|||||||
} from '../../shared/testing/utils';
|
} from '../../shared/testing/utils';
|
||||||
|
|
||||||
const mockItem: Item = Object.assign(new Item(), {
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
||||||
metadata: [],
|
metadata: [],
|
||||||
relationships: createRelationshipsObservable()
|
relationships: createRelationshipsObservable()
|
||||||
});
|
});
|
||||||
|
@@ -1,21 +1,18 @@
|
|||||||
|
import { map } from 'rxjs/operators';
|
||||||
import { mergeMap, filter, map, take, tap } from 'rxjs/operators';
|
|
||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { ItemDataService } from '../../core/data/item-data.service';
|
import { ItemDataService } from '../../core/data/item-data.service';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { Bitstream } from '../../core/shared/bitstream.model';
|
|
||||||
|
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
|
||||||
import { MetadataService } from '../../core/metadata/metadata.service';
|
import { MetadataService } from '../../core/metadata/metadata.service';
|
||||||
|
|
||||||
import { fadeInOut } from '../../shared/animations/fade';
|
import { fadeInOut } from '../../shared/animations/fade';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
|
||||||
import { redirectToPageNotFoundOn404 } from '../../core/shared/operators';
|
import { redirectToPageNotFoundOn404 } from '../../core/shared/operators';
|
||||||
import { ItemViewMode } from '../../shared/items/item-type-decorator';
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a simple item page.
|
* This component renders a simple item page.
|
||||||
@@ -44,7 +41,7 @@ export class ItemPageComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* The view-mode we're currently on
|
* The view-mode we're currently on
|
||||||
*/
|
*/
|
||||||
viewMode = ItemViewMode.Full;
|
viewMode = ViewMode.StandalonePage;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
@@ -53,6 +50,9 @@ export class ItemPageComponent implements OnInit {
|
|||||||
private metadataService: MetadataService,
|
private metadataService: MetadataService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.itemRD$ = this.route.data.pipe(
|
this.itemRD$ = this.route.data.pipe(
|
||||||
map((data) => data.item as RemoteData<Item>),
|
map((data) => data.item as RemoteData<Item>),
|
||||||
|
@@ -1,67 +1,72 @@
|
|||||||
<h2 class="item-page-title-field">
|
<h2 class="item-page-title-field">
|
||||||
{{'publication.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="item?.allMetadata(['dc.title'])"></ds-metadata-values>
|
{{'publication.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="object?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||||
</h2>
|
</h2>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xs-12 col-md-4">
|
<div class="col-xs-12 col-md-4">
|
||||||
<ds-metadata-field-wrapper>
|
<ds-metadata-field-wrapper>
|
||||||
<ds-thumbnail [thumbnail]="this.item.getThumbnail() | async"></ds-thumbnail>
|
<ds-thumbnail [thumbnail]="object.getThumbnail() | async"></ds-thumbnail>
|
||||||
</ds-metadata-field-wrapper>
|
</ds-metadata-field-wrapper>
|
||||||
<ds-item-page-file-section [item]="item"></ds-item-page-file-section>
|
<ds-item-page-file-section [item]="object"></ds-item-page-file-section>
|
||||||
<ds-item-page-date-field [item]="item"></ds-item-page-date-field>
|
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
||||||
<ds-item-page-author-field *ngIf="!(authors$ | async)" [item]="item"></ds-item-page-author-field>
|
<ds-item-page-author-field [item]="object"></ds-item-page-author-field>
|
||||||
<ds-generic-item-page-field [item]="item"
|
<ds-generic-item-page-field [item]="object"
|
||||||
[fields]="['journal.title']"
|
[fields]="['journal.title']"
|
||||||
[label]="'publication.page.journal-title'">
|
[label]="'publication.page.journal-title'">
|
||||||
</ds-generic-item-page-field>
|
</ds-generic-item-page-field>
|
||||||
<ds-generic-item-page-field [item]="item"
|
<ds-generic-item-page-field [item]="object"
|
||||||
[fields]="['journal.identifier.issn']"
|
[fields]="['journal.identifier.issn']"
|
||||||
[label]="'publication.page.journal-issn'">
|
[label]="'publication.page.journal-issn'">
|
||||||
</ds-generic-item-page-field>
|
</ds-generic-item-page-field>
|
||||||
<ds-generic-item-page-field [item]="item"
|
<ds-generic-item-page-field [item]="object"
|
||||||
[fields]="['journalvolume.identifier.name']"
|
[fields]="['journalvolume.identifier.name']"
|
||||||
[label]="'publication.page.volume-title'">
|
[label]="'publication.page.volume-title'">
|
||||||
</ds-generic-item-page-field>
|
</ds-generic-item-page-field>
|
||||||
<ds-generic-item-page-field [item]="item"
|
<ds-generic-item-page-field [item]="object"
|
||||||
[fields]="['dc.publisher']"
|
[fields]="['dc.publisher']"
|
||||||
[label]="'publication.page.publisher'">
|
[label]="'publication.page.publisher'">
|
||||||
</ds-generic-item-page-field>
|
</ds-generic-item-page-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-md-6">
|
<div class="col-xs-12 col-md-6">
|
||||||
<ds-metadata-representation-list
|
<ds-metadata-representation-list
|
||||||
[label]="'relationships.isAuthorOf' | translate"
|
[parentItem]="object"
|
||||||
[representations]="authors$ | async">
|
[itemType]="'Person'"
|
||||||
|
[metadataField]="'dc.contributor.author'"
|
||||||
|
[label]="'relationships.isAuthorOf' | translate">
|
||||||
</ds-metadata-representation-list>
|
</ds-metadata-representation-list>
|
||||||
<ds-related-items
|
<ds-related-items
|
||||||
[items]="projects$ | async"
|
[parentItem]="object"
|
||||||
|
[relationType]="'isProjectOfPublication'"
|
||||||
[label]="'relationships.isProjectOf' | translate">
|
[label]="'relationships.isProjectOf' | translate">
|
||||||
</ds-related-items>
|
</ds-related-items>
|
||||||
<ds-related-items
|
<ds-related-items
|
||||||
[items]="orgUnits$ | async"
|
[parentItem]="object"
|
||||||
|
[relationType]="'isOrgUnitOfPublication'"
|
||||||
[label]="'relationships.isOrgUnitOf' | translate">
|
[label]="'relationships.isOrgUnitOf' | translate">
|
||||||
</ds-related-items>
|
</ds-related-items>
|
||||||
<ds-related-items
|
<ds-related-items
|
||||||
[items]="journalIssues$ | async"
|
[parentItem]="object"
|
||||||
|
[relationType]="'isJournalIssueOfPublication'"
|
||||||
[label]="'relationships.isJournalIssueOf' | translate">
|
[label]="'relationships.isJournalIssueOf' | translate">
|
||||||
</ds-related-items>
|
</ds-related-items>
|
||||||
<ds-item-page-abstract-field [item]="item"></ds-item-page-abstract-field>
|
<ds-item-page-abstract-field [item]="object"></ds-item-page-abstract-field>
|
||||||
<ds-generic-item-page-field [item]="item"
|
<ds-generic-item-page-field [item]="object"
|
||||||
[fields]="['dc.description']"
|
[fields]="['dc.description']"
|
||||||
[label]="'publication.page.description'">
|
[label]="'publication.page.description'">
|
||||||
</ds-generic-item-page-field>
|
</ds-generic-item-page-field>
|
||||||
|
|
||||||
<ds-generic-item-page-field [item]="item"
|
<ds-generic-item-page-field [item]="object"
|
||||||
[fields]="['dc.subject']"
|
[fields]="['dc.subject']"
|
||||||
[separator]="','"
|
[separator]="','"
|
||||||
[label]="'item.page.subject'">
|
[label]="'item.page.subject'">
|
||||||
</ds-generic-item-page-field>
|
</ds-generic-item-page-field>
|
||||||
<ds-generic-item-page-field [item]="item"
|
<ds-generic-item-page-field [item]="object"
|
||||||
[fields]="['dc.identifier.citation']"
|
[fields]="['dc.identifier.citation']"
|
||||||
[label]="'item.page.citation'">
|
[label]="'item.page.citation'">
|
||||||
</ds-generic-item-page-field>
|
</ds-generic-item-page-field>
|
||||||
<ds-item-page-uri-field [item]="item"></ds-item-page-uri-field>
|
<ds-item-page-uri-field [item]="object"></ds-item-page-uri-field>
|
||||||
<ds-item-page-collections [item]="item"></ds-item-page-collections>
|
<ds-item-page-collections [item]="object"></ds-item-page-collections>
|
||||||
<div>
|
<div>
|
||||||
<a class="btn btn-outline-primary" [routerLink]="['/items/' + item.id + '/full']">
|
<a class="btn btn-outline-primary" [routerLink]="['/items/' + object.id + '/full']">
|
||||||
{{"item.page.link.full" | translate}}
|
{{"item.page.link.full" | translate}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -3,24 +3,21 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
|||||||
import { MockTranslateLoader } from '../../../../shared/mocks/mock-translate-loader';
|
import { MockTranslateLoader } from '../../../../shared/mocks/mock-translate-loader';
|
||||||
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
|
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
|
||||||
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
||||||
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
|
||||||
import { ItemDataService } from '../../../../core/data/item-data.service';
|
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||||
import { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
|
|
||||||
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
|
||||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
import { PageInfo } from '../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { createRelationshipsObservable } from '../shared/item.component.spec';
|
import { createRelationshipsObservable } from '../shared/item.component.spec';
|
||||||
import { PublicationComponent } from './publication.component';
|
import { PublicationComponent } from './publication.component';
|
||||||
import { of as observableOf } from 'rxjs';
|
|
||||||
import { MetadataMap } from '../../../../core/shared/metadata.models';
|
import { MetadataMap } from '../../../../core/shared/metadata.models';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
|
||||||
|
import { RelationshipService } from '../../../../core/data/relationship.service';
|
||||||
|
|
||||||
const mockItem: Item = Object.assign(new Item(), {
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
||||||
metadata: new MetadataMap(),
|
metadata: new MetadataMap(),
|
||||||
relationships: createRelationshipsObservable()
|
relationships: createRelationshipsObservable()
|
||||||
});
|
});
|
||||||
@@ -29,12 +26,6 @@ describe('PublicationComponent', () => {
|
|||||||
let comp: PublicationComponent;
|
let comp: PublicationComponent;
|
||||||
let fixture: ComponentFixture<PublicationComponent>;
|
let fixture: ComponentFixture<PublicationComponent>;
|
||||||
|
|
||||||
const searchFixedFilterServiceStub = {
|
|
||||||
/* tslint:disable:no-empty */
|
|
||||||
getQueryByRelations: () => {}
|
|
||||||
/* tslint:enable:no-empty */
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot({
|
imports: [TranslateModule.forRoot({
|
||||||
@@ -45,10 +36,9 @@ describe('PublicationComponent', () => {
|
|||||||
})],
|
})],
|
||||||
declarations: [PublicationComponent, GenericItemPageFieldComponent, TruncatePipe],
|
declarations: [PublicationComponent, GenericItemPageFieldComponent, TruncatePipe],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: ITEM, useValue: mockItem},
|
|
||||||
{provide: ItemDataService, useValue: {}},
|
{provide: ItemDataService, useValue: {}},
|
||||||
{provide: SearchFixedFilterService, useValue: searchFixedFilterServiceStub},
|
{provide: TruncatableService, useValue: {}},
|
||||||
{provide: TruncatableService, useValue: {}}
|
{provide: RelationshipService, useValue: {}}
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@@ -60,6 +50,7 @@ describe('PublicationComponent', () => {
|
|||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
fixture = TestBed.createComponent(PublicationComponent);
|
fixture = TestBed.createComponent(PublicationComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
|
comp.object = mockItem;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@@ -1,62 +1,20 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import {
|
|
||||||
DEFAULT_ITEM_TYPE, ItemViewMode,
|
|
||||||
rendersItemType
|
|
||||||
} from '../../../../shared/items/item-type-decorator';
|
|
||||||
import { ItemComponent } from '../shared/item.component';
|
import { ItemComponent } from '../shared/item.component';
|
||||||
import { MetadataRepresentation } from '../../../../core/shared/metadata-representation/metadata-representation.model';
|
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||||
import { getRelatedItemsByTypeLabel } from '../shared/item-relationships-utils';
|
import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||||
|
|
||||||
@rendersItemType('Publication', ItemViewMode.Full)
|
/**
|
||||||
@rendersItemType(DEFAULT_ITEM_TYPE, ItemViewMode.Full)
|
* Component that represents a publication Item page
|
||||||
|
*/
|
||||||
|
|
||||||
|
@listableObjectComponent('Publication', ViewMode.StandalonePage)
|
||||||
|
@listableObjectComponent(Item, ViewMode.StandalonePage)
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-publication',
|
selector: 'ds-publication',
|
||||||
styleUrls: ['./publication.component.scss'],
|
styleUrls: ['./publication.component.scss'],
|
||||||
templateUrl: './publication.component.html',
|
templateUrl: './publication.component.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class PublicationComponent extends ItemComponent implements OnInit {
|
export class PublicationComponent extends ItemComponent {
|
||||||
/**
|
|
||||||
* The authors related to this publication
|
|
||||||
*/
|
|
||||||
authors$: Observable<MetadataRepresentation[]>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The projects related to this publication
|
|
||||||
*/
|
|
||||||
projects$: Observable<Item[]>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The organisation units related to this publication
|
|
||||||
*/
|
|
||||||
orgUnits$: Observable<Item[]>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The journal issues related to this publication
|
|
||||||
*/
|
|
||||||
journalIssues$: Observable<Item[]>;
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
super.ngOnInit();
|
|
||||||
|
|
||||||
if (this.resolvedRelsAndTypes$) {
|
|
||||||
|
|
||||||
this.authors$ = this.buildRepresentations('Person', 'dc.contributor.author');
|
|
||||||
|
|
||||||
this.projects$ = this.resolvedRelsAndTypes$.pipe(
|
|
||||||
getRelatedItemsByTypeLabel(this.item.id, 'isProjectOfPublication')
|
|
||||||
);
|
|
||||||
|
|
||||||
this.orgUnits$ = this.resolvedRelsAndTypes$.pipe(
|
|
||||||
getRelatedItemsByTypeLabel(this.item.id, 'isOrgUnitOfPublication')
|
|
||||||
);
|
|
||||||
|
|
||||||
this.journalIssues$ = this.resolvedRelsAndTypes$.pipe(
|
|
||||||
getRelatedItemsByTypeLabel(this.item.id, 'isJournalIssueOfPublication')
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +1,12 @@
|
|||||||
import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
|
import { getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||||
import { MetadataRepresentation } from '../../../../core/shared/metadata-representation/metadata-representation.model';
|
import { hasValue } from '../../../../shared/empty.util';
|
||||||
import { MetadatumRepresentation } from '../../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
|
||||||
import { MetadataValue } from '../../../../core/shared/metadata.models';
|
|
||||||
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
|
|
||||||
import { hasNoValue, hasValue } from '../../../../shared/empty.util';
|
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
||||||
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
import { distinctUntilChanged, flatMap, map, switchMap } from 'rxjs/operators';
|
||||||
import { distinctUntilChanged, filter, flatMap, map, switchMap, tap } from 'rxjs/operators';
|
import { combineLatest as observableCombineLatest, zip as observableZip } from 'rxjs';
|
||||||
import { of as observableOf, zip as observableZip, combineLatest as observableCombineLatest } from 'rxjs';
|
|
||||||
import { ItemDataService } from '../../../../core/data/item-data.service';
|
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { RelationshipService } from '../../../../core/data/relationship.service';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Operator for comparing arrays using a mapping function
|
* Operator for comparing arrays using a mapping function
|
||||||
@@ -41,36 +35,6 @@ export const compareArraysUsing = <T>(mapFn: (t: T) => any) =>
|
|||||||
export const compareArraysUsingIds = <T extends { id: string }>() =>
|
export const compareArraysUsingIds = <T extends { id: string }>() =>
|
||||||
compareArraysUsing((t: T) => hasValue(t) ? t.id : undefined);
|
compareArraysUsing((t: T) => hasValue(t) ? t.id : undefined);
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch the relationships which match the type label given
|
|
||||||
* @param {string} label Type label
|
|
||||||
* @param thisId The item's id of which the relations belong to
|
|
||||||
* @returns {(source: Observable<[Relationship[] , RelationshipType[]]>) => Observable<Relationship[]>}
|
|
||||||
*/
|
|
||||||
export const filterRelationsByTypeLabel = (label: string, thisId?: string) =>
|
|
||||||
(source: Observable<[Relationship[], RelationshipType[]]>): Observable<Relationship[]> =>
|
|
||||||
source.pipe(
|
|
||||||
switchMap(([relsCurrentPage, relTypesCurrentPage]) => {
|
|
||||||
const relatedItems$ = observableZip(...relsCurrentPage.map((rel: Relationship) =>
|
|
||||||
observableCombineLatest(
|
|
||||||
rel.leftItem.pipe(getSucceededRemoteData(), getRemoteDataPayload()),
|
|
||||||
rel.rightItem.pipe(getSucceededRemoteData(), getRemoteDataPayload()))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return relatedItems$.pipe(
|
|
||||||
map((arr) => relsCurrentPage.filter((rel: Relationship, idx: number) =>
|
|
||||||
hasValue(relTypesCurrentPage[idx]) && (
|
|
||||||
(hasNoValue(thisId) && (relTypesCurrentPage[idx].leftwardType === label ||
|
|
||||||
relTypesCurrentPage[idx].rightwardType === label)) ||
|
|
||||||
(thisId === arr[idx][0].id && relTypesCurrentPage[idx].leftwardType === label) ||
|
|
||||||
(thisId === arr[idx][1].id && relTypesCurrentPage[idx].rightwardType === label)
|
|
||||||
)
|
|
||||||
))
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
distinctUntilChanged(compareArraysUsingIds())
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Operator for turning a list of relationships into a list of the relevant items
|
* Operator for turning a list of relationships into a list of the relevant items
|
||||||
* @param {string} thisId The item's id of which the relations belong to
|
* @param {string} thisId The item's id of which the relations belong to
|
||||||
@@ -100,66 +64,35 @@ export const relationsToItems = (thisId: string) =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Operator for turning a list of relationships and their relationship-types into a list of relevant items by relationship label
|
* Operator for turning a paginated list of relationships into a paginated list of the relevant items
|
||||||
* @param thisId The item's id of which the relations belong to
|
* The result is wrapped in the original RemoteData and PaginatedList
|
||||||
* @param label The label of the relationship-type to filter on
|
* @param {string} thisId The item's id of which the relations belong to
|
||||||
* @param side Filter only on one side of the relationship (for example: child-parent relationships)
|
* @returns {(source: Observable<Relationship[]>) => Observable<Item[]>}
|
||||||
*/
|
*/
|
||||||
export const getRelatedItemsByTypeLabel = (thisId: string, label: string) =>
|
export const paginatedRelationsToItems = (thisId: string) =>
|
||||||
(source: Observable<[Relationship[], RelationshipType[]]>): Observable<Item[]> =>
|
(source: Observable<RemoteData<PaginatedList<Relationship>>>): Observable<RemoteData<PaginatedList<Item>>> =>
|
||||||
source.pipe(
|
source.pipe(
|
||||||
filterRelationsByTypeLabel(label, thisId),
|
getSucceededRemoteData(),
|
||||||
relationsToItems(thisId)
|
switchMap((relationshipsRD: RemoteData<PaginatedList<Relationship>>) => {
|
||||||
);
|
return observableZip(
|
||||||
|
...relationshipsRD.payload.page.map((rel: Relationship) => observableCombineLatest(rel.leftItem, rel.rightItem))
|
||||||
/**
|
).pipe(
|
||||||
* Operator for turning a list of relationships into a list of metadatarepresentations given the original metadata
|
map((arr) =>
|
||||||
* @param parentId The id of the parent item
|
arr
|
||||||
* @param itemType The type of relation this list resembles (for creating representations)
|
.filter(([leftItem, rightItem]) => leftItem.hasSucceeded && rightItem.hasSucceeded)
|
||||||
* @param metadata The list of original Metadatum objects
|
.map(([leftItem, rightItem]) => {
|
||||||
* @param ids The ItemDataService to use for fetching Items from the Rest API
|
if (leftItem.payload.id === thisId) {
|
||||||
*/
|
return rightItem.payload;
|
||||||
export const relationsToRepresentations = (parentId: string, itemType: string, metadata: MetadataValue[], ids: ItemDataService) =>
|
} else if (rightItem.payload.id === thisId) {
|
||||||
(source: Observable<Relationship[]>): Observable<MetadataRepresentation[]> =>
|
return leftItem.payload;
|
||||||
source.pipe(
|
|
||||||
flatMap((rels: Relationship[]) =>
|
|
||||||
observableZip(
|
|
||||||
...metadata
|
|
||||||
.map((metadatum: any) => Object.assign(new MetadataValue(), metadatum))
|
|
||||||
.map((metadatum: MetadataValue) => {
|
|
||||||
if (metadatum.isVirtual) {
|
|
||||||
const matchingRels = rels.filter((rel: Relationship) => ('' + rel.id) === metadatum.virtualValue);
|
|
||||||
if (matchingRels.length > 0) {
|
|
||||||
const matchingRel = matchingRels[0];
|
|
||||||
return observableCombineLatest(matchingRel.leftItem, matchingRel.rightItem).pipe(
|
|
||||||
map(([leftItem, rightItem]) => {
|
|
||||||
if (leftItem.payload.id === parentId) {
|
|
||||||
return rightItem.payload;
|
|
||||||
} else if (rightItem.payload.id === parentId) {
|
|
||||||
return leftItem.payload;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
map((item: Item) => Object.assign(new ItemMetadataRepresentation(), item))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
return observableOf(Object.assign(new MetadatumRepresentation(itemType), metadatum));
|
.filter((item: Item) => hasValue(item))
|
||||||
}
|
),
|
||||||
})
|
distinctUntilChanged(compareArraysUsingIds()),
|
||||||
|
map((relatedItems: Item[]) =>
|
||||||
|
Object.assign(relationshipsRD, { payload: Object.assign(relationshipsRD.payload, { page: relatedItems } )})
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
})
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Operator for fetching an item's relationships, but filtered by related item IDs (essentially performing a reverse lookup)
|
|
||||||
* Only relationships where leftItem or rightItem's ID is present in the list provided will be returned
|
|
||||||
* @param item
|
|
||||||
* @param relationshipService
|
|
||||||
*/
|
|
||||||
export const getRelationsByRelatedItemIds = (item: Item, relationshipService: RelationshipService) =>
|
|
||||||
(source: Observable<string[]>): Observable<Relationship[]> =>
|
|
||||||
source.pipe(
|
|
||||||
flatMap((relatedItemIds: string[]) => relationshipService.getItemResolvedRelatedItemsAndRelationships(item).pipe(
|
|
||||||
map(([leftItems, rightItems, rels]) => rels.filter((rel: Relationship, index: number) => relatedItemIds.indexOf(leftItems[index].uuid) > -1 || relatedItemIds.indexOf(rightItems[index].uuid) > -1))
|
|
||||||
))
|
|
||||||
);
|
);
|
||||||
|
@@ -7,17 +7,13 @@ import { ItemDataService } from '../../../../core/data/item-data.service';
|
|||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
import { MockTranslateLoader } from '../../../../shared/mocks/mock-translate-loader';
|
import { MockTranslateLoader } from '../../../../shared/mocks/mock-translate-loader';
|
||||||
import { ChangeDetectionStrategy, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectionStrategy, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
|
||||||
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
||||||
import { isNotEmpty } from '../../../../shared/empty.util';
|
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||||
import { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
|
|
||||||
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
|
||||||
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
||||||
import { PageInfo } from '../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
import { ItemComponent } from './item.component';
|
import { ItemComponent } from './item.component';
|
||||||
import { of as observableOf } from 'rxjs';
|
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
import { VarDirective } from '../../../../shared/utils/var.directive';
|
import { VarDirective } from '../../../../shared/utils/var.directive';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
@@ -27,6 +23,7 @@ import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-rep
|
|||||||
import { MetadataMap, MetadataValue } from '../../../../core/shared/metadata.models';
|
import { MetadataMap, MetadataValue } from '../../../../core/shared/metadata.models';
|
||||||
import { compareArraysUsing, compareArraysUsingIds } from './item-relationships-utils';
|
import { compareArraysUsing, compareArraysUsingIds } from './item-relationships-utils';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
|
||||||
|
import { RelationshipService } from '../../../../core/data/relationship.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a generic test for an item-page-fields component using a mockItem and the type of component
|
* Create a generic test for an item-page-fields component using a mockItem and the type of component
|
||||||
@@ -40,12 +37,6 @@ export function getItemPageFieldsTest(mockItem: Item, component) {
|
|||||||
let comp: any;
|
let comp: any;
|
||||||
let fixture: ComponentFixture<any>;
|
let fixture: ComponentFixture<any>;
|
||||||
|
|
||||||
const searchFixedFilterServiceStub = {
|
|
||||||
/* tslint:disable:no-empty */
|
|
||||||
getQueryByRelations: () => {}
|
|
||||||
/* tslint:enable:no-empty */
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot({
|
imports: [TranslateModule.forRoot({
|
||||||
@@ -56,10 +47,9 @@ export function getItemPageFieldsTest(mockItem: Item, component) {
|
|||||||
})],
|
})],
|
||||||
declarations: [component, GenericItemPageFieldComponent, TruncatePipe],
|
declarations: [component, GenericItemPageFieldComponent, TruncatePipe],
|
||||||
providers: [
|
providers: [
|
||||||
{provide: ITEM, useValue: mockItem},
|
|
||||||
{provide: ItemDataService, useValue: {}},
|
{provide: ItemDataService, useValue: {}},
|
||||||
{provide: SearchFixedFilterService, useValue: searchFixedFilterServiceStub},
|
{provide: TruncatableService, useValue: {}},
|
||||||
{provide: TruncatableService, useValue: {}}
|
{provide: RelationshipService, useValue: {}}
|
||||||
],
|
],
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
@@ -71,6 +61,7 @@ export function getItemPageFieldsTest(mockItem: Item, component) {
|
|||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
fixture = TestBed.createComponent(component);
|
fixture = TestBed.createComponent(component);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
|
comp.object = mockItem;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -317,115 +308,4 @@ describe('ItemComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when calling buildRepresentations', () => {
|
|
||||||
let comp: ItemComponent;
|
|
||||||
let fixture: ComponentFixture<ItemComponent>;
|
|
||||||
|
|
||||||
const metadataField = 'dc.contributor.author';
|
|
||||||
const relatedItem = Object.assign(new Item(), {
|
|
||||||
id: '2',
|
|
||||||
metadata: Object.assign(new MetadataMap(), {
|
|
||||||
'dc.title': [
|
|
||||||
{
|
|
||||||
language: 'en_US',
|
|
||||||
value: 'related item'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
});
|
|
||||||
const mockItem = Object.assign(new Item(), {
|
|
||||||
id: '1',
|
|
||||||
uuid: '1',
|
|
||||||
metadata: new MetadataMap()
|
|
||||||
});
|
|
||||||
mockItem.relationships = createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [
|
|
||||||
Object.assign(new Relationship(), {
|
|
||||||
uuid: '123',
|
|
||||||
id: '123',
|
|
||||||
leftItem: createSuccessfulRemoteDataObject$(mockItem),
|
|
||||||
rightItem: createSuccessfulRemoteDataObject$(relatedItem),
|
|
||||||
relationshipType: createSuccessfulRemoteDataObject$(new RelationshipType())
|
|
||||||
})
|
|
||||||
]));
|
|
||||||
mockItem.metadata[metadataField] = [
|
|
||||||
{
|
|
||||||
value: 'Second value',
|
|
||||||
place: 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'Third value',
|
|
||||||
place: 2,
|
|
||||||
authority: 'virtual::123'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'First value',
|
|
||||||
place: 0
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'Fourth value',
|
|
||||||
place: 3,
|
|
||||||
authority: '123'
|
|
||||||
}
|
|
||||||
] as MetadataValue[];
|
|
||||||
const mockItemDataService = Object.assign({
|
|
||||||
findById: (id) => {
|
|
||||||
if (id === relatedItem.id) {
|
|
||||||
return createSuccessfulRemoteDataObject$(relatedItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) as ItemDataService;
|
|
||||||
|
|
||||||
let representations: Observable<MetadataRepresentation[]>;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
TestBed.configureTestingModule({
|
|
||||||
imports: [TranslateModule.forRoot({
|
|
||||||
loader: {
|
|
||||||
provide: TranslateLoader,
|
|
||||||
useClass: MockTranslateLoader
|
|
||||||
}
|
|
||||||
}), BrowserAnimationsModule],
|
|
||||||
declarations: [ItemComponent, VarDirective],
|
|
||||||
providers: [
|
|
||||||
{provide: ITEM, useValue: mockItem}
|
|
||||||
],
|
|
||||||
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
|
||||||
}).overrideComponent(ItemComponent, {
|
|
||||||
set: {changeDetection: ChangeDetectionStrategy.Default}
|
|
||||||
}).compileComponents();
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
fixture = TestBed.createComponent(ItemComponent);
|
|
||||||
comp = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
representations = comp.buildRepresentations('bogus', metadataField);
|
|
||||||
}));
|
|
||||||
|
|
||||||
it('should contain exactly 4 metadata-representations', () => {
|
|
||||||
representations.subscribe((reps: MetadataRepresentation[]) => {
|
|
||||||
expect(reps.length).toEqual(4);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have all the representations in the correct order', () => {
|
|
||||||
representations.subscribe((reps: MetadataRepresentation[]) => {
|
|
||||||
expect(reps[0].getValue()).toEqual('First value');
|
|
||||||
expect(reps[1].getValue()).toEqual('Second value');
|
|
||||||
expect(reps[2].getValue()).toEqual('related item');
|
|
||||||
expect(reps[3].getValue()).toEqual('Fourth value');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should have created the correct MetadatumRepresentation and ItemMetadataRepresentation objects for the correct Metadata', () => {
|
|
||||||
representations.subscribe((reps: MetadataRepresentation[]) => {
|
|
||||||
expect(reps[0] instanceof MetadatumRepresentation).toEqual(true);
|
|
||||||
expect(reps[1] instanceof MetadatumRepresentation).toEqual(true);
|
|
||||||
expect(reps[2] instanceof ItemMetadataRepresentation).toEqual(true);
|
|
||||||
expect(reps[3] instanceof MetadatumRepresentation).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -1,58 +1,5 @@
|
|||||||
import { Component, Inject, OnInit } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { Observable , zip as observableZip, combineLatest as observableCombineLatest } from 'rxjs';
|
|
||||||
import { distinctUntilChanged, filter, flatMap, map } from 'rxjs/operators';
|
|
||||||
import { ItemDataService } from '../../../../core/data/item-data.service';
|
|
||||||
import { PaginatedList } from '../../../../core/data/paginated-list';
|
|
||||||
import { RemoteData } from '../../../../core/data/remote-data';
|
|
||||||
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
|
||||||
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
|
|
||||||
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
|
||||||
import { MetadataRepresentation } from '../../../../core/shared/metadata-representation/metadata-representation.model';
|
|
||||||
import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
|
|
||||||
import { MetadatumRepresentation } from '../../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
|
||||||
import { of } from 'rxjs/internal/observable/of';
|
|
||||||
import { MetadataValue } from '../../../../core/shared/metadata.models';
|
|
||||||
import { compareArraysUsingIds } from './item-relationships-utils';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Operator for turning a list of relationships into a list of metadatarepresentations given the original metadata
|
|
||||||
* @param thisId The id of the parent item
|
|
||||||
* @param itemType The type of relation this list resembles (for creating representations)
|
|
||||||
* @param metadata The list of original Metadatum objects
|
|
||||||
*/
|
|
||||||
export const relationsToRepresentations = (thisId: string, itemType: string, metadata: MetadataValue[]) =>
|
|
||||||
(source: Observable<Relationship[]>): Observable<MetadataRepresentation[]> =>
|
|
||||||
source.pipe(
|
|
||||||
flatMap((rels: Relationship[]) =>
|
|
||||||
observableZip(
|
|
||||||
...metadata
|
|
||||||
.map((metadatum: any) => Object.assign(new MetadataValue(), metadatum))
|
|
||||||
.map((metadatum: MetadataValue) => {
|
|
||||||
if (metadatum.isVirtual) {
|
|
||||||
const matchingRels = rels.filter((rel: Relationship) => ('' + rel.id) === metadatum.virtualValue);
|
|
||||||
if (matchingRels.length > 0) {
|
|
||||||
const matchingRel = matchingRels[0];
|
|
||||||
return observableCombineLatest(matchingRel.leftItem, matchingRel.rightItem).pipe(
|
|
||||||
filter(([leftItem, rightItem]) => leftItem.hasSucceeded && rightItem.hasSucceeded),
|
|
||||||
map(([leftItem, rightItem]) => {
|
|
||||||
if (leftItem.payload.id === thisId) {
|
|
||||||
return rightItem.payload;
|
|
||||||
} else if (rightItem.payload.id === thisId) {
|
|
||||||
return leftItem.payload;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
map((item: Item) => Object.assign(new ItemMetadataRepresentation(), item))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return of(Object.assign(new MetadatumRepresentation(itemType), metadatum));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item',
|
selector: 'ds-item',
|
||||||
@@ -61,60 +8,6 @@ export const relationsToRepresentations = (thisId: string, itemType: string, met
|
|||||||
/**
|
/**
|
||||||
* A generic component for displaying metadata and relations of an item
|
* A generic component for displaying metadata and relations of an item
|
||||||
*/
|
*/
|
||||||
export class ItemComponent implements OnInit {
|
export class ItemComponent {
|
||||||
/**
|
@Input() object: Item;
|
||||||
* Resolved relationships and types together in one observable
|
|
||||||
*/
|
|
||||||
resolvedRelsAndTypes$: Observable<[Relationship[], RelationshipType[]]>;
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@Inject(ITEM) public item: Item
|
|
||||||
) {}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
const relationships$ = this.item.relationships;
|
|
||||||
if (relationships$) {
|
|
||||||
const relsCurrentPage$ = relationships$.pipe(
|
|
||||||
filter((rd: RemoteData<PaginatedList<Relationship>>) => rd.hasSucceeded),
|
|
||||||
getRemoteDataPayload(),
|
|
||||||
map((pl: PaginatedList<Relationship>) => pl.page),
|
|
||||||
distinctUntilChanged(compareArraysUsingIds())
|
|
||||||
);
|
|
||||||
|
|
||||||
const relTypesCurrentPage$ = relsCurrentPage$.pipe(
|
|
||||||
flatMap((rels: Relationship[]) =>
|
|
||||||
observableZip(...rels.map((rel: Relationship) => rel.relationshipType)).pipe(
|
|
||||||
map(([...arr]: Array<RemoteData<RelationshipType>>) => arr.map((d: RemoteData<RelationshipType>) => d.payload))
|
|
||||||
)
|
|
||||||
),
|
|
||||||
distinctUntilChanged(compareArraysUsingIds())
|
|
||||||
);
|
|
||||||
|
|
||||||
this.resolvedRelsAndTypes$ = observableCombineLatest(
|
|
||||||
relsCurrentPage$,
|
|
||||||
relTypesCurrentPage$
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build a list of MetadataRepresentations for the current item. This combines all metadata and relationships of a
|
|
||||||
* certain type.
|
|
||||||
* @param itemType The type of item we're building representations of. Used for matching templates.
|
|
||||||
* @param metadataField The metadata field that resembles the item type.
|
|
||||||
*/
|
|
||||||
buildRepresentations(itemType: string, metadataField: string): Observable<MetadataRepresentation[]> {
|
|
||||||
const metadata = this.item.findMetadataSortedByPlace(metadataField);
|
|
||||||
const relsCurrentPage$ = this.item.relationships.pipe(
|
|
||||||
getSucceededRemoteData(),
|
|
||||||
getRemoteDataPayload(),
|
|
||||||
map((pl: PaginatedList<Relationship>) => pl.page),
|
|
||||||
distinctUntilChanged(compareArraysUsingIds())
|
|
||||||
);
|
|
||||||
|
|
||||||
return relsCurrentPage$.pipe(
|
|
||||||
relationsToRepresentations(this.item.id, itemType, metadata)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,20 @@
|
|||||||
<ds-metadata-field-wrapper *ngIf="representations && representations.length > 0" [label]="label">
|
<ds-metadata-field-wrapper [label]="label">
|
||||||
<ds-item-type-switcher *ngFor="let rep of representations"
|
<ng-container *ngFor="let objectPage of objects; let i = index">
|
||||||
[object]="rep" [viewMode]="viewMode">
|
<ng-container *ngVar="(objectPage | async) as representations">
|
||||||
</ds-item-type-switcher>
|
<ds-metadata-representation-loader *ngFor="let rep of representations"
|
||||||
|
[mdRepresentation]="rep">
|
||||||
|
</ds-metadata-representation-loader>
|
||||||
|
<ds-loading *ngIf="(i + 1) === objects.length && (i > 0) && (!representations || representations?.length === 0)" message="{{'loading.default' | translate}}"></ds-loading>
|
||||||
|
<div class="d-inline-block w-100 mt-2" *ngIf="(i + 1) === objects.length && representations?.length > 0">
|
||||||
|
<div *ngIf="(objects.length * incrementBy) < total" class="float-left">
|
||||||
|
<a [routerLink]="" (click)="increase()">{{'item.page.related-items.view-more' |
|
||||||
|
translate:{ amount: (total - (objects.length * incrementBy) < incrementBy) ? total - (objects.length * incrementBy) : incrementBy } }}</a>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="objects.length > 1" class="float-right">
|
||||||
|
<a [routerLink]="" (click)="decrease()">{{'item.page.related-items.view-less' |
|
||||||
|
translate:{ amount: representations?.length } }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
</ds-metadata-field-wrapper>
|
</ds-metadata-field-wrapper>
|
||||||
|
@@ -2,23 +2,74 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { MetadataRepresentationListComponent } from './metadata-representation-list.component';
|
import { MetadataRepresentationListComponent } from './metadata-representation-list.component';
|
||||||
import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
import { RelationshipService } from '../../../core/data/relationship.service';
|
||||||
import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
|
||||||
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { VarDirective } from '../../../shared/utils/var.directive';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
const itemType = 'type';
|
const itemType = 'Person';
|
||||||
const metadataRepresentation1 = new MetadatumRepresentation(itemType);
|
const metadataField = 'dc.contributor.author';
|
||||||
const metadataRepresentation2 = new ItemMetadataRepresentation();
|
const parentItem: Item = Object.assign(new Item(), {
|
||||||
const representations = [metadataRepresentation1, metadataRepresentation2];
|
id: 'parent-item',
|
||||||
|
metadata: {
|
||||||
|
'dc.contributor.author': [
|
||||||
|
{
|
||||||
|
language: null,
|
||||||
|
value: 'Related Author with authority',
|
||||||
|
authority: 'virtual::related-author',
|
||||||
|
place: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: null,
|
||||||
|
value: 'Author without authority',
|
||||||
|
place: 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
language: null,
|
||||||
|
value: 'Parent Item'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const relatedAuthor: Item = Object.assign(new Item(), {
|
||||||
|
id: 'related-author',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
language: null,
|
||||||
|
value: 'Related Author'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const relation: Relationship = Object.assign(new Relationship(), {
|
||||||
|
leftItem: createSuccessfulRemoteDataObject$(parentItem),
|
||||||
|
rightItem: createSuccessfulRemoteDataObject$(relatedAuthor)
|
||||||
|
});
|
||||||
|
let relationshipService: RelationshipService;
|
||||||
|
|
||||||
describe('MetadataRepresentationListComponent', () => {
|
describe('MetadataRepresentationListComponent', () => {
|
||||||
let comp: MetadataRepresentationListComponent;
|
let comp: MetadataRepresentationListComponent;
|
||||||
let fixture: ComponentFixture<MetadataRepresentationListComponent>;
|
let fixture: ComponentFixture<MetadataRepresentationListComponent>;
|
||||||
|
|
||||||
|
relationshipService = jasmine.createSpyObj('relationshipService',
|
||||||
|
{
|
||||||
|
findById: createSuccessfulRemoteDataObject$(relation)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [],
|
imports: [TranslateModule.forRoot()],
|
||||||
declarations: [MetadataRepresentationListComponent],
|
declarations: [MetadataRepresentationListComponent, VarDirective],
|
||||||
providers: [],
|
providers: [
|
||||||
|
{ provide: RelationshipService, useValue: relationshipService }
|
||||||
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).overrideComponent(MetadataRepresentationListComponent, {
|
}).overrideComponent(MetadataRepresentationListComponent, {
|
||||||
set: {changeDetection: ChangeDetectionStrategy.Default}
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
@@ -28,13 +79,41 @@ describe('MetadataRepresentationListComponent', () => {
|
|||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
fixture = TestBed.createComponent(MetadataRepresentationListComponent);
|
fixture = TestBed.createComponent(MetadataRepresentationListComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
comp.representations = representations;
|
comp.parentItem = parentItem;
|
||||||
|
comp.itemType = itemType;
|
||||||
|
comp.metadataField = metadataField;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it(`should load ${representations.length} item-type-switcher components`, () => {
|
it('should load 2 ds-metadata-representation-loader components', () => {
|
||||||
const fields = fixture.debugElement.queryAll(By.css('ds-item-type-switcher'));
|
const fields = fixture.debugElement.queryAll(By.css('ds-metadata-representation-loader'));
|
||||||
expect(fields.length).toBe(representations.length);
|
expect(fields.length).toBe(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain one page of items', () => {
|
||||||
|
expect(comp.objects.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when increase is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.increase();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a new page to the list', () => {
|
||||||
|
expect(comp.objects.length).toEqual(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when decrease is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Add a second page
|
||||||
|
comp.objects.push(observableOf(undefined));
|
||||||
|
comp.decrease();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should decrease the list of pages', () => {
|
||||||
|
expect(comp.objects.length).toEqual(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@@ -1,6 +1,16 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
|
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
|
||||||
import { ItemViewMode } from '../../../shared/items/item-type-decorator';
|
import { combineLatest as observableCombineLatest, Observable, of as observableOf, zip as observableZip } from 'rxjs';
|
||||||
|
import { RelationshipService } from '../../../core/data/relationship.service';
|
||||||
|
import { MetadataValue } from '../../../core/shared/metadata.models';
|
||||||
|
import { getSucceededRemoteData } from '../../../core/shared/operators';
|
||||||
|
import { filter, map, switchMap } from 'rxjs/operators';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
|
||||||
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
||||||
|
import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
|
||||||
|
import { AbstractIncrementalListComponent } from '../abstract-incremental-list/abstract-incremental-list.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-metadata-representation-list',
|
selector: 'ds-metadata-representation-list',
|
||||||
@@ -8,13 +18,25 @@ import { ItemViewMode } from '../../../shared/items/item-type-decorator';
|
|||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* This component is used for displaying metadata
|
* This component is used for displaying metadata
|
||||||
* It expects a list of MetadataRepresentation objects and a label to put on top of the list
|
* It expects an item and a metadataField to fetch metadata
|
||||||
|
* It expects an itemType to resolve the metadata to a an item
|
||||||
|
* It expects a label to put on top of the list
|
||||||
*/
|
*/
|
||||||
export class MetadataRepresentationListComponent {
|
export class MetadataRepresentationListComponent extends AbstractIncrementalListComponent<Observable<MetadataRepresentation[]>> {
|
||||||
/**
|
/**
|
||||||
* A list of metadata-representations to display
|
* The parent of the list of related items to display
|
||||||
*/
|
*/
|
||||||
@Input() representations: MetadataRepresentation[];
|
@Input() parentItem: Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of item to create a representation of
|
||||||
|
*/
|
||||||
|
@Input() itemType: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The metadata field to use for fetching metadata from the item
|
||||||
|
*/
|
||||||
|
@Input() metadataField: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An i18n label to use as a title for the list
|
* An i18n label to use as a title for the list
|
||||||
@@ -22,8 +44,62 @@ export class MetadataRepresentationListComponent {
|
|||||||
@Input() label: string;
|
@Input() label: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The view-mode we're currently on
|
* The amount to increment the list by when clicking "view more"
|
||||||
* @type {ElementViewMode}
|
* Defaults to 10
|
||||||
|
* The default can optionally be overridden by providing the limit as input to the component
|
||||||
*/
|
*/
|
||||||
viewMode = ItemViewMode.Metadata;
|
@Input() incrementBy = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The total amount of metadata values available
|
||||||
|
*/
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
constructor(public relationshipService: RelationshipService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specific page
|
||||||
|
* @param page The page to fetch
|
||||||
|
*/
|
||||||
|
getPage(page: number): Observable<MetadataRepresentation[]> {
|
||||||
|
const metadata = this.parentItem.findMetadataSortedByPlace(this.metadataField);
|
||||||
|
this.total = metadata.length;
|
||||||
|
return this.resolveMetadataRepresentations(metadata, page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a list of metadata values to a list of metadata representations
|
||||||
|
* @param metadata The list of all metadata values
|
||||||
|
* @param page The page to return representations for
|
||||||
|
*/
|
||||||
|
resolveMetadataRepresentations(metadata: MetadataValue[], page: number): Observable<MetadataRepresentation[]> {
|
||||||
|
return observableZip(
|
||||||
|
...metadata
|
||||||
|
.slice((this.objects.length * this.incrementBy), (this.objects.length * this.incrementBy) + this.incrementBy)
|
||||||
|
.map((metadatum: any) => Object.assign(new MetadataValue(), metadatum))
|
||||||
|
.map((metadatum: MetadataValue) => {
|
||||||
|
if (metadatum.isVirtual) {
|
||||||
|
return this.relationshipService.findById(metadatum.virtualValue).pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
switchMap((relRD: RemoteData<Relationship>) =>
|
||||||
|
observableCombineLatest(relRD.payload.leftItem, relRD.payload.rightItem).pipe(
|
||||||
|
filter(([leftItem, rightItem]) => leftItem.hasSucceeded && rightItem.hasSucceeded),
|
||||||
|
map(([leftItem, rightItem]) => {
|
||||||
|
if (leftItem.payload.id === this.parentItem.id) {
|
||||||
|
return rightItem.payload;
|
||||||
|
} else if (rightItem.payload.id === this.parentItem.id) {
|
||||||
|
return leftItem.payload;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
map((item: Item) => Object.assign(new ItemMetadataRepresentation(metadatum), item))
|
||||||
|
)
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
return observableOf(Object.assign(new MetadatumRepresentation(this.itemType), metadatum));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
<ds-filtered-search-page
|
<ds-configuration-search-page
|
||||||
[fixedFilterQuery]="fixedFilter"
|
[fixedFilterQuery]="fixedFilter"
|
||||||
|
[configuration]="configuration"
|
||||||
[configuration$]="configuration$"
|
[configuration$]="configuration$"
|
||||||
[searchEnabled]="searchEnabled"
|
[searchEnabled]="searchEnabled"
|
||||||
[sideBarWidth]="sideBarWidth">
|
[sideBarWidth]="sideBarWidth">
|
||||||
</ds-filtered-search-page>
|
</ds-configuration-search-page>
|
||||||
|
@@ -4,31 +4,23 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
|
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
|
||||||
describe('RelatedEntitiesSearchComponent', () => {
|
describe('RelatedEntitiesSearchComponent', () => {
|
||||||
let comp: RelatedEntitiesSearchComponent;
|
let comp: RelatedEntitiesSearchComponent;
|
||||||
let fixture: ComponentFixture<RelatedEntitiesSearchComponent>;
|
let fixture: ComponentFixture<RelatedEntitiesSearchComponent>;
|
||||||
let fixedFilterService: SearchFixedFilterService;
|
|
||||||
|
|
||||||
const mockItem = Object.assign(new Item(), {
|
const mockItem = Object.assign(new Item(), {
|
||||||
id: 'id1'
|
id: 'id1'
|
||||||
});
|
});
|
||||||
const mockRelationType = 'publicationsOfAuthor';
|
const mockRelationType = 'publicationsOfAuthor';
|
||||||
const mockRelationEntityType = 'publication';
|
const mockConfiguration = 'publication';
|
||||||
const mockFilter= `f.${mockRelationType}=${mockItem.id}`;
|
const mockFilter= `f.${mockRelationType}=${mockItem.id}`;
|
||||||
const fixedFilterServiceStub = {
|
|
||||||
getFilterByRelation: () => mockFilter
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],
|
imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],
|
||||||
declarations: [RelatedEntitiesSearchComponent],
|
declarations: [RelatedEntitiesSearchComponent],
|
||||||
providers: [
|
|
||||||
{ provide: SearchFixedFilterService, useValue: fixedFilterServiceStub }
|
|
||||||
],
|
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
@@ -36,10 +28,9 @@ describe('RelatedEntitiesSearchComponent', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(RelatedEntitiesSearchComponent);
|
fixture = TestBed.createComponent(RelatedEntitiesSearchComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
fixedFilterService = (comp as any).fixedFilterService;
|
|
||||||
comp.relationType = mockRelationType;
|
comp.relationType = mockRelationType;
|
||||||
comp.item = mockItem;
|
comp.item = mockItem;
|
||||||
comp.relationEntityType = mockRelationEntityType;
|
comp.configuration = mockConfiguration;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,7 +40,7 @@ describe('RelatedEntitiesSearchComponent', () => {
|
|||||||
|
|
||||||
it('should create a configuration$', () => {
|
it('should create a configuration$', () => {
|
||||||
comp.configuration$.subscribe((configuration) => {
|
comp.configuration$.subscribe((configuration) => {
|
||||||
expect(configuration).toEqual(mockRelationEntityType);
|
expect(configuration).toEqual(mockConfiguration);
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { Component, Input, OnInit } from '@angular/core';
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { Item } from '../../../../core/shared/item.model';
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
import { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
|
|
||||||
import { isNotEmpty } from '../../../../shared/empty.util';
|
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||||
import { of } from 'rxjs/internal/observable/of';
|
import { of } from 'rxjs/internal/observable/of';
|
||||||
|
import { getFilterByRelation } from '../../../../shared/utils/relation-query.utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-related-entities-search',
|
selector: 'ds-related-entities-search',
|
||||||
@@ -22,18 +22,16 @@ export class RelatedEntitiesSearchComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
@Input() relationType: string;
|
@Input() relationType: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An optional configuration to use for the search options
|
||||||
|
*/
|
||||||
|
@Input() configuration: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The item to render relationships for
|
* The item to render relationships for
|
||||||
*/
|
*/
|
||||||
@Input() item: Item;
|
@Input() item: Item;
|
||||||
|
|
||||||
/**
|
|
||||||
* The entity type of the relationship items to be displayed
|
|
||||||
* e.g. 'publication'
|
|
||||||
* This determines the title of the search results (if search is enabled)
|
|
||||||
*/
|
|
||||||
@Input() relationEntityType: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the search bar and title should be displayed (defaults to true)
|
* Whether or not the search bar and title should be displayed (defaults to true)
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
@@ -49,15 +47,12 @@ export class RelatedEntitiesSearchComponent implements OnInit {
|
|||||||
fixedFilter: string;
|
fixedFilter: string;
|
||||||
configuration$: Observable<string>;
|
configuration$: Observable<string>;
|
||||||
|
|
||||||
constructor(private fixedFilterService: SearchFixedFilterService) {
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
if (isNotEmpty(this.relationType) && isNotEmpty(this.item)) {
|
if (isNotEmpty(this.relationType) && isNotEmpty(this.item)) {
|
||||||
this.fixedFilter = this.fixedFilterService.getFilterByRelation(this.relationType, this.item.id);
|
this.fixedFilter = getFilterByRelation(this.relationType, this.item.id);
|
||||||
}
|
}
|
||||||
if (isNotEmpty(this.relationEntityType)) {
|
if (isNotEmpty(this.configuration)) {
|
||||||
this.configuration$ = of(this.relationEntityType);
|
this.configuration$ = of(this.configuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -0,0 +1,22 @@
|
|||||||
|
<ngb-tabset *ngIf="relationTypes.length > 1" [destroyOnHide]="true" #tabs="ngbTabset" [activeId]="activeTab$ | async" (tabChange)="onTabChange($event)">
|
||||||
|
<ngb-tab *ngFor="let relationType of relationTypes" title="{{'item.page.relationships.' + relationType.label | translate}}" [id]="relationType.filter">
|
||||||
|
<ng-template ngbTabContent>
|
||||||
|
<div class="mt-4">
|
||||||
|
<ds-related-entities-search [item]="item"
|
||||||
|
[relationType]="relationType.filter"
|
||||||
|
[configuration]="relationType.configuration"
|
||||||
|
[searchEnabled]="searchEnabled"
|
||||||
|
[sideBarWidth]="sideBarWidth">
|
||||||
|
</ds-related-entities-search>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</ngb-tab>
|
||||||
|
</ngb-tabset>
|
||||||
|
<div *ngIf="relationTypes.length === 1" class="mt-4">
|
||||||
|
<ds-related-entities-search *ngVar="relationTypes[0] as relationType" [item]="item"
|
||||||
|
[relationType]="relationType.filter"
|
||||||
|
[configuration]="relationType.configuration"
|
||||||
|
[searchEnabled]="searchEnabled"
|
||||||
|
[sideBarWidth]="sideBarWidth">
|
||||||
|
</ds-related-entities-search>
|
||||||
|
</div>
|
@@ -0,0 +1,82 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { TabbedRelatedEntitiesSearchComponent } from './tabbed-related-entities-search.component';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { MockRouter } from '../../../../shared/mocks/mock-router';
|
||||||
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { VarDirective } from '../../../../shared/utils/var.directive';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
|
describe('TabbedRelatedEntitiesSearchComponent', () => {
|
||||||
|
let comp: TabbedRelatedEntitiesSearchComponent;
|
||||||
|
let fixture: ComponentFixture<TabbedRelatedEntitiesSearchComponent>;
|
||||||
|
|
||||||
|
const mockItem = Object.assign(new Item(), {
|
||||||
|
id: 'id1'
|
||||||
|
});
|
||||||
|
const mockRelationType = 'publications';
|
||||||
|
const relationTypes = [
|
||||||
|
{
|
||||||
|
label: mockRelationType,
|
||||||
|
filter: mockRelationType
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const router = new MockRouter();
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot(), NoopAnimationsModule, NgbModule.forRoot()],
|
||||||
|
declarations: [TabbedRelatedEntitiesSearchComponent, VarDirective],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: ActivatedRoute,
|
||||||
|
useValue: {
|
||||||
|
queryParams: observableOf({ tab: mockRelationType })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ provide: Router, useValue: router }
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(TabbedRelatedEntitiesSearchComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.item = mockItem;
|
||||||
|
comp.relationTypes = relationTypes;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should initialize the activeTab depending on the current query parameters', () => {
|
||||||
|
comp.activeTab$.subscribe((activeTab) => {
|
||||||
|
expect(activeTab).toEqual(mockRelationType);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onTabChange', () => {
|
||||||
|
const event = {
|
||||||
|
currentId: mockRelationType,
|
||||||
|
nextId: 'nextTab'
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.onTabChange(event);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call router natigate with the correct arguments', () => {
|
||||||
|
expect(router.navigate).toHaveBeenCalledWith([], {
|
||||||
|
relativeTo: (comp as any).route,
|
||||||
|
queryParams: {
|
||||||
|
tab: event.nextId
|
||||||
|
},
|
||||||
|
queryParamsHandling: 'merge'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,76 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-tabbed-related-entities-search',
|
||||||
|
templateUrl: './tabbed-related-entities-search.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* A component to show related items as search results, split into tabs by relationship-type
|
||||||
|
* Related items can be facetted, or queried using an
|
||||||
|
* optional search box.
|
||||||
|
*/
|
||||||
|
export class TabbedRelatedEntitiesSearchComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* The types of relationships to fetch items for
|
||||||
|
* e.g. 'isAuthorOfPublication'
|
||||||
|
*/
|
||||||
|
@Input() relationTypes: Array<{
|
||||||
|
label: string,
|
||||||
|
filter: string,
|
||||||
|
configuration?: string
|
||||||
|
}>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item to render relationships for
|
||||||
|
*/
|
||||||
|
@Input() item: Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the search bar and title should be displayed (defaults to true)
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
@Input() searchEnabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ratio of the sidebar's width compared to the search results (1-12) (defaults to 4)
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
@Input() sideBarWidth = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The active tab
|
||||||
|
*/
|
||||||
|
activeTab$: Observable<string>;
|
||||||
|
|
||||||
|
constructor(private route: ActivatedRoute,
|
||||||
|
private router: Router) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the url contains a "tab" query parameter, set this tab to be the active tab
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.activeTab$ = this.route.queryParams.pipe(
|
||||||
|
map((params) => params.tab)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a "tab" query parameter to the URL when changing tabs
|
||||||
|
* @param event
|
||||||
|
*/
|
||||||
|
onTabChange(event) {
|
||||||
|
this.router.navigate([], {
|
||||||
|
relativeTo: this.route,
|
||||||
|
queryParams: {
|
||||||
|
tab: event.nextId
|
||||||
|
},
|
||||||
|
queryParamsHandling: 'merge'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,6 +1,12 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { ItemViewMode } from '../../../shared/items/item-type-decorator';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
|
import { FindListOptions } from '../../../core/data/request.models';
|
||||||
|
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||||
|
import { RelationshipService } from '../../../core/data/relationship.service';
|
||||||
|
import { AbstractIncrementalListComponent } from '../abstract-incremental-list/abstract-incremental-list.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-related-items',
|
selector: 'ds-related-items',
|
||||||
@@ -9,13 +15,32 @@ import { ItemViewMode } from '../../../shared/items/item-type-decorator';
|
|||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* This component is used for displaying relations between items
|
* This component is used for displaying relations between items
|
||||||
* It expects a list of items to display and a label to put on top
|
* It expects a parent item and relationship type, as well as a label to display on top
|
||||||
*/
|
*/
|
||||||
export class RelatedItemsComponent {
|
export class RelatedItemsComponent extends AbstractIncrementalListComponent<Observable<RemoteData<PaginatedList<Item>>>> {
|
||||||
/**
|
/**
|
||||||
* A list of items to display
|
* The parent of the list of related items to display
|
||||||
*/
|
*/
|
||||||
@Input() items: Item[];
|
@Input() parentItem: Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The label of the relationship type to display
|
||||||
|
* Used in sending a search request to the REST API
|
||||||
|
*/
|
||||||
|
@Input() relationType: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount to increment the list by when clicking "view more"
|
||||||
|
* Defaults to 5
|
||||||
|
* The default can optionally be overridden by providing the limit as input to the component
|
||||||
|
*/
|
||||||
|
@Input() incrementBy = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default options to start a search request with
|
||||||
|
* Optional input
|
||||||
|
*/
|
||||||
|
@Input() options = new FindListOptions();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An i18n label to use as a title for the list (usually describes the relation)
|
* An i18n label to use as a title for the list (usually describes the relation)
|
||||||
@@ -24,7 +49,19 @@ export class RelatedItemsComponent {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The view-mode we're currently on
|
* The view-mode we're currently on
|
||||||
* @type {ElementViewMode}
|
* @type {ViewMode}
|
||||||
*/
|
*/
|
||||||
viewMode = ItemViewMode.Element;
|
viewMode = ViewMode.ListElement;
|
||||||
|
|
||||||
|
constructor(public relationshipService: RelationshipService) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specific page
|
||||||
|
* @param page The page to fetch
|
||||||
|
*/
|
||||||
|
getPage(page: number): Observable<RemoteData<PaginatedList<Item>>> {
|
||||||
|
return this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, Object.assign(this.options, { elementsPerPage: this.incrementBy, currentPage: page }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,20 @@
|
|||||||
<ds-metadata-field-wrapper *ngIf="items && items.length > 0" [label]="label">
|
<ds-metadata-field-wrapper [label]="label">
|
||||||
<ds-item-type-switcher *ngFor="let item of items"
|
<ng-container *ngFor="let objectPage of objects; let i = index">
|
||||||
[object]="item" [viewMode]="viewMode">
|
<ng-container *ngVar="(objectPage | async) as itemsRD">
|
||||||
</ds-item-type-switcher>
|
<ds-listable-object-component-loader *ngFor="let item of itemsRD?.payload?.page"
|
||||||
|
[object]="item" [viewMode]="viewMode">
|
||||||
|
</ds-listable-object-component-loader>
|
||||||
|
<ds-loading *ngIf="(i + 1) === objects.length && (itemsRD || i > 0) && !(itemsRD?.hasSucceeded && itemsRD?.payload && itemsRD?.payload?.page?.length > 0)" message="{{'loading.default' | translate}}"></ds-loading>
|
||||||
|
<div class="d-inline-block w-100 mt-2" *ngIf="(i + 1) === objects.length && itemsRD?.payload?.page?.length > 0">
|
||||||
|
<div *ngIf="itemsRD?.payload?.totalPages > objects.length" class="float-left" id="view-more">
|
||||||
|
<a [routerLink]="" (click)="increase()">{{'item.page.related-items.view-more' |
|
||||||
|
translate:{ amount: (itemsRD?.payload?.totalElements - (incrementBy * objects.length) < incrementBy) ? itemsRD?.payload?.totalElements - (incrementBy * objects.length) : incrementBy } }}</a>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="objects.length > 1" class="float-right" id="view-less">
|
||||||
|
<a [routerLink]="" (click)="decrease()">{{'item.page.related-items.view-less' |
|
||||||
|
translate:{ amount: itemsRD?.payload?.page?.length } }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
</ds-metadata-field-wrapper>
|
</ds-metadata-field-wrapper>
|
||||||
|
@@ -2,35 +2,52 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { RelatedItemsComponent } from './related-items-component';
|
import { RelatedItemsComponent } from './related-items-component';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||||
import { PageInfo } from '../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../core/shared/page-info.model';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { createRelationshipsObservable } from '../item-types/shared/item.component.spec';
|
import { createRelationshipsObservable } from '../item-types/shared/item.component.spec';
|
||||||
import { of as observableOf } from 'rxjs';
|
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
|
||||||
|
import { RelationshipService } from '../../../core/data/relationship.service';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { VarDirective } from '../../../shared/utils/var.directive';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
|
const parentItem: Item = Object.assign(new Item(), {
|
||||||
|
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
||||||
|
metadata: [],
|
||||||
|
relationships: createRelationshipsObservable()
|
||||||
|
});
|
||||||
const mockItem1: Item = Object.assign(new Item(), {
|
const mockItem1: Item = Object.assign(new Item(), {
|
||||||
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
||||||
metadata: [],
|
metadata: [],
|
||||||
relationships: createRelationshipsObservable()
|
relationships: createRelationshipsObservable()
|
||||||
});
|
});
|
||||||
const mockItem2: Item = Object.assign(new Item(), {
|
const mockItem2: Item = Object.assign(new Item(), {
|
||||||
bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
||||||
metadata: [],
|
metadata: [],
|
||||||
relationships: createRelationshipsObservable()
|
relationships: createRelationshipsObservable()
|
||||||
});
|
});
|
||||||
const mockItems = [mockItem1, mockItem2];
|
const mockItems = [mockItem1, mockItem2];
|
||||||
|
const relationType = 'isItemOfItem';
|
||||||
|
let relationshipService: RelationshipService;
|
||||||
|
|
||||||
describe('RelatedItemsComponent', () => {
|
describe('RelatedItemsComponent', () => {
|
||||||
let comp: RelatedItemsComponent;
|
let comp: RelatedItemsComponent;
|
||||||
let fixture: ComponentFixture<RelatedItemsComponent>;
|
let fixture: ComponentFixture<RelatedItemsComponent>;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
relationshipService = jasmine.createSpyObj('relationshipService',
|
||||||
|
{
|
||||||
|
getRelatedItemsByLabel: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), mockItems)),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [],
|
imports: [TranslateModule.forRoot()],
|
||||||
declarations: [RelatedItemsComponent],
|
declarations: [RelatedItemsComponent, VarDirective],
|
||||||
providers: [],
|
providers: [
|
||||||
|
{ provide: RelationshipService, useValue: relationshipService }
|
||||||
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).overrideComponent(RelatedItemsComponent, {
|
}).overrideComponent(RelatedItemsComponent, {
|
||||||
set: {changeDetection: ChangeDetectionStrategy.Default}
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
@@ -40,13 +57,44 @@ describe('RelatedItemsComponent', () => {
|
|||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
fixture = TestBed.createComponent(RelatedItemsComponent);
|
fixture = TestBed.createComponent(RelatedItemsComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
comp.items = mockItems;
|
comp.parentItem = parentItem;
|
||||||
|
comp.relationType = relationType;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it(`should load ${mockItems.length} item-type-switcher components`, () => {
|
it(`should load ${mockItems.length} item-type-switcher components`, () => {
|
||||||
const fields = fixture.debugElement.queryAll(By.css('ds-item-type-switcher'));
|
const fields = fixture.debugElement.queryAll(By.css('ds-listable-object-component-loader'));
|
||||||
expect(fields.length).toBe(mockItems.length);
|
expect(fields.length).toBe(mockItems.length);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should contain one page of items', () => {
|
||||||
|
expect(comp.objects.length).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when increase is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.increase();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add a new page to the list', () => {
|
||||||
|
expect(comp.objects.length).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call relationship-service\'s getRelatedItemsByLabel with the correct arguments (second page)', () => {
|
||||||
|
expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, Object.assign(comp.options, { elementsPerPage: comp.incrementBy, currentPage: 2 }));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when decrease is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Add a second page
|
||||||
|
comp.objects.push(observableOf(undefined));
|
||||||
|
comp.decrease();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should decrease the list of pages', () => {
|
||||||
|
expect(comp.objects.length).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
43
src/app/+lookup-by-id/lookup-by-id-routing.module.ts
Normal file
43
src/app/+lookup-by-id/lookup-by-id-routing.module.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { LookupGuard } from './lookup-guard';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, UrlSegment } from '@angular/router';
|
||||||
|
import { ObjectNotFoundComponent } from './objectnotfound/objectnotfound.component';
|
||||||
|
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild([
|
||||||
|
{
|
||||||
|
matcher: urlMatcher,
|
||||||
|
canActivate: [LookupGuard],
|
||||||
|
component: ObjectNotFoundComponent }
|
||||||
|
])
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
LookupGuard
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
export class LookupRoutingModule {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export function urlMatcher(url) {
|
||||||
|
// The expected path is :idType/:id
|
||||||
|
const idType = url[0].path;
|
||||||
|
// Allow for handles that are delimited with a forward slash.
|
||||||
|
const id = url
|
||||||
|
.slice(1)
|
||||||
|
.map((us: UrlSegment) => us.path)
|
||||||
|
.join('/');
|
||||||
|
if (isNotEmpty(idType) && isNotEmpty(id)) {
|
||||||
|
return {
|
||||||
|
consumed: url,
|
||||||
|
posParams: {
|
||||||
|
idType: new UrlSegment(idType, {}),
|
||||||
|
id: new UrlSegment(id, {})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
23
src/app/+lookup-by-id/lookup-by-id.module.ts
Normal file
23
src/app/+lookup-by-id/lookup-by-id.module.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
import { LookupRoutingModule } from './lookup-by-id-routing.module';
|
||||||
|
import { ObjectNotFoundComponent } from './objectnotfound/objectnotfound.component';
|
||||||
|
import { DsoRedirectDataService } from '../core/data/dso-redirect-data.service';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
LookupRoutingModule,
|
||||||
|
CommonModule,
|
||||||
|
SharedModule,
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
ObjectNotFoundComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
DsoRedirectDataService
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class LookupIdModule {
|
||||||
|
|
||||||
|
}
|
50
src/app/+lookup-by-id/lookup-guard.spec.ts
Normal file
50
src/app/+lookup-by-id/lookup-guard.spec.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { LookupGuard } from './lookup-guard';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { IdentifierType } from '../core/data/request.models';
|
||||||
|
|
||||||
|
describe('LookupGuard', () => {
|
||||||
|
let dsoService: any;
|
||||||
|
let guard: any;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dsoService = {
|
||||||
|
findById: jasmine.createSpy('findById').and.returnValue(observableOf({ hasFailed: false,
|
||||||
|
hasSucceeded: true }))
|
||||||
|
};
|
||||||
|
guard = new LookupGuard(dsoService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call findById with handle params', () => {
|
||||||
|
const scopedRoute = {
|
||||||
|
params: {
|
||||||
|
id: '1234',
|
||||||
|
idType: '123456789'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
guard.canActivate(scopedRoute as any, undefined);
|
||||||
|
expect(dsoService.findById).toHaveBeenCalledWith('123456789/1234', IdentifierType.HANDLE)
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call findById with handle params', () => {
|
||||||
|
const scopedRoute = {
|
||||||
|
params: {
|
||||||
|
id: '123456789%2F1234',
|
||||||
|
idType: 'handle'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
guard.canActivate(scopedRoute as any, undefined);
|
||||||
|
expect(dsoService.findById).toHaveBeenCalledWith('123456789%2F1234', IdentifierType.HANDLE)
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call findById with UUID params', () => {
|
||||||
|
const scopedRoute = {
|
||||||
|
params: {
|
||||||
|
id: '34cfed7c-f597-49ef-9cbe-ea351f0023c2',
|
||||||
|
idType: 'uuid'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
guard.canActivate(scopedRoute as any, undefined);
|
||||||
|
expect(dsoService.findById).toHaveBeenCalledWith('34cfed7c-f597-49ef-9cbe-ea351f0023c2', IdentifierType.UUID)
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
53
src/app/+lookup-by-id/lookup-guard.ts
Normal file
53
src/app/+lookup-by-id/lookup-guard.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { IdentifierType } from '../core/data/request.models';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
|
import { FindByIDRequest } from '../core/data/request.models';
|
||||||
|
import { DsoRedirectDataService } from '../core/data/dso-redirect-data.service';
|
||||||
|
|
||||||
|
interface LookupParams {
|
||||||
|
type: IdentifierType;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LookupGuard implements CanActivate {
|
||||||
|
|
||||||
|
constructor(private dsoService: DsoRedirectDataService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
canActivate(route: ActivatedRouteSnapshot, state:RouterStateSnapshot): Observable<boolean> {
|
||||||
|
const params = this.getLookupParams(route);
|
||||||
|
return this.dsoService.findById(params.id, params.type).pipe(
|
||||||
|
map((response: RemoteData<FindByIDRequest>) => response.hasFailed)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLookupParams(route: ActivatedRouteSnapshot): LookupParams {
|
||||||
|
let type;
|
||||||
|
let id;
|
||||||
|
const idType = route.params.idType;
|
||||||
|
|
||||||
|
// If the idType is not recognized, assume a legacy handle request (handle/prefix/id)
|
||||||
|
if (idType !== IdentifierType.HANDLE && idType !== IdentifierType.UUID) {
|
||||||
|
type = IdentifierType.HANDLE;
|
||||||
|
const prefix = route.params.idType;
|
||||||
|
const handleId = route.params.id;
|
||||||
|
id = `${prefix}/${handleId}`;
|
||||||
|
|
||||||
|
} else if (route.params.idType === IdentifierType.HANDLE) {
|
||||||
|
type = IdentifierType.HANDLE;
|
||||||
|
id = route.params.id;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
type = IdentifierType.UUID;
|
||||||
|
id = route.params.id;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: type,
|
||||||
|
id: id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,8 @@
|
|||||||
|
<div class="object-not-found container">
|
||||||
|
<h1>{{"error.identifier" | translate}}</h1>
|
||||||
|
<h2><small><em>{{missingItem}}</em></small></h2>
|
||||||
|
<br />
|
||||||
|
<p class="text-center">
|
||||||
|
<a routerLink="/home" class="btn btn-primary">{{"404.link.home-page" | translate}}</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
@@ -0,0 +1,79 @@
|
|||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { ObjectNotFoundComponent } from './objectnotfound.component';
|
||||||
|
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
|
describe('ObjectNotFoundComponent', () => {
|
||||||
|
let comp: ObjectNotFoundComponent;
|
||||||
|
let fixture: ComponentFixture<ObjectNotFoundComponent>;
|
||||||
|
const testUUID = '34cfed7c-f597-49ef-9cbe-ea351f0023c2';
|
||||||
|
const uuidType = 'uuid';
|
||||||
|
const handlePrefix = '123456789';
|
||||||
|
const handleId = '22';
|
||||||
|
const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {
|
||||||
|
params: observableOf({id: testUUID, idType: uuidType})
|
||||||
|
});
|
||||||
|
const activatedRouteStubHandle = Object.assign(new ActivatedRouteStub(), {
|
||||||
|
params: observableOf({id: handleId, idType: handlePrefix})
|
||||||
|
});
|
||||||
|
describe('uuid request', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
], providers: [
|
||||||
|
{provide: ActivatedRoute, useValue: activatedRouteStub}
|
||||||
|
],
|
||||||
|
declarations: [ObjectNotFoundComponent],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ObjectNotFoundComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create instance', () => {
|
||||||
|
expect(comp).toBeDefined()
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have id and idType', () => {
|
||||||
|
expect(comp.id).toEqual(testUUID);
|
||||||
|
expect(comp.idType).toEqual(uuidType);
|
||||||
|
expect(comp.missingItem).toEqual('uuid: ' + testUUID);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe( 'legacy handle request', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot()
|
||||||
|
], providers: [
|
||||||
|
{provide: ActivatedRoute, useValue: activatedRouteStubHandle}
|
||||||
|
],
|
||||||
|
declarations: [ObjectNotFoundComponent],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(ObjectNotFoundComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have handle prefix and id', () => {
|
||||||
|
expect(comp.id).toEqual(handleId);
|
||||||
|
expect(comp.idType).toEqual(handlePrefix);
|
||||||
|
expect(comp.missingItem).toEqual('handle: ' + handlePrefix + '/' + handleId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component representing the `PageNotFound` DSpace page.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-objnotfound',
|
||||||
|
styleUrls: ['./objectnotfound.component.scss'],
|
||||||
|
templateUrl: './objectnotfound.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.Default
|
||||||
|
})
|
||||||
|
export class ObjectNotFoundComponent implements OnInit {
|
||||||
|
|
||||||
|
idType: string;
|
||||||
|
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
missingItem: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*
|
||||||
|
* @param {AuthService} authservice
|
||||||
|
* @param {ServerResponseService} responseService
|
||||||
|
*/
|
||||||
|
constructor(private route: ActivatedRoute) {
|
||||||
|
route.params.subscribe((params) => {
|
||||||
|
this.idType = params.idType;
|
||||||
|
this.id = params.id;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (this.idType.startsWith('handle') || this.idType.startsWith('uuid')) {
|
||||||
|
this.missingItem = this.idType + ': ' + this.id;
|
||||||
|
} else {
|
||||||
|
this.missingItem = 'handle: ' + this.idType + '/' + this.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,10 +1,10 @@
|
|||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
import { MyDSpaceConfigurationService } from './my-dspace-configuration.service';
|
import { MyDSpaceConfigurationService } from './my-dspace-configuration.service';
|
||||||
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
||||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
||||||
import { SearchFilter } from '../+search-page/search-filter.model';
|
import { SearchFilter } from '../shared/search/search-filter.model';
|
||||||
import { ActivatedRouteStub } from '../shared/testing/active-router-stub';
|
import { ActivatedRouteStub } from '../shared/testing/active-router-stub';
|
||||||
import { MockRoleService } from '../shared/mocks/mock-role-service';
|
import { MockRoleService } from '../shared/mocks/mock-role-service';
|
||||||
import { cold, hot } from 'jasmine-marbles';
|
import { cold, hot } from 'jasmine-marbles';
|
||||||
@@ -38,12 +38,8 @@ describe('MyDSpaceConfigurationService', () => {
|
|||||||
|
|
||||||
const roleService: any = new MockRoleService();
|
const roleService: any = new MockRoleService();
|
||||||
|
|
||||||
const fixedFilterService = jasmine.createSpyObj('SearchFixedFilterService', {
|
|
||||||
getQueryByFilterName: observableOf(''),
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
service = new MyDSpaceConfigurationService(roleService, fixedFilterService, spy, activatedRoute);
|
service = new MyDSpaceConfigurationService(roleService, spy, activatedRoute);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when the scope is called', () => {
|
describe('when the scope is called', () => {
|
||||||
|
@@ -6,12 +6,11 @@ import { first, map } from 'rxjs/operators';
|
|||||||
|
|
||||||
import { MyDSpaceConfigurationValueType } from './my-dspace-configuration-value-type';
|
import { MyDSpaceConfigurationValueType } from './my-dspace-configuration-value-type';
|
||||||
import { RoleService } from '../core/roles/role.service';
|
import { RoleService } from '../core/roles/role.service';
|
||||||
import { SearchConfigurationOption } from '../+search-page/search-switch-configuration/search-configuration-option.model';
|
import { SearchConfigurationOption } from '../shared/search/search-switch-configuration/search-configuration-option.model';
|
||||||
import { SearchConfigurationService } from '../+search-page/search-service/search-configuration.service';
|
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||||
import { RouteService } from '../core/services/route.service';
|
|
||||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
||||||
import { SearchFixedFilterService } from '../+search-page/search-filters/search-filter/search-fixed-filter.service';
|
import { RouteService } from '../core/services/route.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service that performs all actions that have to do with the current mydspace configuration
|
* Service that performs all actions that have to do with the current mydspace configuration
|
||||||
@@ -55,16 +54,14 @@ export class MyDSpaceConfigurationService extends SearchConfigurationService {
|
|||||||
* Initialize class
|
* Initialize class
|
||||||
*
|
*
|
||||||
* @param {roleService} roleService
|
* @param {roleService} roleService
|
||||||
* @param {SearchFixedFilterService} fixedFilterService
|
|
||||||
* @param {RouteService} routeService
|
* @param {RouteService} routeService
|
||||||
* @param {ActivatedRoute} route
|
* @param {ActivatedRoute} route
|
||||||
*/
|
*/
|
||||||
constructor(protected roleService: RoleService,
|
constructor(protected roleService: RoleService,
|
||||||
protected fixedFilterService: SearchFixedFilterService,
|
|
||||||
protected routeService: RouteService,
|
protected routeService: RouteService,
|
||||||
protected route: ActivatedRoute) {
|
protected route: ActivatedRoute) {
|
||||||
|
|
||||||
super(routeService, fixedFilterService, route);
|
super(routeService, route);
|
||||||
|
|
||||||
// override parent class initialization
|
// override parent class initialization
|
||||||
this._defaults = null;
|
this._defaults = null;
|
||||||
|
@@ -7,7 +7,6 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
|
|
||||||
import { SubmissionState } from '../../submission/submission.reducers';
|
import { SubmissionState } from '../../submission/submission.reducers';
|
||||||
import { AuthService } from '../../core/auth/auth.service';
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
import { MyDSpaceResult } from '../my-dspace-result.model';
|
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
import { NotificationOptions } from '../../shared/notifications/models/notification-options.model';
|
import { NotificationOptions } from '../../shared/notifications/models/notification-options.model';
|
||||||
@@ -15,6 +14,7 @@ import { UploaderOptions } from '../../shared/uploader/uploader-options.model';
|
|||||||
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
|
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
|
||||||
import { NotificationType } from '../../shared/notifications/models/notification-type';
|
import { NotificationType } from '../../shared/notifications/models/notification-type';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
|
import { SearchResult } from '../../shared/search/search-result.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component represents the whole mydspace page header
|
* This component represents the whole mydspace page header
|
||||||
@@ -25,7 +25,10 @@ import { hasValue } from '../../shared/empty.util';
|
|||||||
templateUrl: './my-dspace-new-submission.component.html'
|
templateUrl: './my-dspace-new-submission.component.html'
|
||||||
})
|
})
|
||||||
export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit {
|
export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit {
|
||||||
@Output() uploadEnd = new EventEmitter<Array<MyDSpaceResult<DSpaceObject>>>();
|
/**
|
||||||
|
* Output that emits the workspace item when the upload has completed
|
||||||
|
*/
|
||||||
|
@Output() uploadEnd = new EventEmitter<Array<SearchResult<DSpaceObject>>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The UploaderOptions object
|
* The UploaderOptions object
|
||||||
|
@@ -39,7 +39,8 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ds-my-dspace-results [searchResults]="resultsRD$ | async"
|
<ds-my-dspace-results [searchResults]="resultsRD$ | async"
|
||||||
[searchConfig]="searchOptions$ | async"></ds-my-dspace-results>
|
[searchConfig]="searchOptions$ | async"
|
||||||
|
[context]="context$ | async"></ds-my-dspace-results>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1 +1 @@
|
|||||||
@import '../+search-page/search-page.component.scss';
|
@import '../+search-page/search.component.scss';
|
||||||
|
@@ -15,20 +15,18 @@ import { SortDirection, SortOptions } from '../core/cache/models/sort-options.mo
|
|||||||
import { CommunityDataService } from '../core/data/community-data.service';
|
import { CommunityDataService } from '../core/data/community-data.service';
|
||||||
import { HostWindowService } from '../shared/host-window.service';
|
import { HostWindowService } from '../shared/host-window.service';
|
||||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
|
||||||
import { MyDSpacePageComponent, SEARCH_CONFIG_SERVICE } from './my-dspace-page.component';
|
import { MyDSpacePageComponent, SEARCH_CONFIG_SERVICE } from './my-dspace-page.component';
|
||||||
import { RouteService } from '../core/services/route.service';
|
import { RouteService } from '../core/services/route.service';
|
||||||
import { routeServiceStub } from '../shared/testing/route-service-stub';
|
import { routeServiceStub } from '../shared/testing/route-service-stub';
|
||||||
import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub';
|
import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub';
|
||||||
import { SearchService } from '../+search-page/search-service/search.service';
|
import { SearchService } from '../core/shared/search/search.service';
|
||||||
import { SearchConfigurationService } from '../+search-page/search-service/search-configuration.service';
|
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||||
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
||||||
import { SearchSidebarService } from '../+search-page/search-sidebar/search-sidebar.service';
|
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||||
import { SearchFilterService } from '../+search-page/search-filters/search-filter/search-filter.service';
|
import { SearchFilterService } from '../core/shared/search/search-filter.service';
|
||||||
import { RoleDirective } from '../shared/roles/role.directive';
|
import { RoleDirective } from '../shared/roles/role.directive';
|
||||||
import { RoleService } from '../core/roles/role.service';
|
import { RoleService } from '../core/roles/role.service';
|
||||||
import { MockRoleService } from '../shared/mocks/mock-role-service';
|
import { MockRoleService } from '../shared/mocks/mock-role-service';
|
||||||
import { SearchFixedFilterService } from '../+search-page/search-filters/search-filter/search-fixed-filter.service';
|
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../shared/testing/utils';
|
import { createSuccessfulRemoteDataObject$ } from '../shared/testing/utils';
|
||||||
|
|
||||||
describe('MyDSpacePageComponent', () => {
|
describe('MyDSpacePageComponent', () => {
|
||||||
@@ -50,6 +48,7 @@ describe('MyDSpacePageComponent', () => {
|
|||||||
const mockResults = createSuccessfulRemoteDataObject$(['test', 'data']);
|
const mockResults = createSuccessfulRemoteDataObject$(['test', 'data']);
|
||||||
const searchServiceStub = jasmine.createSpyObj('SearchService', {
|
const searchServiceStub = jasmine.createSpyObj('SearchService', {
|
||||||
search: mockResults,
|
search: mockResults,
|
||||||
|
getEndpoint: observableOf('discover/search/objects'),
|
||||||
getSearchLink: '/mydspace',
|
getSearchLink: '/mydspace',
|
||||||
getScopes: observableOf(['test-scope']),
|
getScopes: observableOf(['test-scope']),
|
||||||
setServiceOptions: {}
|
setServiceOptions: {}
|
||||||
@@ -76,13 +75,12 @@ describe('MyDSpacePageComponent', () => {
|
|||||||
scope: scopeParam
|
scope: scopeParam
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
const sidebarService = {
|
const sidebarService = {
|
||||||
isCollapsed: observableOf(true),
|
isCollapsed: observableOf(true),
|
||||||
collapse: () => this.isCollapsed = observableOf(true),
|
collapse: () => this.isCollapsed = observableOf(true),
|
||||||
expand: () => this.isCollapsed = observableOf(false)
|
expand: () => this.isCollapsed = observableOf(false)
|
||||||
};
|
};
|
||||||
const mockFixedFilterService: SearchFixedFilterService = {
|
|
||||||
} as SearchFixedFilterService;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -108,7 +106,7 @@ describe('MyDSpacePageComponent', () => {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: SearchSidebarService,
|
provide: SidebarService,
|
||||||
useValue: sidebarService
|
useValue: sidebarService
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -122,10 +120,6 @@ describe('MyDSpacePageComponent', () => {
|
|||||||
provide: RoleService,
|
provide: RoleService,
|
||||||
useValue: new MockRoleService()
|
useValue: new MockRoleService()
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: SearchFixedFilterService,
|
|
||||||
useValue: mockFixedFilterService
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
}).overrideComponent(MyDSpacePageComponent, {
|
}).overrideComponent(MyDSpacePageComponent, {
|
||||||
|
@@ -8,26 +8,27 @@ import {
|
|||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
|
||||||
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||||
import { switchMap, tap, } from 'rxjs/operators';
|
import { map, switchMap, tap, } from 'rxjs/operators';
|
||||||
|
|
||||||
import { PaginatedList } from '../core/data/paginated-list';
|
import { PaginatedList } from '../core/data/paginated-list';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
||||||
import { pushInOut } from '../shared/animations/push';
|
import { pushInOut } from '../shared/animations/push';
|
||||||
import { HostWindowService } from '../shared/host-window.service';
|
import { HostWindowService } from '../shared/host-window.service';
|
||||||
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
||||||
import { SearchService } from '../+search-page/search-service/search.service';
|
import { SearchService } from '../core/shared/search/search.service';
|
||||||
import { SearchSidebarService } from '../+search-page/search-sidebar/search-sidebar.service';
|
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
import { getSucceededRemoteData } from '../core/shared/operators';
|
import { getSucceededRemoteData } from '../core/shared/operators';
|
||||||
import { MyDSpaceResult } from './my-dspace-result.model';
|
|
||||||
import { MyDSpaceResponseParsingService } from '../core/data/mydspace-response-parsing.service';
|
import { MyDSpaceResponseParsingService } from '../core/data/mydspace-response-parsing.service';
|
||||||
import { SearchConfigurationOption } from '../+search-page/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';
|
||||||
import { SearchConfigurationService } from '../+search-page/search-service/search-configuration.service';
|
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||||
import { MyDSpaceConfigurationService } from './my-dspace-configuration.service';
|
import { MyDSpaceConfigurationService } from './my-dspace-configuration.service';
|
||||||
import { ViewMode } from '../core/shared/view-mode.model';
|
import { ViewMode } from '../core/shared/view-mode.model';
|
||||||
import { MyDSpaceRequest } from '../core/data/request.models';
|
import { MyDSpaceRequest } from '../core/data/request.models';
|
||||||
|
import { SearchResult } from '../shared/search/search-result.model';
|
||||||
|
import { Context } from '../core/shared/context.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');
|
||||||
@@ -63,7 +64,7 @@ export class MyDSpacePageComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* The current search results
|
* The current search results
|
||||||
*/
|
*/
|
||||||
resultsRD$: BehaviorSubject<RemoteData<PaginatedList<MyDSpaceResult<DSpaceObject>>>> = new BehaviorSubject(null);
|
resultsRD$: BehaviorSubject<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> = new BehaviorSubject(null);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current paginated search options
|
* The current paginated search options
|
||||||
@@ -93,10 +94,15 @@ export class MyDSpacePageComponent implements OnInit {
|
|||||||
/**
|
/**
|
||||||
* List of available view mode
|
* List of available view mode
|
||||||
*/
|
*/
|
||||||
viewModeList = [ViewMode.List, ViewMode.Detail];
|
viewModeList = [ViewMode.ListElement, ViewMode.DetailedListElement];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current context of this page: workspace or workflow
|
||||||
|
*/
|
||||||
|
context$: Observable<Context>;
|
||||||
|
|
||||||
constructor(private service: SearchService,
|
constructor(private service: SearchService,
|
||||||
private sidebarService: SearchSidebarService,
|
private sidebarService: SidebarService,
|
||||||
private windowService: HostWindowService,
|
private windowService: HostWindowService,
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: MyDSpaceConfigurationService) {
|
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: MyDSpaceConfigurationService) {
|
||||||
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
||||||
@@ -111,21 +117,35 @@ export class MyDSpacePageComponent implements OnInit {
|
|||||||
*
|
*
|
||||||
* Listen to changes in the scope
|
* Listen to changes in the scope
|
||||||
* If something changes, update the list of scopes for the dropdown
|
* If something changes, update the list of scopes for the dropdown
|
||||||
|
*
|
||||||
|
* Listen to changes in the configuration
|
||||||
|
* If something changes, update the current context
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.configurationList$ = this.searchConfigService.getAvailableConfigurationOptions();
|
this.configurationList$ = this.searchConfigService.getAvailableConfigurationOptions();
|
||||||
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(getSucceededRemoteData())))
|
switchMap((options: PaginatedSearchOptions) => this.service.search(options).pipe(getSucceededRemoteData())))
|
||||||
.subscribe((results) => {
|
.subscribe((results) => {
|
||||||
this.resultsRD$.next(results);
|
this.resultsRD$.next(results);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.scopeListRD$ = this.searchConfigService.getCurrentScope('').pipe(
|
this.scopeListRD$ = this.searchConfigService.getCurrentScope('').pipe(
|
||||||
switchMap((scopeId) => this.service.getScopes(scopeId))
|
switchMap((scopeId) => this.service.getScopes(scopeId))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.context$ = this.searchConfigService.getCurrentConfiguration('workspace')
|
||||||
|
.pipe(
|
||||||
|
map((configuration: string) => {
|
||||||
|
if (configuration === 'workspace') {
|
||||||
|
return Context.Workspace
|
||||||
|
} else {
|
||||||
|
return Context.Workflow
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -5,59 +5,59 @@ import { SharedModule } from '../shared/shared.module';
|
|||||||
|
|
||||||
import { MyDspacePageRoutingModule } from './my-dspace-page-routing.module';
|
import { MyDspacePageRoutingModule } from './my-dspace-page-routing.module';
|
||||||
import { MyDSpacePageComponent } from './my-dspace-page.component';
|
import { MyDSpacePageComponent } from './my-dspace-page.component';
|
||||||
import { SearchPageModule } from '../+search-page/search-page.module';
|
|
||||||
import { MyDSpaceResultsComponent } from './my-dspace-results/my-dspace-results.component';
|
import { MyDSpaceResultsComponent } from './my-dspace-results/my-dspace-results.component';
|
||||||
import { WorkspaceitemMyDSpaceResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/workspaceitem-my-dspace-result/workspaceitem-my-dspace-result-list-element.component';
|
import { WorkspaceItemSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component';
|
||||||
import { ItemMyDSpaceResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/item-my-dspace-result/item-my-dspace-result-list-element.component';
|
import { ClaimedSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component';
|
||||||
import { WorkflowitemMyDSpaceResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/workflowitem-my-dspace-result/workflowitem-my-dspace-result-list-element.component';
|
import { PoolSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component';
|
||||||
import { ClaimedMyDSpaceResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/claimed-my-dspace-result/claimed-my-dspace-result-list-element.component';
|
|
||||||
import { PoolMyDSpaceResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/pool-my-dspace-result/pool-my-dspace-result-list-element.component';
|
|
||||||
import { MyDSpaceNewSubmissionComponent } from './my-dspace-new-submission/my-dspace-new-submission.component';
|
import { MyDSpaceNewSubmissionComponent } from './my-dspace-new-submission/my-dspace-new-submission.component';
|
||||||
import { ItemMyDSpaceResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/item-my-dspace-result/item-my-dspace-result-detail-element.component';
|
import { ItemSearchResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/item-search-result/item-search-result-detail-element.component';
|
||||||
import { WorkspaceitemMyDSpaceResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/workspaceitem-my-dspace-result/workspaceitem-my-dspace-result-detail-element.component';
|
import { WorkspaceItemSearchResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/workspace-item-search-result/workspace-item-search-result-detail-element.component';
|
||||||
import { WorkflowitemMyDSpaceResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/workflowitem-my-dspace-result/workflowitem-my-dspace-result-detail-element.component';
|
import { WorkflowItemSearchResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/workflow-item-search-result/workflow-item-search-result-detail-element.component';
|
||||||
import { ClaimedMyDSpaceResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/claimed-my-dspace-result/claimed-my-dspace-result-detail-element.component';
|
import { ClaimedTaskSearchResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component';
|
||||||
import { PoolMyDSpaceResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/pool-my-dspace-result/pool-my-dspace-result-detail-lement.component';
|
|
||||||
import { MyDSpaceGuard } from './my-dspace.guard';
|
import { MyDSpaceGuard } from './my-dspace.guard';
|
||||||
import { MyDSpaceConfigurationService } from './my-dspace-configuration.service';
|
import { MyDSpaceConfigurationService } from './my-dspace-configuration.service';
|
||||||
|
import { SearchResultListElementComponent } from '../shared/object-list/search-result-list-element/search-result-list-element.component';
|
||||||
|
import { ItemSearchResultListElementSubmissionComponent } from '../shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component';
|
||||||
|
import { WorkflowItemSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component';
|
||||||
|
import { PoolSearchResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
MyDspacePageRoutingModule,
|
MyDspacePageRoutingModule,
|
||||||
SearchPageModule
|
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
MyDSpacePageComponent,
|
MyDSpacePageComponent,
|
||||||
MyDSpaceResultsComponent,
|
MyDSpaceResultsComponent,
|
||||||
ItemMyDSpaceResultListElementComponent,
|
WorkspaceItemSearchResultListElementComponent,
|
||||||
WorkspaceitemMyDSpaceResultListElementComponent,
|
WorkflowItemSearchResultListElementComponent,
|
||||||
WorkflowitemMyDSpaceResultListElementComponent,
|
ClaimedSearchResultListElementComponent,
|
||||||
ClaimedMyDSpaceResultListElementComponent,
|
PoolSearchResultListElementComponent,
|
||||||
PoolMyDSpaceResultListElementComponent,
|
ItemSearchResultDetailElementComponent,
|
||||||
ItemMyDSpaceResultDetailElementComponent,
|
WorkspaceItemSearchResultDetailElementComponent,
|
||||||
WorkspaceitemMyDSpaceResultDetailElementComponent,
|
WorkflowItemSearchResultDetailElementComponent,
|
||||||
WorkflowitemMyDSpaceResultDetailElementComponent,
|
ClaimedTaskSearchResultDetailElementComponent,
|
||||||
ClaimedMyDSpaceResultDetailElementComponent,
|
PoolSearchResultDetailElementComponent,
|
||||||
PoolMyDSpaceResultDetailElementComponent,
|
MyDSpaceNewSubmissionComponent,
|
||||||
MyDSpaceNewSubmissionComponent
|
ItemSearchResultListElementSubmissionComponent
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
MyDSpaceGuard,
|
MyDSpaceGuard,
|
||||||
MyDSpaceConfigurationService
|
MyDSpaceConfigurationService
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
ItemMyDSpaceResultListElementComponent,
|
SearchResultListElementComponent,
|
||||||
WorkspaceitemMyDSpaceResultListElementComponent,
|
WorkspaceItemSearchResultListElementComponent,
|
||||||
WorkflowitemMyDSpaceResultListElementComponent,
|
WorkflowItemSearchResultListElementComponent,
|
||||||
ClaimedMyDSpaceResultListElementComponent,
|
ClaimedSearchResultListElementComponent,
|
||||||
PoolMyDSpaceResultListElementComponent,
|
PoolSearchResultListElementComponent,
|
||||||
ItemMyDSpaceResultDetailElementComponent,
|
ItemSearchResultDetailElementComponent,
|
||||||
WorkspaceitemMyDSpaceResultDetailElementComponent,
|
WorkspaceItemSearchResultDetailElementComponent,
|
||||||
WorkflowitemMyDSpaceResultDetailElementComponent,
|
WorkflowItemSearchResultDetailElementComponent,
|
||||||
ClaimedMyDSpaceResultDetailElementComponent,
|
ClaimedTaskSearchResultDetailElementComponent,
|
||||||
PoolMyDSpaceResultDetailElementComponent
|
PoolSearchResultDetailElementComponent,
|
||||||
|
ItemSearchResultListElementSubmissionComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -1,19 +0,0 @@
|
|||||||
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
|
||||||
import { MetadataMap } from '../core/shared/metadata.models';
|
|
||||||
import { ListableObject } from '../shared/object-collection/shared/listable-object.model';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a search result object of a certain (<T>) DSpaceObject
|
|
||||||
*/
|
|
||||||
export class MyDSpaceResult<T extends DSpaceObject> implements ListableObject {
|
|
||||||
/**
|
|
||||||
* The DSpaceObject that was found
|
|
||||||
*/
|
|
||||||
indexableObject: T;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The metadata that was used to find this item, hithighlighted
|
|
||||||
*/
|
|
||||||
hitHighlights: MetadataMap;
|
|
||||||
|
|
||||||
}
|
|
@@ -4,7 +4,8 @@
|
|||||||
[hasBorder]="hasBorder"
|
[hasBorder]="hasBorder"
|
||||||
[sortConfig]="searchConfig.sort"
|
[sortConfig]="searchConfig.sort"
|
||||||
[objects]="searchResults"
|
[objects]="searchResults"
|
||||||
[hideGear]="true">
|
[hideGear]="true"
|
||||||
|
[context]="context">
|
||||||
</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>
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||||
import { fadeIn, fadeInOut } from '../../shared/animations/fade';
|
import { fadeIn, fadeInOut } from '../../shared/animations/fade';
|
||||||
import { MyDSpaceResult } from '../my-dspace-result.model';
|
import { SearchOptions } from '../../shared/search/search-options.model';
|
||||||
import { SearchOptions } from '../../+search-page/search-options.model';
|
|
||||||
import { PaginatedList } from '../../core/data/paginated-list';
|
import { PaginatedList } from '../../core/data/paginated-list';
|
||||||
import { ViewMode } from '../../core/shared/view-mode.model';
|
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||||
import { isEmpty } from '../../shared/empty.util';
|
import { isEmpty } from '../../shared/empty.util';
|
||||||
|
import { Context } from '../../core/shared/context.model';
|
||||||
|
import { SearchResult } from '../../shared/search/search-result.model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that represents all results for mydspace page
|
* Component that represents all results for mydspace page
|
||||||
@@ -25,7 +25,7 @@ export class MyDSpaceResultsComponent {
|
|||||||
/**
|
/**
|
||||||
* The actual search result objects
|
* The actual search result objects
|
||||||
*/
|
*/
|
||||||
@Input() searchResults: RemoteData<PaginatedList<MyDSpaceResult<DSpaceObject>>>;
|
@Input() searchResults: RemoteData<PaginatedList<SearchResult<DSpaceObject>>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current configuration of the search
|
* The current configuration of the search
|
||||||
@@ -37,6 +37,10 @@ export class MyDSpaceResultsComponent {
|
|||||||
*/
|
*/
|
||||||
@Input() viewMode: ViewMode;
|
@Input() viewMode: ViewMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current context for the search results
|
||||||
|
*/
|
||||||
|
@Input() context: Context;
|
||||||
/**
|
/**
|
||||||
* A boolean representing if search results entry are separated by a line
|
* A boolean representing if search results entry are separated by a line
|
||||||
*/
|
*/
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { configureSearchComponentTestingModule } from './search-page.component.spec';
|
import { configureSearchComponentTestingModule } from './search.component.spec';
|
||||||
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
|
||||||
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
||||||
|
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||||
|
|
||||||
describe('ConfigurationSearchPageComponent', () => {
|
describe('ConfigurationSearchPageComponent', () => {
|
||||||
let comp: ConfigurationSearchPageComponent;
|
let comp: ConfigurationSearchPageComponent;
|
||||||
|
@@ -1,23 +1,22 @@
|
|||||||
import { HostWindowService } from '../shared/host-window.service';
|
import { HostWindowService } from '../shared/host-window.service';
|
||||||
import { SearchService } from './search-service/search.service';
|
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||||
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
import { SearchComponent } from './search.component';
|
||||||
import { SearchPageComponent } from './search-page.component';
|
|
||||||
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
|
||||||
import { pushInOut } from '../shared/animations/push';
|
import { pushInOut } from '../shared/animations/push';
|
||||||
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { PaginatedSearchOptions } from './paginated-search-options.model';
|
|
||||||
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 { map } from 'rxjs/operators';
|
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { hasValue } from '../shared/empty.util';
|
||||||
import { RouteService } from '../core/services/route.service';
|
import { RouteService } from '../core/services/route.service';
|
||||||
|
import { SearchService } from '../core/shared/search/search.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a search page using a configuration as input.
|
* This component renders a search page using a configuration as input.
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-configuration-search-page',
|
selector: 'ds-configuration-search-page',
|
||||||
styleUrls: ['./search-page.component.scss'],
|
styleUrls: ['./search.component.scss'],
|
||||||
templateUrl: './search-page.component.html',
|
templateUrl: './search.component.html',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
animations: [pushInOut],
|
animations: [pushInOut],
|
||||||
providers: [
|
providers: [
|
||||||
@@ -28,19 +27,26 @@ import { RouteService } from '../core/services/route.service';
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
export class ConfigurationSearchPageComponent extends SearchPageComponent implements OnInit {
|
export class ConfigurationSearchPageComponent extends SearchComponent implements OnInit {
|
||||||
/**
|
/**
|
||||||
* The configuration to use for the search options
|
* The configuration to use for the search options
|
||||||
* If empty, the configuration will be determined by the route parameter called 'configuration'
|
* If empty, the configuration will be determined by the route parameter called 'configuration'
|
||||||
*/
|
*/
|
||||||
@Input() configuration: string;
|
@Input() configuration: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual query for the fixed filter.
|
||||||
|
* If empty, the query will be determined by the route parameter called 'filter'
|
||||||
|
*/
|
||||||
|
@Input() fixedFilterQuery: string;
|
||||||
|
|
||||||
constructor(protected service: SearchService,
|
constructor(protected service: SearchService,
|
||||||
protected sidebarService: SearchSidebarService,
|
protected sidebarService: SidebarService,
|
||||||
protected windowService: HostWindowService,
|
protected windowService: HostWindowService,
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||||
protected routeService: RouteService) {
|
protected routeService: RouteService,
|
||||||
super(service, sidebarService, windowService, searchConfigService, routeService);
|
protected router: Router) {
|
||||||
|
super(service, sidebarService, windowService, searchConfigService, routeService, router);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,20 +58,8 @@ export class ConfigurationSearchPageComponent extends SearchPageComponent implem
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
}
|
if (hasValue(this.configuration)) {
|
||||||
|
this.routeService.setParameter('configuration', this.configuration);
|
||||||
/**
|
}
|
||||||
* Get the current paginated search options after updating the configuration using the configuration input
|
|
||||||
* This is to make sure the configuration is included in the paginated search options, as it is not part of any
|
|
||||||
* query or route parameters
|
|
||||||
* @returns {Observable<PaginatedSearchOptions>}
|
|
||||||
*/
|
|
||||||
protected getSearchOptions(): Observable<PaginatedSearchOptions> {
|
|
||||||
return this.searchConfigService.paginatedSearchOptions.pipe(
|
|
||||||
map((options: PaginatedSearchOptions) => {
|
|
||||||
const config = this.configuration || options.configuration;
|
|
||||||
return Object.assign(options, { configuration: config });
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,21 +0,0 @@
|
|||||||
import { FilteredSearchPageComponent } from './filtered-search-page.component';
|
|
||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
import { configureSearchComponentTestingModule } from './search-page.component.spec';
|
|
||||||
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
|
||||||
|
|
||||||
describe('FilteredSearchPageComponent', () => {
|
|
||||||
let comp: FilteredSearchPageComponent;
|
|
||||||
let fixture: ComponentFixture<FilteredSearchPageComponent>;
|
|
||||||
let searchConfigService: SearchConfigurationService;
|
|
||||||
|
|
||||||
beforeEach(async(() => {
|
|
||||||
configureSearchComponentTestingModule(FilteredSearchPageComponent);
|
|
||||||
}));
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
fixture = TestBed.createComponent(FilteredSearchPageComponent);
|
|
||||||
comp = fixture.componentInstance;
|
|
||||||
searchConfigService = (comp as any).searchConfigService;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
});
|
|
@@ -1,73 +0,0 @@
|
|||||||
import { HostWindowService } from '../shared/host-window.service';
|
|
||||||
import { SearchService } from './search-service/search.service';
|
|
||||||
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
|
||||||
import { SearchPageComponent } from './search-page.component';
|
|
||||||
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
|
|
||||||
import { pushInOut } from '../shared/animations/push';
|
|
||||||
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { PaginatedSearchOptions } from './paginated-search-options.model';
|
|
||||||
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
import { RouteService } from '../core/services/route.service';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This component renders a simple item page.
|
|
||||||
* The route parameter 'id' is used to request the item it represents.
|
|
||||||
* All fields of the item that should be displayed, are defined in its template.
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-filtered-search-page',
|
|
||||||
styleUrls: ['./search-page.component.scss'],
|
|
||||||
templateUrl: './search-page.component.html',
|
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
||||||
animations: [pushInOut],
|
|
||||||
providers: [
|
|
||||||
{
|
|
||||||
provide: SEARCH_CONFIG_SERVICE,
|
|
||||||
useClass: SearchConfigurationService
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
export class FilteredSearchPageComponent extends SearchPageComponent implements OnInit {
|
|
||||||
/**
|
|
||||||
* The actual query for the fixed filter.
|
|
||||||
* If empty, the query will be determined by the route parameter called 'filter'
|
|
||||||
*/
|
|
||||||
@Input() fixedFilterQuery: string;
|
|
||||||
|
|
||||||
constructor(protected service: SearchService,
|
|
||||||
protected sidebarService: SearchSidebarService,
|
|
||||||
protected windowService: HostWindowService,
|
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
|
||||||
protected routeService: RouteService) {
|
|
||||||
super(service, sidebarService, windowService, searchConfigService, routeService);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listening to changes in the paginated search options
|
|
||||||
* If something changes, update the search results
|
|
||||||
*
|
|
||||||
* Listen to changes in the scope
|
|
||||||
* If something changes, update the list of scopes for the dropdown
|
|
||||||
*/
|
|
||||||
ngOnInit(): void {
|
|
||||||
super.ngOnInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current paginated search options after updating the fixed filter using the fixedFilterQuery input
|
|
||||||
* This is to make sure the fixed filter is included in the paginated search options, as it is not part of any
|
|
||||||
* query or route parameters
|
|
||||||
* @returns {Observable<PaginatedSearchOptions>}
|
|
||||||
*/
|
|
||||||
protected getSearchOptions(): Observable<PaginatedSearchOptions> {
|
|
||||||
return this.searchConfigService.paginatedSearchOptions.pipe(
|
|
||||||
map((options: PaginatedSearchOptions) => {
|
|
||||||
const filter = this.fixedFilterQuery || options.fixedFilter;
|
|
||||||
return Object.assign(options, { fixedFilter: filter });
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,7 +0,0 @@
|
|||||||
<div class="facet-filter d-block mb-3 p-3" *ngIf="active$ | async">
|
|
||||||
<div (click)="toggle()" class="filter-name"><h5 class="d-inline-block mb-0">{{'search.filters.filter.' + filter.name + '.head'| translate}}</h5> <span class="filter-toggle fas float-right"
|
|
||||||
[ngClass]="(collapsed$ | async) ? 'fa-plus' : 'fa-minus'"></span></div>
|
|
||||||
<div [@slide]="(collapsed$ | async) ? 'collapsed' : 'expanded'" (@slide.start)="startSlide($event)" (@slide.done)="finishSlide($event)" class="search-filter-wrapper" [ngClass]="{'closed' : closed}">
|
|
||||||
<ds-search-facet-filter-wrapper [filterConfig]="filter" [inPlaceSearch]="inPlaceSearch"></ds-search-facet-filter-wrapper>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@@ -1,38 +0,0 @@
|
|||||||
import { SearchFixedFilterService } from './search-fixed-filter.service';
|
|
||||||
import { RequestService } from '../../../core/data/request.service';
|
|
||||||
import { of as observableOf } from 'rxjs';
|
|
||||||
import { RequestEntry } from '../../../core/data/request.reducer';
|
|
||||||
import { FilteredDiscoveryQueryResponse } from '../../../core/cache/response.models';
|
|
||||||
|
|
||||||
describe('SearchFixedFilterService', () => {
|
|
||||||
let service: SearchFixedFilterService;
|
|
||||||
|
|
||||||
const filterQuery = 'filter:query';
|
|
||||||
|
|
||||||
const requestServiceStub = Object.assign({
|
|
||||||
/* tslint:disable:no-empty */
|
|
||||||
configure: () => {
|
|
||||||
},
|
|
||||||
/* tslint:enable:no-empty */
|
|
||||||
generateRequestId: () => 'fake-id',
|
|
||||||
getByHref: () => observableOf(Object.assign(new RequestEntry(), {
|
|
||||||
response: new FilteredDiscoveryQueryResponse(filterQuery, 200, 'OK')
|
|
||||||
}))
|
|
||||||
}) as RequestService;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
service = new SearchFixedFilterService();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when getQueryByRelations is called', () => {
|
|
||||||
const relationType = 'isRelationOf';
|
|
||||||
const itemUUID = 'c5b277e6-2477-48bb-8993-356710c285f3';
|
|
||||||
|
|
||||||
it('should contain the relationType and itemUUID', () => {
|
|
||||||
const query = service.getQueryByRelations(relationType, itemUUID);
|
|
||||||
expect(query.length).toBeGreaterThan(relationType.length + itemUUID.length);
|
|
||||||
expect(query).toContain(relationType);
|
|
||||||
expect(query).toContain(itemUUID);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user