mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-10 03:23:07 +00:00
Merge branch 'master' into w2p-64503_Edit-collection-Content-Source-2
Conflicts: resources/i18n/en.json5 src/app/core/core.module.ts src/app/core/data/collection-data.service.ts src/app/shared/shared.module.ts
This commit is contained in:
97
README.md
97
README.md
@@ -3,13 +3,12 @@
|
||||
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
|
||||
-----------
|
||||
@@ -32,8 +31,6 @@ yarn start
|
||||
|
||||
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.
|
||||
|
||||
Table of Contents
|
||||
@@ -42,24 +39,27 @@ Table of Contents
|
||||
- [Introduction to the technology](#introduction-to-the-technology)
|
||||
- [Requirements](#requirements)
|
||||
- [Installing](#installing)
|
||||
- [Configuring](#configuring)
|
||||
- [Configuring](#configuring)
|
||||
- [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)
|
||||
- [Testing](#testing)
|
||||
- [Test a Pull Request](#test-a-pull-request)
|
||||
- [Documentation](#documentation)
|
||||
- [Other commands](#other-commands)
|
||||
- [Recommended Editors/IDEs](#recommended-editorsides)
|
||||
- [Collaborating](#collaborating)
|
||||
- [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)
|
||||
- [License](#license)
|
||||
|
||||
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
|
||||
------------
|
||||
@@ -75,8 +75,7 @@ Installing
|
||||
- `yarn run global` to install the required global dependencies
|
||||
- `yarn install` to install the local dependencies
|
||||
|
||||
Configuring
|
||||
-----------
|
||||
### Configuring
|
||||
|
||||
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`.
|
||||
|
||||
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.
|
||||
|
||||
@@ -117,6 +115,19 @@ yarn run build:prod
|
||||
|
||||
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
|
||||
--------
|
||||
|
||||
@@ -131,10 +142,6 @@ yarn run clean:prod
|
||||
yarn run clean:dist
|
||||
```
|
||||
|
||||
Running the application with Docker
|
||||
-----------------------------------
|
||||
See [Docker Runtime Options](docker/README.md)
|
||||
|
||||
|
||||
Testing
|
||||
-------
|
||||
@@ -189,21 +196,14 @@ To run all the tests (e.g.: to run tests with Continuous Integration software) y
|
||||
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.
|
||||
|
||||
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
|
||||
--------------
|
||||
|
||||
@@ -229,7 +229,7 @@ To get the most out of TypeScript, you'll need a TypeScript-aware editor. We've
|
||||
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
|
||||
--------------
|
||||
@@ -335,10 +335,20 @@ dspace-angular
|
||||
└── 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:
|
||||
|
||||
@@ -370,24 +380,6 @@ If you're importing a module that uses CommonJS you need to import as
|
||||
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
|
||||
--------------------------
|
||||
|
||||
@@ -411,5 +403,4 @@ Frequently asked questions
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
http://www.dspace.org/license
|
||||
This project's source code is made available under the DSpace BSD License: http://www.dspace.org/license
|
||||
|
@@ -141,6 +141,10 @@ module.exports = {
|
||||
code: 'nl',
|
||||
label: 'Nederlands',
|
||||
active: false,
|
||||
}, {
|
||||
code: 'pt',
|
||||
label: 'Português',
|
||||
active: true,
|
||||
}],
|
||||
// Browse-By Pages
|
||||
browseBy: {
|
||||
|
@@ -2,7 +2,8 @@ import { browser, element, by } from 'protractor';
|
||||
|
||||
export class ProtractorPage {
|
||||
navigateTo() {
|
||||
return browser.get('/');
|
||||
return browser.get('/')
|
||||
.then(() => browser.waitForAngular());
|
||||
}
|
||||
|
||||
getPageTitleText() {
|
||||
|
@@ -15,7 +15,11 @@ module.exports = function (config) {
|
||||
};
|
||||
|
||||
var configuration = {
|
||||
|
||||
client: {
|
||||
jasmine: {
|
||||
random: false
|
||||
}
|
||||
},
|
||||
// base path that will be used to resolve all patterns (e.g. files, exclude)
|
||||
basePath: '',
|
||||
|
||||
|
@@ -75,6 +75,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "^6.1.4",
|
||||
"@angular/cdk": "^6.4.7",
|
||||
"@angular/cli": "^6.1.5",
|
||||
"@angular/common": "^6.1.4",
|
||||
"@angular/core": "^6.1.4",
|
||||
@@ -139,6 +140,7 @@
|
||||
"text-mask-core": "5.0.1",
|
||||
"ts-loader": "^5.2.1",
|
||||
"ts-md5": "^1.2.4",
|
||||
"url-parse": "^1.4.7",
|
||||
"uuid": "^3.2.1",
|
||||
"webfontloader": "1.6.28",
|
||||
"webpack-cli": "^3.1.0",
|
||||
@@ -228,7 +230,7 @@
|
||||
"rollup-plugin-node-globals": "1.2.1",
|
||||
"rollup-plugin-node-resolve": "^3.0.3",
|
||||
"rollup-plugin-terser": "^2.0.2",
|
||||
"sass-loader": "7.1.0",
|
||||
"sass-loader": "^7.1.0",
|
||||
"script-ext-html-webpack-plugin": "2.0.1",
|
||||
"source-map": "0.7.3",
|
||||
"source-map-loader": "0.2.4",
|
||||
|
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.
|
@@ -1125,9 +1125,9 @@
|
||||
// TODO New key - Add a translation
|
||||
"item.edit.move.description": "Select the collection you wish to move this item to. To narrow down the list of displayed collections, you can enter a search query in the box.",
|
||||
|
||||
// "item.edit.move.error": "An error occured when attempting to move the item",
|
||||
// "item.edit.move.error": "An error occurred when attempting to move the item",
|
||||
// TODO New key - Add a translation
|
||||
"item.edit.move.error": "An error occured when attempting to move the item",
|
||||
"item.edit.move.error": "An error occurred when attempting to move the item",
|
||||
|
||||
// "item.edit.move.head": "Move item: {{id}}",
|
||||
// TODO New key - Add a translation
|
||||
@@ -1153,9 +1153,9 @@
|
||||
// TODO New key - Add a translation
|
||||
"item.edit.move.search.placeholder": "Enter a search query to look for collections",
|
||||
|
||||
// "item.edit.move.success": "The item has been moved succesfully",
|
||||
// "item.edit.move.success": "The item has been moved successfully",
|
||||
// TODO New key - Add a translation
|
||||
"item.edit.move.success": "The item has been moved succesfully",
|
||||
"item.edit.move.success": "The item has been moved successfully",
|
||||
|
||||
// "item.edit.move.title": "Move item",
|
||||
// TODO New key - Add a translation
|
||||
@@ -2911,9 +2911,9 @@
|
||||
// TODO New key - Add a translation
|
||||
"submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
|
||||
|
||||
// "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the fle metadata and access conditions or <strong>upload additional files just dragging & dropping them everywhere in the page</strong>",
|
||||
// "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or <strong>upload additional files just dragging & dropping them everywhere in the page</strong>",
|
||||
// TODO New key - Add a translation
|
||||
"submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the fle metadata and access conditions or <strong>upload additional files just dragging & dropping them everywhere in the page</strong>",
|
||||
"submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or <strong>upload additional files just dragging & dropping them everywhere in the page</strong>",
|
||||
|
||||
// "submission.sections.upload.no-entry": "No",
|
||||
// TODO New key - Add a translation
|
||||
|
@@ -587,7 +587,7 @@
|
||||
"item.edit.move.cancel": "Abbrechen",
|
||||
// "item.edit.move.description": "Select the collection you wish to move this item to. To narrow down the list of displayed collections, you can enter a search query in the box.",
|
||||
"item.edit.move.description": "Select the collection you wish to move this item to. To narrow down the list of displayed collections, you can enter a search query in the box.",
|
||||
// "item.edit.move.error": "An error occured when attempting to move the item",
|
||||
// "item.edit.move.error": "An error occurred when attempting to move the item",
|
||||
"item.edit.move.error": "Ein Fehler ist beim Verschieben der Ressource aufgetreten",
|
||||
// "item.edit.move.head": "Move item: {{id}}",
|
||||
"item.edit.move.head": "Ressource verschieben: {{id}}",
|
||||
@@ -601,7 +601,7 @@
|
||||
"item.edit.move.processing": "Verschieben...",
|
||||
// "item.edit.move.search.placeholder": "Enter a search query to look for collections",
|
||||
"item.edit.move.search.placeholder": "Geben Sie einen Begriff ein, um nach Sammlungen zu suchen",
|
||||
// "item.edit.move.success": "The item has been moved succesfully",
|
||||
// "item.edit.move.success": "The item has been moved successfully",
|
||||
"item.edit.move.success": "Die Ressource wurde erfolgreich verschoben",
|
||||
// "item.edit.move.title": "Move item",
|
||||
"item.edit.move.title": "Ressource verschieben",
|
||||
@@ -1515,7 +1515,7 @@
|
||||
"submission.sections.upload.header.policy.default.nolist": "In diese Sammlung {{collectionName}} hochgeladene Dateien werden für folgende(n) Gruppe(n) zugänglich sein:",
|
||||
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
|
||||
"submission.sections.upload.header.policy.default.withlist": "Bitte beachten Sie, dass in diese Sammlung {{collectionName}} hochgeladene Dateien zugüglich zu dem, was für einzelne Dateien entschieden wurde, für folgende Gruppe(n) zugänglich sein:",
|
||||
// "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the fle metadata and access conditions or <strong>upload additional files just dragging & dropping them everywhere in the page</strong>",
|
||||
// "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or <strong>upload additional files just dragging & dropping them everywhere in the page</strong>",
|
||||
"submission.sections.upload.info": "Hier finden Sie alle Dateien, die aktuell zur Ressource gehören. Sie können die Metadaten und Zugriffsrechte bearbeiten oder <strong>weitere Dateien hinzufügen, indem Sie sie einfach irgenwo auf diese Seite ziehen.</strong>",
|
||||
// "submission.sections.upload.no-entry": "No",
|
||||
"submission.sections.upload.no-entry": "Kein Eintrag",
|
||||
|
@@ -400,6 +400,14 @@
|
||||
|
||||
|
||||
|
||||
"communityList.tabTitle": "DSpace - Community List",
|
||||
|
||||
"communityList.title": "List of Communities",
|
||||
|
||||
"communityList.showMore": "Show More",
|
||||
|
||||
|
||||
|
||||
"community.create.head": "Create a Community",
|
||||
|
||||
"community.create.sub-head": "Create a Sub-Community for Community {{ parent }}",
|
||||
@@ -525,6 +533,9 @@
|
||||
"footer.link.duraspace": "DuraSpace",
|
||||
|
||||
|
||||
"form.add": "Add",
|
||||
|
||||
"form.add-help": "Click here to add the current entry and to add another one",
|
||||
|
||||
"form.cancel": "Cancel",
|
||||
|
||||
@@ -550,6 +561,10 @@
|
||||
|
||||
"form.loading": "Loading...",
|
||||
|
||||
"form.lookup": "Lookup",
|
||||
|
||||
"form.lookup-help": "Click here to look up an existing relation",
|
||||
|
||||
"form.no-results": "No results found",
|
||||
|
||||
"form.no-value": "No value entered",
|
||||
@@ -688,7 +703,7 @@
|
||||
|
||||
"item.edit.move.description": "Select the collection you wish to move this item to. To narrow down the list of displayed collections, you can enter a search query in the box.",
|
||||
|
||||
"item.edit.move.error": "An error occured when attempting to move the item",
|
||||
"item.edit.move.error": "An error occurred when attempting to move the item",
|
||||
|
||||
"item.edit.move.head": "Move item: {{id}}",
|
||||
|
||||
@@ -702,7 +717,7 @@
|
||||
|
||||
"item.edit.move.search.placeholder": "Enter a search query to look for collections",
|
||||
|
||||
"item.edit.move.success": "The item has been moved succesfully",
|
||||
"item.edit.move.success": "The item has been moved successfully",
|
||||
|
||||
"item.edit.move.title": "Move item",
|
||||
|
||||
@@ -888,9 +903,17 @@
|
||||
|
||||
"item.page.person.search.title": "Articles by this author",
|
||||
|
||||
"item.page.related-items.view-more": "View more",
|
||||
"item.page.related-items.view-more": "Show {{ amount }} more",
|
||||
|
||||
"item.page.related-items.view-less": "View less",
|
||||
"item.page.related-items.view-less": "Hide last {{ amount }}",
|
||||
|
||||
"item.page.relationships.isAuthorOfPublication": "Publications",
|
||||
|
||||
"item.page.relationships.isJournalOfPublication": "Publications",
|
||||
|
||||
"item.page.relationships.isOrgUnitOfPerson": "Authors",
|
||||
|
||||
"item.page.relationships.isOrgUnitOfProject": "Research Projects",
|
||||
|
||||
"item.page.subject": "Keywords",
|
||||
|
||||
@@ -1344,6 +1367,8 @@
|
||||
|
||||
"project.page.titleprefix": "Research Project: ",
|
||||
|
||||
"project.search.results.head": "Project Search Results",
|
||||
|
||||
|
||||
|
||||
"publication.listelement.badge": "Publication",
|
||||
@@ -1419,6 +1444,9 @@
|
||||
"search.filters.applied.f.subject": "Subject",
|
||||
|
||||
"search.filters.applied.f.submitter": "Submitter",
|
||||
"search.filters.applied.f.jobTitle": "Job Title",
|
||||
"search.filters.applied.f.birthDate.max": "End birth date",
|
||||
"search.filters.applied.f.birthDate.min": "Start birth date",
|
||||
|
||||
|
||||
|
||||
@@ -1587,6 +1615,69 @@
|
||||
"submission.general.save-later": "Save for later",
|
||||
|
||||
|
||||
"submission.sections.describe.relationship-lookup.close": "Close",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.search-tab.deselect-all": "Deselect all",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.search-tab.deselect-page": "Deselect page",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.search-tab.loading": "Loading...",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.search-tab.placeholder": "Search query",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.search-tab.search": "Go",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.search-tab.select-all": "Select all",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.search-tab.select-page": "Select page",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.selected": "Selected {{ size }} items",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Author": "Search for Authors",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal": "Search for Journals",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal Issue": "Search for Journal Issues",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Journal Volume": "Search for Journal Volumes",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Funding Agency": "Search for Funding Agencies",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.search-tab.tab-title.Funding": "Search for Funding",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.selection-tab.tab-title": "Current Selection ({{ count }})",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.title.Journal Issue": "Journal Issues",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.title.Journal Volume": "Journal Volumes",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.title.Journal": "Journals",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.title.Author": "Authors",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.title.Funding Agency": "Funding Agency",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.title.Funding": "Funding",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.search-tab.toggle-dropdown": "Toggle dropdown",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.selection-tab.settings": "Settings",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.selection-tab.no-selection": "Your selection is currently empty.",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.selection-tab.title.Author": "Selected Authors",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.selection-tab.title.Journal": "Selected Journals",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.selection-tab.title.Journal Volume": "Selected Journal Volume",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.selection-tab.title.Journal Issue": "Selected Issue",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.name-variant.notification.content": "Would you like to save \"{{ value }}\" as a name variant for this person so you and others can reuse it for future submissions? If you don\'t you can still use it for this submission.",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.name-variant.notification.confirm": "Save a new name variant",
|
||||
|
||||
"submission.sections.describe.relationship-lookup.name-variant.notification.decline": "Use only for this submission",
|
||||
|
||||
"submission.sections.general.add-more": "Add more",
|
||||
|
||||
@@ -1668,7 +1759,7 @@
|
||||
|
||||
"submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
|
||||
|
||||
"submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the fle metadata and access conditions or <strong>upload additional files just dragging & dropping them everywhere in the page</strong>",
|
||||
"submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or <strong>upload additional files just dragging & dropping them everywhere in the page</strong>",
|
||||
|
||||
"submission.sections.upload.no-entry": "No",
|
||||
|
||||
|
@@ -591,7 +591,7 @@
|
||||
"item.edit.move.cancel": "Cancelar",
|
||||
// "item.edit.move.description": "Select the collection you wish to move this item to. To narrow down the list of displayed collections, you can enter a search query in the box.",
|
||||
"item.edit.move.description": "Seleccione la colección a la que desea mover este ítem. Para reducir la lista de colecciones mostradas, puede ingresar una consulta de búsqueda en el cuadro.",
|
||||
// "item.edit.move.error": "An error occured when attempting to move the item",
|
||||
// "item.edit.move.error": "An error occurred when attempting to move the item",
|
||||
"item.edit.move.error": "Ha ocurrido un error cuando ha intentado mover el ítem",
|
||||
// "item.edit.move.head": "Move item: {{id}}",
|
||||
"item.edit.move.head": "Mover el ítem: {{id}}",
|
||||
@@ -605,7 +605,7 @@
|
||||
"item.edit.move.processing": "Moviendo...",
|
||||
// "item.edit.move.search.placeholder": "Enter a search query to look for collections",
|
||||
"item.edit.move.search.placeholder": "Ingrese una consulta para buscar colecciones",
|
||||
// "item.edit.move.success": "The item has been moved succesfully",
|
||||
// "item.edit.move.success": "The item has been moved successfully",
|
||||
"item.edit.move.success": "El ítem ha sido movido exitosamente",
|
||||
// "item.edit.move.title": "Move item",
|
||||
"item.edit.move.title": "Mover ítem",
|
||||
@@ -1521,7 +1521,7 @@
|
||||
"submission.sections.upload.header.policy.default.nolist": "Archivos subidos a la colección {{collectionName}} serán accesibles según el siguiente grupo(s):",
|
||||
// "submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
|
||||
"submission.sections.upload.header.policy.default.withlist": "Tenga en cuenta que los archivos cargados en la colección {{collectionName}} serán accesibles, además de lo que se decida explísitamente para el único archivo con el siguiente grupo(s):",
|
||||
// "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the fle metadata and access conditions or <strong>upload additional files just dragging & dropping them everywhere in the page</strong>",
|
||||
// "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or <strong>upload additional files just dragging & dropping them everywhere in the page</strong>",
|
||||
"submission.sections.upload.info": "Aquí encontrará todos los archivos aque actualmente están en el ítem.. Puede actualizar los metadatos del archivo y las condiciones de acceso o <strong> cargar los archivos adicionales simplemente arrastrándolos y soltándolos en cualquier parte de la página</strong>",
|
||||
// "submission.sections.upload.no-entry": "No",
|
||||
"submission.sections.upload.no-entry": "No",
|
||||
|
@@ -1122,9 +1122,9 @@
|
||||
// TODO New key - Add a translation
|
||||
"item.edit.move.description": "Select the collection you wish to move this item to. To narrow down the list of displayed collections, you can enter a search query in the box.",
|
||||
|
||||
// "item.edit.move.error": "An error occured when attempting to move the item",
|
||||
// "item.edit.move.error": "An error occurred when attempting to move the item",
|
||||
// TODO New key - Add a translation
|
||||
"item.edit.move.error": "An error occured when attempting to move the item",
|
||||
"item.edit.move.error": "An error occurred when attempting to move the item",
|
||||
|
||||
// "item.edit.move.head": "Move item: {{id}}",
|
||||
// TODO New key - Add a translation
|
||||
@@ -1150,9 +1150,9 @@
|
||||
// TODO New key - Add a translation
|
||||
"item.edit.move.search.placeholder": "Enter a search query to look for collections",
|
||||
|
||||
// "item.edit.move.success": "The item has been moved succesfully",
|
||||
// "item.edit.move.success": "The item has been moved successfully",
|
||||
// TODO New key - Add a translation
|
||||
"item.edit.move.success": "The item has been moved succesfully",
|
||||
"item.edit.move.success": "The item has been moved successfully",
|
||||
|
||||
// "item.edit.move.title": "Move item",
|
||||
// TODO New key - Add a translation
|
||||
@@ -2908,9 +2908,9 @@
|
||||
// TODO New key - Add a translation
|
||||
"submission.sections.upload.header.policy.default.withlist": "Please note that uploaded files in the {{collectionName}} collection will be accessible, in addition to what is explicitly decided for the single file, with the following group(s):",
|
||||
|
||||
// "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the fle metadata and access conditions or <strong>upload additional files just dragging & dropping them everywhere in the page</strong>",
|
||||
// "submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or <strong>upload additional files just dragging & dropping them everywhere in the page</strong>",
|
||||
// TODO New key - Add a translation
|
||||
"submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the fle metadata and access conditions or <strong>upload additional files just dragging & dropping them everywhere in the page</strong>",
|
||||
"submission.sections.upload.info": "Here you will find all the files currently in the item. You can update the file metadata and access conditions or <strong>upload additional files just dragging & dropping them everywhere in the page</strong>",
|
||||
|
||||
// "submission.sections.upload.no-entry": "No",
|
||||
// TODO New key - Add a translation
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@ import { PaginatedList } from '../../../core/data/paginated-list';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
|
||||
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 { hasValue } from '../../../shared/empty.util';
|
||||
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
|
||||
* Currently simply renders all bitstream formats
|
||||
*/
|
||||
config: FindAllOptions = Object.assign(new FindAllOptions(), {
|
||||
config: FindListOptions = Object.assign(new FindListOptions(), {
|
||||
elementsPerPage: 20
|
||||
});
|
||||
|
||||
@@ -145,7 +145,7 @@ export class BitstreamFormatsComponent implements OnInit {
|
||||
* @param event The page change event
|
||||
*/
|
||||
onPageChange(event) {
|
||||
this.config = Object.assign(new FindAllOptions(), this.config, {
|
||||
this.config = Object.assign(new FindListOptions(), this.config, {
|
||||
currentPage: event,
|
||||
});
|
||||
this.pageConfig.currentPage = event;
|
||||
|
@@ -13,7 +13,6 @@ import { combineLatest as combineLatestObservable } from 'rxjs';
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||
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 { 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 { 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';
|
||||
|
@@ -1,29 +1,23 @@
|
||||
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 { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
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 { ActivatedRouteStub } from '../../shared/testing/active-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 { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { NotificationsServiceStub } from '../../shared/testing/notifications-service-stub';
|
||||
import { ItemDataService } from '../../core/data/item-data.service';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
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 { 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 { HostWindowServiceStub } from '../../shared/testing/host-window-service-stub';
|
||||
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 { ObjectSelectServiceStub } from '../../shared/testing/object-select-service-stub';
|
||||
import { VarDirective } from '../../shared/utils/var.directive';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { of as observableOf, of } from 'rxjs/internal/observable/of';
|
||||
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 { ErrorComponent } from '../../shared/error/error.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', () => {
|
||||
let comp: CollectionItemMapperComponent;
|
||||
@@ -135,7 +130,6 @@ describe('CollectionItemMapperComponent', () => {
|
||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
|
||||
{ provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() },
|
||||
{ provide: RouteService, useValue: routeServiceStub },
|
||||
{ provide: SearchFixedFilterService, useValue: fixedFilterServiceStub }
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
@@ -5,12 +5,9 @@ import { fadeIn, fadeInOut } from '../../shared/animations/fade';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
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 { 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 { SearchService } from '../../+search-page/search-service/search.service';
|
||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||
import { DSpaceObjectType } from '../../core/shared/dspace-object-type.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 { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
||||
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({
|
||||
selector: 'ds-collection-item-mapper',
|
||||
|
@@ -3,6 +3,7 @@
|
||||
*ngVar="(collectionRD$ | async) as collectionRD">
|
||||
<div *ngIf="collectionRD?.hasSucceeded" @fadeInOut>
|
||||
<div *ngIf="collectionRD?.payload as collection">
|
||||
<ds-view-tracker [object]="collection"></ds-view-tracker>
|
||||
<header class="comcol-header border-bottom mb-4 pb-4">
|
||||
<!-- Collection logo -->
|
||||
<ds-comcol-page-logo *ngIf="logoRD$"
|
||||
|
@@ -2,8 +2,8 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { BehaviorSubject, of as observableOf, Observable, Subject } from 'rxjs';
|
||||
import { filter, flatMap, map, startWith, switchMap, take, tap } from 'rxjs/operators';
|
||||
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model';
|
||||
import { SearchService } from '../+search-page/search-service/search.service';
|
||||
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
||||
import { SearchService } from '../core/shared/search/search.service';
|
||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
||||
import { CollectionDataService } from '../core/data/collection-data.service';
|
||||
import { PaginatedList } from '../core/data/paginated-list';
|
||||
|
@@ -8,15 +8,16 @@ import { CollectionPageRoutingModule } from './collection-page-routing.module';
|
||||
import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component';
|
||||
import { CollectionFormComponent } from './collection-form/collection-form.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 { 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({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
CollectionPageRoutingModule
|
||||
CollectionPageRoutingModule,
|
||||
StatisticsModule.forRoot()
|
||||
],
|
||||
declarations: [
|
||||
CollectionPageComponent,
|
||||
@@ -30,7 +31,6 @@ import { SearchFixedFilterService } from '../+search-page/search-filters/search-
|
||||
],
|
||||
providers: [
|
||||
SearchService,
|
||||
SearchFixedFilterService
|
||||
]
|
||||
})
|
||||
export class CollectionPageModule {
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<div class="container" *ngVar="(communityRD$ | async) as communityRD">
|
||||
<div class="community-page" *ngIf="communityRD?.hasSucceeded" @fadeInOut>
|
||||
<div *ngIf="communityRD?.payload; let communityPayload">
|
||||
<ds-view-tracker [object]="communityPayload"></ds-view-tracker>
|
||||
<header class="comcol-header border-bottom mb-4 pb-4">
|
||||
<!-- Community logo -->
|
||||
<ds-comcol-page-logo *ngIf="logoRD$" [logo]="(logoRD$ | async)?.payload" [alternateText]="'Community Logo'">
|
||||
|
@@ -6,16 +6,18 @@ import { SharedModule } from '../shared/shared.module';
|
||||
import { CommunityPageComponent } from './community-page.component';
|
||||
import { CommunityPageSubCollectionListComponent } from './sub-collection-list/community-page-sub-collection-list.component';
|
||||
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 { CommunityFormComponent } from './community-form/community-form.component';
|
||||
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
|
||||
import { StatisticsModule } from '../statistics/statistics.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
CommunityPageRoutingModule
|
||||
CommunityPageRoutingModule,
|
||||
StatisticsModule.forRoot()
|
||||
],
|
||||
declarations: [
|
||||
CommunityPageComponent,
|
||||
|
@@ -2,12 +2,25 @@ import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { HomePageComponent } from './home-page.component';
|
||||
import { HomePageResolver } from './home-page.resolver';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
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>
|
||||
<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-top-level-community-list></ds-top-level-community-list>
|
||||
</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({
|
||||
selector: 'ds-home-page',
|
||||
styleUrls: ['./home-page.component.scss'],
|
||||
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 { TopLevelCommunityListComponent } from './top-level-community-list/top-level-community-list.component';
|
||||
import { StatisticsModule } from '../statistics/statistics.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
HomePageRoutingModule
|
||||
HomePageRoutingModule,
|
||||
StatisticsModule.forRoot()
|
||||
],
|
||||
declarations: [
|
||||
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 { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { ItemCollectionMapperComponent } from './item-collection-mapper.component';
|
||||
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 { ItemDataService } from '../../../core/data/item-data.service';
|
||||
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 { SortDirection, SortOptions } from '../../../core/cache/models/sort-options.model';
|
||||
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 { PageInfo } from '../../../core/shared/page-info.model';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { SharedModule } from '../../../shared/shared.module';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
|
||||
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 { ObjectSelectService } from '../../../shared/object-select/object-select.service';
|
||||
import { ObjectSelectServiceStub } from '../../../shared/testing/object-select-service-stub';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { of } from 'rxjs/internal/observable/of';
|
||||
import { RestResponse } from '../../../core/cache/response.models';
|
||||
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 { ErrorComponent } from '../../../shared/error/error.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', () => {
|
||||
let comp: ItemCollectionMapperComponent;
|
||||
|
@@ -2,15 +2,12 @@ import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||
|
||||
import { ChangeDetectionStrategy, Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { fadeIn, fadeInOut } from '../../../shared/animations/fade';
|
||||
import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { getRemoteDataPayload, getSucceededRemoteData, toDSpaceObjectListRD } from '../../../core/shared/operators';
|
||||
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 { ItemDataService } from '../../../core/data/item-data.service';
|
||||
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 { RestResponse } from '../../../core/cache/response.models';
|
||||
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({
|
||||
selector: 'ds-item-collection-mapper',
|
||||
|
@@ -9,7 +9,6 @@ import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { ItemMoveComponent } from './item-move.component';
|
||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { SearchService } from '../../../+search-page/search-service/search.service';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
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 { RestResponse } from '../../../core/cache/response.models';
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { SearchService } from '../../../core/shared/search/search.service';
|
||||
|
||||
describe('ItemMoveComponent', () => {
|
||||
let comp: ItemMoveComponent;
|
||||
|
@@ -1,12 +1,9 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { SearchService } from '../../../+search-page/search-service/search.service';
|
||||
import { first, map } from 'rxjs/operators';
|
||||
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 { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||
import { SearchResult } from '../../../+search-page/search-result.model';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
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 { RestResponse } from '../../../core/cache/response.models';
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { tap } from 'rxjs/internal/operators/tap';
|
||||
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({
|
||||
selector: 'ds-item-move',
|
||||
|
@@ -156,7 +156,9 @@ describe('ItemRelationshipsComponent', () => {
|
||||
getRelatedItemsByLabel: observableOf([author1, author2]),
|
||||
getItemRelationshipsArray: observableOf(relationships),
|
||||
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 { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { filter, map, switchMap, take, tap } from 'rxjs/operators';
|
||||
import { combineLatest as observableCombineLatest, zip as observableZip } from 'rxjs';
|
||||
import { filter, flatMap, map, switchMap, take, tap } from 'rxjs/operators';
|
||||
import { zip as observableZip } from 'rxjs';
|
||||
import { AbstractItemUpdateComponent } from '../abstract-item-update/abstract-item-update.component';
|
||||
import { ItemDataService } from '../../../core/data/item-data.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 { RequestService } from '../../../core/data/request.service';
|
||||
import { Subscription } from 'rxjs/internal/Subscription';
|
||||
import { getRelationsByRelatedItemIds } from '../../simple/item-types/shared/item-relationships-utils';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-relationships',
|
||||
@@ -65,7 +64,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
super.ngOnInit();
|
||||
this.relationLabels$ = this.relationshipService.getItemRelationshipLabels(this.item);
|
||||
this.relationLabels$ = this.relationshipService.getRelationshipTypeLabelsByItem(this.item);
|
||||
this.initializeItemUpdate();
|
||||
}
|
||||
|
||||
@@ -113,8 +112,9 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent impl
|
||||
);
|
||||
// Get all the relationships that should be removed
|
||||
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
|
||||
removedRelationships$.pipe(
|
||||
take(1),
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<div class="container" *ngVar="(itemRD$ | async) as itemRD">
|
||||
<div class="item-page" *ngIf="itemRD?.hasSucceeded" @fadeInOut>
|
||||
<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>
|
||||
<div class="simple-view-link my-3">
|
||||
<a class="btn btn-outline-primary" [routerLink]="['/items/' + item.id]">
|
||||
|
@@ -26,6 +26,9 @@ import { MetadataRepresentationListComponent } from './simple/metadata-represent
|
||||
import { RelatedEntitiesSearchComponent } from './simple/related-entities/related-entities-search/related-entities-search.component';
|
||||
import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.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({
|
||||
imports: [
|
||||
@@ -33,7 +36,8 @@ import { MetadataFieldWrapperComponent } from './field-components/metadata-field
|
||||
SharedModule,
|
||||
ItemPageRoutingModule,
|
||||
EditItemPageModule,
|
||||
SearchPageModule
|
||||
SearchPageModule,
|
||||
StatisticsModule.forRoot()
|
||||
],
|
||||
declarations: [
|
||||
ItemPageComponent,
|
||||
@@ -53,7 +57,9 @@ import { MetadataFieldWrapperComponent } from './field-components/metadata-field
|
||||
ItemComponent,
|
||||
GenericItemPageFieldComponent,
|
||||
MetadataRepresentationListComponent,
|
||||
RelatedEntitiesSearchComponent
|
||||
RelatedEntitiesSearchComponent,
|
||||
TabbedRelatedEntitiesSearchComponent,
|
||||
AbstractIncrementalListComponent
|
||||
],
|
||||
exports: [
|
||||
ItemComponent,
|
||||
@@ -63,7 +69,8 @@ import { MetadataFieldWrapperComponent } from './field-components/metadata-field
|
||||
RelatedEntitiesSearchComponent,
|
||||
RelatedItemsComponent,
|
||||
MetadataRepresentationListComponent,
|
||||
ItemPageTitleFieldComponent
|
||||
ItemPageTitleFieldComponent,
|
||||
TabbedRelatedEntitiesSearchComponent
|
||||
],
|
||||
entryComponents: [
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
<div class="container" *ngVar="(itemRD$ | async) as itemRD">
|
||||
<div class="item-page" *ngIf="itemRD?.hasSucceeded" @fadeInOut>
|
||||
<div *ngIf="itemRD?.payload as item">
|
||||
<ds-view-tracker [object]="item"></ds-view-tracker>
|
||||
<ds-listable-object-component-loader [object]="item" [viewMode]="viewMode"></ds-listable-object-component-loader>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -4,7 +4,6 @@ import { MockTranslateLoader } from '../../../../shared/mocks/mock-translate-loa
|
||||
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
|
||||
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
||||
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 { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
@@ -15,6 +14,7 @@ import { createRelationshipsObservable } from '../shared/item.component.spec';
|
||||
import { PublicationComponent } from './publication.component';
|
||||
import { MetadataMap } from '../../../../core/shared/metadata.models';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
|
||||
import { RelationshipService } from '../../../../core/data/relationship.service';
|
||||
|
||||
const mockItem: Item = Object.assign(new Item(), {
|
||||
bundles: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
|
||||
@@ -26,12 +26,6 @@ describe('PublicationComponent', () => {
|
||||
let comp: PublicationComponent;
|
||||
let fixture: ComponentFixture<PublicationComponent>;
|
||||
|
||||
const searchFixedFilterServiceStub = {
|
||||
/* tslint:disable:no-empty */
|
||||
getQueryByRelations: () => {}
|
||||
/* tslint:enable:no-empty */
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot({
|
||||
@@ -43,8 +37,8 @@ describe('PublicationComponent', () => {
|
||||
declarations: [PublicationComponent, GenericItemPageFieldComponent, TruncatePipe],
|
||||
providers: [
|
||||
{provide: ItemDataService, useValue: {}},
|
||||
{provide: SearchFixedFilterService, useValue: searchFixedFilterServiceStub},
|
||||
{provide: TruncatableService, useValue: {}}
|
||||
{provide: TruncatableService, useValue: {}},
|
||||
{provide: RelationshipService, useValue: {}}
|
||||
],
|
||||
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { ItemComponent } from '../shared/item.component';
|
||||
import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { ItemComponent } from '../shared/item.component';
|
||||
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||
import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||
|
||||
/**
|
||||
* Component that represents a publication Item page
|
||||
|
@@ -1,14 +1,12 @@
|
||||
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||
import { hasNoValue, hasValue } from '../../../../shared/empty.util';
|
||||
import { getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||
import { hasValue } from '../../../../shared/empty.util';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
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 { zip as observableZip, combineLatest as observableCombineLatest } from 'rxjs';
|
||||
import { combineLatest as observableCombineLatest, zip as observableZip } from 'rxjs';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
import { RelationshipService } from '../../../../core/data/relationship.service';
|
||||
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
|
||||
/**
|
||||
* Operator for comparing arrays using a mapping function
|
||||
@@ -37,36 +35,6 @@ export const compareArraysUsing = <T>(mapFn: (t: T) => any) =>
|
||||
export const compareArraysUsingIds = <T extends { id: string }>() =>
|
||||
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
|
||||
* @param {string} thisId The item's id of which the relations belong to
|
||||
@@ -128,17 +96,3 @@ export const paginatedRelationsToItems = (thisId: string) =>
|
||||
)
|
||||
})
|
||||
);
|
||||
|
||||
/**
|
||||
* 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))
|
||||
))
|
||||
);
|
||||
|
@@ -9,7 +9,6 @@ import { MockTranslateLoader } from '../../../../shared/mocks/mock-translate-loa
|
||||
import { ChangeDetectionStrategy, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
|
||||
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 { PaginatedList } from '../../../../core/data/paginated-list';
|
||||
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
||||
@@ -24,6 +23,7 @@ import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-rep
|
||||
import { MetadataMap, MetadataValue } from '../../../../core/shared/metadata.models';
|
||||
import { compareArraysUsing, compareArraysUsingIds } from './item-relationships-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
|
||||
@@ -37,12 +37,6 @@ export function getItemPageFieldsTest(mockItem: Item, component) {
|
||||
let comp: any;
|
||||
let fixture: ComponentFixture<any>;
|
||||
|
||||
const searchFixedFilterServiceStub = {
|
||||
/* tslint:disable:no-empty */
|
||||
getQueryByRelations: () => {}
|
||||
/* tslint:enable:no-empty */
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot({
|
||||
@@ -54,8 +48,8 @@ export function getItemPageFieldsTest(mockItem: Item, component) {
|
||||
declarations: [component, GenericItemPageFieldComponent, TruncatePipe],
|
||||
providers: [
|
||||
{provide: ItemDataService, useValue: {}},
|
||||
{provide: SearchFixedFilterService, useValue: searchFixedFilterServiceStub},
|
||||
{provide: TruncatableService, useValue: {}}
|
||||
{provide: TruncatableService, useValue: {}},
|
||||
{provide: RelationshipService, useValue: {}}
|
||||
],
|
||||
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Component, Inject, Input } from '@angular/core';
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
|
||||
@Component({
|
||||
|
@@ -1,11 +1,20 @@
|
||||
<ds-metadata-field-wrapper *ngIf="representations$ && (representations$ | async)?.length > 0" [label]="label">
|
||||
<ds-metadata-representation-loader *ngFor="let rep of (representations$ | async)"
|
||||
<ds-metadata-field-wrapper [label]="label">
|
||||
<ng-container *ngFor="let objectPage of objects; let i = index">
|
||||
<ng-container *ngVar="(objectPage | async) as representations">
|
||||
<ds-metadata-representation-loader *ngFor="let rep of representations"
|
||||
[mdRepresentation]="rep">
|
||||
</ds-metadata-representation-loader>
|
||||
<div *ngIf="(representations$ | async)?.length < total" class="mt-2">
|
||||
<a [routerLink]="" (click)="viewMore()">{{'item.page.related-items.view-more' | translate}}</a>
|
||||
<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="limit > originalLimit" class="mt-2">
|
||||
<a [routerLink]="" (click)="viewLess()">{{'item.page.related-items.view-less' | translate}}</a>
|
||||
<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>
|
||||
|
@@ -7,6 +7,8 @@ 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 = 'Person';
|
||||
const metadataField = 'dc.contributor.author';
|
||||
@@ -64,7 +66,7 @@ describe('MetadataRepresentationListComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot()],
|
||||
declarations: [MetadataRepresentationListComponent],
|
||||
declarations: [MetadataRepresentationListComponent, VarDirective],
|
||||
providers: [
|
||||
{ provide: RelationshipService, useValue: relationshipService }
|
||||
],
|
||||
@@ -88,33 +90,29 @@ describe('MetadataRepresentationListComponent', () => {
|
||||
expect(fields.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should initialize the original limit', () => {
|
||||
expect(comp.originalLimit).toEqual(comp.limit);
|
||||
it('should contain one page of items', () => {
|
||||
expect(comp.objects.length).toEqual(1);
|
||||
});
|
||||
|
||||
describe('when viewMore is called', () => {
|
||||
describe('when increase is called', () => {
|
||||
beforeEach(() => {
|
||||
comp.viewMore();
|
||||
comp.increase();
|
||||
});
|
||||
|
||||
it('should set the limit to a high number in order to retrieve all metadata representations', () => {
|
||||
expect(comp.limit).toBeGreaterThanOrEqual(999);
|
||||
it('should add a new page to the list', () => {
|
||||
expect(comp.objects.length).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when viewLess is called', () => {
|
||||
let originalLimit;
|
||||
|
||||
describe('when decrease is called', () => {
|
||||
beforeEach(() => {
|
||||
// Store the original value of limit
|
||||
originalLimit = comp.limit;
|
||||
// Set limit to a random number
|
||||
comp.limit = 458;
|
||||
comp.viewLess();
|
||||
// Add a second page
|
||||
comp.objects.push(observableOf(undefined));
|
||||
comp.decrease();
|
||||
});
|
||||
|
||||
it('should reset the limit to the original value', () => {
|
||||
expect(comp.limit).toEqual(originalLimit);
|
||||
it('should decrease the list of pages', () => {
|
||||
expect(comp.objects.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -1,16 +1,16 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { combineLatest as observableCombineLatest, Observable, of as observableOf, zip as observableZip } from 'rxjs';
|
||||
import { RelationshipService } from '../../../core/data/relationship.service';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { combineLatest as observableCombineLatest, of as observableOf, zip as observableZip } from 'rxjs';
|
||||
import { MetadataValue } from '../../../core/shared/metadata.models';
|
||||
import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
||||
import { filter, map, switchMap } from 'rxjs/operators';
|
||||
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({
|
||||
selector: 'ds-metadata-representation-list',
|
||||
@@ -22,7 +22,7 @@ import { ItemMetadataRepresentation } from '../../../core/shared/metadata-repres
|
||||
* 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 implements OnInit {
|
||||
export class MetadataRepresentationListComponent extends AbstractIncrementalListComponent<Observable<MetadataRepresentation[]>> {
|
||||
/**
|
||||
* The parent of the list of related items to display
|
||||
*/
|
||||
@@ -44,22 +44,11 @@ export class MetadataRepresentationListComponent implements OnInit {
|
||||
@Input() label: string;
|
||||
|
||||
/**
|
||||
* The max amount of representations to display
|
||||
* The amount to increment the list by when clicking "view more"
|
||||
* Defaults to 10
|
||||
* The default can optionally be overridden by providing the limit as input to the component
|
||||
*/
|
||||
@Input() limit = 10;
|
||||
|
||||
/**
|
||||
* A list of metadata-representations to display
|
||||
*/
|
||||
representations$: Observable<MetadataRepresentation[]>;
|
||||
|
||||
/**
|
||||
* The originally provided limit
|
||||
* Used for resetting the limit to the original value when collapsing the list
|
||||
*/
|
||||
originalLimit: number;
|
||||
@Input() incrementBy = 10;
|
||||
|
||||
/**
|
||||
* The total amount of metadata values available
|
||||
@@ -67,30 +56,28 @@ export class MetadataRepresentationListComponent implements OnInit {
|
||||
total: number;
|
||||
|
||||
constructor(public relationshipService: RelationshipService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.originalLimit = this.limit;
|
||||
this.setRepresentations();
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the metadata representations
|
||||
* Get a specific page
|
||||
* @param page The page to fetch
|
||||
*/
|
||||
setRepresentations() {
|
||||
getPage(page: number): Observable<MetadataRepresentation[]> {
|
||||
const metadata = this.parentItem.findMetadataSortedByPlace(this.metadataField);
|
||||
this.total = metadata.length;
|
||||
this.representations$ = this.resolveMetadataRepresentations(metadata);
|
||||
return this.resolveMetadataRepresentations(metadata, page);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a list of metadata values to a list of metadata representations
|
||||
* @param metadata
|
||||
* @param metadata The list of all metadata values
|
||||
* @param page The page to return representations for
|
||||
*/
|
||||
resolveMetadataRepresentations(metadata: MetadataValue[]): Observable<MetadataRepresentation[]> {
|
||||
resolveMetadataRepresentations(metadata: MetadataValue[], page: number): Observable<MetadataRepresentation[]> {
|
||||
return observableZip(
|
||||
...metadata
|
||||
.slice(0, this.limit)
|
||||
.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) {
|
||||
@@ -115,20 +102,4 @@ export class MetadataRepresentationListComponent implements OnInit {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the list to display all metadata representations
|
||||
*/
|
||||
viewMore() {
|
||||
this.limit = 9999;
|
||||
this.setRepresentations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse the list to display the originally displayed metadata representations
|
||||
*/
|
||||
viewLess() {
|
||||
this.limit = this.originalLimit;
|
||||
this.setRepresentations();
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
<ds-filtered-search-page
|
||||
<ds-configuration-search-page
|
||||
[fixedFilterQuery]="fixedFilter"
|
||||
[configuration]="configuration"
|
||||
[configuration$]="configuration$"
|
||||
[searchEnabled]="searchEnabled"
|
||||
[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 { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
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';
|
||||
|
||||
describe('RelatedEntitiesSearchComponent', () => {
|
||||
let comp: RelatedEntitiesSearchComponent;
|
||||
let fixture: ComponentFixture<RelatedEntitiesSearchComponent>;
|
||||
let fixedFilterService: SearchFixedFilterService;
|
||||
|
||||
const mockItem = Object.assign(new Item(), {
|
||||
id: 'id1'
|
||||
});
|
||||
const mockRelationType = 'publicationsOfAuthor';
|
||||
const mockRelationEntityType = 'publication';
|
||||
const mockConfiguration = 'publication';
|
||||
const mockFilter= `f.${mockRelationType}=${mockItem.id}`;
|
||||
const fixedFilterServiceStub = {
|
||||
getFilterByRelation: () => mockFilter
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],
|
||||
declarations: [RelatedEntitiesSearchComponent],
|
||||
providers: [
|
||||
{ provide: SearchFixedFilterService, useValue: fixedFilterServiceStub }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
@@ -36,10 +28,9 @@ describe('RelatedEntitiesSearchComponent', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(RelatedEntitiesSearchComponent);
|
||||
comp = fixture.componentInstance;
|
||||
fixedFilterService = (comp as any).fixedFilterService;
|
||||
comp.relationType = mockRelationType;
|
||||
comp.item = mockItem;
|
||||
comp.relationEntityType = mockRelationEntityType;
|
||||
comp.configuration = mockConfiguration;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
@@ -49,7 +40,7 @@ describe('RelatedEntitiesSearchComponent', () => {
|
||||
|
||||
it('should create a 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 { Observable } from 'rxjs';
|
||||
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 { of } from 'rxjs/internal/observable/of';
|
||||
import { getFilterByRelation } from '../../../../shared/utils/relation-query.utils';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-related-entities-search',
|
||||
@@ -22,18 +22,16 @@ export class RelatedEntitiesSearchComponent implements OnInit {
|
||||
*/
|
||||
@Input() relationType: string;
|
||||
|
||||
/**
|
||||
* An optional configuration to use for the search options
|
||||
*/
|
||||
@Input() configuration: string;
|
||||
|
||||
/**
|
||||
* The item to render relationships for
|
||||
*/
|
||||
@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)
|
||||
* @type {boolean}
|
||||
@@ -49,15 +47,12 @@ export class RelatedEntitiesSearchComponent implements OnInit {
|
||||
fixedFilter: string;
|
||||
configuration$: Observable<string>;
|
||||
|
||||
constructor(private fixedFilterService: SearchFixedFilterService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
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)) {
|
||||
this.configuration$ = of(this.relationEntityType);
|
||||
if (isNotEmpty(this.configuration)) {
|
||||
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,12 +1,12 @@
|
||||
import { Component, HostBinding, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Item } from '../../../core/shared/item.model';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||
import { RelationshipService } from '../../../core/data/relationship.service';
|
||||
import { FindAllOptions } from '../../../core/data/request.models';
|
||||
import { Subscription } from 'rxjs/internal/Subscription';
|
||||
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({
|
||||
selector: 'ds-related-items',
|
||||
@@ -17,7 +17,7 @@ import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||
* This component is used for displaying relations between items
|
||||
* It expects a parent item and relationship type, as well as a label to display on top
|
||||
*/
|
||||
export class RelatedItemsComponent implements OnInit, OnDestroy {
|
||||
export class RelatedItemsComponent extends AbstractIncrementalListComponent<Observable<RemoteData<PaginatedList<Item>>>> {
|
||||
/**
|
||||
* The parent of the list of related items to display
|
||||
*/
|
||||
@@ -30,79 +30,38 @@ export class RelatedItemsComponent implements OnInit, OnDestroy {
|
||||
@Input() relationType: string;
|
||||
|
||||
/**
|
||||
* Default options to start a search request with
|
||||
* Optional input, should you wish a different page size (or other options)
|
||||
* 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() options = Object.assign(new FindAllOptions(), { elementsPerPage: 5 });
|
||||
@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)
|
||||
*/
|
||||
@Input() label: string;
|
||||
|
||||
/**
|
||||
* Completely hide the component until there's at least one item visible
|
||||
*/
|
||||
@HostBinding('class.d-none') hidden = true;
|
||||
|
||||
/**
|
||||
* The list of related items
|
||||
*/
|
||||
items$: Observable<RemoteData<PaginatedList<Item>>>;
|
||||
|
||||
/**
|
||||
* Search options for displaying all elements in a list
|
||||
*/
|
||||
allOptions = Object.assign(new FindAllOptions(), { elementsPerPage: 9999 });
|
||||
|
||||
/**
|
||||
* The view-mode we're currently on
|
||||
* @type {ElementViewMode}
|
||||
* @type {ViewMode}
|
||||
*/
|
||||
viewMode = ViewMode.ListElement;
|
||||
|
||||
/**
|
||||
* Whether or not the list is currently expanded to show all related items
|
||||
*/
|
||||
showingAll = false;
|
||||
|
||||
/**
|
||||
* Subscription on items used to update the "hidden" property of this component
|
||||
*/
|
||||
itemSub: Subscription;
|
||||
|
||||
constructor(public relationshipService: RelationshipService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.items$ = this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, this.options);
|
||||
this.itemSub = this.items$.subscribe((itemsRD: RemoteData<PaginatedList<Item>>) => {
|
||||
this.hidden = !(itemsRD.hasSucceeded && itemsRD.payload && itemsRD.payload.page.length > 0);
|
||||
});
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand the list to display all related items
|
||||
* Get a specific page
|
||||
* @param page The page to fetch
|
||||
*/
|
||||
viewMore() {
|
||||
this.items$ = this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, this.allOptions);
|
||||
this.showingAll = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse the list to display the originally displayed items
|
||||
*/
|
||||
viewLess() {
|
||||
this.items$ = this.relationshipService.getRelatedItemsByLabel(this.parentItem, this.relationType, this.options);
|
||||
this.showingAll = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from the item subscription
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
if (this.itemSub) {
|
||||
this.itemSub.unsubscribe();
|
||||
}
|
||||
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,11 +1,20 @@
|
||||
<ds-metadata-field-wrapper *ngIf="(items$ | async)?.payload?.page?.length > 0" [label]="label">
|
||||
<ds-listable-object-component-loader *ngFor="let item of (items$ | async)?.payload?.page"
|
||||
<ds-metadata-field-wrapper [label]="label">
|
||||
<ng-container *ngFor="let objectPage of objects; let i = index">
|
||||
<ng-container *ngVar="(objectPage | async) as itemsRD">
|
||||
<ds-listable-object-component-loader *ngFor="let item of itemsRD?.payload?.page"
|
||||
[object]="item" [viewMode]="viewMode">
|
||||
</ds-listable-object-component-loader>
|
||||
<div *ngIf="(items$ | async)?.payload?.page?.length < (items$ | async)?.payload?.totalElements" class="mt-2" id="view-more">
|
||||
<a [routerLink]="" (click)="viewMore()">{{'item.page.related-items.view-more' | translate}}</a>
|
||||
<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="showingAll" class="mt-2" id="view-less">
|
||||
<a [routerLink]="" (click)="viewLess()">{{'item.page.related-items.view-less' | translate}}</a>
|
||||
<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>
|
||||
|
@@ -9,6 +9,8 @@ import { createRelationshipsObservable } from '../item-types/shared/item.compone
|
||||
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(), [])),
|
||||
@@ -42,7 +44,7 @@ describe('RelatedItemsComponent', () => {
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot()],
|
||||
declarations: [RelatedItemsComponent],
|
||||
declarations: [RelatedItemsComponent, VarDirective],
|
||||
providers: [
|
||||
{ provide: RelationshipService, useValue: relationshipService }
|
||||
],
|
||||
@@ -65,31 +67,33 @@ describe('RelatedItemsComponent', () => {
|
||||
expect(fields.length).toBe(mockItems.length);
|
||||
});
|
||||
|
||||
describe('when viewMore is called', () => {
|
||||
it('should contain one page of items', () => {
|
||||
expect(comp.objects.length).toEqual(1);
|
||||
});
|
||||
|
||||
describe('when increase is called', () => {
|
||||
beforeEach(() => {
|
||||
comp.viewMore();
|
||||
comp.increase();
|
||||
});
|
||||
|
||||
it('should call relationship-service\'s getRelatedItemsByLabel with the correct arguments', () => {
|
||||
expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, comp.allOptions);
|
||||
it('should add a new page to the list', () => {
|
||||
expect(comp.objects.length).toEqual(2);
|
||||
});
|
||||
|
||||
it('should set showingAll to true', () => {
|
||||
expect(comp.showingAll).toEqual(true);
|
||||
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 viewLess is called', () => {
|
||||
describe('when decrease is called', () => {
|
||||
beforeEach(() => {
|
||||
comp.viewLess();
|
||||
// Add a second page
|
||||
comp.objects.push(observableOf(undefined));
|
||||
comp.decrease();
|
||||
});
|
||||
|
||||
it('should call relationship-service\'s getRelatedItemsByLabel with the correct arguments', () => {
|
||||
expect(relationshipService.getRelatedItemsByLabel).toHaveBeenCalledWith(parentItem, relationType, comp.options);
|
||||
});
|
||||
|
||||
it('should set showingAll to false', () => {
|
||||
expect(comp.showingAll).toEqual(false);
|
||||
it('should decrease the list of pages', () => {
|
||||
expect(comp.objects.length).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import { of as observableOf } from 'rxjs';
|
||||
|
||||
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 { 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 { MockRoleService } from '../shared/mocks/mock-role-service';
|
||||
import { cold, hot } from 'jasmine-marbles';
|
||||
@@ -38,12 +38,8 @@ describe('MyDSpaceConfigurationService', () => {
|
||||
|
||||
const roleService: any = new MockRoleService();
|
||||
|
||||
const fixedFilterService = jasmine.createSpyObj('SearchFixedFilterService', {
|
||||
getQueryByFilterName: observableOf(''),
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
service = new MyDSpaceConfigurationService(roleService, fixedFilterService, spy, activatedRoute);
|
||||
service = new MyDSpaceConfigurationService(roleService, spy, activatedRoute);
|
||||
});
|
||||
|
||||
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 { RoleService } from '../core/roles/role.service';
|
||||
import { SearchConfigurationOption } from '../+search-page/search-switch-configuration/search-configuration-option.model';
|
||||
import { SearchConfigurationService } from '../+search-page/search-service/search-configuration.service';
|
||||
import { RouteService } from '../core/services/route.service';
|
||||
import { SearchConfigurationOption } from '../shared/search/search-switch-configuration/search-configuration-option.model';
|
||||
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-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
|
||||
@@ -55,16 +54,14 @@ export class MyDSpaceConfigurationService extends SearchConfigurationService {
|
||||
* Initialize class
|
||||
*
|
||||
* @param {roleService} roleService
|
||||
* @param {SearchFixedFilterService} fixedFilterService
|
||||
* @param {RouteService} routeService
|
||||
* @param {ActivatedRoute} route
|
||||
*/
|
||||
constructor(protected roleService: RoleService,
|
||||
protected fixedFilterService: SearchFixedFilterService,
|
||||
protected routeService: RouteService,
|
||||
protected route: ActivatedRoute) {
|
||||
|
||||
super(routeService, fixedFilterService, route);
|
||||
super(routeService, route);
|
||||
|
||||
// override parent class initialization
|
||||
this._defaults = null;
|
||||
|
@@ -14,7 +14,7 @@ import { UploaderOptions } from '../../shared/uploader/uploader-options.model';
|
||||
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
|
||||
import { NotificationType } from '../../shared/notifications/models/notification-type';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { SearchResult } from '../../+search-page/search-result.model';
|
||||
import { SearchResult } from '../../shared/search/search-result.model';
|
||||
|
||||
/**
|
||||
* This component represents the whole mydspace page header
|
||||
|
@@ -1 +1 @@
|
||||
@import '../+search-page/search-page.component.scss';
|
||||
@import '../+search-page/search.component.scss';
|
||||
|
@@ -19,15 +19,14 @@ import { MyDSpacePageComponent, SEARCH_CONFIG_SERVICE } from './my-dspace-page.c
|
||||
import { RouteService } from '../core/services/route.service';
|
||||
import { routeServiceStub } from '../shared/testing/route-service-stub';
|
||||
import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub';
|
||||
import { SearchService } from '../+search-page/search-service/search.service';
|
||||
import { SearchConfigurationService } from '../+search-page/search-service/search-configuration.service';
|
||||
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model';
|
||||
import { SearchSidebarService } from '../+search-page/search-sidebar/search-sidebar.service';
|
||||
import { SearchFilterService } from '../+search-page/search-filters/search-filter/search-filter.service';
|
||||
import { SearchService } from '../core/shared/search/search.service';
|
||||
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
||||
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||
import { SearchFilterService } from '../core/shared/search/search-filter.service';
|
||||
import { RoleDirective } from '../shared/roles/role.directive';
|
||||
import { RoleService } from '../core/roles/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';
|
||||
|
||||
describe('MyDSpacePageComponent', () => {
|
||||
@@ -82,8 +81,6 @@ describe('MyDSpacePageComponent', () => {
|
||||
collapse: () => this.isCollapsed = observableOf(true),
|
||||
expand: () => this.isCollapsed = observableOf(false)
|
||||
};
|
||||
const mockFixedFilterService: SearchFixedFilterService = {
|
||||
} as SearchFixedFilterService;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -109,7 +106,7 @@ describe('MyDSpacePageComponent', () => {
|
||||
})
|
||||
},
|
||||
{
|
||||
provide: SearchSidebarService,
|
||||
provide: SidebarService,
|
||||
useValue: sidebarService
|
||||
},
|
||||
{
|
||||
@@ -123,10 +120,6 @@ describe('MyDSpacePageComponent', () => {
|
||||
provide: RoleService,
|
||||
useValue: new MockRoleService()
|
||||
},
|
||||
{
|
||||
provide: SearchFixedFilterService,
|
||||
useValue: mockFixedFilterService
|
||||
}
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).overrideComponent(MyDSpacePageComponent, {
|
||||
|
@@ -15,19 +15,19 @@ import { RemoteData } from '../core/data/remote-data';
|
||||
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
||||
import { pushInOut } from '../shared/animations/push';
|
||||
import { HostWindowService } from '../shared/host-window.service';
|
||||
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model';
|
||||
import { SearchService } from '../+search-page/search-service/search.service';
|
||||
import { SearchSidebarService } from '../+search-page/search-sidebar/search-sidebar.service';
|
||||
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
||||
import { SearchService } from '../core/shared/search/search.service';
|
||||
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
import { getSucceededRemoteData } from '../core/shared/operators';
|
||||
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 { 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 { ViewMode } from '../core/shared/view-mode.model';
|
||||
import { MyDSpaceRequest } from '../core/data/request.models';
|
||||
import { SearchResult } from '../+search-page/search-result.model';
|
||||
import { SearchResult } from '../shared/search/search-result.model';
|
||||
import { Context } from '../core/shared/context.model';
|
||||
|
||||
export const MYDSPACE_ROUTE = '/mydspace';
|
||||
@@ -102,7 +102,7 @@ export class MyDSpacePageComponent implements OnInit {
|
||||
context$: Observable<Context>;
|
||||
|
||||
constructor(private service: SearchService,
|
||||
private sidebarService: SearchSidebarService,
|
||||
private sidebarService: SidebarService,
|
||||
private windowService: HostWindowService,
|
||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: MyDSpaceConfigurationService) {
|
||||
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
||||
|
@@ -5,7 +5,6 @@ import { SharedModule } from '../shared/shared.module';
|
||||
|
||||
import { MyDspacePageRoutingModule } from './my-dspace-page-routing.module';
|
||||
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 { WorkspaceItemSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component';
|
||||
import { ClaimedSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component';
|
||||
@@ -27,7 +26,6 @@ import { PoolSearchResultDetailElementComponent } from '../shared/object-detail/
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
MyDspacePageRoutingModule,
|
||||
SearchPageModule
|
||||
],
|
||||
declarations: [
|
||||
MyDSpacePageComponent,
|
||||
|
@@ -2,12 +2,12 @@ import { Component, Input } from '@angular/core';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
||||
import { fadeIn, fadeInOut } from '../../shared/animations/fade';
|
||||
import { SearchOptions } from '../../+search-page/search-options.model';
|
||||
import { SearchOptions } from '../../shared/search/search-options.model';
|
||||
import { PaginatedList } from '../../core/data/paginated-list';
|
||||
import { ViewMode } from '../../core/shared/view-mode.model';
|
||||
import { isEmpty } from '../../shared/empty.util';
|
||||
import { SearchResult } from '../../+search-page/search-result.model';
|
||||
import { Context } from '../../core/shared/context.model';
|
||||
import { SearchResult } from '../../shared/search/search-result.model';
|
||||
|
||||
/**
|
||||
* Component that represents all results for mydspace page
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { configureSearchComponentTestingModule } from './search-page.component.spec';
|
||||
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
||||
import { configureSearchComponentTestingModule } from './search.component.spec';
|
||||
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
||||
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||
|
||||
describe('ConfigurationSearchPageComponent', () => {
|
||||
let comp: ConfigurationSearchPageComponent;
|
||||
|
@@ -1,23 +1,22 @@
|
||||
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 { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||
import { SearchComponent } from './search.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 { 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 { SearchService } from '../core/shared/search/search.service';
|
||||
|
||||
/**
|
||||
* This component renders a search page using a configuration as input.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-configuration-search-page',
|
||||
styleUrls: ['./search-page.component.scss'],
|
||||
templateUrl: './search-page.component.html',
|
||||
styleUrls: ['./search.component.scss'],
|
||||
templateUrl: './search.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
animations: [pushInOut],
|
||||
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
|
||||
* If empty, the configuration will be determined by the route parameter called 'configuration'
|
||||
*/
|
||||
@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,
|
||||
protected sidebarService: SearchSidebarService,
|
||||
protected sidebarService: SidebarService,
|
||||
protected windowService: HostWindowService,
|
||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||
protected routeService: RouteService) {
|
||||
super(service, sidebarService, windowService, searchConfigService, routeService);
|
||||
protected routeService: RouteService,
|
||||
protected router: Router) {
|
||||
super(service, sidebarService, windowService, searchConfigService, routeService, router);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,20 +58,8 @@ export class ConfigurationSearchPageComponent extends SearchPageComponent implem
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,27 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Service for performing actions on the filtered-discovery-pages REST endpoint
|
||||
*/
|
||||
@Injectable()
|
||||
export class SearchFixedFilterService {
|
||||
|
||||
/**
|
||||
* Get the query for looking up items by relation type
|
||||
* @param {string} relationType Relation type
|
||||
* @param {string} itemUUID Item UUID
|
||||
* @returns {string} Query
|
||||
*/
|
||||
getQueryByRelations(relationType: string, itemUUID: string): string {
|
||||
return `query=relation.${relationType}:${itemUUID}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filter for a relation with the item's UUID
|
||||
* @param relationType The type of relation e.g. 'isAuthorOfPublication'
|
||||
* @param itemUUID The item's UUID
|
||||
*/
|
||||
getFilterByRelation(relationType: string, itemUUID: string): string {
|
||||
return `f.${relationType}=${itemUUID}`;
|
||||
}
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
<div class="row mb-3 mb-md-1">
|
||||
<div class="labels col-sm-9 offset-sm-3">
|
||||
<ng-container *ngFor="let key of ((appliedFilters | async) | dsObjectKeys)">
|
||||
<ds-search-label *ngFor="let value of (appliedFilters | async)[key]" [inPlaceSearch]="inPlaceSearch" [key]="key" [value]="value" [appliedFilters]="appliedFilters"></ds-search-label>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
@@ -1,9 +1,9 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { SearchPageComponent } from './search-page.component';
|
||||
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
|
||||
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
||||
import { SearchPageComponent } from './search-page.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@@ -1,43 +1,2 @@
|
||||
<div class="container">
|
||||
<div class="search-page row">
|
||||
<ds-search-sidebar *ngIf="!(isXsOrSm$ | async)" class="col-{{sideBarWidth}} sidebar-md-sticky"
|
||||
id="search-sidebar"
|
||||
[resultCount]="(resultsRD$ | async)?.payload?.totalElements" [inPlaceSearch]="inPlaceSearch"></ds-search-sidebar>
|
||||
<div class="col-12 col-md-{{12 - sideBarWidth}}">
|
||||
<ds-search-form *ngIf="searchEnabled" id="search-form"
|
||||
[query]="(searchOptions$ | async)?.query"
|
||||
[scope]="(searchOptions$ | async)?.scope"
|
||||
[currentUrl]="searchLink"
|
||||
[scopes]="(scopeListRD$ | async)"
|
||||
[inPlaceSearch]="inPlaceSearch">
|
||||
</ds-search-form>
|
||||
<ds-search-labels *ngIf="searchEnabled" [inPlaceSearch]="inPlaceSearch"></ds-search-labels>
|
||||
<div class="row">
|
||||
<div id="search-body"
|
||||
class="row-offcanvas row-offcanvas-left"
|
||||
[@pushInOut]="(isSidebarCollapsed$ | async) ? 'collapsed' : 'expanded'">
|
||||
<ds-search-sidebar *ngIf="(isXsOrSm$ | async)" class="col-12"
|
||||
id="search-sidebar-sm"
|
||||
[resultCount]="(resultsRD$ | async)?.payload.totalElements"
|
||||
(toggleSidebar)="closeSidebar()"
|
||||
[ngClass]="{'active': !(isSidebarCollapsed$ | async)}">
|
||||
</ds-search-sidebar>
|
||||
<div id="search-content" class="col-12">
|
||||
<div class="d-block d-md-none search-controls clearfix">
|
||||
<ds-view-mode-switch [inPlaceSearch]="inPlaceSearch"></ds-view-mode-switch>
|
||||
<button (click)="openSidebar()" aria-controls="#search-body"
|
||||
class="btn btn-outline-primary float-right open-sidebar"><i
|
||||
class="fas fa-sliders"></i> {{"search.sidebar.open"
|
||||
| translate}}
|
||||
</button>
|
||||
</div>
|
||||
<ds-search-results [searchResults]="resultsRD$ | async"
|
||||
[searchConfig]="searchOptions$ | async"
|
||||
[configuration]="configuration$ | async"
|
||||
[disableHeader]="!searchEnabled"></ds-search-results>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ds-search></ds-search>
|
||||
<ds-search-tracker></ds-search-tracker>
|
||||
|
@@ -1,52 +0,0 @@
|
||||
|
||||
@include media-breakpoint-down(md) {
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .search-controls {
|
||||
margin-bottom: $spacer;
|
||||
}
|
||||
|
||||
#search-body {
|
||||
&.row-offcanvas {
|
||||
width: 100%;
|
||||
}
|
||||
@include media-breakpoint-down(sm) {
|
||||
position: relative;
|
||||
|
||||
&.row-offcanvas {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&.row-offcanvas-right #search-sidebar-sm {
|
||||
right: -100%;
|
||||
}
|
||||
|
||||
&.row-offcanvas-left #search-sidebar-sm {
|
||||
left: -100%;
|
||||
}
|
||||
|
||||
#search-sidebar-sm {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
.sidebar-md-sticky {
|
||||
position: sticky;
|
||||
position: -webkit-sticky;
|
||||
top: 0;
|
||||
z-index: $zindex-sticky;
|
||||
padding-top: $content-spacing;
|
||||
margin-top: -$content-spacing;
|
||||
align-self: flex-start;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
@@ -1,184 +1,12 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
|
||||
import { BehaviorSubject, Observable, of as observableOf, Subscription } from 'rxjs';
|
||||
import { startWith, switchMap, } from 'rxjs/operators';
|
||||
import { PaginatedList } from '../core/data/paginated-list';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
||||
import { pushInOut } from '../shared/animations/push';
|
||||
import { HostWindowService } from '../shared/host-window.service';
|
||||
import { PaginatedSearchOptions } from './paginated-search-options.model';
|
||||
import { SearchResult } from './search-result.model';
|
||||
import { SearchService } from './search-service/search.service';
|
||||
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
||||
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
||||
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
||||
import { getSucceededRemoteData } from '../core/shared/operators';
|
||||
import { RouteService } from '../core/services/route.service';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
|
||||
|
||||
export const SEARCH_ROUTE = '/search';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search-page',
|
||||
styleUrls: ['./search-page.component.scss'],
|
||||
templateUrl: './search-page.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
animations: [pushInOut],
|
||||
providers: [
|
||||
{
|
||||
provide: SEARCH_CONFIG_SERVICE,
|
||||
useClass: SearchConfigurationService
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
/**
|
||||
* This component represents the whole search page
|
||||
* It renders search results depending on the current search options
|
||||
*/
|
||||
export class SearchPageComponent implements OnInit {
|
||||
/**
|
||||
* The current search results
|
||||
*/
|
||||
resultsRD$: BehaviorSubject<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> = new BehaviorSubject(null);
|
||||
|
||||
/**
|
||||
* The current paginated search options
|
||||
*/
|
||||
searchOptions$: Observable<PaginatedSearchOptions>;
|
||||
|
||||
/**
|
||||
* The current relevant scopes
|
||||
*/
|
||||
scopeListRD$: Observable<DSpaceObject[]>;
|
||||
|
||||
/**
|
||||
* Emits true if were on a small screen
|
||||
*/
|
||||
isXsOrSm$: Observable<boolean>;
|
||||
|
||||
/**
|
||||
* Subscription to unsubscribe from
|
||||
*/
|
||||
sub: Subscription;
|
||||
|
||||
/**
|
||||
* True when the search component should show results on the current page
|
||||
*/
|
||||
@Input() inPlaceSearch = true;
|
||||
|
||||
/**
|
||||
* Whether or not the search bar should be visible
|
||||
*/
|
||||
@Input()
|
||||
searchEnabled = true;
|
||||
|
||||
/**
|
||||
* The width of the sidebar (bootstrap columns)
|
||||
*/
|
||||
@Input()
|
||||
sideBarWidth = 3;
|
||||
|
||||
/**
|
||||
* The currently applied configuration (determines title of search)
|
||||
*/
|
||||
@Input()
|
||||
configuration$: Observable<string>;
|
||||
|
||||
/**
|
||||
* Link to the search page
|
||||
*/
|
||||
searchLink: string;
|
||||
|
||||
/**
|
||||
* Observable for whether or not the sidebar is currently collapsed
|
||||
*/
|
||||
isSidebarCollapsed$: Observable<boolean>;
|
||||
|
||||
constructor(protected service: SearchService,
|
||||
protected sidebarService: SearchSidebarService,
|
||||
protected windowService: HostWindowService,
|
||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||
protected routeService: RouteService) {
|
||||
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
this.isSidebarCollapsed$ = this.isSidebarCollapsed();
|
||||
this.searchLink = this.getSearchLink();
|
||||
this.searchOptions$ = this.getSearchOptions();
|
||||
this.sub = this.searchOptions$.pipe(
|
||||
switchMap((options) => this.service.search(options).pipe(getSucceededRemoteData(), startWith(undefined))))
|
||||
.subscribe((results) => {
|
||||
this.resultsRD$.next(results);
|
||||
});
|
||||
this.scopeListRD$ = this.searchConfigService.getCurrentScope('').pipe(
|
||||
switchMap((scopeId) => this.service.getScopes(scopeId))
|
||||
);
|
||||
if (!isNotEmpty(this.configuration$)) {
|
||||
this.configuration$ = this.routeService.getRouteParameterValue('configuration');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current paginated search options
|
||||
* @returns {Observable<PaginatedSearchOptions>}
|
||||
*/
|
||||
protected getSearchOptions(): Observable<PaginatedSearchOptions> {
|
||||
return this.searchConfigService.paginatedSearchOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sidebar to a collapsed state
|
||||
*/
|
||||
public closeSidebar(): void {
|
||||
this.sidebarService.collapse()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sidebar to an expanded state
|
||||
*/
|
||||
public openSidebar(): void {
|
||||
this.sidebarService.expand();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the sidebar is collapsed
|
||||
* @returns {Observable<boolean>} emits true if the sidebar is currently collapsed, false if it is expanded
|
||||
*/
|
||||
private isSidebarCollapsed(): Observable<boolean> {
|
||||
return this.sidebarService.isCollapsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} The base path to the search page, or the current page when inPlaceSearch is true
|
||||
*/
|
||||
private getSearchLink(): string {
|
||||
if (this.inPlaceSearch) {
|
||||
return './';
|
||||
}
|
||||
return this.service.getSearchLink();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from the subscription
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
if (hasValue(this.sub)) {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
export class SearchPageComponent {
|
||||
}
|
||||
|
@@ -4,63 +4,17 @@ import { CoreModule } from '../core/core.module';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { SearchPageRoutingModule } from './search-page-routing.module';
|
||||
import { SearchPageComponent } from './search-page.component';
|
||||
import { SearchResultsComponent } from './search-results/search-results.component';
|
||||
import { CommunitySearchResultGridElementComponent } from '../shared/object-grid/search-result-grid-element/community-search-result/community-search-result-grid-element.component'
|
||||
import { CollectionSearchResultGridElementComponent } from '../shared/object-grid/search-result-grid-element/collection-search-result/collection-search-result-grid-element.component';
|
||||
import { SearchSidebarComponent } from './search-sidebar/search-sidebar.component';
|
||||
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
||||
import { SearchSidebarEffects } from './search-sidebar/search-sidebar.effects';
|
||||
import { SearchSettingsComponent } from './search-settings/search-settings.component';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { SearchFiltersComponent } from './search-filters/search-filters.component';
|
||||
import { SearchFilterComponent } from './search-filters/search-filter/search-filter.component';
|
||||
import { SearchFacetFilterComponent } from './search-filters/search-filter/search-facet-filter/search-facet-filter.component';
|
||||
import { SearchFilterService } from './search-filters/search-filter/search-filter.service';
|
||||
import { SearchFixedFilterService } from './search-filters/search-filter/search-fixed-filter.service';
|
||||
import { SearchLabelsComponent } from './search-labels/search-labels.component';
|
||||
import { SearchRangeFilterComponent } from './search-filters/search-filter/search-range-filter/search-range-filter.component';
|
||||
import { SearchTextFilterComponent } from './search-filters/search-filter/search-text-filter/search-text-filter.component';
|
||||
import { SearchFacetFilterWrapperComponent } from './search-filters/search-filter/search-facet-filter-wrapper/search-facet-filter-wrapper.component';
|
||||
import { SearchBooleanFilterComponent } from './search-filters/search-filter/search-boolean-filter/search-boolean-filter.component';
|
||||
import { SearchHierarchyFilterComponent } from './search-filters/search-filter/search-hierarchy-filter/search-hierarchy-filter.component';
|
||||
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
||||
import { SearchFacetOptionComponent } from './search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component';
|
||||
import { SearchFacetSelectedOptionComponent } from './search-filters/search-filter/search-facet-filter-options/search-facet-selected-option/search-facet-selected-option.component';
|
||||
import { SearchFacetRangeOptionComponent } from './search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component';
|
||||
import { SearchSwitchConfigurationComponent } from './search-switch-configuration/search-switch-configuration.component';
|
||||
import { SearchAuthorityFilterComponent } from './search-filters/search-filter/search-authority-filter/search-authority-filter.component';
|
||||
import { SearchLabelComponent } from './search-labels/search-label/search-label.component';
|
||||
import { ConfigurationSearchPageComponent } from './configuration-search-page.component';
|
||||
import { ConfigurationSearchPageGuard } from './configuration-search-page.guard';
|
||||
import { FilteredSearchPageComponent } from './filtered-search-page.component';
|
||||
|
||||
const effects = [
|
||||
SearchSidebarEffects
|
||||
];
|
||||
import { SearchTrackerComponent } from './search-tracker.component';
|
||||
import { StatisticsModule } from '../statistics/statistics.module';
|
||||
import { SearchComponent } from './search.component';
|
||||
|
||||
const components = [
|
||||
SearchPageComponent,
|
||||
SearchResultsComponent,
|
||||
SearchSidebarComponent,
|
||||
SearchSettingsComponent,
|
||||
SearchFiltersComponent,
|
||||
SearchFilterComponent,
|
||||
SearchFacetFilterComponent,
|
||||
SearchLabelsComponent,
|
||||
SearchLabelComponent,
|
||||
SearchFacetFilterComponent,
|
||||
SearchFacetFilterWrapperComponent,
|
||||
SearchRangeFilterComponent,
|
||||
SearchTextFilterComponent,
|
||||
SearchHierarchyFilterComponent,
|
||||
SearchBooleanFilterComponent,
|
||||
SearchFacetOptionComponent,
|
||||
SearchFacetSelectedOptionComponent,
|
||||
SearchFacetRangeOptionComponent,
|
||||
SearchSwitchConfigurationComponent,
|
||||
SearchAuthorityFilterComponent,
|
||||
FilteredSearchPageComponent,
|
||||
ConfigurationSearchPageComponent
|
||||
SearchComponent,
|
||||
ConfigurationSearchPageComponent,
|
||||
SearchTrackerComponent
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
@@ -68,28 +22,11 @@ const components = [
|
||||
SearchPageRoutingModule,
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
EffectsModule.forFeature(effects),
|
||||
CoreModule.forRoot()
|
||||
CoreModule.forRoot(),
|
||||
StatisticsModule.forRoot(),
|
||||
],
|
||||
providers: [ConfigurationSearchPageGuard],
|
||||
declarations: components,
|
||||
providers: [
|
||||
SearchSidebarService,
|
||||
SearchFilterService,
|
||||
SearchFixedFilterService,
|
||||
ConfigurationSearchPageGuard,
|
||||
SearchConfigurationService
|
||||
],
|
||||
entryComponents: [
|
||||
SearchFacetFilterComponent,
|
||||
SearchRangeFilterComponent,
|
||||
SearchTextFilterComponent,
|
||||
SearchHierarchyFilterComponent,
|
||||
SearchBooleanFilterComponent,
|
||||
SearchFacetOptionComponent,
|
||||
SearchFacetSelectedOptionComponent,
|
||||
SearchFacetRangeOptionComponent,
|
||||
SearchAuthorityFilterComponent
|
||||
],
|
||||
exports: components
|
||||
})
|
||||
|
||||
|
@@ -1,26 +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';
|
||||
import { GenericConstructor } from '../core/shared/generic-constructor';
|
||||
|
||||
/**
|
||||
* Represents a search result object of a certain (<T>) DSpaceObject
|
||||
*/
|
||||
export class SearchResult<T extends DSpaceObject> implements ListableObject {
|
||||
/**
|
||||
* The DSpaceObject that was found
|
||||
*/
|
||||
indexableObject: T;
|
||||
|
||||
/**
|
||||
* The metadata that was used to find this item, hithighlighted
|
||||
*/
|
||||
hitHighlights: MetadataMap;
|
||||
|
||||
/**
|
||||
* Method that returns as which type of object this object should be rendered
|
||||
*/
|
||||
getRenderTypes(): Array<string | GenericConstructor<ListableObject>> {
|
||||
return [this.constructor as GenericConstructor<ListableObject>];
|
||||
}
|
||||
}
|
@@ -1,19 +0,0 @@
|
||||
<h2 *ngIf="!disableHeader">{{ (configuration ? configuration + '.search.results.head' : 'search.results.head') | translate }}</h2>
|
||||
<div *ngIf="searchResults?.hasSucceeded && !searchResults?.isLoading && searchResults?.payload?.page.length > 0" @fadeIn>
|
||||
<ds-viewable-collection
|
||||
[config]="searchConfig.pagination"
|
||||
[sortConfig]="searchConfig.sort"
|
||||
[objects]="searchResults"
|
||||
[linkType]="linkType"
|
||||
[hideGear]="true">
|
||||
</ds-viewable-collection></div>
|
||||
<ds-loading *ngIf="hasNoValue(searchResults) || hasNoValue(searchResults.payload) || searchResults.isLoading" message="{{'loading.search-results' | translate}}"></ds-loading>
|
||||
<ds-error *ngIf="searchResults?.hasFailed && (!searchResults?.error || searchResults?.error?.statusCode != 400)" message="{{'error.search-results' | translate}}"></ds-error>
|
||||
<div *ngIf="searchResults?.payload?.page.length == 0 || searchResults?.error?.statusCode == 400">
|
||||
{{ 'search.results.no-results' | translate }}
|
||||
<a [routerLink]="['/search']"
|
||||
[queryParams]="{ query: surroundStringWithQuotes(searchConfig?.query) }"
|
||||
queryParamsHandling="merge">
|
||||
{{"search.results.no-results-link" | translate}}
|
||||
</a>
|
||||
</div>
|
@@ -1,24 +0,0 @@
|
||||
<ng-container *ngVar="(searchOptions$ | async) as config">
|
||||
<h3>{{ 'search.sidebar.settings.title' | translate}}</h3>
|
||||
<div *ngIf="config?.sort" class="setting-option result-order-settings mb-3 p-3">
|
||||
<h5>{{ 'search.sidebar.settings.sort-by' | translate}}</h5>
|
||||
<select class="form-control" (change)="reloadOrder($event)">
|
||||
<option *ngFor="let sortOption of searchOptionPossibilities"
|
||||
[value]="sortOption.field + ',' + sortOption.direction.toString()"
|
||||
[selected]="sortOption.field === config?.sort.field && sortOption.direction === (config?.sort.direction)? 'selected': null">
|
||||
{{'sorting.' + sortOption.field + '.' + sortOption.direction | translate}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="setting-option page-size-settings mb-3 p-3">
|
||||
<h5>{{ 'search.sidebar.settings.rpp' | translate}}</h5>
|
||||
<select class="form-control" (change)="reloadRPP($event)">
|
||||
<option *ngFor="let pageSizeOption of config?.pagination.pageSizeOptions"
|
||||
[value]="pageSizeOption"
|
||||
[selected]="pageSizeOption === +config?.pagination.pageSize ? 'selected': null">
|
||||
{{pageSizeOption}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</ng-container>
|
@@ -1,143 +0,0 @@
|
||||
import { SearchService } from '../search-service/search.service';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SearchSettingsComponent } from './search-settings.component';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { SearchSidebarService } from '../search-sidebar/search-sidebar.service';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { SearchFilterService } from '../search-filters/search-filter/search-filter.service';
|
||||
import { hot } from 'jasmine-marbles';
|
||||
import { VarDirective } from '../../shared/utils/var.directive';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../../+my-dspace-page/my-dspace-page.component';
|
||||
|
||||
describe('SearchSettingsComponent', () => {
|
||||
|
||||
let comp: SearchSettingsComponent;
|
||||
let fixture: ComponentFixture<SearchSettingsComponent>;
|
||||
let searchServiceObject: SearchService;
|
||||
|
||||
const pagination: PaginationComponentOptions = new PaginationComponentOptions();
|
||||
pagination.id = 'search-results-pagination';
|
||||
pagination.currentPage = 1;
|
||||
pagination.pageSize = 10;
|
||||
const sort: SortOptions = new SortOptions('score', SortDirection.DESC);
|
||||
const mockResults = ['test', 'data'];
|
||||
const searchServiceStub = {
|
||||
searchOptions: { pagination: pagination, sort: sort },
|
||||
search: () => mockResults
|
||||
};
|
||||
|
||||
const queryParam = 'test query';
|
||||
const scopeParam = '7669c72a-3f2a-451f-a3b9-9210e7a4c02f';
|
||||
const paginatedSearchOptions = {
|
||||
query: queryParam,
|
||||
scope: scopeParam,
|
||||
pagination,
|
||||
sort
|
||||
};
|
||||
|
||||
const activatedRouteStub = {
|
||||
queryParams: observableOf({
|
||||
query: queryParam,
|
||||
scope: scopeParam
|
||||
})
|
||||
};
|
||||
|
||||
const sidebarService = {
|
||||
isCollapsed: observableOf(true),
|
||||
collapse: () => this.isCollapsed = observableOf(true),
|
||||
expand: () => this.isCollapsed = observableOf(false)
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
|
||||
declarations: [SearchSettingsComponent, EnumKeysPipe, VarDirective],
|
||||
providers: [
|
||||
{ provide: SearchService, useValue: searchServiceStub },
|
||||
|
||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||
{
|
||||
provide: SearchSidebarService,
|
||||
useValue: sidebarService
|
||||
},
|
||||
{
|
||||
provide: SearchFilterService,
|
||||
useValue: {}
|
||||
},
|
||||
{
|
||||
provide: SEARCH_CONFIG_SERVICE,
|
||||
useValue: {
|
||||
paginatedSearchOptions: hot('a', {
|
||||
a: paginatedSearchOptions
|
||||
}),
|
||||
getCurrentScope: hot('a', {
|
||||
a: 'test-id'
|
||||
}),
|
||||
}
|
||||
},
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SearchSettingsComponent);
|
||||
comp = fixture.componentInstance;
|
||||
|
||||
// SearchPageComponent test instance
|
||||
fixture.detectChanges();
|
||||
searchServiceObject = (comp as any).service;
|
||||
spyOn(comp, 'reloadRPP');
|
||||
spyOn(comp, 'reloadOrder');
|
||||
spyOn(searchServiceObject, 'search').and.callThrough();
|
||||
|
||||
});
|
||||
|
||||
it('it should show the order settings with the respective selectable options', () => {
|
||||
(comp as any).searchOptions$.pipe(first()).subscribe((options) => {
|
||||
fixture.detectChanges();
|
||||
const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings'));
|
||||
expect(orderSetting).toBeDefined();
|
||||
const childElements = orderSetting.query(By.css('.form-control')).children;
|
||||
expect(childElements.length).toEqual(comp.searchOptionPossibilities.length);
|
||||
});
|
||||
});
|
||||
|
||||
it('it should show the size settings with the respective selectable options', () => {
|
||||
(comp as any).searchOptions$.pipe(first()).subscribe((options) => {
|
||||
fixture.detectChanges();
|
||||
const pageSizeSetting = fixture.debugElement.query(By.css('div.page-size-settings'));
|
||||
expect(pageSizeSetting).toBeDefined();
|
||||
const childElements = pageSizeSetting.query(By.css('.form-control')).children;
|
||||
expect(childElements.length).toEqual(options.pagination.pageSizeOptions.length);
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
it('should have the proper order value selected by default', () => {
|
||||
(comp as any).searchOptions$.pipe(first()).subscribe((options) => {
|
||||
fixture.detectChanges();
|
||||
const orderSetting = fixture.debugElement.query(By.css('div.result-order-settings'));
|
||||
const childElementToBeSelected = orderSetting.query(By.css('.form-control option[value="0"][selected="selected"]'));
|
||||
expect(childElementToBeSelected).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should have the proper rpp value selected by default', () => {
|
||||
(comp as any).searchOptions$.pipe(first()).subscribe((options) => {
|
||||
fixture.detectChanges();
|
||||
const pageSizeSetting = fixture.debugElement.query(By.css('div.page-size-settings'));
|
||||
const childElementToBeSelected = pageSizeSetting.query(By.css('.form-control option[value="10"][selected="selected"]'));
|
||||
expect(childElementToBeSelected).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
@@ -1,36 +0,0 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { Observable } from 'rxjs';
|
||||
import { provideMockActions } from '@ngrx/effects/testing';
|
||||
import { cold, hot } from 'jasmine-marbles';
|
||||
import * as fromRouter from '@ngrx/router-store';
|
||||
import { SearchSidebarCollapseAction } from './search-sidebar.actions';
|
||||
import { SearchSidebarEffects } from './search-sidebar.effects';
|
||||
|
||||
describe('SearchSidebarEffects', () => {
|
||||
let sidebarEffects: SearchSidebarEffects;
|
||||
let actions: Observable<any>;
|
||||
const dummyURL = 'http://f4fb15e2-1bd3-4e63-8d0d-486ad8bc714a';
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
SearchSidebarEffects,
|
||||
provideMockActions(() => actions),
|
||||
// other providers
|
||||
],
|
||||
});
|
||||
|
||||
sidebarEffects = TestBed.get(SearchSidebarEffects);
|
||||
});
|
||||
|
||||
describe('routeChange$', () => {
|
||||
|
||||
it('should return a COLLAPSE action in response to an UPDATE_LOCATION action to a new route', () => {
|
||||
actions = hot('--a-', { a: { type: fromRouter.ROUTER_NAVIGATION, payload: {routerState: {url: dummyURL}} } });
|
||||
|
||||
const expected = cold('--b-', { b: new SearchSidebarCollapseAction() });
|
||||
|
||||
expect(sidebarEffects.routeChange$).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,47 +0,0 @@
|
||||
import { SearchSidebarAction, SearchSidebarActionTypes } from './search-sidebar.actions';
|
||||
|
||||
/**
|
||||
* Interface that represents the state of the sidebar
|
||||
*/
|
||||
export interface SearchSidebarState {
|
||||
sidebarCollapsed: boolean;
|
||||
}
|
||||
|
||||
const initialState: SearchSidebarState = {
|
||||
sidebarCollapsed: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Performs a search sidebar action on the current state
|
||||
* @param {SearchSidebarState} state The state before the action is performed
|
||||
* @param {SearchSidebarAction} action The action that should be performed
|
||||
* @returns {SearchSidebarState} The state after the action is performed
|
||||
*/
|
||||
export function sidebarReducer(state = initialState, action: SearchSidebarAction): SearchSidebarState {
|
||||
switch (action.type) {
|
||||
|
||||
case SearchSidebarActionTypes.COLLAPSE: {
|
||||
return Object.assign({}, state, {
|
||||
sidebarCollapsed: true
|
||||
});
|
||||
}
|
||||
|
||||
case SearchSidebarActionTypes.EXPAND: {
|
||||
return Object.assign({}, state, {
|
||||
sidebarCollapsed: false
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
case SearchSidebarActionTypes.TOGGLE: {
|
||||
return Object.assign({}, state, {
|
||||
sidebarCollapsed: !state.sidebarCollapsed
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
1
src/app/+search-page/search-tracker.component.html
Normal file
1
src/app/+search-page/search-tracker.component.html
Normal file
@@ -0,0 +1 @@
|
||||
|
3
src/app/+search-page/search-tracker.component.scss
Normal file
3
src/app/+search-page/search-tracker.component.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
:host {
|
||||
display: none
|
||||
}
|
90
src/app/+search-page/search-tracker.component.ts
Normal file
90
src/app/+search-page/search-tracker.component.ts
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { Angulartics2 } from 'angulartics2';
|
||||
import { filter, map, switchMap } from 'rxjs/operators';
|
||||
import { SearchComponent } from './search.component';
|
||||
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||
import { HostWindowService } from '../shared/host-window.service';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
|
||||
import { RouteService } from '../core/services/route.service';
|
||||
import { hasValue } from '../shared/empty.util';
|
||||
import { SearchSuccessResponse } from '../core/cache/response.models';
|
||||
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { SearchService } from '../core/shared/search/search.service';
|
||||
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
||||
import { SearchQueryResponse } from '../shared/search/search-query-response.model';
|
||||
|
||||
/**
|
||||
* This component triggers a page view statistic
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-search-tracker',
|
||||
styleUrls: ['./search-tracker.component.scss'],
|
||||
templateUrl: './search-tracker.component.html',
|
||||
providers: [
|
||||
{
|
||||
provide: SEARCH_CONFIG_SERVICE,
|
||||
useClass: SearchConfigurationService
|
||||
}
|
||||
]
|
||||
})
|
||||
export class SearchTrackerComponent extends SearchComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
protected service: SearchService,
|
||||
protected sidebarService: SidebarService,
|
||||
protected windowService: HostWindowService,
|
||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||
protected routeService: RouteService,
|
||||
public angulartics2: Angulartics2,
|
||||
protected router: Router
|
||||
) {
|
||||
super(service, sidebarService, windowService, searchConfigService, routeService, router);
|
||||
}
|
||||
|
||||
ngOnInit():void {
|
||||
// super.ngOnInit();
|
||||
this.getSearchOptions().pipe(
|
||||
switchMap((options) => this.service.searchEntries(options)
|
||||
.pipe(
|
||||
filter((entry) =>
|
||||
hasValue(entry.requestEntry)
|
||||
&& hasValue(entry.requestEntry.response)
|
||||
&& entry.requestEntry.response.isSuccessful === true
|
||||
),
|
||||
map((entry) => ({
|
||||
searchOptions: entry.searchOptions,
|
||||
response: (entry.requestEntry.response as SearchSuccessResponse).results
|
||||
})),
|
||||
)
|
||||
)
|
||||
)
|
||||
.subscribe((entry) => {
|
||||
const config: PaginatedSearchOptions = entry.searchOptions;
|
||||
const searchQueryResponse: SearchQueryResponse = entry.response;
|
||||
const filters:Array<{ filter: string, operator: string, value: string, label: string; }> = [];
|
||||
const appliedFilters = searchQueryResponse.appliedFilters || [];
|
||||
for (let i = 0, filtersLength = appliedFilters.length; i < filtersLength; i++) {
|
||||
const appliedFilter = appliedFilters[i];
|
||||
filters.push(appliedFilter);
|
||||
}
|
||||
this.angulartics2.eventTrack.next({
|
||||
action: 'search',
|
||||
properties: {
|
||||
searchOptions: config,
|
||||
page: {
|
||||
size: config.pagination.size, // same as searchQueryResponse.page.elementsPerPage
|
||||
totalElements: searchQueryResponse.page.totalElements,
|
||||
totalPages: searchQueryResponse.page.totalPages,
|
||||
number: config.pagination.currentPage, // same as searchQueryResponse.page.currentPage
|
||||
},
|
||||
sort: {
|
||||
by: config.sort.field,
|
||||
order: config.sort.direction
|
||||
},
|
||||
filters: filters,
|
||||
},
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
54
src/app/+search-page/search.component.html
Normal file
54
src/app/+search-page/search.component.html
Normal file
@@ -0,0 +1,54 @@
|
||||
<div class="container" *ngIf="(isXsOrSm$ | async)">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<ng-template *ngTemplateOutlet="searchForm"></ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ds-page-with-sidebar [id]="'search-page'" [sidebarContent]="sidebarContent">
|
||||
<div class="row">
|
||||
<div class="col-12" *ngIf="!(isXsOrSm$ | async)">
|
||||
<ng-template *ngTemplateOutlet="searchForm"></ng-template>
|
||||
</div>
|
||||
<div id="search-content" class="col-12">
|
||||
<div class="d-block d-md-none search-controls clearfix">
|
||||
<ds-view-mode-switch [inPlaceSearch]="inPlaceSearch"></ds-view-mode-switch>
|
||||
<button (click)="openSidebar()" aria-controls="#search-body"
|
||||
class="btn btn-outline-primary float-right open-sidebar"><i
|
||||
class="fas fa-sliders"></i> {{"search.sidebar.open"
|
||||
| translate}}
|
||||
</button>
|
||||
</div>
|
||||
<ds-search-results [searchResults]="resultsRD$ | async"
|
||||
[searchConfig]="searchOptions$ | async"
|
||||
[configuration]="configuration$ | async"
|
||||
[disableHeader]="!searchEnabled"></ds-search-results>
|
||||
</div>
|
||||
</div>
|
||||
</ds-page-with-sidebar>
|
||||
|
||||
<ng-template #sidebarContent>
|
||||
<ds-search-sidebar id="search-sidebar" *ngIf="!(isXsOrSm$ | async)"
|
||||
[resultCount]="(resultsRD$ | async)?.payload?.totalElements"
|
||||
[inPlaceSearch]="inPlaceSearch"></ds-search-sidebar>
|
||||
<ds-search-sidebar id="search-sidebar-sm" *ngIf="(isXsOrSm$ | async)"
|
||||
[resultCount]="(resultsRD$ | async)?.payload.totalElements"
|
||||
(toggleSidebar)="closeSidebar()"
|
||||
>
|
||||
</ds-search-sidebar>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #searchForm>
|
||||
<ds-search-form *ngIf="searchEnabled" id="search-form"
|
||||
[query]="(searchOptions$ | async)?.query"
|
||||
[scope]="(searchOptions$ | async)?.scope"
|
||||
[currentUrl]="searchLink"
|
||||
[scopes]="(scopeListRD$ | async)"
|
||||
[inPlaceSearch]="inPlaceSearch">
|
||||
</ds-search-form>
|
||||
<div class="row mb-3 mb-md-1">
|
||||
<div class="labels col-sm-9 offset-sm-3">
|
||||
<ds-search-labels *ngIf="searchEnabled" [inPlaceSearch]="inPlaceSearch"></ds-search-labels>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
10
src/app/+search-page/search.component.scss
Normal file
10
src/app/+search-page/search.component.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
@include media-breakpoint-down(md) {
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
/deep/ .search-controls {
|
||||
margin-bottom: $spacer;
|
||||
}
|
@@ -10,28 +10,26 @@ import { SortDirection, SortOptions } from '../core/cache/models/sort-options.mo
|
||||
import { CommunityDataService } from '../core/data/community-data.service';
|
||||
import { HostWindowService } from '../shared/host-window.service';
|
||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||
import { SearchPageComponent } from './search-page.component';
|
||||
import { SearchService } from './search-service/search.service';
|
||||
import { SearchComponent } from './search.component';
|
||||
import { SearchService } from '../core/shared/search/search.service';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { NgbCollapseModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { SearchSidebarService } from './search-sidebar/search-sidebar.service';
|
||||
import { SearchFilterService } from './search-filters/search-filter/search-filter.service';
|
||||
import { SearchConfigurationService } from './search-service/search-configuration.service';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||
import { SearchFilterService } from '../core/shared/search/search-filter.service';
|
||||
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
|
||||
import { RouteService } from '../core/services/route.service';
|
||||
import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub';
|
||||
import { PaginatedSearchOptions } from './paginated-search-options.model';
|
||||
import { SearchFixedFilterService } from './search-filters/search-filter/search-fixed-filter.service';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../shared/testing/utils';
|
||||
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
||||
|
||||
let comp: SearchPageComponent;
|
||||
let fixture: ComponentFixture<SearchPageComponent>;
|
||||
let comp: SearchComponent;
|
||||
let fixture: ComponentFixture<SearchComponent>;
|
||||
let searchServiceObject: SearchService;
|
||||
let searchConfigurationServiceObject: SearchConfigurationService;
|
||||
const store: Store<SearchPageComponent> = jasmine.createSpyObj('store', {
|
||||
const store: Store<SearchComponent> = jasmine.createSpyObj('store', {
|
||||
/* tslint:disable:no-empty */
|
||||
dispatch: {},
|
||||
/* tslint:enable:no-empty */
|
||||
@@ -89,7 +87,6 @@ const routeServiceStub = {
|
||||
return observableOf('')
|
||||
}
|
||||
};
|
||||
const mockFixedFilterService: SearchFixedFilterService = {} as SearchFixedFilterService;
|
||||
|
||||
export function configureSearchComponentTestingModule(compType) {
|
||||
TestBed.configureTestingModule({
|
||||
@@ -115,17 +112,13 @@ export function configureSearchComponentTestingModule(compType) {
|
||||
})
|
||||
},
|
||||
{
|
||||
provide: SearchSidebarService,
|
||||
provide: SidebarService,
|
||||
useValue: sidebarService
|
||||
},
|
||||
{
|
||||
provide: SearchFilterService,
|
||||
useValue: {}
|
||||
},
|
||||
{
|
||||
provide: SearchFixedFilterService,
|
||||
useValue: mockFixedFilterService
|
||||
},
|
||||
{
|
||||
provide: SearchConfigurationService,
|
||||
useValue: {
|
||||
@@ -150,14 +143,15 @@ export function configureSearchComponentTestingModule(compType) {
|
||||
}).compileComponents();
|
||||
}
|
||||
|
||||
describe('SearchPageComponent', () => {
|
||||
describe('SearchComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
configureSearchComponentTestingModule(SearchPageComponent);
|
||||
configureSearchComponentTestingModule(SearchComponent);
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SearchPageComponent);
|
||||
comp = fixture.componentInstance; // SearchPageComponent test instance
|
||||
fixture = TestBed.createComponent(SearchComponent);
|
||||
comp = fixture.componentInstance; // SearchComponent test instance
|
||||
comp.inPlaceSearch = false;
|
||||
fixture.detectChanges();
|
||||
searchServiceObject = (comp as any).service;
|
||||
searchConfigurationServiceObject = (comp as any).searchConfigService;
|
||||
@@ -191,34 +185,4 @@ describe('SearchPageComponent', () => {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when sidebarCollapsed is true in mobile view', () => {
|
||||
let menu: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
menu = fixture.debugElement.query(By.css('#search-sidebar-sm')).nativeElement;
|
||||
(comp as any).isSidebarCollapsed$ = observableOf(true);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should close the sidebar', () => {
|
||||
expect(menu.classList).not.toContain('active');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when sidebarCollapsed is false in mobile view', () => {
|
||||
let menu: HTMLElement;
|
||||
|
||||
beforeEach(() => {
|
||||
menu = fixture.debugElement.query(By.css('#search-sidebar-sm')).nativeElement;
|
||||
(comp as any).isSidebarCollapsed$ = observableOf(false);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should open the menu', () => {
|
||||
expect(menu.classList).toContain('active');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
178
src/app/+search-page/search.component.ts
Normal file
178
src/app/+search-page/search.component.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
|
||||
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||
import { startWith, switchMap, } from 'rxjs/operators';
|
||||
import { PaginatedList } from '../core/data/paginated-list';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
||||
import { pushInOut } from '../shared/animations/push';
|
||||
import { HostWindowService } from '../shared/host-window.service';
|
||||
import { SidebarService } from '../shared/sidebar/sidebar.service';
|
||||
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
||||
import { getSucceededRemoteData } from '../core/shared/operators';
|
||||
import { RouteService } from '../core/services/route.service';
|
||||
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
|
||||
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
|
||||
import { SearchResult } from '../shared/search/search-result.model';
|
||||
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
|
||||
import { SearchService } from '../core/shared/search/search.service';
|
||||
import { currentPath } from '../shared/utils/route.utils';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-search',
|
||||
styleUrls: ['./search.component.scss'],
|
||||
templateUrl: './search.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
animations: [pushInOut],
|
||||
providers: [
|
||||
{
|
||||
provide: SEARCH_CONFIG_SERVICE,
|
||||
useClass: SearchConfigurationService
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
/**
|
||||
* This component renders a sidebar, a search input bar and the search results.
|
||||
*/
|
||||
export class SearchComponent implements OnInit {
|
||||
/**
|
||||
* The current search results
|
||||
*/
|
||||
resultsRD$: BehaviorSubject<RemoteData<PaginatedList<SearchResult<DSpaceObject>>>> = new BehaviorSubject(null);
|
||||
|
||||
/**
|
||||
* The current paginated search options
|
||||
*/
|
||||
searchOptions$: Observable<PaginatedSearchOptions>;
|
||||
|
||||
/**
|
||||
* The current relevant scopes
|
||||
*/
|
||||
scopeListRD$: Observable<DSpaceObject[]>;
|
||||
|
||||
/**
|
||||
* Emits true if were on a small screen
|
||||
*/
|
||||
isXsOrSm$: Observable<boolean>;
|
||||
|
||||
/**
|
||||
* Subscription to unsubscribe from
|
||||
*/
|
||||
sub: Subscription;
|
||||
|
||||
/**
|
||||
* True when the search component should show results on the current page
|
||||
*/
|
||||
@Input() inPlaceSearch = true;
|
||||
|
||||
/**
|
||||
* Whether or not the search bar should be visible
|
||||
*/
|
||||
@Input()
|
||||
searchEnabled = true;
|
||||
|
||||
/**
|
||||
* The width of the sidebar (bootstrap columns)
|
||||
*/
|
||||
@Input()
|
||||
sideBarWidth = 3;
|
||||
|
||||
/**
|
||||
* The currently applied configuration (determines title of search)
|
||||
*/
|
||||
@Input()
|
||||
configuration$: Observable<string>;
|
||||
|
||||
/**
|
||||
* Link to the search page
|
||||
*/
|
||||
searchLink: string;
|
||||
|
||||
/**
|
||||
* Observable for whether or not the sidebar is currently collapsed
|
||||
*/
|
||||
isSidebarCollapsed$: Observable<boolean>;
|
||||
|
||||
constructor(protected service: SearchService,
|
||||
protected sidebarService: SidebarService,
|
||||
protected windowService: HostWindowService,
|
||||
@Inject(SEARCH_CONFIG_SERVICE) public searchConfigService: SearchConfigurationService,
|
||||
protected routeService: RouteService,
|
||||
protected router: Router) {
|
||||
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
this.isSidebarCollapsed$ = this.isSidebarCollapsed();
|
||||
this.searchLink = this.getSearchLink();
|
||||
this.searchOptions$ = this.getSearchOptions();
|
||||
this.sub = this.searchOptions$.pipe(
|
||||
switchMap((options) => this.service.search(options).pipe(getSucceededRemoteData(), startWith(undefined))))
|
||||
.subscribe((results) => {
|
||||
this.resultsRD$.next(results);
|
||||
});
|
||||
this.scopeListRD$ = this.searchConfigService.getCurrentScope('').pipe(
|
||||
switchMap((scopeId) => this.service.getScopes(scopeId))
|
||||
);
|
||||
if (!isNotEmpty(this.configuration$)) {
|
||||
this.configuration$ = this.routeService.getRouteParameterValue('configuration');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current paginated search options
|
||||
* @returns {Observable<PaginatedSearchOptions>}
|
||||
*/
|
||||
protected getSearchOptions(): Observable<PaginatedSearchOptions> {
|
||||
return this.searchConfigService.paginatedSearchOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sidebar to a collapsed state
|
||||
*/
|
||||
public closeSidebar(): void {
|
||||
this.sidebarService.collapse()
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sidebar to an expanded state
|
||||
*/
|
||||
public openSidebar(): void {
|
||||
this.sidebarService.expand();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the sidebar is collapsed
|
||||
* @returns {Observable<boolean>} emits true if the sidebar is currently collapsed, false if it is expanded
|
||||
*/
|
||||
private isSidebarCollapsed(): Observable<boolean> {
|
||||
return this.sidebarService.isCollapsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string} The base path to the search page, or the current page when inPlaceSearch is true
|
||||
*/
|
||||
private getSearchLink(): string {
|
||||
if (this.inPlaceSearch) {
|
||||
return currentPath(this.router);
|
||||
}
|
||||
return this.service.getSearchLink();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from the subscription
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
if (hasValue(this.sub)) {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
@@ -27,6 +27,7 @@ export function getAdminModulePath() {
|
||||
RouterModule.forRoot([
|
||||
{ path: '', redirectTo: '/home', pathMatch: 'full' },
|
||||
{ path: 'home', loadChildren: './+home-page/home-page.module#HomePageModule' },
|
||||
{ path: 'community-list', loadChildren: './community-list-page/community-list-page.module#CommunityListPageModule' },
|
||||
{ path: 'id', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' },
|
||||
{ path: 'handle', loadChildren: './+lookup-by-id/lookup-by-id.module#LookupIdModule' },
|
||||
{ path: COMMUNITY_MODULE_PATH, loadChildren: './+community-page/community-page.module#CommunityPageModule' },
|
||||
|
@@ -20,7 +20,7 @@ import { Store, StoreModule } from '@ngrx/store';
|
||||
// Load the implementations that should be tested
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
import { HostWindowState } from './shared/host-window.reducer';
|
||||
import { HostWindowState } from './shared/search/host-window.reducer';
|
||||
import { HostWindowResizeAction } from './shared/host-window.actions';
|
||||
|
||||
import { MetadataService } from './core/metadata/metadata.service';
|
||||
@@ -46,6 +46,7 @@ import { MockActivatedRoute } from './shared/mocks/mock-active-router';
|
||||
import { MockRouter } from './shared/mocks/mock-router';
|
||||
import { MockCookieService } from './shared/mocks/mock-cookie.service';
|
||||
import { CookieService } from './core/services/cookie.service';
|
||||
import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider';
|
||||
|
||||
let comp: AppComponent;
|
||||
let fixture: ComponentFixture<AppComponent>;
|
||||
@@ -74,6 +75,7 @@ describe('App component', () => {
|
||||
{ provide: NativeWindowService, useValue: new NativeWindowRef() },
|
||||
{ provide: MetadataService, useValue: new MockMetadataService() },
|
||||
{ provide: Angulartics2GoogleAnalytics, useValue: new AngularticsMock() },
|
||||
{ provide: Angulartics2DSpace, useValue: new AngularticsMock() },
|
||||
{ provide: AuthService, useValue: new AuthServiceMock() },
|
||||
{ provide: Router, useValue: new MockRouter() },
|
||||
{ provide: ActivatedRoute, useValue: new MockActivatedRoute() },
|
||||
|
@@ -1,13 +1,5 @@
|
||||
import { filter, map, take } from 'rxjs/operators';
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
HostListener,
|
||||
Inject,
|
||||
OnInit,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, Inject, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { NavigationCancel, NavigationEnd, NavigationStart, Router } from '@angular/router';
|
||||
|
||||
import { select, Store } from '@ngrx/store';
|
||||
@@ -18,12 +10,11 @@ import { GLOBAL_CONFIG, GlobalConfig } from '../config';
|
||||
|
||||
import { MetadataService } from './core/metadata/metadata.service';
|
||||
import { HostWindowResizeAction } from './shared/host-window.actions';
|
||||
import { HostWindowState } from './shared/host-window.reducer';
|
||||
import { HostWindowState } from './shared/search/host-window.reducer';
|
||||
import { NativeWindowRef, NativeWindowService } from './core/services/window.service';
|
||||
import { isAuthenticated } from './core/auth/selectors';
|
||||
import { AuthService } from './core/auth/auth.service';
|
||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
||||
import { RouteService } from './core/services/route.service';
|
||||
import variables from '../styles/_exposed_variables.scss';
|
||||
import { CSSVariableService } from './shared/sass-helper/sass-helper.service';
|
||||
import { MenuService } from './shared/menu/menu.service';
|
||||
@@ -34,6 +25,7 @@ import { HostWindowService } from './shared/host-window.service';
|
||||
import { Theme } from '../config/theme.inferface';
|
||||
import { isNotEmpty } from './shared/empty.util';
|
||||
import { CookieService } from './core/services/cookie.service';
|
||||
import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider';
|
||||
|
||||
export const LANG_COOKIE = 'language_cookie';
|
||||
|
||||
@@ -60,6 +52,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
private store: Store<HostWindowState>,
|
||||
private metadata: MetadataService,
|
||||
private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics,
|
||||
private angulartics2DSpace: Angulartics2DSpace,
|
||||
private authService: AuthService,
|
||||
private router: Router,
|
||||
private cssService: CSSVariableService,
|
||||
@@ -89,6 +82,8 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
}
|
||||
}
|
||||
|
||||
angulartics2DSpace.startTracking();
|
||||
|
||||
metadata.listenForRouteChange();
|
||||
|
||||
if (config.debug) {
|
||||
|
@@ -1,9 +1,13 @@
|
||||
import { StoreEffects } from './store.effects';
|
||||
import { NotificationsEffects } from './shared/notifications/notifications.effects';
|
||||
import { NavbarEffects } from './navbar/navbar.effects';
|
||||
import { SidebarEffects } from './shared/sidebar/sidebar-effects.service';
|
||||
import { RelationshipEffects } from './shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects';
|
||||
|
||||
export const appEffects = [
|
||||
StoreEffects,
|
||||
NavbarEffects,
|
||||
NotificationsEffects,
|
||||
SidebarEffects,
|
||||
RelationshipEffects
|
||||
];
|
||||
|
@@ -37,9 +37,9 @@ import { AdminSidebarComponent } from './+admin/admin-sidebar/admin-sidebar.comp
|
||||
import { AdminSidebarSectionComponent } from './+admin/admin-sidebar/admin-sidebar-section/admin-sidebar-section.component';
|
||||
import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component';
|
||||
import { NavbarModule } from './navbar/navbar.module';
|
||||
import { ClientCookieService } from './core/services/client-cookie.service';
|
||||
import { JournalEntitiesModule } from './entity-groups/journal-entities/journal-entities.module';
|
||||
import { ResearchEntitiesModule } from './entity-groups/research-entities/research-entities.module';
|
||||
import { ClientCookieService } from './core/services/client-cookie.service';
|
||||
|
||||
export function getConfig() {
|
||||
return ENV_CONFIG;
|
||||
@@ -76,7 +76,7 @@ const ENTITY_IMPORTS = [
|
||||
|
||||
IMPORTS.push(
|
||||
StoreDevtoolsModule.instrument({
|
||||
maxAge: 100,
|
||||
maxAge: 1000,
|
||||
logOnly: ENV_CONFIG.production,
|
||||
})
|
||||
);
|
||||
|
@@ -1,33 +1,22 @@
|
||||
import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store';
|
||||
import * as fromRouter from '@ngrx/router-store';
|
||||
import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer';
|
||||
import { hostWindowReducer, HostWindowState } from './shared/search/host-window.reducer';
|
||||
import { CommunityListReducer, CommunityListState } from './community-list-page/community-list.reducer';
|
||||
import { formReducer, FormState } from './shared/form/form.reducer';
|
||||
import {
|
||||
SearchSidebarState,
|
||||
sidebarReducer
|
||||
} from './+search-page/search-sidebar/search-sidebar.reducer';
|
||||
import {
|
||||
filterReducer,
|
||||
SearchFiltersState
|
||||
} from './+search-page/search-filters/search-filter/search-filter.reducer';
|
||||
import {
|
||||
notificationsReducer,
|
||||
NotificationsState
|
||||
} from './shared/notifications/notifications.reducers';
|
||||
import { sidebarReducer, SidebarState } from './shared/sidebar/sidebar.reducer';
|
||||
import { sidebarFilterReducer, SidebarFiltersState } from './shared/sidebar/filter/sidebar-filter.reducer';
|
||||
import { filterReducer, SearchFiltersState } from './shared/search/search-filters/search-filter/search-filter.reducer';
|
||||
import { notificationsReducer, NotificationsState } from './shared/notifications/notifications.reducers';
|
||||
import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer';
|
||||
import {
|
||||
metadataRegistryReducer,
|
||||
MetadataRegistryState
|
||||
} from './+admin/admin-registries/metadata-registry/metadata-registry.reducers';
|
||||
import { metadataRegistryReducer, MetadataRegistryState } from './+admin/admin-registries/metadata-registry/metadata-registry.reducers';
|
||||
import { hasValue } from './shared/empty.util';
|
||||
import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer';
|
||||
import { menusReducer, MenusState } from './shared/menu/menu.reducer';
|
||||
import { historyReducer, HistoryState } from './shared/history/history.reducer';
|
||||
import {
|
||||
bitstreamFormatReducer,
|
||||
BitstreamFormatRegistryState
|
||||
} from './+admin/admin-registries/bitstream-formats/bitstream-format.reducers';
|
||||
import { selectableListReducer, SelectableListsState } from './shared/object-list/selectable-list/selectable-list.reducer';
|
||||
import { bitstreamFormatReducer, BitstreamFormatRegistryState } from './+admin/admin-registries/bitstream-formats/bitstream-format.reducers';
|
||||
import { ObjectSelectionListState, objectSelectionReducer } from './shared/object-select/object-select.reducer';
|
||||
import { NameVariantListsState, nameVariantReducer } from './shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
|
||||
|
||||
export interface AppState {
|
||||
router: fromRouter.RouterReducerState;
|
||||
@@ -37,12 +26,16 @@ export interface AppState {
|
||||
metadataRegistry: MetadataRegistryState;
|
||||
bitstreamFormats: BitstreamFormatRegistryState;
|
||||
notifications: NotificationsState;
|
||||
searchSidebar: SearchSidebarState;
|
||||
sidebar: SidebarState;
|
||||
sidebarFilter: SidebarFiltersState;
|
||||
searchFilter: SearchFiltersState;
|
||||
truncatable: TruncatablesState;
|
||||
cssVariables: CSSVariablesState;
|
||||
menus: MenusState;
|
||||
objectSelection: ObjectSelectionListState;
|
||||
selectableLists: SelectableListsState;
|
||||
relationshipLists: NameVariantListsState;
|
||||
communityList: CommunityListState;
|
||||
}
|
||||
|
||||
export const appReducers: ActionReducerMap<AppState> = {
|
||||
@@ -53,12 +46,16 @@ export const appReducers: ActionReducerMap<AppState> = {
|
||||
metadataRegistry: metadataRegistryReducer,
|
||||
bitstreamFormats: bitstreamFormatReducer,
|
||||
notifications: notificationsReducer,
|
||||
searchSidebar: sidebarReducer,
|
||||
sidebar: sidebarReducer,
|
||||
sidebarFilter: sidebarFilterReducer,
|
||||
searchFilter: filterReducer,
|
||||
truncatable: truncatableReducer,
|
||||
cssVariables: cssVariablesReducer,
|
||||
menus: menusReducer,
|
||||
objectSelection: objectSelectionReducer
|
||||
objectSelection: objectSelectionReducer,
|
||||
selectableLists: selectableListReducer,
|
||||
relationshipLists: nameVariantReducer,
|
||||
communityList: CommunityListReducer,
|
||||
};
|
||||
|
||||
export const routerStateSelector = (state: AppState) => state.router;
|
||||
|
40
src/app/community-list-page/community-list-datasource.ts
Normal file
40
src/app/community-list-page/community-list-datasource.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { CommunityListService, FlatNode } from './community-list-service';
|
||||
import { CollectionViewer, DataSource } from '@angular/cdk/typings/collections';
|
||||
import { BehaviorSubject, Observable, } from 'rxjs';
|
||||
import { finalize, take, } from 'rxjs/operators';
|
||||
|
||||
/**
|
||||
* DataSource object needed by a CDK Tree to render its nodes.
|
||||
* The list of FlatNodes that this DataSource object represents gets created in the CommunityListService at
|
||||
* the beginning (initial page-limited top communities) and re-calculated any time the tree state changes
|
||||
* (a node gets expanded or page-limited result become larger by triggering a show more node)
|
||||
*/
|
||||
export class CommunityListDatasource implements DataSource<FlatNode> {
|
||||
|
||||
private communityList$ = new BehaviorSubject<FlatNode[]>([]);
|
||||
public loading$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
constructor(private communityListService: CommunityListService) {
|
||||
}
|
||||
|
||||
connect(collectionViewer: CollectionViewer): Observable<FlatNode[]> {
|
||||
return this.communityList$.asObservable();
|
||||
}
|
||||
|
||||
loadCommunities(expandedNodes: FlatNode[]) {
|
||||
this.loading$.next(true);
|
||||
|
||||
this.communityListService.loadCommunities(expandedNodes).pipe(
|
||||
take(1),
|
||||
finalize(() => this.loading$.next(false)),
|
||||
).subscribe((flatNodes: FlatNode[]) => {
|
||||
this.communityList$.next(flatNodes);
|
||||
});
|
||||
}
|
||||
|
||||
disconnect(collectionViewer: CollectionViewer): void {
|
||||
this.communityList$.complete();
|
||||
this.loading$.complete();
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
<div class="container">
|
||||
<h2>{{ 'communityList.title' | translate }}</h2>
|
||||
<ds-community-list></ds-community-list>
|
||||
</div>
|
@@ -0,0 +1,41 @@
|
||||
import { async, ComponentFixture, TestBed, inject } from '@angular/core/testing';
|
||||
|
||||
import { CommunityListPageComponent } from './community-list-page.component';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { MockTranslateLoader } from '../shared/mocks/mock-translate-loader';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
|
||||
describe('CommunityListPageComponent', () => {
|
||||
let component: CommunityListPageComponent;
|
||||
let fixture: ComponentFixture<CommunityListPageComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: MockTranslateLoader
|
||||
},
|
||||
}),
|
||||
],
|
||||
declarations: [CommunityListPageComponent],
|
||||
providers: [
|
||||
CommunityListPageComponent,
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CommunityListPageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', inject([CommunityListPageComponent], (comp: CommunityListPageComponent) => {
|
||||
expect(comp).toBeTruthy();
|
||||
}));
|
||||
|
||||
});
|
13
src/app/community-list-page/community-list-page.component.ts
Normal file
13
src/app/community-list-page/community-list-page.component.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Page with title and the community list tree, as described in community-list.component;
|
||||
* navigated to with community-list.page.routing.module
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-community-list-page',
|
||||
templateUrl: './community-list-page.component.html',
|
||||
})
|
||||
export class CommunityListPageComponent {
|
||||
|
||||
}
|
26
src/app/community-list-page/community-list-page.module.ts
Normal file
26
src/app/community-list-page/community-list-page.module.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { CommunityListPageComponent } from './community-list-page.component';
|
||||
import { CommunityListPageRoutingModule } from './community-list-page.routing.module';
|
||||
import { CommunityListComponent } from './community-list/community-list.component';
|
||||
import { CdkTreeModule } from '@angular/cdk/tree';
|
||||
|
||||
/**
|
||||
* The page which houses a title and the community list, as described in community-list.component
|
||||
*/
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
CommunityListPageRoutingModule,
|
||||
CdkTreeModule,
|
||||
],
|
||||
declarations: [
|
||||
CommunityListPageComponent,
|
||||
CommunityListComponent
|
||||
]
|
||||
})
|
||||
export class CommunityListPageModule {
|
||||
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { CdkTreeModule } from '@angular/cdk/tree';
|
||||
|
||||
import { CommunityListPageComponent } from './community-list-page.component';
|
||||
import { CommunityListService } from './community-list-service';
|
||||
|
||||
/**
|
||||
* RouterModule to help navigate to the page with the community list tree
|
||||
*/
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: '',
|
||||
component: CommunityListPageComponent,
|
||||
pathMatch: 'full',
|
||||
data: { title: 'communityList.tabTitle' }
|
||||
}
|
||||
]),
|
||||
CdkTreeModule,
|
||||
],
|
||||
providers: [CommunityListService]
|
||||
})
|
||||
export class CommunityListPageRoutingModule {
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user