mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge branch 'master' into w2p-68346_Bundles-in-edit-item-Updates
Conflicts: package.json resources/i18n/en.json5 src/app/+item-page/item-page.module.ts src/app/core/core.module.ts src/app/core/data/bundle-data.service.ts src/app/core/data/data.service.spec.ts src/app/core/data/data.service.ts src/app/core/data/item-data.service.ts src/app/core/data/object-updates/object-updates.service.ts src/app/core/shared/hal-endpoint.service.ts src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts src/app/shared/mocks/mock-request.service.ts src/app/shared/shared.module.ts src/app/shared/trackable/abstract-trackable.component.ts yarn.lock
This commit is contained in:
15
.travis.yml
15
.travis.yml
@@ -1,5 +1,5 @@
|
||||
sudo: required
|
||||
dist: trusty
|
||||
dist: bionic
|
||||
|
||||
env:
|
||||
# Install the latest docker-compose version for ci testing.
|
||||
@@ -12,6 +12,9 @@ env:
|
||||
DSPACE_REST_NAMESPACE: '/server/api'
|
||||
DSPACE_REST_SSL: false
|
||||
|
||||
services:
|
||||
- xvfb
|
||||
|
||||
before_install:
|
||||
# Docker Compose Install
|
||||
- curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
|
||||
@@ -33,14 +36,6 @@ before_script:
|
||||
after_script:
|
||||
- docker-compose -f ./docker/docker-compose-travis.yml down
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- google-chrome
|
||||
packages:
|
||||
- dpkg
|
||||
- google-chrome-stable
|
||||
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
@@ -53,8 +48,6 @@ cache:
|
||||
bundler_args: --retry 5
|
||||
|
||||
script:
|
||||
# Use Chromium instead of Chrome.
|
||||
- export CHROME_BIN=chromium-browser
|
||||
- yarn run build
|
||||
- yarn run ci
|
||||
- cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js
|
||||
|
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: {
|
||||
@@ -181,6 +185,11 @@ module.exports = {
|
||||
undoTimeout: 10000 // 10 seconds
|
||||
}
|
||||
},
|
||||
collection: {
|
||||
edit: {
|
||||
undoTimeout: 10000 // 10 seconds
|
||||
}
|
||||
},
|
||||
theme: {
|
||||
name: 'default',
|
||||
}
|
||||
|
@@ -2,7 +2,8 @@ import { browser, element, by } from 'protractor';
|
||||
|
||||
export class ProtractorPage {
|
||||
navigateTo() {
|
||||
return browser.get('/');
|
||||
return browser.get('/')
|
||||
.then(() => browser.waitForAngular());
|
||||
}
|
||||
|
||||
getPageTitleText() {
|
||||
|
46
e2e/search-navbar/search-navbar.e2e-spec.ts
Normal file
46
e2e/search-navbar/search-navbar.e2e-spec.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { ProtractorPage } from './search-navbar.po';
|
||||
import { browser } from 'protractor';
|
||||
|
||||
describe('protractor SearchNavbar', () => {
|
||||
let page: ProtractorPage;
|
||||
let queryString: string;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new ProtractorPage();
|
||||
queryString = 'the test query';
|
||||
});
|
||||
|
||||
it('should go to search page with correct query if submitted (from home)', () => {
|
||||
page.navigateToHome();
|
||||
return checkIfSearchWorks();
|
||||
});
|
||||
|
||||
it('should go to search page with correct query if submitted (from search)', () => {
|
||||
page.navigateToSearch();
|
||||
return checkIfSearchWorks();
|
||||
});
|
||||
|
||||
it('check if can submit search box with pressing button', () => {
|
||||
page.navigateToHome();
|
||||
page.expandAndFocusSearchBox();
|
||||
page.setCurrentQuery(queryString);
|
||||
page.submitNavbarSearchForm();
|
||||
browser.wait(() => {
|
||||
return browser.getCurrentUrl().then((url: string) => {
|
||||
return url.indexOf('query=' + encodeURI(queryString)) !== -1;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function checkIfSearchWorks(): boolean {
|
||||
page.setCurrentQuery(queryString);
|
||||
page.submitByPressingEnter();
|
||||
browser.wait(() => {
|
||||
return browser.getCurrentUrl().then((url: string) => {
|
||||
return url.indexOf('query=' + encodeURI(queryString)) !== -1;
|
||||
});
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
});
|
40
e2e/search-navbar/search-navbar.po.ts
Normal file
40
e2e/search-navbar/search-navbar.po.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { browser, element, by, protractor } from 'protractor';
|
||||
import { promise } from 'selenium-webdriver';
|
||||
|
||||
export class ProtractorPage {
|
||||
HOME = '/home';
|
||||
SEARCH = '/search';
|
||||
|
||||
navigateToHome() {
|
||||
return browser.get(this.HOME);
|
||||
}
|
||||
|
||||
navigateToSearch() {
|
||||
return browser.get(this.SEARCH);
|
||||
}
|
||||
|
||||
getCurrentQuery(): promise.Promise<string> {
|
||||
return element(by.css('#search-navbar-container form input')).getAttribute('value');
|
||||
}
|
||||
|
||||
expandAndFocusSearchBox() {
|
||||
element(by.css('#search-navbar-container form a')).click();
|
||||
}
|
||||
|
||||
setCurrentQuery(query: string) {
|
||||
element(by.css('#search-navbar-container form input[name="query"]')).sendKeys(query);
|
||||
}
|
||||
|
||||
submitNavbarSearchForm() {
|
||||
element(by.css('#search-navbar-container form .submit-icon')).click();
|
||||
}
|
||||
|
||||
submitByPressingEnter() {
|
||||
element(by.css('#search-navbar-container form input[name="query"]')).sendKeys(protractor.Key.ENTER);
|
||||
}
|
||||
|
||||
submitByPressingEnter() {
|
||||
element(by.css('#search-navbar-container form input[name="query"]')).sendKeys(protractor.Key.ENTER);
|
||||
}
|
||||
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
import { browser, element, by, protractor } from 'protractor';
|
||||
import { browser, by, element, protractor } from 'protractor';
|
||||
import { promise } from 'selenium-webdriver';
|
||||
|
||||
export class ProtractorPage {
|
||||
@@ -27,15 +27,15 @@ export class ProtractorPage {
|
||||
}
|
||||
|
||||
setCurrentScope(scope: string) {
|
||||
element(by.css('option[value="' + scope + '"]')).click();
|
||||
element(by.css('#search-form option[value="' + scope + '"]')).click();
|
||||
}
|
||||
|
||||
setCurrentQuery(query: string) {
|
||||
element(by.css('input[name="query"]')).sendKeys(query);
|
||||
element(by.css('#search-form input[name="query"]')).sendKeys(query);
|
||||
}
|
||||
|
||||
submitSearchForm() {
|
||||
element(by.css('button.search-button')).click();
|
||||
element(by.css('#search-form button.search-button')).click();
|
||||
}
|
||||
|
||||
getRandomScopeOption(): promise.Promise<string> {
|
||||
|
@@ -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: '',
|
||||
|
||||
|
113
package.json
113
package.json
@@ -11,6 +11,7 @@
|
||||
"node": "8.* || >= 10.*"
|
||||
},
|
||||
"resolutions": {
|
||||
"serialize-javascript": ">= 2.1.2",
|
||||
"set-value": ">= 2.0.1"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -70,41 +71,42 @@
|
||||
"docs": "typedoc --options typedoc.json ./src/",
|
||||
"coverage": "http-server -c-1 -o -p 9875 ./coverage",
|
||||
"postinstall": "yarn run patch-protractor",
|
||||
"patch-protractor": "ncp node_modules/webdriver-manager node_modules/protractor/node_modules/webdriver-manager"
|
||||
"patch-protractor": "ncp node_modules/webdriver-manager node_modules/protractor/node_modules/webdriver-manager",
|
||||
"sync-i18n": "node ./scripts/sync-i18n-files.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "^6.1.4",
|
||||
"@angular/animations": "^7.2.15",
|
||||
"@angular/cdk": "^7.3.7",
|
||||
"@angular/cli": "^6.1.5",
|
||||
"@angular/common": "^6.1.4",
|
||||
"@angular/core": "^6.1.4",
|
||||
"@angular/forms": "^6.1.4",
|
||||
"@angular/http": "^6.1.4",
|
||||
"@angular/platform-browser": "^6.1.4",
|
||||
"@angular/platform-browser-dynamic": "^6.1.4",
|
||||
"@angular/platform-server": "^6.1.4",
|
||||
"@angular/router": "^6.1.4",
|
||||
"@angular/cli": "^7.3.5",
|
||||
"@angular/common": "^7.2.15",
|
||||
"@angular/core": "^7.2.15",
|
||||
"@angular/forms": "^7.2.15",
|
||||
"@angular/http": "^7.2.15",
|
||||
"@angular/platform-browser": "^7.2.15",
|
||||
"@angular/platform-browser-dynamic": "^7.2.15",
|
||||
"@angular/platform-server": "^7.2.15",
|
||||
"@angular/router": "^7.2.15",
|
||||
"@angularclass/bootloader": "1.0.1",
|
||||
"@ng-bootstrap/ng-bootstrap": "^2.0.0",
|
||||
"@ng-dynamic-forms/core": "6.2.0",
|
||||
"@ng-dynamic-forms/ui-ng-bootstrap": "6.2.0",
|
||||
"@ngrx/effects": "^6.1.0",
|
||||
"@ngrx/router-store": "^6.1.0",
|
||||
"@ngrx/store": "^6.1.0",
|
||||
"@nguniversal/express-engine": "6.1.0",
|
||||
"@ngx-translate/core": "10.0.2",
|
||||
"@ngx-translate/http-loader": "3.0.1",
|
||||
"@ng-bootstrap/ng-bootstrap": "^4.1.0",
|
||||
"@ng-dynamic-forms/core": "^7.1.0",
|
||||
"@ng-dynamic-forms/ui-ng-bootstrap": "^7.1.0",
|
||||
"@ngrx/effects": "^7.3.0",
|
||||
"@ngrx/router-store": "^7.3.0",
|
||||
"@ngrx/store": "^7.3.0",
|
||||
"@nguniversal/express-engine": "^7.1.1",
|
||||
"@ngx-translate/core": "11.0.1",
|
||||
"@ngx-translate/http-loader": "4.0.0",
|
||||
"@nicky-lenaers/ngx-scroll-to": "^1.0.0",
|
||||
"angular-idle-preload": "3.0.0",
|
||||
"angular-sortablejs": "^2.5.0",
|
||||
"angular2-text-mask": "9.0.0",
|
||||
"angulartics2": "^6.2.0",
|
||||
"angulartics2": "7.5.2",
|
||||
"body-parser": "1.18.2",
|
||||
"bootstrap": "4.3.1",
|
||||
"cerialize": "0.1.18",
|
||||
"compression": "1.7.1",
|
||||
"cookie-parser": "1.4.3",
|
||||
"core-js": "^2.5.7",
|
||||
"core-js": "^2.6.5",
|
||||
"debug-loader": "^0.0.1",
|
||||
"express": "4.16.2",
|
||||
"express-session": "1.15.6",
|
||||
@@ -112,6 +114,7 @@
|
||||
"file-saver": "^1.3.8",
|
||||
"font-awesome": "4.7.0",
|
||||
"fork-ts-checker-webpack-plugin": "^0.4.10",
|
||||
"hammerjs": "^2.0.8",
|
||||
"http-server": "0.11.1",
|
||||
"https": "1.0.0",
|
||||
"js-cookie": "2.2.0",
|
||||
@@ -121,37 +124,40 @@
|
||||
"jwt-decode": "^2.2.0",
|
||||
"methods": "1.1.2",
|
||||
"moment": "^2.22.1",
|
||||
"moment-range": "^4.0.2",
|
||||
"morgan": "^1.9.1",
|
||||
"ng-mocks": "^6.2.1",
|
||||
"ng-mocks": "^7.6.0",
|
||||
"ng2-file-upload": "1.2.1",
|
||||
"ng2-nouislider": "^1.7.11",
|
||||
"ng2-nouislider": "^1.8.2",
|
||||
"ngx-bootstrap": "^3.2.0",
|
||||
"ngx-infinite-scroll": "6.0.1",
|
||||
"ngx-moment": "^3.1.0",
|
||||
"ngx-moment": "^3.4.0",
|
||||
"ngx-pagination": "3.0.3",
|
||||
"nouislider": "^11.0.0",
|
||||
"pem": "1.13.2",
|
||||
"reflect-metadata": "0.1.12",
|
||||
"rxjs": "6.2.2",
|
||||
"rxjs": "6.4.0",
|
||||
"rxjs-spy": "^7.5.1",
|
||||
"sass-resources-loader": "^2.0.0",
|
||||
"sortablejs": "1.7.0",
|
||||
"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",
|
||||
"zone.js": "^0.8.26"
|
||||
"webpack-cli": "^3.2.0",
|
||||
"zone.js": "^0.8.29"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/compiler": "^6.1.4",
|
||||
"@angular/compiler-cli": "^6.1.4",
|
||||
"@angular-devkit/build-angular": "^0.13.5",
|
||||
"@angular/compiler": "^7.2.15",
|
||||
"@angular/compiler-cli": "^7.2.15",
|
||||
"@fortawesome/fontawesome-free": "^5.5.0",
|
||||
"@ngrx/entity": "^6.1.0",
|
||||
"@ngrx/schematics": "^6.1.0",
|
||||
"@ngrx/store-devtools": "^6.1.0",
|
||||
"@ngtools/webpack": "^6.1.5",
|
||||
"@ngrx/entity": "^7.3.0",
|
||||
"@ngrx/schematics": "^7.3.0",
|
||||
"@ngrx/store-devtools": "^7.3.0",
|
||||
"@ngtools/webpack": "^7.3.9",
|
||||
"@schematics/angular": "^0.7.5",
|
||||
"@types/acorn": "^4.0.3",
|
||||
"@types/cookie-parser": "1.4.1",
|
||||
@@ -160,42 +166,47 @@
|
||||
"@types/express-serve-static-core": "4.16.0",
|
||||
"@types/file-saver": "^1.3.0",
|
||||
"@types/hammerjs": "2.0.35",
|
||||
"@types/jasmine": "^2.8.6",
|
||||
"@types/jasmine": "^3.3.9",
|
||||
"@types/js-cookie": "2.1.0",
|
||||
"@types/json5": "^0.0.30",
|
||||
"@types/lodash": "^4.14.110",
|
||||
"@types/memory-cache": "0.2.0",
|
||||
"@types/mime": "2.0.0",
|
||||
"@types/node": "^10.9.4",
|
||||
"@types/node": "^11.11.2",
|
||||
"@types/serve-static": "1.13.2",
|
||||
"@types/uuid": "^3.4.3",
|
||||
"@types/webfontloader": "1.6.29",
|
||||
"@typescript-eslint/eslint-plugin": "^2.12.0",
|
||||
"@typescript-eslint/parser": "^2.12.0",
|
||||
"ajv": "^6.1.1",
|
||||
"ajv-keywords": "^3.1.0",
|
||||
"angular2-template-loader": "0.6.2",
|
||||
"autoprefixer": "^9.1.3",
|
||||
"caniuse-lite": "^1.0.30000697",
|
||||
"codelyzer": "^4.4.4",
|
||||
"compression-webpack-plugin": "^1.1.6",
|
||||
"copy-webpack-plugin": "^4.4.1",
|
||||
"cli-progress": "^3.3.1",
|
||||
"codelyzer": "^5.1.0",
|
||||
"commander": "^3.0.2",
|
||||
"compression-webpack-plugin": "^3.0.1",
|
||||
"copy-webpack-plugin": "^5.1.1",
|
||||
"copyfiles": "^2.1.1",
|
||||
"coveralls": "3.0.0",
|
||||
"css-loader": "1.0.0",
|
||||
"css-loader": "3.4.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"deep-freeze": "0.0.1",
|
||||
"eslint": "^6.7.2",
|
||||
"exports-loader": "^0.7.0",
|
||||
"html-webpack-plugin": "^4.0.0-alpha",
|
||||
"html-webpack-plugin": "3.2.0",
|
||||
"imports-loader": "0.8.0",
|
||||
"istanbul-instrumenter-loader": "3.0.1",
|
||||
"jasmine-core": "^3.2.1",
|
||||
"jasmine-core": "^3.3.0",
|
||||
"jasmine-marbles": "0.3.1",
|
||||
"jasmine-spec-reporter": "4.2.1",
|
||||
"karma": "3.0.0",
|
||||
"karma": "4.0.1",
|
||||
"karma-chrome-launcher": "2.2.0",
|
||||
"karma-cli": "1.0.1",
|
||||
"karma-cli": "2.0.0",
|
||||
"karma-coverage": "1.1.2",
|
||||
"karma-istanbul-preprocessor": "0.0.2",
|
||||
"karma-jasmine": "1.1.2",
|
||||
"karma-jasmine": "2.0.1",
|
||||
"karma-mocha-reporter": "2.2.5",
|
||||
"karma-phantomjs-launcher": "1.0.4",
|
||||
"karma-remap-coverage": "^0.1.5",
|
||||
@@ -219,26 +230,26 @@
|
||||
"protractor": "^5.4.2",
|
||||
"protractor-istanbul-plugin": "2.0.0",
|
||||
"raw-loader": "0.5.1",
|
||||
"resolve-url-loader": "^2.3.0",
|
||||
"rimraf": "2.6.2",
|
||||
"rollup": "^0.65.0",
|
||||
"rollup-plugin-commonjs": "^9.1.6",
|
||||
"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",
|
||||
"script-ext-html-webpack-plugin": "2.0.1",
|
||||
"sass-loader": "7.3.1",
|
||||
"script-ext-html-webpack-plugin": "2.1.4",
|
||||
"source-map": "0.7.3",
|
||||
"source-map-loader": "0.2.4",
|
||||
"string-replace-loader": "^2.1.1",
|
||||
"terser-webpack-plugin": "^2.3.1",
|
||||
"to-string-loader": "1.1.5",
|
||||
"ts-helpers": "1.1.2",
|
||||
"ts-node": "4.1.0",
|
||||
"tslint": "5.11.0",
|
||||
"typedoc": "^0.9.0",
|
||||
"typescript": "^2.9.1",
|
||||
"webdriver-manager": "^12.1.6",
|
||||
"webpack": "^4.17.1",
|
||||
"typescript": "3.1.6",
|
||||
"webdriver-manager": "^12.1.7",
|
||||
"webpack": "^4.29.6",
|
||||
"webpack-bundle-analyzer": "^3.3.2",
|
||||
"webpack-dev-middleware": "3.2.0",
|
||||
"webpack-dev-server": "^3.1.11",
|
||||
|
@@ -5,7 +5,7 @@
|
||||
var SpecReporter = require('jasmine-spec-reporter').SpecReporter;
|
||||
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
allScriptsTimeout: 600000,
|
||||
// -----------------------------------------------------------------
|
||||
// Uncomment to run tests using a remote Selenium server
|
||||
//seleniumAddress: 'http://selenium.address:4444/wd/hub',
|
||||
@@ -73,7 +73,7 @@ exports.config = {
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
defaultTimeoutInterval: 600000,
|
||||
print: function () {}
|
||||
},
|
||||
useAllAngular2AppRoots: true,
|
||||
|
3
resources/fonts/README.md
Normal file
3
resources/fonts/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Supported font formats
|
||||
|
||||
DSpace supports EOT, TTF, OTF, SVG, WOFF and WOFF2 fonts.
|
3220
resources/i18n/ar.json5
Normal file
3220
resources/i18n/ar.json5
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1610
resources/i18n/es.json5
Normal file
1610
resources/i18n/es.json5
Normal file
File diff suppressed because it is too large
Load Diff
3220
resources/i18n/fi.json5
Normal file
3220
resources/i18n/fi.json5
Normal file
File diff suppressed because it is too large
Load Diff
3220
resources/i18n/fr.json5
Normal file
3220
resources/i18n/fr.json5
Normal file
File diff suppressed because it is too large
Load Diff
3220
resources/i18n/ja.json5
Normal file
3220
resources/i18n/ja.json5
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
3220
resources/i18n/pl.json5
Normal file
3220
resources/i18n/pl.json5
Normal file
File diff suppressed because it is too large
Load Diff
2457
resources/i18n/pt.json5
Normal file
2457
resources/i18n/pt.json5
Normal file
File diff suppressed because it is too large
Load Diff
3220
resources/i18n/sw.json5
Normal file
3220
resources/i18n/sw.json5
Normal file
File diff suppressed because it is too large
Load Diff
3220
resources/i18n/tr.json5
Normal file
3220
resources/i18n/tr.json5
Normal file
File diff suppressed because it is too large
Load Diff
342
scripts/sync-i18n-files.js
Executable file
342
scripts/sync-i18n-files.js
Executable file
@@ -0,0 +1,342 @@
|
||||
#!/usr/bin/env node
|
||||
const commander = require('commander');
|
||||
const fs = require('fs');
|
||||
const JSON5 = require('json5');
|
||||
const _cliProgress = require('cli-progress');
|
||||
const _ = require('lodash');
|
||||
const {projectRoot} = require('../webpack/helpers');
|
||||
|
||||
const program = new commander.Command();
|
||||
program.version('1.0.0', '-v, --version');
|
||||
|
||||
const NEW_MESSAGE_TODO = '// TODO New key - Add a translation';
|
||||
const MESSAGE_CHANGED_TODO = '// TODO Source message changed - Revise the translation';
|
||||
const COMMENTS_CHANGED_TODO = '// TODO Source comments changed - Revise the translation';
|
||||
|
||||
const DEFAULT_SOURCE_FILE_LOCATION = 'resources/i18n/en.json5';
|
||||
const LANGUAGE_FILES_LOCATION = 'resources/i18n';
|
||||
|
||||
parseCliInput();
|
||||
|
||||
/**
|
||||
* Parses the CLI input given by the user
|
||||
* If no parameters are set (standard usage) -> source file is default (set to DEFAULT_SOURCE_FILE_LOCATION) and all
|
||||
* other language files in the LANGUAGE_FILES_LOCATION are synced with this one in-place
|
||||
* (replaced with newly synced file)
|
||||
* If only target-file -t is set -> either -i in-place or -o output-file must be set
|
||||
* Source file can be set with -s if it should be something else than DEFAULT_SOURCE_FILE_LOCATION
|
||||
*
|
||||
* If any of the paths to files/dirs given by user are not valid, an error message is printed and script gets aborted
|
||||
*/
|
||||
function parseCliInput() {
|
||||
program
|
||||
.option('-d, --output-dir <output-dir>', 'output dir when running script on all language files; mutually exclusive with -o')
|
||||
.option('-t, --target-file <target>', 'target file we compare with and where completed output ends up if -o is not configured and -i is')
|
||||
.option('-i, --edit-in-place', 'edit-in-place; store output straight in target file; mutually exclusive with -o')
|
||||
.option('-s, --source-file <source>', 'source file to be parsed for translation', projectRoot(DEFAULT_SOURCE_FILE_LOCATION))
|
||||
.option('-o, --output-file <output>', 'where output of script ends up; mutually exclusive with -i')
|
||||
.usage('([-d <output-dir>] [-s <source-file>]) || (-t <target-file> (-i | -o <output>) [-s <source-file>])')
|
||||
.parse(process.argv);
|
||||
|
||||
if (!program.targetFile) {
|
||||
fs.readdirSync(projectRoot(LANGUAGE_FILES_LOCATION)).forEach(file => {
|
||||
if (!program.sourceFile.toString().endsWith(file)) {
|
||||
const targetFileLocation = projectRoot(LANGUAGE_FILES_LOCATION + "/" + file);
|
||||
console.log('Syncing file at: ' + targetFileLocation + ' with source file at: ' + program.sourceFile);
|
||||
if (program.outputDir) {
|
||||
if (!fs.existsSync(program.outputDir)) {
|
||||
fs.mkdirSync(program.outputDir);
|
||||
}
|
||||
const outputFileLocation = program.outputDir + "/" + file;
|
||||
console.log('Output location: ' + outputFileLocation);
|
||||
syncFileWithSource(targetFileLocation, outputFileLocation);
|
||||
} else {
|
||||
console.log('Replacing in target location');
|
||||
syncFileWithSource(targetFileLocation, targetFileLocation);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (program.targetFile && !checkIfPathToFileIsValid(program.targetFile)) {
|
||||
console.error('Directory path of target file is not valid.');
|
||||
console.log(program.outputHelp());
|
||||
process.exit(1);
|
||||
}
|
||||
if (program.targetFile && checkIfFileExists(program.targetFile) && !(program.editInPlace || program.outputFile)) {
|
||||
console.error('This target file already exists, if you want to overwrite this add option -i, or add an -o output location');
|
||||
console.log(program.outputHelp());
|
||||
process.exit(1);
|
||||
}
|
||||
if (!checkIfFileExists(program.sourceFile)) {
|
||||
console.error('Path of source file is not valid.');
|
||||
console.log(program.outputHelp());
|
||||
process.exit(1);
|
||||
}
|
||||
if (program.outputFile && !checkIfPathToFileIsValid(program.outputFile)) {
|
||||
console.error('Directory path of output file is not valid.');
|
||||
console.log(program.outputHelp());
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
syncFileWithSource(program.targetFile, getOutputFileLocationIfExistsElseTargetFileLocation(program.targetFile));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates chunk lists for both the source and the target files (for example en.json5 and nl.json5 respectively)
|
||||
* > Creates output chunks by comparing the source chunk with corresponding target chunk (based on key of translation)
|
||||
* > Writes the output chunks to a new valid lang.json5 file, either replacing the target file (-i in-place)
|
||||
* or sending it to an output file specified by the user
|
||||
* @param pathToTargetFile Valid path to target file to generate target chunks from
|
||||
* @param pathToOutputFile Valid path to output file to write output chunks to
|
||||
*/
|
||||
function syncFileWithSource(pathToTargetFile, pathToOutputFile) {
|
||||
const progressBar = new _cliProgress.SingleBar({}, _cliProgress.Presets.shades_classic);
|
||||
progressBar.start(100, 0);
|
||||
|
||||
const sourceLines = [];
|
||||
const targetLines = [];
|
||||
const existingTargetFile = readFileIfExists(pathToTargetFile);
|
||||
existingTargetFile.toString().split("\n").forEach((function (line) {
|
||||
targetLines.push(line.trim());
|
||||
}));
|
||||
progressBar.update(10);
|
||||
const sourceFile = readFileIfExists(program.sourceFile);
|
||||
sourceFile.toString().split("\n").forEach((function (line) {
|
||||
sourceLines.push(line.trim());
|
||||
}));
|
||||
progressBar.update(20);
|
||||
const sourceChunks = createChunks(sourceLines, progressBar, false);
|
||||
const targetChunks = createChunks(targetLines, progressBar, true);
|
||||
|
||||
const outputChunks = compareChunksAndCreateOutput(sourceChunks, targetChunks, progressBar);
|
||||
|
||||
const file = fs.createWriteStream(pathToOutputFile);
|
||||
file.on('error', function (err) {
|
||||
console.error('Something went wrong writing to output file at: ' + pathToOutputFile + err)
|
||||
});
|
||||
file.on('open', function() {
|
||||
file.write("{\n");
|
||||
outputChunks.forEach(function (chunk) {
|
||||
progressBar.increment();
|
||||
chunk.split("\n").forEach(function (line) {
|
||||
file.write(" " + line + "\n");
|
||||
});
|
||||
});
|
||||
file.write("\n}");
|
||||
file.end();
|
||||
});
|
||||
file.on('finish', function() {
|
||||
const osName = process.platform;
|
||||
if (osName.startsWith("win")) {
|
||||
replaceLineEndingsToCRLF(pathToOutputFile);
|
||||
}
|
||||
});
|
||||
|
||||
progressBar.update(100);
|
||||
progressBar.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* For each of the source chunks:
|
||||
* - Determine if it's a new key-value => Add it to output, with source comments, source key-value commented, a message indicating it's new and the source-key value uncommented
|
||||
* - If it's not new, compare it with the corresponding target chunk and log the differences, see createNewChunkComparingSourceAndTarget
|
||||
* @param sourceChunks All the source chunks, split per key-value pair group
|
||||
* @param targetChunks All the target chunks, split per key-value pair group
|
||||
* @param progressBar The progressbar for the CLI
|
||||
* @return {Array} All the output chunks, split per key-value pair group
|
||||
*/
|
||||
function compareChunksAndCreateOutput(sourceChunks, targetChunks, progressBar) {
|
||||
const outputChunks = [];
|
||||
sourceChunks.map((sourceChunk) => {
|
||||
progressBar.increment();
|
||||
if (sourceChunk.trim().length !== 0) {
|
||||
let newChunk = [];
|
||||
const sourceList = sourceChunk.split("\n");
|
||||
const keyValueSource = sourceList[sourceList.length - 1];
|
||||
const keySource = getSubStringBeforeLastString(keyValueSource, ":");
|
||||
const commentSource = getSubStringBeforeLastString(sourceChunk, keyValueSource);
|
||||
|
||||
const correspondingTargetChunk = targetChunks.find((targetChunk) => {
|
||||
return targetChunk.includes(keySource);
|
||||
});
|
||||
|
||||
// Create new chunk with: the source comments, the commented source key-value, the todos and either the old target key-value pair or if it's a new pair, the source key-value pair
|
||||
newChunk.push(removeWhiteLines(commentSource));
|
||||
newChunk.push("// " + keyValueSource);
|
||||
if (correspondingTargetChunk === undefined) {
|
||||
newChunk.push(NEW_MESSAGE_TODO);
|
||||
newChunk.push(keyValueSource);
|
||||
} else {
|
||||
createNewChunkComparingSourceAndTarget(correspondingTargetChunk, sourceChunk, commentSource, keyValueSource, newChunk);
|
||||
}
|
||||
|
||||
outputChunks.push(newChunk.filter(Boolean).join("\n"));
|
||||
} else {
|
||||
outputChunks.push(sourceChunk);
|
||||
}
|
||||
});
|
||||
return outputChunks;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a corresponding target chunk is found:
|
||||
* - If old key value is not found in comments > Assumed it is new key
|
||||
* - If the target comments do not contain the source comments (because they have changed since last time) => Add comments changed message
|
||||
* - If the key-value in the target comments is not the same as the source key-value (because it changes since last time) => Add message changed message
|
||||
* - Add the old todos if they haven't been added already
|
||||
* - End with the original target key-value
|
||||
*/
|
||||
function createNewChunkComparingSourceAndTarget(correspondingTargetChunk, sourceChunk, commentSource, keyValueSource, newChunk) {
|
||||
let commentsOfSourceHaveChanged = false;
|
||||
let messageOfSourceHasChanged = false;
|
||||
|
||||
const targetList = correspondingTargetChunk.split("\n");
|
||||
const oldKeyValueInTargetComments = getSubStringWithRegex(correspondingTargetChunk, "\\s*\\/\\/\\s*\".*");
|
||||
const keyValueTarget = targetList[targetList.length - 1];
|
||||
|
||||
if (oldKeyValueInTargetComments != null) {
|
||||
const oldKeyValueUncommented = getSubStringWithRegex(oldKeyValueInTargetComments[0], "\".*")[0];
|
||||
|
||||
if (!(_.isEmpty(correspondingTargetChunk) && _.isEmpty(commentSource)) && !removeWhiteLines(correspondingTargetChunk).includes(removeWhiteLines(commentSource.trim()))) {
|
||||
commentsOfSourceHaveChanged = true;
|
||||
newChunk.push(COMMENTS_CHANGED_TODO);
|
||||
}
|
||||
const parsedOldKey = JSON5.stringify("{" + oldKeyValueUncommented + "}");
|
||||
const parsedSourceKey = JSON5.stringify("{" + keyValueSource + "}");
|
||||
if (!_.isEqual(parsedOldKey, parsedSourceKey)) {
|
||||
messageOfSourceHasChanged = true;
|
||||
newChunk.push(MESSAGE_CHANGED_TODO);
|
||||
}
|
||||
addOldTodosIfNeeded(targetList, newChunk, commentsOfSourceHaveChanged, messageOfSourceHasChanged);
|
||||
}
|
||||
newChunk.push(keyValueTarget);
|
||||
}
|
||||
|
||||
// Adds old todos found in target comments if they've not been added already
|
||||
function addOldTodosIfNeeded(targetList, newChunk, commentsOfSourceHaveChanged, messageOfSourceHasChanged) {
|
||||
targetList.map((targetLine) => {
|
||||
const foundTODO = getSubStringWithRegex(targetLine, "\\s*//\\s*TODO.*");
|
||||
if (foundTODO != null) {
|
||||
const todo = foundTODO[0];
|
||||
if (!((todo.includes(COMMENTS_CHANGED_TODO) && commentsOfSourceHaveChanged)
|
||||
|| (todo.includes(MESSAGE_CHANGED_TODO) && messageOfSourceHasChanged))) {
|
||||
newChunk.push(todo);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates chunks from an array of lines, each chunk contains either an empty line or a grouping of comments with their corresponding key-value pair
|
||||
* @param lines Array of lines, to be grouped into chunks
|
||||
* @param progressBar Progressbar of the CLI
|
||||
* @return {Array} Array of chunks, grouped by key-value and their corresponding comments or an empty line
|
||||
*/
|
||||
function createChunks(lines, progressBar, creatingTarget) {
|
||||
const chunks = [];
|
||||
let nextChunk = [];
|
||||
let onMultiLineComment = false;
|
||||
lines.map((line) => {
|
||||
progressBar.increment();
|
||||
if (line.length === 0) {
|
||||
chunks.push(line);
|
||||
}
|
||||
if (isOneLineCommentLine(line)) {
|
||||
nextChunk.push(line);
|
||||
}
|
||||
if (onMultiLineComment) {
|
||||
nextChunk.push(line);
|
||||
if (isEndOfMultiLineComment(line)) {
|
||||
onMultiLineComment = false;
|
||||
}
|
||||
}
|
||||
if (isStartOfMultiLineComment(line)) {
|
||||
nextChunk.push(line);
|
||||
onMultiLineComment = true;
|
||||
}
|
||||
if (isKeyValuePair(line)) {
|
||||
nextChunk.push(line);
|
||||
const newMessageLineIfExists = nextChunk.find((lineInChunk) => lineInChunk.trim().startsWith(NEW_MESSAGE_TODO));
|
||||
if (newMessageLineIfExists === undefined || !creatingTarget) {
|
||||
chunks.push(nextChunk.join("\n"));
|
||||
}
|
||||
nextChunk = [];
|
||||
}
|
||||
});
|
||||
return chunks;
|
||||
}
|
||||
|
||||
function readFileIfExists(pathToFile) {
|
||||
if (checkIfFileExists(pathToFile)) {
|
||||
try {
|
||||
return fs.readFileSync(pathToFile, 'utf8');
|
||||
} catch (e) {
|
||||
console.error('Error:', e.stack);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isOneLineCommentLine(line) {
|
||||
return (line.startsWith("//"));
|
||||
}
|
||||
|
||||
function isStartOfMultiLineComment(line) {
|
||||
return (line.startsWith("/*"));
|
||||
}
|
||||
|
||||
function isEndOfMultiLineComment(line) {
|
||||
return (line.endsWith("*/"));
|
||||
}
|
||||
|
||||
function isKeyValuePair(line) {
|
||||
return (line.startsWith("\""));
|
||||
}
|
||||
|
||||
|
||||
function getSubStringWithRegex(string, regex) {
|
||||
return string.match(regex);
|
||||
}
|
||||
|
||||
function getSubStringBeforeLastString(string, char) {
|
||||
const lastCharIndex = string.lastIndexOf(char);
|
||||
return string.substr(0, lastCharIndex);
|
||||
}
|
||||
|
||||
|
||||
function getOutputFileLocationIfExistsElseTargetFileLocation(targetLocation) {
|
||||
if (program.outputFile) {
|
||||
return program.outputFile;
|
||||
}
|
||||
return targetLocation;
|
||||
}
|
||||
|
||||
function checkIfPathToFileIsValid(pathToCheck) {
|
||||
if (!pathToCheck.includes("/")) {
|
||||
return true;
|
||||
}
|
||||
return checkIfFileExists(getPathOfDirectory(pathToCheck));
|
||||
}
|
||||
|
||||
function checkIfFileExists(pathToCheck) {
|
||||
return fs.existsSync(pathToCheck);
|
||||
}
|
||||
|
||||
function getPathOfDirectory(pathToCheck) {
|
||||
return getSubStringBeforeLastString(pathToCheck, "/");
|
||||
}
|
||||
|
||||
function removeWhiteLines(string) {
|
||||
return string.replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm, "")
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces UNIX \n LF line endings to windows \r\n CRLF line endings.
|
||||
* @param filePath Path to file whose line endings are being converted
|
||||
*/
|
||||
function replaceLineEndingsToCRLF(filePath) {
|
||||
const data = readFileIfExists(filePath);
|
||||
const result = data.replace(/\n/g,"\r\n");
|
||||
fs.writeFileSync(filePath, result, 'utf8');
|
||||
}
|
@@ -5,7 +5,7 @@ import { PaginatedList } from '../../../core/data/paginated-list';
|
||||
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||
import { 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;
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { MetadataRegistryComponent } from './metadata-registry.component';
|
||||
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
@@ -18,6 +17,7 @@ import { NotificationsService } from '../../../shared/notifications/notification
|
||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
|
||||
import { RestResponse } from '../../../core/cache/response.models';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
|
||||
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
|
||||
|
||||
describe('MetadataRegistryComponent', () => {
|
||||
let comp: MetadataRegistryComponent;
|
||||
@@ -101,12 +101,12 @@ describe('MetadataRegistryComponent', () => {
|
||||
|
||||
it('should start editing the selected schema', async(() => {
|
||||
fixture.whenStable().then(() => {
|
||||
expect(registryService.editMetadataSchema).toHaveBeenCalledWith(mockSchemasList[0]);
|
||||
expect(registryService.editMetadataSchema).toHaveBeenCalledWith(mockSchemasList[0] as MetadataSchema);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should cancel editing the selected schema when clicked again', async(() => {
|
||||
spyOn(registryService, 'getActiveMetadataSchema').and.returnValue(observableOf(mockSchemasList[0]));
|
||||
spyOn(registryService, 'getActiveMetadataSchema').and.returnValue(observableOf(mockSchemasList[0] as MetadataSchema));
|
||||
spyOn(registryService, 'cancelEditMetadataSchema');
|
||||
row.click();
|
||||
fixture.detectChanges();
|
||||
@@ -121,7 +121,7 @@ describe('MetadataRegistryComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(registryService, 'deleteMetadataSchema').and.callThrough();
|
||||
spyOn(registryService, 'getSelectedMetadataSchemas').and.returnValue(observableOf(selectedSchemas));
|
||||
spyOn(registryService, 'getSelectedMetadataSchemas').and.returnValue(observableOf(selectedSchemas as MetadataSchema[]));
|
||||
comp.deleteSchemas();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { MetadataSchemaComponent } from './metadata-schema.component';
|
||||
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
@@ -22,6 +21,7 @@ import { NotificationsServiceStub } from '../../../shared/testing/notifications-
|
||||
import { RestResponse } from '../../../core/cache/response.models';
|
||||
import { MetadataSchema } from '../../../core/metadata/metadata-schema.model';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
|
||||
import { MetadataField } from '../../../core/metadata/metadata-field.model';
|
||||
|
||||
describe('MetadataSchemaComponent', () => {
|
||||
let comp: MetadataSchemaComponent;
|
||||
@@ -152,12 +152,12 @@ describe('MetadataSchemaComponent', () => {
|
||||
|
||||
it('should start editing the selected field', async(() => {
|
||||
fixture.whenStable().then(() => {
|
||||
expect(registryService.editMetadataField).toHaveBeenCalledWith(mockFieldsList[2]);
|
||||
expect(registryService.editMetadataField).toHaveBeenCalledWith(mockFieldsList[2] as MetadataField);
|
||||
});
|
||||
}));
|
||||
|
||||
it('should cancel editing the selected field when clicked again', async(() => {
|
||||
spyOn(registryService, 'getActiveMetadataField').and.returnValue(observableOf(mockFieldsList[2]));
|
||||
spyOn(registryService, 'getActiveMetadataField').and.returnValue(observableOf(mockFieldsList[2] as MetadataField));
|
||||
spyOn(registryService, 'cancelEditMetadataField');
|
||||
row.click();
|
||||
fixture.detectChanges();
|
||||
@@ -172,7 +172,7 @@ describe('MetadataSchemaComponent', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(registryService, 'deleteMetadataField').and.callThrough();
|
||||
spyOn(registryService, 'getSelectedMetadataFields').and.returnValue(observableOf(selectedFields));
|
||||
spyOn(registryService, 'getSelectedMetadataFields').and.returnValue(observableOf(selectedFields as MetadataField[]));
|
||||
comp.deleteFields();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -13,11 +13,11 @@ 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';
|
||||
import { EditCollectionSelectorComponent } from '../../shared/dso-selector/modal-wrappers/edit-collection-selector/edit-collection-selector.component';
|
||||
import {CreateItemParentSelectorComponent} from '../../shared/dso-selector/modal-wrappers/create-item-parent-selector/create-item-parent-selector.component';
|
||||
|
||||
/**
|
||||
* Component representing the admin sidebar
|
||||
@@ -138,18 +138,18 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
||||
parentID: 'new',
|
||||
active: false,
|
||||
visible: true,
|
||||
// model: {
|
||||
// type: MenuItemType.ONCLICK,
|
||||
// text: 'menu.section.new_item',
|
||||
// function: () => {
|
||||
// this.modalService.open(CreateItemParentSelectorComponent);
|
||||
// }
|
||||
// } as OnClickMenuItemModel,
|
||||
model: {
|
||||
type: MenuItemType.LINK,
|
||||
type: MenuItemType.ONCLICK,
|
||||
text: 'menu.section.new_item',
|
||||
link: '/submit'
|
||||
} as LinkMenuItemModel,
|
||||
function: () => {
|
||||
this.modalService.open(CreateItemParentSelectorComponent);
|
||||
}
|
||||
} as OnClickMenuItemModel,
|
||||
// model: {
|
||||
// type: MenuItemType.LINK,
|
||||
// text: 'menu.section.new_item',
|
||||
// link: '/submit'
|
||||
// } as LinkMenuItemModel,
|
||||
},
|
||||
{
|
||||
id: 'new_item_version',
|
||||
|
@@ -11,7 +11,6 @@ import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.serv
|
||||
import { MockRouter } from '../../shared/mocks/mock-router';
|
||||
import { ChangeDetectorRef, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { Item } from '../../core/shared/item.model';
|
||||
|
@@ -82,7 +82,8 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
||||
const date = firstItemRD.payload.firstMetadataValue(metadataField);
|
||||
if (hasValue(date)) {
|
||||
const dateObj = new Date(date);
|
||||
lowerLimit = dateObj.getFullYear();
|
||||
// TODO: it appears that getFullYear (based on local time) is sometimes unreliable. Switching to UTC.
|
||||
lowerLimit = dateObj.getUTCFullYear();
|
||||
}
|
||||
}
|
||||
const options = [];
|
||||
|
@@ -1,9 +1,19 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { DynamicInputModel, DynamicTextAreaModel } from '@ng-dynamic-forms/core';
|
||||
import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model';
|
||||
import {
|
||||
DynamicFormControlModel,
|
||||
DynamicFormService,
|
||||
DynamicInputModel,
|
||||
DynamicTextAreaModel
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component';
|
||||
import { NormalizedCollection } from '../../core/cache/models/normalized-collection.model';
|
||||
import { Location } from '@angular/common';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
import { RequestService } from '../../core/data/request.service';
|
||||
import { ObjectCacheService } from '../../core/cache/object-cache.service';
|
||||
|
||||
/**
|
||||
* Form used for creating and editing collections
|
||||
@@ -22,7 +32,7 @@ export class CollectionFormComponent extends ComColFormComponent<Collection> {
|
||||
/**
|
||||
* @type {Collection.type} This is a collection-type form
|
||||
*/
|
||||
protected type = Collection.type;
|
||||
type = Collection.type;
|
||||
|
||||
/**
|
||||
* The dynamic form fields used for creating/editing a collection
|
||||
@@ -65,4 +75,15 @@ export class CollectionFormComponent extends ComColFormComponent<Collection> {
|
||||
name: 'dc.description.provenance',
|
||||
}),
|
||||
];
|
||||
|
||||
public constructor(protected location: Location,
|
||||
protected formService: DynamicFormService,
|
||||
protected translate: TranslateService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected authService: AuthService,
|
||||
protected dsoService: CommunityDataService,
|
||||
protected requestService: RequestService,
|
||||
protected objectCache: ObjectCacheService) {
|
||||
super(location, formService, translate, notificationsService, authService, requestService, objectCache);
|
||||
}
|
||||
}
|
||||
|
@@ -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',
|
||||
|
@@ -5,7 +5,6 @@ import { CollectionPageComponent } from './collection-page.component';
|
||||
import { CollectionPageResolver } from './collection-page.resolver';
|
||||
import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component';
|
||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component';
|
||||
import { CreateCollectionPageGuard } from './create-collection-page/create-collection-page.guard';
|
||||
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
|
||||
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||
@@ -39,12 +38,8 @@ const COLLECTION_EDIT_PATH = ':id/edit';
|
||||
},
|
||||
{
|
||||
path: COLLECTION_EDIT_PATH,
|
||||
pathMatch: 'full',
|
||||
component: EditCollectionPageComponent,
|
||||
canActivate: [AuthenticatedGuard],
|
||||
resolve: {
|
||||
dso: CollectionPageResolver
|
||||
}
|
||||
loadChildren: './edit-collection-page/edit-collection-page.module#EditCollectionPageModule',
|
||||
canActivate: [AuthenticatedGuard]
|
||||
},
|
||||
{
|
||||
path: ':id/delete',
|
||||
|
@@ -3,16 +3,19 @@
|
||||
*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$"
|
||||
[logo]="(logoRD$ | async)?.payload" [alternateText]="'Collection Logo'">
|
||||
[alternateText]="'Collection Logo'">
|
||||
</ds-comcol-page-logo>
|
||||
<!-- Collection Name -->
|
||||
<ds-comcol-page-header
|
||||
[name]="collection.name">
|
||||
</ds-comcol-page-header>
|
||||
<!-- Collection logo -->
|
||||
<ds-comcol-page-logo *ngIf="logoRD$"
|
||||
[logo]="(logoRD$ | async)?.payload"
|
||||
[alternateText]="'Collection Logo'"
|
||||
[alternateText]="'Collection Logo'">
|
||||
</ds-comcol-page-logo>
|
||||
|
||||
<!-- Handle -->
|
||||
<ds-comcol-page-handle
|
||||
[content]="collection.handle"
|
||||
|
@@ -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';
|
||||
|
@@ -7,29 +7,30 @@ import { CollectionPageComponent } from './collection-page.component';
|
||||
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 { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component';
|
||||
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
|
||||
import { SearchService } from '../+search-page/search-service/search.service';
|
||||
import { CollectionItemMapperComponent } from './collection-item-mapper/collection-item-mapper.component';
|
||||
import { 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,
|
||||
CreateCollectionPageComponent,
|
||||
EditCollectionPageComponent,
|
||||
DeleteCollectionPageComponent,
|
||||
CollectionFormComponent,
|
||||
CollectionItemMapperComponent
|
||||
],
|
||||
exports: [
|
||||
CollectionFormComponent
|
||||
],
|
||||
providers: [
|
||||
SearchService,
|
||||
SearchFixedFilterService
|
||||
]
|
||||
})
|
||||
export class CollectionPageModule {
|
||||
|
@@ -4,5 +4,5 @@
|
||||
<h2 id="sub-header" class="border-bottom pb-2">{{'collection.create.sub-head' | translate:{ parent: (parentRD$| async)?.payload.name } }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<ds-collection-form (submitForm)="onSubmit($event)"></ds-collection-form>
|
||||
<ds-collection-form (submitForm)="onSubmit($event)" (finish)="navigateToNewPage()"></ds-collection-form>
|
||||
</div>
|
||||
|
@@ -10,6 +10,8 @@ import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { CreateCollectionPageComponent } from './create-collection-page.component';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { NotificationsServiceStub } from '../../shared/testing/notifications-service-stub';
|
||||
|
||||
describe('CreateCollectionPageComponent', () => {
|
||||
let comp: CreateCollectionPageComponent;
|
||||
@@ -27,6 +29,7 @@ describe('CreateCollectionPageComponent', () => {
|
||||
},
|
||||
{ provide: RouteService, useValue: { getQueryParameterValue: () => observableOf('1234') } },
|
||||
{ provide: Router, useValue: {} },
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
|
@@ -5,6 +5,8 @@ import { Router } from '@angular/router';
|
||||
import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
/**
|
||||
* Component that represents the page where a user can create a new Collection
|
||||
@@ -16,13 +18,16 @@ import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
})
|
||||
export class CreateCollectionPageComponent extends CreateComColPageComponent<Collection> {
|
||||
protected frontendURL = '/collections/';
|
||||
protected type = Collection.type;
|
||||
|
||||
public constructor(
|
||||
protected communityDataService: CommunityDataService,
|
||||
protected collectionDataService: CollectionDataService,
|
||||
protected routeService: RouteService,
|
||||
protected router: Router
|
||||
protected router: Router,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected translate: TranslateService
|
||||
) {
|
||||
super(collectionDataService, communityDataService, routeService, router);
|
||||
super(collectionDataService, communityDataService, routeService, router, notificationsService, translate);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Component for managing a collection's curation tasks
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-collection-curate',
|
||||
templateUrl: './collection-curate.component.html',
|
||||
})
|
||||
export class CollectionCurateComponent {
|
||||
/* TODO: Implement Collection Edit - Curate */
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
<ds-collection-form (submitForm)="onSubmit($event)"
|
||||
[dso]="(dsoRD$ | async)?.payload"
|
||||
(finish)="navigateToHomePage()"></ds-collection-form>
|
||||
<a class="btn btn-danger"
|
||||
[routerLink]="'/collections/' + (dsoRD$ | async)?.payload.uuid + '/delete'">{{'collection.edit.delete'
|
||||
| translate}}</a>
|
@@ -0,0 +1,42 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SharedModule } from '../../../shared/shared.module';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { CollectionMetadataComponent } from './collection-metadata.component';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
|
||||
|
||||
describe('CollectionMetadataComponent', () => {
|
||||
let comp: CollectionMetadataComponent;
|
||||
let fixture: ComponentFixture<CollectionMetadataComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule],
|
||||
declarations: [CollectionMetadataComponent],
|
||||
providers: [
|
||||
{ provide: CollectionDataService, useValue: {} },
|
||||
{ provide: ActivatedRoute, useValue: { parent: { data: observableOf({ dso: { payload: {} } }) } } },
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CollectionMetadataComponent);
|
||||
comp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
describe('frontendURL', () => {
|
||||
it('should have the right frontendURL set', () => {
|
||||
expect((comp as any).frontendURL).toEqual('/collections/');
|
||||
})
|
||||
});
|
||||
});
|
@@ -0,0 +1,29 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ComcolMetadataComponent } from '../../../shared/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
/**
|
||||
* Component for editing a collection's metadata
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-collection-metadata',
|
||||
templateUrl: './collection-metadata.component.html',
|
||||
})
|
||||
export class CollectionMetadataComponent extends ComcolMetadataComponent<Collection> {
|
||||
protected frontendURL = '/collections/';
|
||||
protected type = Collection.type;
|
||||
|
||||
public constructor(
|
||||
protected collectionDataService: CollectionDataService,
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected translate: TranslateService
|
||||
) {
|
||||
super(collectionDataService, router, route, notificationsService, translate);
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Component for managing a collection's roles
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-collection-roles',
|
||||
templateUrl: './collection-roles.component.html',
|
||||
})
|
||||
export class CollectionRolesComponent {
|
||||
/* TODO: Implement Collection Edit - Roles */
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
<div class="container-fluid">
|
||||
<div class="d-inline-block float-right">
|
||||
<button class=" btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
||||
[disabled]="!(hasChanges() | async)"
|
||||
(click)="discard()"><i
|
||||
class="fas fa-times"></i>
|
||||
<span class="d-none d-sm-inline"> {{"item.edit.metadata.discard-button" | translate}}</span>
|
||||
</button>
|
||||
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
|
||||
(click)="reinstate()"><i
|
||||
class="fas fa-undo-alt"></i>
|
||||
<span class="d-none d-sm-inline"> {{"item.edit.metadata.reinstate-button" | translate}}</span>
|
||||
</button>
|
||||
<button class="btn btn-primary" [disabled]="!(hasChanges() | async) || !isValid() || (initialHarvestType === harvestTypeNone && contentSource.harvestType === initialHarvestType)"
|
||||
(click)="onSubmit()"><i
|
||||
class="fas fa-save"></i>
|
||||
<span class="d-none d-sm-inline"> {{"item.edit.metadata.save-button" | translate}}</span>
|
||||
</button>
|
||||
</div>
|
||||
<h4>{{ 'collection.edit.tabs.source.head' | translate }}</h4>
|
||||
<div *ngIf="contentSource" class="form-check mb-4">
|
||||
<input type="checkbox" class="form-check-input" id="externalSourceCheck" [checked]="(contentSource?.harvestType !== harvestTypeNone)" (change)="changeExternalSource()">
|
||||
<label class="form-check-label" for="externalSourceCheck">{{ 'collection.edit.tabs.source.external' | translate }}</label>
|
||||
</div>
|
||||
<ds-loading *ngIf="!contentSource" [message]="'loading.content-source' | translate"></ds-loading>
|
||||
<h4 *ngIf="contentSource && (contentSource?.harvestType !== harvestTypeNone)">{{ 'collection.edit.tabs.source.form.head' | translate }}</h4>
|
||||
</div>
|
||||
<ds-form *ngIf="formGroup && contentSource && (contentSource?.harvestType !== harvestTypeNone)"
|
||||
[formId]="'collection-source-form-id'"
|
||||
[formGroup]="formGroup"
|
||||
[formModel]="formModel"
|
||||
[formLayout]="formLayout"
|
||||
[displaySubmit]="false"
|
||||
(dfChange)="onChange($event)"
|
||||
(submitForm)="onSubmit()"
|
||||
(cancel)="onCancel()"></ds-form>
|
||||
<div class="container-fluid" *ngIf="(contentSource?.harvestType !== harvestTypeNone)">
|
||||
<div class="d-inline-block float-right">
|
||||
<button class=" btn btn-danger" *ngIf="!(isReinstatable() | async)"
|
||||
[disabled]="!(hasChanges() | async)"
|
||||
(click)="discard()"><i
|
||||
class="fas fa-times"></i>
|
||||
<span class="d-none d-sm-inline"> {{"item.edit.metadata.discard-button" | translate}}</span>
|
||||
</button>
|
||||
<button class="btn btn-warning" *ngIf="isReinstatable() | async"
|
||||
(click)="reinstate()"><i
|
||||
class="fas fa-undo-alt"></i>
|
||||
<span class="d-none d-sm-inline"> {{"item.edit.metadata.reinstate-button" | translate}}</span>
|
||||
</button>
|
||||
<button class="btn btn-primary" [disabled]="!(hasChanges() | async) || !isValid() || (initialHarvestType === harvestTypeNone && contentSource.harvestType === initialHarvestType)"
|
||||
(click)="onSubmit()"><i
|
||||
class="fas fa-save"></i>
|
||||
<span class="d-none d-sm-inline"> {{"item.edit.metadata.save-button" | translate}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
@@ -0,0 +1,222 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { CollectionSourceComponent } from './collection-source.component';
|
||||
import { ContentSource, ContentSourceHarvestType } from '../../../core/shared/content-source.model';
|
||||
import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
|
||||
import { INotification, Notification } from '../../../shared/notifications/models/notification.model';
|
||||
import { NotificationType } from '../../../shared/notifications/models/notification-type';
|
||||
import { FieldUpdate } from '../../../core/data/object-updates/object-updates.reducer';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { DynamicFormControlModel, DynamicFormService } from '@ng-dynamic-forms/core';
|
||||
import { hasValue } from '../../../shared/empty.util';
|
||||
import { FormControl, FormGroup } from '@angular/forms';
|
||||
import { RouterStub } from '../../../shared/testing/router-stub';
|
||||
import { GLOBAL_CONFIG } from '../../../../config';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||
import { RequestService } from '../../../core/data/request.service';
|
||||
|
||||
const infoNotification: INotification = new Notification('id', NotificationType.Info, 'info');
|
||||
const warningNotification: INotification = new Notification('id', NotificationType.Warning, 'warning');
|
||||
const successNotification: INotification = new Notification('id', NotificationType.Success, 'success');
|
||||
|
||||
const uuid = '29481ed7-ae6b-409a-8c51-34dd347a0ce4';
|
||||
let date: Date;
|
||||
let contentSource: ContentSource;
|
||||
let fieldUpdate: FieldUpdate;
|
||||
let objectUpdatesService: ObjectUpdatesService;
|
||||
let notificationsService: NotificationsService;
|
||||
let location: Location;
|
||||
let formService: DynamicFormService;
|
||||
let router: any;
|
||||
let collection: Collection;
|
||||
let collectionService: CollectionDataService;
|
||||
let requestService: RequestService;
|
||||
|
||||
describe('CollectionSourceComponent', () => {
|
||||
let comp: CollectionSourceComponent;
|
||||
let fixture: ComponentFixture<CollectionSourceComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
date = new Date();
|
||||
contentSource = Object.assign(new ContentSource(), {
|
||||
uuid: uuid,
|
||||
metadataConfigs: [
|
||||
{
|
||||
id: 'dc',
|
||||
label: 'Simple Dublin Core',
|
||||
nameSpace: 'http://www.openarchives.org/OAI/2.0/oai_dc/'
|
||||
},
|
||||
{
|
||||
id: 'qdc',
|
||||
label: 'Qualified Dublin Core',
|
||||
nameSpace: 'http://purl.org/dc/terms/'
|
||||
},
|
||||
{
|
||||
id: 'dim',
|
||||
label: 'DSpace Intermediate Metadata',
|
||||
nameSpace: 'http://www.dspace.org/xmlns/dspace/dim'
|
||||
}
|
||||
]
|
||||
});
|
||||
fieldUpdate = {
|
||||
field: contentSource,
|
||||
changeType: undefined
|
||||
};
|
||||
objectUpdatesService = jasmine.createSpyObj('objectUpdatesService',
|
||||
{
|
||||
getFieldUpdates: observableOf({
|
||||
[contentSource.uuid]: fieldUpdate
|
||||
}),
|
||||
saveAddFieldUpdate: {},
|
||||
discardFieldUpdates: {},
|
||||
reinstateFieldUpdates: observableOf(true),
|
||||
initialize: {},
|
||||
getUpdatedFields: observableOf([contentSource]),
|
||||
getLastModified: observableOf(date),
|
||||
hasUpdates: observableOf(true),
|
||||
isReinstatable: observableOf(false),
|
||||
isValidPage: observableOf(true)
|
||||
}
|
||||
);
|
||||
notificationsService = jasmine.createSpyObj('notificationsService',
|
||||
{
|
||||
info: infoNotification,
|
||||
warning: warningNotification,
|
||||
success: successNotification
|
||||
}
|
||||
);
|
||||
location = jasmine.createSpyObj('location', ['back']);
|
||||
formService = Object.assign({
|
||||
createFormGroup: (fModel: DynamicFormControlModel[]) => {
|
||||
const controls = {};
|
||||
if (hasValue(fModel)) {
|
||||
fModel.forEach((controlModel) => {
|
||||
controls[controlModel.id] = new FormControl((controlModel as any).value);
|
||||
});
|
||||
return new FormGroup(controls);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
router = Object.assign(new RouterStub(), {
|
||||
url: 'http://test-url.com/test-url'
|
||||
});
|
||||
collection = Object.assign(new Collection(), {
|
||||
uuid: 'fake-collection-id'
|
||||
});
|
||||
collectionService = jasmine.createSpyObj('collectionService', {
|
||||
getContentSource: observableOf(contentSource),
|
||||
updateContentSource: observableOf(contentSource),
|
||||
getHarvesterEndpoint: observableOf('harvester-endpoint')
|
||||
});
|
||||
requestService = jasmine.createSpyObj('requestService', ['removeByHrefSubstring']);
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), RouterTestingModule],
|
||||
declarations: [CollectionSourceComponent],
|
||||
providers: [
|
||||
{ provide: ObjectUpdatesService, useValue: objectUpdatesService },
|
||||
{ provide: NotificationsService, useValue: notificationsService },
|
||||
{ provide: Location, useValue: location },
|
||||
{ provide: DynamicFormService, useValue: formService },
|
||||
{ provide: ActivatedRoute, useValue: { parent: { data: observableOf({ dso: new RemoteData(false, false, true, null, collection) }) } } },
|
||||
{ provide: Router, useValue: router },
|
||||
{ provide: GLOBAL_CONFIG, useValue: { collection: { edit: { undoTimeout: 10 } } } as any },
|
||||
{ provide: CollectionDataService, useValue: collectionService },
|
||||
{ provide: RequestService, useValue: requestService }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CollectionSourceComponent);
|
||||
comp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
describe('on startup', () => {
|
||||
let form;
|
||||
|
||||
beforeEach(() => {
|
||||
form = fixture.debugElement.query(By.css('ds-form'));
|
||||
});
|
||||
|
||||
it('ContentSource should be disabled', () => {
|
||||
expect(comp.contentSource.harvestType).toEqual(ContentSourceHarvestType.None);
|
||||
});
|
||||
|
||||
it('the input-form should be hidden', () => {
|
||||
expect(form).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when selecting the checkbox', () => {
|
||||
let input;
|
||||
let form;
|
||||
|
||||
beforeEach(() => {
|
||||
input = fixture.debugElement.query(By.css('#externalSourceCheck')).nativeElement;
|
||||
input.click();
|
||||
fixture.detectChanges();
|
||||
form = fixture.debugElement.query(By.css('ds-form'));
|
||||
});
|
||||
|
||||
it('should enable ContentSource', () => {
|
||||
expect(comp.contentSource.harvestType).not.toEqual(ContentSourceHarvestType.None);
|
||||
});
|
||||
|
||||
it('should send a field update', () => {
|
||||
expect(objectUpdatesService.saveAddFieldUpdate).toHaveBeenCalledWith(router.url, comp.contentSource)
|
||||
});
|
||||
|
||||
it('should display the form', () => {
|
||||
expect(form).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isValid', () => {
|
||||
it('should return true when ContentSource is disabled but the form invalid', () => {
|
||||
spyOnProperty(comp.formGroup, 'valid').and.returnValue(false);
|
||||
comp.contentSource.harvestType = ContentSourceHarvestType.None;
|
||||
expect(comp.isValid()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when ContentSource is enabled but the form is invalid', () => {
|
||||
spyOnProperty(comp.formGroup, 'valid').and.returnValue(false);
|
||||
comp.contentSource.harvestType = ContentSourceHarvestType.Metadata;
|
||||
expect(comp.isValid()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when ContentSource is enabled and the form is valid', () => {
|
||||
spyOnProperty(comp.formGroup, 'valid').and.returnValue(true);
|
||||
comp.contentSource.harvestType = ContentSourceHarvestType.Metadata;
|
||||
expect(comp.isValid()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onSubmit', () => {
|
||||
beforeEach(() => {
|
||||
comp.onSubmit();
|
||||
});
|
||||
|
||||
it('should re-initialize the field updates', () => {
|
||||
expect(objectUpdatesService.initialize).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should display a success notification', () => {
|
||||
expect(notificationsService.success).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call updateContentSource on the collectionService', () => {
|
||||
expect(collectionService.updateContentSource).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
@@ -0,0 +1,479 @@
|
||||
import { Component, Inject, OnDestroy, OnInit } from '@angular/core';
|
||||
import { AbstractTrackableComponent } from '../../../shared/trackable/abstract-trackable.component';
|
||||
import {
|
||||
DynamicFormControlModel,
|
||||
DynamicFormGroupModel,
|
||||
DynamicFormLayout,
|
||||
DynamicFormService,
|
||||
DynamicInputModel,
|
||||
DynamicOptionControlModel,
|
||||
DynamicRadioGroupModel,
|
||||
DynamicSelectModel,
|
||||
DynamicTextAreaModel
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { Location } from '@angular/common';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { ObjectUpdatesService } from '../../../core/data/object-updates/object-updates.service';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
import { hasNoValue, hasValue, isNotEmpty } from '../../../shared/empty.util';
|
||||
import { ContentSource, ContentSourceHarvestType } from '../../../core/shared/content-source.model';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { Collection } from '../../../core/shared/collection.model';
|
||||
import { first, map, switchMap, take } from 'rxjs/operators';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
|
||||
import { Subscription } from 'rxjs/internal/Subscription';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { GLOBAL_CONFIG, GlobalConfig } from '../../../../config';
|
||||
import { CollectionDataService } from '../../../core/data/collection-data.service';
|
||||
import { getSucceededRemoteData } from '../../../core/shared/operators';
|
||||
import { MetadataConfig } from '../../../core/shared/metadata-config.model';
|
||||
import { INotification } from '../../../shared/notifications/models/notification.model';
|
||||
import { RequestService } from '../../../core/data/request.service';
|
||||
|
||||
/**
|
||||
* Component for managing the content source of the collection
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-collection-source',
|
||||
templateUrl: './collection-source.component.html',
|
||||
})
|
||||
export class CollectionSourceComponent extends AbstractTrackableComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* The current collection's remote data
|
||||
*/
|
||||
collectionRD$: Observable<RemoteData<Collection>>;
|
||||
|
||||
/**
|
||||
* The collection's content source
|
||||
*/
|
||||
contentSource: ContentSource;
|
||||
|
||||
/**
|
||||
* The current update to the content source
|
||||
*/
|
||||
update$: Observable<FieldUpdate>;
|
||||
|
||||
/**
|
||||
* The initial harvest type we started off with
|
||||
* Used to compare changes
|
||||
*/
|
||||
initialHarvestType: ContentSourceHarvestType;
|
||||
|
||||
/**
|
||||
* @type {string} Key prefix used to generate form labels
|
||||
*/
|
||||
LABEL_KEY_PREFIX = 'collection.edit.tabs.source.form.';
|
||||
|
||||
/**
|
||||
* @type {string} Key prefix used to generate form error messages
|
||||
*/
|
||||
ERROR_KEY_PREFIX = 'collection.edit.tabs.source.form.errors.';
|
||||
|
||||
/**
|
||||
* @type {string} Key prefix used to generate form option labels
|
||||
*/
|
||||
OPTIONS_KEY_PREFIX = 'collection.edit.tabs.source.form.options.';
|
||||
|
||||
/**
|
||||
* The Dynamic Input Model for the OAI Provider
|
||||
*/
|
||||
oaiSourceModel = new DynamicInputModel({
|
||||
id: 'oaiSource',
|
||||
name: 'oaiSource',
|
||||
required: true,
|
||||
validators: {
|
||||
required: null
|
||||
},
|
||||
errorMessages: {
|
||||
required: 'You must provide a set id of the target collection.'
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* The Dynamic Input Model for the OAI Set
|
||||
*/
|
||||
oaiSetIdModel = new DynamicInputModel({
|
||||
id: 'oaiSetId',
|
||||
name: 'oaiSetId'
|
||||
});
|
||||
|
||||
/**
|
||||
* The Dynamic Input Model for the Metadata Format used
|
||||
*/
|
||||
metadataConfigIdModel = new DynamicSelectModel({
|
||||
id: 'metadataConfigId',
|
||||
name: 'metadataConfigId'
|
||||
});
|
||||
|
||||
/**
|
||||
* The Dynamic Input Model for the type of harvesting
|
||||
*/
|
||||
harvestTypeModel = new DynamicRadioGroupModel<string>({
|
||||
id: 'harvestType',
|
||||
name: 'harvestType',
|
||||
options: [
|
||||
{
|
||||
value: ContentSourceHarvestType.Metadata
|
||||
},
|
||||
{
|
||||
value: ContentSourceHarvestType.MetadataAndRef
|
||||
},
|
||||
{
|
||||
value: ContentSourceHarvestType.MetadataAndBitstreams
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/**
|
||||
* All input models in a simple array for easier iterations
|
||||
*/
|
||||
inputModels = [this.oaiSourceModel, this.oaiSetIdModel, this.metadataConfigIdModel, this.harvestTypeModel];
|
||||
|
||||
/**
|
||||
* The dynamic form fields used for editing the content source of a collection
|
||||
* @type {(DynamicInputModel | DynamicTextAreaModel)[]}
|
||||
*/
|
||||
formModel: DynamicFormControlModel[] = [
|
||||
new DynamicFormGroupModel({
|
||||
id: 'oaiSourceContainer',
|
||||
group: [
|
||||
this.oaiSourceModel
|
||||
]
|
||||
}),
|
||||
new DynamicFormGroupModel({
|
||||
id: 'oaiSetContainer',
|
||||
group: [
|
||||
this.oaiSetIdModel,
|
||||
this.metadataConfigIdModel
|
||||
]
|
||||
}),
|
||||
new DynamicFormGroupModel({
|
||||
id: 'harvestTypeContainer',
|
||||
group: [
|
||||
this.harvestTypeModel
|
||||
]
|
||||
})
|
||||
];
|
||||
|
||||
/**
|
||||
* Layout used for structuring the form inputs
|
||||
*/
|
||||
formLayout: DynamicFormLayout = {
|
||||
oaiSource: {
|
||||
grid: {
|
||||
host: 'col-12 d-inline-block'
|
||||
}
|
||||
},
|
||||
oaiSetId: {
|
||||
grid: {
|
||||
host: 'col col-sm-6 d-inline-block'
|
||||
}
|
||||
},
|
||||
metadataConfigId: {
|
||||
grid: {
|
||||
host: 'col col-sm-6 d-inline-block'
|
||||
}
|
||||
},
|
||||
harvestType: {
|
||||
grid: {
|
||||
host: 'col-12',
|
||||
option: 'btn-outline-secondary'
|
||||
}
|
||||
},
|
||||
oaiSetContainer: {
|
||||
grid: {
|
||||
host: 'row'
|
||||
}
|
||||
},
|
||||
oaiSourceContainer: {
|
||||
grid: {
|
||||
host: 'row'
|
||||
}
|
||||
},
|
||||
harvestTypeContainer: {
|
||||
grid: {
|
||||
host: 'row'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The form group of this form
|
||||
*/
|
||||
formGroup: FormGroup;
|
||||
|
||||
/**
|
||||
* Subscription to update the current form
|
||||
*/
|
||||
updateSub: Subscription;
|
||||
|
||||
/**
|
||||
* The content harvesting type used when harvesting is disabled
|
||||
*/
|
||||
harvestTypeNone = ContentSourceHarvestType.None;
|
||||
|
||||
/**
|
||||
* The previously selected harvesting type
|
||||
* Used for switching between ContentSourceHarvestType.None and the previously selected value when enabling / disabling harvesting
|
||||
* Defaults to ContentSourceHarvestType.Metadata
|
||||
*/
|
||||
previouslySelectedHarvestType = ContentSourceHarvestType.Metadata;
|
||||
|
||||
/**
|
||||
* Notifications displayed after clicking submit
|
||||
* These are cleaned up every time a user submits the form to prevent error or other notifications from staying active
|
||||
* while they shouldn't be.
|
||||
*/
|
||||
displayedNotifications: INotification[] = [];
|
||||
|
||||
public constructor(public objectUpdatesService: ObjectUpdatesService,
|
||||
public notificationsService: NotificationsService,
|
||||
protected location: Location,
|
||||
protected formService: DynamicFormService,
|
||||
protected translate: TranslateService,
|
||||
protected route: ActivatedRoute,
|
||||
protected router: Router,
|
||||
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig,
|
||||
protected collectionService: CollectionDataService,
|
||||
protected requestService: RequestService) {
|
||||
super(objectUpdatesService, notificationsService, translate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize properties to setup the Field Update and Form
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.notificationsPrefix = 'collection.edit.tabs.source.notifications.';
|
||||
this.discardTimeOut = this.EnvConfig.collection.edit.undoTimeout;
|
||||
this.url = this.router.url;
|
||||
if (this.url.indexOf('?') > 0) {
|
||||
this.url = this.url.substr(0, this.url.indexOf('?'));
|
||||
}
|
||||
this.formGroup = this.formService.createFormGroup(this.formModel);
|
||||
this.collectionRD$ = this.route.parent.data.pipe(first(), map((data) => data.dso));
|
||||
|
||||
this.collectionRD$.pipe(
|
||||
getSucceededRemoteData(),
|
||||
map((col) => col.payload.uuid),
|
||||
switchMap((uuid) => this.collectionService.getContentSource(uuid)),
|
||||
take(1)
|
||||
).subscribe((contentSource: ContentSource) => {
|
||||
this.initializeOriginalContentSource(contentSource);
|
||||
});
|
||||
|
||||
this.updateFieldTranslations();
|
||||
this.translate.onLangChange
|
||||
.subscribe(() => {
|
||||
this.updateFieldTranslations();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the Field Update and subscribe on it to fire updates to the form whenever it changes
|
||||
*/
|
||||
initializeOriginalContentSource(contentSource: ContentSource) {
|
||||
this.contentSource = contentSource;
|
||||
this.initialHarvestType = contentSource.harvestType;
|
||||
this.initializeMetadataConfigs();
|
||||
const initialContentSource = cloneDeep(this.contentSource);
|
||||
this.objectUpdatesService.initialize(this.url, [initialContentSource], new Date());
|
||||
this.update$ = this.objectUpdatesService.getFieldUpdates(this.url, [initialContentSource]).pipe(
|
||||
map((updates: FieldUpdates) => updates[initialContentSource.uuid])
|
||||
);
|
||||
this.updateSub = this.update$.subscribe((update: FieldUpdate) => {
|
||||
if (update) {
|
||||
const field = update.field as ContentSource;
|
||||
let configId;
|
||||
if (hasValue(this.contentSource) && isNotEmpty(this.contentSource.metadataConfigs)) {
|
||||
configId = this.contentSource.metadataConfigs[0].id;
|
||||
}
|
||||
if (hasValue(field) && hasValue(field.metadataConfigId)) {
|
||||
configId = field.metadataConfigId;
|
||||
}
|
||||
if (hasValue(field)) {
|
||||
this.formGroup.patchValue({
|
||||
oaiSourceContainer: {
|
||||
oaiSource: field.oaiSource
|
||||
},
|
||||
oaiSetContainer: {
|
||||
oaiSetId: field.oaiSetId,
|
||||
metadataConfigId: configId
|
||||
},
|
||||
harvestTypeContainer: {
|
||||
harvestType: field.harvestType
|
||||
}
|
||||
});
|
||||
this.contentSource = cloneDeep(field);
|
||||
}
|
||||
this.contentSource.metadataConfigId = configId;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the metadataConfigIdModel's options using the contentSource's metadataConfigs property
|
||||
*/
|
||||
initializeMetadataConfigs() {
|
||||
this.metadataConfigIdModel.options = this.contentSource.metadataConfigs
|
||||
.map((metadataConfig: MetadataConfig) => Object.assign({ value: metadataConfig.id, label: metadataConfig.label }));
|
||||
if (this.metadataConfigIdModel.options.length > 0) {
|
||||
this.formGroup.patchValue({
|
||||
oaiSetContainer: {
|
||||
metadataConfigId: this.metadataConfigIdModel.options[0].value
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used the update translations of errors and labels on init and on language change
|
||||
*/
|
||||
private updateFieldTranslations() {
|
||||
this.inputModels.forEach(
|
||||
(fieldModel: DynamicFormControlModel) => {
|
||||
this.updateFieldTranslation(fieldModel);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the translations of a DynamicInputModel
|
||||
* @param fieldModel
|
||||
*/
|
||||
private updateFieldTranslation(fieldModel: DynamicFormControlModel) {
|
||||
fieldModel.label = this.translate.instant(this.LABEL_KEY_PREFIX + fieldModel.id);
|
||||
if (isNotEmpty(fieldModel.validators)) {
|
||||
fieldModel.errorMessages = {};
|
||||
Object.keys(fieldModel.validators).forEach((key) => {
|
||||
fieldModel.errorMessages[key] = this.translate.instant(this.ERROR_KEY_PREFIX + fieldModel.id + '.' + key);
|
||||
});
|
||||
}
|
||||
if (fieldModel instanceof DynamicOptionControlModel) {
|
||||
if (isNotEmpty(fieldModel.options)) {
|
||||
fieldModel.options.forEach((option) => {
|
||||
if (hasNoValue(option.label)) {
|
||||
option.label = this.translate.instant(this.OPTIONS_KEY_PREFIX + fieldModel.id + '.' + option.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fired whenever the form receives an update and makes sure the Content Source and field update is up-to-date with the changes
|
||||
* @param event
|
||||
*/
|
||||
onChange(event) {
|
||||
this.updateContentSourceField(event.model, true);
|
||||
this.saveFieldUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit the edited Content Source to the REST API, re-initialize the field update and display a notification
|
||||
*/
|
||||
onSubmit() {
|
||||
// Remove cached harvester request to allow for latest harvester to be displayed when switching tabs
|
||||
this.collectionRD$.pipe(
|
||||
getSucceededRemoteData(),
|
||||
map((col) => col.payload.uuid),
|
||||
switchMap((uuid) => this.collectionService.getHarvesterEndpoint(uuid)),
|
||||
take(1)
|
||||
).subscribe((endpoint) => this.requestService.removeByHrefSubstring(endpoint));
|
||||
|
||||
// Update harvester
|
||||
this.collectionRD$.pipe(
|
||||
getSucceededRemoteData(),
|
||||
map((col) => col.payload.uuid),
|
||||
switchMap((uuid) => this.collectionService.updateContentSource(uuid, this.contentSource)),
|
||||
take(1)
|
||||
).subscribe((result: ContentSource | INotification) => {
|
||||
if (hasValue((result as any).harvestType)) {
|
||||
this.clearNotifications();
|
||||
this.initializeOriginalContentSource(result as ContentSource);
|
||||
this.displayedNotifications.push(this.notificationsService.success(this.getNotificationTitle('saved'), this.getNotificationContent('saved')));
|
||||
} else {
|
||||
this.displayedNotifications.push(result as INotification);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel the edit and return to the previous page
|
||||
*/
|
||||
onCancel() {
|
||||
this.location.back();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the current form valid to be submitted ?
|
||||
*/
|
||||
isValid(): boolean {
|
||||
return (this.contentSource.harvestType === ContentSourceHarvestType.None) || this.formGroup.valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch the external source on or off and fire a field update
|
||||
*/
|
||||
changeExternalSource() {
|
||||
if (this.contentSource.harvestType === ContentSourceHarvestType.None) {
|
||||
this.contentSource.harvestType = this.previouslySelectedHarvestType;
|
||||
} else {
|
||||
this.previouslySelectedHarvestType = this.contentSource.harvestType;
|
||||
this.contentSource.harvestType = ContentSourceHarvestType.None;
|
||||
}
|
||||
this.updateContentSource(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop over all inputs and update the Content Source with their value
|
||||
* @param updateHarvestType When set to false, the harvestType of the contentSource will be ignored in the update
|
||||
*/
|
||||
updateContentSource(updateHarvestType: boolean) {
|
||||
this.inputModels.forEach(
|
||||
(fieldModel: DynamicInputModel) => {
|
||||
this.updateContentSourceField(fieldModel, updateHarvestType)
|
||||
}
|
||||
);
|
||||
this.saveFieldUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the Content Source with the value from a DynamicInputModel
|
||||
* @param fieldModel The fieldModel to fetch the value from and update the contentSource with
|
||||
* @param updateHarvestType When set to false, the harvestType of the contentSource will be ignored in the update
|
||||
*/
|
||||
updateContentSourceField(fieldModel: DynamicInputModel, updateHarvestType: boolean) {
|
||||
if (hasValue(fieldModel.value) && !(fieldModel.id === this.harvestTypeModel.id && !updateHarvestType)) {
|
||||
this.contentSource[fieldModel.id] = fieldModel.value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current Content Source to the Object Updates cache
|
||||
*/
|
||||
saveFieldUpdate() {
|
||||
this.objectUpdatesService.saveAddFieldUpdate(this.url, cloneDeep(this.contentSource));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear possible active notifications
|
||||
*/
|
||||
clearNotifications() {
|
||||
this.displayedNotifications.forEach((notification: INotification) => {
|
||||
this.notificationsService.remove(notification);
|
||||
});
|
||||
this.displayedNotifications = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure open subscriptions are closed
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
if (this.updateSub) {
|
||||
this.updateSub.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 pb-4">
|
||||
<h2 id="header" class="border-bottom pb-2">{{ 'collection.edit.head' | translate }}</h2>
|
||||
<ds-collection-form (submitForm)="onSubmit($event)" [dso]="(dsoRD$ | async)?.payload"></ds-collection-form>
|
||||
<a class="btn btn-danger"
|
||||
[routerLink]="'/collections/' + (dsoRD$ | async)?.payload.uuid + '/delete'">{{'collection.edit.delete'
|
||||
| translate}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -1 +0,0 @@
|
||||
|
@@ -13,13 +13,29 @@ describe('EditCollectionPageComponent', () => {
|
||||
let comp: EditCollectionPageComponent;
|
||||
let fixture: ComponentFixture<EditCollectionPageComponent>;
|
||||
|
||||
const routeStub = {
|
||||
data: observableOf({
|
||||
dso: { payload: {} }
|
||||
}),
|
||||
routeConfig: {
|
||||
children: []
|
||||
},
|
||||
snapshot: {
|
||||
firstChild: {
|
||||
routeConfig: {
|
||||
path: 'mockUrl'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule],
|
||||
declarations: [EditCollectionPageComponent],
|
||||
providers: [
|
||||
{ provide: CollectionDataService, useValue: {} },
|
||||
{ provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }) } },
|
||||
{ provide: ActivatedRoute, useValue: routeStub },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
@@ -31,9 +47,9 @@ describe('EditCollectionPageComponent', () => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
describe('frontendURL', () => {
|
||||
it('should have the right frontendURL set', () => {
|
||||
expect((comp as any).frontendURL).toEqual('/collections/');
|
||||
describe('type', () => {
|
||||
it('should have the right type set', () => {
|
||||
expect((comp as any).type).toEqual('collection');
|
||||
})
|
||||
});
|
||||
});
|
||||
|
@@ -2,24 +2,30 @@ import { Component } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
import { getCollectionPageRoute } from '../collection-page-routing.module';
|
||||
|
||||
/**
|
||||
* Component that represents the page where a user can edit an existing Collection
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-edit-collection',
|
||||
styleUrls: ['./edit-collection-page.component.scss'],
|
||||
templateUrl: './edit-collection-page.component.html'
|
||||
templateUrl: '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.html'
|
||||
})
|
||||
export class EditCollectionPageComponent extends EditComColPageComponent<Collection> {
|
||||
protected frontendURL = '/collections/';
|
||||
type = 'collection';
|
||||
|
||||
public constructor(
|
||||
protected collectionDataService: CollectionDataService,
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute
|
||||
) {
|
||||
super(collectionDataService, router, route);
|
||||
super(router, route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection page url
|
||||
* @param collection The collection for which the url is requested
|
||||
*/
|
||||
getPageUrl(collection: Collection): string {
|
||||
return getCollectionPageRoute(collection.id)
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,32 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { EditCollectionPageComponent } from './edit-collection-page.component';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { EditCollectionPageRoutingModule } from './edit-collection-page.routing.module';
|
||||
import { CollectionMetadataComponent } from './collection-metadata/collection-metadata.component';
|
||||
import { CollectionPageModule } from '../collection-page.module';
|
||||
import { CollectionRolesComponent } from './collection-roles/collection-roles.component';
|
||||
import { CollectionCurateComponent } from './collection-curate/collection-curate.component';
|
||||
import { CollectionSourceComponent } from './collection-source/collection-source.component';
|
||||
|
||||
/**
|
||||
* Module that contains all components related to the Edit Collection page administrator functionality
|
||||
*/
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
EditCollectionPageRoutingModule,
|
||||
CollectionPageModule
|
||||
],
|
||||
declarations: [
|
||||
EditCollectionPageComponent,
|
||||
CollectionMetadataComponent,
|
||||
CollectionRolesComponent,
|
||||
CollectionCurateComponent,
|
||||
CollectionSourceComponent
|
||||
]
|
||||
})
|
||||
export class EditCollectionPageModule {
|
||||
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { EditCollectionPageComponent } from './edit-collection-page.component';
|
||||
import { CollectionPageResolver } from '../collection-page.resolver';
|
||||
import { CollectionMetadataComponent } from './collection-metadata/collection-metadata.component';
|
||||
import { CollectionRolesComponent } from './collection-roles/collection-roles.component';
|
||||
import { CollectionSourceComponent } from './collection-source/collection-source.component';
|
||||
import { CollectionCurateComponent } from './collection-curate/collection-curate.component';
|
||||
|
||||
/**
|
||||
* Routing module that handles the routing for the Edit Collection page administrator functionality
|
||||
*/
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: '',
|
||||
component: EditCollectionPageComponent,
|
||||
resolve: {
|
||||
dso: CollectionPageResolver
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'metadata',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'metadata',
|
||||
component: CollectionMetadataComponent,
|
||||
data: {
|
||||
title: 'collection.edit.tabs.metadata.title',
|
||||
hideReturnButton: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'roles',
|
||||
component: CollectionRolesComponent,
|
||||
data: { title: 'collection.edit.tabs.roles.title' }
|
||||
},
|
||||
{
|
||||
path: 'source',
|
||||
component: CollectionSourceComponent,
|
||||
data: { title: 'collection.edit.tabs.source.title' }
|
||||
},
|
||||
{
|
||||
path: 'curate',
|
||||
component: CollectionCurateComponent,
|
||||
data: { title: 'collection.edit.tabs.curate.title' }
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
],
|
||||
providers: [
|
||||
CollectionPageResolver,
|
||||
]
|
||||
})
|
||||
export class EditCollectionPageRoutingModule {
|
||||
|
||||
}
|
@@ -1,9 +1,19 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { DynamicInputModel, DynamicTextAreaModel } from '@ng-dynamic-forms/core';
|
||||
import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynamic-form-control.model';
|
||||
import {
|
||||
DynamicFormControlModel,
|
||||
DynamicFormService,
|
||||
DynamicInputModel,
|
||||
DynamicTextAreaModel
|
||||
} from '@ng-dynamic-forms/core';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { ResourceType } from '../../core/shared/resource-type';
|
||||
import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component';
|
||||
import { Location } from '@angular/common';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { AuthService } from '../../core/auth/auth.service';
|
||||
import { RequestService } from '../../core/data/request.service';
|
||||
import { ObjectCacheService } from '../../core/cache/object-cache.service';
|
||||
|
||||
/**
|
||||
* Form used for creating and editing communities
|
||||
@@ -22,7 +32,7 @@ export class CommunityFormComponent extends ComColFormComponent<Community> {
|
||||
/**
|
||||
* @type {Community.type} This is a community-type form
|
||||
*/
|
||||
protected type = Community.type;
|
||||
type = Community.type;
|
||||
|
||||
/**
|
||||
* The dynamic form fields used for creating/editing a community
|
||||
@@ -57,4 +67,15 @@ export class CommunityFormComponent extends ComColFormComponent<Community> {
|
||||
name: 'dc.description.tableofcontents',
|
||||
}),
|
||||
];
|
||||
|
||||
public constructor(protected location: Location,
|
||||
protected formService: DynamicFormService,
|
||||
protected translate: TranslateService,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected authService: AuthService,
|
||||
protected dsoService: CommunityDataService,
|
||||
protected requestService: RequestService,
|
||||
protected objectCache: ObjectCacheService) {
|
||||
super(location, formService, translate, notificationsService, authService, requestService, objectCache);
|
||||
}
|
||||
}
|
||||
|
@@ -5,7 +5,6 @@ import { CommunityPageComponent } from './community-page.component';
|
||||
import { CommunityPageResolver } from './community-page.resolver';
|
||||
import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component';
|
||||
import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
|
||||
import { EditCommunityPageComponent } from './edit-community-page/edit-community-page.component';
|
||||
import { CreateCommunityPageGuard } from './create-community-page/create-community-page.guard';
|
||||
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
|
||||
import { URLCombiner } from '../core/url-combiner/url-combiner';
|
||||
@@ -38,12 +37,8 @@ const COMMUNITY_EDIT_PATH = ':id/edit';
|
||||
},
|
||||
{
|
||||
path: COMMUNITY_EDIT_PATH,
|
||||
pathMatch: 'full',
|
||||
component: EditCommunityPageComponent,
|
||||
canActivate: [AuthenticatedGuard],
|
||||
resolve: {
|
||||
dso: CommunityPageResolver
|
||||
}
|
||||
loadChildren: './edit-community-page/edit-community-page.module#EditCommunityPageModule',
|
||||
canActivate: [AuthenticatedGuard]
|
||||
},
|
||||
{
|
||||
path: ':id/delete',
|
||||
|
@@ -1,13 +1,13 @@
|
||||
<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 name -->
|
||||
<ds-comcol-page-header [name]="communityPayload.name"></ds-comcol-page-header>
|
||||
<!-- Community logo -->
|
||||
<ds-comcol-page-logo *ngIf="logoRD$" [logo]="(logoRD$ | async)?.payload" [alternateText]="'Community Logo'">
|
||||
</ds-comcol-page-logo>
|
||||
|
||||
<!-- Community name -->
|
||||
<ds-comcol-page-header [name]="communityPayload.name"></ds-comcol-page-header>
|
||||
<!-- Handle -->
|
||||
<ds-comcol-page-handle [content]="communityPayload.handle" [title]="'community.page.handle'">
|
||||
</ds-comcol-page-handle>
|
||||
|
@@ -6,26 +6,29 @@ 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 { EditCommunityPageComponent } from './edit-community-page/edit-community-page.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,
|
||||
CommunityPageSubCollectionListComponent,
|
||||
CommunityPageSubCommunityListComponent,
|
||||
CreateCommunityPageComponent,
|
||||
EditCommunityPageComponent,
|
||||
DeleteCommunityPageComponent,
|
||||
CommunityFormComponent
|
||||
],
|
||||
exports: [
|
||||
CommunityFormComponent
|
||||
]
|
||||
})
|
||||
|
||||
|
@@ -7,5 +7,5 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
<ds-community-form (submitForm)="onSubmit($event)"></ds-community-form>
|
||||
<ds-community-form (submitForm)="onSubmit($event)" (finish)="navigateToNewPage()"></ds-community-form>
|
||||
</div>
|
||||
|
@@ -10,6 +10,8 @@ import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { CreateCommunityPageComponent } from './create-community-page.component';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { NotificationsServiceStub } from '../../shared/testing/notifications-service-stub';
|
||||
|
||||
describe('CreateCommunityPageComponent', () => {
|
||||
let comp: CreateCommunityPageComponent;
|
||||
@@ -23,6 +25,7 @@ describe('CreateCommunityPageComponent', () => {
|
||||
{ provide: CommunityDataService, useValue: { findById: () => observableOf({}) } },
|
||||
{ provide: RouteService, useValue: { getQueryParameterValue: () => observableOf('1234') } },
|
||||
{ provide: Router, useValue: {} },
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
|
@@ -4,6 +4,8 @@ import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { RouteService } from '../../core/services/route.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comcol-page/create-comcol-page.component';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
/**
|
||||
* Component that represents the page where a user can create a new Community
|
||||
@@ -15,12 +17,15 @@ import { CreateComColPageComponent } from '../../shared/comcol-forms/create-comc
|
||||
})
|
||||
export class CreateCommunityPageComponent extends CreateComColPageComponent<Community> {
|
||||
protected frontendURL = '/communities/';
|
||||
protected type = Community.type;
|
||||
|
||||
public constructor(
|
||||
protected communityDataService: CommunityDataService,
|
||||
protected routeService: RouteService,
|
||||
protected router: Router
|
||||
protected router: Router,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected translate: TranslateService
|
||||
) {
|
||||
super(communityDataService, communityDataService, routeService, router);
|
||||
super(communityDataService, communityDataService, routeService, router, notificationsService, translate);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Component for managing a community's curation tasks
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-community-curate',
|
||||
templateUrl: './community-curate.component.html',
|
||||
})
|
||||
export class CommunityCurateComponent {
|
||||
/* TODO: Implement Community Edit - Curate */
|
||||
}
|
@@ -0,0 +1,6 @@
|
||||
<ds-community-form (submitForm)="onSubmit($event)"
|
||||
[dso]="(dsoRD$ | async)?.payload"
|
||||
(finish)="navigateToHomePage()"></ds-community-form>
|
||||
<a class="btn btn-danger"
|
||||
[routerLink]="'/communities/' + (dsoRD$ | async)?.payload.uuid + '/delete'">{{'community.edit.delete'
|
||||
| translate}}</a>
|
@@ -0,0 +1,42 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { SharedModule } from '../../../shared/shared.module';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { of as observableOf } from 'rxjs/internal/observable/of';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { CommunityMetadataComponent } from './community-metadata.component';
|
||||
import { CommunityDataService } from '../../../core/data/community-data.service';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
|
||||
|
||||
describe('CommunityMetadataComponent', () => {
|
||||
let comp: CommunityMetadataComponent;
|
||||
let fixture: ComponentFixture<CommunityMetadataComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule],
|
||||
declarations: [CommunityMetadataComponent],
|
||||
providers: [
|
||||
{ provide: CommunityDataService, useValue: {} },
|
||||
{ provide: ActivatedRoute, useValue: { parent: { data: observableOf({ dso: { payload: {} } }) } } },
|
||||
{ provide: NotificationsService, useValue: new NotificationsServiceStub() }
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CommunityMetadataComponent);
|
||||
comp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
describe('frontendURL', () => {
|
||||
it('should have the right frontendURL set', () => {
|
||||
expect((comp as any).frontendURL).toEqual('/communities/');
|
||||
})
|
||||
});
|
||||
});
|
@@ -0,0 +1,29 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ComcolMetadataComponent } from '../../../shared/comcol-forms/edit-comcol-page/comcol-metadata/comcol-metadata.component';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Community } from '../../../core/shared/community.model';
|
||||
import { CommunityDataService } from '../../../core/data/community-data.service';
|
||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
/**
|
||||
* Component for editing a community's metadata
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-community-metadata',
|
||||
templateUrl: './community-metadata.component.html',
|
||||
})
|
||||
export class CommunityMetadataComponent extends ComcolMetadataComponent<Community> {
|
||||
protected frontendURL = '/communities/';
|
||||
protected type = Community.type;
|
||||
|
||||
public constructor(
|
||||
protected communityDataService: CommunityDataService,
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected translate: TranslateService
|
||||
) {
|
||||
super(communityDataService, router, route, notificationsService, translate);
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Component for managing a community's roles
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-community-roles',
|
||||
templateUrl: './community-roles.component.html',
|
||||
})
|
||||
export class CommunityRolesComponent {
|
||||
/* TODO: Implement Community Edit - Roles */
|
||||
}
|
@@ -1,12 +0,0 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 pb-4">
|
||||
<h2 id="header" class="border-bottom pb-2">{{ 'community.edit.head' | translate }}</h2>
|
||||
<ds-community-form (submitForm)="onSubmit($event)"
|
||||
[dso]="(dsoRD$ | async)?.payload"></ds-community-form>
|
||||
<a class="btn btn-danger"
|
||||
[routerLink]="'/communities/' + (dsoRD$ | async)?.payload.uuid + '/delete'">{{'community.edit.delete'
|
||||
| translate}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@@ -1 +0,0 @@
|
||||
|
@@ -13,13 +13,29 @@ describe('EditCommunityPageComponent', () => {
|
||||
let comp: EditCommunityPageComponent;
|
||||
let fixture: ComponentFixture<EditCommunityPageComponent>;
|
||||
|
||||
const routeStub = {
|
||||
data: observableOf({
|
||||
dso: { payload: {} }
|
||||
}),
|
||||
routeConfig: {
|
||||
children: []
|
||||
},
|
||||
snapshot: {
|
||||
firstChild: {
|
||||
routeConfig: {
|
||||
path: 'mockUrl'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), SharedModule, CommonModule, RouterTestingModule],
|
||||
declarations: [EditCommunityPageComponent],
|
||||
providers: [
|
||||
{ provide: CommunityDataService, useValue: {} },
|
||||
{ provide: ActivatedRoute, useValue: { data: observableOf({ dso: { payload: {} } }) } },
|
||||
{ provide: ActivatedRoute, useValue: routeStub },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
@@ -31,9 +47,9 @@ describe('EditCommunityPageComponent', () => {
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
describe('frontendURL', () => {
|
||||
it('should have the right frontendURL set', () => {
|
||||
expect((comp as any).frontendURL).toEqual('/communities/');
|
||||
describe('type', () => {
|
||||
it('should have the right type set', () => {
|
||||
expect((comp as any).type).toEqual('community');
|
||||
})
|
||||
});
|
||||
});
|
||||
|
@@ -1,25 +1,31 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component';
|
||||
import { getCommunityPageRoute } from '../community-page-routing.module';
|
||||
|
||||
/**
|
||||
* Component that represents the page where a user can edit an existing Community
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-edit-community',
|
||||
styleUrls: ['./edit-community-page.component.scss'],
|
||||
templateUrl: './edit-community-page.component.html'
|
||||
templateUrl: '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component.html'
|
||||
})
|
||||
export class EditCommunityPageComponent extends EditComColPageComponent<Community> {
|
||||
protected frontendURL = '/communities/';
|
||||
type = 'community';
|
||||
|
||||
public constructor(
|
||||
protected communityDataService: CommunityDataService,
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute
|
||||
) {
|
||||
super(communityDataService, router, route);
|
||||
super(router, route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the community page url
|
||||
* @param community The community for which the url is requested
|
||||
*/
|
||||
getPageUrl(community: Community): string {
|
||||
return getCommunityPageRoute(community.id)
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,30 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { EditCommunityPageRoutingModule } from './edit-community-page.routing.module';
|
||||
import { CommunityPageModule } from '../community-page.module';
|
||||
import { EditCommunityPageComponent } from './edit-community-page.component';
|
||||
import { CommunityCurateComponent } from './community-curate/community-curate.component';
|
||||
import { CommunityMetadataComponent } from './community-metadata/community-metadata.component';
|
||||
import { CommunityRolesComponent } from './community-roles/community-roles.component';
|
||||
|
||||
/**
|
||||
* Module that contains all components related to the Edit Community page administrator functionality
|
||||
*/
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule,
|
||||
EditCommunityPageRoutingModule,
|
||||
CommunityPageModule
|
||||
],
|
||||
declarations: [
|
||||
EditCommunityPageComponent,
|
||||
CommunityCurateComponent,
|
||||
CommunityMetadataComponent,
|
||||
CommunityRolesComponent
|
||||
]
|
||||
})
|
||||
export class EditCommunityPageModule {
|
||||
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
import { CommunityPageResolver } from '../community-page.resolver';
|
||||
import { EditCommunityPageComponent } from './edit-community-page.component';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommunityMetadataComponent } from './community-metadata/community-metadata.component';
|
||||
import { CommunityRolesComponent } from './community-roles/community-roles.component';
|
||||
import { CommunityCurateComponent } from './community-curate/community-curate.component';
|
||||
|
||||
/**
|
||||
* Routing module that handles the routing for the Edit Community page administrator functionality
|
||||
*/
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: '',
|
||||
component: EditCommunityPageComponent,
|
||||
resolve: {
|
||||
dso: CommunityPageResolver
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'metadata',
|
||||
pathMatch: 'full'
|
||||
},
|
||||
{
|
||||
path: 'metadata',
|
||||
component: CommunityMetadataComponent,
|
||||
data: {
|
||||
title: 'community.edit.tabs.metadata.title',
|
||||
hideReturnButton: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'roles',
|
||||
component: CommunityRolesComponent,
|
||||
data: { title: 'community.edit.tabs.roles.title' }
|
||||
},
|
||||
{
|
||||
path: 'curate',
|
||||
component: CommunityCurateComponent,
|
||||
data: { title: 'community.edit.tabs.curate.title' }
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
],
|
||||
providers: [
|
||||
CommunityPageResolver,
|
||||
]
|
||||
})
|
||||
export class EditCommunityPageRoutingModule {
|
||||
|
||||
}
|
@@ -1,14 +1,13 @@
|
||||
<ng-container *ngVar="(subCollectionsRDObs | async) as subCollectionsRD">
|
||||
<div *ngIf="subCollectionsRD?.hasSucceeded && subCollectionsRD?.payload.totalElements > 0" @fadeIn>
|
||||
<h2>{{'community.sub-collection-list.head' | translate}}</h2>
|
||||
<ul>
|
||||
<li *ngFor="let collection of subCollectionsRD?.payload.page">
|
||||
<p>
|
||||
<span class="lead"><a [routerLink]="['/collections', collection.id]">{{collection.name}}</a></span><br>
|
||||
<span class="text-muted">{{collection.shortDescription}}</span>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
<ds-viewable-collection
|
||||
[config]="config"
|
||||
[sortConfig]="sortConfig"
|
||||
[objects]="subCollectionsRD"
|
||||
[hideGear]="false"
|
||||
(paginationChange)="onPaginationChange($event)">
|
||||
</ds-viewable-collection>
|
||||
</div>
|
||||
<ds-error *ngIf="subCollectionsRD?.hasFailed" message="{{'error.sub-collections' | translate}}"></ds-error>
|
||||
<ds-loading *ngIf="subCollectionsRD?.isLoading" message="{{'loading.sub-collections' | translate}}"></ds-loading>
|
||||
|
@@ -0,0 +1,182 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
import { CommunityPageSubCollectionListComponent } from './community-page-sub-collection-list.component';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
import { FindListOptions } from '../../core/data/request.models';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
|
||||
import { PaginatedList } from '../../core/data/paginated-list';
|
||||
import { PageInfo } from '../../core/shared/page-info.model';
|
||||
import { HostWindowService } from '../../shared/host-window.service';
|
||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service-stub';
|
||||
import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service';
|
||||
|
||||
describe('CommunityPageSubCollectionList Component', () => {
|
||||
let comp: CommunityPageSubCollectionListComponent;
|
||||
let fixture: ComponentFixture<CommunityPageSubCollectionListComponent>;
|
||||
let collectionDataServiceStub: any;
|
||||
let subCollList = [];
|
||||
|
||||
const collections = [Object.assign(new Community(), {
|
||||
id: '123456789-1',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'Collection 1' }
|
||||
]
|
||||
}
|
||||
}),
|
||||
Object.assign(new Community(), {
|
||||
id: '123456789-2',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'Collection 2' }
|
||||
]
|
||||
}
|
||||
}),
|
||||
Object.assign(new Community(), {
|
||||
id: '123456789-3',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'Collection 3' }
|
||||
]
|
||||
}
|
||||
}),
|
||||
Object.assign(new Community(), {
|
||||
id: '123456789-4',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'Collection 4' }
|
||||
]
|
||||
}
|
||||
}),
|
||||
Object.assign(new Community(), {
|
||||
id: '123456789-5',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'Collection 5' }
|
||||
]
|
||||
}
|
||||
}),
|
||||
Object.assign(new Community(), {
|
||||
id: '123456789-6',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'Collection 6' }
|
||||
]
|
||||
}
|
||||
}),
|
||||
Object.assign(new Community(), {
|
||||
id: '123456789-7',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'Collection 7' }
|
||||
]
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
const mockCommunity = Object.assign(new Community(), {
|
||||
id: '123456789',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'Test title' }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
collectionDataServiceStub = {
|
||||
findByParent(parentUUID: string, options: FindListOptions = {}) {
|
||||
let currentPage = options.currentPage;
|
||||
let elementsPerPage = options.elementsPerPage;
|
||||
if (currentPage === undefined) {
|
||||
currentPage = 1
|
||||
}
|
||||
elementsPerPage = 5;
|
||||
const startPageIndex = (currentPage - 1) * elementsPerPage;
|
||||
let endPageIndex = (currentPage * elementsPerPage);
|
||||
if (endPageIndex > subCollList.length) {
|
||||
endPageIndex = subCollList.length;
|
||||
}
|
||||
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), subCollList.slice(startPageIndex, endPageIndex)));
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
SharedModule,
|
||||
RouterTestingModule.withRoutes([]),
|
||||
NgbModule.forRoot(),
|
||||
NoopAnimationsModule
|
||||
],
|
||||
declarations: [CommunityPageSubCollectionListComponent],
|
||||
providers: [
|
||||
{ provide: CollectionDataService, useValue: collectionDataServiceStub },
|
||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
|
||||
{ provide: SelectableListService, useValue: {} },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CommunityPageSubCollectionListComponent);
|
||||
comp = fixture.componentInstance;
|
||||
comp.community = mockCommunity;
|
||||
});
|
||||
|
||||
it('should display a list of collections', () => {
|
||||
subCollList = collections;
|
||||
fixture.detectChanges();
|
||||
|
||||
const collList = fixture.debugElement.queryAll(By.css('li'));
|
||||
expect(collList.length).toEqual(5);
|
||||
expect(collList[0].nativeElement.textContent).toContain('Collection 1');
|
||||
expect(collList[1].nativeElement.textContent).toContain('Collection 2');
|
||||
expect(collList[2].nativeElement.textContent).toContain('Collection 3');
|
||||
expect(collList[3].nativeElement.textContent).toContain('Collection 4');
|
||||
expect(collList[4].nativeElement.textContent).toContain('Collection 5');
|
||||
});
|
||||
|
||||
it('should not display the header when list of collections is empty', () => {
|
||||
subCollList = [];
|
||||
fixture.detectChanges();
|
||||
|
||||
const subComHead = fixture.debugElement.queryAll(By.css('h2'));
|
||||
expect(subComHead.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should update list of collections on pagination change', () => {
|
||||
subCollList = collections;
|
||||
fixture.detectChanges();
|
||||
|
||||
const pagination = Object.create({
|
||||
pagination:{
|
||||
id: comp.pageId,
|
||||
currentPage: 2,
|
||||
pageSize: 5
|
||||
},
|
||||
sort: {
|
||||
field: 'dc.title',
|
||||
direction: 'ASC'
|
||||
}
|
||||
});
|
||||
comp.onPaginationChange(pagination);
|
||||
fixture.detectChanges();
|
||||
|
||||
const collList = fixture.debugElement.queryAll(By.css('li'));
|
||||
expect(collList.length).toEqual(2);
|
||||
expect(collList[0].nativeElement.textContent).toContain('Collection 6');
|
||||
expect(collList[1].nativeElement.textContent).toContain('Collection 7');
|
||||
});
|
||||
});
|
@@ -1,12 +1,16 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
|
||||
import { fadeIn } from '../../shared/animations/fade';
|
||||
import { PaginatedList } from '../../core/data/paginated-list';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-community-page-sub-collection-list',
|
||||
@@ -16,9 +20,60 @@ import { PaginatedList } from '../../core/data/paginated-list';
|
||||
})
|
||||
export class CommunityPageSubCollectionListComponent implements OnInit {
|
||||
@Input() community: Community;
|
||||
subCollectionsRDObs: Observable<RemoteData<PaginatedList<Collection>>>;
|
||||
|
||||
/**
|
||||
* The pagination configuration
|
||||
*/
|
||||
config: PaginationComponentOptions;
|
||||
|
||||
/**
|
||||
* The pagination id
|
||||
*/
|
||||
pageId = 'community-collections-pagination';
|
||||
|
||||
/**
|
||||
* The sorting configuration
|
||||
*/
|
||||
sortConfig: SortOptions;
|
||||
|
||||
/**
|
||||
* A list of remote data objects of communities' collections
|
||||
*/
|
||||
subCollectionsRDObs: BehaviorSubject<RemoteData<PaginatedList<Collection>>> = new BehaviorSubject<RemoteData<PaginatedList<Collection>>>({} as any);
|
||||
|
||||
constructor(private cds: CollectionDataService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.subCollectionsRDObs = this.community.collections;
|
||||
this.config = new PaginationComponentOptions();
|
||||
this.config.id = this.pageId;
|
||||
this.config.pageSize = 5;
|
||||
this.config.currentPage = 1;
|
||||
this.sortConfig = new SortOptions('dc.title', SortDirection.ASC);
|
||||
this.updatePage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when one of the pagination settings is changed
|
||||
* @param event The new pagination data
|
||||
*/
|
||||
onPaginationChange(event) {
|
||||
this.config.currentPage = event.pagination.currentPage;
|
||||
this.config.pageSize = event.pagination.pageSize;
|
||||
this.sortConfig.field = event.sort.field;
|
||||
this.sortConfig.direction = event.sort.direction;
|
||||
this.updatePage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the list of collections
|
||||
*/
|
||||
updatePage() {
|
||||
this.cds.findByParent(this.community.id,{
|
||||
currentPage: this.config.currentPage,
|
||||
elementsPerPage: this.config.pageSize,
|
||||
sort: { field: this.sortConfig.field, direction: this.sortConfig.direction }
|
||||
}).pipe(take(1)).subscribe((results) => {
|
||||
this.subCollectionsRDObs.next(results);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -1,14 +1,13 @@
|
||||
<ng-container *ngVar="(subCommunitiesRDObs | async) as subCommunitiesRD">
|
||||
<div *ngIf="subCommunitiesRD?.hasSucceeded && subCommunitiesRD?.payload.totalElements > 0" @fadeIn>
|
||||
<h2>{{'community.sub-community-list.head' | translate}}</h2>
|
||||
<ul>
|
||||
<li *ngFor="let community of subCommunitiesRD?.payload.page">
|
||||
<p>
|
||||
<span class="lead"><a [routerLink]="['/communities', community.id]">{{community.name}}</a></span><br>
|
||||
<span class="text-muted">{{community.shortDescription}}</span>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
<ds-viewable-collection
|
||||
[config]="config"
|
||||
[sortConfig]="sortConfig"
|
||||
[objects]="subCommunitiesRD"
|
||||
[hideGear]="false"
|
||||
(paginationChange)="onPaginationChange($event)">
|
||||
</ds-viewable-collection>
|
||||
</div>
|
||||
<ds-error *ngIf="subCommunitiesRD?.hasFailed" message="{{'error.sub-communities' | translate}}"></ds-error>
|
||||
<ds-loading *ngIf="subCommunitiesRD?.isLoading" message="{{'loading.sub-communities' | translate}}"></ds-loading>
|
||||
|
@@ -1,21 +1,29 @@
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {TranslateModule} from '@ngx-translate/core';
|
||||
import {NO_ERRORS_SCHEMA} from '@angular/core';
|
||||
import {CommunityPageSubCommunityListComponent} from './community-page-sub-community-list.component';
|
||||
import {Community} from '../../core/shared/community.model';
|
||||
import {RemoteData} from '../../core/data/remote-data';
|
||||
import {PaginatedList} from '../../core/data/paginated-list';
|
||||
import {PageInfo} from '../../core/shared/page-info.model';
|
||||
import {SharedModule} from '../../shared/shared.module';
|
||||
import {RouterTestingModule} from '@angular/router/testing';
|
||||
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {of as observableOf, Observable } from 'rxjs';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
describe('SubCommunityList Component', () => {
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
import { CommunityPageSubCommunityListComponent } from './community-page-sub-community-list.component';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { PaginatedList } from '../../core/data/paginated-list';
|
||||
import { PageInfo } from '../../core/shared/page-info.model';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
|
||||
import { FindListOptions } from '../../core/data/request.models';
|
||||
import { HostWindowService } from '../../shared/host-window.service';
|
||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service-stub';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service';
|
||||
|
||||
describe('CommunityPageSubCommunityListComponent Component', () => {
|
||||
let comp: CommunityPageSubCommunityListComponent;
|
||||
let fixture: ComponentFixture<CommunityPageSubCommunityListComponent>;
|
||||
let communityDataServiceStub: any;
|
||||
let subCommList = [];
|
||||
|
||||
const subcommunities = [Object.assign(new Community(), {
|
||||
id: '123456789-1',
|
||||
@@ -32,34 +40,92 @@ describe('SubCommunityList Component', () => {
|
||||
{ language: 'en_US', value: 'SubCommunity 2' }
|
||||
]
|
||||
}
|
||||
}),
|
||||
Object.assign(new Community(), {
|
||||
id: '123456789-3',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'SubCommunity 3' }
|
||||
]
|
||||
}
|
||||
}),
|
||||
Object.assign(new Community(), {
|
||||
id: '12345678942',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'SubCommunity 4' }
|
||||
]
|
||||
}
|
||||
}),
|
||||
Object.assign(new Community(), {
|
||||
id: '123456789-5',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'SubCommunity 5' }
|
||||
]
|
||||
}
|
||||
}),
|
||||
Object.assign(new Community(), {
|
||||
id: '123456789-6',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'SubCommunity 6' }
|
||||
]
|
||||
}
|
||||
}),
|
||||
Object.assign(new Community(), {
|
||||
id: '123456789-7',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'SubCommunity 7' }
|
||||
]
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
const emptySubCommunitiesCommunity = Object.assign(new Community(), {
|
||||
const mockCommunity = Object.assign(new Community(), {
|
||||
id: '123456789',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'Test title' }
|
||||
]
|
||||
},
|
||||
subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), []))
|
||||
}
|
||||
});
|
||||
|
||||
const mockCommunity = Object.assign(new Community(), {
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'Test title' }
|
||||
]
|
||||
},
|
||||
subcommunities: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), subcommunities))
|
||||
})
|
||||
;
|
||||
communityDataServiceStub = {
|
||||
findByParent(parentUUID: string, options: FindListOptions = {}) {
|
||||
let currentPage = options.currentPage;
|
||||
let elementsPerPage = options.elementsPerPage;
|
||||
if (currentPage === undefined) {
|
||||
currentPage = 1
|
||||
}
|
||||
elementsPerPage = 5;
|
||||
|
||||
const startPageIndex = (currentPage - 1) * elementsPerPage;
|
||||
let endPageIndex = (currentPage * elementsPerPage);
|
||||
if (endPageIndex > subCommList.length) {
|
||||
endPageIndex = subCommList.length;
|
||||
}
|
||||
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), subCommList.slice(startPageIndex, endPageIndex)));
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [TranslateModule.forRoot(), SharedModule,
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
SharedModule,
|
||||
RouterTestingModule.withRoutes([]),
|
||||
NoopAnimationsModule],
|
||||
NgbModule.forRoot(),
|
||||
NoopAnimationsModule
|
||||
],
|
||||
declarations: [CommunityPageSubCommunityListComponent],
|
||||
providers: [
|
||||
{ provide: CommunityDataService, useValue: communityDataServiceStub },
|
||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
|
||||
{ provide: SelectableListService, useValue: {} },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
@@ -67,23 +133,52 @@ describe('SubCommunityList Component', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CommunityPageSubCommunityListComponent);
|
||||
comp = fixture.componentInstance;
|
||||
comp.community = mockCommunity;
|
||||
|
||||
});
|
||||
|
||||
it('should display a list of subCommunities', () => {
|
||||
comp.community = mockCommunity;
|
||||
it('should display a list of sub-communities', () => {
|
||||
subCommList = subcommunities;
|
||||
fixture.detectChanges();
|
||||
|
||||
const subComList = fixture.debugElement.queryAll(By.css('li'));
|
||||
expect(subComList.length).toEqual(2);
|
||||
expect(subComList.length).toEqual(5);
|
||||
expect(subComList[0].nativeElement.textContent).toContain('SubCommunity 1');
|
||||
expect(subComList[1].nativeElement.textContent).toContain('SubCommunity 2');
|
||||
expect(subComList[2].nativeElement.textContent).toContain('SubCommunity 3');
|
||||
expect(subComList[3].nativeElement.textContent).toContain('SubCommunity 4');
|
||||
expect(subComList[4].nativeElement.textContent).toContain('SubCommunity 5');
|
||||
});
|
||||
|
||||
it('should not display the header when subCommunities are empty', () => {
|
||||
comp.community = emptySubCommunitiesCommunity;
|
||||
it('should not display the header when list of sub-communities is empty', () => {
|
||||
subCommList = [];
|
||||
fixture.detectChanges();
|
||||
|
||||
const subComHead = fixture.debugElement.queryAll(By.css('h2'));
|
||||
expect(subComHead.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should update list of sub-communities on pagination change', () => {
|
||||
subCommList = subcommunities;
|
||||
fixture.detectChanges();
|
||||
|
||||
const pagination = Object.create({
|
||||
pagination:{
|
||||
id: comp.pageId,
|
||||
currentPage: 2,
|
||||
pageSize: 5
|
||||
},
|
||||
sort: {
|
||||
field: 'dc.title',
|
||||
direction: 'ASC'
|
||||
}
|
||||
});
|
||||
comp.onPaginationChange(pagination);
|
||||
fixture.detectChanges();
|
||||
|
||||
const collList = fixture.debugElement.queryAll(By.css('li'));
|
||||
expect(collList.length).toEqual(2);
|
||||
expect(collList[0].nativeElement.textContent).toContain('SubCommunity 6');
|
||||
expect(collList[1].nativeElement.textContent).toContain('SubCommunity 7');
|
||||
});
|
||||
});
|
||||
|
@@ -1,26 +1,82 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
|
||||
import { fadeIn } from '../../shared/animations/fade';
|
||||
import { PaginatedList } from '../../core/data/paginated-list';
|
||||
import {Observable} from 'rxjs';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-community-page-sub-community-list',
|
||||
styleUrls: ['./community-page-sub-community-list.component.scss'],
|
||||
templateUrl: './community-page-sub-community-list.component.html',
|
||||
animations:[fadeIn]
|
||||
animations: [fadeIn]
|
||||
})
|
||||
/**
|
||||
* Component to render the sub-communities of a Community
|
||||
*/
|
||||
export class CommunityPageSubCommunityListComponent implements OnInit {
|
||||
@Input() community: Community;
|
||||
subCommunitiesRDObs: Observable<RemoteData<PaginatedList<Community>>>;
|
||||
|
||||
/**
|
||||
* The pagination configuration
|
||||
*/
|
||||
config: PaginationComponentOptions;
|
||||
|
||||
/**
|
||||
* The pagination id
|
||||
*/
|
||||
pageId = 'community-subCommunities-pagination';
|
||||
|
||||
/**
|
||||
* The sorting configuration
|
||||
*/
|
||||
sortConfig: SortOptions;
|
||||
|
||||
/**
|
||||
* A list of remote data objects of communities' collections
|
||||
*/
|
||||
subCommunitiesRDObs: BehaviorSubject<RemoteData<PaginatedList<Community>>> = new BehaviorSubject<RemoteData<PaginatedList<Community>>>({} as any);
|
||||
|
||||
constructor(private cds: CommunityDataService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.subCommunitiesRDObs = this.community.subcommunities;
|
||||
this.config = new PaginationComponentOptions();
|
||||
this.config.id = this.pageId;
|
||||
this.config.pageSize = 5;
|
||||
this.config.currentPage = 1;
|
||||
this.sortConfig = new SortOptions('dc.title', SortDirection.ASC);
|
||||
this.updatePage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when one of the pagination settings is changed
|
||||
* @param event The new pagination data
|
||||
*/
|
||||
onPaginationChange(event) {
|
||||
this.config.currentPage = event.pagination.currentPage;
|
||||
this.config.pageSize = event.pagination.pageSize;
|
||||
this.sortConfig.field = event.sort.field;
|
||||
this.sortConfig.direction = event.sort.direction;
|
||||
this.updatePage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the list of sub-communities
|
||||
*/
|
||||
updatePage() {
|
||||
this.cds.findByParent(this.community.id, {
|
||||
currentPage: this.config.currentPage,
|
||||
elementsPerPage: this.config.pageSize,
|
||||
sort: { field: this.sortConfig.field, direction: this.sortConfig.direction }
|
||||
}).pipe(take(1)).subscribe((results) => {
|
||||
this.subCommunitiesRDObs.next(results);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -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));
|
||||
}
|
||||
}
|
@@ -0,0 +1,161 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
|
||||
import { TopLevelCommunityListComponent } from './top-level-community-list.component';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
import { PaginatedList } from '../../core/data/paginated-list';
|
||||
import { PageInfo } from '../../core/shared/page-info.model';
|
||||
import { SharedModule } from '../../shared/shared.module';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
|
||||
import { FindListOptions } from '../../core/data/request.models';
|
||||
import { HostWindowService } from '../../shared/host-window.service';
|
||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service-stub';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service';
|
||||
|
||||
describe('TopLevelCommunityList Component', () => {
|
||||
let comp: TopLevelCommunityListComponent;
|
||||
let fixture: ComponentFixture<TopLevelCommunityListComponent>;
|
||||
let communityDataServiceStub: any;
|
||||
|
||||
const topCommList = [Object.assign(new Community(), {
|
||||
id: '123456789-1',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'TopCommunity 1' }
|
||||
]
|
||||
}
|
||||
}),
|
||||
Object.assign(new Community(), {
|
||||
id: '123456789-2',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'TopCommunity 2' }
|
||||
]
|
||||
}
|
||||
}),
|
||||
Object.assign(new Community(), {
|
||||
id: '123456789-3',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'TopCommunity 3' }
|
||||
]
|
||||
}
|
||||
}),
|
||||
Object.assign(new Community(), {
|
||||
id: '12345678942',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'TopCommunity 4' }
|
||||
]
|
||||
}
|
||||
}),
|
||||
Object.assign(new Community(), {
|
||||
id: '123456789-5',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'TopCommunity 5' }
|
||||
]
|
||||
}
|
||||
}),
|
||||
Object.assign(new Community(), {
|
||||
id: '123456789-6',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'TopCommunity 6' }
|
||||
]
|
||||
}
|
||||
}),
|
||||
Object.assign(new Community(), {
|
||||
id: '123456789-7',
|
||||
metadata: {
|
||||
'dc.title': [
|
||||
{ language: 'en_US', value: 'TopCommunity 7' }
|
||||
]
|
||||
}
|
||||
})
|
||||
];
|
||||
|
||||
communityDataServiceStub = {
|
||||
findTop(options: FindListOptions = {}) {
|
||||
let currentPage = options.currentPage;
|
||||
let elementsPerPage = options.elementsPerPage;
|
||||
if (currentPage === undefined) {
|
||||
currentPage = 1
|
||||
}
|
||||
elementsPerPage = 5;
|
||||
|
||||
const startPageIndex = (currentPage - 1) * elementsPerPage;
|
||||
let endPageIndex = (currentPage * elementsPerPage);
|
||||
if (endPageIndex > topCommList.length) {
|
||||
endPageIndex = topCommList.length;
|
||||
}
|
||||
return createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), topCommList.slice(startPageIndex, endPageIndex)));
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
SharedModule,
|
||||
RouterTestingModule.withRoutes([]),
|
||||
NgbModule.forRoot(),
|
||||
NoopAnimationsModule
|
||||
],
|
||||
declarations: [TopLevelCommunityListComponent],
|
||||
providers: [
|
||||
{ provide: CommunityDataService, useValue: communityDataServiceStub },
|
||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
|
||||
{ provide: SelectableListService, useValue: {} },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TopLevelCommunityListComponent);
|
||||
comp = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
|
||||
});
|
||||
|
||||
it('should display a list of top-communities', () => {
|
||||
const subComList = fixture.debugElement.queryAll(By.css('li'));
|
||||
|
||||
expect(subComList.length).toEqual(5);
|
||||
expect(subComList[0].nativeElement.textContent).toContain('TopCommunity 1');
|
||||
expect(subComList[1].nativeElement.textContent).toContain('TopCommunity 2');
|
||||
expect(subComList[2].nativeElement.textContent).toContain('TopCommunity 3');
|
||||
expect(subComList[3].nativeElement.textContent).toContain('TopCommunity 4');
|
||||
expect(subComList[4].nativeElement.textContent).toContain('TopCommunity 5');
|
||||
});
|
||||
|
||||
it('should update list of top-communities on pagination change', () => {
|
||||
const pagination = Object.create({
|
||||
pagination:{
|
||||
id: comp.pageId,
|
||||
currentPage: 2,
|
||||
pageSize: 5
|
||||
},
|
||||
sort: {
|
||||
field: 'dc.title',
|
||||
direction: 'ASC'
|
||||
}
|
||||
});
|
||||
comp.onPaginationChange(pagination);
|
||||
fixture.detectChanges();
|
||||
|
||||
const collList = fixture.debugElement.queryAll(By.css('li'));
|
||||
expect(collList.length).toEqual(2);
|
||||
expect(collList[0].nativeElement.textContent).toContain('TopCommunity 6');
|
||||
expect(collList[1].nativeElement.textContent).toContain('TopCommunity 7');
|
||||
});
|
||||
});
|
@@ -1,15 +1,15 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { PaginatedList } from '../../core/data/paginated-list';
|
||||
|
||||
import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Community } from '../../core/shared/community.model';
|
||||
|
||||
import { fadeInOut } from '../../shared/animations/fade';
|
||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
/**
|
||||
* this component renders the Top-Level Community list
|
||||
@@ -33,6 +33,11 @@ export class TopLevelCommunityListComponent implements OnInit {
|
||||
*/
|
||||
config: PaginationComponentOptions;
|
||||
|
||||
/**
|
||||
* The pagination id
|
||||
*/
|
||||
pageId = 'top-level-pagination';
|
||||
|
||||
/**
|
||||
* The sorting configuration
|
||||
*/
|
||||
@@ -40,7 +45,7 @@ export class TopLevelCommunityListComponent implements OnInit {
|
||||
|
||||
constructor(private cds: CommunityDataService) {
|
||||
this.config = new PaginationComponentOptions();
|
||||
this.config.id = 'top-level-pagination';
|
||||
this.config.id = this.pageId;
|
||||
this.config.pageSize = 5;
|
||||
this.config.currentPage = 1;
|
||||
this.sortConfig = new SortOptions('dc.title', SortDirection.ASC);
|
||||
@@ -55,10 +60,10 @@ export class TopLevelCommunityListComponent implements OnInit {
|
||||
* @param event The new pagination data
|
||||
*/
|
||||
onPaginationChange(event) {
|
||||
this.config.currentPage = event.page;
|
||||
this.config.pageSize = event.pageSize;
|
||||
this.sortConfig.field = event.sortField;
|
||||
this.sortConfig.direction = event.sortDirection;
|
||||
this.config.currentPage = event.pagination.currentPage;
|
||||
this.config.pageSize = event.pagination.pageSize;
|
||||
this.sortConfig.field = event.sort.field;
|
||||
this.sortConfig.direction = event.sort.direction;
|
||||
this.updatePage();
|
||||
}
|
||||
|
||||
|
@@ -46,13 +46,13 @@ export class UploadBitstreamComponent implements OnInit, OnDestroy {
|
||||
* The uploader configuration options
|
||||
* @type {UploaderOptions}
|
||||
*/
|
||||
uploadFilesOptions: UploaderOptions = {
|
||||
uploadFilesOptions: UploaderOptions = Object.assign(new UploaderOptions(), {
|
||||
// URL needs to contain something to not produce any errors. This will be replaced once a bundle has been selected.
|
||||
url: 'placeholder',
|
||||
authToken: null,
|
||||
disableMultipart: false,
|
||||
itemAlias: null
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Array to track all subscriptions and unsubscribe them onDestroy
|
||||
|
@@ -20,13 +20,13 @@ import { Item } from '../../../core/shared/item.model';
|
||||
import { RemoteData } from '../../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||
import { Bundle } from '../../../core/shared/bundle.model';
|
||||
import { PaginatedSearchOptions } from '../../../+search-page/paginated-search-options.model';
|
||||
import { FieldUpdate, FieldUpdates } from '../../../core/data/object-updates/object-updates.reducer';
|
||||
import { Bitstream } from '../../../core/shared/bitstream.model';
|
||||
import { FieldChangeType } from '../../../core/data/object-updates/object-updates.actions';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { MoveOperation } from 'fast-json-patch/lib/core';
|
||||
import { BundleDataService } from '../../../core/data/bundle-data.service';
|
||||
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-bitstreams',
|
||||
|
@@ -11,10 +11,10 @@ import { CdkDragDrop } from '@angular/cdk/drag-drop';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||
import { BundleDataService } from '../../../../core/data/bundle-data.service';
|
||||
import { PaginatedSearchOptions } from '../../../../+search-page/paginated-search-options.model';
|
||||
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
|
||||
import { combineLatest as observableCombineLatest } from 'rxjs';
|
||||
import { hasNoValue } from '../../../../shared/empty.util';
|
||||
import { PaginatedSearchOptions } from '../../../../shared/search/paginated-search-options.model';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-item-edit-bitstream-bundle',
|
||||
|
@@ -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',
|
||||
|
@@ -97,7 +97,7 @@ describe('EditRelationshipListComponent', () => {
|
||||
|
||||
relationshipService = jasmine.createSpyObj('relationshipService',
|
||||
{
|
||||
getRelatedItemsByLabel: observableOf([author1, author2]),
|
||||
getRelatedItemsByLabel: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [author1, author2]))),
|
||||
}
|
||||
);
|
||||
|
||||
|
@@ -4,8 +4,10 @@ import { Observable } from 'rxjs/internal/Observable';
|
||||
import { FieldUpdate, FieldUpdates } from '../../../../core/data/object-updates/object-updates.reducer';
|
||||
import { RelationshipService } from '../../../../core/data/relationship.service';
|
||||
import { Item } from '../../../../core/shared/item.model';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { hasValue } from '../../../../shared/empty.util';
|
||||
import { RemoteData } from '../../../../core/data/remote-data';
|
||||
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-edit-relationship-list',
|
||||
@@ -63,7 +65,7 @@ export class EditRelationshipListComponent implements OnInit, OnChanges {
|
||||
* Transform the item's relationships of a specific type into related items
|
||||
* @param label The relationship type's label
|
||||
*/
|
||||
public getRelatedItemsByLabel(label: string): Observable<Item[]> {
|
||||
public getRelatedItemsByLabel(label: string): Observable<RemoteData<PaginatedList<Item>>> {
|
||||
return this.relationshipService.getRelatedItemsByLabel(this.item, label);
|
||||
}
|
||||
|
||||
@@ -73,7 +75,7 @@ export class EditRelationshipListComponent implements OnInit, OnChanges {
|
||||
*/
|
||||
public getUpdatesByLabel(label: string): Observable<FieldUpdates> {
|
||||
return this.getRelatedItemsByLabel(label).pipe(
|
||||
switchMap((items: Item[]) => this.objectUpdatesService.getFieldUpdatesExclusive(this.url, items))
|
||||
switchMap((itemsRD) => this.objectUpdatesService.getFieldUpdatesExclusive(this.url, itemsRD.payload.page))
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<div class="row" *ngIf="item">
|
||||
<div class="col-10 relationship">
|
||||
<ds-item-type-switcher [object]="item" [viewMode]="viewMode"></ds-item-type-switcher>
|
||||
<ds-listable-object-component-loader [object]="item" [viewMode]="viewMode"></ds-listable-object-component-loader>
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<div class="btn-group relationship-action-buttons">
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user