diff --git a/README.md b/README.md
index a9f2b0861b..27e61afe76 100644
--- a/README.md
+++ b/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
diff --git a/config/environment.default.js b/config/environment.default.js
index 03080f756f..24386d6cf7 100644
--- a/config/environment.default.js
+++ b/config/environment.default.js
@@ -141,6 +141,10 @@ module.exports = {
code: 'nl',
label: 'Nederlands',
active: false,
+ }, {
+ code: 'pt',
+ label: 'Português',
+ active: true,
}],
// Browse-By Pages
browseBy: {
diff --git a/resources/i18n/ar.json5 b/resources/i18n/ar.json5
new file mode 100644
index 0000000000..398c57e6b2
--- /dev/null
+++ b/resources/i18n/ar.json5
@@ -0,0 +1,3220 @@
+{
+
+ // "404.help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ",
+ // TODO New key - Add a translation
+ "404.help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ",
+
+ // "404.link.home-page": "Take me to the home page",
+ // TODO New key - Add a translation
+ "404.link.home-page": "Take me to the home page",
+
+ // "404.page-not-found": "page not found",
+ // TODO New key - Add a translation
+ "404.page-not-found": "page not found",
+
+
+
+ // "admin.registries.bitstream-formats.create.failure.content": "An error occurred while creating the new bitstream format.",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.create.failure.content": "An error occurred while creating the new bitstream format.",
+
+ // "admin.registries.bitstream-formats.create.failure.head": "Failure",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.create.failure.head": "Failure",
+
+ // "admin.registries.bitstream-formats.create.head": "Create Bitstream format",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.create.head": "Create Bitstream format",
+
+ // "admin.registries.bitstream-formats.create.new": "Add a new bitstream format",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.create.new": "Add a new bitstream format",
+
+ // "admin.registries.bitstream-formats.create.success.content": "The new bitstream format was successfully created.",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.create.success.content": "The new bitstream format was successfully created.",
+
+ // "admin.registries.bitstream-formats.create.success.head": "Success",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.create.success.head": "Success",
+
+ // "admin.registries.bitstream-formats.delete.failure.amount": "Failed to remove {{ amount }} format(s)",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.delete.failure.amount": "Failed to remove {{ amount }} format(s)",
+
+ // "admin.registries.bitstream-formats.delete.failure.head": "Failure",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.delete.failure.head": "Failure",
+
+ // "admin.registries.bitstream-formats.delete.success.amount": "Successfully removed {{ amount }} format(s)",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.delete.success.amount": "Successfully removed {{ amount }} format(s)",
+
+ // "admin.registries.bitstream-formats.delete.success.head": "Success",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.delete.success.head": "Success",
+
+ // "admin.registries.bitstream-formats.description": "This list of bitstream formats provides information about known formats and their support level.",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.description": "This list of bitstream formats provides information about known formats and their support level.",
+
+ // "admin.registries.bitstream-formats.edit.description.hint": "",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.description.hint": "",
+
+ // "admin.registries.bitstream-formats.edit.description.label": "Description",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.description.label": "Description",
+
+ // "admin.registries.bitstream-formats.edit.extensions.hint": "Extensions are file extensions that are used to automatically identify the format of uploaded files. You can enter several extensions for each format.",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.extensions.hint": "Extensions are file extensions that are used to automatically identify the format of uploaded files. You can enter several extensions for each format.",
+
+ // "admin.registries.bitstream-formats.edit.extensions.label": "File extensions",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.extensions.label": "File extensions",
+
+ // "admin.registries.bitstream-formats.edit.extensions.placeholder": "Enter a file extenstion without the dot",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.extensions.placeholder": "Enter a file extenstion without the dot",
+
+ // "admin.registries.bitstream-formats.edit.failure.content": "An error occurred while editing the bitstream format.",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.failure.content": "An error occurred while editing the bitstream format.",
+
+ // "admin.registries.bitstream-formats.edit.failure.head": "Failure",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.failure.head": "Failure",
+
+ // "admin.registries.bitstream-formats.edit.head": "Bitstream format: {{ format }}",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.head": "Bitstream format: {{ format }}",
+
+ // "admin.registries.bitstream-formats.edit.internal.hint": "Formats marked as internal are are hidden from the user, and used for administrative purposes.",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.internal.hint": "Formats marked as internal are are hidden from the user, and used for administrative purposes.",
+
+ // "admin.registries.bitstream-formats.edit.internal.label": "Internal",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.internal.label": "Internal",
+
+ // "admin.registries.bitstream-formats.edit.mimetype.hint": "The MIME type associated with this format, does not have to be unique.",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.mimetype.hint": "The MIME type associated with this format, does not have to be unique.",
+
+ // "admin.registries.bitstream-formats.edit.mimetype.label": "MIME Type",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.mimetype.label": "MIME Type",
+
+ // "admin.registries.bitstream-formats.edit.shortDescription.hint": "A unique name for this format, (e.g. Microsoft Word XP or Microsoft Word 2000)",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.shortDescription.hint": "A unique name for this format, (e.g. Microsoft Word XP or Microsoft Word 2000)",
+
+ // "admin.registries.bitstream-formats.edit.shortDescription.label": "Name",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.shortDescription.label": "Name",
+
+ // "admin.registries.bitstream-formats.edit.success.content": "The bitstream format was successfully edited.",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.success.content": "The bitstream format was successfully edited.",
+
+ // "admin.registries.bitstream-formats.edit.success.head": "Success",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.success.head": "Success",
+
+ // "admin.registries.bitstream-formats.edit.supportLevel.hint": "The level of support your institution pledges for this format.",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.supportLevel.hint": "The level of support your institution pledges for this format.",
+
+ // "admin.registries.bitstream-formats.edit.supportLevel.label": "Support level",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.edit.supportLevel.label": "Support level",
+
+ // "admin.registries.bitstream-formats.head": "Bitstream Format Registry",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.head": "Bitstream Format Registry",
+
+ // "admin.registries.bitstream-formats.no-items": "No bitstream formats to show.",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.no-items": "No bitstream formats to show.",
+
+ // "admin.registries.bitstream-formats.table.delete": "Delete selected",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.table.delete": "Delete selected",
+
+ // "admin.registries.bitstream-formats.table.deselect-all": "Deselect all",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.table.deselect-all": "Deselect all",
+
+ // "admin.registries.bitstream-formats.table.internal": "internal",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.table.internal": "internal",
+
+ // "admin.registries.bitstream-formats.table.mimetype": "MIME Type",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.table.mimetype": "MIME Type",
+
+ // "admin.registries.bitstream-formats.table.name": "Name",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.table.name": "Name",
+
+ // "admin.registries.bitstream-formats.table.return": "Return",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.table.return": "Return",
+
+ // "admin.registries.bitstream-formats.table.supportLevel.KNOWN": "Known",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.table.supportLevel.KNOWN": "Known",
+
+ // "admin.registries.bitstream-formats.table.supportLevel.SUPPORTED": "Supported",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.table.supportLevel.SUPPORTED": "Supported",
+
+ // "admin.registries.bitstream-formats.table.supportLevel.UNKNOWN": "Unknown",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.table.supportLevel.UNKNOWN": "Unknown",
+
+ // "admin.registries.bitstream-formats.table.supportLevel.head": "Support Level",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.table.supportLevel.head": "Support Level",
+
+ // "admin.registries.bitstream-formats.title": "DSpace Angular :: Bitstream Format Registry",
+ // TODO New key - Add a translation
+ "admin.registries.bitstream-formats.title": "DSpace Angular :: Bitstream Format Registry",
+
+
+
+ // "admin.registries.metadata.description": "The metadata registry maintains a list of all metadata fields available in the repository. These fields may be divided amongst multiple schemas. However, DSpace requires the qualified Dublin Core schema.",
+ // TODO New key - Add a translation
+ "admin.registries.metadata.description": "The metadata registry maintains a list of all metadata fields available in the repository. These fields may be divided amongst multiple schemas. However, DSpace requires the qualified Dublin Core schema.",
+
+ // "admin.registries.metadata.form.create": "Create metadata schema",
+ // TODO New key - Add a translation
+ "admin.registries.metadata.form.create": "Create metadata schema",
+
+ // "admin.registries.metadata.form.edit": "Edit metadata schema",
+ // TODO New key - Add a translation
+ "admin.registries.metadata.form.edit": "Edit metadata schema",
+
+ // "admin.registries.metadata.form.name": "Name",
+ // TODO New key - Add a translation
+ "admin.registries.metadata.form.name": "Name",
+
+ // "admin.registries.metadata.form.namespace": "Namespace",
+ // TODO New key - Add a translation
+ "admin.registries.metadata.form.namespace": "Namespace",
+
+ // "admin.registries.metadata.head": "Metadata Registry",
+ // TODO New key - Add a translation
+ "admin.registries.metadata.head": "Metadata Registry",
+
+ // "admin.registries.metadata.schemas.no-items": "No metadata schemas to show.",
+ // TODO New key - Add a translation
+ "admin.registries.metadata.schemas.no-items": "No metadata schemas to show.",
+
+ // "admin.registries.metadata.schemas.table.delete": "Delete selected",
+ // TODO New key - Add a translation
+ "admin.registries.metadata.schemas.table.delete": "Delete selected",
+
+ // "admin.registries.metadata.schemas.table.id": "ID",
+ // TODO New key - Add a translation
+ "admin.registries.metadata.schemas.table.id": "ID",
+
+ // "admin.registries.metadata.schemas.table.name": "Name",
+ // TODO New key - Add a translation
+ "admin.registries.metadata.schemas.table.name": "Name",
+
+ // "admin.registries.metadata.schemas.table.namespace": "Namespace",
+ // TODO New key - Add a translation
+ "admin.registries.metadata.schemas.table.namespace": "Namespace",
+
+ // "admin.registries.metadata.title": "DSpace Angular :: Metadata Registry",
+ // TODO New key - Add a translation
+ "admin.registries.metadata.title": "DSpace Angular :: Metadata Registry",
+
+
+
+ // "admin.registries.schema.description": "This is the metadata schema for \"{{namespace}}\".",
+ // TODO New key - Add a translation
+ "admin.registries.schema.description": "This is the metadata schema for \"{{namespace}}\".",
+
+ // "admin.registries.schema.fields.head": "Schema metadata fields",
+ // TODO New key - Add a translation
+ "admin.registries.schema.fields.head": "Schema metadata fields",
+
+ // "admin.registries.schema.fields.no-items": "No metadata fields to show.",
+ // TODO New key - Add a translation
+ "admin.registries.schema.fields.no-items": "No metadata fields to show.",
+
+ // "admin.registries.schema.fields.table.delete": "Delete selected",
+ // TODO New key - Add a translation
+ "admin.registries.schema.fields.table.delete": "Delete selected",
+
+ // "admin.registries.schema.fields.table.field": "Field",
+ // TODO New key - Add a translation
+ "admin.registries.schema.fields.table.field": "Field",
+
+ // "admin.registries.schema.fields.table.scopenote": "Scope Note",
+ // TODO New key - Add a translation
+ "admin.registries.schema.fields.table.scopenote": "Scope Note",
+
+ // "admin.registries.schema.form.create": "Create metadata field",
+ // TODO New key - Add a translation
+ "admin.registries.schema.form.create": "Create metadata field",
+
+ // "admin.registries.schema.form.edit": "Edit metadata field",
+ // TODO New key - Add a translation
+ "admin.registries.schema.form.edit": "Edit metadata field",
+
+ // "admin.registries.schema.form.element": "Element",
+ // TODO New key - Add a translation
+ "admin.registries.schema.form.element": "Element",
+
+ // "admin.registries.schema.form.qualifier": "Qualifier",
+ // TODO New key - Add a translation
+ "admin.registries.schema.form.qualifier": "Qualifier",
+
+ // "admin.registries.schema.form.scopenote": "Scope Note",
+ // TODO New key - Add a translation
+ "admin.registries.schema.form.scopenote": "Scope Note",
+
+ // "admin.registries.schema.head": "Metadata Schema",
+ // TODO New key - Add a translation
+ "admin.registries.schema.head": "Metadata Schema",
+
+ // "admin.registries.schema.notification.created": "Successfully created metadata schema \"{{prefix}}\"",
+ // TODO New key - Add a translation
+ "admin.registries.schema.notification.created": "Successfully created metadata schema \"{{prefix}}\"",
+
+ // "admin.registries.schema.notification.deleted.failure": "Failed to delete {{amount}} metadata schemas",
+ // TODO New key - Add a translation
+ "admin.registries.schema.notification.deleted.failure": "Failed to delete {{amount}} metadata schemas",
+
+ // "admin.registries.schema.notification.deleted.success": "Successfully deleted {{amount}} metadata schemas",
+ // TODO New key - Add a translation
+ "admin.registries.schema.notification.deleted.success": "Successfully deleted {{amount}} metadata schemas",
+
+ // "admin.registries.schema.notification.edited": "Successfully edited metadata schema \"{{prefix}}\"",
+ // TODO New key - Add a translation
+ "admin.registries.schema.notification.edited": "Successfully edited metadata schema \"{{prefix}}\"",
+
+ // "admin.registries.schema.notification.failure": "Error",
+ // TODO New key - Add a translation
+ "admin.registries.schema.notification.failure": "Error",
+
+ // "admin.registries.schema.notification.field.created": "Successfully created metadata field \"{{field}}\"",
+ // TODO New key - Add a translation
+ "admin.registries.schema.notification.field.created": "Successfully created metadata field \"{{field}}\"",
+
+ // "admin.registries.schema.notification.field.deleted.failure": "Failed to delete {{amount}} metadata fields",
+ // TODO New key - Add a translation
+ "admin.registries.schema.notification.field.deleted.failure": "Failed to delete {{amount}} metadata fields",
+
+ // "admin.registries.schema.notification.field.deleted.success": "Successfully deleted {{amount}} metadata fields",
+ // TODO New key - Add a translation
+ "admin.registries.schema.notification.field.deleted.success": "Successfully deleted {{amount}} metadata fields",
+
+ // "admin.registries.schema.notification.field.edited": "Successfully edited metadata field \"{{field}}\"",
+ // TODO New key - Add a translation
+ "admin.registries.schema.notification.field.edited": "Successfully edited metadata field \"{{field}}\"",
+
+ // "admin.registries.schema.notification.success": "Success",
+ // TODO New key - Add a translation
+ "admin.registries.schema.notification.success": "Success",
+
+ // "admin.registries.schema.return": "Return",
+ // TODO New key - Add a translation
+ "admin.registries.schema.return": "Return",
+
+ // "admin.registries.schema.title": "DSpace Angular :: Metadata Schema Registry",
+ // TODO New key - Add a translation
+ "admin.registries.schema.title": "DSpace Angular :: Metadata Schema Registry",
+
+
+
+ // "auth.errors.invalid-user": "Invalid email address or password.",
+ // TODO New key - Add a translation
+ "auth.errors.invalid-user": "Invalid email address or password.",
+
+ // "auth.messages.expired": "Your session has expired. Please log in again.",
+ // TODO New key - Add a translation
+ "auth.messages.expired": "Your session has expired. Please log in again.",
+
+
+
+ // "browse.comcol.by.author": "By Author",
+ // TODO New key - Add a translation
+ "browse.comcol.by.author": "By Author",
+
+ // "browse.comcol.by.dateissued": "By Issue Date",
+ // TODO New key - Add a translation
+ "browse.comcol.by.dateissued": "By Issue Date",
+
+ // "browse.comcol.by.subject": "By Subject",
+ // TODO New key - Add a translation
+ "browse.comcol.by.subject": "By Subject",
+
+ // "browse.comcol.by.title": "By Title",
+ // TODO New key - Add a translation
+ "browse.comcol.by.title": "By Title",
+
+ // "browse.comcol.head": "Browse",
+ // TODO New key - Add a translation
+ "browse.comcol.head": "Browse",
+
+ // "browse.empty": "No items to show.",
+ // TODO New key - Add a translation
+ "browse.empty": "No items to show.",
+
+ // "browse.metadata.author": "Author",
+ // TODO New key - Add a translation
+ "browse.metadata.author": "Author",
+
+ // "browse.metadata.dateissued": "Issue Date",
+ // TODO New key - Add a translation
+ "browse.metadata.dateissued": "Issue Date",
+
+ // "browse.metadata.subject": "Subject",
+ // TODO New key - Add a translation
+ "browse.metadata.subject": "Subject",
+
+ // "browse.metadata.title": "Title",
+ // TODO New key - Add a translation
+ "browse.metadata.title": "Title",
+
+ // "browse.startsWith.choose_start": "(Choose start)",
+ // TODO New key - Add a translation
+ "browse.startsWith.choose_start": "(Choose start)",
+
+ // "browse.startsWith.choose_year": "(Choose year)",
+ // TODO New key - Add a translation
+ "browse.startsWith.choose_year": "(Choose year)",
+
+ // "browse.startsWith.jump": "Jump to a point in the index:",
+ // TODO New key - Add a translation
+ "browse.startsWith.jump": "Jump to a point in the index:",
+
+ // "browse.startsWith.months.april": "April",
+ // TODO New key - Add a translation
+ "browse.startsWith.months.april": "April",
+
+ // "browse.startsWith.months.august": "August",
+ // TODO New key - Add a translation
+ "browse.startsWith.months.august": "August",
+
+ // "browse.startsWith.months.december": "December",
+ // TODO New key - Add a translation
+ "browse.startsWith.months.december": "December",
+
+ // "browse.startsWith.months.february": "February",
+ // TODO New key - Add a translation
+ "browse.startsWith.months.february": "February",
+
+ // "browse.startsWith.months.january": "January",
+ // TODO New key - Add a translation
+ "browse.startsWith.months.january": "January",
+
+ // "browse.startsWith.months.july": "July",
+ // TODO New key - Add a translation
+ "browse.startsWith.months.july": "July",
+
+ // "browse.startsWith.months.june": "June",
+ // TODO New key - Add a translation
+ "browse.startsWith.months.june": "June",
+
+ // "browse.startsWith.months.march": "March",
+ // TODO New key - Add a translation
+ "browse.startsWith.months.march": "March",
+
+ // "browse.startsWith.months.may": "May",
+ // TODO New key - Add a translation
+ "browse.startsWith.months.may": "May",
+
+ // "browse.startsWith.months.none": "(Choose month)",
+ // TODO New key - Add a translation
+ "browse.startsWith.months.none": "(Choose month)",
+
+ // "browse.startsWith.months.november": "November",
+ // TODO New key - Add a translation
+ "browse.startsWith.months.november": "November",
+
+ // "browse.startsWith.months.october": "October",
+ // TODO New key - Add a translation
+ "browse.startsWith.months.october": "October",
+
+ // "browse.startsWith.months.september": "September",
+ // TODO New key - Add a translation
+ "browse.startsWith.months.september": "September",
+
+ // "browse.startsWith.submit": "Go",
+ // TODO New key - Add a translation
+ "browse.startsWith.submit": "Go",
+
+ // "browse.startsWith.type_date": "Or type in a date (year-month):",
+ // TODO New key - Add a translation
+ "browse.startsWith.type_date": "Or type in a date (year-month):",
+
+ // "browse.startsWith.type_text": "Or enter first few letters:",
+ // TODO New key - Add a translation
+ "browse.startsWith.type_text": "Or enter first few letters:",
+
+ // "browse.title": "Browsing {{ collection }} by {{ field }} {{ value }}",
+ // TODO New key - Add a translation
+ "browse.title": "Browsing {{ collection }} by {{ field }} {{ value }}",
+
+
+
+ // "chips.remove": "Remove chip",
+ // TODO New key - Add a translation
+ "chips.remove": "Remove chip",
+
+
+
+ // "collection.create.head": "Create a Collection",
+ // TODO New key - Add a translation
+ "collection.create.head": "Create a Collection",
+
+ // "collection.create.sub-head": "Create a Collection for Community {{ parent }}",
+ // TODO New key - Add a translation
+ "collection.create.sub-head": "Create a Collection for Community {{ parent }}",
+
+ // "collection.delete.cancel": "Cancel",
+ // TODO New key - Add a translation
+ "collection.delete.cancel": "Cancel",
+
+ // "collection.delete.confirm": "Confirm",
+ // TODO New key - Add a translation
+ "collection.delete.confirm": "Confirm",
+
+ // "collection.delete.head": "Delete Collection",
+ // TODO New key - Add a translation
+ "collection.delete.head": "Delete Collection",
+
+ // "collection.delete.notification.fail": "Collection could not be deleted",
+ // TODO New key - Add a translation
+ "collection.delete.notification.fail": "Collection could not be deleted",
+
+ // "collection.delete.notification.success": "Successfully deleted collection",
+ // TODO New key - Add a translation
+ "collection.delete.notification.success": "Successfully deleted collection",
+
+ // "collection.delete.text": "Are you sure you want to delete collection \"{{ dso }}\"",
+ // TODO New key - Add a translation
+ "collection.delete.text": "Are you sure you want to delete collection \"{{ dso }}\"",
+
+
+
+ // "collection.edit.delete": "Delete this collection",
+ // TODO New key - Add a translation
+ "collection.edit.delete": "Delete this collection",
+
+ // "collection.edit.head": "Edit Collection",
+ // TODO New key - Add a translation
+ "collection.edit.head": "Edit Collection",
+
+
+
+ // "collection.edit.item-mapper.cancel": "Cancel",
+ // TODO New key - Add a translation
+ "collection.edit.item-mapper.cancel": "Cancel",
+
+ // "collection.edit.item-mapper.collection": "Collection: \"{{name}}\"",
+ // TODO New key - Add a translation
+ "collection.edit.item-mapper.collection": "Collection: \"{{name}}\"",
+
+ // "collection.edit.item-mapper.confirm": "Map selected items",
+ // TODO New key - Add a translation
+ "collection.edit.item-mapper.confirm": "Map selected items",
+
+ // "collection.edit.item-mapper.description": "This is the item mapper tool that allows collection administrators to map items from other collections into this collection. You can search for items from other collections and map them, or browse the list of currently mapped items.",
+ // TODO New key - Add a translation
+ "collection.edit.item-mapper.description": "This is the item mapper tool that allows collection administrators to map items from other collections into this collection. You can search for items from other collections and map them, or browse the list of currently mapped items.",
+
+ // "collection.edit.item-mapper.head": "Item Mapper - Map Items from Other Collections",
+ // TODO New key - Add a translation
+ "collection.edit.item-mapper.head": "Item Mapper - Map Items from Other Collections",
+
+ // "collection.edit.item-mapper.no-search": "Please enter a query to search",
+ // TODO New key - Add a translation
+ "collection.edit.item-mapper.no-search": "Please enter a query to search",
+
+ // "collection.edit.item-mapper.notifications.map.error.content": "Errors occurred for mapping of {{amount}} items.",
+ // TODO New key - Add a translation
+ "collection.edit.item-mapper.notifications.map.error.content": "Errors occurred for mapping of {{amount}} items.",
+
+ // "collection.edit.item-mapper.notifications.map.error.head": "Mapping errors",
+ // TODO New key - Add a translation
+ "collection.edit.item-mapper.notifications.map.error.head": "Mapping errors",
+
+ // "collection.edit.item-mapper.notifications.map.success.content": "Successfully mapped {{amount}} items.",
+ // TODO New key - Add a translation
+ "collection.edit.item-mapper.notifications.map.success.content": "Successfully mapped {{amount}} items.",
+
+ // "collection.edit.item-mapper.notifications.map.success.head": "Mapping completed",
+ // TODO New key - Add a translation
+ "collection.edit.item-mapper.notifications.map.success.head": "Mapping completed",
+
+ // "collection.edit.item-mapper.notifications.unmap.error.content": "Errors occurred for removing the mappings of {{amount}} items.",
+ // TODO New key - Add a translation
+ "collection.edit.item-mapper.notifications.unmap.error.content": "Errors occurred for removing the mappings of {{amount}} items.",
+
+ // "collection.edit.item-mapper.notifications.unmap.error.head": "Remove mapping errors",
+ // TODO New key - Add a translation
+ "collection.edit.item-mapper.notifications.unmap.error.head": "Remove mapping errors",
+
+ // "collection.edit.item-mapper.notifications.unmap.success.content": "Successfully removed the mappings of {{amount}} items.",
+ // TODO New key - Add a translation
+ "collection.edit.item-mapper.notifications.unmap.success.content": "Successfully removed the mappings of {{amount}} items.",
+
+ // "collection.edit.item-mapper.notifications.unmap.success.head": "Remove mapping completed",
+ // TODO New key - Add a translation
+ "collection.edit.item-mapper.notifications.unmap.success.head": "Remove mapping completed",
+
+ // "collection.edit.item-mapper.remove": "Remove selected item mappings",
+ // TODO New key - Add a translation
+ "collection.edit.item-mapper.remove": "Remove selected item mappings",
+
+ // "collection.edit.item-mapper.tabs.browse": "Browse mapped items",
+ // TODO New key - Add a translation
+ "collection.edit.item-mapper.tabs.browse": "Browse mapped items",
+
+ // "collection.edit.item-mapper.tabs.map": "Map new items",
+ // TODO New key - Add a translation
+ "collection.edit.item-mapper.tabs.map": "Map new items",
+
+
+
+ // "collection.form.abstract": "Short Description",
+ // TODO New key - Add a translation
+ "collection.form.abstract": "Short Description",
+
+ // "collection.form.description": "Introductory text (HTML)",
+ // TODO New key - Add a translation
+ "collection.form.description": "Introductory text (HTML)",
+
+ // "collection.form.errors.title.required": "Please enter a collection name",
+ // TODO New key - Add a translation
+ "collection.form.errors.title.required": "Please enter a collection name",
+
+ // "collection.form.license": "License",
+ // TODO New key - Add a translation
+ "collection.form.license": "License",
+
+ // "collection.form.provenance": "Provenance",
+ // TODO New key - Add a translation
+ "collection.form.provenance": "Provenance",
+
+ // "collection.form.rights": "Copyright text (HTML)",
+ // TODO New key - Add a translation
+ "collection.form.rights": "Copyright text (HTML)",
+
+ // "collection.form.tableofcontents": "News (HTML)",
+ // TODO New key - Add a translation
+ "collection.form.tableofcontents": "News (HTML)",
+
+ // "collection.form.title": "Name",
+ // TODO New key - Add a translation
+ "collection.form.title": "Name",
+
+
+
+ // "collection.page.browse.recent.head": "Recent Submissions",
+ // TODO New key - Add a translation
+ "collection.page.browse.recent.head": "Recent Submissions",
+
+ // "collection.page.browse.recent.empty": "No items to show",
+ // TODO New key - Add a translation
+ "collection.page.browse.recent.empty": "No items to show",
+
+ // "collection.page.handle": "Permanent URI for this collection",
+ // TODO New key - Add a translation
+ "collection.page.handle": "Permanent URI for this collection",
+
+ // "collection.page.license": "License",
+ // TODO New key - Add a translation
+ "collection.page.license": "License",
+
+ // "collection.page.news": "News",
+ // TODO New key - Add a translation
+ "collection.page.news": "News",
+
+
+
+ // "collection.select.confirm": "Confirm selected",
+ // TODO New key - Add a translation
+ "collection.select.confirm": "Confirm selected",
+
+ // "collection.select.empty": "No collections to show",
+ // TODO New key - Add a translation
+ "collection.select.empty": "No collections to show",
+
+ // "collection.select.table.title": "Title",
+ // TODO New key - Add a translation
+ "collection.select.table.title": "Title",
+
+
+
+ // "community.create.head": "Create a Community",
+ // TODO New key - Add a translation
+ "community.create.head": "Create a Community",
+
+ // "community.create.sub-head": "Create a Sub-Community for Community {{ parent }}",
+ // TODO New key - Add a translation
+ "community.create.sub-head": "Create a Sub-Community for Community {{ parent }}",
+
+ // "community.delete.cancel": "Cancel",
+ // TODO New key - Add a translation
+ "community.delete.cancel": "Cancel",
+
+ // "community.delete.confirm": "Confirm",
+ // TODO New key - Add a translation
+ "community.delete.confirm": "Confirm",
+
+ // "community.delete.head": "Delete Community",
+ // TODO New key - Add a translation
+ "community.delete.head": "Delete Community",
+
+ // "community.delete.notification.fail": "Community could not be deleted",
+ // TODO New key - Add a translation
+ "community.delete.notification.fail": "Community could not be deleted",
+
+ // "community.delete.notification.success": "Successfully deleted community",
+ // TODO New key - Add a translation
+ "community.delete.notification.success": "Successfully deleted community",
+
+ // "community.delete.text": "Are you sure you want to delete community \"{{ dso }}\"",
+ // TODO New key - Add a translation
+ "community.delete.text": "Are you sure you want to delete community \"{{ dso }}\"",
+
+ // "community.edit.delete": "Delete this community",
+ // TODO New key - Add a translation
+ "community.edit.delete": "Delete this community",
+
+ // "community.edit.head": "Edit Community",
+ // TODO New key - Add a translation
+ "community.edit.head": "Edit Community",
+
+ // "community.form.abstract": "Short Description",
+ // TODO New key - Add a translation
+ "community.form.abstract": "Short Description",
+
+ // "community.form.description": "Introductory text (HTML)",
+ // TODO New key - Add a translation
+ "community.form.description": "Introductory text (HTML)",
+
+ // "community.form.errors.title.required": "Please enter a community name",
+ // TODO New key - Add a translation
+ "community.form.errors.title.required": "Please enter a community name",
+
+ // "community.form.rights": "Copyright text (HTML)",
+ // TODO New key - Add a translation
+ "community.form.rights": "Copyright text (HTML)",
+
+ // "community.form.tableofcontents": "News (HTML)",
+ // TODO New key - Add a translation
+ "community.form.tableofcontents": "News (HTML)",
+
+ // "community.form.title": "Name",
+ // TODO New key - Add a translation
+ "community.form.title": "Name",
+
+ // "community.page.handle": "Permanent URI for this community",
+ // TODO New key - Add a translation
+ "community.page.handle": "Permanent URI for this community",
+
+ // "community.page.license": "License",
+ // TODO New key - Add a translation
+ "community.page.license": "License",
+
+ // "community.page.news": "News",
+ // TODO New key - Add a translation
+ "community.page.news": "News",
+
+ // "community.all-lists.head": "Subcommunities and Collections",
+ // TODO New key - Add a translation
+ "community.all-lists.head": "Subcommunities and Collections",
+
+ // "community.sub-collection-list.head": "Collections of this Community",
+ // TODO New key - Add a translation
+ "community.sub-collection-list.head": "Collections of this Community",
+
+ // "community.sub-community-list.head": "Communities of this Community",
+ // TODO New key - Add a translation
+ "community.sub-community-list.head": "Communities of this Community",
+
+
+
+ // "dso-selector.create.collection.head": "New collection",
+ // TODO New key - Add a translation
+ "dso-selector.create.collection.head": "New collection",
+
+ // "dso-selector.create.community.head": "New community",
+ // TODO New key - Add a translation
+ "dso-selector.create.community.head": "New community",
+
+ // "dso-selector.create.community.sub-level": "Create a new community in",
+ // TODO New key - Add a translation
+ "dso-selector.create.community.sub-level": "Create a new community in",
+
+ // "dso-selector.create.community.top-level": "Create a new top-level community",
+ // TODO New key - Add a translation
+ "dso-selector.create.community.top-level": "Create a new top-level community",
+
+ // "dso-selector.create.item.head": "New item",
+ // TODO New key - Add a translation
+ "dso-selector.create.item.head": "New item",
+
+ // "dso-selector.edit.collection.head": "Edit collection",
+ // TODO New key - Add a translation
+ "dso-selector.edit.collection.head": "Edit collection",
+
+ // "dso-selector.edit.community.head": "Edit community",
+ // TODO New key - Add a translation
+ "dso-selector.edit.community.head": "Edit community",
+
+ // "dso-selector.edit.item.head": "Edit item",
+ // TODO New key - Add a translation
+ "dso-selector.edit.item.head": "Edit item",
+
+ // "dso-selector.no-results": "No {{ type }} found",
+ // TODO New key - Add a translation
+ "dso-selector.no-results": "No {{ type }} found",
+
+ // "dso-selector.placeholder": "Search for a {{ type }}",
+ // TODO New key - Add a translation
+ "dso-selector.placeholder": "Search for a {{ type }}",
+
+
+
+ // "error.browse-by": "Error fetching items",
+ // TODO New key - Add a translation
+ "error.browse-by": "Error fetching items",
+
+ // "error.collection": "Error fetching collection",
+ // TODO New key - Add a translation
+ "error.collection": "Error fetching collection",
+
+ // "error.collections": "Error fetching collections",
+ // TODO New key - Add a translation
+ "error.collections": "Error fetching collections",
+
+ // "error.community": "Error fetching community",
+ // TODO New key - Add a translation
+ "error.community": "Error fetching community",
+
+ // "error.identifier": "No item found for the identifier",
+ // TODO New key - Add a translation
+ "error.identifier": "No item found for the identifier",
+
+ // "error.default": "Error",
+ // TODO New key - Add a translation
+ "error.default": "Error",
+
+ // "error.item": "Error fetching item",
+ // TODO New key - Add a translation
+ "error.item": "Error fetching item",
+
+ // "error.items": "Error fetching items",
+ // TODO New key - Add a translation
+ "error.items": "Error fetching items",
+
+ // "error.objects": "Error fetching objects",
+ // TODO New key - Add a translation
+ "error.objects": "Error fetching objects",
+
+ // "error.recent-submissions": "Error fetching recent submissions",
+ // TODO New key - Add a translation
+ "error.recent-submissions": "Error fetching recent submissions",
+
+ // "error.search-results": "Error fetching search results",
+ // TODO New key - Add a translation
+ "error.search-results": "Error fetching search results",
+
+ // "error.sub-collections": "Error fetching sub-collections",
+ // TODO New key - Add a translation
+ "error.sub-collections": "Error fetching sub-collections",
+
+ // "error.sub-communities": "Error fetching sub-communities",
+ // TODO New key - Add a translation
+ "error.sub-communities": "Error fetching sub-communities",
+
+ // "error.submission.sections.init-form-error": "An error occurred during section initialize, please check your input-form configuration. Details are below :
",
+ // TODO New key - Add a translation
+ "error.submission.sections.init-form-error": "An error occurred during section initialize, please check your input-form configuration. Details are below :
",
+ "error.submission.sections.init-form-error": "Se produjo un error ocurrió durante el inicio de sesión, favor verifique su configuración del formulario de entrada. Los detalles están abajo :
",
+ // TODO New key - Add a translation
+ "error.submission.sections.init-form-error": "An error occurred during section initialize, please check your input-form configuration. Details are below :
",
+ // TODO New key - Add a translation
+ "error.submission.sections.init-form-error": "An error occurred during section initialize, please check your input-form configuration. Details are below :
",
+ // TODO New key - Add a translation
+ "error.submission.sections.init-form-error": "An error occurred during section initialize, please check your input-form configuration. Details are below :
",
+ // TODO New key - Add a translation
+ "error.submission.sections.init-form-error": "An error occurred during section initialize, please check your input-form configuration. Details are below :
",
+ "error.submission.sections.init-form-error": "Ocorreu um erro durante a seção de inicialização, por favor verifique sua configuração de input-form. Detalhes estão abaixo :
",
+ // TODO New key - Add a translation
+ "error.submission.sections.init-form-error": "An error occurred during section initialize, please check your input-form configuration. Details are below :
",
+ // TODO New key - Add a translation
+ "error.submission.sections.init-form-error": "An error occurred during section initialize, please check your input-form configuration. Details are below :
diff --git a/src/app/shared/sidebar/filter/sidebar-filter.component.scss b/src/app/shared/sidebar/filter/sidebar-filter.component.scss
new file mode 100644
index 0000000000..68949f3450
--- /dev/null
+++ b/src/app/shared/sidebar/filter/sidebar-filter.component.scss
@@ -0,0 +1,12 @@
+:host .facet-filter {
+ border: 1px solid map-get($theme-colors, light);
+ cursor: pointer;
+
+ .sidebar-filter-wrapper.closed {
+ overflow: hidden;
+ }
+
+ .filter-toggle {
+ line-height: $line-height-base;
+ }
+}
diff --git a/src/app/shared/sidebar/filter/sidebar-filter.component.ts b/src/app/shared/sidebar/filter/sidebar-filter.component.ts
new file mode 100644
index 0000000000..2a98565639
--- /dev/null
+++ b/src/app/shared/sidebar/filter/sidebar-filter.component.ts
@@ -0,0 +1,89 @@
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { Observable } from 'rxjs';
+import { SidebarFilterService } from './sidebar-filter.service';
+import { slide } from '../../animations/slide';
+
+@Component({
+ selector: 'ds-sidebar-filter',
+ styleUrls: ['./sidebar-filter.component.scss'],
+ templateUrl: './sidebar-filter.component.html',
+ animations: [slide],
+})
+/**
+ * This components renders a sidebar filter including the label and the selected values.
+ * The filter input itself should still be provided in the content.
+ */
+export class SidebarFilterComponent implements OnInit {
+
+ @Input() name:string;
+ @Input() type:string;
+ @Input() label:string;
+ @Input() expanded = true;
+ @Input() singleValue = false;
+ @Input() selectedValues:Observable;
+ @Output() removeValue:EventEmitter = new EventEmitter();
+
+ /**
+ * True when the filter is 100% collapsed in the UI
+ */
+ closed = true;
+
+ /**
+ * Emits true when the filter is currently collapsed in the store
+ */
+ collapsed$:Observable;
+
+ constructor(
+ protected filterService:SidebarFilterService,
+ ) {
+ }
+
+ /**
+ * Changes the state for this filter to collapsed when it's expanded and to expanded it when it's collapsed
+ */
+ toggle() {
+ this.filterService.toggle(this.name);
+ }
+
+ /**
+ * Method to change this.collapsed to false when the slide animation ends and is sliding open
+ * @param event The animation event
+ */
+ finishSlide(event:any):void {
+ if (event.fromState === 'collapsed') {
+ this.closed = false;
+ }
+ }
+
+ /**
+ * Method to change this.collapsed to true when the slide animation starts and is sliding closed
+ * @param event The animation event
+ */
+ startSlide(event:any):void {
+ if (event.toState === 'collapsed') {
+ this.closed = true;
+ }
+ }
+
+ ngOnInit():void {
+ this.closed = !this.expanded;
+ this.initializeFilter();
+ this.collapsed$ = this.isCollapsed();
+ }
+
+ /**
+ * Sets the initial state of the filter
+ */
+ initializeFilter() {
+ this.filterService.initializeFilter(this.name, this.expanded);
+ }
+
+ /**
+ * Checks if the filter is currently collapsed
+ * @returns {Observable} Emits true when the current state of the filter is collapsed, false when it's expanded
+ */
+ private isCollapsed():Observable {
+ return this.filterService.isCollapsed(this.name);
+ }
+
+}
diff --git a/src/app/shared/sidebar/filter/sidebar-filter.reducer.ts b/src/app/shared/sidebar/filter/sidebar-filter.reducer.ts
new file mode 100644
index 0000000000..d25737eaa9
--- /dev/null
+++ b/src/app/shared/sidebar/filter/sidebar-filter.reducer.ts
@@ -0,0 +1,70 @@
+import {
+ FilterInitializeAction,
+ SidebarFilterAction,
+ SidebarFilterActionTypes
+} from './sidebar-filter.actions';
+
+/**
+ * Interface that represents the state for a single filters
+ */
+export interface SidebarFilterState {
+ filterCollapsed:boolean,
+}
+
+/**
+ * Interface that represents the state for all available filters
+ */
+export interface SidebarFiltersState {
+ [name:string]:SidebarFilterState
+}
+
+const initialState:SidebarFiltersState = Object.create(null);
+
+/**
+ * Performs a filter action on the current state
+ * @param {SidebarFiltersState} state The state before the action is performed
+ * @param {SidebarFilterAction} action The action that should be performed
+ * @returns {SidebarFiltersState} The state after the action is performed
+ */
+export function sidebarFilterReducer(state = initialState, action:SidebarFilterAction):SidebarFiltersState {
+
+ switch (action.type) {
+
+ case SidebarFilterActionTypes.INITIALIZE: {
+ const initAction = (action as FilterInitializeAction);
+ return Object.assign({}, state, {
+ [action.filterName]: {
+ filterCollapsed: !initAction.initiallyExpanded,
+ }
+ });
+ }
+
+ case SidebarFilterActionTypes.COLLAPSE: {
+ return Object.assign({}, state, {
+ [action.filterName]: {
+ filterCollapsed: true,
+ }
+ });
+ }
+
+ case SidebarFilterActionTypes.EXPAND: {
+ return Object.assign({}, state, {
+ [action.filterName]: {
+ filterCollapsed: false,
+ }
+ });
+ }
+
+ case SidebarFilterActionTypes.TOGGLE: {
+ return Object.assign({}, state, {
+ [action.filterName]: {
+ filterCollapsed: !state[action.filterName].filterCollapsed,
+ }
+ });
+ }
+
+ default: {
+ return state;
+ }
+ }
+}
diff --git a/src/app/shared/sidebar/filter/sidebar-filter.service.ts b/src/app/shared/sidebar/filter/sidebar-filter.service.ts
new file mode 100644
index 0000000000..2ff28fd2f5
--- /dev/null
+++ b/src/app/shared/sidebar/filter/sidebar-filter.service.ts
@@ -0,0 +1,90 @@
+import { Injectable } from '@angular/core';
+import {
+ FilterCollapseAction,
+ FilterExpandAction, FilterInitializeAction,
+ FilterToggleAction
+} from './sidebar-filter.actions';
+import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
+import { SidebarFiltersState, SidebarFilterState } from './sidebar-filter.reducer';
+import { Observable } from 'rxjs';
+import { distinctUntilChanged, map } from 'rxjs/operators';
+import { hasValue } from '../../empty.util';
+
+/**
+ * Service that performs all actions that have to do with sidebar filters like collapsing or expanding them.
+ */
+@Injectable()
+export class SidebarFilterService {
+
+ constructor(private store:Store) {
+ }
+
+ /**
+ * Dispatches an initialize action to the store for a given filter
+ * @param {string} filter The filter for which the action is dispatched
+ * @param {boolean} expanded If the filter should be open from the start
+ */
+ public initializeFilter(filter:string, expanded:boolean):void {
+ this.store.dispatch(new FilterInitializeAction(filter, expanded));
+ }
+
+ /**
+ * Dispatches a collapse action to the store for a given filter
+ * @param {string} filterName The filter for which the action is dispatched
+ */
+ public collapse(filterName:string):void {
+ this.store.dispatch(new FilterCollapseAction(filterName));
+ }
+
+ /**
+ * Dispatches an expand action to the store for a given filter
+ * @param {string} filterName The filter for which the action is dispatched
+ */
+ public expand(filterName:string):void {
+ this.store.dispatch(new FilterExpandAction(filterName));
+ }
+
+ /**
+ * Dispatches a toggle action to the store for a given filter
+ * @param {string} filterName The filter for which the action is dispatched
+ */
+ public toggle(filterName:string):void {
+ this.store.dispatch(new FilterToggleAction(filterName));
+ }
+
+ /**
+ * Checks if the state of a given filter is currently collapsed or not
+ * @param {string} filterName The filtername for which the collapsed state is checked
+ * @returns {Observable} Emits the current collapsed state of the given filter, if it's unavailable, return false
+ */
+ isCollapsed(filterName:string):Observable {
+ return this.store.pipe(
+ select(filterByNameSelector(filterName)),
+ map((object:SidebarFilterState) => {
+ if (object) {
+ return object.filterCollapsed;
+ } else {
+ return false;
+ }
+ }),
+ distinctUntilChanged()
+ );
+ }
+
+}
+
+const filterStateSelector = (state:SidebarFiltersState) => state.sidebarFilter;
+
+function filterByNameSelector(name:string):MemoizedSelector {
+ return keySelector(name);
+}
+
+export function keySelector(key:string):MemoizedSelector {
+ return createSelector(filterStateSelector, (state:SidebarFilterState) => {
+ if (hasValue(state)) {
+ return state[key];
+ } else {
+ return undefined;
+ }
+ });
+}
diff --git a/src/app/shared/sidebar/page-with-sidebar.component.html b/src/app/shared/sidebar/page-with-sidebar.component.html
new file mode 100644
index 0000000000..9feb6c792e
--- /dev/null
+++ b/src/app/shared/sidebar/page-with-sidebar.component.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/shared/sidebar/page-with-sidebar.component.scss b/src/app/shared/sidebar/page-with-sidebar.component.scss
new file mode 100644
index 0000000000..8be48cea2b
--- /dev/null
+++ b/src/app/shared/sidebar/page-with-sidebar.component.scss
@@ -0,0 +1,52 @@
+@include media-breakpoint-down(md) {
+ .container {
+ width: 100%;
+ max-width: none;
+ }
+}
+
+.row-with-sidebar {
+
+ &.row-offcanvas {
+ width: 100%;
+ }
+
+ @include media-breakpoint-up(md) {
+ display: flex;
+ }
+
+ @include media-breakpoint-down(sm) {
+ position: relative;
+
+ &.row-offcanvas {
+ position: relative;
+ }
+
+ &.row-offcanvas-right .sidebar-content {
+ right: -100%;
+ }
+
+ &.row-offcanvas-left .sidebar-content {
+ left: -100%;
+ }
+
+ .sidebar-content {
+ position: absolute;
+ top: 0;
+ width: 100%;
+ }
+ }
+}
+
+@include media-breakpoint-up(md) {
+ .sidebar-content {
+ 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;
+ }
+}
diff --git a/src/app/shared/sidebar/page-with-sidebar.component.spec.ts b/src/app/shared/sidebar/page-with-sidebar.component.spec.ts
new file mode 100644
index 0000000000..77f59090ab
--- /dev/null
+++ b/src/app/shared/sidebar/page-with-sidebar.component.spec.ts
@@ -0,0 +1,75 @@
+import { By } from '@angular/platform-browser';
+import { of as observableOf } from 'rxjs';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { PageWithSidebarComponent } from './page-with-sidebar.component';
+import { SidebarService } from './sidebar.service';
+import { HostWindowService } from '../host-window.service';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+
+describe('PageWithSidebarComponent', () => {
+ let comp:PageWithSidebarComponent;
+ let fixture:ComponentFixture;
+
+ const sidebarService = {
+ isCollapsed: observableOf(true),
+ collapse: () => this.isCollapsed = observableOf(true),
+ expand: () => this.isCollapsed = observableOf(false)
+ };
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [NoopAnimationsModule],
+ providers: [
+ {
+ provide: SidebarService,
+ useValue: sidebarService
+ },
+ {
+ provide: HostWindowService, useValue: jasmine.createSpyObj('hostWindowService',
+ {
+ isXs: observableOf(true),
+ isSm: observableOf(false),
+ isXsOrSm: observableOf(true)
+ })
+ },
+ ],
+ declarations: [PageWithSidebarComponent]
+ }).compileComponents();
+ fixture = TestBed.createComponent(PageWithSidebarComponent);
+ comp = fixture.componentInstance;
+ comp.id = 'mock-id';
+ fixture.detectChanges();
+ });
+
+ describe('when sidebarCollapsed is true in mobile view', () => {
+ let menu:HTMLElement;
+
+ beforeEach(() => {
+ menu = fixture.debugElement.query(By.css('#mock-id-sidebar-content')).nativeElement;
+ (comp as any).sidebarService.isCollapsed = observableOf(true);
+ comp.ngOnInit();
+ 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('#mock-id-sidebar-content')).nativeElement;
+ (comp as any).sidebarService.isCollapsed = observableOf(false);
+ comp.ngOnInit();
+ fixture.detectChanges();
+ });
+
+ it('should open the menu', () => {
+ expect(menu.classList).toContain('active');
+ });
+
+ });
+});
diff --git a/src/app/shared/sidebar/page-with-sidebar.component.ts b/src/app/shared/sidebar/page-with-sidebar.component.ts
new file mode 100644
index 0000000000..8b7f987a37
--- /dev/null
+++ b/src/app/shared/sidebar/page-with-sidebar.component.ts
@@ -0,0 +1,77 @@
+import { Component, Input, OnInit, TemplateRef } from '@angular/core';
+import { SidebarService } from './sidebar.service';
+import { HostWindowService } from '../host-window.service';
+import { Observable } from 'rxjs';
+import { pushInOut } from '../animations/push';
+import { map } from 'rxjs/operators';
+
+@Component({
+ selector: 'ds-page-with-sidebar',
+ styleUrls: ['./page-with-sidebar.component.scss'],
+ templateUrl: './page-with-sidebar.component.html',
+ animations: [pushInOut],
+})
+/**
+ * This component takes care of displaying the sidebar properly on all viewports. It does not
+ * provide default buttons to open or close the sidebar. Instead the parent component is expected
+ * to provide the content of the sidebar through an input. The main content of the page goes in
+ * the template outlet (inside the page-width-sidebar tags).
+ */
+export class PageWithSidebarComponent implements OnInit {
+ @Input() id:string;
+ @Input() sidebarContent:TemplateRef;
+
+ /**
+ * Emits true if were on a small screen
+ */
+ isXsOrSm$:Observable;
+
+ /**
+ * The width of the sidebar (bootstrap columns)
+ */
+ @Input()
+ sideBarWidth = 3;
+
+ /**
+ * Observable for whether or not the sidebar is currently collapsed
+ */
+ isSidebarCollapsed$:Observable;
+
+ sidebarClasses:Observable;
+
+ constructor(protected sidebarService:SidebarService,
+ protected windowService:HostWindowService,
+ ) {
+ }
+
+ ngOnInit():void {
+ this.isXsOrSm$ = this.windowService.isXsOrSm();
+ this.isSidebarCollapsed$ = this.isSidebarCollapsed();
+ this.sidebarClasses = this.isSidebarCollapsed$.pipe(
+ map((isCollapsed) => isCollapsed ? '' : 'active')
+ );
+ }
+
+ /**
+ * Check if the sidebar is collapsed
+ * @returns {Observable} emits true if the sidebar is currently collapsed, false if it is expanded
+ */
+ private isSidebarCollapsed():Observable {
+ return this.sidebarService.isCollapsed;
+ }
+
+ /**
+ * 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();
+ }
+
+}
diff --git a/src/app/+search-page/search-sidebar/search-sidebar.effects.spec.ts b/src/app/shared/sidebar/search-sidebar.effects.spec.ts
similarity index 68%
rename from src/app/+search-page/search-sidebar/search-sidebar.effects.spec.ts
rename to src/app/shared/sidebar/search-sidebar.effects.spec.ts
index f34f6b72de..da675a38ce 100644
--- a/src/app/+search-page/search-sidebar/search-sidebar.effects.spec.ts
+++ b/src/app/shared/sidebar/search-sidebar.effects.spec.ts
@@ -3,24 +3,24 @@ 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';
+import { SidebarCollapseAction } from './sidebar.actions';
+import { SidebarEffects } from './sidebar-effects.service';
-describe('SearchSidebarEffects', () => {
- let sidebarEffects: SearchSidebarEffects;
+describe('SidebarEffects', () => {
+ let sidebarEffects: SidebarEffects;
let actions: Observable;
const dummyURL = 'http://f4fb15e2-1bd3-4e63-8d0d-486ad8bc714a';
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
- SearchSidebarEffects,
+ SidebarEffects,
provideMockActions(() => actions),
// other providers
],
});
- sidebarEffects = TestBed.get(SearchSidebarEffects);
+ sidebarEffects = TestBed.get(SidebarEffects);
});
describe('routeChange$', () => {
@@ -28,7 +28,7 @@ describe('SearchSidebarEffects', () => {
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() });
+ const expected = cold('--b-', { b: new SidebarCollapseAction() });
expect(sidebarEffects.routeChange$).toBeObservable(expected);
});
diff --git a/src/app/shared/sidebar/sidebar-dropdown.component.html b/src/app/shared/sidebar/sidebar-dropdown.component.html
new file mode 100644
index 0000000000..0c2a1c05d2
--- /dev/null
+++ b/src/app/shared/sidebar/sidebar-dropdown.component.html
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/src/app/shared/sidebar/sidebar-dropdown.component.scss b/src/app/shared/sidebar/sidebar-dropdown.component.scss
new file mode 100644
index 0000000000..1c025095dd
--- /dev/null
+++ b/src/app/shared/sidebar/sidebar-dropdown.component.scss
@@ -0,0 +1,3 @@
+.setting-option {
+ border: 1px solid map-get($theme-colors, light);
+}
diff --git a/src/app/shared/sidebar/sidebar-dropdown.component.ts b/src/app/shared/sidebar/sidebar-dropdown.component.ts
new file mode 100644
index 0000000000..313538eded
--- /dev/null
+++ b/src/app/shared/sidebar/sidebar-dropdown.component.ts
@@ -0,0 +1,16 @@
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+
+@Component({
+ selector: 'ds-sidebar-dropdown',
+ styleUrls: ['./sidebar-dropdown.component.scss'],
+ templateUrl: './sidebar-dropdown.component.html',
+})
+/**
+ * This components renders a sidebar dropdown including the label.
+ * The options should still be provided in the content.
+ */
+export class SidebarDropdownComponent {
+ @Input() id:string;
+ @Input() label:string;
+ @Output() change:EventEmitter = new EventEmitter();
+}
diff --git a/src/app/+search-page/search-sidebar/search-sidebar.effects.ts b/src/app/shared/sidebar/sidebar-effects.service.ts
similarity index 85%
rename from src/app/+search-page/search-sidebar/search-sidebar.effects.ts
rename to src/app/shared/sidebar/sidebar-effects.service.ts
index 1f5fb0ef60..fc6643be4b 100644
--- a/src/app/+search-page/search-sidebar/search-sidebar.effects.ts
+++ b/src/app/shared/sidebar/sidebar-effects.service.ts
@@ -3,14 +3,14 @@ import { Injectable } from '@angular/core';
import { Effect, Actions, ofType } from '@ngrx/effects'
import * as fromRouter from '@ngrx/router-store';
-import { SearchSidebarCollapseAction } from './search-sidebar.actions';
+import { SidebarCollapseAction } from './sidebar.actions';
import { URLBaser } from '../../core/url-baser/url-baser';
/**
* Makes sure that if the user navigates to another route, the sidebar is collapsed
*/
@Injectable()
-export class SearchSidebarEffects {
+export class SidebarEffects {
private previousPath: string;
@Effect() routeChange$ = this.actions$
.pipe(
@@ -19,7 +19,7 @@ export class SearchSidebarEffects {
tap((action) => {
this.previousPath = this.getBaseUrl(action)
}),
- map(() => new SearchSidebarCollapseAction())
+ map(() => new SidebarCollapseAction())
);
constructor(private actions$: Actions) {
diff --git a/src/app/+search-page/search-sidebar/search-sidebar.actions.ts b/src/app/shared/sidebar/sidebar.actions.ts
similarity index 51%
rename from src/app/+search-page/search-sidebar/search-sidebar.actions.ts
rename to src/app/shared/sidebar/sidebar.actions.ts
index 84a34b2790..4a7c85696a 100644
--- a/src/app/+search-page/search-sidebar/search-sidebar.actions.ts
+++ b/src/app/shared/sidebar/sidebar.actions.ts
@@ -1,6 +1,6 @@
import { Action } from '@ngrx/store';
-import { type } from '../../shared/ngrx/type';
+import { type } from '../ngrx/type';
/**
* For each action type in an action group, make a simple
@@ -10,32 +10,32 @@ import { type } from '../../shared/ngrx/type';
* literal types and runs a simple check to guarantee all
* action types in the application are unique.
*/
-export const SearchSidebarActionTypes = {
- COLLAPSE: type('dspace/search-sidebar/COLLAPSE'),
- EXPAND: type('dspace/search-sidebar/EXPAND'),
- TOGGLE: type('dspace/search-sidebar/TOGGLE')
+export const SidebarActionTypes = {
+ COLLAPSE: type('dspace/sidebar/COLLAPSE'),
+ EXPAND: type('dspace/sidebar/EXPAND'),
+ TOGGLE: type('dspace/sidebar/TOGGLE')
};
/* tslint:disable:max-classes-per-file */
/**
* Used to collapse the sidebar
*/
-export class SearchSidebarCollapseAction implements Action {
- type = SearchSidebarActionTypes.COLLAPSE;
+export class SidebarCollapseAction implements Action {
+ type = SidebarActionTypes.COLLAPSE;
}
/**
* Used to expand the sidebar
*/
-export class SearchSidebarExpandAction implements Action {
- type = SearchSidebarActionTypes.EXPAND;
+export class SidebarExpandAction implements Action {
+ type = SidebarActionTypes.EXPAND;
}
/**
* Used to collapse the sidebar when it's expanded and expand it when it's collapsed
*/
-export class SearchSidebarToggleAction implements Action {
- type = SearchSidebarActionTypes.TOGGLE;
+export class SidebarToggleAction implements Action {
+ type = SidebarActionTypes.TOGGLE;
}
/* tslint:enable:max-classes-per-file */
@@ -43,7 +43,7 @@ export class SearchSidebarToggleAction implements Action {
* Export a type alias of all actions in this action group
* so that reducers can easily compose action types
*/
-export type SearchSidebarAction
- = SearchSidebarCollapseAction
- | SearchSidebarExpandAction
- | SearchSidebarToggleAction
+export type SidebarAction
+ = SidebarCollapseAction
+ | SidebarExpandAction
+ | SidebarToggleAction
diff --git a/src/app/+search-page/search-sidebar/search-sidebar.reducer.spec.ts b/src/app/shared/sidebar/sidebar.reducer.spec.ts
similarity index 80%
rename from src/app/+search-page/search-sidebar/search-sidebar.reducer.spec.ts
rename to src/app/shared/sidebar/sidebar.reducer.spec.ts
index dfdead4369..0fbfce84fc 100644
--- a/src/app/+search-page/search-sidebar/search-sidebar.reducer.spec.ts
+++ b/src/app/shared/sidebar/sidebar.reducer.spec.ts
@@ -1,12 +1,12 @@
import * as deepFreeze from 'deep-freeze';
-import { sidebarReducer } from './search-sidebar.reducer';
+import { sidebarReducer } from './sidebar.reducer';
import {
- SearchSidebarCollapseAction, SearchSidebarExpandAction,
- SearchSidebarToggleAction
-} from './search-sidebar.actions';
+ SidebarCollapseAction, SidebarExpandAction,
+ SidebarToggleAction
+} from './sidebar.actions';
-class NullAction extends SearchSidebarCollapseAction {
+class NullAction extends SidebarCollapseAction {
type = null;
constructor() {
@@ -34,7 +34,7 @@ describe('sidebarReducer', () => {
it('should set sidebarCollapsed to true in response to the COLLAPSE action', () => {
const state = { sidebarCollapsed: false };
- const action = new SearchSidebarCollapseAction();
+ const action = new SidebarCollapseAction();
const newState = sidebarReducer(state, action);
expect(newState.sidebarCollapsed).toEqual(true);
@@ -44,7 +44,7 @@ describe('sidebarReducer', () => {
const state = { sidebarCollapsed: false };
deepFreeze([state]);
- const action = new SearchSidebarCollapseAction();
+ const action = new SidebarCollapseAction();
sidebarReducer(state, action);
// no expect required, deepFreeze will ensure an exception is thrown if the state
@@ -53,7 +53,7 @@ describe('sidebarReducer', () => {
it('should set sidebarCollapsed to false in response to the EXPAND action', () => {
const state = { sidebarCollapsed: true };
- const action = new SearchSidebarExpandAction();
+ const action = new SidebarExpandAction();
const newState = sidebarReducer(state, action);
expect(newState.sidebarCollapsed).toEqual(false);
@@ -63,13 +63,13 @@ describe('sidebarReducer', () => {
const state = { sidebarCollapsed: true };
deepFreeze([state]);
- const action = new SearchSidebarExpandAction();
+ const action = new SidebarExpandAction();
sidebarReducer(state, action);
});
it('should flip the value of sidebarCollapsed in response to the TOGGLE action', () => {
const state1 = { sidebarCollapsed: true };
- const action = new SearchSidebarToggleAction();
+ const action = new SidebarToggleAction();
const state2 = sidebarReducer(state1, action);
const state3 = sidebarReducer(state2, action);
@@ -82,7 +82,7 @@ describe('sidebarReducer', () => {
const state = { sidebarCollapsed: true };
deepFreeze([state]);
- const action = new SearchSidebarToggleAction();
+ const action = new SidebarToggleAction();
sidebarReducer(state, action);
});
diff --git a/src/app/shared/sidebar/sidebar.reducer.ts b/src/app/shared/sidebar/sidebar.reducer.ts
new file mode 100644
index 0000000000..05e7d38d48
--- /dev/null
+++ b/src/app/shared/sidebar/sidebar.reducer.ts
@@ -0,0 +1,47 @@
+import { SidebarAction, SidebarActionTypes } from './sidebar.actions';
+
+/**
+ * Interface that represents the state of the sidebar
+ */
+export interface SidebarState {
+ sidebarCollapsed: boolean;
+}
+
+const initialState: SidebarState = {
+ sidebarCollapsed: true
+};
+
+/**
+ * Performs a sidebar action on the current state
+ * @param {SidebarState} state The state before the action is performed
+ * @param {SidebarAction} action The action that should be performed
+ * @returns {SidebarState} The state after the action is performed
+ */
+export function sidebarReducer(state = initialState, action: SidebarAction): SidebarState {
+ switch (action.type) {
+
+ case SidebarActionTypes.COLLAPSE: {
+ return Object.assign({}, state, {
+ sidebarCollapsed: true
+ });
+ }
+
+ case SidebarActionTypes.EXPAND: {
+ return Object.assign({}, state, {
+ sidebarCollapsed: false
+ });
+
+ }
+
+ case SidebarActionTypes.TOGGLE: {
+ return Object.assign({}, state, {
+ sidebarCollapsed: !state.sidebarCollapsed
+ });
+
+ }
+
+ default: {
+ return state;
+ }
+ }
+}
diff --git a/src/app/+search-page/search-sidebar/search-sidebar.service.spec.ts b/src/app/shared/sidebar/sidebar.service.spec.ts
similarity index 61%
rename from src/app/+search-page/search-sidebar/search-sidebar.service.spec.ts
rename to src/app/shared/sidebar/sidebar.service.spec.ts
index 0cccf9ea40..6d8e845836 100644
--- a/src/app/+search-page/search-sidebar/search-sidebar.service.spec.ts
+++ b/src/app/shared/sidebar/sidebar.service.spec.ts
@@ -1,13 +1,13 @@
import { Store } from '@ngrx/store';
-import { SearchSidebarService } from './search-sidebar.service';
+import { SidebarService } from './sidebar.service';
import { AppState } from '../../app.reducer';
import { async, TestBed } from '@angular/core/testing';
import { of as observableOf } from 'rxjs';
-import { SearchSidebarCollapseAction, SearchSidebarExpandAction } from './search-sidebar.actions';
-import { HostWindowService } from '../../shared/host-window.service';
+import { SidebarCollapseAction, SidebarExpandAction } from './sidebar.actions';
+import { HostWindowService } from '../host-window.service';
-describe('SearchSidebarService', () => {
- let service: SearchSidebarService;
+describe('SidebarService', () => {
+ let service: SidebarService;
const store: Store = jasmine.createSpyObj('store', {
/* tslint:disable:no-empty */
dispatch: {},
@@ -35,7 +35,7 @@ describe('SearchSidebarService', () => {
}));
beforeEach(() => {
- service = new SearchSidebarService(store, windowService);
+ service = new SidebarService(store, windowService);
}) ;
describe('when the collapse method is triggered', () => {
@@ -43,8 +43,8 @@ describe('SearchSidebarService', () => {
service.collapse();
});
- it('SearchSidebarCollapseAction should be dispatched to the store', () => {
- expect(store.dispatch).toHaveBeenCalledWith(new SearchSidebarCollapseAction());
+ it('SidebarCollapseAction should be dispatched to the store', () => {
+ expect(store.dispatch).toHaveBeenCalledWith(new SidebarCollapseAction());
});
});
@@ -54,8 +54,8 @@ describe('SearchSidebarService', () => {
service.expand();
});
- it('SearchSidebarExpandAction should be dispatched to the store', () => {
- expect(store.dispatch).toHaveBeenCalledWith(new SearchSidebarExpandAction());
+ it('SidebarExpandAction should be dispatched to the store', () => {
+ expect(store.dispatch).toHaveBeenCalledWith(new SidebarExpandAction());
});
});
diff --git a/src/app/+search-page/search-sidebar/search-sidebar.service.ts b/src/app/shared/sidebar/sidebar.service.ts
similarity index 70%
rename from src/app/+search-page/search-sidebar/search-sidebar.service.ts
rename to src/app/shared/sidebar/sidebar.service.ts
index 7185984538..8e3bbbf117 100644
--- a/src/app/+search-page/search-sidebar/search-sidebar.service.ts
+++ b/src/app/shared/sidebar/sidebar.service.ts
@@ -1,20 +1,20 @@
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { Injectable } from '@angular/core';
-import { SearchSidebarState } from './search-sidebar.reducer';
+import { SidebarState } from './sidebar.reducer';
import { createSelector, select, Store } from '@ngrx/store';
-import { SearchSidebarCollapseAction, SearchSidebarExpandAction } from './search-sidebar.actions';
+import { SidebarCollapseAction, SidebarExpandAction } from './sidebar.actions';
import { AppState } from '../../app.reducer';
-import { HostWindowService } from '../../shared/host-window.service';
+import { HostWindowService } from '../host-window.service';
import { map } from 'rxjs/operators';
-const sidebarStateSelector = (state: AppState) => state.searchSidebar;
-const sidebarCollapsedSelector = createSelector(sidebarStateSelector, (sidebar: SearchSidebarState) => sidebar.sidebarCollapsed);
+const sidebarStateSelector = (state: AppState) => state.sidebar;
+const sidebarCollapsedSelector = createSelector(sidebarStateSelector, (sidebar: SidebarState) => sidebar.sidebarCollapsed);
/**
- * Service that performs all actions that have to do with the search sidebar
+ * Service that performs all actions that have to do with the sidebar
*/
@Injectable()
-export class SearchSidebarService {
+export class SidebarService {
/**
* Emits true is the current screen size is mobile
*/
@@ -47,13 +47,13 @@ export class SearchSidebarService {
* Dispatches a collapse action to the store
*/
public collapse(): void {
- this.store.dispatch(new SearchSidebarCollapseAction());
+ this.store.dispatch(new SidebarCollapseAction());
}
/**
* Dispatches an expand action to the store
*/
public expand(): void {
- this.store.dispatch(new SearchSidebarExpandAction());
+ this.store.dispatch(new SidebarExpandAction());
}
}
diff --git a/src/app/shared/testing/utils.ts b/src/app/shared/testing/utils.ts
index afd048096a..d68d18e51e 100644
--- a/src/app/shared/testing/utils.ts
+++ b/src/app/shared/testing/utils.ts
@@ -2,6 +2,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RemoteData } from '../../core/data/remote-data';
import { Observable, of as observableOf } from 'rxjs';
import { RemoteDataError } from '../../core/data/remote-data-error';
+import { PaginatedList } from '../../core/data/paginated-list';
+import { PageInfo } from '../../core/shared/page-info.model';
/**
* Returns true if a Native Element has a specified css class.
@@ -122,6 +124,14 @@ export function createPendingRemoteDataObject$(object?: T): Observable(objects?: T[]): PaginatedList {
+ return new PaginatedList(new PageInfo(), objects);
+}
+
/**
* Creates a jasmine spy for an exported function
* @param target The object to spy on