diff --git a/.travis.yml b/.travis.yml
index 403a10b770..901dee8186 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,10 +1,44 @@
sudo: required
dist: trusty
+
+env:
+ # Install the latest docker-compose version for ci testing.
+ # The default installation in travis is not compatible with the latest docker-compose file version.
+ COMPOSE_VERSION: 1.24.1
+ # The ci step will test the dspace-angular code against DSpace REST.
+ # Direct that step to utilize a DSpace REST service that has been started in docker.
+ DSPACE_REST_HOST: localhost
+ DSPACE_REST_PORT: 8080
+ DSPACE_REST_NAMESPACE: '/server/api'
+ DSPACE_REST_SSL: false
+
+before_install:
+ # Docker Compose Install
+ - curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
+ - chmod +x docker-compose
+ - sudo mv docker-compose /usr/local/bin
+
+install:
+ # Start up DSpace 7 using the entities database dump
+ - docker-compose -f ./docker/docker-compose-travis.yml up -d
+ # Use the dspace-cli image to populate the assetstore. Trigger a discovery and oai update
+ - docker-compose -f ./docker/cli.yml -f ./docker/cli.assetstore.yml run --rm dspace-cli
+ - travis_retry yarn install
+
+before_script:
+ # The following line could be enabled to verify that the rest server is responding.
+ # Currently, "yarn run build" takes enough time to run to allow the service to be available
+ #- curl http://localhost:8080/
+
+after_script:
+ - docker-compose -f ./docker/docker-compose-travis.yml down
+
addons:
apt:
sources:
- google-chrome
packages:
+ - dpkg
- google-chrome-stable
language: node_js
@@ -18,9 +52,6 @@ cache:
bundler_args: --retry 5
-install:
- - travis_retry yarn install
-
script:
# Use Chromium instead of Chrome.
- export CHROME_BIN=chromium-browser
diff --git a/README.md b/README.md
index 1b3ed9b7cb..a9f2b0861b 100644
--- a/README.md
+++ b/README.md
@@ -131,6 +131,11 @@ yarn run clean:prod
yarn run clean:dist
```
+Running the application with Docker
+-----------------------------------
+See [Docker Runtime Options](docker/README.md)
+
+
Testing
-------
diff --git a/config/environment.test.js b/config/environment.test.js
index f4d625303f..6897e29aa7 100644
--- a/config/environment.test.js
+++ b/config/environment.test.js
@@ -1,3 +1,4 @@
+// This configuration is currently only being used for unit tests, end-to-end tests use environment.dev.ts
module.exports = {
};
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 0000000000..f7b4b04848
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,79 @@
+# Docker Compose files
+
+## docker directory
+- docker-compose.yml
+ - Starts DSpace Angular with Docker Compose from the current branch. This file assumes that a DSpace 7 REST instance will also be started in Docker.
+- docker-compose-rest.yml
+ - Runs a published instance of the DSpace 7 REST API - persists data in Docker volumes
+- docker-compose-travis.yml
+ - Runs a published instance of the DSpace 7 REST API for CI testing. The database is re-populated from a SQL dump on each startup.
+- cli.yml
+ - Docker compose file that provides a DSpace CLI container to work with a running DSpace REST container.
+- cli.assetstore.yml
+ - Docker compose file that will download and install data into a DSpace REST assetstore. This script points to a default dataset that will be utilized for CI testing.
+- environment.dev.js
+ - Environment file for running DSpace Angular in Docker
+- local.cfg
+ - Environment file for running the DSpace 7 REST API in Docker.
+
+
+## To refresh / pull DSpace images from Dockerhub
+```
+docker-compose -f docker/docker-compose.yml pull
+```
+
+## To build DSpace images using code in your branch
+```
+docker-compose -f docker/docker-compose.yml build
+```
+
+## To start DSpace (REST and Angular) from your branch
+
+```
+docker-compose -p d7 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml up -d
+```
+
+## Run DSpace REST and DSpace Angular from local branches.
+_The system will be started in 2 steps. Each step shares the same docker network._
+
+From DSpace/DSpace (build as needed)
+```
+docker-compose -p d7 up -d
+```
+
+From DSpace/DSpace-angular
+```
+docker-compose -p d7 -f docker/docker-compose.yml up -d
+```
+
+## Ingest test data from AIPDIR
+
+Create an administrator
+```
+docker-compose -p d7 -f docker/cli.yml run --rm dspace-cli create-administrator -e test@test.edu -f admin -l user -p admin -c en
+```
+
+Load content from AIP files
+```
+docker-compose -p d7 -f docker/cli.yml -f ./docker/cli.ingest.yml run --rm dspace-cli
+```
+
+## Alternative Ingest - Use Entities dataset
+_Delete your docker volumes or use a unique project (-p) name_
+
+Start DSpace with Database Content from a database dump
+```
+docker-compose -p d7 -f docker/docker-compose.yml -f docker/docker-compose-rest.yml -f docker/db.entities.yml up -d
+```
+
+Load assetstore content and trigger a re-index of the repository
+```
+docker-compose -p d7 -f docker/cli.yml -f docker/cli.assetstore.yml run --rm dspace-cli
+```
+
+## End to end testing of the rest api (runs in travis).
+_In this instance, only the REST api runs in Docker using the Entities dataset. Travis will perform CI testing of Angular using Node to drive the tests._
+
+```
+docker-compose -p d7ci -f docker/docker-compose-travis.yml up -d
+```
diff --git a/docker/cli.assetstore.yml b/docker/cli.assetstore.yml
new file mode 100644
index 0000000000..075c494a6c
--- /dev/null
+++ b/docker/cli.assetstore.yml
@@ -0,0 +1,23 @@
+version: "3.7"
+
+networks:
+ dspacenet:
+
+services:
+ dspace-cli:
+ networks:
+ dspacenet: {}
+ environment:
+ - LOADASSETS=https://www.dropbox.com/s/zv7lj8j2lp3egjs/assetstore.tar.gz?dl=1
+ entrypoint:
+ - /bin/bash
+ - '-c'
+ - |
+ if [ ! -z $${LOADASSETS} ]
+ then
+ curl $${LOADASSETS} -L -s --output /tmp/assetstore.tar.gz
+ cd /dspace
+ tar xvfz /tmp/assetstore.tar.gz
+ fi
+
+ /dspace/bin/dspace index-discovery
diff --git a/docker/cli.ingest.yml b/docker/cli.ingest.yml
new file mode 100644
index 0000000000..f5ec7eb90d
--- /dev/null
+++ b/docker/cli.ingest.yml
@@ -0,0 +1,32 @@
+#
+# The contents of this file are subject to the license and copyright
+# detailed in the LICENSE and NOTICE files at the root of the source
+# tree and available online at
+#
+# http://www.dspace.org/license/
+#
+
+version: "3.7"
+
+services:
+ dspace-cli:
+ environment:
+ - AIPZIP=https://github.com/DSpace-Labs/AIP-Files/raw/master/dogAndReport.zip
+ - ADMIN_EMAIL=test@test.edu
+ - AIPDIR=/tmp/aip-dir
+ entrypoint:
+ - /bin/bash
+ - '-c'
+ - |
+ rm -rf $${AIPDIR}
+ mkdir $${AIPDIR} /dspace/upload
+ cd $${AIPDIR}
+ pwd
+ curl $${AIPZIP} -L -s --output aip.zip
+ unzip aip.zip
+ cd $${AIPDIR}
+
+ /dspace/bin/dspace packager -r -a -t AIP -e $${ADMIN_EMAIL} -f -u SITE*.zip
+ /dspace/bin/dspace database update-sequences
+
+ /dspace/bin/dspace index-discovery
diff --git a/docker/cli.yml b/docker/cli.yml
new file mode 100644
index 0000000000..ea5e3e0595
--- /dev/null
+++ b/docker/cli.yml
@@ -0,0 +1,22 @@
+version: "3.7"
+
+services:
+ dspace-cli:
+ image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-dspace-7_x}"
+ container_name: dspace-cli
+ #environment:
+ volumes:
+ - "assetstore:/dspace/assetstore"
+ - "./local.cfg:/dspace/config/local.cfg"
+ entrypoint: /dspace/bin/dspace
+ command: help
+ networks:
+ - dspacenet
+ tty: true
+ stdin_open: true
+
+volumes:
+ assetstore:
+
+networks:
+ dspacenet:
diff --git a/docker/db.entities.yml b/docker/db.entities.yml
new file mode 100644
index 0000000000..91d96bd72b
--- /dev/null
+++ b/docker/db.entities.yml
@@ -0,0 +1,16 @@
+#
+# The contents of this file are subject to the license and copyright
+# detailed in the LICENSE and NOTICE files at the root of the source
+# tree and available online at
+#
+# http://www.dspace.org/license/
+#
+
+version: "3.7"
+
+services:
+ dspacedb:
+ image: dspace/dspace-postgres-pgcrypto:loadsql
+ environment:
+ # Double underbars in env names will be replaced with periods for apache commons
+ - LOADSQL=https://www.dropbox.com/s/xh3ack0vg0922p2/configurable-entities-2019-05-08.sql?dl=1
diff --git a/docker/docker-compose-rest.yml b/docker/docker-compose-rest.yml
new file mode 100644
index 0000000000..222557bc81
--- /dev/null
+++ b/docker/docker-compose-rest.yml
@@ -0,0 +1,59 @@
+networks:
+ dspacenet:
+services:
+ dspace:
+ container_name: dspace
+ depends_on:
+ - dspacedb
+ image: dspace/dspace:dspace-7_x-jdk8-test
+ networks:
+ dspacenet:
+ ports:
+ - published: 8080
+ target: 8080
+ stdin_open: true
+ tty: true
+ volumes:
+ - assetstore:/dspace/assetstore
+ - "./local.cfg:/dspace/config/local.cfg"
+ # Ensure that the database is ready before starting tomcat
+ entrypoint:
+ - /bin/bash
+ - '-c'
+ - |
+ /dspace/bin/dspace database migrate
+ catalina.sh run
+ dspacedb:
+ container_name: dspacedb
+ image: dspace/dspace-postgres-pgcrypto
+ environment:
+ PGDATA: /pgdata
+ networks:
+ dspacenet:
+ stdin_open: true
+ tty: true
+ volumes:
+ - pgdata:/pgdata
+ dspacesolr:
+ container_name: dspacesolr
+ image: dspace/dspace-solr
+ networks:
+ dspacenet:
+ ports:
+ - published: 8983
+ target: 8983
+ stdin_open: true
+ tty: true
+ volumes:
+ - solr_authority:/opt/solr/server/solr/authority/data
+ - solr_oai:/opt/solr/server/solr/oai/data
+ - solr_search:/opt/solr/server/solr/search/data
+ - solr_statistics:/opt/solr/server/solr/statistics/data
+version: '3.7'
+volumes:
+ assetstore:
+ pgdata:
+ solr_authority:
+ solr_oai:
+ solr_search:
+ solr_statistics:
diff --git a/docker/docker-compose-travis.yml b/docker/docker-compose-travis.yml
new file mode 100644
index 0000000000..6ca44e4e47
--- /dev/null
+++ b/docker/docker-compose-travis.yml
@@ -0,0 +1,53 @@
+networks:
+ dspacenet:
+services:
+ dspace:
+ container_name: dspace
+ depends_on:
+ - dspacedb
+ image: dspace/dspace:dspace-7_x-jdk8-test
+ networks:
+ dspacenet:
+ ports:
+ - published: 8080
+ target: 8080
+ stdin_open: true
+ tty: true
+ volumes:
+ - assetstore:/dspace/assetstore
+ - "./local.cfg:/dspace/config/local.cfg"
+ dspacedb:
+ container_name: dspacedb
+ environment:
+ LOADSQL: https://www.dropbox.com/s/xh3ack0vg0922p2/configurable-entities-2019-05-08.sql?dl=1
+ PGDATA: /pgdata
+ image: dspace/dspace-postgres-pgcrypto:loadsql
+ networks:
+ dspacenet:
+ stdin_open: true
+ tty: true
+ volumes:
+ - pgdata:/pgdata
+ dspacesolr:
+ container_name: dspacesolr
+ image: dspace/dspace-solr
+ networks:
+ dspacenet:
+ ports:
+ - published: 8983
+ target: 8983
+ stdin_open: true
+ tty: true
+ volumes:
+ - solr_authority:/opt/solr/server/solr/authority/data
+ - solr_oai:/opt/solr/server/solr/oai/data
+ - solr_search:/opt/solr/server/solr/search/data
+ - solr_statistics:/opt/solr/server/solr/statistics/data
+version: '3.7'
+volumes:
+ assetstore:
+ pgdata:
+ solr_authority:
+ solr_oai:
+ solr_search:
+ solr_statistics:
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
new file mode 100644
index 0000000000..23f0615a1f
--- /dev/null
+++ b/docker/docker-compose.yml
@@ -0,0 +1,26 @@
+version: '3.7'
+networks:
+ dspacenet:
+services:
+ dspace-angular:
+ container_name: dspace-angular
+ environment:
+ DSPACE_HOST: dspace-angular
+ DSPACE_NAMESPACE: /
+ DSPACE_PORT: '3000'
+ DSPACE_SSL: "false"
+ image: dspace/dspace-angular:latest
+ build:
+ context: ..
+ dockerfile: Dockerfile
+ networks:
+ dspacenet:
+ ports:
+ - published: 3000
+ target: 3000
+ - published: 9876
+ target: 9876
+ stdin_open: true
+ tty: true
+ volumes:
+ - ./environment.dev.js:/app/config/environment.dev.js
diff --git a/docker/environment.dev.js b/docker/environment.dev.js
new file mode 100644
index 0000000000..f88506012f
--- /dev/null
+++ b/docker/environment.dev.js
@@ -0,0 +1,16 @@
+/*
+ * The contents of this file are subject to the license and copyright
+ * detailed in the LICENSE and NOTICE files at the root of the source
+ * tree and available online at
+ *
+ * http://www.dspace.org/license/
+ */
+module.exports = {
+ rest: {
+ ssl: false,
+ host: 'localhost',
+ port: 8080,
+ // NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
+ nameSpace: '/server/api'
+ }
+};
diff --git a/docker/local.cfg b/docker/local.cfg
new file mode 100644
index 0000000000..6692b13658
--- /dev/null
+++ b/docker/local.cfg
@@ -0,0 +1,6 @@
+dspace.dir=/dspace
+db.url=jdbc:postgresql://dspacedb:5432/dspace
+dspace.hostname=dspace
+dspace.baseUrl=http://localhost:8080
+dspace.name=DSpace Started with Docker Compose
+solr.server=http://dspacesolr:8983/solr
diff --git a/package.json b/package.json
index 6340f1cfa2..cdad082215 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,9 @@
"engines": {
"node": "8.* || >= 10.*"
},
+ "resolutions": {
+ "set-value": ">= 2.0.1"
+ },
"scripts": {
"global": "npm install -g @angular/cli marked node-gyp nodemon node-nightly npm-check-updates npm-run-all rimraf typescript ts-node typedoc webpack webpack-bundle-analyzer pm2 rollup",
"clean:coverage": "rimraf coverage",
@@ -22,10 +25,10 @@
"clean:prod": "yarn run clean:coverage && yarn run clean:doc && yarn run clean:dist && yarn run clean:log && yarn run clean:json && yarn run clean:bld",
"clean": "yarn run clean:prod && yarn run clean:node",
"prebuild": "yarn run clean:bld && yarn run clean:dist",
- "prebuild:aot": "yarn run prebuild",
+ "prebuild:ci": "yarn run prebuild",
"prebuild:prod": "yarn run prebuild",
"build": "node ./scripts/webpack.js --progress --mode development",
- "build:aot": "yarn run syncbuilddir && node ./scripts/webpack.js --env.aot --env.server --mode development && node ./scripts/webpack.js --env.aot --env.client --mode development",
+ "build:ci": "yarn run syncbuilddir && node ./scripts/webpack.js --env.aot --env.server --mode development && node ./scripts/webpack.js --env.aot --env.client --mode development",
"build:prod": "yarn run syncbuilddir && node ./scripts/webpack.js --env.aot --env.server --mode production && node ./scripts/webpack.js --env.aot --env.client --mode production",
"postbuild:prod": "yarn run rollup",
"rollup": "rollup -c rollup.config.js",
@@ -51,10 +54,13 @@
"debug:server": "node-nightly --inspect --debug-brk dist/server.js",
"debug:build": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --mode development",
"debug:build:prod": "node-nightly --inspect --debug-brk node_modules/webpack/bin/webpack.js --env.aot --env.client --env.server --mode production",
- "ci": "yarn run lint && yarn run build:aot && yarn run test:headless",
+ "ci": "yarn run lint && yarn run build:ci && yarn run test:headless && npm-run-all -p -r server e2e",
"protractor": "node node_modules/protractor/bin/protractor",
"pree2e": "yarn run webdriver:update",
"e2e": "yarn run protractor",
+ "pretest": "yarn run clean:bld",
+ "pretest:headless": "yarn run pretest",
+ "pretest:watch": "yarn run pretest",
"test": "karma start --single-run",
"test:headless": "karma start --single-run --browsers ChromeHeadless",
"test:watch": "karma start --no-single-run --auto-watch",
@@ -109,6 +115,7 @@
"https": "1.0.0",
"js-cookie": "2.2.0",
"js.clone": "0.0.3",
+ "json5": "^2.1.0",
"jsonschema": "1.2.2",
"jwt-decode": "^2.2.0",
"methods": "1.1.2",
@@ -155,6 +162,7 @@
"@types/hammerjs": "2.0.35",
"@types/jasmine": "^2.8.6",
"@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",
diff --git a/resources/i18n/cs.json b/resources/i18n/cs.json5
similarity index 97%
rename from resources/i18n/cs.json
rename to resources/i18n/cs.json5
index d658030b6b..bd4363409b 100644
--- a/resources/i18n/cs.json
+++ b/resources/i18n/cs.json5
@@ -2,6 +2,7 @@
"404.help": "Nepodařilo se najít stránku, kterou hledáte. Je možné, že stránka byla přesunuta nebo smazána. Pomocí tlačítka níže můžete přejít na domovskou stránku. ",
"404.link.home-page": "Přejít na domovskou stránku",
"404.page-not-found": "stránka nenalezena",
+
"admin.registries.bitstream-formats.description": "Tento seznam formátů souborů poskytuje informace o známých formátech a o úrovni jejich podpory.",
"admin.registries.bitstream-formats.formats.no-items": "Žádné formáty souborů.",
"admin.registries.bitstream-formats.formats.table.internal": "interní",
@@ -13,29 +14,36 @@
"admin.registries.bitstream-formats.formats.table.supportLevel.head": "Úroveň podpory",
"admin.registries.bitstream-formats.head": "Registr formátů souborů",
"admin.registries.bitstream-formats.title": "DSpace Angular :: Registr formátů souborů",
- "admin.registries.metadata.description": "Registr metadat je seznam všech metadatových polí dostupných v repozitáři. Tyto pole mohou být rozdělena do více schémat. DSpace však vyžaduje použití schématu kvalifikový Dublin Core.",
+
+ "admin.registries.metadata.description": "Registr metadat je seznam všech metadatových polí dostupných v repozitáři. Tyto pole mohou být rozdělena do více schémat. DSpace však vyžaduje použití schématu Kvalifikovaný Dublin Core.",
"admin.registries.metadata.head": "Registr metadat",
"admin.registries.metadata.schemas.no-items": "Žádná schémata metadat.",
"admin.registries.metadata.schemas.table.id": "ID",
"admin.registries.metadata.schemas.table.name": "Název",
"admin.registries.metadata.schemas.table.namespace": "Jmenný prostor",
"admin.registries.metadata.title": "DSpace Angular :: Registr metadat",
+
"admin.registries.schema.description": "Toto je schéma metadat pro „{{namespace}}“.",
"admin.registries.schema.fields.head": "Pole schématu metadat",
"admin.registries.schema.fields.no-items": "Žádná metadatová pole.",
"admin.registries.schema.fields.table.field": "Pole",
"admin.registries.schema.fields.table.scopenote": "Poznámka o rozsahu",
- "admin.registries.schema.head": "Metadata Schema",
+ "admin.registries.schema.head": "Schéma metadat",
"admin.registries.schema.title": "DSpace Angular :: Registr schémat metadat",
+
"auth.errors.invalid-user": "Neplatná e-mailová adresa nebo heslo.",
"auth.messages.expired": "Vaše relace vypršela. Prosím, znova se přihlaste.",
+
"browse.title": "Prohlížíte {{ collection }} dle {{ field }} {{ value }}",
+
"collection.page.browse.recent.head": "Poslední příspěvky",
"collection.page.license": "Licence",
"collection.page.news": "Novinky",
+
"community.page.license": "Licence",
"community.page.news": "Novinky",
"community.sub-collection-list.head": "Kolekce v této komunitě",
+
"error.browse-by": "Chyba během stahování záznamů",
"error.collection": "Chyba během stahování kolekce",
"error.community": "Chyba během stahování komunity",
@@ -48,9 +56,11 @@
"error.top-level-communities": "Chyba během stahování komunit nejvyšší úrovně",
"error.validation.license.notgranted": "Pro dokončení zaslání Musíte udělit licenci. Pokud v tuto chvíli tuto licenci nemůžete udělit, můžete svou práci uložit a později se k svému příspěveku vrátit nebo jej smazat.",
"error.validation.pattern": "Tento vstup je omezen dle vzoru: {{ pattern }}.",
+
"footer.copyright": "copyright © 2002-{{ year }}",
"footer.link.dspace": "software DSpace",
"footer.link.duraspace": "DuraSpace",
+
"form.cancel": "Zrušit",
"form.first-name": "Křestní jméno",
"form.group-collapse": "Sbalit",
@@ -64,11 +74,13 @@
"form.remove": "Smazat",
"form.search": "Hledat",
"form.submit": "Odeslat",
+
"home.description": "",
"home.title": "DSpace Angular :: Domů",
"home.top-level-communities.head": "Komunity v DSpace",
"home.top-level-communities.help": "Vybráním komunity můžete prohlížet její kolekce.",
- "item.page.abstract": "Abstract",
+
+ "item.page.abstract": "Abstrakt",
"item.page.author": "Autor",
"item.page.collections": "Kolekce",
"item.page.date": "Datum",
@@ -81,6 +93,7 @@
"item.page.link.full": "Úplný záznam",
"item.page.link.simple": "Minimální záznam",
"item.page.uri": "URI",
+
"loading.browse-by": "Načítají se záznamy...",
"loading.collection": "Načítá se kolekce...",
"loading.community": "Načítá se komunita...",
@@ -91,6 +104,7 @@
"loading.search-results": "Načítají se výsledky hledání...",
"loading.sub-collections": "Načítají se subkolekce...",
"loading.top-level-communities": "Načítají se komunity nejvyšší úrovně...",
+
"login.form.email": "E-mailová adresa",
"login.form.forgot-password": "Zapomněli jste své heslo?",
"login.form.header": "Prosím, přihlaste se do DSpace",
@@ -98,22 +112,29 @@
"login.form.password": "Heslo",
"login.form.submit": "Přihlásit se",
"login.title": "Přihlásit se",
+
"logout.form.header": "Odhlásit se z DSpace",
"logout.form.submit": "Odhlásit se",
"logout.title": "Odhlásit se",
+
"nav.home": "Domů",
"nav.login": "Přihlásit se",
"nav.logout": "Odhlásit se",
+
"pagination.results-per-page": "Výsledků na stránku",
"pagination.showing.detail": "{{ range }} z {{ total }}",
"pagination.showing.label": "Zobrazují se záznamy ",
"pagination.sort-direction": "Seřazení",
+
"search.description": "",
+ "search.title": "DSpace Angular :: Hledat",
+
"search.filters.applied.f.author": "Autor",
"search.filters.applied.f.dateIssued.max": "Do data",
"search.filters.applied.f.dateIssued.min": "Od data",
"search.filters.applied.f.has_content_in_original_bundle": "Má soubory",
"search.filters.applied.f.subject": "Předmět",
+
"search.filters.filter.author.head": "Autor",
"search.filters.filter.author.placeholder": "Jméno autora",
"search.filters.filter.dateIssued.head": "Datum",
@@ -126,12 +147,16 @@
"search.filters.filter.show-more": "Zobrazit více",
"search.filters.filter.subject.head": "Předmět",
"search.filters.filter.subject.placeholder": "Předmět",
+
"search.filters.head": "Filtry",
"search.filters.reset": "Obnovit filtry",
+
"search.form.search": "Hledat",
"search.form.search_dspace": "Hledat v DSpace",
+
"search.results.head": "Výsledky hledání",
"search.results.no-results": "Nebyli nalezeny žádné výsledky",
+
"search.sidebar.close": "Zpět na výsledky",
"search.sidebar.filters.title": "Filtry",
"search.sidebar.open": "Vyhledávací nástroje",
@@ -139,11 +164,13 @@
"search.sidebar.settings.rpp": "Výsledků na stránku",
"search.sidebar.settings.sort-by": "Řadit dle",
"search.sidebar.settings.title": "Nastavení",
- "search.title": "DSpace Angular :: Hledat",
+
"search.view-switch.show-grid": "Zobrazit mřížku",
"search.view-switch.show-list": "Zobrazit seznam",
+
"sorting.dc.title.ASC": "Název vzestupně",
"sorting.dc.title.DESC": "Název sestupně",
"sorting.score.DESC": "Relevance",
- "title": "DSpace"
+
+ "title": "DSpace",
}
diff --git a/resources/i18n/de.json b/resources/i18n/de.json5
similarity index 99%
rename from resources/i18n/de.json
rename to resources/i18n/de.json5
index d184e7d091..29e3073592 100644
--- a/resources/i18n/de.json
+++ b/resources/i18n/de.json5
@@ -2,6 +2,7 @@
"404.help": "Die Seite, die Sie aufrufen wollten, konnte nicht gefunden werden. Sie könnte verschoben oder gelöscht worden sein. Mit dem Link unten kommen Sie zurück zur Startseite. ",
"404.link.home-page": "Zurück zur Startseite",
"404.page-not-found": "Seite nicht gefunden",
+
"admin.registries.bitstream-formats.description": "Diese Liste enhtält die in diesem Repositorium zulässigen Dateiformate und den jeweiligen Unterstützungsgrad.",
"admin.registries.bitstream-formats.formats.no-items": "Es gibt keine Formate in dieser Referenzliste.",
"admin.registries.bitstream-formats.formats.table.internal": "intern",
@@ -13,6 +14,7 @@
"admin.registries.bitstream-formats.formats.table.supportLevel.head": "Unterstützungsgrad",
"admin.registries.bitstream-formats.head": "Referenzliste der Dateiformate",
"admin.registries.bitstream-formats.title": "DSpace Angular :: Referenzliste der Dateiformate",
+
"admin.registries.metadata.description": "Die Metadatenreferenzliste beinhaltet alle Metadatenfelder, die zur Verfügung stehen. Die Felder können in unterschiedlichen Schemata enthalten sein. Nichtsdestotrotz benötigt DSpace mindestens qualifiziertes Dublin Core.",
"admin.registries.metadata.head": "Metadatenreferenzliste",
"admin.registries.metadata.schemas.no-items": "Es gbit keine Metadatenschemata.",
@@ -20,6 +22,7 @@
"admin.registries.metadata.schemas.table.name": "Name",
"admin.registries.metadata.schemas.table.namespace": "Namensraum",
"admin.registries.metadata.title": "DSpace Angular :: Metadatenreferenzliste",
+
"admin.registries.schema.description": "Dies ist das Metadatenschema für \"{{namespace}}\".",
"admin.registries.schema.fields.head": "Felder in diesem Schema",
"admin.registries.schema.fields.no-items": "Es gibt keine Felder in diesem Schema.",
@@ -27,15 +30,19 @@
"admin.registries.schema.fields.table.scopenote": "Gültigkeitsbereich",
"admin.registries.schema.head": "Metadatenschemata",
"admin.registries.schema.title": "DSpace Angular :: Referenzliste der Metadatenschemata",
+
"auth.errors.invalid-user": "Ungültige E-Mail-Adresse oder Passwort.",
"auth.messages.expired": "Ihre Sitzung ist abgelaufen, bitte melden Sie sich erneut an.",
+
"browse.title": "Anzeige {{ collection }} nach {{ field }} {{ value }}",
"collection.page.browse.recent.head": "Aktuellste Veröffentlichungen",
"collection.page.license": "Lizenz",
"collection.page.news": "Neuigkeiten",
+
"community.page.license": "Lizenz",
"community.page.news": "Neuigkeiten",
"community.sub-collection-list.head": "Sammlungen in diesem Bereich",
+
"error.browse-by": "Fehler beim Laden der Ressourcen",
"error.collection": "Fehler beim Laden der Sammlung.",
"error.community": "Fehler beim Laden des Bereiches.",
@@ -48,9 +55,11 @@
"error.top-level-communities": "Fehler beim Laden der Hauptbereiche.",
"error.validation.license.notgranted": "Sie müssen der Lizenz zustimmen, um die Ressource einzureichen. Wenn dies zur Zeit nicht geht, können Sie die Einreichung speichern und später wiederaufnehmen oder löschen.",
"error.validation.pattern": "Die Eingabe kann nur folgendes Muster haben: {{ pattern }}.",
+
"footer.copyright": "Copyright © 2002-{{ year }}",
"footer.link.dspace": "DSpace Software",
"footer.link.duraspace": "DuraSpace",
+
"form.cancel": "Abbrechen",
"form.first-name": "Vorname",
"form.group-collapse": "Weniger",
@@ -64,10 +73,12 @@
"form.remove": "Löschen",
"form.search": "Suchen",
"form.submit": "Los",
+
"home.description": "",
"home.title": "DSpace Angular :: Startseite",
"home.top-level-communities.head": "Bereiche in DSpace",
"home.top-level-communities.help": "Wählen Sie einen Bereich, um seine Sammlungen einzusehen.",
+
"item.page.abstract": "Kurzfassung",
"item.page.author": "Autor",
"item.page.collections": "Sammlungen",
@@ -81,6 +92,7 @@
"item.page.link.full": "Vollanzeige",
"item.page.link.simple": "Kurzanzeige",
"item.page.uri": "URI",
+
"loading.browse-by": "Die Ressourcen werden geladen ...",
"loading.collection": "Die Sammlung wird geladen ...",
"loading.community": "Der Bereich wird geladen ...",
@@ -91,6 +103,7 @@
"loading.search-results": "Die Suchergebnisse werden geladen ...",
"loading.sub-collections": "Die untergeordneten Sammlungen werden geladen ...",
"loading.top-level-communities": "Die Hauptbereiche werden geladen ...",
+
"login.form.email": "E-Mail-Adresse",
"login.form.forgot-password": "Haben Sie Ihr Passwort vergessen?",
"login.form.header": "Bitte Loggen Sie sich ein.",
@@ -98,22 +111,29 @@
"login.form.password": "Passwort",
"login.form.submit": "Einloggen",
"login.title": "Einloggen",
+
"logout.form.header": "Ausloggen aus DSpace",
"logout.form.submit": "Ausloggen",
"logout.title": "Ausloggen",
+
"nav.home": "Zur Startseite",
"nav.login": "Anmelden",
"nav.logout": "Abmelden",
+
"pagination.results-per-page": "Ergebnisse pro Seite",
"pagination.showing.detail": "{{ range }} bis {{ total }}",
"pagination.showing.label": "Anzeige der Treffer ",
"pagination.sort-direction": "Sortiermöglichkeiten",
+
"search.description": "",
+ "search.title": "DSpace Angular :: Suche",
+
"search.filters.applied.f.author": "Autor",
"search.filters.applied.f.dateIssued.max": "Enddatum",
"search.filters.applied.f.dateIssued.min": "Anfangsdatum",
"search.filters.applied.f.has_content_in_original_bundle": "Besitzt Dateien",
"search.filters.applied.f.subject": "Thema",
+
"search.filters.filter.author.head": "Autor",
"search.filters.filter.author.placeholder": "Autor",
"search.filters.filter.dateIssued.head": "Datum",
@@ -126,12 +146,16 @@
"search.filters.filter.show-more": "Zeige mehr",
"search.filters.filter.subject.head": "Schlagwort",
"search.filters.filter.subject.placeholder": "Schlagwort",
+
"search.filters.head": "Filter",
"search.filters.reset": "Filter zurücksetzen",
+
"search.form.search": "Suche",
"search.form.search_dspace": "DSpace durchsuchen",
+
"search.results.head": "Suchergebnisse",
"search.results.no-results": "Zu dieser Suche gibt es keine Treffer.",
+
"search.sidebar.close": "Zurück zu den Ergebnissen",
"search.sidebar.filters.title": "Filter",
"search.sidebar.open": "Suchwerkzeuge",
@@ -139,11 +163,13 @@
"search.sidebar.settings.rpp": "Treffer pro Seite",
"search.sidebar.settings.sort-by": "Sortiere nach",
"search.sidebar.settings.title": "Einstellungen",
- "search.title": "DSpace Angular :: Suche",
+
"search.view-switch.show-grid": "Zeige als Raster",
"search.view-switch.show-list": "Zeige als Liste",
+
"sorting.dc.title.ASC": "Titel aufsteigend",
"sorting.dc.title.DESC": "Titel absteigend",
"sorting.score.DESC": "Relevanz",
- "title": "DSpace"
+
+ "title": "DSpace",
}
diff --git a/resources/i18n/en.json b/resources/i18n/en.json5
similarity index 84%
rename from resources/i18n/en.json
rename to resources/i18n/en.json5
index de89469390..80847f5101 100644
--- a/resources/i18n/en.json
+++ b/resources/i18n/en.json5
@@ -2,17 +2,50 @@
"404.help": "We can't find the page you're looking for. The page may have been moved or deleted. You can use the button below to get back to the home page. ",
"404.link.home-page": "Take me to the home page",
"404.page-not-found": "page not found",
+
+ "admin.registries.bitstream-formats.create.failure.content": "An error occurred while creating the new bitstream format.",
+ "admin.registries.bitstream-formats.create.failure.head": "Failure",
+ "admin.registries.bitstream-formats.create.head": "Create Bitstream format",
+ "admin.registries.bitstream-formats.create.new": "Add a new bitstream format",
+ "admin.registries.bitstream-formats.create.success.content": "The new bitstream format was successfully created.",
+ "admin.registries.bitstream-formats.create.success.head": "Success",
+ "admin.registries.bitstream-formats.delete.failure.amount": "Failed to remove {{ amount }} format(s)",
+ "admin.registries.bitstream-formats.delete.failure.head": "Failure",
+ "admin.registries.bitstream-formats.delete.success.amount": "Successfully removed {{ amount }} format(s)",
+ "admin.registries.bitstream-formats.delete.success.head": "Success",
"admin.registries.bitstream-formats.description": "This list of bitstream formats provides information about known formats and their support level.",
- "admin.registries.bitstream-formats.formats.no-items": "No bitstream formats to show.",
- "admin.registries.bitstream-formats.formats.table.internal": "internal",
- "admin.registries.bitstream-formats.formats.table.mimetype": "MIME Type",
- "admin.registries.bitstream-formats.formats.table.name": "Name",
- "admin.registries.bitstream-formats.formats.table.supportLevel.0": "Unknown",
- "admin.registries.bitstream-formats.formats.table.supportLevel.1": "Known",
- "admin.registries.bitstream-formats.formats.table.supportLevel.2": "Support",
- "admin.registries.bitstream-formats.formats.table.supportLevel.head": "Support Level",
+ "admin.registries.bitstream-formats.edit.description.hint": "",
+ "admin.registries.bitstream-formats.edit.description.label": "Description",
+ "admin.registries.bitstream-formats.edit.extensions.hint": "Extensions are file extensions that are used to automatically identify the format of uploaded files. You can enter several extensions for each format.",
+ "admin.registries.bitstream-formats.edit.extensions.label": "File extensions",
+ "admin.registries.bitstream-formats.edit.extensions.placeholder": "Enter a file extenstion without the dot",
+ "admin.registries.bitstream-formats.edit.failure.content": "An error occurred while editing the bitstream format.",
+ "admin.registries.bitstream-formats.edit.failure.head": "Failure",
+ "admin.registries.bitstream-formats.edit.head": "Bitstream format: {{ format }}",
+ "admin.registries.bitstream-formats.edit.internal.hint": "Formats marked as internal are are hidden from the user, and used for administrative purposes.",
+ "admin.registries.bitstream-formats.edit.internal.label": "Internal",
+ "admin.registries.bitstream-formats.edit.mimetype.hint": "The MIME type associated with this format, does not have to be unique.",
+ "admin.registries.bitstream-formats.edit.mimetype.label": "MIME Type",
+ "admin.registries.bitstream-formats.edit.shortDescription.hint": "A unique name for this format, (e.g. Microsoft Word XP or Microsoft Word 2000)",
+ "admin.registries.bitstream-formats.edit.shortDescription.label": "Name",
+ "admin.registries.bitstream-formats.edit.success.content": "The bitstream format was successfully edited.",
+ "admin.registries.bitstream-formats.edit.success.head": "Success",
+ "admin.registries.bitstream-formats.edit.supportLevel.hint": "The level of support your institution pledges for this format.",
+ "admin.registries.bitstream-formats.edit.supportLevel.label": "Support level",
"admin.registries.bitstream-formats.head": "Bitstream Format Registry",
+ "admin.registries.bitstream-formats.no-items": "No bitstream formats to show.",
+ "admin.registries.bitstream-formats.table.delete": "Delete selected",
+ "admin.registries.bitstream-formats.table.deselect-all": "Deselect all",
+ "admin.registries.bitstream-formats.table.internal": "internal",
+ "admin.registries.bitstream-formats.table.mimetype": "MIME Type",
+ "admin.registries.bitstream-formats.table.name": "Name",
+ "admin.registries.bitstream-formats.table.return": "Return",
+ "admin.registries.bitstream-formats.table.supportLevel.KNOWN": "Known",
+ "admin.registries.bitstream-formats.table.supportLevel.SUPPORTED": "Supported",
+ "admin.registries.bitstream-formats.table.supportLevel.UNKNOWN": "Unknown",
+ "admin.registries.bitstream-formats.table.supportLevel.head": "Support Level",
"admin.registries.bitstream-formats.title": "DSpace Angular :: Bitstream Format Registry",
+
"admin.registries.metadata.description": "The metadata registry maintains a list of all metadata fields available in the repository. These fields may be divided amongst multiple schemas. However, DSpace requires the qualified Dublin Core schema.",
"admin.registries.metadata.form.create": "Create metadata schema",
"admin.registries.metadata.form.edit": "Edit metadata schema",
@@ -25,6 +58,7 @@
"admin.registries.metadata.schemas.table.name": "Name",
"admin.registries.metadata.schemas.table.namespace": "Namespace",
"admin.registries.metadata.title": "DSpace Angular :: Metadata Registry",
+
"admin.registries.schema.description": "This is the metadata schema for \"{{namespace}}\".",
"admin.registries.schema.fields.head": "Schema metadata fields",
"admin.registries.schema.fields.no-items": "No metadata fields to show.",
@@ -49,8 +83,10 @@
"admin.registries.schema.notification.success": "Success",
"admin.registries.schema.return": "Return",
"admin.registries.schema.title": "DSpace Angular :: Metadata Schema Registry",
+
"auth.errors.invalid-user": "Invalid email address or password.",
"auth.messages.expired": "Your session has expired. Please log in again.",
+
"browse.comcol.by.author": "By Author",
"browse.comcol.by.dateissued": "By Issue Date",
"browse.comcol.by.subject": "By Subject",
@@ -81,7 +117,9 @@
"browse.startsWith.type_date": "Or type in a date (year-month):",
"browse.startsWith.type_text": "Or enter first few letters:",
"browse.title": "Browsing {{ collection }} by {{ field }} {{ value }}",
+
"chips.remove": "Remove chip",
+
"collection.create.head": "Create a Collection",
"collection.create.sub-head": "Create a Collection for Community {{ parent }}",
"collection.delete.cancel": "Cancel",
@@ -90,8 +128,28 @@
"collection.delete.notification.fail": "Collection could not be deleted",
"collection.delete.notification.success": "Successfully deleted collection",
"collection.delete.text": "Are you sure you want to delete collection \"{{ dso }}\"",
+
"collection.edit.delete": "Delete this collection",
"collection.edit.head": "Edit Collection",
+
+ "collection.edit.item-mapper.cancel": "Cancel",
+ "collection.edit.item-mapper.collection": "Collection: \"{{name}}\"",
+ "collection.edit.item-mapper.confirm": "Map selected items",
+ "collection.edit.item-mapper.description": "This is the item mapper tool that allows collection administrators to map items from other collections into this collection. You can search for items from other collections and map them, or browse the list of currently mapped items.",
+ "collection.edit.item-mapper.head": "Item Mapper - Map Items from Other Collections",
+ "collection.edit.item-mapper.no-search": "Please enter a query to search",
+ "collection.edit.item-mapper.notifications.map.error.content": "Errors occurred for mapping of {{amount}} items.",
+ "collection.edit.item-mapper.notifications.map.error.head": "Mapping errors",
+ "collection.edit.item-mapper.notifications.map.success.content": "Successfully mapped {{amount}} items.",
+ "collection.edit.item-mapper.notifications.map.success.head": "Mapping completed",
+ "collection.edit.item-mapper.notifications.unmap.error.content": "Errors occurred for removing the mappings of {{amount}} items.",
+ "collection.edit.item-mapper.notifications.unmap.error.head": "Remove mapping errors",
+ "collection.edit.item-mapper.notifications.unmap.success.content": "Successfully removed the mappings of {{amount}} items.",
+ "collection.edit.item-mapper.notifications.unmap.success.head": "Remove mapping completed",
+ "collection.edit.item-mapper.remove": "Remove selected item mappings",
+ "collection.edit.item-mapper.tabs.browse": "Browse mapped items",
+ "collection.edit.item-mapper.tabs.map": "Map new items",
+
"collection.form.abstract": "Short Description",
"collection.form.description": "Introductory text (HTML)",
"collection.form.errors.title.required": "Please enter a collection name",
@@ -100,9 +158,16 @@
"collection.form.rights": "Copyright text (HTML)",
"collection.form.tableofcontents": "News (HTML)",
"collection.form.title": "Name",
+
"collection.page.browse.recent.head": "Recent Submissions",
+ "collection.page.browse.recent.empty": "No items to show",
"collection.page.license": "License",
"collection.page.news": "News",
+
+ "collection.select.confirm": "Confirm selected",
+ "collection.select.empty": "No collections to show",
+ "collection.select.table.title": "Title",
+
"community.create.head": "Create a Community",
"community.create.sub-head": "Create a Sub-Community for Community {{ parent }}",
"community.delete.cancel": "Cancel",
@@ -123,6 +188,7 @@
"community.page.news": "News",
"community.sub-collection-list.head": "Collections of this Community",
"community.sub-community-list.head": "Communities of this Community",
+
"dso-selector.create.collection.head": "New collection",
"dso-selector.create.community.head": "New community",
"dso-selector.create.community.sub-level": "Create a new community in",
@@ -133,11 +199,14 @@
"dso-selector.edit.item.head": "Edit item",
"dso-selector.no-results": "No {{ type }} found",
"dso-selector.placeholder": "Search for a {{ type }}",
+
"error.browse-by": "Error fetching items",
"error.collection": "Error fetching collection",
+ "error.collections": "Error fetching collections",
"error.community": "Error fetching community",
"error.default": "Error",
"error.item": "Error fetching item",
+ "error.items": "Error fetching items",
"error.objects": "Error fetching objects",
"error.recent-submissions": "Error fetching recent submissions",
"error.search-results": "Error fetching search results",
@@ -147,9 +216,11 @@
"error.top-level-communities": "Error fetching top-level communities",
"error.validation.license.notgranted": "You must grant this license to complete your submission. If you are unable to grant this license at this time you may save your work and return later or remove the submission.",
"error.validation.pattern": "This input is restricted by the current pattern: {{ pattern }}.",
+
"footer.copyright": "copyright © 2002-{{ year }}",
"footer.link.dspace": "DSpace software",
"footer.link.duraspace": "DuraSpace",
+
"form.add": "Add",
"form.add-help": "Click here to add the current entry and to add another one",
"form.cancel": "Cancel",
@@ -175,10 +246,12 @@
"form.search": "Search",
"form.search-help": "Click here to looking for an existing correspondence",
"form.submit": "Submit",
+
"home.description": "",
"home.title": "DSpace Angular :: Home",
"home.top-level-communities.head": "Communities in DSpace",
"home.top-level-communities.help": "Select a community to browse its collections.",
+
"item.edit.delete.cancel": "Cancel",
"item.edit.delete.confirm": "Delete",
"item.edit.delete.description": "Are you sure this item should be completely deleted? Caution: At present, no tombstone would be left.",
@@ -186,6 +259,25 @@
"item.edit.delete.header": "Delete item: {{ id }}",
"item.edit.delete.success": "The item has been deleted",
"item.edit.head": "Edit Item",
+
+ "item.edit.item-mapper.buttons.add": "Map item to selected collections",
+ "item.edit.item-mapper.buttons.remove": "Remove item's mapping for selected collections",
+ "item.edit.item-mapper.cancel": "Cancel",
+ "item.edit.item-mapper.description": "This is the item mapper tool that allows administrators to map this item to other collections. You can search for collections and map them, or browse the list of collections the item is currently mapped to.",
+ "item.edit.item-mapper.head": "Item Mapper - Map Item to Collections",
+ "item.edit.item-mapper.item": "Item: \"{{name}}\"",
+ "item.edit.item-mapper.no-search": "Please enter a query to search",
+ "item.edit.item-mapper.notifications.add.error.content": "Errors occurred for mapping of item to {{amount}} collections.",
+ "item.edit.item-mapper.notifications.add.error.head": "Mapping errors",
+ "item.edit.item-mapper.notifications.add.success.content": "Successfully mapped item to {{amount}} collections.",
+ "item.edit.item-mapper.notifications.add.success.head": "Mapping completed",
+ "item.edit.item-mapper.notifications.remove.error.content": "Errors occurred for the removal of the mapping to {{amount}} collections.",
+ "item.edit.item-mapper.notifications.remove.error.head": "Removal of mapping errors",
+ "item.edit.item-mapper.notifications.remove.success.content": "Successfully removed mapping of item to {{amount}} collections.",
+ "item.edit.item-mapper.notifications.remove.success.head": "Removal of mapping completed",
+ "item.edit.item-mapper.tabs.browse": "Browse mapped collections",
+ "item.edit.item-mapper.tabs.map": "Map new collections",
+
"item.edit.metadata.add-button": "Add",
"item.edit.metadata.discard-button": "Discard",
"item.edit.metadata.edit.buttons.edit": "Edit",
@@ -207,27 +299,44 @@
"item.edit.metadata.notifications.saved.title": "Metadata saved",
"item.edit.metadata.reinstate-button": "Undo",
"item.edit.metadata.save-button": "Save",
+
"item.edit.modify.overview.field": "Field",
"item.edit.modify.overview.language": "Language",
"item.edit.modify.overview.value": "Value",
+
+ "item.edit.move.cancel": "Cancel",
+ "item.edit.move.description": "Select the collection you wish to move this item to. To narrow down the list of displayed collections, you can enter a search query in the box.",
+ "item.edit.move.error": "An error occured when attempting to move the item",
+ "item.edit.move.head": "Move item: {{id}}",
+ "item.edit.move.inheritpolicies.checkbox": "Inherit policies",
+ "item.edit.move.inheritpolicies.description": "Inherit the default policies of the destination collection",
+ "item.edit.move.move": "Move",
+ "item.edit.move.processing": "Moving...",
+ "item.edit.move.search.placeholder": "Enter a search query to look for collections",
+ "item.edit.move.success": "The item has been moved succesfully",
+ "item.edit.move.title": "Move item",
+
"item.edit.private.cancel": "Cancel",
"item.edit.private.confirm": "Make it Private",
"item.edit.private.description": "Are you sure this item should be made private in the archive?",
"item.edit.private.error": "An error occurred while making the item private",
"item.edit.private.header": "Make item private: {{ id }}",
"item.edit.private.success": "The item is now private",
+
"item.edit.public.cancel": "Cancel",
"item.edit.public.confirm": "Make it Public",
"item.edit.public.description": "Are you sure this item should be made public in the archive?",
"item.edit.public.error": "An error occurred while making the item public",
"item.edit.public.header": "Make item public: {{ id }}",
"item.edit.public.success": "The item is now public",
+
"item.edit.reinstate.cancel": "Cancel",
"item.edit.reinstate.confirm": "Reinstate",
"item.edit.reinstate.description": "Are you sure this item should be reinstated to the archive?",
"item.edit.reinstate.error": "An error occurred while reinstating the item",
"item.edit.reinstate.header": "Reinstate item: {{ id }}",
"item.edit.reinstate.success": "The item was reinstated successfully",
+
"item.edit.relationships.discard-button": "Discard",
"item.edit.relationships.edit.buttons.remove": "Remove",
"item.edit.relationships.edit.buttons.undo": "Undo changes",
@@ -240,6 +349,7 @@
"item.edit.relationships.notifications.saved.title": "Relationships saved",
"item.edit.relationships.reinstate-button": "Undo",
"item.edit.relationships.save-button": "Save",
+
"item.edit.tabs.bitstreams.head": "Item Bitstreams",
"item.edit.tabs.bitstreams.title": "Item Edit - Bitstreams",
"item.edit.tabs.curate.head": "Curate",
@@ -273,12 +383,14 @@
"item.edit.tabs.status.title": "Item Edit - Status",
"item.edit.tabs.view.head": "View Item",
"item.edit.tabs.view.title": "Item Edit - View",
+
"item.edit.withdraw.cancel": "Cancel",
"item.edit.withdraw.confirm": "Withdraw",
"item.edit.withdraw.description": "Are you sure this item should be withdrawn from the archive?",
"item.edit.withdraw.error": "An error occurred while withdrawing the item",
"item.edit.withdraw.header": "Withdraw item: {{ id }}",
"item.edit.withdraw.success": "The item was withdrawn successfully",
+
"item.page.abstract": "Abstract",
"item.page.author": "Authors",
"item.page.citation": "Citation",
@@ -298,10 +410,13 @@
"item.page.related-items.view-less": "View less",
"item.page.subject": "Keywords",
"item.page.uri": "URI",
+
"item.select.confirm": "Confirm selected",
+ "item.select.empty": "No items to show",
"item.select.table.author": "Author",
"item.select.table.collection": "Collection",
"item.select.table.title": "Title",
+
"journal.listelement.badge": "Journal",
"journal.page.description": "Description",
"journal.page.editor": "Editor-in-Chief",
@@ -310,6 +425,7 @@
"journal.page.titleprefix": "Journal: ",
"journal.search.results.head": "Journal Search Results",
"journal.search.title": "DSpace Angular :: Journal Search",
+
"journalissue.listelement.badge": "Journal Issue",
"journalissue.page.description": "Description",
"journalissue.page.issuedate": "Issue Date",
@@ -318,14 +434,17 @@
"journalissue.page.keyword": "Keywords",
"journalissue.page.number": "Number",
"journalissue.page.titleprefix": "Journal Issue: ",
+
"journalvolume.listelement.badge": "Journal Volume",
"journalvolume.page.description": "Description",
"journalvolume.page.issuedate": "Issue Date",
"journalvolume.page.titleprefix": "Journal Volume: ",
"journalvolume.page.volume": "Volume",
+
"loading.browse-by": "Loading items...",
"loading.browse-by-page": "Loading page...",
"loading.collection": "Loading collection...",
+ "loading.collections": "Loading collections...",
"loading.community": "Loading community...",
"loading.default": "Loading...",
"loading.item": "Loading item...",
@@ -337,6 +456,7 @@
"loading.sub-collections": "Loading sub-collections...",
"loading.sub-communities": "Loading sub-communities...",
"loading.top-level-communities": "Loading top-level communities...",
+
"login.form.email": "Email address",
"login.form.forgot-password": "Have you forgotten your password?",
"login.form.header": "Please log in to DSpace",
@@ -344,15 +464,19 @@
"login.form.password": "Password",
"login.form.submit": "Log in",
"login.title": "Login",
+
"logout.form.header": "Log out from DSpace",
"logout.form.submit": "Log out",
"logout.title": "Logout",
+
"menu.header.admin": "Admin",
"menu.header.image.logo": "Repository logo",
+
"menu.section.access_control": "Access Control",
"menu.section.access_control_authorizations": "Authorizations",
"menu.section.access_control_groups": "Groups",
"menu.section.access_control_people": "People",
+
"menu.section.browse_community": "This Community",
"menu.section.browse_community_by_author": "By Author",
"menu.section.browse_community_by_issue_date": "By Issue Date",
@@ -363,21 +487,26 @@
"menu.section.browse_global_by_subject": "By Subject",
"menu.section.browse_global_by_title": "By Title",
"menu.section.browse_global_communities_and_collections": "Communities & Collections",
+
"menu.section.control_panel": "Control Panel",
"menu.section.curation_task": "Curation Task",
+
"menu.section.edit": "Edit",
"menu.section.edit_collection": "Collection",
"menu.section.edit_community": "Community",
"menu.section.edit_item": "Item",
+
"menu.section.export": "Export",
"menu.section.export_collection": "Collection",
"menu.section.export_community": "Community",
"menu.section.export_item": "Item",
"menu.section.export_metadata": "Metadata",
+
"menu.section.find": "Find",
"menu.section.find_items": "Items",
"menu.section.find_private_items": "Private Items",
"menu.section.find_withdrawn_items": "Withdrawn Items",
+
"menu.section.icon.access_control": "Access Control menu section",
"menu.section.icon.control_panel": "Control Panel menu section",
"menu.section.icon.curation_task": "Curation Task menu section",
@@ -390,20 +519,27 @@
"menu.section.icon.registries": "Registries menu section",
"menu.section.icon.statistics_task": "Statistics Task menu section",
"menu.section.icon.unpin": "Unpin sidebar",
+
"menu.section.import": "Import",
"menu.section.import_batch": "Batch Import (ZIP)",
"menu.section.import_metadata": "Metadata",
+
"menu.section.new": "New",
"menu.section.new_collection": "Collection",
"menu.section.new_community": "Community",
"menu.section.new_item": "Item",
"menu.section.new_item_version": "Item Version",
+
"menu.section.pin": "Pin sidebar",
+ "menu.section.unpin": "Unpin sidebar",
+
"menu.section.registries": "Registries",
"menu.section.registries_format": "Format",
"menu.section.registries_metadata": "Metadata",
+
"menu.section.statistics": "Statistics",
"menu.section.statistics_task": "Statistics Task",
+
"menu.section.toggle.access_control": "Toggle Access Control section",
"menu.section.toggle.control_panel": "Toggle Control Panel section",
"menu.section.toggle.curation_task": "Toggle Curation Task section",
@@ -414,7 +550,7 @@
"menu.section.toggle.new": "Toggle New section",
"menu.section.toggle.registries": "Toggle Registries section",
"menu.section.toggle.statistics_task": "Toggle Statistics Task section",
- "menu.section.unpin": "Unpin sidebar",
+
"mydspace.description": "",
"mydspace.general.text-here": "HERE",
"mydspace.messages.controller-help": "Select this option to send a message to item's submitter.",
@@ -452,6 +588,7 @@
"mydspace.upload.upload-multiple-successful": "{{qty}} new workspace items created.",
"mydspace.upload.upload-successful": "New workspace item created. Click {{here}} for edit it.",
"mydspace.view-btn": "View",
+
"nav.browse.header": "All of DSpace",
"nav.community-browse.header": "By Community",
"nav.language": "Language switch",
@@ -460,6 +597,7 @@
"nav.mydspace": "MyDSpace",
"nav.search": "Search",
"nav.statistics.header": "Statistics",
+
"orgunit.listelement.badge": "Organizational Unit",
"orgunit.page.city": "City",
"orgunit.page.country": "Country",
@@ -467,10 +605,12 @@
"orgunit.page.description": "Description",
"orgunit.page.id": "ID",
"orgunit.page.titleprefix": "Organizational Unit: ",
+
"pagination.results-per-page": "Results Per Page",
"pagination.showing.detail": "{{ range }} of {{ total }}",
"pagination.showing.label": "Now showing ",
"pagination.sort-direction": "Sort Options",
+
"person.listelement.badge": "Person",
"person.page.birthdate": "Birth Date",
"person.page.email": "Email Address",
@@ -483,6 +623,7 @@
"person.page.titleprefix": "Person: ",
"person.search.results.head": "Person Search Results",
"person.search.title": "DSpace Angular :: Person Search",
+
"project.listelement.badge": "Research Project",
"project.page.contributor": "Contributors",
"project.page.description": "Description",
@@ -492,6 +633,7 @@
"project.page.keyword": "Keywords",
"project.page.status": "Status",
"project.page.titleprefix": "Research Project: ",
+
"publication.listelement.badge": "Publication",
"publication.page.description": "Description",
"publication.page.journal-issn": "Journal ISSN",
@@ -501,6 +643,7 @@
"publication.page.volume-title": "Volume Title",
"publication.search.results.head": "Publication Search Results",
"publication.search.title": "DSpace Angular :: Publication Search",
+
"relationships.isAuthorOf": "Authors",
"relationships.isIssueOf": "Journal Issues",
"relationships.isJournalIssueOf": "Journal Issue",
@@ -513,7 +656,11 @@
"relationships.isSingleJournalOf": "Journal",
"relationships.isSingleVolumeOf": "Journal Volume",
"relationships.isVolumeOf": "Journal Volumes",
+
"search.description": "",
+ "search.switch-configuration.title": "Show",
+ "search.title": "DSpace Angular :: Search",
+
"search.filters.applied.f.author": "Author",
"search.filters.applied.f.dateIssued.max": "End date",
"search.filters.applied.f.dateIssued.min": "Start date",
@@ -527,6 +674,7 @@
"search.filters.applied.f.jobTitle": "Job Title",
"search.filters.applied.f.birthDate.max": "End birth date",
"search.filters.applied.f.birthDate.min": "Start birth date",
+
"search.filters.filter.author.head": "Author",
"search.filters.filter.author.placeholder": "Author name",
"search.filters.filter.birthDate.head": "Birth Date",
@@ -571,14 +719,18 @@
"search.filters.filter.subject.placeholder": "Subject",
"search.filters.filter.submitter.head": "Submitter",
"search.filters.filter.submitter.placeholder": "Submitter",
+
"search.filters.head": "Filters",
"search.filters.reset": "Reset filters",
+
"search.form.search": "Search",
"search.form.search_dspace": "Search DSpace",
"search.form.search_mydspace": "Search MyDSpace",
+
"search.results.head": "Search Results",
"search.results.no-results": "Your search returned no results. Having trouble finding what you're looking for? Try putting",
"search.results.no-results-link": "quotes around it",
+
"search.sidebar.close": "Back to results",
"search.sidebar.filters.title": "Filters",
"search.sidebar.open": "Search Tools",
@@ -586,14 +738,15 @@
"search.sidebar.settings.rpp": "Results per page",
"search.sidebar.settings.sort-by": "Sort By",
"search.sidebar.settings.title": "Settings",
- "search.switch-configuration.title": "Show",
- "search.title": "DSpace Angular :: Search",
+
"search.view-switch.show-detail": "Show detail",
"search.view-switch.show-grid": "Show as grid",
"search.view-switch.show-list": "Show as list",
+
"sorting.dc.title.ASC": "Title Ascending",
"sorting.dc.title.DESC": "Title Descending",
"sorting.score.DESC": "Relevance",
+
"submission.edit.title": "Edit Submission",
"submission.general.cannot_submit": "You have not the privilege to make a new submission.",
"submission.general.deposit": "Deposit",
@@ -604,7 +757,7 @@
"submission.general.discard.submit": "Discard",
"submission.general.save": "Save",
"submission.general.save-later": "Save for later",
- "submission.mydspace": {},
+
"submission.sections.describe.relationship-lookup.close": "Close",
"submission.sections.describe.relationship-lookup.search-tab.deselect-all": "Deselect all",
"submission.sections.describe.relationship-lookup.search-tab.deselect-page": "Deselect page",
@@ -644,6 +797,7 @@
"submission.sections.general.save_success_notice": "Submission saved successfully.",
"submission.sections.general.search-collection": "Search for a collection",
"submission.sections.general.sections_not_valid": "There are incomplete sections.",
+
"submission.sections.submit.progressbar.cclicense": "Creative commons license",
"submission.sections.submit.progressbar.describe.recycle": "Recycle",
"submission.sections.submit.progressbar.describe.stepcustom": "Describe",
@@ -652,6 +806,7 @@
"submission.sections.submit.progressbar.detect-duplicate": "Potential duplicates",
"submission.sections.submit.progressbar.license": "Deposit license",
"submission.sections.submit.progressbar.upload": "Upload files",
+
"submission.sections.upload.delete.confirm.cancel": "Cancel",
"submission.sections.upload.delete.confirm.info": "This operation can't be undone. Are you sure?",
"submission.sections.upload.delete.confirm.submit": "Yes, I'm sure",
@@ -675,13 +830,16 @@
"submission.sections.upload.undo": "Cancel",
"submission.sections.upload.upload-failed": "Upload failed",
"submission.sections.upload.upload-successful": "Upload successful",
+
"submission.submit.title": "Submission",
+
"submission.workflow.generic.delete": "Delete",
"submission.workflow.generic.delete-help": "If you would to discard this item, select \"Delete\". You will then be asked to confirm it.",
"submission.workflow.generic.edit": "Edit",
"submission.workflow.generic.edit-help": "Select this option to change the item's metadata.",
"submission.workflow.generic.view": "View",
"submission.workflow.generic.view-help": "Select this option to view the item's metadata.",
+
"submission.workflow.tasks.claimed.approve": "Approve",
"submission.workflow.tasks.claimed.approve_help": "If you have reviewed the item and it is suitable for inclusion in the collection, select \"Approve\".",
"submission.workflow.tasks.claimed.edit": "Edit",
@@ -694,18 +852,22 @@
"submission.workflow.tasks.claimed.reject_help": "If you have reviewed the item and found it is not suitable for inclusion in the collection, select \"Reject\". You will then be asked to enter a message indicating why the item is unsuitable, and whether the submitter should change something and resubmit.",
"submission.workflow.tasks.claimed.return": "Return to pool",
"submission.workflow.tasks.claimed.return_help": "Return the task to the pool so that another user may perform the task.",
+
"submission.workflow.tasks.generic.error": "Error occurred during operation...",
"submission.workflow.tasks.generic.processing": "Processing...",
"submission.workflow.tasks.generic.submitter": "Submitter",
"submission.workflow.tasks.generic.success": "Operation successful",
+
"submission.workflow.tasks.pool.claim": "Claim",
"submission.workflow.tasks.pool.claim_help": "Assign this task to yourself.",
"submission.workflow.tasks.pool.hide-detail": "Hide detail",
"submission.workflow.tasks.pool.show-detail": "Show detail",
+
"title": "DSpace",
+
"uploader.browse": "browse",
"uploader.drag-message": "Drag & Drop your files here",
"uploader.or": ", or",
"uploader.processing": "Processing",
- "uploader.queue-lenght": "Queue length"
+ "uploader.queue-length": "Queue length",
}
diff --git a/resources/i18n/nl.json b/resources/i18n/nl.json5
similarity index 99%
rename from resources/i18n/nl.json
rename to resources/i18n/nl.json5
index da12ff0518..a195e13e01 100644
--- a/resources/i18n/nl.json
+++ b/resources/i18n/nl.json5
@@ -2,6 +2,7 @@
"404.help": "De pagina die u zoekt kan niet gevonden worden. De pagina werd mogelijk verplaatst of verwijderd. U kan onderstaande knop gebruiken om terug naar de homepagina te gaan. ",
"404.link.home-page": "Terug naar de homepagina",
"404.page-not-found": "Pagina niet gevonden",
+
"admin.registries.bitstream-formats.description": "Deze lijst van Bitstream formaten biedt informatie over de formaten die in deze repository zijn toegelaten en op welke manier ze ondersteund worden. De term Bitstream wordt in DSpace gebruikt om een bestand aan te duiden dat samen met metadata onderdeel uitmaakt van een item. De naam bitstream duidt op het feit dat het bestand achterliggend wordt opgeslaan zonder bestandsextensie.",
"admin.registries.bitstream-formats.formats.no-items": "Er kunnen geen bitstreamformaten getoond worden.",
"admin.registries.bitstream-formats.formats.table.internal": "intern",
@@ -13,6 +14,7 @@
"admin.registries.bitstream-formats.formats.table.supportLevel.head": "Ondersteuning",
"admin.registries.bitstream-formats.head": "Bitstream Formaat Register",
"admin.registries.bitstream-formats.title": "DSpace Angular :: Bitstream Formaat Register",
+
"admin.registries.metadata.description": "Het metadataregister omvat de lijst van alle metadatavelden die beschikbaar zijn in het systeem. Deze velden kunnen verspreid zijn over verschillende metadataschema's. Het qualified Dublin Core schema (dc) is een verplicht schema en kan niet worden verwijderd.",
"admin.registries.metadata.head": "Metadata Register",
"admin.registries.metadata.schemas.no-items": "Er kunnen geen metadataschema's getoond worden.",
@@ -20,6 +22,7 @@
"admin.registries.metadata.schemas.table.name": "Naam",
"admin.registries.metadata.schemas.table.namespace": "Naamruimte",
"admin.registries.metadata.title": "DSpace Angular :: Metadata Register",
+
"admin.registries.schema.description": "Dit is het metadataschema voor \"{{namespace}}\".",
"admin.registries.schema.fields.head": "Schema metadatavelden",
"admin.registries.schema.fields.no-items": "Er kunnen geen metadatavelden getoond worden.",
@@ -27,15 +30,20 @@
"admin.registries.schema.fields.table.scopenote": "Opmerking over bereik",
"admin.registries.schema.head": "Metadata Schema",
"admin.registries.schema.title": "DSpace Angular :: Metadata Schema Register",
+
"auth.errors.invalid-user": "Ongeldig e-mailadres of wachtwoord.",
"auth.messages.expired": "Uw sessie is vervallen. Gelieve opnieuw aan te melden.",
+
"browse.title": "Verken {{ collection }} volgens {{ field }} {{ value }}",
+
"collection.page.browse.recent.head": "Recent toegevoegd",
"collection.page.license": "Licentie",
"collection.page.news": "Nieuws",
+
"community.page.license": "Licentie",
"community.page.news": "Nieuws",
"community.sub-collection-list.head": "Collecties in deze Community",
+
"error.browse-by": "Fout bij het ophalen van items",
"error.collection": "Fout bij het ophalen van een collectie",
"error.community": "Fout bij het ophalen van een community",
@@ -48,9 +56,11 @@
"error.top-level-communities": "Fout bij het inladen van communities op het hoogste niveau",
"error.validation.license.notgranted": "U moet de invoerlicentie goedkeuren om de invoer af te werken. Indien u deze licentie momenteel niet kan of mag goedkeuren, kan u uw werk opslaan en de invoer later afwerken. U kunt dit nieuwe item ook verwijderen indien u niet voldoet aan de vereisten van de invoerlicentie.",
"error.validation.pattern": "Deze invoer is niet toegelaten volgens dit patroon: {{ pattern }}.",
+
"footer.copyright": "copyright © 2002-{{ year }}",
"footer.link.dspace": "DSpace software",
"footer.link.duraspace": "DuraSpace",
+
"form.cancel": "Annuleer",
"form.first-name": "Voornaam",
"form.group-collapse": "Inklappen",
@@ -64,10 +74,12 @@
"form.remove": "Verwijder",
"form.search": "Zoek",
"form.submit": "Verstuur",
+
"home.description": "",
"home.title": "DSpace Angular :: Home",
"home.top-level-communities.head": "Communities in DSpace",
"home.top-level-communities.help": "Selecteer een community om diens collecties te verkennen.",
+
"item.page.abstract": "Abstract",
"item.page.author": "Auteur",
"item.page.collections": "Collecties",
@@ -81,6 +93,7 @@
"item.page.link.full": "Volledige itemweergave",
"item.page.link.simple": "Eenvoudige itemweergave",
"item.page.uri": "URI",
+
"loading.browse-by": "Items worden ingeladen...",
"loading.collection": "Collectie wordt ingeladen...",
"loading.community": "Community wordt ingeladen...",
@@ -91,6 +104,7 @@
"loading.search-results": "Zoekresultaten worden ingeladen...",
"loading.sub-collections": "De sub-collecties worden ingeladen...",
"loading.top-level-communities": "Inladen van de Communities op het hoogste niveau...",
+
"login.form.email": "Email adres",
"login.form.forgot-password": "Bent u uw wachtwoord vergeten?",
"login.form.header": "Gelieve in te loggen in DSpace",
@@ -98,17 +112,23 @@
"login.form.password": "Wachtwoord",
"login.form.submit": "Aanmelden",
"login.title": "Aanmelden",
+
"logout.form.header": "Afmelden in DSpace",
"logout.form.submit": "Afmelden",
"logout.title": "Afmelden",
+
"nav.home": "Home",
"nav.login": "Log In",
"nav.logout": "Log Uit",
+
"pagination.results-per-page": "Resultaten per pagina",
"pagination.showing.detail": "{{ range }} van {{ total }}",
"pagination.showing.label": "Resultaten ",
"pagination.sort-direction": "Sorteermogelijkheden",
+
"search.description": "",
+ "search.title": "DSpace Angular :: Zoek",
+
"search.filters.applied.f.author": "Auteur",
"search.filters.applied.f.dateIssued.max": "Einddatum",
"search.filters.applied.f.dateIssued.min": "Startdatum",
@@ -126,12 +146,16 @@
"search.filters.filter.show-more": "Toon meer",
"search.filters.filter.subject.head": "Onderwerp",
"search.filters.filter.subject.placeholder": "Onderwerp",
+
"search.filters.head": "Filters",
"search.filters.reset": "Filters verwijderen",
+
"search.form.search": "Zoek",
"search.form.search_dspace": "Zoek in DSpace",
+
"search.results.head": "Zoekresultaten",
"search.results.no-results": "Er waren geen resultaten voor deze zoekopdracht",
+
"search.sidebar.close": "Terug naar de resultaten",
"search.sidebar.filters.title": "Filters",
"search.sidebar.open": "Zoek Tools",
@@ -139,11 +163,13 @@
"search.sidebar.settings.rpp": "Resultaten per pagina",
"search.sidebar.settings.sort-by": "Sorteer volgens",
"search.sidebar.settings.title": "Instellingen",
- "search.title": "DSpace Angular :: Zoek",
+
"search.view-switch.show-grid": "Toon in raster",
"search.view-switch.show-list": "Toon als lijst",
+
"sorting.dc.title.ASC": "Oplopend op titel",
"sorting.dc.title.DESC": "Aflopend op titel",
"sorting.score.DESC": "Relevantie",
- "title": "DSpace"
+
+ "title": "DSpace",
}
diff --git a/src/app/+admin/admin-registries/admin-registries-routing.module.ts b/src/app/+admin/admin-registries/admin-registries-routing.module.ts
index 8e3c322bc8..afdc46bf17 100644
--- a/src/app/+admin/admin-registries/admin-registries-routing.module.ts
+++ b/src/app/+admin/admin-registries/admin-registries-routing.module.ts
@@ -2,14 +2,29 @@ import { MetadataRegistryComponent } from './metadata-registry/metadata-registry
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.component';
-import { BitstreamFormatsComponent } from './bitstream-formats/bitstream-formats.component';
+import { URLCombiner } from '../../core/url-combiner/url-combiner';
+import { getRegistriesModulePath } from '../admin-routing.module';
+
+const BITSTREAMFORMATS_MODULE_PATH = 'bitstream-formats';
+
+export function getBitstreamFormatsModulePath() {
+ return new URLCombiner(getRegistriesModulePath(), BITSTREAMFORMATS_MODULE_PATH).toString();
+}
@NgModule({
imports: [
RouterModule.forChild([
- { path: 'metadata', component: MetadataRegistryComponent, data: { title: 'admin.registries.metadata.title' } },
- { path: 'metadata/:schemaName', component: MetadataSchemaComponent, data: { title: 'admin.registries.schema.title' } },
- { path: 'bitstream-formats', component: BitstreamFormatsComponent, data: { title: 'admin.registries.bitstream-formats.title' } },
+ {path: 'metadata', component: MetadataRegistryComponent, data: {title: 'admin.registries.metadata.title'}},
+ {
+ path: 'metadata/:schemaName',
+ component: MetadataSchemaComponent,
+ data: {title: 'admin.registries.schema.title'}
+ },
+ {
+ path: BITSTREAMFORMATS_MODULE_PATH,
+ loadChildren: './bitstream-formats/bitstream-formats.module#BitstreamFormatsModule',
+ data: {title: 'admin.registries.bitstream-formats.title'}
+ },
])
]
})
diff --git a/src/app/+admin/admin-registries/admin-registries.module.ts b/src/app/+admin/admin-registries/admin-registries.module.ts
index c7890e6697..bbeb59f0ab 100644
--- a/src/app/+admin/admin-registries/admin-registries.module.ts
+++ b/src/app/+admin/admin-registries/admin-registries.module.ts
@@ -5,10 +5,10 @@ import { CommonModule } from '@angular/common';
import { MetadataSchemaComponent } from './metadata-schema/metadata-schema.component';
import { RouterModule } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
-import { BitstreamFormatsComponent } from './bitstream-formats/bitstream-formats.component';
import { SharedModule } from '../../shared/shared.module';
import { MetadataSchemaFormComponent } from './metadata-registry/metadata-schema-form/metadata-schema-form.component';
-import {MetadataFieldFormComponent} from './metadata-schema/metadata-field-form/metadata-field-form.component';
+import { MetadataFieldFormComponent } from './metadata-schema/metadata-field-form/metadata-field-form.component';
+import { BitstreamFormatsModule } from './bitstream-formats/bitstream-formats.module';
@NgModule({
imports: [
@@ -16,12 +16,12 @@ import {MetadataFieldFormComponent} from './metadata-schema/metadata-field-form/
SharedModule,
RouterModule,
TranslateModule,
+ BitstreamFormatsModule,
AdminRegistriesRoutingModule
],
declarations: [
MetadataRegistryComponent,
MetadataSchemaComponent,
- BitstreamFormatsComponent,
MetadataSchemaFormComponent,
MetadataFieldFormComponent
],
diff --git a/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.html b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.html
new file mode 100644
index 0000000000..2b65b369b2
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.html
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.spec.ts
new file mode 100644
index 0000000000..0a10633956
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.spec.ts
@@ -0,0 +1,106 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { CommonModule } from '@angular/common';
+import { RouterTestingModule } from '@angular/router/testing';
+import { TranslateModule } from '@ngx-translate/core';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+import { Router } from '@angular/router';
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { RouterStub } from '../../../../shared/testing/router-stub';
+import { of as observableOf } from 'rxjs';
+import { NotificationsService } from '../../../../shared/notifications/notifications.service';
+import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service-stub';
+import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
+import { RestResponse } from '../../../../core/cache/response.models';
+import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
+import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
+import { ResourceType } from '../../../../core/shared/resource-type';
+import { AddBitstreamFormatComponent } from './add-bitstream-format.component';
+
+describe('AddBitstreamFormatComponent', () => {
+ let comp: AddBitstreamFormatComponent;
+ let fixture: ComponentFixture;
+
+ const bitstreamFormat = new BitstreamFormat();
+ bitstreamFormat.uuid = 'test-uuid-1';
+ bitstreamFormat.id = 'test-uuid-1';
+ bitstreamFormat.shortDescription = 'Unknown';
+ bitstreamFormat.description = 'Unknown data format';
+ bitstreamFormat.mimetype = 'application/octet-stream';
+ bitstreamFormat.supportLevel = BitstreamFormatSupportLevel.Unknown;
+ bitstreamFormat.internal = false;
+ bitstreamFormat.extensions = null;
+
+ let router;
+ let notificationService: NotificationsServiceStub;
+ let bitstreamFormatDataService: BitstreamFormatDataService;
+
+ const initAsync = () => {
+ router = new RouterStub();
+ notificationService = new NotificationsServiceStub();
+ bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
+ createBitstreamFormat: observableOf(new RestResponse(true, 200, 'Success')),
+ clearBitStreamFormatRequests: observableOf(null)
+ });
+
+ TestBed.configureTestingModule({
+ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+ declarations: [AddBitstreamFormatComponent],
+ providers: [
+ {provide: Router, useValue: router},
+ {provide: NotificationsService, useValue: notificationService},
+ {provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService},
+ ],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+ }).compileComponents();
+ };
+
+ const initBeforeEach = () => {
+ fixture = TestBed.createComponent(AddBitstreamFormatComponent);
+ comp = fixture.componentInstance;
+
+ fixture.detectChanges();
+ };
+
+ describe('createBitstreamFormat success', () => {
+ beforeEach(async(initAsync));
+ beforeEach(initBeforeEach);
+ it('should send the updated form to the service, show a notification and navigate to ', () => {
+ comp.createBitstreamFormat(bitstreamFormat);
+
+ expect(bitstreamFormatDataService.createBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat);
+ expect(notificationService.success).toHaveBeenCalled();
+ expect(router.navigate).toHaveBeenCalledWith(['/admin/registries/bitstream-formats']);
+
+ });
+ });
+ describe('createBitstreamFormat error', () => {
+ beforeEach(async(() => {
+ router = new RouterStub();
+ notificationService = new NotificationsServiceStub();
+ bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
+ createBitstreamFormat: observableOf(new RestResponse(false, 400, 'Bad Request')),
+ clearBitStreamFormatRequests: observableOf(null)
+ });
+
+ TestBed.configureTestingModule({
+ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+ declarations: [AddBitstreamFormatComponent],
+ providers: [
+ {provide: Router, useValue: router},
+ {provide: NotificationsService, useValue: notificationService},
+ {provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService},
+ ],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+ }).compileComponents();
+ }));
+ beforeEach(initBeforeEach);
+ it('should send the updated form to the service, show a notification and navigate to ', () => {
+ comp.createBitstreamFormat(bitstreamFormat);
+
+ expect(bitstreamFormatDataService.createBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat);
+ expect(notificationService.error).toHaveBeenCalled();
+ expect(router.navigate).not.toHaveBeenCalled();
+
+ });
+ });
+});
diff --git a/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.ts b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.ts
new file mode 100644
index 0000000000..9712be70ca
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/add-bitstream-format/add-bitstream-format.component.ts
@@ -0,0 +1,49 @@
+import { take } from 'rxjs/operators';
+import { Router } from '@angular/router';
+import { Component } from '@angular/core';
+import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
+import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
+import { RestResponse } from '../../../../core/cache/response.models';
+import { NotificationsService } from '../../../../shared/notifications/notifications.service';
+import { getBitstreamFormatsModulePath } from '../../admin-registries-routing.module';
+import { TranslateService } from '@ngx-translate/core';
+
+/**
+ * This component renders the page to create a new bitstream format.
+ */
+@Component({
+ selector: 'ds-add-bitstream-format',
+ templateUrl: './add-bitstream-format.component.html',
+})
+export class AddBitstreamFormatComponent {
+
+ constructor(
+ private router: Router,
+ private notificationService: NotificationsService,
+ private translateService: TranslateService,
+ private bitstreamFormatDataService: BitstreamFormatDataService,
+ ) {
+ }
+
+ /**
+ * Creates a new bitstream format based on the provided bitstream format emitted by the form.
+ * When successful, a success notification will be shown and the user will be navigated back to the overview page.
+ * When failed, an error notification will be shown.
+ * @param bitstreamFormat
+ */
+ createBitstreamFormat(bitstreamFormat: BitstreamFormat) {
+ this.bitstreamFormatDataService.createBitstreamFormat(bitstreamFormat).pipe(take(1)
+ ).subscribe((response: RestResponse) => {
+ if (response.isSuccessful) {
+ this.notificationService.success(this.translateService.get('admin.registries.bitstream-formats.create.success.head'),
+ this.translateService.get('admin.registries.bitstream-formats.create.success.content'));
+ this.router.navigate([getBitstreamFormatsModulePath()]);
+ this.bitstreamFormatDataService.clearBitStreamFormatRequests().subscribe();
+ } else {
+ this.notificationService.error(this.translateService.get('admin.registries.bitstream-formats.create.failure.head'),
+ this.translateService.get('admin.registries.bitstream-formats.create.failure.content'));
+ }
+ }
+ );
+ }
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.actions.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.actions.ts
new file mode 100644
index 0000000000..58b0686dfd
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.actions.ts
@@ -0,0 +1,64 @@
+import { Action } from '@ngrx/store';
+import { type } from '../../../shared/ngrx/type';
+import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
+
+/**
+ * For each action type in an action group, make a simple
+ * enum object for all of this group's action types.
+ *
+ * The 'type' utility function coerces strings into string
+ * literal types and runs a simple check to guarantee all
+ * action types in the application are unique.
+ */
+export const BitstreamFormatsRegistryActionTypes = {
+
+ SELECT_FORMAT: type('dspace/bitstream-formats-registry/SELECT_FORMAT'),
+ DESELECT_FORMAT: type('dspace/bitstream-formats-registry/DESELECT_FORMAT'),
+ DESELECT_ALL_FORMAT: type('dspace/bitstream-formats-registry/DESELECT_ALL_FORMAT')
+};
+
+/* tslint:disable:max-classes-per-file */
+/**
+ * Used to select a single bitstream format in the bitstream format registry
+ */
+export class BitstreamFormatsRegistrySelectAction implements Action {
+ type = BitstreamFormatsRegistryActionTypes.SELECT_FORMAT;
+
+ bitstreamFormat: BitstreamFormat;
+
+ constructor(bitstreamFormat: BitstreamFormat) {
+ this.bitstreamFormat = bitstreamFormat;
+ }
+}
+
+/**
+ * Used to deselect a single bitstream format in the bitstream format registry
+ */
+export class BitstreamFormatsRegistryDeselectAction implements Action {
+ type = BitstreamFormatsRegistryActionTypes.DESELECT_FORMAT;
+
+ bitstreamFormat: BitstreamFormat;
+
+ constructor(bitstreamFormat: BitstreamFormat) {
+ this.bitstreamFormat = bitstreamFormat;
+ }
+}
+
+/**
+ * Used to deselect all bitstream formats in the bitstream format registry
+ */
+export class BitstreamFormatsRegistryDeselectAllAction implements Action {
+ type = BitstreamFormatsRegistryActionTypes.DESELECT_ALL_FORMAT;
+}
+
+/* tslint:enable:max-classes-per-file */
+
+/**
+ * Export a type alias of all actions in this action group
+ * so that reducers can easily compose action types
+ * These are all the actions to perform on the bitstream format registry state
+ */
+export type BitstreamFormatsRegistryAction
+ = BitstreamFormatsRegistrySelectAction
+ | BitstreamFormatsRegistryDeselectAction
+ | BitstreamFormatsRegistryDeselectAllAction
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.spec.ts
new file mode 100644
index 0000000000..76576afc7a
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.spec.ts
@@ -0,0 +1,83 @@
+import { Action } from '@ngrx/store';
+import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
+import { bitstreamFormatReducer, BitstreamFormatRegistryState } from './bitstream-format.reducers';
+import {
+ BitstreamFormatsRegistryDeselectAction,
+ BitstreamFormatsRegistryDeselectAllAction,
+ BitstreamFormatsRegistrySelectAction
+} from './bitstream-format.actions';
+
+const bitstreamFormat1: BitstreamFormat = new BitstreamFormat();
+bitstreamFormat1.id = 'test-uuid-1';
+bitstreamFormat1.shortDescription = 'test-short-1';
+
+const bitstreamFormat2: BitstreamFormat = new BitstreamFormat();
+bitstreamFormat2.id = 'test-uuid-2';
+bitstreamFormat2.shortDescription = 'test-short-2';
+
+const initialState: BitstreamFormatRegistryState = {
+ selectedBitstreamFormats: []
+};
+
+const bitstream1SelectedState: BitstreamFormatRegistryState = {
+ selectedBitstreamFormats: [bitstreamFormat1]
+};
+
+const bitstream1and2SelectedState: BitstreamFormatRegistryState = {
+ selectedBitstreamFormats: [bitstreamFormat1, bitstreamFormat2]
+};
+
+describe('BitstreamFormatReducer', () => {
+ describe('BitstreamFormatsRegistryActionTypes.SELECT_FORMAT', () => {
+ it('should add the format to the list of selected formats when initial list is empty', () => {
+ const state = initialState;
+ const action = new BitstreamFormatsRegistrySelectAction(bitstreamFormat1);
+ const newState = bitstreamFormatReducer(state, action);
+
+ expect(newState).toEqual(bitstream1SelectedState);
+ });
+ it('should add the format to the list of selected formats when formats are already present', () => {
+ const state = bitstream1SelectedState;
+ const action = new BitstreamFormatsRegistrySelectAction(bitstreamFormat2);
+ const newState = bitstreamFormatReducer(state, action);
+
+ expect(newState).toEqual(bitstream1and2SelectedState);
+ });
+ });
+ describe('BitstreamFormatsRegistryActionTypes.DESELECT_FORMAT', () => {
+ it('should deselect a format', () => {
+ const state = bitstream1and2SelectedState;
+ const action = new BitstreamFormatsRegistryDeselectAction(bitstreamFormat2);
+ const newState = bitstreamFormatReducer(state, action);
+
+ expect(newState).toEqual(bitstream1SelectedState);
+ });
+ });
+ describe('BitstreamFormatsRegistryActionTypes.DESELECT_ALL_FORMAT', () => {
+ it('should deselect all formats', () => {
+ const state = bitstream1and2SelectedState;
+ const action = new BitstreamFormatsRegistryDeselectAllAction();
+ const newState = bitstreamFormatReducer(state, action);
+
+ expect(newState).toEqual(initialState);
+ });
+ });
+ describe('Invalid action', () => {
+ it('should return the current state', () => {
+ const state = initialState;
+ const action = new NullAction();
+
+ const newState = bitstreamFormatReducer(state, action);
+
+ expect(newState).toEqual(state);
+ });
+ });
+});
+
+class NullAction implements Action {
+ type = null;
+
+ constructor() {
+ // empty constructor
+ }
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.ts
new file mode 100644
index 0000000000..41880bf16c
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-format.reducers.ts
@@ -0,0 +1,55 @@
+import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
+import {
+ BitstreamFormatsRegistryAction,
+ BitstreamFormatsRegistryActionTypes,
+ BitstreamFormatsRegistryDeselectAction,
+ BitstreamFormatsRegistrySelectAction
+} from './bitstream-format.actions';
+
+/**
+ * The bitstream format registry state.
+ * @interface BitstreamFormatRegistryState
+ */
+export interface BitstreamFormatRegistryState {
+ selectedBitstreamFormats: BitstreamFormat[];
+}
+
+/**
+ * The initial state.
+ */
+const initialState: BitstreamFormatRegistryState = {
+ selectedBitstreamFormats: [],
+};
+
+/**
+ * Reducer that handles BitstreamFormatsRegistryActions to modify the bitstream format registry state
+ * @param state The current BitstreamFormatRegistryState
+ * @param action The BitstreamFormatsRegistryAction to perform on the state
+ */
+export function bitstreamFormatReducer(state = initialState, action: BitstreamFormatsRegistryAction): BitstreamFormatRegistryState {
+
+ switch (action.type) {
+
+ case BitstreamFormatsRegistryActionTypes.SELECT_FORMAT: {
+ return Object.assign({}, state, {
+ selectedBitstreamFormats: [...state.selectedBitstreamFormats, (action as BitstreamFormatsRegistrySelectAction).bitstreamFormat]
+ });
+ }
+
+ case BitstreamFormatsRegistryActionTypes.DESELECT_FORMAT: {
+ return Object.assign({}, state, {
+ selectedBitstreamFormats: state.selectedBitstreamFormats.filter(
+ (selectedBitstreamFormats) => selectedBitstreamFormats !== (action as BitstreamFormatsRegistryDeselectAction).bitstreamFormat
+ )
+ });
+ }
+
+ case BitstreamFormatsRegistryActionTypes.DESELECT_ALL_FORMAT: {
+ return Object.assign({}, state, {
+ selectedBitstreamFormats: []
+ });
+ }
+ default:
+ return state;
+ }
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats-routing.module.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats-routing.module.ts
new file mode 100644
index 0000000000..67f6aa373e
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats-routing.module.ts
@@ -0,0 +1,37 @@
+import { NgModule } from '@angular/core';
+import { RouterModule } from '@angular/router';
+import { BitstreamFormatsResolver } from './bitstream-formats.resolver';
+import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component';
+import { BitstreamFormatsComponent } from './bitstream-formats.component';
+import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component';
+
+const BITSTREAMFORMAT_EDIT_PATH = ':id/edit';
+const BITSTREAMFORMAT_ADD_PATH = 'add';
+
+@NgModule({
+ imports: [
+ RouterModule.forChild([
+ {
+ path: '',
+ component: BitstreamFormatsComponent
+ },
+ {
+ path: BITSTREAMFORMAT_ADD_PATH,
+ component: AddBitstreamFormatComponent,
+ },
+ {
+ path: BITSTREAMFORMAT_EDIT_PATH,
+ component: EditBitstreamFormatComponent,
+ resolve: {
+ bitstreamFormat: BitstreamFormatsResolver
+ }
+ },
+ ])
+ ],
+ providers: [
+ BitstreamFormatsResolver,
+ ]
+})
+export class BitstreamFormatsRoutingModule {
+
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html
index 1ac547653f..e5cf7cf5ec 100644
--- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.html
@@ -2,13 +2,15 @@
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts
index 3a680c906b..e672dc82ea 100644
--- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.spec.ts
@@ -1,6 +1,5 @@
import { BitstreamFormatsComponent } from './bitstream-formats.component';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { RegistryService } from '../../../core/registry/registry.service';
import { of as observableOf } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list';
@@ -13,86 +12,278 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
import { HostWindowService } from '../../../shared/host-window.service';
import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub';
-import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
+import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { NotificationsServiceStub } from '../../../shared/testing/notifications-service-stub';
+import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
+import { BitstreamFormatSupportLevel } from '../../../core/shared/bitstream-format-support-level';
+import { cold, getTestScheduler, hot } from 'jasmine-marbles';
+import { TestScheduler } from 'rxjs/testing';
describe('BitstreamFormatsComponent', () => {
let comp: BitstreamFormatsComponent;
let fixture: ComponentFixture;
- let registryService: RegistryService;
- const mockFormatsList = [
- {
- shortDescription: 'Unknown',
- description: 'Unknown data format',
- mimetype: 'application/octet-stream',
- supportLevel: 0,
- internal: false,
- extensions: null
- },
- {
- shortDescription: 'License',
- description: 'Item-specific license agreed upon to submission',
- mimetype: 'text/plain; charset=utf-8',
- supportLevel: 1,
- internal: true,
- extensions: null
- },
- {
- shortDescription: 'CC License',
- description: 'Item-specific Creative Commons license agreed upon to submission',
- mimetype: 'text/html; charset=utf-8',
- supportLevel: 2,
- internal: true,
- extensions: null
- },
- {
- shortDescription: 'Adobe PDF',
- description: 'Adobe Portable Document Format',
- mimetype: 'application/pdf',
- supportLevel: 0,
- internal: false,
- extensions: null
- }
- ];
- const mockFormats = createSuccessfulRemoteDataObject$(new PaginatedList(null, mockFormatsList));
- const registryServiceStub = {
- getBitstreamFormats: () => mockFormats
- };
+ let bitstreamFormatService;
+ let scheduler: TestScheduler;
+ let notificationsServiceStub;
+
+ const bitstreamFormat1 = new BitstreamFormat();
+ bitstreamFormat1.uuid = 'test-uuid-1';
+ bitstreamFormat1.id = 'test-uuid-1';
+ bitstreamFormat1.shortDescription = 'Unknown';
+ bitstreamFormat1.description = 'Unknown data format';
+ bitstreamFormat1.mimetype = 'application/octet-stream';
+ bitstreamFormat1.supportLevel = BitstreamFormatSupportLevel.Unknown;
+ bitstreamFormat1.internal = false;
+ bitstreamFormat1.extensions = null;
+
+ const bitstreamFormat2 = new BitstreamFormat();
+ bitstreamFormat2.uuid = 'test-uuid-2';
+ bitstreamFormat2.id = 'test-uuid-2';
+ bitstreamFormat2.shortDescription = 'License';
+ bitstreamFormat2.description = 'Item-specific license agreed upon to submission';
+ bitstreamFormat2.mimetype = 'text/plain; charset=utf-8';
+ bitstreamFormat2.supportLevel = BitstreamFormatSupportLevel.Known;
+ bitstreamFormat2.internal = true;
+ bitstreamFormat2.extensions = null;
+
+ const bitstreamFormat3 = new BitstreamFormat();
+ bitstreamFormat3.uuid = 'test-uuid-3';
+ bitstreamFormat3.id = 'test-uuid-3';
+ bitstreamFormat3.shortDescription = 'CC License';
+ bitstreamFormat3.description = 'Item-specific Creative Commons license agreed upon to submission';
+ bitstreamFormat3.mimetype = 'text/html; charset=utf-8';
+ bitstreamFormat3.supportLevel = BitstreamFormatSupportLevel.Supported;
+ bitstreamFormat3.internal = true;
+ bitstreamFormat3.extensions = null;
+
+ const bitstreamFormat4 = new BitstreamFormat();
+ bitstreamFormat4.uuid = 'test-uuid-4';
+ bitstreamFormat4.id = 'test-uuid-4';
+ bitstreamFormat4.shortDescription = 'Adobe PDF';
+ bitstreamFormat4.description = 'Adobe Portable Document Format';
+ bitstreamFormat4.mimetype = 'application/pdf';
+ bitstreamFormat4.supportLevel = BitstreamFormatSupportLevel.Unknown;
+ bitstreamFormat4.internal = false;
+ bitstreamFormat4.extensions = null;
+
+ const mockFormatsList: BitstreamFormat[] = [
+ bitstreamFormat1,
+ bitstreamFormat2,
+ bitstreamFormat3,
+ bitstreamFormat4
+ ];
+ const mockFormatsRD = new RemoteData(false, false, true, undefined, new PaginatedList(null, mockFormatsList));
+
+ const initAsync = () => {
+ notificationsServiceStub = new NotificationsServiceStub();
+
+ scheduler = getTestScheduler();
+
+ bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
+ findAll: observableOf(mockFormatsRD),
+ find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])),
+ getSelectedBitstreamFormats: hot('a', {a: mockFormatsList}),
+ selectBitstreamFormat: {},
+ deselectBitstreamFormat: {},
+ deselectAllBitstreamFormats: {},
+ delete: observableOf(true),
+ clearBitStreamFormatRequests: observableOf('cleared')
+ });
- beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe],
providers: [
- { provide: RegistryService, useValue: registryServiceStub },
- { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }
+ {provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
+ {provide: HostWindowService, useValue: new HostWindowServiceStub(0)},
+ {provide: NotificationsService, useValue: notificationsServiceStub}
]
}).compileComponents();
- }));
+ };
- beforeEach(() => {
+ const initBeforeEach = () => {
fixture = TestBed.createComponent(BitstreamFormatsComponent);
comp = fixture.componentInstance;
fixture.detectChanges();
- registryService = (comp as any).service;
+ };
+
+ describe('Bitstream format page content', () => {
+ beforeEach(async(initAsync));
+ beforeEach(initBeforeEach);
+
+ it('should contain four formats', () => {
+ const tbody: HTMLElement = fixture.debugElement.query(By.css('#formats>tbody')).nativeElement;
+ expect(tbody.children.length).toBe(4);
+ });
+
+ it('should contain the correct formats', () => {
+ const unknownName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(1) td:nth-child(2)')).nativeElement;
+ expect(unknownName.textContent).toBe('Unknown');
+
+ const licenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(2) td:nth-child(2)')).nativeElement;
+ expect(licenseName.textContent).toBe('License');
+
+ const ccLicenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(3) td:nth-child(2)')).nativeElement;
+ expect(ccLicenseName.textContent).toBe('CC License');
+
+ const adobeName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(4) td:nth-child(2)')).nativeElement;
+ expect(adobeName.textContent).toBe('Adobe PDF');
+ });
});
- it('should contain four formats', () => {
- const tbody: HTMLElement = fixture.debugElement.query(By.css('#formats>tbody')).nativeElement;
- expect(tbody.children.length).toBe(4);
+ describe('selectBitStreamFormat', () => {
+ beforeEach(async(initAsync));
+ beforeEach(initBeforeEach);
+ it('should select a bitstreamFormat if it was selected in the event', () => {
+ const event = {target: {checked: true}};
+
+ comp.selectBitStreamFormat(bitstreamFormat1, event);
+
+ expect(bitstreamFormatService.selectBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat1);
+ });
+ it('should deselect a bitstreamFormat if it is deselected in the event', () => {
+ const event = {target: {checked: false}};
+
+ comp.selectBitStreamFormat(bitstreamFormat1, event);
+
+ expect(bitstreamFormatService.deselectBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat1);
+ });
+ it('should be called when a user clicks a checkbox', () => {
+ spyOn(comp, 'selectBitStreamFormat');
+ const unknownFormat = fixture.debugElement.query(By.css('#formats tr:nth-child(1) input'));
+
+ const event = {target: {checked: true}};
+ unknownFormat.triggerEventHandler('change', event);
+
+ expect(comp.selectBitStreamFormat).toHaveBeenCalledWith(bitstreamFormat1, event);
+ });
});
- it('should contain the correct formats', () => {
- const unknownName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(1) td:nth-child(1)')).nativeElement;
- expect(unknownName.textContent).toBe('Unknown');
+ describe('isSelected', () => {
+ beforeEach(async(initAsync));
+ beforeEach(initBeforeEach);
+ it('should return an observable of true if the provided bistream is in the list returned by the service', () => {
+ const result = comp.isSelected(bitstreamFormat1);
- const licenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(2) td:nth-child(1)')).nativeElement;
- expect(licenseName.textContent).toBe('License');
+ expect(result).toBeObservable(cold('b', {b: true}));
+ });
+ it('should return an observable of false if the provided bistream is not in the list returned by the service', () => {
+ const format = new BitstreamFormat();
+ format.uuid = 'new';
- const ccLicenseName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(3) td:nth-child(1)')).nativeElement;
- expect(ccLicenseName.textContent).toBe('CC License');
+ const result = comp.isSelected(format);
- const adobeName: HTMLElement = fixture.debugElement.query(By.css('#formats tr:nth-child(4) td:nth-child(1)')).nativeElement;
- expect(adobeName.textContent).toBe('Adobe PDF');
+ expect(result).toBeObservable(cold('b', {b: false}));
+ });
});
+ describe('deselectAll', () => {
+ beforeEach(async(initAsync));
+ beforeEach(initBeforeEach);
+ it('should deselect all bitstreamFormats', () => {
+ comp.deselectAll();
+ expect(bitstreamFormatService.deselectAllBitstreamFormats).toHaveBeenCalled();
+ });
+
+ it('should be called when the deselect all button is clicked', () => {
+ spyOn(comp, 'deselectAll');
+ const deselectAllButton = fixture.debugElement.query(By.css('button.deselect'));
+ deselectAllButton.triggerEventHandler('click', null);
+
+ expect(comp.deselectAll).toHaveBeenCalled();
+
+ });
+ });
+
+ describe('deleteFormats success', () => {
+ beforeEach(async(() => {
+ notificationsServiceStub = new NotificationsServiceStub();
+
+ scheduler = getTestScheduler();
+
+ bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
+ findAll: observableOf(mockFormatsRD),
+ find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])),
+ getSelectedBitstreamFormats: observableOf(mockFormatsList),
+ selectBitstreamFormat: {},
+ deselectBitstreamFormat: {},
+ deselectAllBitstreamFormats: {},
+ delete: observableOf(true),
+ clearBitStreamFormatRequests: observableOf('cleared')
+ });
+
+ TestBed.configureTestingModule({
+ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+ declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe],
+ providers: [
+ {provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
+ {provide: HostWindowService, useValue: new HostWindowServiceStub(0)},
+ {provide: NotificationsService, useValue: notificationsServiceStub}
+ ]
+ }).compileComponents();
+ }
+ ));
+
+ beforeEach(initBeforeEach);
+ it('should clear bitstream formats ', () => {
+ comp.deleteFormats();
+
+ expect(bitstreamFormatService.clearBitStreamFormatRequests).toHaveBeenCalled();
+ expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat1);
+ expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat2);
+ expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat3);
+ expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat4);
+
+ expect(notificationsServiceStub.success).toHaveBeenCalledWith('admin.registries.bitstream-formats.delete.success.head',
+ 'admin.registries.bitstream-formats.delete.success.amount');
+ expect(notificationsServiceStub.error).not.toHaveBeenCalled();
+
+ });
+ });
+
+ describe('deleteFormats error', () => {
+ beforeEach(async(() => {
+ notificationsServiceStub = new NotificationsServiceStub();
+
+ scheduler = getTestScheduler();
+
+ bitstreamFormatService = jasmine.createSpyObj('bitstreamFormatService', {
+ findAll: observableOf(mockFormatsRD),
+ find: observableOf(new RemoteData(false, false, true, undefined, mockFormatsList[0])),
+ getSelectedBitstreamFormats: observableOf(mockFormatsList),
+ selectBitstreamFormat: {},
+ deselectBitstreamFormat: {},
+ deselectAllBitstreamFormats: {},
+ delete: observableOf(false),
+ clearBitStreamFormatRequests: observableOf('cleared')
+ });
+
+ TestBed.configureTestingModule({
+ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+ declarations: [BitstreamFormatsComponent, PaginationComponent, EnumKeysPipe],
+ providers: [
+ {provide: BitstreamFormatDataService, useValue: bitstreamFormatService},
+ {provide: HostWindowService, useValue: new HostWindowServiceStub(0)},
+ {provide: NotificationsService, useValue: notificationsServiceStub}
+ ]
+ }).compileComponents();
+ }
+ ));
+
+ beforeEach(initBeforeEach);
+ it('should clear bitstream formats ', () => {
+ comp.deleteFormats();
+
+ expect(bitstreamFormatService.clearBitStreamFormatRequests).toHaveBeenCalled();
+ expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat1);
+ expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat2);
+ expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat3);
+ expect(bitstreamFormatService.delete).toHaveBeenCalledWith(bitstreamFormat4);
+
+ expect(notificationsServiceStub.error).toHaveBeenCalledWith('admin.registries.bitstream-formats.delete.failure.head',
+ 'admin.registries.bitstream-formats.delete.failure.amount');
+ expect(notificationsServiceStub.success).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts
index bc0cbb8da6..cb7aa1ef91 100644
--- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts
@@ -1,10 +1,16 @@
-import { Component } from '@angular/core';
-import { RegistryService } from '../../../core/registry/registry.service';
-import { Observable } from 'rxjs';
+import { Component, OnInit } from '@angular/core';
+import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, zip } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list';
-import { BitstreamFormat } from '../../../core/registry/mock-bitstream-format.model';
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 { map, switchMap, take } from 'rxjs/operators';
+import { hasValue } from '../../../shared/empty.util';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+import { Router } from '@angular/router';
+import { TranslateService } from '@ngx-translate/core';
/**
* This component renders a list of bitstream formats
@@ -13,24 +19,125 @@ import { PaginationComponentOptions } from '../../../shared/pagination/paginatio
selector: 'ds-bitstream-formats',
templateUrl: './bitstream-formats.component.html'
})
-export class BitstreamFormatsComponent {
+export class BitstreamFormatsComponent implements OnInit {
/**
* A paginated list of bitstream formats to be shown on the page
*/
bitstreamFormats: Observable>>;
+ /**
+ * A BehaviourSubject that keeps track of the pageState used to update the currently displayed bitstreamFormats
+ */
+ pageState: BehaviorSubject;
+
+ /**
+ * The current pagination configuration for the page used by the FindAll method
+ * Currently simply renders all bitstream formats
+ */
+ config: FindAllOptions = Object.assign(new FindAllOptions(), {
+ elementsPerPage: 20
+ });
+
/**
* The current pagination configuration for the page
* Currently simply renders all bitstream formats
*/
- config: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
+ pageConfig: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
id: 'registry-bitstreamformats-pagination',
- pageSize: 10000
+ pageSize: 20
});
- constructor(private registryService: RegistryService) {
- this.updateFormats();
+ constructor(private notificationsService: NotificationsService,
+ private router: Router,
+ private translateService: TranslateService,
+ private bitstreamFormatService: BitstreamFormatDataService) {
+ }
+
+ /**
+ * Deletes the currently selected formats from the registry and updates the presented list
+ */
+ deleteFormats() {
+ this.bitstreamFormatService.clearBitStreamFormatRequests().subscribe();
+ this.bitstreamFormatService.getSelectedBitstreamFormats().pipe(take(1)).subscribe(
+ (formats) => {
+ const tasks$ = [];
+ for (const format of formats) {
+ if (hasValue(format.id)) {
+ tasks$.push(this.bitstreamFormatService.delete(format));
+ }
+ }
+ zip(...tasks$).subscribe((results: boolean[]) => {
+ const successResponses = results.filter((result: boolean) => result);
+ const failedResponses = results.filter((result: boolean) => !result);
+ if (successResponses.length > 0) {
+ this.showNotification(true, successResponses.length);
+ }
+ if (failedResponses.length > 0) {
+ this.showNotification(false, failedResponses.length);
+ }
+
+ this.deselectAll();
+
+ this.router.navigate([], {
+ queryParams: Object.assign({}, { page: 1 }),
+ queryParamsHandling: 'merge'
+ }); });
+ }
+ );
+ }
+
+ /**
+ * Deselects all selecetd bitstream formats
+ */
+ deselectAll() {
+ this.bitstreamFormatService.deselectAllBitstreamFormats();
+ }
+
+ /**
+ * Checks whether a given bitstream format is selected in the list (checkbox)
+ * @param bitstreamFormat
+ */
+ isSelected(bitstreamFormat: BitstreamFormat): Observable {
+ return this.bitstreamFormatService.getSelectedBitstreamFormats().pipe(
+ map((bitstreamFormats: BitstreamFormat[]) => {
+ return bitstreamFormats.find((selectedFormat) => selectedFormat.id === bitstreamFormat.id) != null;
+ })
+ );
+ }
+
+ /**
+ * Selects or deselects a bitstream format based on the checkbox state
+ * @param bitstreamFormat
+ * @param event
+ */
+ selectBitStreamFormat(bitstreamFormat: BitstreamFormat, event) {
+ event.target.checked ?
+ this.bitstreamFormatService.selectBitstreamFormat(bitstreamFormat) :
+ this.bitstreamFormatService.deselectBitstreamFormat(bitstreamFormat);
+ }
+
+ /**
+ * Show notifications for an amount of deleted bitstream formats
+ * @param success Whether or not the notification should be a success message (error message when false)
+ * @param amount The amount of deleted bitstream formats
+ */
+ private showNotification(success: boolean, amount: number) {
+ const prefix = 'admin.registries.bitstream-formats.delete';
+ const suffix = success ? 'success' : 'failure';
+
+ const messages = observableCombineLatest(
+ this.translateService.get(`${prefix}.${suffix}.head`),
+ this.translateService.get(`${prefix}.${suffix}.amount`, {amount: amount})
+ );
+ messages.subscribe(([head, content]) => {
+
+ if (success) {
+ this.notificationsService.success(head, content);
+ } else {
+ this.notificationsService.error(head, content);
+ }
+ });
}
/**
@@ -38,14 +145,26 @@ export class BitstreamFormatsComponent {
* @param event The page change event
*/
onPageChange(event) {
- this.config.currentPage = event;
- this.updateFormats();
+ this.config = Object.assign(new FindAllOptions(), this.config, {
+ currentPage: event,
+ });
+ this.pageConfig.currentPage = event;
+ this.pageState.next('pageChange');
+ }
+
+ ngOnInit(): void {
+ this.pageState = new BehaviorSubject('init');
+ this.bitstreamFormats = this.pageState.pipe(
+ switchMap(() => {
+ return this.updateFormats()
+ ;
+ }));
}
/**
- * Method to update the bitstream formats that are shown
+ * Finds all formats based on the current config
*/
private updateFormats() {
- this.bitstreamFormats = this.registryService.getBitstreamFormats(this.config);
+ return this.bitstreamFormatService.findAll(this.config);
}
}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.module.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.module.ts
new file mode 100644
index 0000000000..0800c50169
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.module.ts
@@ -0,0 +1,30 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { RouterModule } from '@angular/router';
+import { TranslateModule } from '@ngx-translate/core';
+import { BitstreamFormatsComponent } from './bitstream-formats.component';
+import { SharedModule } from '../../../shared/shared.module';
+import { FormatFormComponent } from './format-form/format-form.component';
+import { EditBitstreamFormatComponent } from './edit-bitstream-format/edit-bitstream-format.component';
+import { BitstreamFormatsRoutingModule } from './bitstream-formats-routing.module';
+import { AddBitstreamFormatComponent } from './add-bitstream-format/add-bitstream-format.component';
+
+@NgModule({
+ imports: [
+ CommonModule,
+ SharedModule,
+ RouterModule,
+ TranslateModule,
+ BitstreamFormatsRoutingModule
+ ],
+ declarations: [
+ BitstreamFormatsComponent,
+ EditBitstreamFormatComponent,
+ AddBitstreamFormatComponent,
+ FormatFormComponent
+ ],
+ entryComponents: []
+})
+export class BitstreamFormatsModule {
+
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.resolver.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.resolver.ts
new file mode 100644
index 0000000000..f6eef741fd
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.resolver.ts
@@ -0,0 +1,31 @@
+import { Injectable } from '@angular/core';
+import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
+import { Observable } from 'rxjs';
+import { find } from 'rxjs/operators';
+import { RemoteData } from '../../../core/data/remote-data';
+import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
+import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
+import { hasValue } from '../../../shared/empty.util';
+
+/**
+ * This class represents a resolver that requests a specific bitstreamFormat before the route is activated
+ */
+@Injectable()
+export class BitstreamFormatsResolver implements Resolve> {
+ constructor(private bitstreamFormatDataService: BitstreamFormatDataService) {
+ }
+
+ /**
+ * Method for resolving an bitstreamFormat based on the parameters in the current route
+ * @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
+ * @param {RouterStateSnapshot} state The current RouterStateSnapshot
+ * @returns Observable<> Emits the found bitstreamFormat based on the parameters in the current route,
+ * or an error if something went wrong
+ */
+ resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable> {
+ return this.bitstreamFormatDataService.findById(route.params.id)
+ .pipe(
+ find((RD) => hasValue(RD.error) || RD.hasSucceeded),
+ );
+ }
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.html b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.html
new file mode 100644
index 0000000000..f57ec9cd38
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.html
@@ -0,0 +1,11 @@
+
\ No newline at end of file
diff --git a/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts
new file mode 100644
index 0000000000..cfa93a15a8
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.spec.ts
@@ -0,0 +1,123 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { CommonModule } from '@angular/common';
+import { RouterTestingModule } from '@angular/router/testing';
+import { TranslateModule } from '@ngx-translate/core';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+import { ActivatedRoute, Router } from '@angular/router';
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { RouterStub } from '../../../../shared/testing/router-stub';
+import { of as observableOf } from 'rxjs';
+import { RemoteData } from '../../../../core/data/remote-data';
+import { EditBitstreamFormatComponent } from './edit-bitstream-format.component';
+import { NotificationsService } from '../../../../shared/notifications/notifications.service';
+import { NotificationsServiceStub } from '../../../../shared/testing/notifications-service-stub';
+import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
+import { RestResponse } from '../../../../core/cache/response.models';
+import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
+import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
+import { ResourceType } from '../../../../core/shared/resource-type';
+
+describe('EditBitstreamFormatComponent', () => {
+ let comp: EditBitstreamFormatComponent;
+ let fixture: ComponentFixture;
+
+ const bitstreamFormat = new BitstreamFormat();
+ bitstreamFormat.uuid = 'test-uuid-1';
+ bitstreamFormat.id = 'test-uuid-1';
+ bitstreamFormat.shortDescription = 'Unknown';
+ bitstreamFormat.description = 'Unknown data format';
+ bitstreamFormat.mimetype = 'application/octet-stream';
+ bitstreamFormat.supportLevel = BitstreamFormatSupportLevel.Unknown;
+ bitstreamFormat.internal = false;
+ bitstreamFormat.extensions = null;
+
+ const routeStub = {
+ data: observableOf({
+ bitstreamFormat: new RemoteData(false, false, true, null, bitstreamFormat)
+ })
+ };
+
+ let router;
+ let notificationService: NotificationsServiceStub;
+ let bitstreamFormatDataService: BitstreamFormatDataService;
+
+ const initAsync = () => {
+ router = new RouterStub();
+ notificationService = new NotificationsServiceStub();
+ bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
+ updateBitstreamFormat: observableOf(new RestResponse(true, 200, 'Success'))
+ });
+
+ TestBed.configureTestingModule({
+ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+ declarations: [EditBitstreamFormatComponent],
+ providers: [
+ {provide: ActivatedRoute, useValue: routeStub},
+ {provide: Router, useValue: router},
+ {provide: NotificationsService, useValue: notificationService},
+ {provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService},
+ ],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+ }).compileComponents();
+ };
+
+ const initBeforeEach = () => {
+ fixture = TestBed.createComponent(EditBitstreamFormatComponent);
+ comp = fixture.componentInstance;
+
+ fixture.detectChanges();
+ };
+
+ describe('init', () => {
+ beforeEach(async(initAsync));
+ beforeEach(initBeforeEach);
+ it('should initialise the bitstreamFormat based on the route', () => {
+
+ comp.bitstreamFormatRD$.subscribe((format: RemoteData) => {
+ expect(format).toEqual(new RemoteData(false, false, true, null, bitstreamFormat));
+ });
+ });
+ });
+ describe('updateFormat success', () => {
+ beforeEach(async(initAsync));
+ beforeEach(initBeforeEach);
+ it('should send the updated form to the service, show a notification and navigate to ', () => {
+ comp.updateFormat(bitstreamFormat);
+
+ expect(bitstreamFormatDataService.updateBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat);
+ expect(notificationService.success).toHaveBeenCalled();
+ expect(router.navigate).toHaveBeenCalledWith(['/admin/registries/bitstream-formats']);
+
+ });
+ });
+ describe('updateFormat error', () => {
+ beforeEach(async( () => {
+ router = new RouterStub();
+ notificationService = new NotificationsServiceStub();
+ bitstreamFormatDataService = jasmine.createSpyObj('bitstreamFormatDataService', {
+ updateBitstreamFormat: observableOf(new RestResponse(false, 400, 'Bad Request'))
+ });
+
+ TestBed.configureTestingModule({
+ imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+ declarations: [EditBitstreamFormatComponent],
+ providers: [
+ {provide: ActivatedRoute, useValue: routeStub},
+ {provide: Router, useValue: router},
+ {provide: NotificationsService, useValue: notificationService},
+ {provide: BitstreamFormatDataService, useValue: bitstreamFormatDataService},
+ ],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+ }).compileComponents();
+ }));
+ beforeEach(initBeforeEach);
+ it('should send the updated form to the service, show a notification and navigate to ', () => {
+ comp.updateFormat(bitstreamFormat);
+
+ expect(bitstreamFormatDataService.updateBitstreamFormat).toHaveBeenCalledWith(bitstreamFormat);
+ expect(notificationService.error).toHaveBeenCalled();
+ expect(router.navigate).not.toHaveBeenCalled();
+
+ });
+ });
+});
diff --git a/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.ts b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.ts
new file mode 100644
index 0000000000..0fdcc75689
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/edit-bitstream-format/edit-bitstream-format.component.ts
@@ -0,0 +1,62 @@
+import { map, take } from 'rxjs/operators';
+import { ActivatedRoute, Router } from '@angular/router';
+import { Observable } from 'rxjs';
+import { Component, OnInit } from '@angular/core';
+import { RemoteData } from '../../../../core/data/remote-data';
+import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
+import { BitstreamFormatDataService } from '../../../../core/data/bitstream-format-data.service';
+import { RestResponse } from '../../../../core/cache/response.models';
+import { NotificationsService } from '../../../../shared/notifications/notifications.service';
+import { getBitstreamFormatsModulePath } from '../../admin-registries-routing.module';
+import { TranslateService } from '@ngx-translate/core';
+
+/**
+ * This component renders the edit page of a bitstream format.
+ * The route parameter 'id' is used to request the bitstream format.
+ */
+@Component({
+ selector: 'ds-edit-bitstream-format',
+ templateUrl: './edit-bitstream-format.component.html',
+})
+export class EditBitstreamFormatComponent implements OnInit {
+
+ /**
+ * The bitstream format wrapped in a remote-data object
+ */
+ bitstreamFormatRD$: Observable>;
+
+ constructor(
+ private route: ActivatedRoute,
+ private router: Router,
+ private notificationService: NotificationsService,
+ private translateService: TranslateService,
+ private bitstreamFormatDataService: BitstreamFormatDataService,
+ ) {
+ }
+
+ ngOnInit(): void {
+ this.bitstreamFormatRD$ = this.route.data.pipe(
+ map((data) => data.bitstreamFormat as RemoteData)
+ );
+ }
+
+ /**
+ * Updates the bitstream format based on the provided bitstream format emitted by the form.
+ * When successful, a success notification will be shown and the user will be navigated back to the overview page.
+ * When failed, an error notification will be shown.
+ */
+ updateFormat(bitstreamFormat: BitstreamFormat) {
+ this.bitstreamFormatDataService.updateBitstreamFormat(bitstreamFormat).pipe(take(1)
+ ).subscribe((response: RestResponse) => {
+ if (response.isSuccessful) {
+ this.notificationService.success(this.translateService.get('admin.registries.bitstream-formats.edit.success.head'),
+ this.translateService.get('admin.registries.bitstream-formats.edit.success.content'));
+ this.router.navigate([getBitstreamFormatsModulePath()]);
+ } else {
+ this.notificationService.error('admin.registries.bitstream-formats.edit.failure.head',
+ 'admin.registries.bitstream-formats.create.edit.content');
+ }
+ }
+ );
+ }
+}
diff --git a/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.html b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.html
new file mode 100644
index 0000000000..be6ebf2599
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.html
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts
new file mode 100644
index 0000000000..2870705fc8
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.spec.ts
@@ -0,0 +1,104 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { CommonModule } from '@angular/common';
+import { RouterTestingModule } from '@angular/router/testing';
+import { TranslateModule } from '@ngx-translate/core';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+import { RouterStub } from '../../../../shared/testing/router-stub';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { Router } from '@angular/router';
+import { FormatFormComponent } from './format-form.component';
+import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
+import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
+import { DynamicCheckboxModel, DynamicFormArrayModel, DynamicInputModel } from '@ng-dynamic-forms/core';
+import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { isEmpty } from '../../../../shared/empty.util';
+
+describe('FormatFormComponent', () => {
+ let comp: FormatFormComponent;
+ let fixture: ComponentFixture;
+
+ const router = new RouterStub();
+
+ const bitstreamFormat = new BitstreamFormat();
+ bitstreamFormat.uuid = 'test-uuid-1';
+ bitstreamFormat.id = 'test-uuid-1';
+ bitstreamFormat.shortDescription = 'Unknown';
+ bitstreamFormat.description = 'Unknown data format';
+ bitstreamFormat.mimetype = 'application/octet-stream';
+ bitstreamFormat.supportLevel = BitstreamFormatSupportLevel.Unknown;
+ bitstreamFormat.internal = false;
+ bitstreamFormat.extensions = [];
+
+ const submittedBitstreamFormat = new BitstreamFormat();
+ submittedBitstreamFormat.id = bitstreamFormat.id;
+ submittedBitstreamFormat.shortDescription = bitstreamFormat.shortDescription;
+ submittedBitstreamFormat.mimetype = bitstreamFormat.mimetype;
+ submittedBitstreamFormat.description = bitstreamFormat.description;
+ submittedBitstreamFormat.supportLevel = bitstreamFormat.supportLevel;
+ submittedBitstreamFormat.internal = bitstreamFormat.internal;
+ submittedBitstreamFormat.extensions = bitstreamFormat.extensions;
+
+ const initAsync = () => {
+ TestBed.configureTestingModule({
+ imports: [CommonModule, RouterTestingModule.withRoutes([]), ReactiveFormsModule, FormsModule, TranslateModule.forRoot(), NgbModule.forRoot()],
+ declarations: [FormatFormComponent],
+ providers: [
+ {provide: Router, useValue: router},
+ ],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+ }).compileComponents();
+ };
+
+ const initBeforeEach = () => {
+ fixture = TestBed.createComponent(FormatFormComponent);
+ comp = fixture.componentInstance;
+
+ comp.bitstreamFormat = bitstreamFormat;
+ fixture.detectChanges();
+ };
+
+ describe('initialise', () => {
+ beforeEach(async(initAsync));
+ beforeEach(initBeforeEach);
+ it('should initialises the values in the form', () => {
+
+ expect((comp.formModel[0] as DynamicInputModel).value).toBe(bitstreamFormat.shortDescription);
+ expect((comp.formModel[1] as DynamicInputModel).value).toBe(bitstreamFormat.mimetype);
+ expect((comp.formModel[2] as DynamicInputModel).value).toBe(bitstreamFormat.description);
+ expect((comp.formModel[3] as DynamicInputModel).value).toBe(bitstreamFormat.supportLevel);
+ expect((comp.formModel[4] as DynamicCheckboxModel).value).toBe(bitstreamFormat.internal);
+
+ const formArray = (comp.formModel[5] as DynamicFormArrayModel);
+ const extensions = [];
+ for (let i = 0; i < formArray.groups.length; i++) {
+ const value = (formArray.get(i).get(0) as DynamicInputModel).value;
+ if (!isEmpty(value)) {
+ extensions.push((formArray.get(i).get(0) as DynamicInputModel).value);
+ }
+ }
+
+ expect(extensions).toEqual(bitstreamFormat.extensions);
+
+ });
+ });
+ describe('onSubmit', () => {
+ beforeEach(async(initAsync));
+ beforeEach(initBeforeEach);
+
+ it('should emit the bitstreamFormat currently present in the form', () => {
+ spyOn(comp.updatedFormat, 'emit');
+ comp.onSubmit();
+
+ expect(comp.updatedFormat.emit).toHaveBeenCalledWith(submittedBitstreamFormat);
+ });
+ });
+ describe('onCancel', () => {
+ beforeEach(async(initAsync));
+ beforeEach(initBeforeEach);
+
+ it('should navigate back to the bitstream overview', () => {
+ comp.onCancel();
+ expect(router.navigate).toHaveBeenCalledWith(['/admin/registries/bitstream-formats']);
+ });
+ });
+});
diff --git a/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.ts b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.ts
new file mode 100644
index 0000000000..505ccccd91
--- /dev/null
+++ b/src/app/+admin/admin-registries/bitstream-formats/format-form/format-form.component.ts
@@ -0,0 +1,194 @@
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { BitstreamFormat } from '../../../../core/shared/bitstream-format.model';
+import { BitstreamFormatSupportLevel } from '../../../../core/shared/bitstream-format-support-level';
+import {
+ DynamicCheckboxModel,
+ DynamicFormArrayModel,
+ DynamicFormControlLayout, DynamicFormControlLayoutConfig,
+ DynamicFormControlModel,
+ DynamicFormService,
+ DynamicInputModel,
+ DynamicSelectModel,
+ DynamicTextAreaModel
+} from '@ng-dynamic-forms/core';
+import { Router } from '@angular/router';
+import { getBitstreamFormatsModulePath } from '../../admin-registries-routing.module';
+import { hasValue, isEmpty } from '../../../../shared/empty.util';
+import { TranslateService } from '@ngx-translate/core';
+
+/**
+ * The component responsible for rendering the form to create/edit a bitstream format
+ */
+@Component({
+ selector: 'ds-bitstream-format-form',
+ templateUrl: './format-form.component.html'
+})
+export class FormatFormComponent implements OnInit {
+
+ /**
+ * The current bitstream format
+ * This can either be and existing one or a new one
+ */
+ @Input() bitstreamFormat: BitstreamFormat = new BitstreamFormat();
+
+ /**
+ * EventEmitter that will emit the updated bitstream format
+ */
+ @Output() updatedFormat: EventEmitter = new EventEmitter();
+
+ /**
+ * The different supported support level of the bitstream format
+ */
+ supportLevelOptions = [{label: BitstreamFormatSupportLevel.Known, value: BitstreamFormatSupportLevel.Known},
+ {label: BitstreamFormatSupportLevel.Unknown, value: BitstreamFormatSupportLevel.Unknown},
+ {label: BitstreamFormatSupportLevel.Supported, value: BitstreamFormatSupportLevel.Supported}];
+
+ /**
+ * Styling element for repeatable field
+ */
+ arrayElementLayout: DynamicFormControlLayout = {
+ grid: {
+ group: 'form-row',
+ },
+ };
+
+ /**
+ * Styling element for element of repeatable field
+ */
+ arrayInputElementLayout: DynamicFormControlLayout = {
+ grid: {
+ host: 'col'
+ }
+ };
+
+ /**
+ * The form model representing the bitstream format
+ */
+ formModel: DynamicFormControlModel[] = [
+ new DynamicInputModel({
+ id: 'shortDescription',
+ name: 'shortDescription',
+ label: 'admin.registries.bitstream-formats.edit.shortDescription.label',
+ hint: 'admin.registries.bitstream-formats.edit.shortDescription.hint',
+ required: true,
+ validators: {
+ required: null
+ },
+ errorMessages: {
+ required: 'Please enter a name for this bitstream format'
+ },
+ }),
+ new DynamicInputModel({
+ id: 'mimetype',
+ name: 'mimetype',
+ label: 'admin.registries.bitstream-formats.edit.mimetype.label',
+ hint: 'admin.registries.bitstream-formats.edit.mimetype.hint',
+
+ }),
+ new DynamicTextAreaModel({
+ id: 'description',
+ name: 'description',
+ label: 'admin.registries.bitstream-formats.edit.description.label',
+ hint: 'admin.registries.bitstream-formats.edit.description.hint',
+
+ }),
+ new DynamicSelectModel({
+ id: 'supportLevel',
+ name: 'supportLevel',
+ options: this.supportLevelOptions,
+ label: 'admin.registries.bitstream-formats.edit.supportLevel.label',
+ hint: 'admin.registries.bitstream-formats.edit.supportLevel.hint',
+ value: this.supportLevelOptions[0].value
+
+ }),
+ new DynamicCheckboxModel({
+ id: 'internal',
+ name: 'internal',
+ label: 'Internal',
+ hint: 'admin.registries.bitstream-formats.edit.internal.hint',
+ }),
+ new DynamicFormArrayModel({
+ id: 'extensions',
+ name: 'extensions',
+ label: 'admin.registries.bitstream-formats.edit.extensions.label',
+ groupFactory: () => [
+ new DynamicInputModel({
+ id: 'extension',
+ placeholder: 'admin.registries.bitstream-formats.edit.extensions.placeholder',
+ }, this.arrayInputElementLayout)
+ ]
+ }, this.arrayElementLayout),
+ ];
+
+ constructor(private dynamicFormService: DynamicFormService,
+ private translateService: TranslateService,
+ private router: Router) {
+
+ }
+
+ ngOnInit(): void {
+
+ this.initValues();
+ }
+
+ /**
+ * Initializes the form based on the provided bitstream format
+ */
+ initValues() {
+ this.formModel.forEach(
+ (fieldModel: DynamicFormControlModel) => {
+ if (fieldModel.name === 'extensions') {
+ if (hasValue(this.bitstreamFormat.extensions)) {
+ const extenstions = this.bitstreamFormat.extensions;
+ const formArray = (fieldModel as DynamicFormArrayModel);
+ for (let i = 0; i < extenstions.length; i++) {
+ formArray.insertGroup(i).group[0] = new DynamicInputModel({
+ id: `extension-${i}`,
+ value: extenstions[i]
+ }, this.arrayInputElementLayout);
+ }
+ }
+ } else {
+ if (hasValue(this.bitstreamFormat[fieldModel.name])) {
+ (fieldModel as DynamicInputModel).value = this.bitstreamFormat[fieldModel.name];
+ }
+ }
+ });
+ }
+
+ /**
+ * Creates an updated bistream format based on the current values in the form
+ * Emits the updated bitstream format trouhg the updatedFormat emitter
+ */
+ onSubmit() {
+ const updatedBitstreamFormat = Object.assign(new BitstreamFormat(),
+ {
+ id: this.bitstreamFormat.id
+ });
+
+ this.formModel.forEach(
+ (fieldModel: DynamicFormControlModel) => {
+ if (fieldModel.name === 'extensions') {
+ const formArray = (fieldModel as DynamicFormArrayModel);
+ const extensions = [];
+ for (let i = 0; i < formArray.groups.length; i++) {
+ const value = (formArray.get(i).get(0) as DynamicInputModel).value;
+ if (!isEmpty(value)) {
+ extensions.push((formArray.get(i).get(0) as DynamicInputModel).value);
+ }
+ }
+ updatedBitstreamFormat.extensions = extensions;
+ } else {
+ updatedBitstreamFormat[fieldModel.name] = (fieldModel as DynamicInputModel).value;
+ }
+ });
+ this.updatedFormat.emit(updatedBitstreamFormat);
+ }
+
+ /**
+ * Cancels the edit/create action of the bitstream format and navigates back to the bitstream format registry
+ */
+ onCancel() {
+ this.router.navigate([getBitstreamFormatsModulePath()]);
+ }
+}
diff --git a/src/app/+admin/admin-routing.module.ts b/src/app/+admin/admin-routing.module.ts
index 71af51c683..2003ecf124 100644
--- a/src/app/+admin/admin-routing.module.ts
+++ b/src/app/+admin/admin-routing.module.ts
@@ -1,11 +1,19 @@
import { RouterModule } from '@angular/router';
import { NgModule } from '@angular/core';
+import { URLCombiner } from '../core/url-combiner/url-combiner';
+import { getAdminModulePath } from '../app-routing.module';
+
+const REGISTRIES_MODULE_PATH = 'registries';
+
+export function getRegistriesModulePath() {
+ return new URLCombiner(getAdminModulePath(), REGISTRIES_MODULE_PATH).toString();
+}
@NgModule({
imports: [
RouterModule.forChild([
{
- path: 'registries',
+ path: REGISTRIES_MODULE_PATH,
loadChildren: './admin-registries/admin-registries.module#AdminRegistriesModule'
}
])
diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.html b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.html
new file mode 100644
index 0000000000..af4153220f
--- /dev/null
+++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.html
@@ -0,0 +1,57 @@
+
+
+
+
{{'collection.edit.item-mapper.head' | translate}}
+
+
{{'collection.edit.item-mapper.description' | translate}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{'collection.edit.item-mapper.no-search' | translate}}
+
+
+
+
+
+
+
diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.scss b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.scss
new file mode 100644
index 0000000000..50be6f5ad0
--- /dev/null
+++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.scss
@@ -0,0 +1 @@
+@import '../../../styles/variables.scss';
diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts
new file mode 100644
index 0000000000..0bbfb30821
--- /dev/null
+++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.spec.ts
@@ -0,0 +1,214 @@
+import { CollectionItemMapperComponent } from './collection-item-mapper.component';
+import { async, ComponentFixture, fakeAsync, TestBed, tick } 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 { HostWindowService } from '../../shared/host-window.service';
+import { HostWindowServiceStub } from '../../shared/testing/host-window-service-stub';
+import { By } from '@angular/platform-browser';
+import { PaginatedList } from '../../core/data/paginated-list';
+import { PageInfo } from '../../core/shared/page-info.model';
+import { CollectionDataService } from '../../core/data/collection-data.service';
+import { PaginationComponent } from '../../shared/pagination/pagination.component';
+import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe';
+import { ItemSelectComponent } from '../../shared/object-select/item-select/item-select.component';
+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';
+
+describe('CollectionItemMapperComponent', () => {
+ let comp: CollectionItemMapperComponent;
+ let fixture: ComponentFixture;
+
+ let route: ActivatedRoute;
+ let router: Router;
+ let searchConfigService: SearchConfigurationService;
+ let searchService: SearchService;
+ let notificationsService: NotificationsService;
+ let itemDataService: ItemDataService;
+
+ const mockCollection: Collection = Object.assign(new Collection(), {
+ id: 'ce41d451-97ed-4a9c-94a1-7de34f16a9f4',
+ name: 'test-collection'
+ });
+ const mockCollectionRD: RemoteData = new RemoteData(false, false, true, null, mockCollection);
+ const mockSearchOptions = of(new PaginatedSearchOptions({
+ pagination: Object.assign(new PaginationComponentOptions(), {
+ id: 'search-page-configuration',
+ pageSize: 10,
+ currentPage: 1
+ }),
+ sort: new SortOptions('dc.title', SortDirection.ASC),
+ scope: mockCollection.id
+ }));
+ const url = 'http://test.url';
+ const urlWithParam = url + '?param=value';
+ const routerStub = Object.assign(new RouterStub(), {
+ url: urlWithParam,
+ navigateByUrl: {},
+ navigate: {}
+ });
+ const searchConfigServiceStub = {
+ paginatedSearchOptions: mockSearchOptions
+ };
+ const itemDataServiceStub = {
+ mapToCollection: () => of(new RestResponse(true, 200, 'OK'))
+ };
+ const activatedRouteStub = new ActivatedRouteStub({}, { collection: mockCollectionRD });
+ const translateServiceStub = {
+ get: () => of('test-message of collection ' + mockCollection.name),
+ onLangChange: new EventEmitter(),
+ onTranslationChange: new EventEmitter(),
+ onDefaultLangChange: new EventEmitter()
+ };
+ const emptyList = new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []));
+ const searchServiceStub = Object.assign(new SearchServiceStub(), {
+ search: () => of(emptyList),
+ /* tslint:disable:no-empty */
+ clearDiscoveryRequests: () => {}
+ /* tslint:enable:no-empty */
+ });
+ const collectionDataServiceStub = {
+ getMappedItems: () => of(emptyList),
+ /* tslint:disable:no-empty */
+ clearMappedItemsRequests: () => {}
+ /* tslint:enable:no-empty */
+ };
+ const routeServiceStub = {
+ getRouteParameterValue: () => {
+ return observableOf('');
+ },
+ getQueryParameterValue: () => {
+ return observableOf('')
+ },
+ getQueryParamsWithPrefix: () => {
+ return observableOf('')
+ }
+ };
+ const fixedFilterServiceStub = {
+ getQueryByFilterName: () => {
+ return observableOf('')
+ }
+ };
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+ declarations: [CollectionItemMapperComponent, ItemSelectComponent, SearchFormComponent, PaginationComponent, EnumKeysPipe, VarDirective, ErrorComponent, LoadingComponent],
+ providers: [
+ { provide: ActivatedRoute, useValue: activatedRouteStub },
+ { provide: Router, useValue: routerStub },
+ { provide: SearchConfigurationService, useValue: searchConfigServiceStub },
+ { provide: SearchService, useValue: searchServiceStub },
+ { provide: NotificationsService, useValue: new NotificationsServiceStub() },
+ { provide: ItemDataService, useValue: itemDataServiceStub },
+ { provide: CollectionDataService, useValue: collectionDataServiceStub },
+ { provide: TranslateService, useValue: translateServiceStub },
+ { provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
+ { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() },
+ { provide: RouteService, useValue: routeServiceStub },
+ { provide: SearchFixedFilterService, useValue: fixedFilterServiceStub }
+ ]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(CollectionItemMapperComponent);
+ comp = fixture.componentInstance;
+ fixture.detectChanges();
+ route = (comp as any).route;
+ router = (comp as any).router;
+ searchConfigService = (comp as any).searchConfigService;
+ searchService = (comp as any).searchService;
+ notificationsService = (comp as any).notificationsService;
+ itemDataService = (comp as any).itemDataService;
+ });
+
+ it('should display the correct collection name', () => {
+ const name: HTMLElement = fixture.debugElement.query(By.css('#collection-name')).nativeElement;
+ expect(name.innerHTML).toContain(mockCollection.name);
+ });
+
+ describe('mapItems', () => {
+ const ids = ['id1', 'id2', 'id3', 'id4'];
+
+ it('should display a success message if at least one mapping was successful', () => {
+ comp.mapItems(ids);
+ expect(notificationsService.success).toHaveBeenCalled();
+ expect(notificationsService.error).not.toHaveBeenCalled();
+ });
+
+ it('should display an error message if at least one mapping was unsuccessful', () => {
+ spyOn(itemDataService, 'mapToCollection').and.returnValue(of(new RestResponse(false, 404, 'Not Found')));
+ comp.mapItems(ids);
+ expect(notificationsService.success).not.toHaveBeenCalled();
+ expect(notificationsService.error).toHaveBeenCalled();
+ });
+ });
+
+ describe('tabChange', () => {
+ beforeEach(() => {
+ spyOn(routerStub, 'navigateByUrl');
+ comp.tabChange({});
+ });
+
+ it('should navigate to the same page to remove parameters', () => {
+ expect(router.navigateByUrl).toHaveBeenCalledWith(url);
+ });
+ });
+
+ describe('buildQuery', () => {
+ const query = 'query';
+ const expected = `-location.coll:\"${mockCollection.id}\" AND ${query}`;
+
+ let result;
+
+ beforeEach(() => {
+ result = comp.buildQuery(mockCollection.id, query);
+ });
+
+ it('should build a solr query to exclude the provided collection', () => {
+ expect(result).toEqual(expected);
+ })
+ });
+
+ describe('onCancel', () => {
+ beforeEach(() => {
+ spyOn(routerStub, 'navigate');
+ comp.onCancel();
+ });
+
+ it('should navigate to the collection page', () => {
+ expect(router.navigate).toHaveBeenCalledWith(['/collections/', mockCollection.id]);
+ });
+ });
+
+});
diff --git a/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts
new file mode 100644
index 0000000000..750578cc35
--- /dev/null
+++ b/src/app/+collection-page/collection-item-mapper/collection-item-mapper.component.ts
@@ -0,0 +1,256 @@
+import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
+
+import { ChangeDetectionStrategy, Component, Inject, OnInit, ViewChild } from '@angular/core';
+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 { 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';
+import { NotificationsService } from '../../shared/notifications/notifications.service';
+import { ItemDataService } from '../../core/data/item-data.service';
+import { TranslateService } from '@ngx-translate/core';
+import { CollectionDataService } from '../../core/data/collection-data.service';
+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';
+
+@Component({
+ selector: 'ds-collection-item-mapper',
+ styleUrls: ['./collection-item-mapper.component.scss'],
+ templateUrl: './collection-item-mapper.component.html',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ animations: [
+ fadeIn,
+ fadeInOut
+ ],
+ providers: [
+ {
+ provide: SEARCH_CONFIG_SERVICE,
+ useClass: SearchConfigurationService
+ }
+ ]
+})
+/**
+ * Component used to map items to a collection
+ */
+export class CollectionItemMapperComponent implements OnInit {
+
+ /**
+ * A view on the tabset element
+ * Used to switch tabs programmatically
+ */
+ @ViewChild('tabs') tabs;
+
+ /**
+ * The collection to map items to
+ */
+ collectionRD$: Observable>;
+
+ /**
+ * Search options
+ */
+ searchOptions$: Observable;
+
+ /**
+ * List of items to show under the "Browse" tab
+ * Items inside the collection
+ */
+ collectionItemsRD$: Observable>>;
+
+ /**
+ * List of items to show under the "Map" tab
+ * Items outside the collection
+ */
+ mappedItemsRD$: Observable>>;
+
+ /**
+ * Sort on title ASC by default
+ * @type {SortOptions}
+ */
+ defaultSortOptions: SortOptions = new SortOptions('dc.title', SortDirection.ASC);
+
+ /**
+ * Firing this observable (shouldUpdate$.next(true)) forces the two lists to reload themselves
+ * Usually fired after the lists their cache is cleared (to force a new request to the REST API)
+ */
+ shouldUpdate$: BehaviorSubject;
+
+ /**
+ * Track whether at least one search has been performed or not
+ * As soon as at least one search has been performed, we display the search results
+ */
+ performedSearch = false;
+
+ constructor(private route: ActivatedRoute,
+ private router: Router,
+ @Inject(SEARCH_CONFIG_SERVICE) private searchConfigService: SearchConfigurationService,
+ private searchService: SearchService,
+ private notificationsService: NotificationsService,
+ private itemDataService: ItemDataService,
+ private collectionDataService: CollectionDataService,
+ private translateService: TranslateService) {
+ }
+
+ ngOnInit(): void {
+ this.collectionRD$ = this.route.data.pipe(map((data) => data.collection)).pipe(getSucceededRemoteData()) as Observable>;
+ this.searchOptions$ = this.searchConfigService.paginatedSearchOptions;
+ this.loadItemLists();
+ }
+
+ /**
+ * Load collectionItemsRD$ with a fixed scope to only obtain the items this collection owns
+ * Load mappedItemsRD$ to only obtain items this collection doesn't own
+ */
+ loadItemLists() {
+ this.shouldUpdate$ = new BehaviorSubject(true);
+ const collectionAndOptions$ = observableCombineLatest(
+ this.collectionRD$,
+ this.searchOptions$,
+ this.shouldUpdate$
+ );
+ this.collectionItemsRD$ = collectionAndOptions$.pipe(
+ switchMap(([collectionRD, options, shouldUpdate]) => {
+ if (shouldUpdate) {
+ return this.collectionDataService.getMappedItems(collectionRD.payload.id, Object.assign(options, {
+ sort: this.defaultSortOptions
+ }))
+ }
+ })
+ );
+ this.mappedItemsRD$ = collectionAndOptions$.pipe(
+ switchMap(([collectionRD, options, shouldUpdate]) => {
+ if (shouldUpdate) {
+ return this.searchService.search(Object.assign(new PaginatedSearchOptions(options), {
+ query: this.buildQuery(collectionRD.payload.id, options.query),
+ scope: undefined,
+ dsoType: DSpaceObjectType.ITEM,
+ sort: this.defaultSortOptions
+ }), 10000).pipe(
+ toDSpaceObjectListRD(),
+ startWith(undefined)
+ );
+ }
+ })
+ );
+ }
+
+ /**
+ * Map/Unmap the selected items to the collection and display notifications
+ * @param ids The list of item UUID's to map/unmap to the collection
+ * @param remove Whether or not it's supposed to remove mappings
+ */
+ mapItems(ids: string[], remove?: boolean) {
+ const responses$ = this.collectionRD$.pipe(
+ getSucceededRemoteData(),
+ map((collectionRD: RemoteData) => collectionRD.payload),
+ switchMap((collection: Collection) =>
+ observableCombineLatest(ids.map((id: string) =>
+ remove ? this.itemDataService.removeMappingFromCollection(id, collection.id) : this.itemDataService.mapToCollection(id, collection.self)
+ ))
+ )
+ );
+
+ this.showNotifications(responses$, remove);
+ }
+
+ /**
+ * Display notifications
+ * @param {Observable} responses$ The responses after adding/removing a mapping
+ * @param {boolean} remove Whether or not the goal was to remove mappings
+ */
+ private showNotifications(responses$: Observable, remove?: boolean) {
+ const messageInsertion = remove ? 'unmap' : 'map';
+
+ responses$.subscribe((responses: RestResponse[]) => {
+ const successful = responses.filter((response: RestResponse) => response.isSuccessful);
+ const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful);
+ if (successful.length > 0) {
+ const successMessages = observableCombineLatest(
+ this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.success.head`),
+ this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.success.content`, { amount: successful.length })
+ );
+
+ successMessages.subscribe(([head, content]) => {
+ this.notificationsService.success(head, content);
+ });
+ }
+ if (unsuccessful.length > 0) {
+ const unsuccessMessages = observableCombineLatest(
+ this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.error.head`),
+ this.translateService.get(`collection.edit.item-mapper.notifications.${messageInsertion}.error.content`, { amount: unsuccessful.length })
+ );
+
+ unsuccessMessages.subscribe(([head, content]) => {
+ this.notificationsService.error(head, content);
+ });
+ }
+ // Force an update on all lists and switch back to the first tab
+ this.shouldUpdate$.next(true);
+ this.switchToFirstTab();
+ });
+ }
+
+ /**
+ * Clear url parameters on tab change (temporary fix until pagination is improved)
+ * @param event
+ */
+ tabChange(event) {
+ this.performedSearch = false;
+ this.router.navigateByUrl(this.getCurrentUrl());
+ }
+
+ /**
+ * Get current url without parameters
+ * @returns {string}
+ */
+ getCurrentUrl(): string {
+ if (this.router.url.indexOf('?') > -1) {
+ return this.router.url.substring(0, this.router.url.indexOf('?'));
+ }
+ return this.router.url;
+ }
+
+ /**
+ * Build a query where items that are already mapped to a collection are excluded from
+ * @param collectionId The collection's UUID
+ * @param query The query to add to it
+ */
+ buildQuery(collectionId: string, query: string): string {
+ const excludeColQuery = `-location.coll:\"${collectionId}\"`;
+ if (isNotEmpty(query)) {
+ return `${excludeColQuery} AND ${query}`;
+ } else {
+ return excludeColQuery;
+ }
+ }
+
+ /**
+ * Switch the view to focus on the first tab
+ */
+ switchToFirstTab() {
+ this.tabs.select('browseTab');
+ }
+
+ /**
+ * When a cancel event is fired, return to the collection page
+ */
+ onCancel() {
+ this.collectionRD$.pipe(
+ getSucceededRemoteData(),
+ getRemoteDataPayload(),
+ take(1)
+ ).subscribe((collection: Collection) => {
+ this.router.navigate(['/collections/', collection.id])
+ });
+ }
+
+}
diff --git a/src/app/+collection-page/collection-page-routing.module.ts b/src/app/+collection-page/collection-page-routing.module.ts
index cdbd7650b2..66c623657d 100644
--- a/src/app/+collection-page/collection-page-routing.module.ts
+++ b/src/app/+collection-page/collection-page-routing.module.ts
@@ -10,6 +10,7 @@ import { CreateCollectionPageGuard } from './create-collection-page/create-colle
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
import { URLCombiner } from '../core/url-combiner/url-combiner';
import { getCollectionModulePath } from '../app-routing.module';
+import { CollectionItemMapperComponent } from './collection-item-mapper/collection-item-mapper.component';
export const COLLECTION_PARENT_PARAMETER = 'parent';
@@ -61,6 +62,15 @@ const COLLECTION_EDIT_PATH = ':id/edit';
resolve: {
collection: CollectionPageResolver
}
+ },
+ {
+ path: ':id/edit/mapper',
+ component: CollectionItemMapperComponent,
+ pathMatch: 'full',
+ resolve: {
+ collection: CollectionPageResolver
+ },
+ canActivate: [AuthenticatedGuard]
}
])
],
diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html
index 91239de17c..2b16bc1ca6 100644
--- a/src/app/+collection-page/collection-page.component.html
+++ b/src/app/+collection-page/collection-page.component.html
@@ -52,6 +52,9 @@
message="{{'error.recent-submissions' | translate}}">
+
+ {{'collection.page.browse.recent.empty' | translate}}
+
+
+
+
{{'item.edit.item-mapper.head' | translate}}
+
+
{{'item.edit.item-mapper.description' | translate}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{'item.edit.item-mapper.no-search' | translate}}
+
+
+
+
+
+
+
diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/workspaceitem-my-dspace-result/workspaceitem-my-dspace-result-detail-element.component.scss b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.scss
similarity index 100%
rename from src/app/shared/object-detail/my-dspace-result-detail-element/workspaceitem-my-dspace-result/workspaceitem-my-dspace-result-detail-element.component.scss
rename to src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.scss
diff --git a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts
new file mode 100644
index 0000000000..ed9351d5d2
--- /dev/null
+++ b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.spec.ts
@@ -0,0 +1,207 @@
+import { async, ComponentFixture, fakeAsync, TestBed, tick } 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';
+import { ActivatedRouteStub } from '../../../shared/testing/active-router-stub';
+import { EventEmitter } from '@angular/core';
+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';
+import { HostWindowServiceStub } from '../../../shared/testing/host-window-service-stub';
+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';
+import { PaginationComponent } from '../../../shared/pagination/pagination.component';
+import { EnumKeysPipe } from '../../../shared/utils/enum-keys-pipe';
+import { VarDirective } from '../../../shared/utils/var.directive';
+import { SearchFormComponent } from '../../../shared/search-form/search-form.component';
+import { Collection } from '../../../core/shared/collection.model';
+import { ErrorComponent } from '../../../shared/error/error.component';
+import { LoadingComponent } from '../../../shared/loading/loading.component';
+
+describe('ItemCollectionMapperComponent', () => {
+ let comp: ItemCollectionMapperComponent;
+ let fixture: ComponentFixture;
+
+ let route: ActivatedRoute;
+ let router: Router;
+ let searchConfigService: SearchConfigurationService;
+ let searchService: SearchService;
+ let notificationsService: NotificationsService;
+ let itemDataService: ItemDataService;
+
+ const mockCollection = Object.assign(new Collection(), { id: 'collection1' });
+ const mockItem: Item = Object.assign(new Item(), {
+ id: '932c7d50-d85a-44cb-b9dc-b427b12877bd',
+ name: 'test-item'
+ });
+ const mockItemRD: RemoteData- = new RemoteData
- (false, false, true, null, mockItem);
+ const mockSearchOptions = of(new PaginatedSearchOptions({
+ pagination: Object.assign(new PaginationComponentOptions(), {
+ id: 'search-page-configuration',
+ pageSize: 10,
+ currentPage: 1
+ }),
+ sort: new SortOptions('dc.title', SortDirection.ASC)
+ }));
+ const url = 'http://test.url';
+ const urlWithParam = url + '?param=value';
+ const routerStub = Object.assign(new RouterStub(), {
+ url: urlWithParam,
+ navigateByUrl: {},
+ navigate: {}
+ });
+ const searchConfigServiceStub = {
+ paginatedSearchOptions: mockSearchOptions
+ };
+ const mockCollectionsRD = new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []));
+ const itemDataServiceStub = {
+ mapToCollection: () => of(new RestResponse(true, 200, 'OK')),
+ removeMappingFromCollection: () => of(new RestResponse(true, 200, 'OK')),
+ getMappedCollections: () => of(mockCollectionsRD),
+ /* tslint:disable:no-empty */
+ clearMappedCollectionsRequests: () => {}
+ /* tslint:enable:no-empty */
+ };
+ const searchServiceStub = Object.assign(new SearchServiceStub(), {
+ search: () => of(mockCollectionsRD),
+ /* tslint:disable:no-empty */
+ clearDiscoveryRequests: () => {}
+ /* tslint:enable:no-empty */
+ });
+ const activatedRouteStub = new ActivatedRouteStub({}, { item: mockItemRD });
+ const translateServiceStub = {
+ get: () => of('test-message of item ' + mockItem.name),
+ onLangChange: new EventEmitter(),
+ onTranslationChange: new EventEmitter(),
+ onDefaultLangChange: new EventEmitter()
+ };
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+ declarations: [ItemCollectionMapperComponent, CollectionSelectComponent, SearchFormComponent, PaginationComponent, EnumKeysPipe, VarDirective, ErrorComponent, LoadingComponent],
+ providers: [
+ { provide: ActivatedRoute, useValue: activatedRouteStub },
+ { provide: Router, useValue: routerStub },
+ { provide: SearchConfigurationService, useValue: searchConfigServiceStub },
+ { provide: NotificationsService, useValue: new NotificationsServiceStub() },
+ { provide: ItemDataService, useValue: itemDataServiceStub },
+ { provide: SearchService, useValue: searchServiceStub },
+ { provide: ObjectSelectService, useValue: new ObjectSelectServiceStub() },
+ { provide: TranslateService, useValue: translateServiceStub },
+ { provide: HostWindowService, useValue: new HostWindowServiceStub(0) }
+ ]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ItemCollectionMapperComponent);
+ comp = fixture.componentInstance;
+ fixture.detectChanges();
+ route = (comp as any).route;
+ router = (comp as any).router;
+ searchConfigService = (comp as any).searchConfigService;
+ notificationsService = (comp as any).notificationsService;
+ itemDataService = (comp as any).itemDataService;
+ searchService = (comp as any).searchService;
+ });
+
+ it('should display the correct collection name', () => {
+ const name: HTMLElement = fixture.debugElement.query(By.css('#item-name')).nativeElement;
+ expect(name.innerHTML).toContain(mockItem.name);
+ });
+
+ describe('mapCollections', () => {
+ const ids = ['id1', 'id2', 'id3', 'id4'];
+
+ it('should display a success message if at least one mapping was successful', () => {
+ comp.mapCollections(ids);
+ expect(notificationsService.success).toHaveBeenCalled();
+ expect(notificationsService.error).not.toHaveBeenCalled();
+ });
+
+ it('should display an error message if at least one mapping was unsuccessful', () => {
+ spyOn(itemDataService, 'mapToCollection').and.returnValue(of(new RestResponse(false, 404, 'Not Found')));
+ comp.mapCollections(ids);
+ expect(notificationsService.success).not.toHaveBeenCalled();
+ expect(notificationsService.error).toHaveBeenCalled();
+ });
+ });
+
+ describe('removeMappings', () => {
+ const ids = ['id1', 'id2', 'id3', 'id4'];
+
+ it('should display a success message if the removal of at least one mapping was successful', () => {
+ comp.removeMappings(ids);
+ expect(notificationsService.success).toHaveBeenCalled();
+ expect(notificationsService.error).not.toHaveBeenCalled();
+ });
+
+ it('should display an error message if the removal of at least one mapping was unsuccessful', () => {
+ spyOn(itemDataService, 'removeMappingFromCollection').and.returnValue(of(new RestResponse(false, 404, 'Not Found')));
+ comp.removeMappings(ids);
+ expect(notificationsService.success).not.toHaveBeenCalled();
+ expect(notificationsService.error).toHaveBeenCalled();
+ });
+ });
+
+ describe('tabChange', () => {
+ beforeEach(() => {
+ spyOn(routerStub, 'navigateByUrl');
+ comp.tabChange({});
+ });
+
+ it('should navigate to the same page to remove parameters', () => {
+ expect(router.navigateByUrl).toHaveBeenCalledWith(url);
+ });
+ });
+
+ describe('buildQuery', () => {
+ const query = 'query';
+ const expected = `${query} AND -search.resourceid:${mockCollection.id}`;
+
+ let result;
+
+ beforeEach(() => {
+ result = comp.buildQuery([mockCollection], query);
+ });
+
+ it('should build a solr query to exclude the provided collection', () => {
+ expect(result).toEqual(expected);
+ })
+ });
+
+ describe('onCancel', () => {
+ beforeEach(() => {
+ spyOn(routerStub, 'navigate');
+ comp.onCancel();
+ });
+
+ it('should navigate to the item page', () => {
+ expect(router.navigate).toHaveBeenCalledWith(['/items/', mockItem.id]);
+ });
+ });
+
+});
diff --git a/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts
new file mode 100644
index 0000000000..97b8164a6e
--- /dev/null
+++ b/src/app/+item-page/edit-item-page/item-collection-mapper/item-collection-mapper.component.ts
@@ -0,0 +1,283 @@
+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';
+import { NotificationsService } from '../../../shared/notifications/notifications.service';
+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';
+
+@Component({
+ selector: 'ds-item-collection-mapper',
+ styleUrls: ['./item-collection-mapper.component.scss'],
+ templateUrl: './item-collection-mapper.component.html',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ animations: [
+ fadeIn,
+ fadeInOut
+ ]
+})
+/**
+ * Component for mapping collections to an item
+ */
+export class ItemCollectionMapperComponent implements OnInit {
+
+ /**
+ * A view on the tabset element
+ * Used to switch tabs programmatically
+ */
+ @ViewChild('tabs') tabs;
+
+ /**
+ * The item to map to collections
+ */
+ itemRD$: Observable>;
+
+ /**
+ * Search options
+ */
+ searchOptions$: Observable;
+
+ /**
+ * List of collections to show under the "Browse" tab
+ * Collections that are mapped to the item
+ */
+ itemCollectionsRD$: Observable>>;
+
+ /**
+ * List of collections to show under the "Map" tab
+ * Collections that are not mapped to the item
+ */
+ mappedCollectionsRD$: Observable>>;
+
+ /**
+ * Firing this observable (shouldUpdate$.next(true)) forces the two lists to reload themselves
+ * Usually fired after the lists their cache is cleared (to force a new request to the REST API)
+ */
+ shouldUpdate$: BehaviorSubject;
+
+ /**
+ * Track whether at least one search has been performed or not
+ * As soon as at least one search has been performed, we display the search results
+ */
+ performedSearch = false;
+
+ constructor(private route: ActivatedRoute,
+ private router: Router,
+ private searchConfigService: SearchConfigurationService,
+ private searchService: SearchService,
+ private notificationsService: NotificationsService,
+ private itemDataService: ItemDataService,
+ private translateService: TranslateService) {
+ }
+
+ ngOnInit(): void {
+ this.itemRD$ = this.route.data.pipe(map((data) => data.item)).pipe(getSucceededRemoteData()) as Observable>;
+ this.searchOptions$ = this.searchConfigService.paginatedSearchOptions;
+ this.loadCollectionLists();
+ }
+
+ /**
+ * Load itemCollectionsRD$ with a fixed scope to only obtain the collections that own this item
+ * Load mappedCollectionsRD$ to only obtain collections that don't own this item
+ */
+ loadCollectionLists() {
+ this.shouldUpdate$ = new BehaviorSubject(true);
+ this.itemCollectionsRD$ = observableCombineLatest(this.itemRD$, this.shouldUpdate$).pipe(
+ map(([itemRD, shouldUpdate]) => {
+ if (shouldUpdate) {
+ return itemRD.payload
+ }
+ }),
+ switchMap((item: Item) => this.itemDataService.getMappedCollections(item.id))
+ );
+
+ const owningCollectionRD$ = this.itemRD$.pipe(
+ switchMap((itemRD: RemoteData
- ) => itemRD.payload.owningCollection)
+ );
+ const itemCollectionsAndOptions$ = observableCombineLatest(
+ this.itemCollectionsRD$,
+ owningCollectionRD$,
+ this.searchOptions$
+ );
+ this.mappedCollectionsRD$ = itemCollectionsAndOptions$.pipe(
+ switchMap(([itemCollectionsRD, owningCollectionRD, searchOptions]) => {
+ return this.searchService.search(Object.assign(new PaginatedSearchOptions(searchOptions), {
+ query: this.buildQuery([...itemCollectionsRD.payload.page, owningCollectionRD.payload], searchOptions.query),
+ dsoType: DSpaceObjectType.COLLECTION
+ }), 10000).pipe(
+ toDSpaceObjectListRD(),
+ startWith(undefined)
+ );
+ })
+ ) as Observable>>;
+ }
+
+ /**
+ * Map the item to the selected collections and display notifications
+ * @param {string[]} ids The list of collection UUID's to map the item to
+ */
+ mapCollections(ids: string[]) {
+ const itemIdAndExcludingIds$ = observableCombineLatest(
+ this.itemRD$.pipe(
+ getSucceededRemoteData(),
+ take(1),
+ map((rd: RemoteData
- ) => rd.payload),
+ map((item: Item) => item.id)
+ ),
+ this.itemCollectionsRD$.pipe(
+ getSucceededRemoteData(),
+ take(1),
+ map((rd: RemoteData>) => rd.payload.page),
+ map((collections: Collection[]) => collections.map((collection: Collection) => collection.id))
+ )
+ );
+
+ // Map the item to the collections found in ids, excluding the collections the item is already mapped to
+ const responses$ = itemIdAndExcludingIds$.pipe(
+ switchMap(([itemId, excludingIds]) => observableCombineLatest(this.filterIds(ids, excludingIds).map((id: string) => this.itemDataService.mapToCollection(itemId, id))))
+ );
+
+ this.showNotifications(responses$, 'item.edit.item-mapper.notifications.add');
+ }
+
+ /**
+ * Remove the mapping of the item to the selected collections and display notifications
+ * @param {string[]} ids The list of collection UUID's to remove the mapping of the item for
+ */
+ removeMappings(ids: string[]) {
+ const responses$ = this.itemRD$.pipe(
+ getSucceededRemoteData(),
+ map((itemRD: RemoteData
- ) => itemRD.payload.id),
+ switchMap((itemId: string) => observableCombineLatest(ids.map((id: string) => this.itemDataService.removeMappingFromCollection(itemId, id))))
+ );
+
+ this.showNotifications(responses$, 'item.edit.item-mapper.notifications.remove');
+ }
+
+ /**
+ * Filters ids from a given list of ids, which exist in a second given list of ids
+ * @param {string[]} ids The list of ids to filter out of
+ * @param {string[]} excluding The ids that should be excluded from the first list
+ * @returns {string[]}
+ */
+ private filterIds(ids: string[], excluding: string[]): string[] {
+ return ids.filter((id: string) => excluding.indexOf(id) < 0);
+ }
+
+ /**
+ * Display notifications
+ * @param {Observable} responses$ The responses after adding/removing a mapping
+ * @param {string} messagePrefix The prefix to build the notification messages with
+ */
+ private showNotifications(responses$: Observable, messagePrefix: string) {
+ responses$.subscribe((responses: RestResponse[]) => {
+ const successful = responses.filter((response: RestResponse) => response.isSuccessful);
+ const unsuccessful = responses.filter((response: RestResponse) => !response.isSuccessful);
+ if (successful.length > 0) {
+ const successMessages = observableCombineLatest(
+ this.translateService.get(`${messagePrefix}.success.head`),
+ this.translateService.get(`${messagePrefix}.success.content`, { amount: successful.length })
+ );
+
+ successMessages.subscribe(([head, content]) => {
+ this.notificationsService.success(head, content);
+ });
+ }
+ if (unsuccessful.length > 0) {
+ const unsuccessMessages = observableCombineLatest(
+ this.translateService.get(`${messagePrefix}.error.head`),
+ this.translateService.get(`${messagePrefix}.error.content`, { amount: unsuccessful.length })
+ );
+
+ unsuccessMessages.subscribe(([head, content]) => {
+ this.notificationsService.error(head, content);
+ });
+ }
+ // Force an update on all lists and switch back to the first tab
+ this.shouldUpdate$.next(true);
+ this.switchToFirstTab();
+ });
+ }
+
+ /**
+ * Clear url parameters on tab change (temporary fix until pagination is improved)
+ * @param event
+ */
+ tabChange(event) {
+ this.performedSearch = false;
+ this.router.navigateByUrl(this.getCurrentUrl());
+ }
+
+ /**
+ * Get current url without parameters
+ * @returns {string}
+ */
+ getCurrentUrl(): string {
+ if (this.router.url.indexOf('?') > -1) {
+ return this.router.url.substring(0, this.router.url.indexOf('?'));
+ }
+ return this.router.url;
+ }
+
+ /**
+ * Build a query to exclude collections from
+ * @param collections The collections their UUIDs
+ * @param query The query to add to it
+ */
+ buildQuery(collections: Collection[], query: string): string {
+ let result = query;
+ for (const collection of collections) {
+ result = this.addExcludeCollection(collection.id, result);
+ }
+ return result;
+ }
+
+ /**
+ * Add an exclusion of a collection to a query
+ * @param collectionId The collection's UUID
+ * @param query The query to add the exclusion to
+ */
+ addExcludeCollection(collectionId: string, query: string): string {
+ const excludeQuery = `-search.resourceid:${collectionId}`;
+ if (isNotEmpty(query)) {
+ return `${query} AND ${excludeQuery}`;
+ } else {
+ return excludeQuery;
+ }
+ }
+
+ /**
+ * Switch the view to focus on the first tab
+ */
+ switchToFirstTab() {
+ this.tabs.select('browseTab');
+ }
+
+ /**
+ * When a cancel event is fired, return to the item page
+ */
+ onCancel() {
+ this.itemRD$.pipe(
+ getSucceededRemoteData(),
+ getRemoteDataPayload(),
+ take(1)
+ ).subscribe((item: Item) => {
+ this.router.navigate(['/items/', item.id])
+ });
+ }
+
+}
diff --git a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html
index e9c5de95ca..e8ffc28920 100644
--- a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html
+++ b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.html
@@ -4,7 +4,7 @@
{{metadata?.key?.split('.').join('.')}}
-
+ >
{{"item.edit.metadata.metadatafield.invalid" | translate}}
diff --git a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts
index b8d122d4f6..e07df15651 100644
--- a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts
+++ b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.spec.ts
@@ -10,7 +10,6 @@ import { By } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { SharedModule } from '../../../../shared/shared.module';
import { getTestScheduler } from 'jasmine-marbles';
-import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model';
import { TestScheduler } from 'rxjs/testing';
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
import { TranslateModule } from '@ngx-translate/core';
@@ -18,6 +17,7 @@ import { MetadatumViewModel } from '../../../../core/shared/metadata.models';
import { MetadataSchema } from '../../../../core/metadata/metadata-schema.model';
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
+import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model';
let comp: EditInPlaceFieldComponent;
let fixture: ComponentFixture;
diff --git a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts
index 1722cde8bc..7dce025a73 100644
--- a/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts
+++ b/src/app/+item-page/edit-item-page/item-metadata/edit-in-place-field/edit-in-place-field.component.ts
@@ -4,13 +4,13 @@ import { RegistryService } from '../../../../core/registry/registry.service';
import { cloneDeep } from 'lodash';
import { BehaviorSubject, Observable, of as observableOf } from 'rxjs';
import { map, take } from 'rxjs/operators';
-import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model';
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
import { FieldUpdate } from '../../../../core/data/object-updates/object-updates.reducer';
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
import { NgModel } from '@angular/forms';
import { MetadatumViewModel } from '../../../../core/shared/metadata.models';
import { MetadataField } from '../../../../core/metadata/metadata-field.model';
+import { InputSuggestion } from '../../../../shared/input-suggestions/input-suggestions.model';
@Component({
// tslint:disable-next-line:component-selector
diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.html b/src/app/+item-page/edit-item-page/item-move/item-move.component.html
new file mode 100644
index 0000000000..cf5ada77cf
--- /dev/null
+++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.html
@@ -0,0 +1,48 @@
+
+
+
+
{{'item.edit.move.head' | translate: {id: (itemRD$ | async)?.payload?.handle} }}
+
{{'item.edit.move.description' | translate}}
+
+
+
+
+
+
+
+
+ {{'item.edit.move.inheritpolicies.description' | translate}}
+
+
+
+
+
+
+
+
+
diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.spec.ts b/src/app/+item-page/edit-item-page/item-move/item-move.component.spec.ts
new file mode 100644
index 0000000000..e73b4b6f9a
--- /dev/null
+++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.spec.ts
@@ -0,0 +1,172 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { Item } from '../../../core/shared/item.model';
+import { RouterStub } from '../../../shared/testing/router-stub';
+import { CommonModule } from '@angular/common';
+import { RouterTestingModule } from '@angular/router/testing';
+import { TranslateModule } from '@ngx-translate/core';
+import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
+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';
+import { RemoteData } from '../../../core/data/remote-data';
+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';
+
+describe('ItemMoveComponent', () => {
+ let comp: ItemMoveComponent;
+ let fixture: ComponentFixture;
+
+ const mockItem = Object.assign(new Item(), {
+ id: 'fake-id',
+ handle: 'fake/handle',
+ lastModified: '2018'
+ });
+
+ const itemPageUrl = `fake-url/${mockItem.id}`;
+ const routerStub = Object.assign(new RouterStub(), {
+ url: `${itemPageUrl}/edit`
+ });
+
+ const mockItemDataService = jasmine.createSpyObj({
+ moveToCollection: observableOf(new RestResponse(true, 200, 'Success'))
+ });
+
+ const mockItemDataServiceFail = jasmine.createSpyObj({
+ moveToCollection: observableOf(new RestResponse(false, 500, 'Internal server error'))
+ });
+
+ const routeStub = {
+ data: observableOf({
+ item: new RemoteData(false, false, true, null, {
+ id: 'item1'
+ })
+ })
+ };
+
+ const collection1 = Object.assign(new Collection(),{
+ uuid: 'collection-uuid-1',
+ name: 'Test collection 1',
+ self: 'self-link-1',
+ });
+
+ const collection2 = Object.assign(new Collection(),{
+ uuid: 'collection-uuid-2',
+ name: 'Test collection 2',
+ self: 'self-link-2',
+ });
+
+ const mockSearchService = {
+ search: () => {
+ return observableOf(new RemoteData(false, false, true, null,
+ new PaginatedList(null, [
+ {
+ indexableObject: collection1,
+ hitHighlights: {}
+ }, {
+ indexableObject: collection2,
+ hitHighlights: {}
+ }
+ ])));
+ }
+ };
+
+ const notificationsServiceStub = new NotificationsServiceStub();
+
+ describe('ItemMoveComponent success', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+ declarations: [ItemMoveComponent],
+ providers: [
+ {provide: ActivatedRoute, useValue: routeStub},
+ {provide: Router, useValue: routerStub},
+ {provide: ItemDataService, useValue: mockItemDataService},
+ {provide: NotificationsService, useValue: notificationsServiceStub},
+ {provide: SearchService, useValue: mockSearchService},
+ ], schemas: [
+ CUSTOM_ELEMENTS_SCHEMA
+ ]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ItemMoveComponent);
+ comp = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+ it('should load suggestions', () => {
+ const expected = [
+ collection1,
+ collection2
+ ];
+
+ comp.collectionSearchResults.subscribe((value) => {
+ expect(value).toEqual(expected);
+ }
+ );
+ });
+ it('should get current url ', () => {
+ expect(comp.getCurrentUrl()).toEqual('fake-url/fake-id/edit');
+ });
+ it('should on click select the correct collection name and id', () => {
+ const data = collection1;
+
+ comp.onClick(data);
+
+ expect(comp.selectedCollectionName).toEqual('Test collection 1');
+ expect(comp.selectedCollection).toEqual(collection1);
+ });
+ describe('moveCollection', () => {
+ it('should call itemDataService.moveToCollection', () => {
+ comp.itemId = 'item-id';
+ comp.selectedCollectionName = 'selected-collection-id';
+ comp.selectedCollection = collection1;
+ comp.moveCollection();
+
+ expect(mockItemDataService.moveToCollection).toHaveBeenCalledWith('item-id', collection1);
+ });
+ it('should call notificationsService success message on success', () => {
+ comp.moveCollection();
+
+ expect(notificationsServiceStub.success).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('ItemMoveComponent fail', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [CommonModule, FormsModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule.forRoot()],
+ declarations: [ItemMoveComponent],
+ providers: [
+ {provide: ActivatedRoute, useValue: routeStub},
+ {provide: Router, useValue: routerStub},
+ {provide: ItemDataService, useValue: mockItemDataServiceFail},
+ {provide: NotificationsService, useValue: notificationsServiceStub},
+ {provide: SearchService, useValue: mockSearchService},
+ ], schemas: [
+ CUSTOM_ELEMENTS_SCHEMA
+ ]
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ItemMoveComponent);
+ comp = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should call notificationsService error message on fail', () => {
+ comp.moveCollection();
+
+ expect(notificationsServiceStub.error).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/src/app/+item-page/edit-item-page/item-move/item-move.component.ts b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts
new file mode 100644
index 0000000000..113ee97b3f
--- /dev/null
+++ b/src/app/+item-page/edit-item-page/item-move/item-move.component.ts
@@ -0,0 +1,139 @@
+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';
+import { TranslateService } from '@ngx-translate/core';
+import { getSucceededRemoteData } from '../../../core/shared/operators';
+import { ItemDataService } from '../../../core/data/item-data.service';
+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';
+
+@Component({
+ selector: 'ds-item-move',
+ templateUrl: './item-move.component.html'
+})
+/**
+ * Component that handles the moving of an item to a different collection
+ */
+export class ItemMoveComponent implements OnInit {
+ /**
+ * TODO: There is currently no backend support to change the owningCollection and inherit policies,
+ * TODO: when this is added, the inherit policies option should be used.
+ */
+
+ selectorType = DSpaceObjectType.COLLECTION;
+
+ inheritPolicies = false;
+ itemRD$: Observable>;
+ collectionSearchResults: Observable = observableOf([]);
+ selectedCollectionName: string;
+ selectedCollection: Collection;
+ canSubmit = false;
+
+ itemId: string;
+ processing = false;
+
+ pagination = new PaginationComponentOptions();
+
+ constructor(private route: ActivatedRoute,
+ private router: Router,
+ private notificationsService: NotificationsService,
+ private itemDataService: ItemDataService,
+ private searchService: SearchService,
+ private translateService: TranslateService) {
+ }
+
+ ngOnInit(): void {
+ this.itemRD$ = this.route.data.pipe(map((data) => data.item), getSucceededRemoteData()) as Observable>;
+ this.itemRD$.subscribe((rd) => {
+ this.itemId = rd.payload.id;
+ }
+ );
+ this.pagination.pageSize = 5;
+ this.loadSuggestions('');
+ }
+
+ /**
+ * Find suggestions based on entered query
+ * @param query - Search query
+ */
+ findSuggestions(query): void {
+ this.loadSuggestions(query);
+ }
+
+ /**
+ * Load all available collections to move the item to.
+ * TODO: When the API support it, only fetch collections where user has ADD rights to.
+ */
+ loadSuggestions(query): void {
+ this.collectionSearchResults = this.searchService.search(new PaginatedSearchOptions({
+ pagination: this.pagination,
+ dsoType: DSpaceObjectType.COLLECTION,
+ query: query
+ })).pipe(
+ first(),
+ map((rd: RemoteData>>) => {
+ return rd.payload.page.map((searchResult) => {
+ return searchResult.indexableObject
+ })
+ }) ,
+ );
+
+ }
+
+ /**
+ * Set the collection name and id based on the selected value
+ * @param data - obtained from the ds-input-suggestions component
+ */
+ onClick(data: any): void {
+ this.selectedCollection = data;
+ this.selectedCollectionName = data.name;
+ this.canSubmit = true;
+ }
+
+ /**
+ * @returns {string} the current URL
+ */
+ getCurrentUrl() {
+ return this.router.url;
+ }
+
+ /**
+ * Moves the item to a new collection based on the selected collection
+ */
+ moveCollection() {
+ this.processing = true;
+ this.itemDataService.moveToCollection(this.itemId, this.selectedCollection).pipe(first()).subscribe(
+ (response: RestResponse) => {
+ this.router.navigate([getItemEditPath(this.itemId)]);
+ if (response.isSuccessful) {
+ this.notificationsService.success(this.translateService.get('item.edit.move.success'));
+ } else {
+ this.notificationsService.error(this.translateService.get('item.edit.move.error'));
+ }
+ this.processing = false;
+ }
+ );
+ }
+
+ /**
+ * Resets the can submit when the user changes the content of the input field
+ * @param data
+ */
+ resetCollection(data: any) {
+ this.canSubmit = false;
+ }
+}
diff --git a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.html b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.html
index 4623195437..3a52fd0d12 100644
--- a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.html
+++ b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.html
@@ -4,7 +4,7 @@
diff --git a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts
index 1901bf5fb4..7122dbaf42 100644
--- a/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts
+++ b/src/app/+item-page/edit-item-page/item-operation/item-operation.component.spec.ts
@@ -1,8 +1,9 @@
-import {ItemOperation} from './itemOperation.model';
-import {async, TestBed} from '@angular/core/testing';
-import {ItemOperationComponent} from './item-operation.component';
-import {TranslateModule} from '@ngx-translate/core';
-import {By} from '@angular/platform-browser';
+import { ItemOperation } from './itemOperation.model';
+import { async, TestBed } from '@angular/core/testing';
+import { ItemOperationComponent } from './item-operation.component';
+import { TranslateModule } from '@ngx-translate/core';
+import { By } from '@angular/platform-browser';
+import { RouterTestingModule } from '@angular/router/testing';
describe('ItemOperationComponent', () => {
let itemOperation: ItemOperation;
@@ -12,7 +13,7 @@ describe('ItemOperationComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
- imports: [TranslateModule.forRoot()],
+ imports: [TranslateModule.forRoot(), RouterTestingModule.withRoutes([])],
declarations: [ItemOperationComponent]
}).compileComponents();
}));
diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts
index ca6c1fdd2e..54cb2837a2 100644
--- a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts
+++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship-list/edit-relationship-list.component.spec.ts
@@ -38,8 +38,8 @@ describe('EditRelationshipListComponent', () => {
relationshipType = Object.assign(new RelationshipType(), {
id: '1',
uuid: '1',
- leftLabel: 'isAuthorOfPublication',
- rightLabel: 'isPublicationOfAuthor'
+ leftwardType: 'isAuthorOfPublication',
+ rightwardType: 'isPublicationOfAuthor'
});
relationships = [
@@ -119,7 +119,7 @@ describe('EditRelationshipListComponent', () => {
de = fixture.debugElement;
comp.item = item;
comp.url = url;
- comp.relationshipLabel = relationshipType.leftLabel;
+ comp.relationshipLabel = relationshipType.leftwardType;
fixture.detectChanges();
});
diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html
index e25c3c204f..03040ce8e0 100644
--- a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html
+++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.html
@@ -1,6 +1,6 @@
-
+
diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.spec.ts b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.spec.ts
index 3306d8eb01..54fce0a68e 100644
--- a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.spec.ts
+++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.spec.ts
@@ -5,7 +5,6 @@ import { ObjectUpdatesService } from '../../../../core/data/object-updates/objec
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { EditRelationshipComponent } from './edit-relationship.component';
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
-import { ResourceType } from '../../../../core/shared/resource-type';
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
import { RemoteData } from '../../../../core/data/remote-data';
import { Item } from '../../../../core/shared/item.model';
@@ -34,8 +33,8 @@ describe('EditRelationshipComponent', () => {
relationshipType = Object.assign(new RelationshipType(), {
id: '1',
uuid: '1',
- leftLabel: 'isAuthorOfPublication',
- rightLabel: 'isPublicationOfAuthor'
+ leftwardType: 'isAuthorOfPublication',
+ rightwardType: 'isPublicationOfAuthor'
});
relationships = [
diff --git a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.ts b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.ts
index a593b11bfa..302ebf68a7 100644
--- a/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.ts
+++ b/src/app/+item-page/edit-item-page/item-relationships/edit-relationship/edit-relationship.component.ts
@@ -4,7 +4,7 @@ import { cloneDeep } from 'lodash';
import { Item } from '../../../../core/shared/item.model';
import { ObjectUpdatesService } from '../../../../core/data/object-updates/object-updates.service';
import { FieldChangeType } from '../../../../core/data/object-updates/object-updates.actions';
-import { ItemViewMode } from '../../../../shared/items/item-type-decorator';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
@Component({
// tslint:disable-next-line:component-selector
@@ -31,7 +31,7 @@ export class EditRelationshipComponent implements OnChanges {
/**
* The view-mode we're currently on
*/
- viewMode = ItemViewMode.Summary;
+ viewMode = ViewMode.ListElement;
constructor(private objectUpdatesService: ObjectUpdatesService) {
}
diff --git a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts
index 608c5f1026..910ebad579 100644
--- a/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts
+++ b/src/app/+item-page/edit-item-page/item-relationships/item-relationships.component.spec.ts
@@ -68,8 +68,8 @@ describe('ItemRelationshipsComponent', () => {
relationshipType = Object.assign(new RelationshipType(), {
id: '1',
uuid: '1',
- leftLabel: 'isAuthorOfPublication',
- rightLabel: 'isPublicationOfAuthor'
+ leftwardType: 'isAuthorOfPublication',
+ rightwardType: 'isPublicationOfAuthor'
});
relationships = [
diff --git a/src/app/+item-page/edit-item-page/item-status/item-status.component.ts b/src/app/+item-page/edit-item-page/item-status/item-status.component.ts
index c7e3a023d1..e63154918b 100644
--- a/src/app/+item-page/edit-item-page/item-status/item-status.component.ts
+++ b/src/app/+item-page/edit-item-page/item-status/item-status.component.ts
@@ -68,6 +68,7 @@ export class ItemStatusComponent implements OnInit {
The value is supposed to be a href for the button
*/
this.operations = [];
+ this.operations.push(new ItemOperation('mappedCollections', this.getCurrentUrl(item) + '/mapper'));
if (item.isWithdrawn) {
this.operations.push(new ItemOperation('reinstate', this.getCurrentUrl(item) + '/reinstate'));
} else {
@@ -79,6 +80,7 @@ export class ItemStatusComponent implements OnInit {
this.operations.push(new ItemOperation('public', this.getCurrentUrl(item) + '/public'));
}
this.operations.push(new ItemOperation('delete', this.getCurrentUrl(item) + '/delete'));
+ this.operations.push(new ItemOperation('move', this.getCurrentUrl(item) + '/move'));
});
}
diff --git a/src/app/+item-page/item-page.module.ts b/src/app/+item-page/item-page.module.ts
index 6743028b6c..2a5d0b6da7 100644
--- a/src/app/+item-page/item-page.module.ts
+++ b/src/app/+item-page/item-page.module.ts
@@ -31,8 +31,8 @@ import { MetadataFieldWrapperComponent } from './field-components/metadata-field
imports: [
CommonModule,
SharedModule,
- EditItemPageModule,
ItemPageRoutingModule,
+ EditItemPageModule,
SearchPageModule
],
declarations: [
@@ -62,7 +62,8 @@ import { MetadataFieldWrapperComponent } from './field-components/metadata-field
GenericItemPageFieldComponent,
RelatedEntitiesSearchComponent,
RelatedItemsComponent,
- MetadataRepresentationListComponent
+ MetadataRepresentationListComponent,
+ ItemPageTitleFieldComponent
],
entryComponents: [
PublicationComponent
diff --git a/src/app/+item-page/simple/field-components/specific-field/generic/generic-item-page-field.component.ts b/src/app/+item-page/simple/field-components/specific-field/generic/generic-item-page-field.component.ts
index ee7d27a11f..3b8d261dcc 100644
--- a/src/app/+item-page/simple/field-components/specific-field/generic/generic-item-page-field.component.ts
+++ b/src/app/+item-page/simple/field-components/specific-field/generic/generic-item-page-field.component.ts
@@ -10,7 +10,7 @@ import { ItemPageFieldComponent } from '../item-page-field.component';
/**
* This component can be used to represent metadata on a simple item page.
* It is the most generic way of displaying metadata values
- * It expects 4 parameters: The item, a seperator, the metadata keys and an i18n key
+ * It expects 4 parameters: The item, a separator, the metadata keys and an i18n key
*/
export class GenericItemPageFieldComponent extends ItemPageFieldComponent {
diff --git a/src/app/+item-page/simple/field-components/specific-field/item-page-field.component.spec.ts b/src/app/+item-page/simple/field-components/specific-field/item-page-field.component.spec.ts
index 1b7acb2e3b..6e5ed2c535 100644
--- a/src/app/+item-page/simple/field-components/specific-field/item-page-field.component.spec.ts
+++ b/src/app/+item-page/simple/field-components/specific-field/item-page-field.component.spec.ts
@@ -4,12 +4,9 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Item } from '../../../../core/shared/item.model';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { MockTranslateLoader } from '../../../../shared/mocks/mock-translate-loader';
-import { Observable } from 'rxjs';
import { PageInfo } from '../../../../core/shared/page-info.model';
-import { RemoteData } from '../../../../core/data/remote-data';
import { ItemPageFieldComponent } from './item-page-field.component';
import { MetadataValuesComponent } from '../../../field-components/metadata-values/metadata-values.component';
-import { of as observableOf } from 'rxjs';
import { MetadataMap, MetadataValue } from '../../../../core/shared/metadata.models';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
diff --git a/src/app/+item-page/simple/item-page.component.html b/src/app/+item-page/simple/item-page.component.html
index b6de496dc4..501e3e161e 100644
--- a/src/app/+item-page/simple/item-page.component.html
+++ b/src/app/+item-page/simple/item-page.component.html
@@ -1,7 +1,7 @@
diff --git a/src/app/+item-page/simple/item-page.component.ts b/src/app/+item-page/simple/item-page.component.ts
index 7d2c5bdd9c..10deef23e4 100644
--- a/src/app/+item-page/simple/item-page.component.ts
+++ b/src/app/+item-page/simple/item-page.component.ts
@@ -1,21 +1,18 @@
-
-import { mergeMap, filter, map, take, tap } from 'rxjs/operators';
+import { map } from 'rxjs/operators';
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { ItemDataService } from '../../core/data/item-data.service';
import { RemoteData } from '../../core/data/remote-data';
-import { Bitstream } from '../../core/shared/bitstream.model';
import { Item } from '../../core/shared/item.model';
import { MetadataService } from '../../core/metadata/metadata.service';
import { fadeInOut } from '../../shared/animations/fade';
-import { hasValue } from '../../shared/empty.util';
import { redirectToPageNotFoundOn404 } from '../../core/shared/operators';
-import { ItemViewMode } from '../../shared/items/item-type-decorator';
+import { ViewMode } from '../../core/shared/view-mode.model';
/**
* This component renders a simple item page.
@@ -44,7 +41,7 @@ export class ItemPageComponent implements OnInit {
/**
* The view-mode we're currently on
*/
- viewMode = ItemViewMode.Detail;
+ viewMode = ViewMode.StandalonePage;
constructor(
private route: ActivatedRoute,
@@ -53,6 +50,9 @@ export class ItemPageComponent implements OnInit {
private metadataService: MetadataService,
) { }
+ /**
+ * Initialize instance variables
+ */
ngOnInit(): void {
this.itemRD$ = this.route.data.pipe(
map((data) => data.item as RemoteData
- ),
diff --git a/src/app/+item-page/simple/item-types/publication/publication.component.html b/src/app/+item-page/simple/item-types/publication/publication.component.html
index deac919072..0f049e5bf4 100644
--- a/src/app/+item-page/simple/item-types/publication/publication.component.html
+++ b/src/app/+item-page/simple/item-types/publication/publication.component.html
@@ -1,27 +1,27 @@
- {{'publication.page.titleprefix' | translate}}
+ {{'publication.page.titleprefix' | translate}}
-
+
-
-
-
-
+
+
+
-
-
-
@@ -48,25 +48,25 @@
[relationType]="'isJournalIssueOfPublication'"
[label]="'relationships.isJournalIssueOf' | translate">
-
-
+
-
-
-
-
+
+
diff --git a/src/app/+item-page/simple/item-types/publication/publication.component.spec.ts b/src/app/+item-page/simple/item-types/publication/publication.component.spec.ts
index 2908e12342..f87b15f476 100644
--- a/src/app/+item-page/simple/item-types/publication/publication.component.spec.ts
+++ b/src/app/+item-page/simple/item-types/publication/publication.component.spec.ts
@@ -3,7 +3,6 @@ import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { MockTranslateLoader } from '../../../../shared/mocks/mock-translate-loader';
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
-import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
@@ -37,7 +36,6 @@ describe('PublicationComponent', () => {
})],
declarations: [PublicationComponent, GenericItemPageFieldComponent, TruncatePipe],
providers: [
- {provide: ITEM, useValue: mockItem},
{provide: ItemDataService, useValue: {}},
{provide: TruncatableService, useValue: {}},
{provide: RelationshipService, useValue: {}}
@@ -52,6 +50,7 @@ describe('PublicationComponent', () => {
beforeEach(async(() => {
fixture = TestBed.createComponent(PublicationComponent);
comp = fixture.componentInstance;
+ comp.object = mockItem;
fixture.detectChanges();
}));
diff --git a/src/app/+item-page/simple/item-types/publication/publication.component.ts b/src/app/+item-page/simple/item-types/publication/publication.component.ts
index f0986624da..d926e1efdb 100644
--- a/src/app/+item-page/simple/item-types/publication/publication.component.ts
+++ b/src/app/+item-page/simple/item-types/publication/publication.component.ts
@@ -1,12 +1,15 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
-import {
- DEFAULT_ITEM_TYPE, ItemViewMode,
- rendersItemType
-} from '../../../../shared/items/item-type-decorator';
+import { Item } from '../../../../core/shared/item.model';
import { ItemComponent } from '../shared/item.component';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
-@rendersItemType('Publication', ItemViewMode.Detail)
-@rendersItemType(DEFAULT_ITEM_TYPE, ItemViewMode.Detail)
+/**
+ * Component that represents a publication Item page
+ */
+
+@listableObjectComponent('Publication', ViewMode.StandalonePage)
+@listableObjectComponent(Item, ViewMode.StandalonePage)
@Component({
selector: 'ds-publication',
styleUrls: ['./publication.component.scss'],
diff --git a/src/app/+item-page/simple/item-types/shared/item.component.spec.ts b/src/app/+item-page/simple/item-types/shared/item.component.spec.ts
index a615bdd94b..5a9f1c509d 100644
--- a/src/app/+item-page/simple/item-types/shared/item.component.spec.ts
+++ b/src/app/+item-page/simple/item-types/shared/item.component.spec.ts
@@ -7,13 +7,20 @@ import { ItemDataService } from '../../../../core/data/item-data.service';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { MockTranslateLoader } from '../../../../shared/mocks/mock-translate-loader';
import { ChangeDetectionStrategy, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
-import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
import { isNotEmpty } from '../../../../shared/empty.util';
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
import { PageInfo } from '../../../../core/shared/page-info.model';
+import { ItemComponent } from './item.component';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { VarDirective } from '../../../../shared/utils/var.directive';
+import { Observable } from 'rxjs/internal/Observable';
+import { MetadataRepresentation } from '../../../../core/shared/metadata-representation/metadata-representation.model';
+import { MetadatumRepresentation } from '../../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
+import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
+import { MetadataMap, MetadataValue } from '../../../../core/shared/metadata.models';
import { compareArraysUsing, compareArraysUsingIds } from './item-relationships-utils';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { RelationshipService } from '../../../../core/data/relationship.service';
@@ -40,7 +47,6 @@ export function getItemPageFieldsTest(mockItem: Item, component) {
})],
declarations: [component, GenericItemPageFieldComponent, TruncatePipe],
providers: [
- {provide: ITEM, useValue: mockItem},
{provide: ItemDataService, useValue: {}},
{provide: TruncatableService, useValue: {}},
{provide: RelationshipService, useValue: {}}
@@ -55,6 +61,7 @@ export function getItemPageFieldsTest(mockItem: Item, component) {
beforeEach(async(() => {
fixture = TestBed.createComponent(component);
comp = fixture.componentInstance;
+ comp.object = mockItem;
fixture.detectChanges();
}));
diff --git a/src/app/+item-page/simple/item-types/shared/item.component.ts b/src/app/+item-page/simple/item-types/shared/item.component.ts
index 9814191475..64a96fdd52 100644
--- a/src/app/+item-page/simple/item-types/shared/item.component.ts
+++ b/src/app/+item-page/simple/item-types/shared/item.component.ts
@@ -1,6 +1,5 @@
-import { Component, Inject } from '@angular/core';
+import { Component, Input } from '@angular/core';
import { Item } from '../../../../core/shared/item.model';
-import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
@Component({
selector: 'ds-item',
@@ -10,8 +9,5 @@ import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.compo
* A generic component for displaying metadata and relations of an item
*/
export class ItemComponent {
-
- constructor(
- @Inject(ITEM) public item: Item
- ) {}
+ @Input() object: Item;
}
diff --git a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html
index e9d2364ca6..750029b58b 100644
--- a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html
+++ b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.html
@@ -1,7 +1,7 @@
0" [label]="label">
-
-
+
+
diff --git a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.spec.ts b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.spec.ts
index 120e846523..ae1dc4d16f 100644
--- a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.spec.ts
+++ b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.spec.ts
@@ -83,8 +83,8 @@ describe('MetadataRepresentationListComponent', () => {
fixture.detectChanges();
}));
- it('should load 2 item-type-switcher components', () => {
- const fields = fixture.debugElement.queryAll(By.css('ds-item-type-switcher'));
+ it('should load 2 metadata-representation-loader components', () => {
+ const fields = fixture.debugElement.queryAll(By.css('ds-metadata-representation-loader'));
expect(fields.length).toBe(2);
});
diff --git a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.ts b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.ts
index 4b5553e404..39a03216de 100644
--- a/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.ts
+++ b/src/app/+item-page/simple/metadata-representation-list/metadata-representation-list.component.ts
@@ -1,17 +1,16 @@
import { Component, Input, OnInit } from '@angular/core';
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
-import { ItemViewMode } from '../../../shared/items/item-type-decorator';
-import { Observable } from 'rxjs/internal/Observable';
-import { RemoteData } from '../../../core/data/remote-data';
+import { combineLatest as observableCombineLatest, Observable, of as observableOf, zip as observableZip } from 'rxjs';
import { RelationshipService } from '../../../core/data/relationship.service';
-import { Item } from '../../../core/shared/item.model';
-import { zip as observableZip, combineLatest as observableCombineLatest, of as observableOf } from 'rxjs';
import { MetadataValue } from '../../../core/shared/metadata.models';
-import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
-import { filter, map, switchMap } from 'rxjs/operators';
import { getSucceededRemoteData } from '../../../core/shared/operators';
+import { switchMap } from 'rxjs/operators';
+import { RemoteData } from '../../../core/data/remote-data';
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
+import { Item } from '../../../core/shared/item.model';
+import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
+import { map, filter } from 'rxjs/operators';
@Component({
selector: 'ds-metadata-representation-list',
@@ -56,12 +55,6 @@ export class MetadataRepresentationListComponent implements OnInit {
*/
representations$: Observable;
- /**
- * The view-mode we're currently on
- * @type {ElementViewMode}
- */
- viewMode = ItemViewMode.Metadata;
-
/**
* The originally provided limit
* Used for resetting the limit to the original value when collapsing the list
diff --git a/src/app/+item-page/simple/related-items/related-items-component.ts b/src/app/+item-page/simple/related-items/related-items-component.ts
index 05ae5e4d49..6cb2bfee81 100644
--- a/src/app/+item-page/simple/related-items/related-items-component.ts
+++ b/src/app/+item-page/simple/related-items/related-items-component.ts
@@ -1,11 +1,11 @@
import { Component, Input, OnInit } from '@angular/core';
import { Item } from '../../../core/shared/item.model';
-import { ItemViewMode } from '../../../shared/items/item-type-decorator';
import { Observable } from 'rxjs/internal/Observable';
import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list';
-import { RelationshipService } from '../../../core/data/relationship.service';
import { FindAllOptions } from '../../../core/data/request.models';
+import { ViewMode } from '../../../core/shared/view-mode.model';
+import { RelationshipService } from '../../../core/data/relationship.service';
@Component({
selector: 'ds-related-items',
@@ -51,9 +51,9 @@ export class RelatedItemsComponent implements OnInit {
/**
* The view-mode we're currently on
- * @type {ElementViewMode}
+ * @type {ViewMode}
*/
- viewMode = ItemViewMode.Summary;
+ viewMode = ViewMode.ListElement;
/**
* Whether or not the list is currently expanded to show all related items
diff --git a/src/app/+item-page/simple/related-items/related-items.component.html b/src/app/+item-page/simple/related-items/related-items.component.html
index a4baee9825..dab85ee0e5 100644
--- a/src/app/+item-page/simple/related-items/related-items.component.html
+++ b/src/app/+item-page/simple/related-items/related-items.component.html
@@ -1,7 +1,7 @@
0" [label]="label">
-
-
+
+
diff --git a/src/app/+item-page/simple/related-items/related-items.component.spec.ts b/src/app/+item-page/simple/related-items/related-items.component.spec.ts
index 6637091f02..1e76f1173e 100644
--- a/src/app/+item-page/simple/related-items/related-items.component.spec.ts
+++ b/src/app/+item-page/simple/related-items/related-items.component.spec.ts
@@ -61,7 +61,7 @@ describe('RelatedItemsComponent', () => {
}));
it(`should load ${mockItems.length} item-type-switcher components`, () => {
- const fields = fixture.debugElement.queryAll(By.css('ds-item-type-switcher'));
+ const fields = fixture.debugElement.queryAll(By.css('ds-listable-object-component-loader'));
expect(fields.length).toBe(mockItems.length);
});
diff --git a/src/app/+login-page/login-page.component.html b/src/app/+login-page/login-page.component.html
index 6dcb11fbb0..84059877f4 100644
--- a/src/app/+login-page/login-page.component.html
+++ b/src/app/+login-page/login-page.component.html
@@ -3,7 +3,8 @@
{{"login.form.header" | translate}}
-
+
diff --git a/src/app/+my-dspace-page/my-dspace-configuration.service.ts b/src/app/+my-dspace-page/my-dspace-configuration.service.ts
index 667668eda9..10ca580d3c 100644
--- a/src/app/+my-dspace-page/my-dspace-configuration.service.ts
+++ b/src/app/+my-dspace-page/my-dspace-configuration.service.ts
@@ -8,9 +8,9 @@ import { MyDSpaceConfigurationValueType } from './my-dspace-configuration-value-
import { RoleService } from '../core/roles/role.service';
import { SearchConfigurationOption } from '../shared/search/search-switch-configuration/search-configuration-option.model';
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
-import { RouteService } from '../shared/services/route.service';
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
+import { RouteService } from '../core/services/route.service';
/**
* Service that performs all actions that have to do with the current mydspace configuration
@@ -54,7 +54,6 @@ export class MyDSpaceConfigurationService extends SearchConfigurationService {
* Initialize class
*
* @param {roleService} roleService
- * @param {SearchFixedFilterService} fixedFilterService
* @param {RouteService} routeService
* @param {ActivatedRoute} route
*/
diff --git a/src/app/+my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts b/src/app/+my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts
index 938a1ec899..76853db924 100644
--- a/src/app/+my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts
+++ b/src/app/+my-dspace-page/my-dspace-new-submission/my-dspace-new-submission.component.ts
@@ -7,7 +7,6 @@ import { TranslateService } from '@ngx-translate/core';
import { SubmissionState } from '../../submission/submission.reducers';
import { AuthService } from '../../core/auth/auth.service';
-import { MyDSpaceResult } from '../my-dspace-result.model';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { NotificationOptions } from '../../shared/notifications/models/notification-options.model';
@@ -15,6 +14,7 @@ import { UploaderOptions } from '../../shared/uploader/uploader-options.model';
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
import { NotificationType } from '../../shared/notifications/models/notification-type';
import { hasValue } from '../../shared/empty.util';
+import { SearchResult } from '../../+search-page/search-result.model';
/**
* This component represents the whole mydspace page header
@@ -25,7 +25,10 @@ import { hasValue } from '../../shared/empty.util';
templateUrl: './my-dspace-new-submission.component.html'
})
export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit {
- @Output() uploadEnd = new EventEmitter>>();
+ /**
+ * Output that emits the workspace item when the upload has completed
+ */
+ @Output() uploadEnd = new EventEmitter>>();
/**
* The UploaderOptions object
diff --git a/src/app/+my-dspace-page/my-dspace-page.component.spec.ts b/src/app/+my-dspace-page/my-dspace-page.component.spec.ts
index 4ead113078..709e676e98 100644
--- a/src/app/+my-dspace-page/my-dspace-page.component.spec.ts
+++ b/src/app/+my-dspace-page/my-dspace-page.component.spec.ts
@@ -17,7 +17,7 @@ import { HostWindowService } from '../shared/host-window.service';
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
import { RemoteData } from '../core/data/remote-data';
import { MyDSpacePageComponent, SEARCH_CONFIG_SERVICE } from './my-dspace-page.component';
-import { RouteService } from '../shared/services/route.service';
+import { RouteService } from '../core/services/route.service';
import { routeServiceStub } from '../shared/testing/route-service-stub';
import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub';
import { SearchService } from '../core/shared/search/search.service';
diff --git a/src/app/+my-dspace-page/my-dspace-page.component.ts b/src/app/+my-dspace-page/my-dspace-page.component.ts
index c257898078..0ee697bae7 100644
--- a/src/app/+my-dspace-page/my-dspace-page.component.ts
+++ b/src/app/+my-dspace-page/my-dspace-page.component.ts
@@ -20,7 +20,6 @@ import { SearchService } from '../core/shared/search/search.service';
import { SearchSidebarService } from '../core/shared/search/search-sidebar.service';
import { hasValue } from '../shared/empty.util';
import { getSucceededRemoteData } from '../core/shared/operators';
-import { MyDSpaceResult } from './my-dspace-result.model';
import { MyDSpaceResponseParsingService } from '../core/data/mydspace-response-parsing.service';
import { SearchConfigurationOption } from '../shared/search/search-switch-configuration/search-configuration-option.model';
import { RoleType } from '../core/roles/role-types';
@@ -28,6 +27,7 @@ import { SearchConfigurationService } from '../core/shared/search/search-configu
import { MyDSpaceConfigurationService } from './my-dspace-configuration.service';
import { ViewMode } from '../core/shared/view-mode.model';
import { MyDSpaceRequest } from '../core/data/request.models';
+import { SearchResult } from '../+search-page/search-result.model';
export const MYDSPACE_ROUTE = '/mydspace';
export const SEARCH_CONFIG_SERVICE: InjectionToken = new InjectionToken('searchConfigurationService');
@@ -63,7 +63,7 @@ export class MyDSpacePageComponent implements OnInit {
/**
* The current search results
*/
- resultsRD$: BehaviorSubject>>> = new BehaviorSubject(null);
+ resultsRD$: BehaviorSubject>>> = new BehaviorSubject(null);
/**
* The current paginated search options
@@ -93,7 +93,7 @@ export class MyDSpacePageComponent implements OnInit {
/**
* List of available view mode
*/
- viewModeList = [ViewMode.List, ViewMode.Detail];
+ viewModeList = [ViewMode.ListElement, ViewMode.DetailedListElement];
constructor(private service: SearchService,
private sidebarService: SearchSidebarService,
diff --git a/src/app/+my-dspace-page/my-dspace-page.module.ts b/src/app/+my-dspace-page/my-dspace-page.module.ts
index 6c0b3334d4..1cf30c4ec9 100644
--- a/src/app/+my-dspace-page/my-dspace-page.module.ts
+++ b/src/app/+my-dspace-page/my-dspace-page.module.ts
@@ -6,19 +6,20 @@ import { SharedModule } from '../shared/shared.module';
import { MyDspacePageRoutingModule } from './my-dspace-page-routing.module';
import { MyDSpacePageComponent } from './my-dspace-page.component';
import { MyDSpaceResultsComponent } from './my-dspace-results/my-dspace-results.component';
-import { WorkspaceitemMyDSpaceResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/workspaceitem-my-dspace-result/workspaceitem-my-dspace-result-list-element.component';
-import { ItemMyDSpaceResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/item-my-dspace-result/item-my-dspace-result-list-element.component';
-import { WorkflowitemMyDSpaceResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/workflowitem-my-dspace-result/workflowitem-my-dspace-result-list-element.component';
-import { ClaimedMyDSpaceResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/claimed-my-dspace-result/claimed-my-dspace-result-list-element.component';
-import { PoolMyDSpaceResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/pool-my-dspace-result/pool-my-dspace-result-list-element.component';
+import { WorkspaceItemSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/workspace-item-search-result/workspace-item-search-result-list-element.component';
+import { ClaimedSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/claimed-search-result/claimed-search-result-list-element.component';
+import { PoolSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/pool-search-result/pool-search-result-list-element.component';
import { MyDSpaceNewSubmissionComponent } from './my-dspace-new-submission/my-dspace-new-submission.component';
-import { ItemMyDSpaceResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/item-my-dspace-result/item-my-dspace-result-detail-element.component';
-import { WorkspaceitemMyDSpaceResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/workspaceitem-my-dspace-result/workspaceitem-my-dspace-result-detail-element.component';
-import { WorkflowitemMyDSpaceResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/workflowitem-my-dspace-result/workflowitem-my-dspace-result-detail-element.component';
-import { ClaimedMyDSpaceResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/claimed-my-dspace-result/claimed-my-dspace-result-detail-element.component';
-import { PoolMyDSpaceResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/pool-my-dspace-result/pool-my-dspace-result-detail-lement.component';
+import { ItemSearchResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/item-search-result/item-search-result-detail-element.component';
+import { WorkspaceItemSearchResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/workspace-item-search-result/workspace-item-search-result-detail-element.component';
+import { WorkflowItemSearchResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/workflow-item-search-result/workflow-item-search-result-detail-element.component';
+import { ClaimedTaskSearchResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/claimed-task-search-result/claimed-task-search-result-detail-element.component';
import { MyDSpaceGuard } from './my-dspace.guard';
import { MyDSpaceConfigurationService } from './my-dspace-configuration.service';
+import { SearchResultListElementComponent } from '../shared/object-list/search-result-list-element/search-result-list-element.component';
+import { ItemSearchResultListElementSubmissionComponent } from '../shared/object-list/my-dspace-result-list-element/item-search-result/item-search-result-list-element-submission.component';
+import { WorkflowItemSearchResultListElementComponent } from '../shared/object-list/my-dspace-result-list-element/workflow-item-search-result/workflow-item-search-result-list-element.component';
+import { PoolSearchResultDetailElementComponent } from '../shared/object-detail/my-dspace-result-detail-element/pool-search-result/pool-search-result-detail-element.component';
@NgModule({
imports: [
@@ -29,33 +30,34 @@ import { MyDSpaceConfigurationService } from './my-dspace-configuration.service'
declarations: [
MyDSpacePageComponent,
MyDSpaceResultsComponent,
- ItemMyDSpaceResultListElementComponent,
- WorkspaceitemMyDSpaceResultListElementComponent,
- WorkflowitemMyDSpaceResultListElementComponent,
- ClaimedMyDSpaceResultListElementComponent,
- PoolMyDSpaceResultListElementComponent,
- ItemMyDSpaceResultDetailElementComponent,
- WorkspaceitemMyDSpaceResultDetailElementComponent,
- WorkflowitemMyDSpaceResultDetailElementComponent,
- ClaimedMyDSpaceResultDetailElementComponent,
- PoolMyDSpaceResultDetailElementComponent,
- MyDSpaceNewSubmissionComponent
+ WorkspaceItemSearchResultListElementComponent,
+ WorkflowItemSearchResultListElementComponent,
+ ClaimedSearchResultListElementComponent,
+ PoolSearchResultListElementComponent,
+ ItemSearchResultDetailElementComponent,
+ WorkspaceItemSearchResultDetailElementComponent,
+ WorkflowItemSearchResultDetailElementComponent,
+ ClaimedTaskSearchResultDetailElementComponent,
+ PoolSearchResultDetailElementComponent,
+ MyDSpaceNewSubmissionComponent,
+ ItemSearchResultListElementSubmissionComponent
],
providers: [
MyDSpaceGuard,
MyDSpaceConfigurationService
],
entryComponents: [
- ItemMyDSpaceResultListElementComponent,
- WorkspaceitemMyDSpaceResultListElementComponent,
- WorkflowitemMyDSpaceResultListElementComponent,
- ClaimedMyDSpaceResultListElementComponent,
- PoolMyDSpaceResultListElementComponent,
- ItemMyDSpaceResultDetailElementComponent,
- WorkspaceitemMyDSpaceResultDetailElementComponent,
- WorkflowitemMyDSpaceResultDetailElementComponent,
- ClaimedMyDSpaceResultDetailElementComponent,
- PoolMyDSpaceResultDetailElementComponent
+ SearchResultListElementComponent,
+ WorkspaceItemSearchResultListElementComponent,
+ WorkflowItemSearchResultListElementComponent,
+ ClaimedSearchResultListElementComponent,
+ PoolSearchResultListElementComponent,
+ ItemSearchResultDetailElementComponent,
+ WorkspaceItemSearchResultDetailElementComponent,
+ WorkflowItemSearchResultDetailElementComponent,
+ ClaimedTaskSearchResultDetailElementComponent,
+ PoolSearchResultDetailElementComponent,
+ ItemSearchResultListElementSubmissionComponent
]
})
diff --git a/src/app/+my-dspace-page/my-dspace-result.model.ts b/src/app/+my-dspace-page/my-dspace-result.model.ts
deleted file mode 100644
index 4a4f63aead..0000000000
--- a/src/app/+my-dspace-page/my-dspace-result.model.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { DSpaceObject } from '../core/shared/dspace-object.model';
-import { MetadataMap } from '../core/shared/metadata.models';
-import { ListableObject } from '../shared/object-collection/shared/listable-object.model';
-
-/**
- * Represents a search result object of a certain () DSpaceObject
- */
-export class MyDSpaceResult extends ListableObject {
- /**
- * The DSpaceObject that was found
- */
- indexableObject: T;
-
- /**
- * The metadata that was used to find this item, hithighlighted
- */
- hitHighlights: MetadataMap;
-
-}
diff --git a/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.html b/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.html
index 132a0d2204..63eb0ff9a7 100644
--- a/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.html
+++ b/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.html
@@ -4,7 +4,8 @@
[hasBorder]="hasBorder"
[sortConfig]="searchConfig.sort"
[objects]="searchResults"
- [hideGear]="true">
+ [hideGear]="true"
+ [context]="context">
diff --git a/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.ts b/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.ts
index a3df249c1f..408d167ec1 100644
--- a/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.ts
+++ b/src/app/+my-dspace-page/my-dspace-results/my-dspace-results.component.ts
@@ -1,13 +1,13 @@
import { Component, Input } from '@angular/core';
-
import { RemoteData } from '../../core/data/remote-data';
import { DSpaceObject } from '../../core/shared/dspace-object.model';
import { fadeIn, fadeInOut } from '../../shared/animations/fade';
-import { MyDSpaceResult } from '../my-dspace-result.model';
import { SearchOptions } from '../../shared/search/search-options.model';
import { PaginatedList } from '../../core/data/paginated-list';
import { ViewMode } from '../../core/shared/view-mode.model';
import { isEmpty } from '../../shared/empty.util';
+import { SearchResult } from '../../+search-page/search-result.model';
+import { Context } from '../../core/shared/context.model';
/**
* Component that represents all results for mydspace page
@@ -25,7 +25,7 @@ export class MyDSpaceResultsComponent {
/**
* The actual search result objects
*/
- @Input() searchResults: RemoteData
>>;
+ @Input() searchResults: RemoteData>>;
/**
* The current configuration of the search
@@ -37,6 +37,7 @@ export class MyDSpaceResultsComponent {
*/
@Input() viewMode: ViewMode;
+ context = Context.Submission;
/**
* A boolean representing if search results entry are separated by a line
*/
diff --git a/src/app/+search-page/configuration-search-page.component.ts b/src/app/+search-page/configuration-search-page.component.ts
index 2d331d1acb..3c78935f04 100644
--- a/src/app/+search-page/configuration-search-page.component.ts
+++ b/src/app/+search-page/configuration-search-page.component.ts
@@ -2,13 +2,13 @@ import { HostWindowService } from '../shared/host-window.service';
import { SearchPageComponent } from './search-page.component';
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
import { pushInOut } from '../shared/animations/push';
-import { RouteService } from '../shared/services/route.service';
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
import { SearchService } from '../core/shared/search/search.service';
import { SearchSidebarService } from '../core/shared/search/search-sidebar.service';
import { Router } from '@angular/router';
import { hasValue } from '../shared/empty.util';
+import { RouteService } from '../core/services/route.service';
/**
* This component renders a search page using a configuration as input.
@@ -52,7 +52,7 @@ export class ConfigurationSearchPageComponent extends SearchPageComponent implem
*/
ngOnInit(): void {
super.ngOnInit();
- if (hasValue(this.configuration )) {
+ if (hasValue(this.configuration)) {
this.routeService.setParameter('configuration', this.configuration);
}
}
diff --git a/src/app/+search-page/filtered-search-page.component.ts b/src/app/+search-page/filtered-search-page.component.ts
index c2bc2b50c5..7dc4cf536e 100644
--- a/src/app/+search-page/filtered-search-page.component.ts
+++ b/src/app/+search-page/filtered-search-page.component.ts
@@ -4,14 +4,12 @@ import { SearchSidebarService } from '../core/shared/search/search-sidebar.servi
import { SearchPageComponent } from './search-page.component';
import { ChangeDetectionStrategy, Component, Inject, Input, OnInit } from '@angular/core';
import { pushInOut } from '../shared/animations/push';
-import { RouteService } from '../shared/services/route.service';
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
-import { Observable } from 'rxjs';
-import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
import { map, take } from 'rxjs/operators';
import { Router } from '@angular/router';
import { hasValue, isNotEmpty, isNotEmptyOperator } from '../shared/empty.util';
+import { RouteService } from '../core/services/route.service';
/**
* This component renders a simple item page.
diff --git a/src/app/+search-page/search-labels/search-label/search-label.component.html b/src/app/+search-page/search-labels/search-label/search-label.component.html
new file mode 100644
index 0000000000..391efcb763
--- /dev/null
+++ b/src/app/+search-page/search-labels/search-label/search-label.component.html
@@ -0,0 +1,6 @@
+
+ {{('search.filters.applied.' + key) | translate}}: {{normalizeFilterValue(value)}}
+ ×
+
\ No newline at end of file
diff --git a/src/app/+search-page/search-labels/search-label/search-label.component.spec.ts b/src/app/+search-page/search-labels/search-label/search-label.component.spec.ts
new file mode 100644
index 0000000000..a2603b7b8b
--- /dev/null
+++ b/src/app/+search-page/search-labels/search-label/search-label.component.spec.ts
@@ -0,0 +1,87 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { TranslateModule } from '@ngx-translate/core';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+import { Observable, of as observableOf } from 'rxjs';
+import { Params } from '@angular/router';
+import { SearchLabelComponent } from './search-label.component';
+import { ObjectKeysPipe } from '../../../shared/utils/object-keys-pipe';
+import { SearchService } from '../../search-service/search.service';
+import { SEARCH_CONFIG_SERVICE } from '../../../+my-dspace-page/my-dspace-page.component';
+import { SearchServiceStub } from '../../../shared/testing/search-service-stub';
+import { SearchConfigurationServiceStub } from '../../../shared/testing/search-configuration-service-stub';
+
+describe('SearchLabelComponent', () => {
+ let comp: SearchLabelComponent;
+ let fixture: ComponentFixture;
+
+ const searchLink = '/search';
+ let searchService;
+
+ const key1 = 'author';
+ const key2 = 'subject';
+ const value1 = 'Test, Author';
+ const normValue1 = 'Test, Author';
+ const value2 = 'TestSubject';
+ const value3 = 'Test, Authority,authority';
+ const normValue3 = 'Test, Authority';
+ const filter1 = [key1, value1];
+ const filter2 = [key2, value2];
+ const mockFilters = [
+ filter1,
+ filter2
+ ];
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],
+ declarations: [SearchLabelComponent, ObjectKeysPipe],
+ providers: [
+ { provide: SearchService, useValue: new SearchServiceStub(searchLink) },
+ { provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() }
+ // { provide: SearchConfigurationService, useValue: {getCurrentFrontendFilters : () => observableOf({})} }
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+ }).overrideComponent(SearchLabelComponent, {
+ set: { changeDetection: ChangeDetectionStrategy.Default }
+ }).compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(SearchLabelComponent);
+ comp = fixture.componentInstance;
+ searchService = (comp as any).searchService;
+ comp.key = key1;
+ comp.value = value1;
+ (comp as any).appliedFilters = observableOf(mockFilters);
+ fixture.detectChanges();
+ });
+
+ describe('when getRemoveParams is called', () => {
+ let obs: Observable;
+
+ beforeEach(() => {
+ obs = comp.getRemoveParams();
+ });
+
+ it('should return all params but the provided filter', () => {
+ obs.subscribe((params) => {
+ // Should contain only filter2 and page: length == 2
+ expect(Object.keys(params).length).toBe(2);
+ });
+ })
+ });
+
+ describe('when normalizeFilterValue is called', () => {
+ it('should return properly filter value', () => {
+ let result: string;
+
+ result = comp.normalizeFilterValue(value1);
+ expect(result).toBe(normValue1);
+
+ result = comp.normalizeFilterValue(value3);
+ expect(result).toBe(normValue3);
+ })
+ });
+});
diff --git a/src/app/+search-page/search-labels/search-label/search-label.component.ts b/src/app/+search-page/search-labels/search-label/search-label.component.ts
new file mode 100644
index 0000000000..2f44f91a35
--- /dev/null
+++ b/src/app/+search-page/search-labels/search-label/search-label.component.ts
@@ -0,0 +1,75 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { Observable } from 'rxjs';
+import { Params } from '@angular/router';
+import { SearchService } from '../../search-service/search.service';
+import { map } from 'rxjs/operators';
+import { hasValue, isNotEmpty } from '../../../shared/empty.util';
+
+@Component({
+ selector: 'ds-search-label',
+ templateUrl: './search-label.component.html',
+})
+
+/**
+ * Component that represents the label containing the currently active filters
+ */
+export class SearchLabelComponent implements OnInit {
+ @Input() key: string;
+ @Input() value: string;
+ @Input() inPlaceSearch: boolean;
+ @Input() appliedFilters: Observable;
+ searchLink: string;
+ removeParameters: Observable;
+
+ /**
+ * Initialize the instance variable
+ */
+ constructor(
+ private searchService: SearchService) {
+ }
+
+ ngOnInit(): void {
+ this.searchLink = this.getSearchLink();
+ this.removeParameters = this.getRemoveParams();
+ }
+
+ /**
+ * Calculates the parameters that should change if a given value for the given filter would be removed from the active filters
+ * @returns {Observable} The changed filter parameters
+ */
+ getRemoveParams(): Observable {
+ return this.appliedFilters.pipe(
+ map((filters) => {
+ const field: string = Object.keys(filters).find((f) => f === this.key);
+ const newValues = hasValue(filters[field]) ? filters[field].filter((v) => v !== this.value) : null;
+ return {
+ [field]: isNotEmpty(newValues) ? newValues : null,
+ page: 1
+ };
+ })
+ )
+ }
+
+ /**
+ * @returns {string} The base path to the search page, or the current page when inPlaceSearch is true
+ */
+ private getSearchLink(): string {
+ if (this.inPlaceSearch) {
+ return './';
+ }
+ return this.searchService.getSearchLink();
+ }
+
+ /**
+ * TODO to review after https://github.com/DSpace/dspace-angular/issues/368 is resolved
+ * Strips authority operator from filter value
+ * e.g. 'test ,authority' => 'test'
+ *
+ * @param value
+ */
+ normalizeFilterValue(value: string) {
+ // const pattern = /,[^,]*$/g;
+ const pattern = /,authority*$/g;
+ return value.replace(pattern, '');
+ }
+}
diff --git a/src/app/+search-page/search-page-routing.module.ts b/src/app/+search-page/search-page-routing.module.ts
index d1ab02945e..d2c3b3be39 100644
--- a/src/app/+search-page/search-page-routing.module.ts
+++ b/src/app/+search-page/search-page-routing.module.ts
@@ -13,4 +13,5 @@ import { ConfigurationSearchPageComponent } from './configuration-search-page.co
])
]
})
-export class SearchPageRoutingModule { }
+export class SearchPageRoutingModule {
+}
diff --git a/src/app/+search-page/search-page.component.html b/src/app/+search-page/search-page.component.html
index f98187b2b9..56e042f811 100644
--- a/src/app/+search-page/search-page.component.html
+++ b/src/app/+search-page/search-page.component.html
@@ -7,7 +7,7 @@
@@ -19,12 +19,12 @@
+ [@pushInOut]="(isSidebarCollapsed$ | async) ? 'collapsed' : 'expanded'">
diff --git a/src/app/+search-page/search-page.component.spec.ts b/src/app/+search-page/search-page.component.spec.ts
index bc8cb2907b..7b6db860e0 100644
--- a/src/app/+search-page/search-page.component.spec.ts
+++ b/src/app/+search-page/search-page.component.spec.ts
@@ -20,7 +20,7 @@ import { SearchSidebarService } from '../core/shared/search/search-sidebar.servi
import { SearchFilterService } from '../core/shared/search/search-filter.service';
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
-import { RouteService } from '../shared/services/route.service';
+import { RouteService } from '../core/services/route.service';
import { SearchConfigurationServiceStub } from '../shared/testing/search-configuration-service-stub';
import { createSuccessfulRemoteDataObject$ } from '../shared/testing/utils';
import { PaginatedSearchOptions } from '../shared/search/paginated-search-options.model';
@@ -191,7 +191,7 @@ describe('SearchPageComponent', () => {
beforeEach(() => {
menu = fixture.debugElement.query(By.css('#search-sidebar-sm')).nativeElement;
- comp.isSidebarCollapsed = () => observableOf(true);
+ (comp as any).isSidebarCollapsed$ = observableOf(true);
fixture.detectChanges();
});
@@ -206,7 +206,7 @@ describe('SearchPageComponent', () => {
beforeEach(() => {
menu = fixture.debugElement.query(By.css('#search-sidebar-sm')).nativeElement;
- comp.isSidebarCollapsed = () => observableOf(false);
+ (comp as any).isSidebarCollapsed$ = observableOf(false);
fixture.detectChanges();
});
diff --git a/src/app/+search-page/search-page.component.ts b/src/app/+search-page/search-page.component.ts
index cc77f65c55..5896490260 100644
--- a/src/app/+search-page/search-page.component.ts
+++ b/src/app/+search-page/search-page.component.ts
@@ -13,7 +13,7 @@ import { SearchSidebarService } from '../core/shared/search/search-sidebar.servi
import { hasValue, isNotEmpty } from '../shared/empty.util';
import { SearchConfigurationService } from '../core/shared/search/search-configuration.service';
import { getSucceededRemoteData } from '../core/shared/operators';
-import { RouteService } from '../shared/services/route.service';
+import { RouteService } from '../core/services/route.service';
import { SEARCH_CONFIG_SERVICE } from '../+my-dspace-page/my-dspace-page.component';
import { currentPath } from '../shared/utils/route.utils';
import { Router } from '@angular/router';
@@ -93,6 +93,16 @@ export class SearchPageComponent implements OnInit {
@Input()
configuration$: Observable
;
+ /**
+ * Link to the search page
+ */
+ searchLink: string;
+
+ /**
+ * Observable for whether or not the sidebar is currently collapsed
+ */
+ isSidebarCollapsed$: Observable;
+
constructor(protected service: SearchService,
protected sidebarService: SearchSidebarService,
protected windowService: HostWindowService,
@@ -110,6 +120,8 @@ export class SearchPageComponent implements OnInit {
* If something changes, update the list of scopes for the dropdown
*/
ngOnInit(): void {
+ this.isSidebarCollapsed$ = this.isSidebarCollapsed();
+ this.searchLink = this.getSearchLink();
this.searchOptions$ = this.getSearchOptions();
this.sub = this.searchOptions$.pipe(
switchMap((options) => this.service.search(options).pipe(getSucceededRemoteData(), startWith(undefined))))
@@ -150,14 +162,14 @@ export class SearchPageComponent implements OnInit {
* Check if the sidebar is collapsed
* @returns {Observable} emits true if the sidebar is currently collapsed, false if it is expanded
*/
- public isSidebarCollapsed(): Observable {
+ private isSidebarCollapsed(): Observable {
return this.sidebarService.isCollapsed;
}
/**
* @returns {string} The base path to the search page, or the current page when inPlaceSearch is true
*/
- public getSearchLink(): string {
+ private getSearchLink(): string {
if (this.inPlaceSearch) {
return currentPath(this.router);
}
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 86364aca89..e1ddc2b889 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -16,6 +16,12 @@ const COMMUNITY_MODULE_PATH = 'communities';
export function getCommunityModulePath() {
return `/${COMMUNITY_MODULE_PATH}`;
}
+
+const ADMIN_MODULE_PATH = 'admin';
+export function getAdminModulePath() {
+ return `/${ADMIN_MODULE_PATH}`;
+}
+
@NgModule({
imports: [
RouterModule.forRoot([
@@ -27,7 +33,7 @@ export function getCommunityModulePath() {
{ path: 'mydspace', loadChildren: './+my-dspace-page/my-dspace-page.module#MyDSpacePageModule', canActivate: [AuthenticatedGuard] },
{ path: 'search', loadChildren: './+search-page/search-page.module#SearchPageModule' },
{ path: 'browse', loadChildren: './+browse-by/browse-by.module#BrowseByModule' },
- { path: 'admin', loadChildren: './+admin/admin.module#AdminModule', canActivate: [AuthenticatedGuard] },
+ { path: ADMIN_MODULE_PATH, loadChildren: './+admin/admin.module#AdminModule', canActivate: [AuthenticatedGuard] },
{ path: 'login', loadChildren: './+login-page/login-page.module#LoginPageModule' },
{ path: 'logout', loadChildren: './+logout-page/logout-page.module#LogoutPageModule' },
{ path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' },
diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts
index 03fa490bb3..33a46aa4de 100644
--- a/src/app/app.component.spec.ts
+++ b/src/app/app.component.spec.ts
@@ -26,7 +26,7 @@ import { HostWindowResizeAction } from './shared/host-window.actions';
import { MetadataService } from './core/metadata/metadata.service';
import { GLOBAL_CONFIG, ENV_CONFIG } from '../config';
-import { NativeWindowRef, NativeWindowService } from './shared/services/window.service';
+import { NativeWindowRef, NativeWindowService } from './core/services/window.service';
import { MockTranslateLoader } from './shared/mocks/mock-translate-loader';
import { MockMetadataService } from './shared/mocks/mock-metadata-service';
@@ -41,9 +41,11 @@ import { MenuServiceStub } from './shared/testing/menu-service-stub';
import { HostWindowService } from './shared/host-window.service';
import { HostWindowServiceStub } from './shared/testing/host-window-service-stub';
import { ActivatedRoute, Router } from '@angular/router';
-import { RouteService } from './shared/services/route.service';
+import { RouteService } from './core/services/route.service';
import { MockActivatedRoute } from './shared/mocks/mock-active-router';
import { MockRouter } from './shared/mocks/mock-router';
+import { MockCookieService } from './shared/mocks/mock-cookie.service';
+import { CookieService } from './core/services/cookie.service';
let comp: AppComponent;
let fixture: ComponentFixture;
@@ -78,6 +80,7 @@ describe('App component', () => {
{ provide: MenuService, useValue: menuService },
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
+ { provide: CookieService, useValue: new MockCookieService()},
AppComponent,
RouteService
],
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 4591611b7f..41ed61e699 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,13 +1,5 @@
import { filter, map, take } from 'rxjs/operators';
-import {
- AfterViewInit,
- ChangeDetectionStrategy,
- Component,
- HostListener,
- Inject,
- OnInit,
- ViewEncapsulation
-} from '@angular/core';
+import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, Inject, OnInit, ViewEncapsulation } from '@angular/core';
import { NavigationCancel, NavigationEnd, NavigationStart, Router } from '@angular/router';
import { select, Store } from '@ngrx/store';
@@ -19,7 +11,7 @@ import { GLOBAL_CONFIG, GlobalConfig } from '../config';
import { MetadataService } from './core/metadata/metadata.service';
import { HostWindowResizeAction } from './shared/host-window.actions';
import { HostWindowState } from './shared/search/host-window.reducer';
-import { NativeWindowRef, NativeWindowService } from './shared/services/window.service';
+import { NativeWindowRef, NativeWindowService } from './core/services/window.service';
import { isAuthenticated } from './core/auth/selectors';
import { AuthService } from './core/auth/auth.service';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
@@ -31,6 +23,10 @@ import { combineLatest as combineLatestObservable, Observable, of } from 'rxjs';
import { slideSidebarPadding } from './shared/animations/slide';
import { HostWindowService } from './shared/host-window.service';
import { Theme } from '../config/theme.inferface';
+import { isNotEmpty } from './shared/empty.util';
+import { CookieService } from './core/services/cookie.service';
+
+export const LANG_COOKIE = 'language_cookie';
@Component({
selector: 'ds-app',
@@ -60,6 +56,7 @@ export class AppComponent implements OnInit, AfterViewInit {
private cssService: CSSVariableService,
private menuService: MenuService,
private windowService: HostWindowService,
+ private cookie: CookieService
) {
// Load all the languages that are defined as active from the config file
translate.addLangs(config.languages.filter((LangConfig) => LangConfig.active === true).map((a) => a.code));
@@ -67,11 +64,20 @@ export class AppComponent implements OnInit, AfterViewInit {
// Load the default language from the config file
translate.setDefaultLang(config.defaultLanguage);
- // Attempt to get the browser language from the user
- if (translate.getLangs().includes(translate.getBrowserLang())) {
- translate.use(translate.getBrowserLang());
+ // Attempt to get the language from a cookie
+ const lang = cookie.get(LANG_COOKIE);
+ if (isNotEmpty(lang)) {
+ // Cookie found
+ // Use the language from the cookie
+ translate.use(lang);
} else {
- translate.use(config.defaultLanguage);
+ // Cookie not found
+ // Attempt to get the browser language from the user
+ if (translate.getLangs().includes(translate.getBrowserLang())) {
+ translate.use(translate.getBrowserLang());
+ } else {
+ translate.use(config.defaultLanguage);
+ }
}
metadata.listenForRouteChange();
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index ce5a2d78a2..916788df8c 100755
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -39,6 +39,7 @@ import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/e
import { NavbarModule } from './navbar/navbar.module';
import { JournalEntitiesModule } from './entity-groups/journal-entities/journal-entities.module';
import { ResearchEntitiesModule } from './entity-groups/research-entities/research-entities.module';
+import { ClientCookieService } from './core/services/client-cookie.service';
export function getConfig() {
return ENV_CONFIG;
@@ -97,7 +98,8 @@ const PROVIDERS = [
{
provide: RouterStateSerializer,
useClass: DSpaceRouterStateSerializer
- }
+ },
+ ClientCookieService
];
const DECLARATIONS = [
diff --git a/src/app/app.reducer.ts b/src/app/app.reducer.ts
index 26a9c6d2e2..3622b52e83 100644
--- a/src/app/app.reducer.ts
+++ b/src/app/app.reducer.ts
@@ -27,6 +27,11 @@ import {
selectableListReducer,
SelectableListsState
} from './shared/object-list/selectable-list/selectable-list.reducer';
+import {
+ bitstreamFormatReducer,
+ BitstreamFormatRegistryState
+} from './+admin/admin-registries/bitstream-formats/bitstream-format.reducers';
+import { ObjectSelectionListState, objectSelectionReducer } from './shared/object-select/object-select.reducer';
export interface AppState {
router: fromRouter.RouterReducerState;
@@ -34,13 +39,15 @@ export interface AppState {
hostWindow: HostWindowState;
forms: FormState;
metadataRegistry: MetadataRegistryState;
+ bitstreamFormats: BitstreamFormatRegistryState;
notifications: NotificationsState;
searchSidebar: SearchSidebarState;
searchFilter: SearchFiltersState;
truncatable: TruncatablesState;
cssVariables: CSSVariablesState;
menus: MenusState;
- selectableLists: SelectableListsState
+ objectSelection: ObjectSelectionListState;
+ selectableLists: SelectableListsState;
}
export const appReducers: ActionReducerMap = {
@@ -49,12 +56,14 @@ export const appReducers: ActionReducerMap = {
hostWindow: hostWindowReducer,
forms: formReducer,
metadataRegistry: metadataRegistryReducer,
+ bitstreamFormats: bitstreamFormatReducer,
notifications: notificationsReducer,
searchSidebar: sidebarReducer,
searchFilter: filterReducer,
truncatable: truncatableReducer,
cssVariables: cssVariablesReducer,
menus: menusReducer,
+ objectSelection: objectSelectionReducer,
selectableLists: selectableListReducer
};
diff --git a/src/app/core/auth/auth.service.spec.ts b/src/app/core/auth/auth.service.spec.ts
index e766a45e48..5084dc8596 100644
--- a/src/app/core/auth/auth.service.spec.ts
+++ b/src/app/core/auth/auth.service.spec.ts
@@ -7,12 +7,12 @@ import { REQUEST } from '@nguniversal/express-engine/tokens';
import { of as observableOf } from 'rxjs';
import { authReducer, AuthState } from './auth.reducer';
-import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service';
+import { NativeWindowRef, NativeWindowService } from '../services/window.service';
import { AuthService } from './auth.service';
import { RouterStub } from '../../shared/testing/router-stub';
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
-import { CookieService } from '../../shared/services/cookie.service';
+import { CookieService } from '../services/cookie.service';
import { AuthRequestServiceStub } from '../../shared/testing/auth-request-service-stub';
import { AuthRequestService } from './auth-request.service';
import { AuthStatus } from './models/auth-status.model';
@@ -20,14 +20,17 @@ import { AuthTokenInfo } from './models/auth-token-info.model';
import { EPerson } from '../eperson/models/eperson.model';
import { EPersonMock } from '../../shared/testing/eperson-mock';
import { AppState } from '../../app.reducer';
-import { ClientCookieService } from '../../shared/services/client-cookie.service';
+import { ClientCookieService } from '../services/client-cookie.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { getMockRemoteDataBuildService } from '../../shared/mocks/mock-remote-data-build.service';
+import { routeServiceStub } from '../../shared/testing/route-service-stub';
+import { RouteService } from '../services/route.service';
describe('AuthService test', () => {
let mockStore: Store;
let authService: AuthService;
+ let routeServiceMock: RouteService;
let authRequest;
let window;
let routerStub;
@@ -74,6 +77,7 @@ describe('AuthService test', () => {
{ provide: NativeWindowService, useValue: window },
{ provide: REQUEST, useValue: {} },
{ provide: Router, useValue: routerStub },
+ { provide: RouteService, useValue: routeServiceStub },
{ provide: ActivatedRoute, useValue: routeStub },
{ provide: Store, useValue: mockStore },
{ provide: RemoteDataBuildService, useValue: rdbService },
@@ -138,6 +142,7 @@ describe('AuthService test', () => {
{ provide: AuthRequestService, useValue: authRequest },
{ provide: REQUEST, useValue: {} },
{ provide: Router, useValue: routerStub },
+ { provide: RouteService, useValue: routeServiceStub },
{ provide: RemoteDataBuildService, useValue: rdbService },
CookieService,
AuthService
@@ -145,13 +150,13 @@ describe('AuthService test', () => {
}).compileComponents();
}));
- beforeEach(inject([CookieService, AuthRequestService, Store, Router], (cookieService: CookieService, authReqService: AuthRequestService, store: Store, router: Router) => {
+ beforeEach(inject([CookieService, AuthRequestService, Store, Router, RouteService], (cookieService: CookieService, authReqService: AuthRequestService, store: Store, router: Router, routeService: RouteService) => {
store
.subscribe((state) => {
(state as any).core = Object.create({});
(state as any).core.auth = authenticatedState;
});
- authService = new AuthService({}, window, undefined, authReqService, router, cookieService, store, rdbService);
+ authService = new AuthService({}, window, undefined, authReqService, router, routeService, cookieService, store, rdbService);
}));
it('should return true when user is logged in', () => {
@@ -189,6 +194,7 @@ describe('AuthService test', () => {
{ provide: AuthRequestService, useValue: authRequest },
{ provide: REQUEST, useValue: {} },
{ provide: Router, useValue: routerStub },
+ { provide: RouteService, useValue: routeServiceStub },
{ provide: RemoteDataBuildService, useValue: rdbService },
ClientCookieService,
CookieService,
@@ -197,7 +203,7 @@ describe('AuthService test', () => {
}).compileComponents();
}));
- beforeEach(inject([ClientCookieService, AuthRequestService, Store, Router], (cookieService: ClientCookieService, authReqService: AuthRequestService, store: Store, router: Router) => {
+ beforeEach(inject([ClientCookieService, AuthRequestService, Store, Router, RouteService], (cookieService: ClientCookieService, authReqService: AuthRequestService, store: Store, router: Router, routeService: RouteService) => {
const expiredToken: AuthTokenInfo = new AuthTokenInfo('test_token');
expiredToken.expires = Date.now() - (1000 * 60 * 60);
authenticatedState = {
@@ -212,11 +218,14 @@ describe('AuthService test', () => {
(state as any).core = Object.create({});
(state as any).core.auth = authenticatedState;
});
- authService = new AuthService({}, window, undefined, authReqService, router, cookieService, store, rdbService);
+ authService = new AuthService({}, window, undefined, authReqService, router, routeService, cookieService, store, rdbService);
storage = (authService as any).storage;
+ routeServiceMock = TestBed.get(RouteService);
+ routerStub = TestBed.get(Router);
spyOn(storage, 'get');
spyOn(storage, 'remove');
spyOn(storage, 'set');
+
}));
it('should throw false when token is not valid', () => {
@@ -238,5 +247,32 @@ describe('AuthService test', () => {
expect(storage.remove).toHaveBeenCalled();
});
+ it ('should set redirect url to previous page', () => {
+ spyOn(routeServiceMock, 'getHistory').and.callThrough();
+ authService.redirectAfterLoginSuccess(true);
+ expect(routeServiceMock.getHistory).toHaveBeenCalled();
+ expect(routerStub.navigate).toHaveBeenCalledWith(['/collection/123']);
+ });
+
+ it ('should set redirect url to current page', () => {
+ spyOn(routeServiceMock, 'getHistory').and.callThrough();
+ authService.redirectAfterLoginSuccess(false);
+ expect(routeServiceMock.getHistory).toHaveBeenCalled();
+ expect(routerStub.navigate).toHaveBeenCalledWith(['/home']);
+ });
+
+ it ('should redirect to / and not to /login', () => {
+ spyOn(routeServiceMock, 'getHistory').and.returnValue(observableOf(['/login', '/login']));
+ authService.redirectAfterLoginSuccess(true);
+ expect(routeServiceMock.getHistory).toHaveBeenCalled();
+ expect(routerStub.navigate).toHaveBeenCalledWith(['/']);
+ });
+
+ it ('should redirect to / when no redirect url is found', () => {
+ spyOn(routeServiceMock, 'getHistory').and.returnValue(observableOf(['']));
+ authService.redirectAfterLoginSuccess(true);
+ expect(routeServiceMock.getHistory).toHaveBeenCalled();
+ expect(routerStub.navigate).toHaveBeenCalledWith(['/']);
+ });
});
});
diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts
index a01768e687..5287e537ee 100644
--- a/src/app/core/auth/auth.service.ts
+++ b/src/app/core/auth/auth.service.ts
@@ -15,13 +15,14 @@ import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { AuthStatus } from './models/auth-status.model';
import { AuthTokenInfo, TOKENITEM } from './models/auth-token-info.model';
import { isEmpty, isNotEmpty, isNotNull, isNotUndefined } from '../../shared/empty.util';
-import { CookieService } from '../../shared/services/cookie.service';
+import { CookieService } from '../services/cookie.service';
import { getAuthenticationToken, getRedirectUrl, isAuthenticated, isTokenRefreshing } from './selectors';
import { AppState, routerStateSelector } from '../../app.reducer';
import { ResetAuthenticationMessagesAction, SetRedirectUrlAction } from './auth.actions';
-import { NativeWindowRef, NativeWindowService } from '../../shared/services/window.service';
+import { NativeWindowRef, NativeWindowService } from '../services/window.service';
import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
+import {RouteService} from '../services/route.service';
export const LOGIN_ROUTE = '/login';
export const LOGOUT_ROUTE = '/logout';
@@ -45,6 +46,7 @@ export class AuthService {
protected authRequestService: AuthRequestService,
@Optional() @Inject(RESPONSE) private response: any,
protected router: Router,
+ protected routeService: RouteService,
protected storage: CookieService,
protected store: Store,
protected rdbService: RemoteDataBuildService
@@ -337,7 +339,7 @@ export class AuthService {
/**
* Redirect to the route navigated before the login
*/
- public redirectToPreviousUrl() {
+ public redirectAfterLoginSuccess(isStandalonePage: boolean) {
this.getRedirectUrl().pipe(
take(1))
.subscribe((redirectUrl) => {
@@ -346,18 +348,39 @@ export class AuthService {
this.clearRedirectUrl();
this.router.onSameUrlNavigation = 'reload';
const url = decodeURIComponent(redirectUrl);
- this.router.navigateByUrl(url);
- /* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */
- // this._window.nativeWindow.location.href = url;
+ this.navigateToRedirectUrl(url);
} else {
- this.router.navigate(['/']);
- /* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */
- // this._window.nativeWindow.location.href = '/';
+ // If redirectUrl is empty use history.
+ this.routeService.getHistory().pipe(
+ take(1)
+ ).subscribe((history) => {
+ let redirUrl;
+ if (isStandalonePage) {
+ // For standalone login pages, use the previous route.
+ redirUrl = history[history.length - 2] || '';
+ } else {
+ redirUrl = history[history.length - 1] || '';
+ }
+ this.navigateToRedirectUrl(redirUrl);
+ });
}
- })
+ });
}
+ protected navigateToRedirectUrl(url: string) {
+ // in case the user navigates directly to /login (via bookmark, etc), or the route history is not found.
+ if (isEmpty(url) || url.startsWith(LOGIN_ROUTE)) {
+ this.router.navigate(['/']);
+ /* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */
+ // this._window.nativeWindow.location.href = '/';
+ } else {
+ /* TODO Reenable hard redirect when REST API can handle x-forwarded-for, see https://github.com/DSpace/DSpace/pull/2207 */
+ // this._window.nativeWindow.location.href = url;
+ this.router.navigate([url]);
+ }
+ }
+
/**
* Refresh route navigated
*/
@@ -400,4 +423,5 @@ export class AuthService {
this.store.dispatch(new SetRedirectUrlAction(''));
this.storage.remove(REDIRECT_COOKIE);
}
+
}
diff --git a/src/app/core/auth/server-auth.service.ts b/src/app/core/auth/server-auth.service.ts
index c344683e38..cf4d4a658e 100644
--- a/src/app/core/auth/server-auth.service.ts
+++ b/src/app/core/auth/server-auth.service.ts
@@ -1,12 +1,12 @@
-import { map, switchMap, take } from 'rxjs/operators';
+import { filter, map, switchMap, take } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpHeaders } from '@angular/common/http';
import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
import { AuthStatus } from './models/auth-status.model';
-import { isNotEmpty } from '../../shared/empty.util';
-import { AuthService } from './auth.service';
+import { isEmpty, isNotEmpty } from '../../shared/empty.util';
+import { AuthService, LOGIN_ROUTE } from './auth.service';
import { AuthTokenInfo } from './models/auth-token-info.model';
import { CheckAuthenticationTokenAction } from './auth.actions';
import { EPerson } from '../eperson/models/eperson.model';
@@ -54,7 +54,7 @@ export class ServerAuthService extends AuthService {
/**
* Redirect to the route navigated before the login
*/
- public redirectToPreviousUrl() {
+ public redirectAfterLoginSuccess(isStandalonePage: boolean) {
this.getRedirectUrl().pipe(
take(1))
.subscribe((redirectUrl) => {
@@ -67,10 +67,15 @@ export class ServerAuthService extends AuthService {
const url = decodeURIComponent(redirectUrl);
this.router.navigateByUrl(url);
} else {
- this.router.navigate(['/']);
+ // If redirectUrl is empty use history. For ssr the history array should contain the requested url.
+ this.routeService.getHistory().pipe(
+ filter((history) => history.length > 0),
+ take(1)
+ ).subscribe((history) => {
+ this.navigateToRedirectUrl(history[history.length - 1] || '');
+ });
}
})
-
}
}
diff --git a/src/app/core/cache/builders/remote-data-build.service.ts b/src/app/core/cache/builders/remote-data-build.service.ts
index c6ba704d81..0a1ba0b9e0 100644
--- a/src/app/core/cache/builders/remote-data-build.service.ts
+++ b/src/app/core/cache/builders/remote-data-build.service.ts
@@ -82,8 +82,8 @@ export class RemoteDataBuildService {
toRemoteDataObservable(requestEntry$: Observable, payload$: Observable) {
return observableCombineLatest(requestEntry$, payload$).pipe(
map(([reqEntry, payload]) => {
- const requestPending = hasValue(reqEntry.requestPending) ? reqEntry.requestPending : true;
- const responsePending = hasValue(reqEntry.responsePending) ? reqEntry.responsePending : false;
+ const requestPending = hasValue(reqEntry) && hasValue(reqEntry.requestPending) ? reqEntry.requestPending : true;
+ const responsePending = hasValue(reqEntry) && hasValue(reqEntry.responsePending) ? reqEntry.responsePending : false;
let isSuccessful: boolean;
let error: RemoteDataError;
if (hasValue(reqEntry) && hasValue(reqEntry.response)) {
diff --git a/src/app/core/cache/models/items/normalized-relationship-type.model.ts b/src/app/core/cache/models/items/normalized-relationship-type.model.ts
index 800b27cd7e..23c3333a9b 100644
--- a/src/app/core/cache/models/items/normalized-relationship-type.model.ts
+++ b/src/app/core/cache/models/items/normalized-relationship-type.model.ts
@@ -23,7 +23,7 @@ export class NormalizedRelationshipType extends NormalizedObject
* The level of support the system offers for this Bitstream Format
*/
@autoserialize
- supportLevel: SupportLevel;
+ supportLevel: BitstreamFormatSupportLevel;
/**
* True if the Bitstream Format is used to store system information, rather than the content of items in the system
@@ -46,7 +46,7 @@ export class NormalizedBitstreamFormat extends NormalizedObject
* String representing this Bitstream Format's file extension
*/
@autoserialize
- extensions: string;
+ extensions: string[];
/**
* Identifier for this Bitstream Format
diff --git a/src/app/core/core.effects.ts b/src/app/core/core.effects.ts
index 11dba82593..9f4242cc9a 100644
--- a/src/app/core/core.effects.ts
+++ b/src/app/core/core.effects.ts
@@ -5,7 +5,7 @@ import { AuthEffects } from './auth/auth.effects';
import { JsonPatchOperationsEffects } from './json-patch/json-patch-operations.effects';
import { ServerSyncBufferEffects } from './cache/server-sync-buffer.effects';
import { ObjectUpdatesEffects } from './data/object-updates/object-updates.effects';
-import { RouteEffects } from '../shared/services/route.effects';
+import { RouteEffects } from './services/route.effects';
import { RouterEffects } from './router/router.effects';
export const coreEffects = [
diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts
index 9c4620d72e..0f9857b431 100644
--- a/src/app/core/core.module.ts
+++ b/src/app/core/core.module.ts
@@ -14,7 +14,7 @@ import { coreReducers } from './core.reducers';
import { isNotEmpty } from '../shared/empty.util';
-import { ApiService } from '../shared/services/api.service';
+import { ApiService } from './services/api.service';
import { BrowseEntriesResponseParsingService } from './data/browse-entries-response-parsing.service';
import { CollectionDataService } from './data/collection-data.service';
import { CommunityDataService } from './data/community-data.service';
@@ -34,12 +34,12 @@ import { PaginationComponentOptions } from '../shared/pagination/pagination-comp
import { RemoteDataBuildService } from './cache/builders/remote-data-build.service';
import { RequestService } from './data/request.service';
import { EndpointMapResponseParsingService } from './data/endpoint-map-response-parsing.service';
-import { ServerResponseService } from '../shared/services/server-response.service';
-import { NativeWindowFactory, NativeWindowService } from '../shared/services/window.service';
+import { ServerResponseService } from './services/server-response.service';
+import { NativeWindowFactory, NativeWindowService } from './services/window.service';
import { BrowseService } from './browse/browse.service';
import { BrowseResponseParsingService } from './data/browse-response-parsing.service';
import { ConfigResponseParsingService } from './config/config-response-parsing.service';
-import { RouteService } from '../shared/services/route.service';
+import { RouteService } from './services/route.service';
import { SubmissionDefinitionsConfigService } from './config/submission-definitions-config.service';
import { SubmissionFormsConfigService } from './config/submission-forms-config.service';
import { SubmissionSectionsConfigService } from './config/submission-sections-config.service';
@@ -108,6 +108,7 @@ import { MyDSpaceResponseParsingService } from './data/mydspace-response-parsing
import { ClaimedTaskDataService } from './tasks/claimed-task-data.service';
import { PoolTaskDataService } from './tasks/pool-task-data.service';
import { TaskResponseParsingService } from './tasks/task-response-parsing.service';
+import { BitstreamFormatDataService } from './data/bitstream-format-data.service';
import { NormalizedClaimedTask } from './tasks/models/normalized-claimed-task-object.model';
import { NormalizedTaskObject } from './tasks/models/normalized-task-object.model';
import { NormalizedPoolTask } from './tasks/models/normalized-pool-task-object.model';
@@ -118,6 +119,8 @@ import { MetadatafieldParsingService } from './data/metadatafield-parsing.servic
import { NormalizedSubmissionUploadsModel } from './config/models/normalized-config-submission-uploads.model';
import { NormalizedBrowseEntry } from './shared/normalized-browse-entry.model';
import { BrowseDefinition } from './shared/browse-definition.model';
+import { MappedCollectionsReponseParsingService } from './data/mapped-collections-reponse-parsing.service';
+import { ObjectSelectService } from '../shared/object-select/object-select.service';
import {
MOCK_RESPONSE_MAP,
@@ -176,6 +179,7 @@ const PROVIDERS = [
PaginationComponentOptions,
ResourcePolicyService,
RegistryService,
+ BitstreamFormatDataService,
NormalizedObjectBuildService,
RemoteDataBuildService,
RequestService,
@@ -186,6 +190,7 @@ const PROVIDERS = [
RegistryMetadataschemasResponseParsingService,
RegistryMetadatafieldsResponseParsingService,
RegistryBitstreamformatsResponseParsingService,
+ MappedCollectionsReponseParsingService,
DebugResponseParsingService,
SearchResponseParsingService,
MyDSpaceResponseParsingService,
@@ -217,6 +222,7 @@ const PROVIDERS = [
DSpaceObjectDataService,
DSOChangeAnalyzer,
DefaultChangeAnalyzer,
+ ObjectSelectService,
CSSVariableService,
MenuService,
ObjectUpdatesService,
diff --git a/src/app/core/core.reducers.ts b/src/app/core/core.reducers.ts
index 7aecb91a7a..4fcf36f9cc 100644
--- a/src/app/core/core.reducers.ts
+++ b/src/app/core/core.reducers.ts
@@ -13,7 +13,7 @@ import {
objectUpdatesReducer,
ObjectUpdatesState
} from './data/object-updates/object-updates.reducer';
-import { routeReducer, RouteState } from '../shared/services/route.reducer';
+import { routeReducer, RouteState } from './services/route.reducer';
export interface CoreState {
'cache/object': ObjectCacheState,
diff --git a/src/app/core/data/bitstream-format-data.service.spec.ts b/src/app/core/data/bitstream-format-data.service.spec.ts
new file mode 100644
index 0000000000..f3ce478236
--- /dev/null
+++ b/src/app/core/data/bitstream-format-data.service.spec.ts
@@ -0,0 +1,293 @@
+import { BitstreamFormatDataService } from './bitstream-format-data.service';
+import { RequestEntry } from './request.reducer';
+import { RestResponse } from '../cache/response.models';
+import { Observable, of as observableOf } from 'rxjs';
+import { Action, Store } from '@ngrx/store';
+import { CoreState } from '../core.reducers';
+import { ObjectCacheService } from '../cache/object-cache.service';
+import { cold, getTestScheduler, hot } from 'jasmine-marbles';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
+import { NotificationsService } from '../../shared/notifications/notifications.service';
+import { HttpClient } from '@angular/common/http';
+import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
+import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
+import { BitstreamFormat } from '../shared/bitstream-format.model';
+import { async } from '@angular/core/testing';
+import {
+ BitstreamFormatsRegistryDeselectAction,
+ BitstreamFormatsRegistryDeselectAllAction,
+ BitstreamFormatsRegistrySelectAction
+} from '../../+admin/admin-registries/bitstream-formats/bitstream-format.actions';
+import { TestScheduler } from 'rxjs/testing';
+
+describe('BitstreamFormatDataService', () => {
+ let service: BitstreamFormatDataService;
+ let requestService;
+ let scheduler: TestScheduler;
+
+ const bitstreamFormatsEndpoint = 'https://rest.api/core/bitstream-formats';
+ const bitstreamFormatsIdEndpoint = 'https://rest.api/core/bitstream-formats/format-id';
+
+ const responseCacheEntry = new RequestEntry();
+ responseCacheEntry.response = new RestResponse(true, 200, 'Success');
+ responseCacheEntry.completed = true;
+
+ const store = {
+ dispatch(action: Action) {
+ // Do Nothing
+ }
+ } as Store;
+
+ const objectCache = {} as ObjectCacheService;
+ const halEndpointService = {
+ getEndpoint(linkPath: string): Observable {
+ return cold('a', {a: bitstreamFormatsEndpoint});
+ }
+ } as HALEndpointService;
+
+ const notificationsService = {} as NotificationsService;
+ const http = {} as HttpClient;
+ const comparator = {} as any;
+ const dataBuildService = {} as NormalizedObjectBuildService;
+ const rdbService = {} as RemoteDataBuildService;
+
+ function initTestService(halService) {
+ return new BitstreamFormatDataService(
+ requestService,
+ rdbService,
+ dataBuildService,
+ store,
+ objectCache,
+ halService,
+ notificationsService,
+ http,
+ comparator
+ );
+ }
+
+ describe('getBrowseEndpoint', () => {
+ beforeEach(async(() => {
+ scheduler = getTestScheduler();
+ requestService = jasmine.createSpyObj('requestService', {
+ configure: {},
+ getByHref: observableOf(responseCacheEntry),
+ getByUUID: cold('a', {a: responseCacheEntry}),
+ generateRequestId: 'request-id',
+ removeByHrefSubstring: {}
+ });
+ service = initTestService(halEndpointService);
+ }));
+ it('should get the browse endpoint', () => {
+ const result = service.getBrowseEndpoint();
+ const expected = cold('b', {b: bitstreamFormatsEndpoint});
+
+ expect(result).toBeObservable(expected);
+ });
+ });
+
+ describe('getUpdateEndpoint', () => {
+ beforeEach(async(() => {
+ scheduler = getTestScheduler();
+ requestService = jasmine.createSpyObj('requestService', {
+ configure: {},
+ getByHref: observableOf(responseCacheEntry),
+ getByUUID: cold('a', {a: responseCacheEntry}),
+ generateRequestId: 'request-id',
+ removeByHrefSubstring: {}
+ });
+ service = initTestService(halEndpointService);
+ }));
+ it('should get the update endpoint', () => {
+ const formatId = 'format-id';
+
+ const result = service.getUpdateEndpoint(formatId);
+ const expected = cold('b', {b: bitstreamFormatsIdEndpoint});
+
+ expect(result).toBeObservable(expected);
+ });
+ });
+
+ describe('getCreateEndpoint', () => {
+ beforeEach(async(() => {
+ scheduler = getTestScheduler();
+ requestService = jasmine.createSpyObj('requestService', {
+ configure: {},
+ getByHref: observableOf(responseCacheEntry),
+ getByUUID: cold('a', {a: responseCacheEntry}),
+ generateRequestId: 'request-id',
+ removeByHrefSubstring: {}
+ });
+ service = initTestService(halEndpointService);
+ }));
+ it('should get the create endpoint ', () => {
+
+ const result = service.getCreateEndpoint();
+ const expected = cold('b', {b: bitstreamFormatsEndpoint});
+
+ expect(result).toBeObservable(expected);
+ });
+ });
+
+ describe('updateBitstreamFormat', () => {
+ beforeEach(async(() => {
+ scheduler = getTestScheduler();
+ requestService = jasmine.createSpyObj('requestService', {
+ configure: {},
+ getByHref: observableOf(responseCacheEntry),
+ getByUUID: cold('a', {a: responseCacheEntry}),
+ generateRequestId: 'request-id',
+ removeByHrefSubstring: {}
+ });
+ service = initTestService(halEndpointService);
+ }));
+ it('should update the bitstream format', () => {
+ const updatedBistreamFormat = new BitstreamFormat();
+ updatedBistreamFormat.uuid = 'updated-uuid';
+
+ const expected = cold('(b)', {b: new RestResponse(true, 200, 'Success')});
+ const result = service.updateBitstreamFormat(updatedBistreamFormat);
+
+ expect(result).toBeObservable(expected);
+
+ });
+ });
+
+ describe('createBitstreamFormat', () => {
+ beforeEach(async(() => {
+ scheduler = getTestScheduler();
+ requestService = jasmine.createSpyObj('requestService', {
+ configure: {},
+ getByHref: observableOf(responseCacheEntry),
+ getByUUID: cold('a', {a: responseCacheEntry}),
+ generateRequestId: 'request-id',
+ removeByHrefSubstring: {}
+ });
+ service = initTestService(halEndpointService);
+ }));
+ it('should create a new bitstream format', () => {
+ const newFormat = new BitstreamFormat();
+ newFormat.uuid = 'new-uuid';
+
+ const expected = cold('(b)', {b: new RestResponse(true, 200, 'Success')});
+ const result = service.createBitstreamFormat(newFormat);
+
+ expect(result).toBeObservable(expected);
+ });
+ });
+
+ describe('clearBitStreamFormatRequests', () => {
+ beforeEach(async(() => {
+ scheduler = getTestScheduler();
+ requestService = jasmine.createSpyObj('requestService', {
+ configure: {},
+ getByHref: observableOf(responseCacheEntry),
+ getByUUID: cold('a', {a: responseCacheEntry}),
+ generateRequestId: 'request-id',
+ removeByHrefSubstring: {}
+ });
+ const halService = {
+ getEndpoint(linkPath: string): Observable {
+ return observableOf(bitstreamFormatsEndpoint);
+ }
+ } as HALEndpointService;
+ service = initTestService(halService);
+ service.clearBitStreamFormatRequests().subscribe();
+ }));
+ it('should remove the bitstream format hrefs in the request service', () => {
+ expect(requestService.removeByHrefSubstring).toHaveBeenCalledWith(bitstreamFormatsEndpoint);
+ });
+ });
+
+ describe('selectBitstreamFormat', () => {
+ beforeEach(async(() => {
+ scheduler = getTestScheduler();
+ requestService = jasmine.createSpyObj('requestService', {
+ configure: {},
+ getByHref: observableOf(responseCacheEntry),
+ getByUUID: cold('a', {a: responseCacheEntry}),
+ generateRequestId: 'request-id',
+ removeByHrefSubstring: {}
+ });
+ service = initTestService(halEndpointService);
+ spyOn(store, 'dispatch');
+ }));
+ it('should add a selected bitstream to the store', () => {
+ const format = new BitstreamFormat();
+ format.uuid = 'uuid';
+
+ service.selectBitstreamFormat(format);
+ expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistrySelectAction(format));
+ });
+ });
+
+ describe('deselectBitstreamFormat', () => {
+ beforeEach(async(() => {
+ scheduler = getTestScheduler();
+ requestService = jasmine.createSpyObj('requestService', {
+ configure: {},
+ getByHref: observableOf(responseCacheEntry),
+ getByUUID: cold('a', {a: responseCacheEntry}),
+ generateRequestId: 'request-id',
+ removeByHrefSubstring: {}
+ });
+ service = initTestService(halEndpointService);
+ spyOn(store, 'dispatch');
+ }));
+ it('should remove a bitstream from the store', () => {
+ const format = new BitstreamFormat();
+ format.uuid = 'uuid';
+
+ service.deselectBitstreamFormat(format);
+ expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistryDeselectAction(format));
+ });
+ });
+
+ describe('deselectAllBitstreamFormats', () => {
+ beforeEach(async(() => {
+ scheduler = getTestScheduler();
+ requestService = jasmine.createSpyObj('requestService', {
+ configure: {},
+ getByHref: observableOf(responseCacheEntry),
+ getByUUID: cold('a', {a: responseCacheEntry}),
+ generateRequestId: 'request-id',
+ removeByHrefSubstring: {}
+ });
+ service = initTestService(halEndpointService);
+ spyOn(store, 'dispatch');
+
+ }));
+ it('should remove all bitstreamFormats from the store', () => {
+ service.deselectAllBitstreamFormats();
+ expect(store.dispatch).toHaveBeenCalledWith(new BitstreamFormatsRegistryDeselectAllAction());
+ });
+ });
+
+ describe('delete', () => {
+ beforeEach(async(() => {
+ scheduler = getTestScheduler();
+ requestService = jasmine.createSpyObj('requestService', {
+ configure: {},
+ getByHref: observableOf(responseCacheEntry),
+ getByUUID: hot('a', {a: responseCacheEntry}),
+ generateRequestId: 'request-id',
+ removeByHrefSubstring: {}
+ });
+ const halService = {
+ getEndpoint(linkPath: string): Observable {
+ return observableOf(bitstreamFormatsEndpoint);
+ }
+ } as HALEndpointService;
+ service = initTestService(halService);
+ }));
+ it('should delete a bitstream format', () => {
+ const format = new BitstreamFormat();
+ format.uuid = 'format-uuid';
+ format.id = 'format-id';
+
+ const expected = cold('(b|)', {b: true});
+ const result = service.delete(format);
+
+ expect(result).toBeObservable(expected);
+ });
+ });
+});
diff --git a/src/app/core/data/bitstream-format-data.service.ts b/src/app/core/data/bitstream-format-data.service.ts
new file mode 100644
index 0000000000..a5638183c0
--- /dev/null
+++ b/src/app/core/data/bitstream-format-data.service.ts
@@ -0,0 +1,183 @@
+import { Injectable } from '@angular/core';
+import { DataService } from './data.service';
+import { BitstreamFormat } from '../shared/bitstream-format.model';
+import { RequestService } from './request.service';
+import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
+import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
+import { createSelector, select, Store } from '@ngrx/store';
+import { CoreState } from '../core.reducers';
+import { ObjectCacheService } from '../cache/object-cache.service';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
+import { NotificationsService } from '../../shared/notifications/notifications.service';
+import { HttpClient } from '@angular/common/http';
+import { DefaultChangeAnalyzer } from './default-change-analyzer.service';
+import { DeleteByIDRequest, FindAllOptions, PostRequest, PutRequest } from './request.models';
+import { Observable } from 'rxjs';
+import { find, map, tap } from 'rxjs/operators';
+import { configureRequest, getResponseFromEntry } from '../shared/operators';
+import { distinctUntilChanged } from 'rxjs/internal/operators/distinctUntilChanged';
+import { RestResponse } from '../cache/response.models';
+import { AppState } from '../../app.reducer';
+import { BitstreamFormatRegistryState } from '../../+admin/admin-registries/bitstream-formats/bitstream-format.reducers';
+import {
+ BitstreamFormatsRegistryDeselectAction,
+ BitstreamFormatsRegistryDeselectAllAction,
+ BitstreamFormatsRegistrySelectAction
+} from '../../+admin/admin-registries/bitstream-formats/bitstream-format.actions';
+import { hasValue } from '../../shared/empty.util';
+import { RequestEntry } from './request.reducer';
+
+const bitstreamFormatsStateSelector = (state: AppState) => state.bitstreamFormats;
+const selectedBitstreamFormatSelector = createSelector(bitstreamFormatsStateSelector,
+ (bitstreamFormatRegistryState: BitstreamFormatRegistryState) => bitstreamFormatRegistryState.selectedBitstreamFormats);
+
+/**
+ * A service responsible for fetching/sending data from/to the REST API on the bitstreamformats endpoint
+ */
+@Injectable()
+export class BitstreamFormatDataService extends DataService {
+
+ protected linkPath = 'bitstreamformats';
+ protected forceBypassCache = false;
+
+ constructor(
+ protected requestService: RequestService,
+ protected rdbService: RemoteDataBuildService,
+ protected dataBuildService: NormalizedObjectBuildService,
+ protected store: Store,
+ protected objectCache: ObjectCacheService,
+ protected halService: HALEndpointService,
+ protected notificationsService: NotificationsService,
+ protected http: HttpClient,
+ protected comparator: DefaultChangeAnalyzer) {
+ super();
+ }
+
+ /**
+ * Get the endpoint for browsing bitstream formats
+ * @param {FindAllOptions} options
+ * @returns {Observable}
+ */
+ getBrowseEndpoint(options: FindAllOptions = {}, linkPath?: string): Observable {
+ return this.halService.getEndpoint(this.linkPath);
+ }
+
+ /**
+ * Get the endpoint to update an existing bitstream format
+ * @param formatId
+ */
+ public getUpdateEndpoint(formatId: string): Observable {
+ return this.getBrowseEndpoint().pipe(
+ map((endpoint: string) => this.getIDHref(endpoint, formatId))
+ );
+ }
+
+ /**
+ * Get the endpoint to create a new bitstream format
+ */
+ public getCreateEndpoint(): Observable {
+ return this.getBrowseEndpoint();
+ }
+
+ /**
+ * Update an existing bitstreamFormat
+ * @param bitstreamFormat
+ */
+ updateBitstreamFormat(bitstreamFormat: BitstreamFormat): Observable {
+ const requestId = this.requestService.generateRequestId();
+
+ this.getUpdateEndpoint(bitstreamFormat.id).pipe(
+ distinctUntilChanged(),
+ map((endpointURL: string) =>
+ new PutRequest(requestId, endpointURL, bitstreamFormat)),
+ configureRequest(this.requestService)).subscribe();
+
+ return this.requestService.getByUUID(requestId).pipe(
+ getResponseFromEntry()
+ );
+
+ }
+
+ /**
+ * Create a new BitstreamFormat
+ * @param BitstreamFormat
+ */
+ public createBitstreamFormat(bitstreamFormat: BitstreamFormat): Observable {
+ const requestId = this.requestService.generateRequestId();
+
+ this.getCreateEndpoint().pipe(
+ map((endpointURL: string) => {
+ return new PostRequest(requestId, endpointURL, bitstreamFormat);
+ }),
+ configureRequest(this.requestService)
+ ).subscribe();
+
+ return this.requestService.getByUUID(requestId).pipe(
+ getResponseFromEntry()
+ );
+ }
+
+ /**
+ * Clears the cache of the list of BitstreamFormats
+ */
+ public clearBitStreamFormatRequests(): Observable {
+ return this.getBrowseEndpoint().pipe(
+ tap((href: string) => this.requestService.removeByHrefSubstring(href))
+ );
+ }
+
+ /**
+ * Gets all the selected BitstreamFormats from the store
+ */
+ public getSelectedBitstreamFormats(): Observable {
+ return this.store.pipe(select(selectedBitstreamFormatSelector));
+ }
+
+ /**
+ * Adds a BistreamFormat to the selected BitstreamFormats in the store
+ * @param bitstreamFormat
+ */
+ public selectBitstreamFormat(bitstreamFormat: BitstreamFormat) {
+ this.store.dispatch(new BitstreamFormatsRegistrySelectAction(bitstreamFormat));
+ }
+
+ /**
+ * Removes a BistreamFormat from the list of selected BitstreamFormats in the store
+ * @param bitstreamFormat
+ */
+ public deselectBitstreamFormat(bitstreamFormat: BitstreamFormat) {
+ this.store.dispatch(new BitstreamFormatsRegistryDeselectAction(bitstreamFormat));
+ }
+
+ /**
+ * Removes all BitstreamFormats from the list of selected BitstreamFormats in the store
+ */
+ public deselectAllBitstreamFormats() {
+ this.store.dispatch(new BitstreamFormatsRegistryDeselectAllAction());
+ }
+
+ /**
+ * Delete an existing DSpace Object on the server
+ * @param format The DSpace Object to be removed
+ * Return an observable that emits true when the deletion was successful, false when it failed
+ */
+ delete(format: BitstreamFormat): Observable {
+ const requestId = this.requestService.generateRequestId();
+
+ const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(
+ map((endpoint: string) => this.getIDHref(endpoint, format.id)));
+
+ hrefObs.pipe(
+ find((href: string) => hasValue(href)),
+ map((href: string) => {
+ const request = new DeleteByIDRequest(requestId, href, format.id);
+ this.requestService.configure(request);
+ })
+ ).subscribe();
+
+ return this.requestService.getByUUID(requestId).pipe(
+ find((request: RequestEntry) => request.completed),
+ map((request: RequestEntry) => request.response.isSuccessful)
+ );
+ }
+}
diff --git a/src/app/core/data/collection-data.service.spec.ts b/src/app/core/data/collection-data.service.spec.ts
new file mode 100644
index 0000000000..5cb7fed5e4
--- /dev/null
+++ b/src/app/core/data/collection-data.service.spec.ts
@@ -0,0 +1,44 @@
+import { CollectionDataService } from './collection-data.service';
+import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
+import { getMockRequestService } from '../../shared/mocks/mock-request.service';
+import { HALEndpointService } from '../shared/hal-endpoint.service';
+import { RequestService } from './request.service';
+import { ObjectCacheService } from '../cache/object-cache.service';
+import { GetRequest } from './request.models';
+import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
+
+describe('CollectionDataService', () => {
+ let service: CollectionDataService;
+ let objectCache: ObjectCacheService;
+ let requestService: RequestService;
+ let halService: HALEndpointService;
+ let rdbService: RemoteDataBuildService;
+
+ const url = 'fake-collections-url';
+
+ beforeEach(() => {
+ objectCache = jasmine.createSpyObj('objectCache', {
+ remove: jasmine.createSpy('remove')
+ });
+ requestService = getMockRequestService();
+ halService = Object.assign(new HALEndpointServiceStub(url));
+ rdbService = jasmine.createSpyObj('rdbService', {
+ buildList: jasmine.createSpy('buildList')
+ });
+
+ service = new CollectionDataService(requestService, rdbService, null, null, null, objectCache, halService, null, null, null);
+ });
+
+ describe('getMappedItems', () => {
+ let result;
+
+ beforeEach(() => {
+ result = service.getMappedItems('collection-id');
+ });
+
+ it('should configure a GET request', () => {
+ expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(GetRequest), undefined);
+ });
+ });
+
+});
diff --git a/src/app/core/data/collection-data.service.ts b/src/app/core/data/collection-data.service.ts
index 993954a360..e49267d1f2 100644
--- a/src/app/core/data/collection-data.service.ts
+++ b/src/app/core/data/collection-data.service.ts
@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core';
-import { filter, map, take } from 'rxjs/operators';
+import { distinctUntilChanged, filter, map, take } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
@@ -16,9 +16,17 @@ import { HttpClient } from '@angular/common/http';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
import { Observable } from 'rxjs/internal/Observable';
-import { FindAllOptions } from './request.models';
+import { FindAllOptions, GetRequest } from './request.models';
import { RemoteData } from './remote-data';
import { PaginatedList } from './paginated-list';
+import { configureRequest } from '../shared/operators';
+import { DSOResponseParsingService } from './dso-response-parsing.service';
+import { ResponseParsingService } from './parsing.service';
+import { GenericConstructor } from '../shared/generic-constructor';
+import { hasValue, isNotEmptyOperator } from '../../shared/empty.util';
+import { DSpaceObject } from '../shared/dspace-object.model';
+import { PaginatedSearchOptions } from '../../+search-page/paginated-search-options.model';
+import { SearchParam } from '../cache/models/search-param.model';
@Injectable()
export class CollectionDataService extends ComColDataService {
@@ -40,6 +48,36 @@ export class CollectionDataService extends ComColDataService {
super();
}
+ /**
+ * Get all collections the user is authorized to submit to
+ *
+ * @param options The [[FindAllOptions]] object
+ * @return Observable>>
+ * collection list
+ */
+ getAuthorizedCollection(options: FindAllOptions = {}): Observable>> {
+ const searchHref = 'findAuthorized';
+
+ return this.searchBy(searchHref, options).pipe(
+ filter((collections: RemoteData>) => !collections.isResponsePending));
+ }
+
+ /**
+ * Get all collections the user is authorized to submit to, by community
+ *
+ * @param communityId The community id
+ * @param options The [[FindAllOptions]] object
+ * @return Observable>>
+ * collection list
+ */
+ getAuthorizedCollectionByCommunity(communityId: string, options: FindAllOptions = {}): Observable>> {
+ const searchHref = 'findAuthorizedByCommunity';
+ options.searchParams = [new SearchParam('uuid', communityId)];
+
+ return this.searchBy(searchHref, options).pipe(
+ filter((collections: RemoteData>) => !collections.isResponsePending));
+ }
+
/**
* Find whether there is a collection whom user has authorization to submit to
*
@@ -57,4 +95,46 @@ export class CollectionDataService extends ComColDataService {
map((collections: RemoteData>) => collections.payload.totalElements > 0)
);
}
+
+ /**
+ * Fetches the endpoint used for mapping items to a collection
+ * @param collectionId The id of the collection to map items to
+ */
+ getMappedItemsEndpoint(collectionId): Observable {
+ return this.halService.getEndpoint(this.linkPath).pipe(
+ map((endpoint: string) => this.getIDHref(endpoint, collectionId)),
+ map((endpoint: string) => `${endpoint}/mappedItems`)
+ );
+ }
+
+ /**
+ * Fetches a list of items that are mapped to a collection
+ * @param collectionId The id of the collection
+ * @param searchOptions Search options to sort or filter out items
+ */
+ getMappedItems(collectionId: string, searchOptions?: PaginatedSearchOptions): Observable>> {
+ const requestUuid = this.requestService.generateRequestId();
+
+ const href$ = this.getMappedItemsEndpoint(collectionId).pipe(
+ isNotEmptyOperator(),
+ distinctUntilChanged(),
+ map((endpoint: string) => hasValue(searchOptions) ? searchOptions.toRestUrl(endpoint) : endpoint)
+ );
+
+ href$.pipe(
+ map((endpoint: string) => {
+ const request = new GetRequest(requestUuid, endpoint);
+ return Object.assign(request, {
+ responseMsToLive: 0,
+ getResponseParser(): GenericConstructor {
+ return DSOResponseParsingService;
+ }
+ });
+ }),
+ configureRequest(this.requestService)
+ ).subscribe();
+
+ return this.rdbService.buildList(href$);
+ }
+
}
diff --git a/src/app/core/data/item-data.service.spec.ts b/src/app/core/data/item-data.service.spec.ts
index 3553a63af4..36b8e6b3c5 100644
--- a/src/app/core/data/item-data.service.spec.ts
+++ b/src/app/core/data/item-data.service.spec.ts
@@ -7,7 +7,14 @@ import { CoreState } from '../core.reducers';
import { ItemDataService } from './item-data.service';
import { RequestService } from './request.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
-import { FindAllOptions, RestRequest } from './request.models';
+import {
+ DeleteRequest,
+ FindAllOptions,
+ GetRequest,
+ MappedCollectionsRequest,
+ PostRequest,
+ RestRequest
+} from './request.models';
import { ObjectCacheService } from '../cache/object-cache.service';
import { Observable } from 'rxjs';
import { RestResponse } from '../cache/response.models';
@@ -16,12 +23,13 @@ import { NormalizedObjectBuildService } from '../cache/builders/normalized-objec
import { HttpClient } from '@angular/common/http';
import { RequestEntry } from './request.reducer';
import { of as observableOf } from 'rxjs';
+import { getMockRequestService } from '../../shared/mocks/mock-request.service';
describe('ItemDataService', () => {
let scheduler: TestScheduler;
let service: ItemDataService;
let bs: BrowseService;
- const requestService = {
+ const requestService = Object.assign(getMockRequestService(), {
generateRequestId(): string {
return scopeID;
},
@@ -32,9 +40,14 @@ describe('ItemDataService', () => {
const responseCacheEntry = new RequestEntry();
responseCacheEntry.response = new RestResponse(true, 200, 'OK');
return observableOf(responseCacheEntry);
+ },
+ removeByHrefSubstring(href: string) {
+ // Do nothing
}
- } as RequestService;
- const rdbService = {} as RemoteDataBuildService;
+ }) as RequestService;
+ const rdbService = jasmine.createSpyObj('rdbService', {
+ toRemoteDataObservable: observableOf({})
+ });
const store = {} as Store;
const objectCache = {} as ObjectCacheService;
@@ -162,4 +175,32 @@ describe('ItemDataService', () => {
});
});
+ describe('removeMappingFromCollection', () => {
+ let result;
+
+ beforeEach(() => {
+ service = initTestService();
+ spyOn(requestService, 'configure');
+ result = service.removeMappingFromCollection('item-id', 'collection-id');
+ });
+
+ it('should configure a DELETE request', () => {
+ result.subscribe(() => expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(DeleteRequest), undefined));
+ });
+ });
+
+ describe('mapToCollection', () => {
+ let result;
+
+ beforeEach(() => {
+ service = initTestService();
+ spyOn(requestService, 'configure');
+ result = service.mapToCollection('item-id', 'collection-href');
+ });
+
+ it('should configure a POST request', () => {
+ result.subscribe(() => expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PostRequest), undefined));
+ });
+ });
+
});
diff --git a/src/app/core/data/item-data.service.ts b/src/app/core/data/item-data.service.ts
index f6adbb23c2..de05dad0c1 100644
--- a/src/app/core/data/item-data.service.ts
+++ b/src/app/core/data/item-data.service.ts
@@ -1,8 +1,8 @@
-import { distinctUntilChanged, filter, map } from 'rxjs/operators';
+import { distinctUntilChanged, filter, find, map, switchMap, take } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs';
-import { isNotEmpty } from '../../shared/empty.util';
+import { hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
import { BrowseService } from '../browse/browse.service';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { CoreState } from '../core.reducers';
@@ -12,14 +12,31 @@ import { URLCombiner } from '../url-combiner/url-combiner';
import { DataService } from './data.service';
import { RequestService } from './request.service';
import { HALEndpointService } from '../shared/hal-endpoint.service';
-import { FindAllOptions, PatchRequest, RestRequest } from './request.models';
+import {
+ DeleteRequest,
+ FindAllOptions,
+ MappedCollectionsRequest,
+ PatchRequest,
+ PostRequest, PutRequest,
+ RestRequest
+} from './request.models';
import { ObjectCacheService } from '../cache/object-cache.service';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { DSOChangeAnalyzer } from './dso-change-analyzer.service';
-import { HttpClient } from '@angular/common/http';
+import { HttpClient, HttpHeaders } from '@angular/common/http';
import { NormalizedObjectBuildService } from '../cache/builders/normalized-object-build.service';
-import { configureRequest, getRequestFromRequestHref } from '../shared/operators';
+import {
+ configureRequest,
+ filterSuccessfulResponses,
+ getRequestFromRequestHref,
+ getResponseFromEntry
+} from '../shared/operators';
import { RequestEntry } from './request.reducer';
+import { GenericSuccessResponse, RestResponse } from '../cache/response.models';
+import { HttpOptions } from '../dspace-rest-v2/dspace-rest-v2.service';
+import { Collection } from '../shared/collection.model';
+import { RemoteData } from './remote-data';
+import { PaginatedList } from './paginated-list';
@Injectable()
export class ItemDataService extends DataService- {
@@ -57,6 +74,80 @@ export class ItemDataService extends DataService
- {
distinctUntilChanged(),);
}
+ /**
+ * Fetches the endpoint used for mapping an item to a collection,
+ * or for fetching all collections the item is mapped to if no collection is provided
+ * @param itemId The item's id
+ * @param collectionId The collection's id (optional)
+ */
+ public getMappedCollectionsEndpoint(itemId: string, collectionId?: string): Observable {
+ return this.halService.getEndpoint(this.linkPath).pipe(
+ map((endpoint: string) => this.getIDHref(endpoint, itemId)),
+ map((endpoint: string) => `${endpoint}/mappedCollections${collectionId ? `/${collectionId}` : ''}`)
+ );
+ }
+
+ /**
+ * Removes the mapping of an item from a collection
+ * @param itemId The item's id
+ * @param collectionId The collection's id
+ */
+ public removeMappingFromCollection(itemId: string, collectionId: string): Observable {
+ return this.getMappedCollectionsEndpoint(itemId, collectionId).pipe(
+ isNotEmptyOperator(),
+ distinctUntilChanged(),
+ map((endpointURL: string) => new DeleteRequest(this.requestService.generateRequestId(), endpointURL)),
+ configureRequest(this.requestService),
+ switchMap((request: RestRequest) => this.requestService.getByUUID(request.uuid)),
+ getResponseFromEntry()
+ );
+ }
+
+ /**
+ * Maps an item to a collection
+ * @param itemId The item's id
+ * @param collectionHref The collection's self link
+ */
+ public mapToCollection(itemId: string, collectionHref: string): Observable {
+ return this.getMappedCollectionsEndpoint(itemId).pipe(
+ isNotEmptyOperator(),
+ distinctUntilChanged(),
+ map((endpointURL: string) => {
+ const options: HttpOptions = Object.create({});
+ let headers = new HttpHeaders();
+ headers = headers.append('Content-Type', 'text/uri-list');
+ options.headers = headers;
+ return new PostRequest(this.requestService.generateRequestId(), endpointURL, collectionHref, options);
+ }),
+ configureRequest(this.requestService),
+ switchMap((request: RestRequest) => this.requestService.getByUUID(request.uuid)),
+ getResponseFromEntry()
+ );
+ }
+
+ /**
+ * Fetches all collections the item is mapped to
+ * @param itemId The item's id
+ */
+ public getMappedCollections(itemId: string): Observable>> {
+ const request$ = this.getMappedCollectionsEndpoint(itemId).pipe(
+ isNotEmptyOperator(),
+ distinctUntilChanged(),
+ map((endpointURL: string) => new MappedCollectionsRequest(this.requestService.generateRequestId(), endpointURL)),
+ configureRequest(this.requestService)
+ );
+
+ const requestEntry$ = request$.pipe(
+ switchMap((request: RestRequest) => this.requestService.getByHref(request.href))
+ );
+ const payload$ = requestEntry$.pipe(
+ filterSuccessfulResponses(),
+ map((response: GenericSuccessResponse>) => response.payload)
+ );
+
+ return this.rdbService.toRemoteDataObservable(requestEntry$, payload$);
+ }
+
/**
* Get the endpoint for item withdrawal and reinstatement
* @param itemId
@@ -118,4 +209,43 @@ export class ItemDataService extends DataService
- {
map((requestEntry: RequestEntry) => requestEntry.response)
);
}
+
+ /**
+ * Get the endpoint to move the item
+ * @param itemId
+ */
+ public getMoveItemEndpoint(itemId: string): Observable {
+ return this.halService.getEndpoint(this.linkPath).pipe(
+ map((endpoint: string) => this.getIDHref(endpoint, itemId)),
+ map((endpoint: string) => `${endpoint}/owningCollection`)
+ );
+ }
+
+ /**
+ * Move the item to a different owning collection
+ * @param itemId
+ * @param collection
+ */
+ public moveToCollection(itemId: string, collection: Collection): Observable {
+ const options: HttpOptions = Object.create({});
+ let headers = new HttpHeaders();
+ headers = headers.append('Content-Type', 'text/uri-list');
+ options.headers = headers;
+
+ const requestId = this.requestService.generateRequestId();
+ const hrefObs = this.getMoveItemEndpoint(itemId);
+
+ hrefObs.pipe(
+ find((href: string) => hasValue(href)),
+ map((href: string) => {
+ const request = new PutRequest(requestId, href, collection.self, options);
+ this.requestService.configure(request);
+ })
+ ).subscribe();
+
+ return this.requestService.getByUUID(requestId).pipe(
+ find((request: RequestEntry) => request.completed),
+ map((request: RequestEntry) => request.response)
+ );
+ }
}
diff --git a/src/app/core/data/mapped-collections-reponse-parsing.service.ts b/src/app/core/data/mapped-collections-reponse-parsing.service.ts
new file mode 100644
index 0000000000..bf8ed036e3
--- /dev/null
+++ b/src/app/core/data/mapped-collections-reponse-parsing.service.ts
@@ -0,0 +1,37 @@
+import { Injectable } from '@angular/core';
+import { ResponseParsingService } from './parsing.service';
+import { RestRequest } from './request.models';
+import { DSpaceRESTV2Response } from '../dspace-rest-v2/dspace-rest-v2-response.model';
+import { PaginatedList } from './paginated-list';
+import { PageInfo } from '../shared/page-info.model';
+import { ErrorResponse, GenericSuccessResponse, RestResponse } from '../cache/response.models';
+
+@Injectable()
+/**
+ * A ResponseParsingService used to parse DSpaceRESTV2Response coming from the REST API to a GenericSuccessResponse
+ * containing a PaginatedList of mapped collections
+ */
+export class MappedCollectionsReponseParsingService implements ResponseParsingService {
+ parse(request: RestRequest, data: DSpaceRESTV2Response): RestResponse {
+ const payload = data.payload;
+
+ if (payload._embedded && payload._embedded.mappedCollections) {
+ const mappedCollections = payload._embedded.mappedCollections;
+ // TODO: When the API supports it, change this to fetch a paginated list, instead of creating static one
+ // Reason: Pagination is currently not supported on the mappedCollections endpoint
+ const paginatedMappedCollections = new PaginatedList(Object.assign(new PageInfo(), {
+ elementsPerPage: mappedCollections.length,
+ totalElements: mappedCollections.length,
+ totalPages: 1,
+ currentPage: 1
+ }), mappedCollections);
+ return new GenericSuccessResponse(paginatedMappedCollections, data.statusCode, data.statusText);
+ } else {
+ return new ErrorResponse(
+ Object.assign(
+ new Error('Unexpected response from mappedCollections endpoint'), data
+ )
+ );
+ }
+ }
+}
diff --git a/src/app/core/data/relationship.service.spec.ts b/src/app/core/data/relationship.service.spec.ts
index 88495c89bf..25eea901ce 100644
--- a/src/app/core/data/relationship.service.spec.ts
+++ b/src/app/core/data/relationship.service.spec.ts
@@ -34,8 +34,8 @@ describe('RelationshipService', () => {
const relationshipType = Object.assign(new RelationshipType(), {
id: '1',
uuid: '1',
- leftLabel: 'isAuthorOfPublication',
- rightLabel: 'isPublicationOfAuthor'
+ leftwardType: 'isAuthorOfPublication',
+ rightwardType: 'isPublicationOfAuthor'
});
const relationship1 = Object.assign(new Relationship(), {
diff --git a/src/app/core/data/relationship.service.ts b/src/app/core/data/relationship.service.ts
index 8a1619e277..1041dd2d5f 100644
--- a/src/app/core/data/relationship.service.ts
+++ b/src/app/core/data/relationship.service.ts
@@ -159,10 +159,10 @@ export class RelationshipService extends DataService {
getSucceededRemoteData(),
map((relationshipTypeRD) => relationshipTypeRD.payload),
map((relationshipType: RelationshipType) => {
- if (otherItem.uuid == item.uuid) {
- return relationshipType.leftLabel;
+ if (otherItem.uuid === item.uuid) {
+ return relationshipType.leftwardType;
} else {
- return relationshipType.rightLabel;
+ return relationshipType.rightwardType;
}
})
)
@@ -211,7 +211,6 @@ export class RelationshipService extends DataService {
return this.searchBy('byLabel', findAllOptions);
}
-
/**
* Method for fetching an item's relationships, but filtered by related item IDs (essentially performing a reverse lookup)
* Only relationships where leftItem or rightItem's ID is present in the list provided will be returned
@@ -231,7 +230,7 @@ export class RelationshipService extends DataService {
);
}))
}),
- map((relationships: Relationship[]) => relationships.filter((relationship => hasValue(relationship)))),
+ map((relationships: Relationship[]) => relationships.filter(((relationship) => hasValue(relationship)))),
)
}
@@ -243,7 +242,6 @@ export class RelationshipService extends DataService {
);
}
-
getRelationshipByItemsAndLabel(item1: Item, item2: Item, label: string): Observable {
return this.getItemRelationshipsByLabel(item1, label)
.pipe(
@@ -260,7 +258,7 @@ export class RelationshipService extends DataService {
);
}))
}),
- map((relationships: Relationship[]) => relationships.find((relationship => hasValue(relationship))))
+ map((relationships: Relationship[]) => relationships.find(((relationship) => hasValue(relationship))))
)
}
diff --git a/src/app/core/data/request.models.ts b/src/app/core/data/request.models.ts
index a2b3423960..43ab9e58e7 100644
--- a/src/app/core/data/request.models.ts
+++ b/src/app/core/data/request.models.ts
@@ -18,6 +18,7 @@ import { MetadataschemaParsingService } from './metadataschema-parsing.service';
import { MetadatafieldParsingService } from './metadatafield-parsing.service';
import { URLCombiner } from '../url-combiner/url-combiner';
import { TaskResponseParsingService } from '../tasks/task-response-parsing.service';
+import { MappedCollectionsReponseParsingService } from './mapped-collections-reponse-parsing.service';
/* tslint:disable:max-classes-per-file */
@@ -185,6 +186,17 @@ export class BrowseItemsRequest extends GetRequest {
}
}
+/**
+ * Request to fetch the mapped collections of an item
+ */
+export class MappedCollectionsRequest extends GetRequest {
+ public responseMsToLive = 10000;
+
+ getResponseParser(): GenericConstructor {
+ return MappedCollectionsReponseParsingService;
+ }
+}
+
export class ConfigRequest extends GetRequest {
constructor(uuid: string, href: string, public options?: HttpOptions) {
super(uuid, href, null, options);
diff --git a/src/app/core/data/request.service.ts b/src/app/core/data/request.service.ts
index 381fbd58c5..00bae3c0ef 100644
--- a/src/app/core/data/request.service.ts
+++ b/src/app/core/data/request.service.ts
@@ -3,7 +3,7 @@ import { HttpHeaders } from '@angular/common/http';
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
import { Observable, race as observableRace } from 'rxjs';
-import { filter, find, map, mergeMap, take } from 'rxjs/operators';
+import { filter, map, mergeMap, take } from 'rxjs/operators';
import { cloneDeep, remove } from 'lodash';
import { AppState } from '../../app.reducer';
@@ -262,12 +262,13 @@ export class RequestService {
*/
private clearRequestsOnTheirWayToTheStore(request: GetRequest) {
this.getByHref(request.href).pipe(
- find((re: RequestEntry) => hasValue(re)))
- .subscribe((re: RequestEntry) => {
- if (!re.responsePending) {
- remove(this.requestsOnTheirWayToTheStore, (item) => item === request.href);
- }
- });
+ filter((re: RequestEntry) => hasValue(re)),
+ take(1)
+ ).subscribe((re: RequestEntry) => {
+ if (!re.responsePending) {
+ remove(this.requestsOnTheirWayToTheStore, (item) => item === request.href);
+ }
+ });
}
/**
diff --git a/src/app/core/metadata/metadata-field.model.ts b/src/app/core/metadata/metadata-field.model.ts
index bba9aae35a..45ac4b2051 100644
--- a/src/app/core/metadata/metadata-field.model.ts
+++ b/src/app/core/metadata/metadata-field.model.ts
@@ -2,6 +2,7 @@ import { ListableObject } from '../../shared/object-collection/shared/listable-o
import { isNotEmpty } from '../../shared/empty.util';
import { MetadataSchema } from './metadata-schema.model';
import { ResourceType } from '../shared/resource-type';
+import { GenericConstructor } from '../shared/generic-constructor';
/**
* Class the represents a metadata field
@@ -50,4 +51,11 @@ export class MetadataField extends ListableObject {
}
return key;
}
+
+ /**
+ * Method that returns as which type of object this object should be rendered
+ */
+ getRenderTypes(): Array> {
+ return [this.constructor as GenericConstructor];
+ }
}
diff --git a/src/app/core/metadata/metadata-schema.model.ts b/src/app/core/metadata/metadata-schema.model.ts
index 481cd0b9b4..2059b21094 100644
--- a/src/app/core/metadata/metadata-schema.model.ts
+++ b/src/app/core/metadata/metadata-schema.model.ts
@@ -1,5 +1,6 @@
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { ResourceType } from '../shared/resource-type';
+import { GenericConstructor } from '../shared/generic-constructor';
/**
* Class that represents a metadata schema
@@ -26,4 +27,11 @@ export class MetadataSchema extends ListableObject {
* The namespace of this metadata schema
*/
namespace: string;
+
+ /**
+ * Method that returns as which type of object this object should be rendered
+ */
+ getRenderTypes(): Array> {
+ return [this.constructor as GenericConstructor];
+ }
}
diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts
index 309dfd8890..2b1cf4ffc1 100644
--- a/src/app/core/metadata/metadata.service.ts
+++ b/src/app/core/metadata/metadata.service.ts
@@ -2,7 +2,6 @@ import {
catchError,
distinctUntilKeyChanged,
filter,
- find,
first,
map,
take
diff --git a/src/app/core/registry/mock-bitstream-format.model.ts b/src/app/core/registry/mock-bitstream-format.model.ts
deleted file mode 100644
index f5811e367c..0000000000
--- a/src/app/core/registry/mock-bitstream-format.model.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-export class BitstreamFormat {
- shortDescription: string;
- description: string;
- mimetype: string;
- supportLevel: number;
- internal: boolean;
- extensions: string;
-}
diff --git a/src/app/core/registry/registry.service.spec.ts b/src/app/core/registry/registry.service.spec.ts
index 16339233ca..03a7c132de 100644
--- a/src/app/core/registry/registry.service.spec.ts
+++ b/src/app/core/registry/registry.service.spec.ts
@@ -12,7 +12,6 @@ import { PageInfo } from '../shared/page-info.model';
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
import {
- RegistryBitstreamformatsSuccessResponse,
RegistryMetadatafieldsSuccessResponse,
RegistryMetadataschemasSuccessResponse,
RestResponse
@@ -20,7 +19,6 @@ import {
import { Component } from '@angular/core';
import { RegistryMetadataschemasResponse } from './registry-metadataschemas-response.model';
import { RegistryMetadatafieldsResponse } from './registry-metadatafields-response.model';
-import { RegistryBitstreamformatsResponse } from './registry-bitstreamformats-response.model';
import { map } from 'rxjs/operators';
import { Store, StoreModule } from '@ngrx/store';
import { MockStore } from '../../shared/testing/mock-store';
@@ -44,7 +42,7 @@ import { MetadataSchema } from '../metadata/metadata-schema.model';
import { MetadataField } from '../metadata/metadata-field.model';
import { createSuccessfulRemoteDataObject$ } from '../../shared/testing/utils';
-@Component({ template: '' })
+@Component({template: ''})
class DummyComponent {
}
@@ -57,15 +55,15 @@ describe('RegistryService', () => {
});
const mockSchemasList = [
-
Object.assign(new MetadataSchema(), {
id: 1,
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/metadataschemas/1',
prefix: 'dc',
namespace: 'http://dublincore.org/documents/dcmi-terms/',
type: MetadataSchema.type
- }),
+}),
Object.assign(new MetadataSchema(), {
+
id: 2,
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/metadataschemas/2',
prefix: 'mock',
@@ -74,7 +72,8 @@ describe('RegistryService', () => {
})
];
const mockFieldsList = [
- Object.assign(new MetadataField(), {
+ Object.assign(new MetadataField(),
+ {
id: 1,
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/metadatafields/8',
element: 'contributor',
@@ -83,7 +82,8 @@ describe('RegistryService', () => {
schema: mockSchemasList[0],
type: MetadataField.type
}),
- Object.assign(new MetadataField(), {
+ Object.assign(new MetadataField(),
+ {
id: 2,
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/metadatafields/9',
element: 'contributor',
@@ -92,7 +92,8 @@ describe('RegistryService', () => {
schema: mockSchemasList[0],
type: MetadataField.type
}),
- Object.assign(new MetadataField(), {
+ Object.assign(new MetadataField(),
+ {
id: 3,
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/metadatafields/10',
element: 'contributor',
@@ -101,7 +102,8 @@ describe('RegistryService', () => {
schema: mockSchemasList[1],
type: MetadataField.type
}),
- Object.assign(new MetadataField(), {
+ Object.assign(new MetadataField(),
+ {
id: 4,
self: 'https://dspace7.4science.it/dspace-spring-rest/api/core/metadatafields/11',
element: 'contributor',
@@ -128,7 +130,7 @@ describe('RegistryService', () => {
toRemoteDataObservable: (requestEntryObs: Observable, payloadObs: Observable) => {
return observableCombineLatest(requestEntryObs,
payloadObs).pipe(map(([req, pay]) => {
- return { req, pay };
+ return {req, pay};
})
);
},
@@ -144,11 +146,11 @@ describe('RegistryService', () => {
DummyComponent
],
providers: [
- { provide: RequestService, useValue: getMockRequestService() },
- { provide: RemoteDataBuildService, useValue: rdbStub },
- { provide: HALEndpointService, useValue: halServiceStub },
- { provide: Store, useClass: MockStore },
- { provide: NotificationsService, useValue: new NotificationsServiceStub() },
+ {provide: RequestService, useValue: getMockRequestService()},
+ {provide: RemoteDataBuildService, useValue: rdbStub},
+ {provide: HALEndpointService, useValue: halServiceStub},
+ {provide: Store, useClass: MockStore},
+ {provide: NotificationsService, useValue: new NotificationsServiceStub()},
RegistryService
]
});
@@ -163,7 +165,7 @@ describe('RegistryService', () => {
page: pageInfo
});
const response = new RegistryMetadataschemasSuccessResponse(queryResponse, 200, 'OK', pageInfo);
- const responseEntry = Object.assign(new RequestEntry(), { response: response });
+ const responseEntry = Object.assign(new RequestEntry(), {response: response});
beforeEach(() => {
(registryService as any).requestService.getByHref.and.returnValue(observableOf(responseEntry));
@@ -192,7 +194,7 @@ describe('RegistryService', () => {
page: pageInfo
});
const response = new RegistryMetadataschemasSuccessResponse(queryResponse, 200, 'OK', pageInfo);
- const responseEntry = Object.assign(new RequestEntry(), { response: response });
+ const responseEntry = Object.assign(new RequestEntry(), {response: response});
beforeEach(() => {
(registryService as any).requestService.getByHref.and.returnValue(observableOf(responseEntry));
@@ -221,7 +223,7 @@ describe('RegistryService', () => {
page: pageInfo
});
const response = new RegistryMetadatafieldsSuccessResponse(queryResponse, 200, 'OK', pageInfo);
- const responseEntry = Object.assign(new RequestEntry(), { response: response });
+ const responseEntry = Object.assign(new RequestEntry(), {response: response});
beforeEach(() => {
(registryService as any).requestService.getByHref.and.returnValue(observableOf(responseEntry));
@@ -244,35 +246,6 @@ describe('RegistryService', () => {
});
});
- describe('when requesting bitstreamformats', () => {
- const queryResponse = Object.assign(new RegistryBitstreamformatsResponse(), {
- bitstreamformats: mockFieldsList,
- page: pageInfo
- });
- const response = new RegistryBitstreamformatsSuccessResponse(queryResponse, 200, 'OK', pageInfo);
- const responseEntry = Object.assign(new RequestEntry(), { response: response });
-
- beforeEach(() => {
- (registryService as any).requestService.getByHref.and.returnValue(observableOf(responseEntry));
- /* tslint:disable:no-empty */
- registryService.getBitstreamFormats(pagination).subscribe((value) => {
- });
- /* tslint:enable:no-empty */
- });
-
- it('should call getEndpoint on the halService', () => {
- expect((registryService as any).halService.getEndpoint).toHaveBeenCalled();
- });
-
- it('should send out the request on the request service', () => {
- expect((registryService as any).requestService.configure).toHaveBeenCalled();
- });
-
- it('should call getByHref on the request service with the correct request url', () => {
- expect((registryService as any).requestService.getByHref).toHaveBeenCalledWith(endpointWithParams);
- });
- });
-
describe('when dispatching to the store', () => {
beforeEach(() => {
spyOn(mockStore, 'dispatch');
@@ -285,7 +258,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryEditSchemaAction with the correct schema', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryEditSchemaAction(mockSchemasList[0]));
- })
+ });
});
describe('when calling cancelEditMetadataSchema', () => {
@@ -295,7 +268,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryCancelSchemaAction', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryCancelSchemaAction());
- })
+ });
});
describe('when calling selectMetadataSchema', () => {
@@ -305,7 +278,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistrySelectSchemaAction with the correct schema', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistrySelectSchemaAction(mockSchemasList[0]));
- })
+ });
});
describe('when calling deselectMetadataSchema', () => {
@@ -315,7 +288,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryDeselectSchemaAction with the correct schema', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectSchemaAction(mockSchemasList[0]));
- })
+ });
});
describe('when calling deselectAllMetadataSchema', () => {
@@ -325,7 +298,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryDeselectAllSchemaAction', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectAllSchemaAction());
- })
+ });
});
describe('when calling editMetadataField', () => {
@@ -335,7 +308,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryEditFieldAction with the correct Field', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryEditFieldAction(mockFieldsList[0]));
- })
+ });
});
describe('when calling cancelEditMetadataField', () => {
@@ -345,7 +318,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryCancelFieldAction', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryCancelFieldAction());
- })
+ });
});
describe('when calling selectMetadataField', () => {
@@ -355,7 +328,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistrySelectFieldAction with the correct Field', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistrySelectFieldAction(mockFieldsList[0]));
- })
+ });
});
describe('when calling deselectMetadataField', () => {
@@ -365,7 +338,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryDeselectFieldAction with the correct Field', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectFieldAction(mockFieldsList[0]));
- })
+ });
});
describe('when calling deselectAllMetadataField', () => {
@@ -375,7 +348,7 @@ describe('RegistryService', () => {
it('should dispatch a MetadataRegistryDeselectAllFieldAction', () => {
expect(mockStore.dispatch).toHaveBeenCalledWith(new MetadataRegistryDeselectAllFieldAction());
- })
+ });
});
});
@@ -418,7 +391,7 @@ describe('RegistryService', () => {
result.subscribe((response: RestResponse) => {
expect(response.isSuccessful).toBe(true);
});
- })
+ });
});
describe('when deleteMetadataField is called', () => {
@@ -432,7 +405,7 @@ describe('RegistryService', () => {
result.subscribe((response: RestResponse) => {
expect(response.isSuccessful).toBe(true);
});
- })
+ });
});
describe('when clearMetadataSchemaRequests is called', () => {
diff --git a/src/app/core/registry/registry.service.ts b/src/app/core/registry/registry.service.ts
index 650889b8af..3c6de36492 100644
--- a/src/app/core/registry/registry.service.ts
+++ b/src/app/core/registry/registry.service.ts
@@ -3,13 +3,13 @@ import { Injectable } from '@angular/core';
import { RemoteData } from '../data/remote-data';
import { PaginatedList } from '../data/paginated-list';
import { PageInfo } from '../shared/page-info.model';
-import { BitstreamFormat } from './mock-bitstream-format.model';
import {
CreateMetadataFieldRequest,
CreateMetadataSchemaRequest,
DeleteRequest,
GetRequest,
- RestRequest, UpdateMetadataFieldRequest,
+ RestRequest,
+ UpdateMetadataFieldRequest,
UpdateMetadataSchemaRequest
} from '../data/request.models';
import { GenericConstructor } from '../shared/generic-constructor';
@@ -19,24 +19,19 @@ import { RemoteDataBuildService } from '../cache/builders/remote-data-build.serv
import { RequestService } from '../data/request.service';
import { RegistryMetadataschemasResponse } from './registry-metadataschemas-response.model';
import {
- ErrorResponse, MetadatafieldSuccessResponse, MetadataschemaSuccessResponse,
- RegistryBitstreamformatsSuccessResponse,
+ MetadatafieldSuccessResponse,
+ MetadataschemaSuccessResponse,
RegistryMetadatafieldsSuccessResponse,
- RegistryMetadataschemasSuccessResponse, RestResponse
+ RegistryMetadataschemasSuccessResponse,
+ RestResponse
} from '../cache/response.models';
import { HALEndpointService } from '../shared/hal-endpoint.service';
import { RegistryMetadatafieldsResponseParsingService } from '../data/registry-metadatafields-response-parsing.service';
import { RegistryMetadatafieldsResponse } from './registry-metadatafields-response.model';
-import { hasValue, hasNoValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
+import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../shared/empty.util';
import { URLCombiner } from '../url-combiner/url-combiner';
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
-import { RegistryBitstreamformatsResponseParsingService } from '../data/registry-bitstreamformats-response-parsing.service';
-import { RegistryBitstreamformatsResponse } from './registry-bitstreamformats-response.model';
-import {
- configureRequest,
- getResponseFromEntry,
- getSucceededRemoteData
-} from '../shared/operators';
+import { configureRequest, getResponseFromEntry } from '../shared/operators';
import { createSelector, select, Store } from '@ngrx/store';
import { AppState } from '../../app.reducer';
import { MetadataRegistryState } from '../../+admin/admin-registries/metadata-registry/metadata-registry.reducers';
@@ -52,9 +47,8 @@ import {
MetadataRegistrySelectFieldAction,
MetadataRegistrySelectSchemaAction
} from '../../+admin/admin-registries/metadata-registry/metadata-registry.actions';
-import { distinctUntilChanged, flatMap, map, switchMap, take, tap } from 'rxjs/operators';
+import { distinctUntilChanged, flatMap, map, take, tap } from 'rxjs/operators';
import { DSpaceRESTv2Serializer } from '../dspace-rest-v2/dspace-rest-v2.serializer';
-import { ResourceType } from '../shared/resource-type';
import { NormalizedMetadataSchema } from '../metadata/normalized-metadata-schema.model';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { NotificationOptions } from '../../shared/notifications/models/notification-options.model';
@@ -79,7 +73,8 @@ export class RegistryService {
private metadataSchemasPath = 'metadataschemas';
private metadataFieldsPath = 'metadatafields';
- private bitstreamFormatsPath = 'bitstreamformats';
+
+ // private bitstreamFormatsPath = 'bitstreamformats';
constructor(protected requestService: RequestService,
private rdb: RemoteDataBuildService,
@@ -197,7 +192,7 @@ export class RegistryService {
*/
public getAllMetadataFields(pagination?: PaginationComponentOptions): Observable>> {
if (hasNoValue(pagination)) {
- pagination = { currentPage: 1, pageSize: 10000 } as any;
+ pagination = {currentPage: 1, pageSize: 10000} as any;
}
const requestObs = this.getMetadataFieldsRequestObs(pagination);
@@ -231,41 +226,7 @@ export class RegistryService {
return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs);
}
- /**
- * Retrieves all bitstream formats
- * @param pagination The pagination info used to retrieve the bitstream formats
- */
- public getBitstreamFormats(pagination: PaginationComponentOptions): Observable>> {
- const requestObs = this.getBitstreamFormatsRequestObs(pagination);
-
- const requestEntryObs = requestObs.pipe(
- flatMap((request: RestRequest) => this.requestService.getByHref(request.href))
- );
-
- const rbrObs: Observable = requestEntryObs.pipe(
- getResponseFromEntry(),
- map((response: RegistryBitstreamformatsSuccessResponse) => response.bitstreamformatsResponse)
- );
-
- const bitstreamformatsObs: Observable = rbrObs.pipe(
- map((rbr: RegistryBitstreamformatsResponse) => rbr.bitstreamformats)
- );
-
- const pageInfoObs: Observable = requestEntryObs.pipe(
- getResponseFromEntry(),
- map((response: RegistryBitstreamformatsSuccessResponse) => response.pageInfo)
- );
-
- const payloadObs = observableCombineLatest(bitstreamformatsObs, pageInfoObs).pipe(
- map(([bitstreamformats, pageInfo]) => {
- return new PaginatedList(pageInfo, bitstreamformats);
- })
- );
-
- return this.rdb.toRemoteDataObservable(requestEntryObs, payloadObs);
- }
-
- private getMetadataSchemasRequestObs(pagination: PaginationComponentOptions): Observable {
+ public getMetadataSchemasRequestObs(pagination: PaginationComponentOptions): Observable {
return this.halService.getEndpoint(this.metadataSchemasPath).pipe(
map((url: string) => {
const args: string[] = [];
@@ -327,30 +288,6 @@ export class RegistryService {
);
}
- private getBitstreamFormatsRequestObs(pagination: PaginationComponentOptions): Observable {
- return this.halService.getEndpoint(this.bitstreamFormatsPath).pipe(
- map((url: string) => {
- const args: string[] = [];
- args.push(`size=${pagination.pageSize}`);
- args.push(`page=${pagination.currentPage - 1}`);
- if (isNotEmpty(args)) {
- url = new URLCombiner(url, `?${args.join('&')}`).toString();
- }
- const request = new GetRequest(this.requestService.generateRequestId(), url);
- return Object.assign(request, {
- getResponseParser(): GenericConstructor {
- return RegistryBitstreamformatsResponseParsingService;
- }
- });
- }),
- tap((request: RestRequest) => this.requestService.configure(request)),
- );
- }
-
- /**
- * Method to start editing a metadata schema, dispatches an edit schema action
- * @param schema The schema that's being edited
- */
public editMetadataSchema(schema: MetadataSchema) {
this.store.dispatch(new MetadataRegistryEditSchemaAction(schema));
}
@@ -374,7 +311,7 @@ export class RegistryService {
* @param schema The schema that's being selected
*/
public selectMetadataSchema(schema: MetadataSchema) {
- this.store.dispatch(new MetadataRegistrySelectSchemaAction(schema))
+ this.store.dispatch(new MetadataRegistrySelectSchemaAction(schema));
}
/**
@@ -382,14 +319,14 @@ export class RegistryService {
* @param schema The schema that's it being deselected
*/
public deselectMetadataSchema(schema: MetadataSchema) {
- this.store.dispatch(new MetadataRegistryDeselectSchemaAction(schema))
+ this.store.dispatch(new MetadataRegistryDeselectSchemaAction(schema));
}
/**
* Method to deselect all currently selected metadata schema, dispatches a deselect all schema action
*/
public deselectAllMetadataSchema() {
- this.store.dispatch(new MetadataRegistryDeselectAllSchemaAction())
+ this.store.dispatch(new MetadataRegistryDeselectAllSchemaAction());
}
/**
@@ -423,20 +360,20 @@ export class RegistryService {
* @param field The field that's being selected
*/
public selectMetadataField(field: MetadataField) {
- this.store.dispatch(new MetadataRegistrySelectFieldAction(field))
+ this.store.dispatch(new MetadataRegistrySelectFieldAction(field));
}
/**
* Method to deselect a metadata field, dispatches a deselect field action
* @param field The field that's it being deselected
*/
public deselectMetadataField(field: MetadataField) {
- this.store.dispatch(new MetadataRegistryDeselectFieldAction(field))
+ this.store.dispatch(new MetadataRegistryDeselectFieldAction(field));
}
/**
* Method to deselect all currently selected metadata fields, dispatches a deselect all field action
*/
public deselectAllMetadataField() {
- this.store.dispatch(new MetadataRegistryDeselectAllFieldAction())
+ this.store.dispatch(new MetadataRegistryDeselectAllFieldAction());
}
/**
@@ -463,7 +400,7 @@ export class RegistryService {
distinctUntilChanged()
);
- const serializedSchema = new DSpaceRESTv2Serializer(getMapsToType(MetadataSchema.type)).serialize(schema as any as NormalizedMetadataSchema);
+ const serializedSchema = new DSpaceRESTv2Serializer(getMapsToType(MetadataSchema.type)).serialize(schema);
const request$ = endpoint$.pipe(
take(1),
@@ -494,7 +431,7 @@ export class RegistryService {
this.notificationsService.error('Server Error:', (response as any).errorMessage, new NotificationOptions(-1));
}
} else {
- this.showNotifications(true, isUpdate, false, { prefix: schema.prefix });
+ this.showNotifications(true, isUpdate, false, {prefix: schema.prefix});
return response;
}
}),
@@ -521,7 +458,7 @@ export class RegistryService {
public clearMetadataSchemaRequests(): Observable {
return this.halService.getEndpoint(this.metadataSchemasPath).pipe(
tap((href: string) => this.requestService.removeByHrefSubstring(href))
- )
+ );
}
/**
@@ -571,7 +508,7 @@ export class RegistryService {
}
} else {
const fieldString = `${field.schema.prefix}.${field.element}${field.qualifier ? `.${field.qualifier}` : ''}`;
- this.showNotifications(true, isUpdate, true, { field: fieldString });
+ this.showNotifications(true, isUpdate, true, {field: fieldString});
return response;
}
}),
@@ -597,7 +534,7 @@ export class RegistryService {
public clearMetadataFieldRequests(): Observable {
return this.halService.getEndpoint(this.metadataFieldsPath).pipe(
tap((href: string) => this.requestService.removeByHrefSubstring(href))
- )
+ );
}
private delete(path: string, id: number): Observable {
@@ -633,9 +570,9 @@ export class RegistryService {
);
messages.subscribe(([head, content]) => {
if (success) {
- this.notificationsService.success(head, content)
+ this.notificationsService.success(head, content);
} else {
- this.notificationsService.error(head, content)
+ this.notificationsService.error(head, content);
}
});
}
diff --git a/src/app/shared/services/api.service.ts b/src/app/core/services/api.service.ts
similarity index 100%
rename from src/app/shared/services/api.service.ts
rename to src/app/core/services/api.service.ts
diff --git a/src/app/shared/services/client-cookie.service.ts b/src/app/core/services/client-cookie.service.ts
similarity index 100%
rename from src/app/shared/services/client-cookie.service.ts
rename to src/app/core/services/client-cookie.service.ts
diff --git a/src/app/shared/services/cookie.service.spec.ts b/src/app/core/services/cookie.service.spec.ts
similarity index 100%
rename from src/app/shared/services/cookie.service.spec.ts
rename to src/app/core/services/cookie.service.spec.ts
diff --git a/src/app/shared/services/cookie.service.ts b/src/app/core/services/cookie.service.ts
similarity index 100%
rename from src/app/shared/services/cookie.service.ts
rename to src/app/core/services/cookie.service.ts
diff --git a/src/app/shared/services/route.actions.ts b/src/app/core/services/route.actions.ts
similarity index 100%
rename from src/app/shared/services/route.actions.ts
rename to src/app/core/services/route.actions.ts
diff --git a/src/app/shared/services/route.effects.ts b/src/app/core/services/route.effects.ts
similarity index 100%
rename from src/app/shared/services/route.effects.ts
rename to src/app/core/services/route.effects.ts
diff --git a/src/app/shared/services/route.reducer.ts b/src/app/core/services/route.reducer.ts
similarity index 100%
rename from src/app/shared/services/route.reducer.ts
rename to src/app/core/services/route.reducer.ts
diff --git a/src/app/shared/services/route.service.spec.ts b/src/app/core/services/route.service.spec.ts
similarity index 97%
rename from src/app/shared/services/route.service.spec.ts
rename to src/app/core/services/route.service.spec.ts
index c6003521a7..ae31f28384 100644
--- a/src/app/shared/services/route.service.spec.ts
+++ b/src/app/core/services/route.service.spec.ts
@@ -6,9 +6,9 @@ import { Store } from '@ngrx/store';
import { getTestScheduler, hot } from 'jasmine-marbles';
import { RouteService } from './route.service';
-import { MockRouter } from '../mocks/mock-router';
+import { MockRouter } from '../../shared/mocks/mock-router';
import { TestScheduler } from 'rxjs/testing';
-import { AddUrlToHistoryAction } from '../history/history.actions';
+import { AddUrlToHistoryAction } from '../../shared/history/history.actions';
describe('RouteService', () => {
let scheduler: TestScheduler;
diff --git a/src/app/shared/services/route.service.ts b/src/app/core/services/route.service.ts
similarity index 97%
rename from src/app/shared/services/route.service.ts
rename to src/app/core/services/route.service.ts
index c5783aad5c..8bca76f7d2 100644
--- a/src/app/shared/services/route.service.ts
+++ b/src/app/core/services/route.service.ts
@@ -12,8 +12,6 @@ import { combineLatest, Observable } from 'rxjs';
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
import { isEqual } from 'lodash';
-import { AddUrlToHistoryAction } from '../history/history.actions';
-import { historySelector } from '../history/selectors';
import {
AddParameterAction,
SetParameterAction,
@@ -21,8 +19,10 @@ import {
SetQueryParametersAction
} from './route.actions';
import { CoreState } from '../../core/core.reducers';
-import { hasValue } from '../empty.util';
import { coreSelector } from '../../core/core.selectors';
+import { hasValue } from '../../shared/empty.util';
+import { historySelector } from '../../shared/history/selectors';
+import { AddUrlToHistoryAction } from '../../shared/history/history.actions';
/**
* Selector to select all route parameters from the store
@@ -195,7 +195,6 @@ export class RouteService {
this.store.dispatch(new SetParameterAction(key, value));
}
-
public setCurrentRouteInfo() {
combineLatest(this.getRouteParams(), this.route.queryParams)
.pipe(take(1))
diff --git a/src/app/shared/services/server-cookie.service.ts b/src/app/core/services/server-cookie.service.ts
similarity index 100%
rename from src/app/shared/services/server-cookie.service.ts
rename to src/app/core/services/server-cookie.service.ts
diff --git a/src/app/shared/services/server-response.service.ts b/src/app/core/services/server-response.service.ts
similarity index 100%
rename from src/app/shared/services/server-response.service.ts
rename to src/app/core/services/server-response.service.ts
diff --git a/src/app/shared/services/window.service.ts b/src/app/core/services/window.service.ts
similarity index 100%
rename from src/app/shared/services/window.service.ts
rename to src/app/core/services/window.service.ts
diff --git a/src/app/core/shared/bitstream-format-support-level.ts b/src/app/core/shared/bitstream-format-support-level.ts
new file mode 100644
index 0000000000..d92aac7708
--- /dev/null
+++ b/src/app/core/shared/bitstream-format-support-level.ts
@@ -0,0 +1,5 @@
+export enum BitstreamFormatSupportLevel {
+ Known = 'KNOWN',
+ Unknown = 'UNKNOWN',
+ Supported = 'SUPPORTED'
+}
diff --git a/src/app/core/shared/bitstream-format.model.ts b/src/app/core/shared/bitstream-format.model.ts
index bf50cd832f..0e1279e978 100644
--- a/src/app/core/shared/bitstream-format.model.ts
+++ b/src/app/core/shared/bitstream-format.model.ts
@@ -1,6 +1,6 @@
-
import { CacheableObject, TypedObject } from '../cache/object-cache.reducer';
import { ResourceType } from './resource-type';
+import { BitstreamFormatSupportLevel } from './bitstream-format-support-level';
/**
* Model class for a Bitstream Format
@@ -27,7 +27,7 @@ export class BitstreamFormat implements CacheableObject {
/**
* The level of support the system offers for this Bitstream Format
*/
- supportLevel: number;
+ supportLevel: BitstreamFormatSupportLevel;
/**
* True if the Bitstream Format is used to store system information, rather than the content of items in the system
@@ -37,7 +37,7 @@ export class BitstreamFormat implements CacheableObject {
/**
* String representing this Bitstream Format's file extension
*/
- extensions: string;
+ extensions: string[];
/**
* The link to the rest endpoint where this Bitstream Format can be found
@@ -49,4 +49,11 @@ export class BitstreamFormat implements CacheableObject {
*/
uuid: string;
+ /**
+ * Identifier for this Bitstream Format
+ * Note that this ID is unique for bitstream formats,
+ * but might not be unique across different object types
+ */
+ id: string;
+
}
diff --git a/src/app/core/shared/browse-entry.model.ts b/src/app/core/shared/browse-entry.model.ts
index b544413af7..d6074de3f5 100644
--- a/src/app/core/shared/browse-entry.model.ts
+++ b/src/app/core/shared/browse-entry.model.ts
@@ -1,6 +1,7 @@
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { TypedObject } from '../cache/object-cache.reducer';
import { ResourceType } from './resource-type';
+import { GenericConstructor } from './generic-constructor';
import { excludeFromEquals } from '../utilities/equals.decorators';
/**
@@ -30,4 +31,11 @@ export class BrowseEntry extends ListableObject implements TypedObject {
*/
@excludeFromEquals
count: number;
+
+ /**
+ * Method that returns as which type of object this object should be rendered
+ */
+ getRenderTypes(): Array> {
+ return [this.constructor as GenericConstructor];
+ }
}
diff --git a/src/app/core/shared/context.model.ts b/src/app/core/shared/context.model.ts
new file mode 100644
index 0000000000..a0e7f6a967
--- /dev/null
+++ b/src/app/core/shared/context.model.ts
@@ -0,0 +1,12 @@
+/**
+ * This enumeration represents all possible ways of representing a group of objects in the UI
+ */
+
+export enum Context {
+ Undefined = 'undefined',
+ ItemPage = 'itemPage',
+ Search = 'search',
+ Submission = 'submission',
+ AdminMenu = 'adminMenu',
+ SubmissionModal = 'submissionModal',
+}
diff --git a/src/app/core/shared/dspace-object.model.ts b/src/app/core/shared/dspace-object.model.ts
index ec42a37db4..4fec28d246 100644
--- a/src/app/core/shared/dspace-object.model.ts
+++ b/src/app/core/shared/dspace-object.model.ts
@@ -13,6 +13,7 @@ import { RemoteData } from '../data/remote-data';
import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
import { excludeFromEquals } from '../utilities/equals.decorators';
import { ResourceType } from './resource-type';
+import { GenericConstructor } from './generic-constructor';
/**
* An abstract model class for a DSpaceObject.
@@ -122,7 +123,7 @@ export class DSpaceObject extends ListableObject implements CacheableObject {
* Like [[firstMetadata]], but only returns a string value, or `undefined`.
*
* @param {string|string[]} keyOrKeys The metadata key(s) in scope. Wildcards are supported; see [[Metadata]].
- * @param {MetadataValueFilter} filter The value filter to use. If unspecified, no filtering will be done.
+ * @param {MetadataValueFilter} valueFilter The value filter to use. If unspecified, no filtering will be done.
* @returns {string} the first matching string value, or `undefined`.
*/
firstMetadataValue(keyOrKeys: string | string[], valueFilter?: MetadataValueFilter): string {
@@ -159,4 +160,10 @@ export class DSpaceObject extends ListableObject implements CacheableObject {
});
}
+ /**
+ * Method that returns as which type of object this object should be rendered
+ */
+ getRenderTypes(): Array> {
+ return [this.constructor as GenericConstructor];
+ }
}
diff --git a/src/app/core/shared/item-relationships/relationship-type.model.ts b/src/app/core/shared/item-relationships/relationship-type.model.ts
index 98454bc000..06ac94b041 100644
--- a/src/app/core/shared/item-relationships/relationship-type.model.ts
+++ b/src/app/core/shared/item-relationships/relationship-type.model.ts
@@ -33,7 +33,7 @@ export class RelationshipType implements CacheableObject {
/**
* The label that describes the Relation to the left of this RelationshipType
*/
- leftLabel: string;
+ leftwardType: string;
/**
* The maximum amount of Relationships allowed to the left of this RelationshipType
@@ -48,7 +48,7 @@ export class RelationshipType implements CacheableObject {
/**
* The label that describes the Relation to the right of this RelationshipType
*/
- rightLabel: string;
+ rightwardType: string;
/**
* The maximum amount of Relationships allowed to the right of this RelationshipType
diff --git a/src/app/core/shared/item.model.ts b/src/app/core/shared/item.model.ts
index a3e625c022..733a8f941c 100644
--- a/src/app/core/shared/item.model.ts
+++ b/src/app/core/shared/item.model.ts
@@ -5,12 +5,18 @@ import { DSpaceObject } from './dspace-object.model';
import { Collection } from './collection.model';
import { RemoteData } from '../data/remote-data';
import { Bitstream } from './bitstream.model';
-import { hasValue, isNotEmpty } from '../../shared/empty.util';
+import { hasValue, isEmpty, isNotEmpty } from '../../shared/empty.util';
import { PaginatedList } from '../data/paginated-list';
import { Relationship } from './item-relationships/relationship.model';
import { ResourceType } from './resource-type';
import { getSucceededRemoteData } from './operators';
+import { GenericConstructor } from './generic-constructor';
+import { ListableObject } from '../../shared/object-collection/shared/listable-object.model';
+import { DEFAULT_ENTITY_TYPE } from '../../shared/metadata-representation/metadata-representation.decorator';
+/**
+ * Class representing a DSpace Item
+ */
export class Item extends DSpaceObject {
static type = new ResourceType('item');
@@ -110,4 +116,14 @@ export class Item extends DSpaceObject {
}));
}
+ /**
+ * Method that returns as which type of object this object should be rendered
+ */
+ getRenderTypes(): Array> {
+ let entityType = this.firstMetadataValue('relationship.type');
+ if (isEmpty(entityType)) {
+ entityType = DEFAULT_ENTITY_TYPE;
+ }
+ return [entityType, ...super.getRenderTypes()];
+ }
}
diff --git a/src/app/core/shared/operators.spec.ts b/src/app/core/shared/operators.spec.ts
index 56b5d5db7e..548a3f1339 100644
--- a/src/app/core/shared/operators.spec.ts
+++ b/src/app/core/shared/operators.spec.ts
@@ -103,7 +103,7 @@ describe('Core Module - RxJS Operators', () => {
scheduler.schedule(() => source.pipe(getRequestFromRequestUUID(requestService)).subscribe());
scheduler.flush();
- expect(requestService.getByUUID).toHaveBeenCalledWith(testRequestUUID)
+ expect(requestService.getByUUID).toHaveBeenCalledWith(testRequestUUID);
});
it('shouldn\'t return anything if there is no request matching the request uuid', () => {
diff --git a/src/app/core/shared/search/search-configuration.service.spec.ts b/src/app/core/shared/search/search-configuration.service.spec.ts
index b20264493e..b5423e0df0 100644
--- a/src/app/core/shared/search/search-configuration.service.spec.ts
+++ b/src/app/core/shared/search/search-configuration.service.spec.ts
@@ -159,12 +159,4 @@ describe('SearchConfigurationService', () => {
});
});
- describe('when getCurrentFixedFilter is called', () => {
- beforeEach(() => {
- service.getCurrentFixedFilter();
- });
- it('should call getRouteParameterValue on the routeService with parameter name \'fixedFilterQuery\'', () => {
- expect((service as any).routeService.getRouteParameterValue).toHaveBeenCalledWith('fixedFilterQuery');
- });
- });
});
diff --git a/src/app/core/shared/search/search-configuration.service.ts b/src/app/core/shared/search/search-configuration.service.ts
index bbdc9dad50..83073d6822 100644
--- a/src/app/core/shared/search/search-configuration.service.ts
+++ b/src/app/core/shared/search/search-configuration.service.ts
@@ -2,17 +2,17 @@ import { Injectable, OnDestroy } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { BehaviorSubject, combineLatest as observableCombineLatest, merge as observableMerge, Observable, Subscription } from 'rxjs';
-import { filter, map, startWith } from 'rxjs/operators';
+import { filter, map, startWith, switchMap } from 'rxjs/operators';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { SearchOptions } from '../../../shared/search/search-options.model';
import { PaginatedSearchOptions } from '../../../shared/search/paginated-search-options.model';
-import { RouteService } from '../../../shared/services/route.service';
-import { hasNoValue, hasValue, isNotEmpty, isNotEmptyOperator } from '../../../shared/empty.util';
import { SearchFilter } from '../../../shared/search/search-filter.model';
import { RemoteData } from '../../data/remote-data';
-import { getSucceededRemoteData } from '../operators';
import { DSpaceObjectType } from '../dspace-object-type.model';
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
+import { RouteService } from '../../services/route.service';
+import { getSucceededRemoteData } from '../operators';
+import { hasNoValue, hasValue, isNotEmpty } from '../../../shared/empty.util';
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
/**
@@ -195,13 +195,6 @@ export class SearchConfigurationService implements OnDestroy {
}));
}
- /**
- * @returns {Observable} Emits the current fixed filter as a string
- */
- getCurrentFixedFilter(): Observable {
- return this.routeService.getRouteParameterValue('fixedFilterQuery');
- }
-
/**
* @returns {Observable} Emits the current active filters with their values as they are displayed in the frontend URL
*/
@@ -221,7 +214,6 @@ export class SearchConfigurationService implements OnDestroy {
this.getQueryPart(defaults.query),
this.getDSOTypePart(),
this.getFiltersPart(),
- this.getFixedFilterPart()
).subscribe((update) => {
const currentValue: SearchOptions = this.searchOptions.getValue();
const updatedValue: SearchOptions = Object.assign(new PaginatedSearchOptions({}), currentValue, update);
@@ -243,7 +235,6 @@ export class SearchConfigurationService implements OnDestroy {
this.getQueryPart(defaults.query),
this.getDSOTypePart(),
this.getFiltersPart(),
- this.getFixedFilterPart()
).subscribe((update) => {
const currentValue: PaginatedSearchOptions = this.paginatedSearchOptions.getValue();
const updatedValue: PaginatedSearchOptions = Object.assign(new PaginatedSearchOptions({}), currentValue, update);
@@ -340,16 +331,4 @@ export class SearchConfigurationService implements OnDestroy {
return { filters }
}));
}
-
- /**
- * @returns {Observable} Emits the current fixed filter as a partial SearchOptions object
- */
- private getFixedFilterPart(): Observable {
- return this.getCurrentFixedFilter().pipe(
- isNotEmptyOperator(),
- map((fixedFilter) => {
- return { fixedFilter }
- }),
- );
- }
}
diff --git a/src/app/core/shared/search/search-filter.service.spec.ts b/src/app/core/shared/search/search-filter.service.spec.ts
index 84567f4e3c..1031f6ce2f 100644
--- a/src/app/core/shared/search/search-filter.service.spec.ts
+++ b/src/app/core/shared/search/search-filter.service.spec.ts
@@ -258,7 +258,6 @@ describe('SearchFilterService', () => {
});
});
-
describe('when the getCurrentView method is called', () => {
beforeEach(() => {
spyOn(routeServiceStub, 'getQueryParameterValue');
diff --git a/src/app/core/shared/search/search-filter.service.ts b/src/app/core/shared/search/search-filter.service.ts
index 22a8ae7c5b..c1559db525 100644
--- a/src/app/core/shared/search/search-filter.service.ts
+++ b/src/app/core/shared/search/search-filter.service.ts
@@ -1,10 +1,7 @@
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { distinctUntilChanged, map, mergeMap } from 'rxjs/operators';
import { Injectable, InjectionToken } from '@angular/core';
-import {
- SearchFiltersState,
- SearchFilterState
-} from '../../../shared/search/search-filters/search-filter/search-filter.reducer';
+import { SearchFiltersState, SearchFilterState } from '../../../shared/search/search-filters/search-filter/search-filter.reducer';
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
import {
SearchFilterCollapseAction,
@@ -17,11 +14,11 @@ import {
} from '../../../shared/search/search-filters/search-filter/search-filter.actions';
import { hasValue, isNotEmpty, } from '../../../shared/empty.util';
import { SearchFilterConfig } from '../../../shared/search/search-filter-config.model';
-import { RouteService } from '../../../shared/services/route.service';
import { SortDirection, SortOptions } from '../../cache/models/sort-options.model';
+import { RouteService } from '../../../core/services/route.service';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { Params } from '@angular/router';
-// const spy = create();
+
const filterStateSelector = (state: SearchFiltersState) => state.searchFilter;
export const FILTER_CONFIG: InjectionToken = new InjectionToken('filterConfig');
@@ -114,14 +111,6 @@ export class SearchFilterService {
return this.routeService.getQueryParamsWithPrefix('f.');
}
- /**
- * Fetch the current active fixed filter from the route parameters and return the query by filter name
- * @returns {Observable}
- */
- getCurrentFixedFilter(): Observable {
- return this.routeService.getRouteParameterValue('fixedFilterQuery');
- }
-
/**
* Fetch the current view from the query parameters
* @returns {Observable}
diff --git a/src/app/core/shared/search/search.service.spec.ts b/src/app/core/shared/search/search.service.spec.ts
index 98a8e630b6..0e093d119c 100644
--- a/src/app/core/shared/search/search.service.spec.ts
+++ b/src/app/core/shared/search/search.service.spec.ts
@@ -5,10 +5,6 @@ import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { SearchService } from './search.service';
-import { ItemDataService } from '../../data/item-data.service';
-import { SetViewMode } from '../../../shared/view-mode';
-import { GLOBAL_CONFIG } from '../../../../config';
-import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
import { Router, UrlTree } from '@angular/router';
import { RequestService } from '../../data/request.service';
import { ActivatedRouteStub } from '../../../shared/testing/active-router-stub';
@@ -26,8 +22,9 @@ import { CommunityDataService } from '../../data/community-data.service';
import { ViewMode } from '../view-mode.model';
import { DSpaceObjectDataService } from '../../data/dspace-object-data.service';
import { map } from 'rxjs/operators';
-import { RouteService } from '../../../shared/services/route.service';
+import { RouteService } from '../../services/route.service';
import { routeServiceStub } from '../../../shared/testing/route-service-stub';
+import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
import { createSuccessfulRemoteDataObject$ } from '../../../shared/testing/utils';
@Component({ template: '' })
@@ -66,7 +63,7 @@ describe('SearchService', () => {
it('should return list view mode', () => {
searchService.getViewMode().subscribe((viewMode) => {
- expect(viewMode).toBe(ViewMode.List);
+ expect(viewMode).toBe(ViewMode.ListElement);
});
});
});
@@ -125,38 +122,38 @@ describe('SearchService', () => {
});
it('should call the navigate method on the Router with view mode list parameter as a parameter when setViewMode is called', () => {
- searchService.setViewMode(ViewMode.List);
+ searchService.setViewMode(ViewMode.ListElement);
expect(router.navigate).toHaveBeenCalledWith(['/search'], {
- queryParams: { view: ViewMode.List, page: 1 },
+ queryParams: { view: ViewMode.ListElement, page: 1 },
queryParamsHandling: 'merge'
});
});
it('should call the navigate method on the Router with view mode grid parameter as a parameter when setViewMode is called', () => {
- searchService.setViewMode(ViewMode.Grid);
+ searchService.setViewMode(ViewMode.GridElement);
expect(router.navigate).toHaveBeenCalledWith(['/search'], {
- queryParams: { view: ViewMode.Grid, page: 1 },
+ queryParams: { view: ViewMode.GridElement, page: 1 },
queryParamsHandling: 'merge'
});
});
it('should return ViewMode.List when the viewMode is set to ViewMode.List in the ActivatedRoute', () => {
- let viewMode = ViewMode.Grid;
+ let viewMode = ViewMode.GridElement;
spyOn(routeService, 'getQueryParamMap').and.returnValue(observableOf(new Map([
- [ 'view', ViewMode.List ],
+ [ 'view', ViewMode.ListElement ],
])));
searchService.getViewMode().subscribe((mode) => viewMode = mode);
- expect(viewMode).toEqual(ViewMode.List);
+ expect(viewMode).toEqual(ViewMode.ListElement);
});
it('should return ViewMode.Grid when the viewMode is set to ViewMode.Grid in the ActivatedRoute', () => {
- let viewMode = ViewMode.List;
+ let viewMode = ViewMode.ListElement;
spyOn(routeService, 'getQueryParamMap').and.returnValue(observableOf(new Map([
- [ 'view', ViewMode.Grid ],
+ [ 'view', ViewMode.GridElement ],
])));
searchService.getViewMode().subscribe((mode) => viewMode = mode);
- expect(viewMode).toEqual(ViewMode.Grid);
+ expect(viewMode).toEqual(ViewMode.GridElement);
});
describe('when search is called', () => {
diff --git a/src/app/core/shared/search/search.service.ts b/src/app/core/shared/search/search.service.ts
index e37c2de48b..9c1c81db5a 100644
--- a/src/app/core/shared/search/search.service.ts
+++ b/src/app/core/shared/search/search.service.ts
@@ -30,9 +30,9 @@ import { Community } from '../community.model';
import { CommunityDataService } from '../../data/community-data.service';
import { ViewMode } from '../view-mode.model';
import { DSpaceObjectDataService } from '../../data/dspace-object-data.service';
-import { RouteService } from '../../../shared/services/route.service';
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
import { configureRequest, filterSuccessfulResponses, getResponseFromEntry, getSucceededRemoteData } from '../operators';
+import { RouteService } from '../../services/route.service';
/**
* Service that performs all general actions that have to do with the search page
@@ -91,9 +91,10 @@ export class SearchService implements OnDestroy {
/**
* Method to retrieve a paginated list of search results from the server
* @param {PaginatedSearchOptions} searchOptions The configuration necessary to perform this search
+ * @param responseMsToLive The amount of milliseconds for the response to live in cache
* @returns {Observable>>>} Emits a paginated list with all search results found
*/
- search(searchOptions?: PaginatedSearchOptions): Observable>>> {
+ search(searchOptions?: PaginatedSearchOptions, responseMsToLive?: number): Observable>>> {
const hrefObs = this.halService.getEndpoint(this.searchLinkPath).pipe(
map((url: string) => {
if (hasValue(searchOptions)) {
@@ -113,6 +114,7 @@ export class SearchService implements OnDestroy {
};
return Object.assign(request, {
+ responseMsToLive: hasValue(responseMsToLive) ? responseMsToLive : request.responseMsToLive,
getResponseParser: getResponseParserFn
});
}),
@@ -149,7 +151,7 @@ export class SearchService implements OnDestroy {
let co = DSpaceObject;
if (dsos.payload[index]) {
const constructor: GenericConstructor = dsos.payload[index].constructor as GenericConstructor;
- co = getSearchResultFor(constructor, searchOptions.configuration);
+ co = getSearchResultFor(constructor);
return Object.assign(new co(), object, {
indexableObject: dsos.payload[index]
});
@@ -330,7 +332,7 @@ export class SearchService implements OnDestroy {
if (isNotEmpty(params.get('view')) && hasValue(params.get('view'))) {
return params.get('view');
} else {
- return ViewMode.List;
+ return ViewMode.ListElement;
}
}));
}
@@ -343,7 +345,7 @@ export class SearchService implements OnDestroy {
this.routeService.getQueryParameterValue('pageSize').pipe(first())
.subscribe((pageSize) => {
let queryParams = { view: viewMode, page: 1 };
- if (viewMode === ViewMode.Detail) {
+ if (viewMode === ViewMode.DetailedListElement) {
queryParams = Object.assign(queryParams, {pageSize: '1'});
} else if (pageSize === '1') {
queryParams = Object.assign(queryParams, {pageSize: '10'});
diff --git a/src/app/core/shared/view-mode.model.ts b/src/app/core/shared/view-mode.model.ts
index 9c8d086097..c2f076a5e5 100644
--- a/src/app/core/shared/view-mode.model.ts
+++ b/src/app/core/shared/view-mode.model.ts
@@ -3,7 +3,8 @@
*/
export enum ViewMode {
- List = 'list',
- Grid = 'grid',
- Detail = 'detail'
+ ListElement = 'listElement',
+ GridElement = 'gridElement',
+ DetailedListElement = 'detailedListElement',
+ StandalonePage = 'standalonePage',
}
diff --git a/src/app/core/submission/submission-response-parsing.service.ts b/src/app/core/submission/submission-response-parsing.service.ts
index a0811c8f2d..de7d683d91 100644
--- a/src/app/core/submission/submission-response-parsing.service.ts
+++ b/src/app/core/submission/submission-response-parsing.service.ts
@@ -128,7 +128,10 @@ export class SubmissionResponseParsingService extends BaseResponseParsingService
// Iterate over all workspaceitem's sections
Object.keys(item.sections)
.forEach((sectionId) => {
- if (typeof item.sections[sectionId] === 'object' && isNotEmpty(item.sections[sectionId])) {
+ if (typeof item.sections[sectionId] === 'object' && (isNotEmpty(item.sections[sectionId]) &&
+ // When Upload section is disabled, add to submission only if there are files
+ (!item.sections[sectionId].hasOwnProperty('files') || isNotEmpty((item.sections[sectionId] as any).files)))) {
+
const normalizedSectionData = Object.create({});
// Iterate over all sections property
Object.keys(item.sections[sectionId])
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html
new file mode 100644
index 0000000000..fb69ed92f5
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/app/shared/object-list/item-list-element/item-list-element.component.scss b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.scss
similarity index 100%
rename from src/app/shared/object-list/item-list-element/item-list-element.component.scss
rename to src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.scss
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts
new file mode 100644
index 0000000000..9a147637fa
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.spec.ts
@@ -0,0 +1,75 @@
+import { Item } from '../../../../core/shared/item.model';
+import { JournalIssueGridElementComponent } from './journal-issue-grid-element.component';
+import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
+import { PaginatedList } from '../../../../core/data/paginated-list';
+import { PageInfo } from '../../../../core/shared/page-info.model';
+import { of as observableOf } from 'rxjs';
+import { async, TestBed } from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
+import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { By } from '@angular/platform-browser';
+
+const mockItem = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'creativework.datePublished': [
+ {
+ language: null,
+ value: '2015-06-26'
+ }
+ ],
+ 'journal.title': [
+ {
+ language: 'en_US',
+ value: 'The journal title'
+ }
+ ]
+ }
+});
+
+describe('JournalIssueGridElementComponent', () => {
+ let comp;
+ let fixture;
+
+ const truncatableServiceStub: any = {
+ isCollapsed: (id: number) => observableOf(true),
+ };
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [NoopAnimationsModule],
+ declarations: [JournalIssueGridElementComponent, TruncatePipe],
+ providers: [
+ { provide: TruncatableService, useValue: truncatableServiceStub },
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+ }).overrideComponent(JournalIssueGridElementComponent, {
+ set: { changeDetection: ChangeDetectionStrategy.Default }
+ }).compileComponents();
+ }));
+
+ beforeEach(async(() => {
+ fixture = TestBed.createComponent(JournalIssueGridElementComponent);
+ comp = fixture.componentInstance;
+ }));
+
+ describe(`when the journal issue is rendered`, () => {
+ beforeEach(() => {
+ comp.object = mockItem;
+ fixture.detectChanges();
+ });
+
+ it(`should contain a JournalIssueSearchResultGridElementComponent`, () => {
+ const journalIssueGridElement = fixture.debugElement.query(By.css(`ds-journal-issue-search-result-grid-element`));
+ expect(journalIssueGridElement).not.toBeNull();
+ });
+ });
+});
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.ts
new file mode 100644
index 0000000000..3e57731613
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-issue/journal-issue-grid-element.component.ts
@@ -0,0 +1,17 @@
+import { Component } from '@angular/core';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { AbstractListableElementComponent } from '../../../../shared/object-collection/shared/object-collection-element/abstract-listable-element.component';
+import { Item } from '../../../../core/shared/item.model';
+
+@listableObjectComponent('JournalIssue', ViewMode.GridElement)
+@Component({
+ selector: 'ds-journal-issue-grid-element',
+ styleUrls: ['./journal-issue-grid-element.component.scss'],
+ templateUrl: './journal-issue-grid-element.component.html',
+})
+/**
+ * The component for displaying a grid element for an item of the type Journal Issue
+ */
+export class JournalIssueGridElementComponent extends AbstractListableElementComponent
- {
+}
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html
new file mode 100644
index 0000000000..53713b47ee
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.scss b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.scss
similarity index 100%
rename from src/app/shared/object-list/search-result-list-element/item-search-result/item-search-result-list-element.component.scss
rename to src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.scss
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts
new file mode 100644
index 0000000000..6f74f97ac1
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.spec.ts
@@ -0,0 +1,75 @@
+import { Item } from '../../../../core/shared/item.model';
+import { JournalVolumeGridElementComponent } from './journal-volume-grid-element.component';
+import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
+import { PaginatedList } from '../../../../core/data/paginated-list';
+import { PageInfo } from '../../../../core/shared/page-info.model';
+import { of as observableOf } from 'rxjs';
+import { async, TestBed } from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
+import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { By } from '@angular/platform-browser';
+
+const mockItem = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'creativework.datePublished': [
+ {
+ language: null,
+ value: '2015-06-26'
+ }
+ ],
+ 'dc.description': [
+ {
+ language: 'en_US',
+ value: 'A description for the journal volume'
+ }
+ ]
+ }
+});
+
+describe('JournalVolumeGridElementComponent', () => {
+ let comp;
+ let fixture;
+
+ const truncatableServiceStub: any = {
+ isCollapsed: (id: number) => observableOf(true),
+ };
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [NoopAnimationsModule],
+ declarations: [JournalVolumeGridElementComponent, TruncatePipe],
+ providers: [
+ { provide: TruncatableService, useValue: truncatableServiceStub },
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+ }).overrideComponent(JournalVolumeGridElementComponent, {
+ set: { changeDetection: ChangeDetectionStrategy.Default }
+ }).compileComponents();
+ }));
+
+ beforeEach(async(() => {
+ fixture = TestBed.createComponent(JournalVolumeGridElementComponent);
+ comp = fixture.componentInstance;
+ }));
+
+ describe(`when the journal volume is rendered`, () => {
+ beforeEach(() => {
+ comp.object = mockItem;
+ fixture.detectChanges();
+ });
+
+ it(`should contain a JournalVolumeSearchResultGridElementComponent`, () => {
+ const journalVolumeGridElement = fixture.debugElement.query(By.css(`ds-journal-volume-search-result-grid-element`));
+ expect(journalVolumeGridElement).not.toBeNull();
+ });
+ });
+});
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.ts
new file mode 100644
index 0000000000..eb88c25a12
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal-volume/journal-volume-grid-element.component.ts
@@ -0,0 +1,17 @@
+import { Component } from '@angular/core';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { AbstractListableElementComponent } from '../../../../shared/object-collection/shared/object-collection-element/abstract-listable-element.component';
+import { Item } from '../../../../core/shared/item.model';
+
+@listableObjectComponent('JournalVolume', ViewMode.GridElement)
+@Component({
+ selector: 'ds-journal-volume-grid-element',
+ styleUrls: ['./journal-volume-grid-element.component.scss'],
+ templateUrl: './journal-volume-grid-element.component.html',
+})
+/**
+ * The component for displaying a grid element for an item of the type Journal Volume
+ */
+export class JournalVolumeGridElementComponent extends AbstractListableElementComponent
- {
+}
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html
new file mode 100644
index 0000000000..594a0e0dc1
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.scss b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts
new file mode 100644
index 0000000000..0619309d09
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.spec.ts
@@ -0,0 +1,81 @@
+import { Item } from '../../../../core/shared/item.model';
+import { of as observableOf } from 'rxjs/internal/observable/of';
+import { JournalGridElementComponent } from './journal-grid-element.component';
+import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
+import { PaginatedList } from '../../../../core/data/paginated-list';
+import { PageInfo } from '../../../../core/shared/page-info.model';
+import { async, TestBed } from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
+import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { By } from '@angular/platform-browser';
+
+const mockItem = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'creativework.editor': [
+ {
+ language: 'en_US',
+ value: 'Smith, Donald'
+ }
+ ],
+ 'creativework.publisher': [
+ {
+ language: 'en_US',
+ value: 'A company'
+ }
+ ],
+ 'dc.description': [
+ {
+ language: 'en_US',
+ value: 'This is the description'
+ }
+ ]
+ }
+});
+
+describe('JournalGridElementComponent', () => {
+ let comp;
+ let fixture;
+
+ const truncatableServiceStub: any = {
+ isCollapsed: (id: number) => observableOf(true),
+ };
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [NoopAnimationsModule],
+ declarations: [JournalGridElementComponent, TruncatePipe],
+ providers: [
+ { provide: TruncatableService, useValue: truncatableServiceStub },
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+ }).overrideComponent(JournalGridElementComponent, {
+ set: { changeDetection: ChangeDetectionStrategy.Default }
+ }).compileComponents();
+ }));
+
+ beforeEach(async(() => {
+ fixture = TestBed.createComponent(JournalGridElementComponent);
+ comp = fixture.componentInstance;
+ }));
+
+ describe(`when the journal is rendered`, () => {
+ beforeEach(() => {
+ comp.object = mockItem;
+ fixture.detectChanges();
+ });
+
+ it(`should contain a JournalGridElementComponent`, () => {
+ const journalGridElement = fixture.debugElement.query(By.css(`ds-journal-search-result-grid-element`));
+ expect(journalGridElement).not.toBeNull();
+ });
+ });
+});
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.ts
new file mode 100644
index 0000000000..1d7c1e5b73
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/journal/journal-grid-element.component.ts
@@ -0,0 +1,17 @@
+import { Component } from '@angular/core';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { AbstractListableElementComponent } from '../../../../shared/object-collection/shared/object-collection-element/abstract-listable-element.component';
+import { Item } from '../../../../core/shared/item.model';
+
+@listableObjectComponent('Journal', ViewMode.GridElement)
+@Component({
+ selector: 'ds-journal-grid-element',
+ styleUrls: ['./journal-grid-element.component.scss'],
+ templateUrl: './journal-grid-element.component.html',
+})
+/**
+ * The component for displaying a grid element for an item of the type Journal
+ */
+export class JournalGridElementComponent extends AbstractListableElementComponent
- {
+}
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html
new file mode 100644
index 0000000000..18ff77bf23
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.scss b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.spec.ts
new file mode 100644
index 0000000000..3cca9071b0
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.spec.ts
@@ -0,0 +1,49 @@
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/testing/utils';
+import { PaginatedList } from '../../../../../core/data/paginated-list';
+import { PageInfo } from '../../../../../core/shared/page-info.model';
+import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
+import { JournalIssueSearchResultGridElementComponent } from './journal-issue-search-result-grid-element.component';
+
+const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithMetadata.hitHighlights = {};
+mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'creativework.datePublished': [
+ {
+ language: null,
+ value: '2015-06-26'
+ }
+ ],
+ 'journal.title': [
+ {
+ language: 'en_US',
+ value: 'The journal title'
+ }
+ ]
+ }
+});
+
+const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithoutMetadata.hitHighlights = {};
+mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ]
+ }
+});
+
+describe('JournalIssueSearchResultGridElementComponent', getEntityGridElementTestComponent(JournalIssueSearchResultGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['date', 'journal-title']));
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.ts
new file mode 100644
index 0000000000..9d27842c16
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component.ts
@@ -0,0 +1,20 @@
+import { Component } from '@angular/core';
+import { ViewMode } from '../../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { focusShadow } from '../../../../../shared/animations/focus';
+import { SearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/search-result-grid-element.component';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+
+@listableObjectComponent('JournalIssueSearchResult', ViewMode.GridElement)
+@Component({
+ selector: 'ds-journal-issue-search-result-grid-element',
+ styleUrls: ['./journal-issue-search-result-grid-element.component.scss'],
+ templateUrl: './journal-issue-search-result-grid-element.component.html',
+ animations: [focusShadow]
+})
+/**
+ * The component for displaying a grid element for an item search result of the type Journal Issue
+ */
+export class JournalIssueSearchResultGridElementComponent extends SearchResultGridElementComponent {
+}
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html
new file mode 100644
index 0000000000..07e50eb6fb
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.scss b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.spec.ts
new file mode 100644
index 0000000000..8923b15ccb
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.spec.ts
@@ -0,0 +1,49 @@
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/testing/utils';
+import { PaginatedList } from '../../../../../core/data/paginated-list';
+import { PageInfo } from '../../../../../core/shared/page-info.model';
+import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
+import { JournalVolumeSearchResultGridElementComponent } from './journal-volume-search-result-grid-element.component';
+
+const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithMetadata.hitHighlights = {};
+mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'creativework.datePublished': [
+ {
+ language: null,
+ value: '2015-06-26'
+ }
+ ],
+ 'dc.description': [
+ {
+ language: 'en_US',
+ value: 'A description for the journal volume'
+ }
+ ]
+ }
+});
+
+const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithoutMetadata.hitHighlights = {};
+mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ]
+ }
+});
+
+describe('JournalVolumeSearchResultGridElementComponent', getEntityGridElementTestComponent(JournalVolumeSearchResultGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['date', 'description']));
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.ts
new file mode 100644
index 0000000000..802a6d8692
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component.ts
@@ -0,0 +1,20 @@
+import { Component } from '@angular/core';
+import { SearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/search-result-grid-element.component';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+import { ViewMode } from '../../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { focusShadow } from '../../../../../shared/animations/focus';
+
+@listableObjectComponent('JournalVolumeSearchResult', ViewMode.GridElement)
+@Component({
+ selector: 'ds-journal-volume-search-result-grid-element',
+ styleUrls: ['./journal-volume-search-result-grid-element.component.scss'],
+ templateUrl: './journal-volume-search-result-grid-element.component.html',
+ animations: [focusShadow]
+})
+/**
+ * The component for displaying a grid element for an item search result of the type Journal Volume
+ */
+export class JournalVolumeSearchResultGridElementComponent extends SearchResultGridElementComponent {
+}
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html
new file mode 100644
index 0000000000..394e5241e1
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{firstMetadataValue('creativework.editor')}}
+
+ ,
+ {{firstMetadataValue('creativework.publisher')}}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.scss b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.spec.ts
new file mode 100644
index 0000000000..5712b211ff
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.spec.ts
@@ -0,0 +1,55 @@
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/testing/utils';
+import { PaginatedList } from '../../../../../core/data/paginated-list';
+import { PageInfo } from '../../../../../core/shared/page-info.model';
+import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
+import { JournalSearchResultGridElementComponent } from './journal-search-result-grid-element.component';
+
+const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithMetadata.hitHighlights = {};
+mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'creativework.editor': [
+ {
+ language: 'en_US',
+ value: 'Smith, Donald'
+ }
+ ],
+ 'creativework.publisher': [
+ {
+ language: 'en_US',
+ value: 'A company'
+ }
+ ],
+ 'dc.description': [
+ {
+ language: 'en_US',
+ value: 'This is the description'
+ }
+ ]
+ }
+});
+
+const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithoutMetadata.hitHighlights = {};
+mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ]
+ }
+});
+
+describe('JournalSearchResultGridElementComponent', getEntityGridElementTestComponent(JournalSearchResultGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['editor', 'publisher', 'description']));
diff --git a/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.ts b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.ts
new file mode 100644
index 0000000000..97d43356c6
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component.ts
@@ -0,0 +1,20 @@
+import { Component } from '@angular/core';
+import { focusShadow } from '../../../../../shared/animations/focus';
+import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { ViewMode } from '../../../../../core/shared/view-mode.model';
+import { SearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/search-result-grid-element.component';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+
+@listableObjectComponent('JournalSearchResult', ViewMode.GridElement)
+@Component({
+ selector: 'ds-journal-search-result-grid-element',
+ styleUrls: ['./journal-search-result-grid-element.component.scss'],
+ templateUrl: './journal-search-result-grid-element.component.html',
+ animations: [focusShadow]
+})
+/**
+ * The component for displaying a grid element for an item search result of the type Journal
+ */
+export class JournalSearchResultGridElementComponent extends SearchResultGridElementComponent {
+}
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/journal-issue/journal-issue-list-element.component.html b/src/app/entity-groups/journal-entities/item-list-elements/journal-issue/journal-issue-list-element.component.html
index 030a26df39..398feea260 100644
--- a/src/app/entity-groups/journal-entities/item-list-elements/journal-issue/journal-issue-list-element.component.html
+++ b/src/app/entity-groups/journal-entities/item-list-elements/journal-issue/journal-issue-list-element.component.html
@@ -1,21 +1 @@
-
-
-
-
- 0"
- class="item-list-journal-issues">
-
-
-
- 0"
- class="item-list-journal-issue-numbers">
-
- -
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/journal-issue/journal-issue-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/journal-issue/journal-issue-list-element.component.spec.ts
index 24498088cb..a7dfcbbf36 100644
--- a/src/app/entity-groups/journal-entities/item-list-elements/journal-issue/journal-issue-list-element.component.spec.ts
+++ b/src/app/entity-groups/journal-entities/item-list-elements/journal-issue/journal-issue-list-element.component.spec.ts
@@ -1,17 +1,13 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { async, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { JournalIssueListElementComponent } from './journal-issue-list-element.component';
import { of as observableOf } from 'rxjs';
import { Item } from '../../../../core/shared/item.model';
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
-import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
-let journalIssueListElementComponent: JournalIssueListElementComponent;
-let fixture: ComponentFixture;
-
-const mockItemWithMetadata: Item = Object.assign(new Item(), {
+const mockItem: Item = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
@@ -34,28 +30,22 @@ const mockItemWithMetadata: Item = Object.assign(new Item(), {
]
}
});
-const mockItemWithoutMetadata: Item = Object.assign(new Item(), {
- bitstreams: observableOf({}),
- metadata: {
- 'dc.title': [
- {
- language: 'en_US',
- value: 'This is just another title'
- }
- ]
- }
-});
describe('JournalIssueListElementComponent', () => {
+ let comp;
+ let fixture;
+
+ const truncatableServiceStub: any = {
+ isCollapsed: (id: number) => observableOf(true),
+ };
+
beforeEach(async(() => {
TestBed.configureTestingModule({
- declarations: [ JournalIssueListElementComponent , TruncatePipe],
+ declarations: [JournalIssueListElementComponent, TruncatePipe],
providers: [
- { provide: ITEM, useValue: mockItemWithMetadata},
- { provide: TruncatableService, useValue: {} }
+ { provide: TruncatableService, useValue: truncatableServiceStub },
],
-
- schemas: [ NO_ERRORS_SCHEMA ]
+ schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(JournalIssueListElementComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
@@ -63,55 +53,19 @@ describe('JournalIssueListElementComponent', () => {
beforeEach(async(() => {
fixture = TestBed.createComponent(JournalIssueListElementComponent);
- journalIssueListElementComponent = fixture.componentInstance;
-
+ comp = fixture.componentInstance;
}));
- describe('When the item has a journal identifier', () => {
+ describe(`when the journal issue is rendered`, () => {
beforeEach(() => {
- journalIssueListElementComponent.item = mockItemWithMetadata;
+ comp.object = mockItem;
fixture.detectChanges();
});
- it('should show the journal issues span', () => {
- const journalIdentifierField = fixture.debugElement.query(By.css('span.item-list-journal-issues'));
- expect(journalIdentifierField).not.toBeNull();
+ it(`should contain a JournalIssueListElementComponent`, () => {
+ const journalIssueListElement = fixture.debugElement.query(By.css(`ds-journal-issue-search-result-list-element`));
+ expect(journalIssueListElement).not.toBeNull();
});
});
- describe('When the item has no journal identifier', () => {
- beforeEach(() => {
- journalIssueListElementComponent.item = mockItemWithoutMetadata;
- fixture.detectChanges();
- });
-
- it('should not show the journal issues span', () => {
- const journalIdentifierField = fixture.debugElement.query(By.css('span.item-list-journal-issues'));
- expect(journalIdentifierField).toBeNull();
- });
- });
-
- describe('When the item has a journal number', () => {
- beforeEach(() => {
- journalIssueListElementComponent.item = mockItemWithMetadata;
- fixture.detectChanges();
- });
-
- it('should show the journal issue numbers span', () => {
- const journalNumberField = fixture.debugElement.query(By.css('span.item-list-journal-issue-numbers'));
- expect(journalNumberField).not.toBeNull();
- });
- });
-
- describe('When the item has no journal number', () => {
- beforeEach(() => {
- journalIssueListElementComponent.item = mockItemWithoutMetadata;
- fixture.detectChanges();
- });
-
- it('should not show the journal issue numbers span', () => {
- const journalNumberField = fixture.debugElement.query(By.css('span.item-list-journal-issue-numbers'));
- expect(journalNumberField).toBeNull();
- });
- });
});
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/journal-issue/journal-issue-list-element.component.ts b/src/app/entity-groups/journal-entities/item-list-elements/journal-issue/journal-issue-list-element.component.ts
index 5c2b211be4..454c140050 100644
--- a/src/app/entity-groups/journal-entities/item-list-elements/journal-issue/journal-issue-list-element.component.ts
+++ b/src/app/entity-groups/journal-entities/item-list-elements/journal-issue/journal-issue-list-element.component.ts
@@ -1,8 +1,10 @@
import { Component } from '@angular/core';
-import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
-import { TypedItemSearchResultListElementComponent } from '../../../../shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { AbstractListableElementComponent } from '../../../../shared/object-collection/shared/object-collection-element/abstract-listable-element.component';
+import { Item } from '../../../../core/shared/item.model';
-@rendersItemType('JournalIssue', ItemViewMode.Summary)
+@listableObjectComponent('JournalIssue', ViewMode.ListElement)
@Component({
selector: 'ds-journal-issue-list-element',
styleUrls: ['./journal-issue-list-element.component.scss'],
@@ -11,5 +13,5 @@ import { TypedItemSearchResultListElementComponent } from '../../../../shared/ob
/**
* The component for displaying a list element for an item of the type Journal Issue
*/
-export class JournalIssueListElementComponent extends TypedItemSearchResultListElementComponent {
+export class JournalIssueListElementComponent extends AbstractListableElementComponent- {
}
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/journal-volume/journal-volume-list-element.component.html b/src/app/entity-groups/journal-entities/item-list-elements/journal-volume/journal-volume-list-element.component.html
index 4e6e34d3d6..bf967e6e78 100644
--- a/src/app/entity-groups/journal-entities/item-list-elements/journal-volume/journal-volume-list-element.component.html
+++ b/src/app/entity-groups/journal-entities/item-list-elements/journal-volume/journal-volume-list-element.component.html
@@ -1,21 +1 @@
-
-
-
-
- 0"
- class="item-list-journal-volumes">
-
-
-
-
- 0"
- class="item-list-journal-volume-identifiers">
-
- ()
-
-
-
-
-
+
\ No newline at end of file
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/journal-volume/journal-volume-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/journal-volume/journal-volume-list-element.component.spec.ts
index 15f5424960..ba43805525 100644
--- a/src/app/entity-groups/journal-entities/item-list-elements/journal-volume/journal-volume-list-element.component.spec.ts
+++ b/src/app/entity-groups/journal-entities/item-list-elements/journal-volume/journal-volume-list-element.component.spec.ts
@@ -1,17 +1,13 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { async, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { JournalVolumeListElementComponent } from './journal-volume-list-element.component';
import { of as observableOf } from 'rxjs';
import { Item } from '../../../../core/shared/item.model';
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
-import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
-let journalVolumeListElementComponent: JournalVolumeListElementComponent;
-let fixture: ComponentFixture;
-
-const mockItemWithMetadata: Item = Object.assign(new Item(), {
+const mockItem: Item = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
@@ -34,28 +30,22 @@ const mockItemWithMetadata: Item = Object.assign(new Item(), {
]
}
});
-const mockItemWithoutMetadata: Item = Object.assign(new Item(), {
- bitstreams: observableOf({}),
- metadata: {
- 'dc.title': [
- {
- language: 'en_US',
- value: 'This is just another title'
- }
- ]
- }
-});
describe('JournalVolumeListElementComponent', () => {
+ let comp;
+ let fixture;
+
+ const truncatableServiceStub: any = {
+ isCollapsed: (id: number) => observableOf(true),
+ };
+
beforeEach(async(() => {
TestBed.configureTestingModule({
- declarations: [ JournalVolumeListElementComponent , TruncatePipe],
+ declarations: [JournalVolumeListElementComponent, TruncatePipe],
providers: [
- { provide: ITEM, useValue: mockItemWithMetadata},
- { provide: TruncatableService, useValue: {} }
+ { provide: TruncatableService, useValue: truncatableServiceStub },
],
-
- schemas: [ NO_ERRORS_SCHEMA ]
+ schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(JournalVolumeListElementComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
@@ -63,55 +53,18 @@ describe('JournalVolumeListElementComponent', () => {
beforeEach(async(() => {
fixture = TestBed.createComponent(JournalVolumeListElementComponent);
- journalVolumeListElementComponent = fixture.componentInstance;
-
+ comp = fixture.componentInstance;
}));
- describe('When the item has a journal title', () => {
+ describe(`when the journal volume is rendered`, () => {
beforeEach(() => {
- journalVolumeListElementComponent.item = mockItemWithMetadata;
+ comp.object = mockItem;
fixture.detectChanges();
});
- it('should show the journal title span', () => {
- const journalTitleField = fixture.debugElement.query(By.css('span.item-list-journal-volumes'));
- expect(journalTitleField).not.toBeNull();
- });
- });
-
- describe('When the item has no journal title', () => {
- beforeEach(() => {
- journalVolumeListElementComponent.item = mockItemWithoutMetadata;
- fixture.detectChanges();
- });
-
- it('should not show the journal title span', () => {
- const journalTitleField = fixture.debugElement.query(By.css('span.item-list-journal-volumes'));
- expect(journalTitleField).toBeNull();
- });
- });
-
- describe('When the item has a journal identifier', () => {
- beforeEach(() => {
- journalVolumeListElementComponent.item = mockItemWithMetadata;
- fixture.detectChanges();
- });
-
- it('should show the journal identifiers span', () => {
- const journalIdentifierField = fixture.debugElement.query(By.css('span.item-list-journal-volume-identifiers'));
- expect(journalIdentifierField).not.toBeNull();
- });
- });
-
- describe('When the item has no journal identifier', () => {
- beforeEach(() => {
- journalVolumeListElementComponent.item = mockItemWithoutMetadata;
- fixture.detectChanges();
- });
-
- it('should not show the journal identifiers span', () => {
- const journalIdentifierField = fixture.debugElement.query(By.css('span.item-list-journal-volume-identifiers'));
- expect(journalIdentifierField).toBeNull();
+ it(`should contain a JournalVolumeListElementComponent`, () => {
+ const journalVolumeListElement = fixture.debugElement.query(By.css(`ds-journal-volume-search-result-list-element`));
+ expect(journalVolumeListElement).not.toBeNull();
});
});
});
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/journal-volume/journal-volume-list-element.component.ts b/src/app/entity-groups/journal-entities/item-list-elements/journal-volume/journal-volume-list-element.component.ts
index 450c080c90..5a5bfde49e 100644
--- a/src/app/entity-groups/journal-entities/item-list-elements/journal-volume/journal-volume-list-element.component.ts
+++ b/src/app/entity-groups/journal-entities/item-list-elements/journal-volume/journal-volume-list-element.component.ts
@@ -1,8 +1,10 @@
import { Component } from '@angular/core';
-import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
-import { TypedItemSearchResultListElementComponent } from '../../../../shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component';
+import { AbstractListableElementComponent } from '../../../../shared/object-collection/shared/object-collection-element/abstract-listable-element.component';
+import { Item } from '../../../../core/shared/item.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
-@rendersItemType('JournalVolume', ItemViewMode.Summary)
+@listableObjectComponent('JournalVolume', ViewMode.ListElement)
@Component({
selector: 'ds-journal-volume-list-element',
styleUrls: ['./journal-volume-list-element.component.scss'],
@@ -11,5 +13,5 @@ import { TypedItemSearchResultListElementComponent } from '../../../../shared/ob
/**
* The component for displaying a list element for an item of the type Journal Volume
*/
-export class JournalVolumeListElementComponent extends TypedItemSearchResultListElementComponent {
+export class JournalVolumeListElementComponent extends AbstractListableElementComponent
- {
}
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/journal/journal-list-element.component.html b/src/app/entity-groups/journal-entities/item-list-elements/journal/journal-list-element.component.html
index 0e46e921bb..3e4dfb0b48 100644
--- a/src/app/entity-groups/journal-entities/item-list-elements/journal/journal-list-element.component.html
+++ b/src/app/entity-groups/journal-entities/item-list-elements/journal/journal-list-element.component.html
@@ -1,15 +1 @@
-
-
-
-
- 0"
- class="item-list-journals">
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/journal/journal-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/journal/journal-list-element.component.spec.ts
index 204672dfe9..6581619787 100644
--- a/src/app/entity-groups/journal-entities/item-list-elements/journal/journal-list-element.component.spec.ts
+++ b/src/app/entity-groups/journal-entities/item-list-elements/journal/journal-list-element.component.spec.ts
@@ -1,17 +1,13 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { async, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
import { JournalListElementComponent } from './journal-list-element.component';
import { of as observableOf } from 'rxjs';
import { Item } from '../../../../core/shared/item.model';
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
-import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
-let journalListElementComponent: JournalListElementComponent;
-let fixture: ComponentFixture;
-
-const mockItemWithMetadata: Item = Object.assign(new Item(), {
+const mockItem: Item = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
@@ -28,28 +24,22 @@ const mockItemWithMetadata: Item = Object.assign(new Item(), {
]
}
});
-const mockItemWithoutMetadata: Item = Object.assign(new Item(), {
- bitstreams: observableOf({}),
- metadata: {
- 'dc.title': [
- {
- language: 'en_US',
- value: 'This is just another title'
- }
- ]
- }
-});
describe('JournalListElementComponent', () => {
+ let comp;
+ let fixture;
+
+ const truncatableServiceStub: any = {
+ isCollapsed: (id: number) => observableOf(true),
+ };
+
beforeEach(async(() => {
TestBed.configureTestingModule({
- declarations: [ JournalListElementComponent , TruncatePipe],
+ declarations: [JournalListElementComponent, TruncatePipe],
providers: [
- { provide: ITEM, useValue: mockItemWithMetadata},
- { provide: TruncatableService, useValue: {} }
+ { provide: TruncatableService, useValue: truncatableServiceStub },
],
-
- schemas: [ NO_ERRORS_SCHEMA ]
+ schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(JournalListElementComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
@@ -57,31 +47,18 @@ describe('JournalListElementComponent', () => {
beforeEach(async(() => {
fixture = TestBed.createComponent(JournalListElementComponent);
- journalListElementComponent = fixture.componentInstance;
-
+ comp = fixture.componentInstance;
}));
- describe('When the item has an issn', () => {
+ describe(`when the journal is rendered`, () => {
beforeEach(() => {
- journalListElementComponent.item = mockItemWithMetadata;
+ comp.object = mockItem;
fixture.detectChanges();
});
- it('should show the journals span', () => {
- const issnField = fixture.debugElement.query(By.css('span.item-list-journals'));
- expect(issnField).not.toBeNull();
- });
- });
-
- describe('When the item has no issn', () => {
- beforeEach(() => {
- journalListElementComponent.item = mockItemWithoutMetadata;
- fixture.detectChanges();
- });
-
- it('should not show the journals span', () => {
- const issnField = fixture.debugElement.query(By.css('span.item-list-journals'));
- expect(issnField).toBeNull();
+ it(`should contain a JournalListElementComponent`, () => {
+ const journalListElement = fixture.debugElement.query(By.css(`ds-journal-search-result-list-element`));
+ expect(journalListElement).not.toBeNull();
});
});
});
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/journal/journal-list-element.component.ts b/src/app/entity-groups/journal-entities/item-list-elements/journal/journal-list-element.component.ts
index b57e350bf3..fa83c3cff4 100644
--- a/src/app/entity-groups/journal-entities/item-list-elements/journal/journal-list-element.component.ts
+++ b/src/app/entity-groups/journal-entities/item-list-elements/journal/journal-list-element.component.ts
@@ -1,8 +1,10 @@
import { Component } from '@angular/core';
-import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
-import { TypedItemSearchResultListElementComponent } from '../../../../shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { AbstractListableElementComponent } from '../../../../shared/object-collection/shared/object-collection-element/abstract-listable-element.component';
+import { Item } from '../../../../core/shared/item.model';
-@rendersItemType('Journal', ItemViewMode.Summary)
+@listableObjectComponent('Journal', ViewMode.ListElement)
@Component({
selector: 'ds-journal-list-element',
styleUrls: ['./journal-list-element.component.scss'],
@@ -11,5 +13,5 @@ import { TypedItemSearchResultListElementComponent } from '../../../../shared/ob
/**
* The component for displaying a list element for an item of the type Journal
*/
-export class JournalListElementComponent extends TypedItemSearchResultListElementComponent {
+export class JournalListElementComponent extends AbstractListableElementComponent
- {
}
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html
new file mode 100644
index 0000000000..38094c5c79
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+ 0"
+ class="item-list-journal-issues">
+
+
+
+ 0"
+ class="item-list-journal-issue-numbers">
+
+ -
+
+
+
+
+
+
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.scss b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts
new file mode 100644
index 0000000000..ab02ce244e
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.spec.ts
@@ -0,0 +1,125 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { By } from '@angular/platform-browser';
+import { of as observableOf } from 'rxjs';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { JournalIssueSearchResultListElementComponent } from './journal-issue-search-result-list-element.component';
+import { Item } from '../../../../../core/shared/item.model';
+import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
+import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
+
+let journalIssueListElementComponent: JournalIssueSearchResultListElementComponent;
+let fixture: ComponentFixture;
+
+const mockItemWithMetadata: ItemSearchResult = Object.assign(
+ new ItemSearchResult(),
+ {
+ indexableObject: Object.assign(new Item(), {
+ bitstreams: observableOf({}),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'publicationvolume.volumeNumber': [
+ {
+ language: 'en_US',
+ value: '1234'
+ }
+ ],
+ 'publicationissue.issueNumber': [
+ {
+ language: 'en_US',
+ value: '5678'
+ }
+ ]
+ }
+ })
+ });
+
+const mockItemWithoutMetadata: ItemSearchResult = Object.assign(
+ new ItemSearchResult(),
+ {
+ indexableObject: Object.assign(new Item(), {
+ bitstreams: observableOf({}),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ]
+ }
+ })
+ });
+
+describe('JournalIssueSearchResultListElementComponent', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [JournalIssueSearchResultListElementComponent, TruncatePipe],
+ providers: [
+ { provide: TruncatableService, useValue: {} }
+ ],
+
+ schemas: [NO_ERRORS_SCHEMA]
+ }).overrideComponent(JournalIssueSearchResultListElementComponent, {
+ set: { changeDetection: ChangeDetectionStrategy.Default }
+ }).compileComponents();
+ }));
+
+ beforeEach(async(() => {
+ fixture = TestBed.createComponent(JournalIssueSearchResultListElementComponent);
+ journalIssueListElementComponent = fixture.componentInstance;
+
+ }));
+
+ describe('When the item has a journal identifier', () => {
+ beforeEach(() => {
+ journalIssueListElementComponent.object = mockItemWithMetadata;
+ fixture.detectChanges();
+ });
+
+ it('should show the journal issues span', () => {
+ const journalIdentifierField = fixture.debugElement.query(By.css('span.item-list-journal-issues'));
+ expect(journalIdentifierField).not.toBeNull();
+ });
+ });
+
+ describe('When the item has no journal identifier', () => {
+ beforeEach(() => {
+ journalIssueListElementComponent.object = mockItemWithoutMetadata;
+ fixture.detectChanges();
+ });
+
+ it('should not show the journal issues span', () => {
+ const journalIdentifierField = fixture.debugElement.query(By.css('span.item-list-journal-issues'));
+ expect(journalIdentifierField).toBeNull();
+ });
+ });
+
+ describe('When the item has a journal number', () => {
+ beforeEach(() => {
+ journalIssueListElementComponent.object = mockItemWithMetadata;
+ fixture.detectChanges();
+ });
+
+ it('should show the journal issue numbers span', () => {
+ const journalNumberField = fixture.debugElement.query(By.css('span.item-list-journal-issue-numbers'));
+ expect(journalNumberField).not.toBeNull();
+ });
+ });
+
+ describe('When the item has no journal number', () => {
+ beforeEach(() => {
+ journalIssueListElementComponent.object = mockItemWithoutMetadata;
+ fixture.detectChanges();
+ });
+
+ it('should not show the journal issue numbers span', () => {
+ const journalNumberField = fixture.debugElement.query(By.css('span.item-list-journal-issue-numbers'));
+ expect(journalNumberField).toBeNull();
+ });
+ });
+});
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.ts
new file mode 100644
index 0000000000..1d320cbfe8
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component.ts
@@ -0,0 +1,18 @@
+import { Component } from '@angular/core';
+import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { ViewMode } from '../../../../../core/shared/view-mode.model';
+import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+
+@listableObjectComponent('JournalIssueSearchResult', ViewMode.ListElement)
+@Component({
+ selector: 'ds-journal-issue-search-result-list-element',
+ styleUrls: ['./journal-issue-search-result-list-element.component.scss'],
+ templateUrl: './journal-issue-search-result-list-element.component.html'
+})
+/**
+ * The component for displaying a list element for an item search result of the type Journal Issue
+ */
+export class JournalIssueSearchResultListElementComponent extends SearchResultListElementComponent {
+}
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.html b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.html
new file mode 100644
index 0000000000..460c4a2187
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+ 0"
+ class="item-list-journal-volumes">
+
+
+
+
+ 0"
+ class="item-list-journal-volume-identifiers">
+
+ ()
+
+
+
+
+
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.scss b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts
new file mode 100644
index 0000000000..4ac4e51439
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.spec.ts
@@ -0,0 +1,124 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { By } from '@angular/platform-browser';
+import { of as observableOf } from 'rxjs';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+import { JournalVolumeSearchResultListElementComponent } from './journal-volume-search-result-list-element.component';
+import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
+import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
+
+let journalVolumeListElementComponent: JournalVolumeSearchResultListElementComponent;
+let fixture: ComponentFixture;
+
+const mockItemWithMetadata: ItemSearchResult = Object.assign(
+ new ItemSearchResult(),
+ {
+ indexableObject: Object.assign(new Item(), {
+ bitstreams: observableOf({}),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'journal.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another journal title'
+ }
+ ],
+ 'publicationvolume.volumeNumber': [
+ {
+ language: 'en_US',
+ value: '1234'
+ }
+ ]
+ }
+ })
+ });
+const mockItemWithoutMetadata: ItemSearchResult = Object.assign(
+ new ItemSearchResult(),
+ {
+ indexableObject: Object.assign(new Item(), {
+ bitstreams: observableOf({}),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ]
+ }
+ })
+ });
+
+describe('JournalVolumeSearchResultListElementComponent', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [JournalVolumeSearchResultListElementComponent, TruncatePipe],
+ providers: [
+ { provide: TruncatableService, useValue: {} }
+ ],
+
+ schemas: [NO_ERRORS_SCHEMA]
+ }).overrideComponent(JournalVolumeSearchResultListElementComponent, {
+ set: { changeDetection: ChangeDetectionStrategy.Default }
+ }).compileComponents();
+ }));
+
+ beforeEach(async(() => {
+ fixture = TestBed.createComponent(JournalVolumeSearchResultListElementComponent);
+ journalVolumeListElementComponent = fixture.componentInstance;
+
+ }));
+
+ describe('When the item has a journal title', () => {
+ beforeEach(() => {
+ journalVolumeListElementComponent.object = mockItemWithMetadata;
+ fixture.detectChanges();
+ });
+
+ it('should show the journal title span', () => {
+ const journalTitleField = fixture.debugElement.query(By.css('span.item-list-journal-volumes'));
+ expect(journalTitleField).not.toBeNull();
+ });
+ });
+
+ describe('When the item has no journal title', () => {
+ beforeEach(() => {
+ journalVolumeListElementComponent.object = mockItemWithoutMetadata;
+ fixture.detectChanges();
+ });
+
+ it('should not show the journal title span', () => {
+ const journalTitleField = fixture.debugElement.query(By.css('span.item-list-journal-volumes'));
+ expect(journalTitleField).toBeNull();
+ });
+ });
+
+ describe('When the item has a journal identifier', () => {
+ beforeEach(() => {
+ journalVolumeListElementComponent.object = mockItemWithMetadata;
+ fixture.detectChanges();
+ });
+
+ it('should show the journal identifiers span', () => {
+ const journalIdentifierField = fixture.debugElement.query(By.css('span.item-list-journal-volume-identifiers'));
+ expect(journalIdentifierField).not.toBeNull();
+ });
+ });
+
+ describe('When the item has no journal identifier', () => {
+ beforeEach(() => {
+ journalVolumeListElementComponent.object = mockItemWithoutMetadata;
+ fixture.detectChanges();
+ });
+
+ it('should not show the journal identifiers span', () => {
+ const journalIdentifierField = fixture.debugElement.query(By.css('span.item-list-journal-volume-identifiers'));
+ expect(journalIdentifierField).toBeNull();
+ });
+ });
+});
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.ts
new file mode 100644
index 0000000000..41795b8022
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component.ts
@@ -0,0 +1,18 @@
+import { Component } from '@angular/core';
+import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { ViewMode } from '../../../../../core/shared/view-mode.model';
+
+@listableObjectComponent('JournalVolumeSearchResult', ViewMode.ListElement)
+@Component({
+ selector: 'ds-journal-volume-search-result-list-element',
+ styleUrls: ['./journal-volume-search-result-list-element.component.scss'],
+ templateUrl: './journal-volume-search-result-list-element.component.html'
+})
+/**
+ * The component for displaying a list element for an item search result of the type Journal Volume
+ */
+export class JournalVolumeSearchResultListElementComponent extends SearchResultListElementComponent {
+}
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.html b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.html
new file mode 100644
index 0000000000..a43132d332
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ 0"
+ class="item-list-journals">
+
+
+
+
+
+
+
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.scss b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts
new file mode 100644
index 0000000000..46eeaa1fb2
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.spec.ts
@@ -0,0 +1,96 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { By } from '@angular/platform-browser';
+import { of as observableOf } from 'rxjs';
+import { JournalSearchResultListElementComponent } from './journal-search-result-list-element.component';
+import { Item } from '../../../../../core/shared/item.model';
+import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
+import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+
+let journalListElementComponent: JournalSearchResultListElementComponent;
+let fixture: ComponentFixture;
+
+const mockItemWithMetadata: ItemSearchResult = Object.assign(
+ new ItemSearchResult(),
+ {
+ indexableObject: Object.assign(new Item(), {
+ bitstreams: observableOf({}),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'creativeworkseries.issn': [
+ {
+ language: 'en_US',
+ value: '1234'
+ }
+ ]
+ }
+ })
+ });
+
+const mockItemWithoutMetadata: ItemSearchResult = Object.assign(
+ new ItemSearchResult(),
+ {
+ indexableObject: Object.assign(new Item(), {
+ bitstreams: observableOf({}),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ]
+ }
+ })
+ }
+);
+
+describe('JournalSearchResultListElementComponent', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [JournalSearchResultListElementComponent, TruncatePipe],
+ providers: [
+ { provide: TruncatableService, useValue: {} }
+ ],
+
+ schemas: [NO_ERRORS_SCHEMA]
+ }).overrideComponent(JournalSearchResultListElementComponent, {
+ set: { changeDetection: ChangeDetectionStrategy.Default }
+ }).compileComponents();
+ }));
+
+ beforeEach(async(() => {
+ fixture = TestBed.createComponent(JournalSearchResultListElementComponent);
+ journalListElementComponent = fixture.componentInstance;
+
+ }));
+
+ describe('When the item has an issn', () => {
+ beforeEach(() => {
+ journalListElementComponent.object = mockItemWithMetadata;
+ fixture.detectChanges();
+ });
+
+ it('should show the journals span', () => {
+ const issnField = fixture.debugElement.query(By.css('span.item-list-journals'));
+ expect(issnField).not.toBeNull();
+ });
+ });
+
+ describe('When the item has no issn', () => {
+ beforeEach(() => {
+ journalListElementComponent.object = mockItemWithoutMetadata;
+ fixture.detectChanges();
+ });
+
+ it('should not show the journals span', () => {
+ const issnField = fixture.debugElement.query(By.css('span.item-list-journals'));
+ expect(issnField).toBeNull();
+ });
+ });
+});
diff --git a/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.ts b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.ts
new file mode 100644
index 0000000000..01de0d4626
--- /dev/null
+++ b/src/app/entity-groups/journal-entities/item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component.ts
@@ -0,0 +1,18 @@
+import { Component } from '@angular/core';
+import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { ViewMode } from '../../../../../core/shared/view-mode.model';
+
+@listableObjectComponent('JournalSearchResult', ViewMode.ListElement)
+@Component({
+ selector: 'ds-journal-search-result-list-element',
+ styleUrls: ['./journal-search-result-list-element.component.scss'],
+ templateUrl: './journal-search-result-list-element.component.html'
+})
+/**
+ * The component for displaying a list element for an item search result of the type Journal
+ */
+export class JournalSearchResultListElementComponent extends SearchResultListElementComponent {
+}
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html
index 8db50e78c4..cd982ff17d 100644
--- a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html
+++ b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html
@@ -1,28 +1,28 @@
- {{'journalissue.page.titleprefix' | translate}}
+ {{'journalissue.page.titleprefix' | translate}}
-
+
-
-
-
-
-
@@ -39,16 +39,16 @@
[relationType]="'isPublicationOfJournalIssue'"
[label]="'relationships.isPublicationOfJournalIssue' | translate">
-
-
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.ts b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.ts
index f73d9e75d0..9be8a1f4e9 100644
--- a/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.ts
+++ b/src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.ts
@@ -1,8 +1,9 @@
import { Component } from '@angular/core';
-import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
import { ItemComponent } from '../../../../+item-page/simple/item-types/shared/item.component';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
-@rendersItemType('JournalIssue', ItemViewMode.Detail)
+@listableObjectComponent('JournalIssue', ViewMode.StandalonePage)
@Component({
selector: 'ds-journal-issue',
styleUrls: ['./journal-issue.component.scss'],
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html b/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html
index 150037eccb..6ecc124978 100644
--- a/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html
+++ b/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html
@@ -1,16 +1,16 @@
- {{'journalvolume.page.titleprefix' | translate}}
+ {{'journalvolume.page.titleprefix' | translate}}
-
+
-
-
@@ -26,12 +26,12 @@
[relationType]="'isIssueOfJournalVolume'"
[label]="'relationships.isIssueOf' | translate">
-
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.ts b/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.ts
index d21cb5d94f..ee90dd8f5a 100644
--- a/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.ts
+++ b/src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.ts
@@ -1,8 +1,9 @@
import { Component } from '@angular/core';
-import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
import { ItemComponent } from '../../../../+item-page/simple/item-types/shared/item.component';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
-@rendersItemType('JournalVolume', ItemViewMode.Detail)
+@listableObjectComponent('JournalVolume', ViewMode.StandalonePage)
@Component({
selector: 'ds-journal-volume',
styleUrls: ['./journal-volume.component.scss'],
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html
index d22933a657..1a48f525fa 100644
--- a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html
+++ b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.html
@@ -1,20 +1,20 @@
- {{'journal.page.titleprefix' | translate}}
+ {{'journal.page.titleprefix' | translate}}
-
+
-
-
-
@@ -25,18 +25,18 @@
[relationType]="'isVolumeOfJournal'"
[label]="'relationships.isVolumeOf' | translate">
-
-
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.spec.ts b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.spec.ts
index 28420af381..4fcd2ee4dd 100644
--- a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.spec.ts
+++ b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.spec.ts
@@ -1,19 +1,16 @@
import { ChangeDetectionStrategy, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
-import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
import { ItemDataService } from '../../../../core/data/item-data.service';
import { Item } from '../../../../core/shared/item.model';
import { By } from '@angular/platform-browser';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { MockTranslateLoader } from '../../../../shared/mocks/mock-translate-loader';
-import { RemoteData } from '../../../../core/data/remote-data';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
import { isNotEmpty } from '../../../../shared/empty.util';
import { JournalComponent } from './journal.component';
-import { of as observableOf } from 'rxjs';
import { GenericItemPageFieldComponent } from '../../../../+item-page/simple/field-components/specific-field/generic/generic-item-page-field.component';
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
import { RelationshipService } from '../../../../core/data/relationship.service';
@@ -56,7 +53,6 @@ describe('JournalComponent', () => {
})],
declarations: [JournalComponent, GenericItemPageFieldComponent, TruncatePipe],
providers: [
- {provide: ITEM, useValue: mockItem},
{provide: ItemDataService, useValue: {}},
{provide: TruncatableService, useValue: {}},
{provide: RelationshipService, useValue: {}}
@@ -71,6 +67,7 @@ describe('JournalComponent', () => {
beforeEach(async(() => {
fixture = TestBed.createComponent(JournalComponent);
comp = fixture.componentInstance;
+ comp.object = mockItem;
fixture.detectChanges();
}));
diff --git a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.ts b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.ts
index 67077d5cba..605bd52238 100644
--- a/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.ts
+++ b/src/app/entity-groups/journal-entities/item-pages/journal/journal.component.ts
@@ -1,8 +1,9 @@
import { Component } from '@angular/core';
-import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
import { ItemComponent } from '../../../../+item-page/simple/item-types/shared/item.component';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
-@rendersItemType('Journal', ItemViewMode.Detail)
+@listableObjectComponent('Journal', ViewMode.StandalonePage)
@Component({
selector: 'ds-journal',
styleUrls: ['./journal.component.scss'],
diff --git a/src/app/entity-groups/journal-entities/journal-entities.module.ts b/src/app/entity-groups/journal-entities/journal-entities.module.ts
index 50ec160650..d00eae1e54 100644
--- a/src/app/entity-groups/journal-entities/journal-entities.module.ts
+++ b/src/app/entity-groups/journal-entities/journal-entities.module.ts
@@ -9,6 +9,15 @@ import { JournalListElementComponent } from './item-list-elements/journal/journa
import { JournalIssueListElementComponent } from './item-list-elements/journal-issue/journal-issue-list-element.component';
import { JournalVolumeListElementComponent } from './item-list-elements/journal-volume/journal-volume-list-element.component';
import { TooltipModule } from 'ngx-bootstrap';
+import { JournalIssueGridElementComponent } from './item-grid-elements/journal-issue/journal-issue-grid-element.component';
+import { JournalVolumeGridElementComponent } from './item-grid-elements/journal-volume/journal-volume-grid-element.component';
+import { JournalGridElementComponent } from './item-grid-elements/journal/journal-grid-element.component';
+import { JournalSearchResultListElementComponent } from './item-list-elements/search-result-list-elements/journal/journal-search-result-list-element.component';
+import { JournalSearchResultGridElementComponent } from './item-grid-elements/search-result-grid-elements/journal/journal-search-result-grid-element.component';
+import { JournalIssueSearchResultListElementComponent } from './item-list-elements/search-result-list-elements/journal-issue/journal-issue-search-result-list-element.component';
+import { JournalVolumeSearchResultListElementComponent } from './item-list-elements/search-result-list-elements/journal-volume/journal-volume-search-result-list-element.component';
+import { JournalIssueSearchResultGridElementComponent } from './item-grid-elements/search-result-grid-elements/journal-issue/journal-issue-search-result-grid-element.component';
+import { JournalVolumeSearchResultGridElementComponent } from './item-grid-elements/search-result-grid-elements/journal-volume/journal-volume-search-result-grid-element.component';
const ENTRY_COMPONENTS = [
JournalComponent,
@@ -16,7 +25,16 @@ const ENTRY_COMPONENTS = [
JournalVolumeComponent,
JournalListElementComponent,
JournalIssueListElementComponent,
- JournalVolumeListElementComponent
+ JournalVolumeListElementComponent,
+ JournalIssueGridElementComponent,
+ JournalVolumeGridElementComponent,
+ JournalGridElementComponent,
+ JournalSearchResultListElementComponent,
+ JournalIssueSearchResultListElementComponent,
+ JournalVolumeSearchResultListElementComponent,
+ JournalIssueSearchResultGridElementComponent,
+ JournalVolumeSearchResultGridElementComponent,
+ JournalSearchResultGridElementComponent
];
@NgModule({
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/org-unit/org-unit-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/org-unit/org-unit-grid-element.component.html
new file mode 100644
index 0000000000..e4522398a6
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/org-unit/org-unit-grid-element.component.html
@@ -0,0 +1 @@
+
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/org-unit/org-unit-grid-element.component.scss b/src/app/entity-groups/research-entities/item-grid-elements/org-unit/org-unit-grid-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/org-unit/org-unit-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/org-unit/org-unit-grid-element.component.spec.ts
new file mode 100644
index 0000000000..953cc78535
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/org-unit/org-unit-grid-element.component.spec.ts
@@ -0,0 +1,81 @@
+import { Item } from '../../../../core/shared/item.model';
+import { of as observableOf } from 'rxjs/internal/observable/of';
+import { OrgUnitGridElementComponent } from './org-unit-grid-element.component';
+import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
+import { PaginatedList } from '../../../../core/data/paginated-list';
+import { PageInfo } from '../../../../core/shared/page-info.model';
+import { async, TestBed } from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
+import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { By } from '@angular/platform-browser';
+
+const mockItem = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'organization.foundingDate': [
+ {
+ language: null,
+ value: '2015-06-26'
+ }
+ ],
+ 'organization.address.addressCountry': [
+ {
+ language: 'en_US',
+ value: 'Belgium'
+ }
+ ],
+ 'organization.address.addressLocality': [
+ {
+ language: 'en_US',
+ value: 'Brussels'
+ }
+ ]
+ }
+});
+
+describe('OrgUnitGridElementComponent', () => {
+ let comp;
+ let fixture;
+
+ const truncatableServiceStub: any = {
+ isCollapsed: (id: number) => observableOf(true),
+ };
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [NoopAnimationsModule],
+ declarations: [OrgUnitGridElementComponent, TruncatePipe],
+ providers: [
+ { provide: TruncatableService, useValue: truncatableServiceStub },
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+ }).overrideComponent(OrgUnitGridElementComponent, {
+ set: { changeDetection: ChangeDetectionStrategy.Default }
+ }).compileComponents();
+ }));
+
+ beforeEach(async(() => {
+ fixture = TestBed.createComponent(OrgUnitGridElementComponent);
+ comp = fixture.componentInstance;
+ }));
+
+ describe(`when the org unit is rendered`, () => {
+ beforeEach(() => {
+ comp.object = mockItem;
+ fixture.detectChanges();
+ });
+
+ it(`should contain a OrgUnitGridElementComponent`, () => {
+ const orgUnitGridElement = fixture.debugElement.query(By.css(`ds-org-unit-search-result-grid-element`));
+ expect(orgUnitGridElement).not.toBeNull();
+ });
+ });
+});
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/org-unit/org-unit-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/org-unit/org-unit-grid-element.component.ts
new file mode 100644
index 0000000000..05a7f6c8c5
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/org-unit/org-unit-grid-element.component.ts
@@ -0,0 +1,17 @@
+import { Component } from '@angular/core';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { AbstractListableElementComponent } from '../../../../shared/object-collection/shared/object-collection-element/abstract-listable-element.component';
+import { Item } from '../../../../core/shared/item.model';
+
+@listableObjectComponent('OrgUnit', ViewMode.GridElement)
+@Component({
+ selector: 'ds-org-unit-grid-element',
+ styleUrls: ['./org-unit-grid-element.component.scss'],
+ templateUrl: './org-unit-grid-element.component.html',
+})
+/**
+ * The component for displaying a grid element for an item of the type Organisation Unit
+ */
+export class OrgUnitGridElementComponent extends AbstractListableElementComponent
- {
+}
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html
new file mode 100644
index 0000000000..a431f5979f
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.scss b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts
new file mode 100644
index 0000000000..3edb0fdea7
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.spec.ts
@@ -0,0 +1,75 @@
+import { Item } from '../../../../core/shared/item.model';
+import { of as observableOf } from 'rxjs/internal/observable/of';
+import { PersonGridElementComponent } from './person-grid-element.component';
+import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
+import { PaginatedList } from '../../../../core/data/paginated-list';
+import { PageInfo } from '../../../../core/shared/page-info.model';
+import { async, TestBed } from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
+import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { By } from '@angular/platform-browser';
+
+const mockItem = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'person.email': [
+ {
+ language: 'en_US',
+ value: 'Smith-Donald@gmail.com'
+ }
+ ],
+ 'person.jobTitle': [
+ {
+ language: 'en_US',
+ value: 'Web Developer'
+ }
+ ]
+ }
+});
+
+describe('PersonGridElementComponent', () => {
+ let comp;
+ let fixture;
+
+ const truncatableServiceStub: any = {
+ isCollapsed: (id: number) => observableOf(true),
+ };
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [NoopAnimationsModule],
+ declarations: [PersonGridElementComponent, TruncatePipe],
+ providers: [
+ { provide: TruncatableService, useValue: truncatableServiceStub },
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+ }).overrideComponent(PersonGridElementComponent, {
+ set: { changeDetection: ChangeDetectionStrategy.Default }
+ }).compileComponents();
+ }));
+
+ beforeEach(async(() => {
+ fixture = TestBed.createComponent(PersonGridElementComponent);
+ comp = fixture.componentInstance;
+ }));
+
+ describe(`when the person is rendered`, () => {
+ beforeEach(() => {
+ comp.object = mockItem;
+ fixture.detectChanges();
+ });
+
+ it(`should contain a PersonGridElementComponent`, () => {
+ const personGridElement = fixture.debugElement.query(By.css(`ds-person-search-result-grid-element`));
+ expect(personGridElement).not.toBeNull();
+ });
+ });
+});
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.ts
new file mode 100644
index 0000000000..2e3ce5804e
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/person/person-grid-element.component.ts
@@ -0,0 +1,17 @@
+import { Component } from '@angular/core';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { AbstractListableElementComponent } from '../../../../shared/object-collection/shared/object-collection-element/abstract-listable-element.component';
+import { Item } from '../../../../core/shared/item.model';
+
+@listableObjectComponent('Person', ViewMode.GridElement)
+@Component({
+ selector: 'ds-person-grid-element',
+ styleUrls: ['./person-grid-element.component.scss'],
+ templateUrl: './person-grid-element.component.html',
+})
+/**
+ * The component for displaying a grid element for an item of the type Person
+ */
+export class PersonGridElementComponent extends AbstractListableElementComponent
- {
+}
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html
new file mode 100644
index 0000000000..0c87599399
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.scss b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts
new file mode 100644
index 0000000000..8bd462b0e6
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.spec.ts
@@ -0,0 +1,69 @@
+import { Item } from '../../../../core/shared/item.model';
+import { of as observableOf } from 'rxjs/internal/observable/of';
+import { ProjectGridElementComponent } from './project-grid-element.component';
+import { createSuccessfulRemoteDataObject$ } from '../../../../shared/testing/utils';
+import { PaginatedList } from '../../../../core/data/paginated-list';
+import { PageInfo } from '../../../../core/shared/page-info.model';
+import { async, TestBed } from '@angular/core/testing';
+import { NoopAnimationsModule } from '@angular/platform-browser/animations';
+import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
+import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { By } from '@angular/platform-browser';
+
+const mockItem = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'dc.description': [
+ {
+ language: 'en_US',
+ value: 'The project description'
+ }
+ ]
+ }
+});
+
+describe('ProjectGridElementComponent', () => {
+ let comp;
+ let fixture;
+
+ const truncatableServiceStub: any = {
+ isCollapsed: (id: number) => observableOf(true),
+ };
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [NoopAnimationsModule],
+ declarations: [ProjectGridElementComponent, TruncatePipe],
+ providers: [
+ { provide: TruncatableService, useValue: truncatableServiceStub },
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+ }).overrideComponent(ProjectGridElementComponent, {
+ set: { changeDetection: ChangeDetectionStrategy.Default }
+ }).compileComponents();
+ }));
+
+ beforeEach(async(() => {
+ fixture = TestBed.createComponent(ProjectGridElementComponent);
+ comp = fixture.componentInstance;
+ }));
+
+ describe(`when the project is rendered`, () => {
+ beforeEach(() => {
+ comp.object = mockItem;
+ fixture.detectChanges();
+ });
+
+ it(`should contain a ProjectGridElementComponent`, () => {
+ const projectGridElement = fixture.debugElement.query(By.css(`ds-project-search-result-grid-element`));
+ expect(projectGridElement).not.toBeNull();
+ });
+ });
+});
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.ts
new file mode 100644
index 0000000000..58547960cf
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/project/project-grid-element.component.ts
@@ -0,0 +1,17 @@
+import { Component } from '@angular/core';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { AbstractListableElementComponent } from '../../../../shared/object-collection/shared/object-collection-element/abstract-listable-element.component';
+import { Item } from '../../../../core/shared/item.model';
+
+@listableObjectComponent('Project', ViewMode.GridElement)
+@Component({
+ selector: 'ds-project-grid-element',
+ styleUrls: ['./project-grid-element.component.scss'],
+ templateUrl: './project-grid-element.component.html',
+})
+/**
+ * The component for displaying a grid element for an item of the type Project
+ */
+export class ProjectGridElementComponent extends AbstractListableElementComponent
- {
+}
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.html
new file mode 100644
index 0000000000..5c42be2b24
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{firstMetadataValue('organization.address.addressCountry')}}
+
+ ,
+ {{firstMetadataValue('organization.address.addressLocality')}}
+
+
+
+
+
+
+
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.scss b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.spec.ts
new file mode 100644
index 0000000000..36324ad627
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.spec.ts
@@ -0,0 +1,55 @@
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/testing/utils';
+import { PaginatedList } from '../../../../../core/data/paginated-list';
+import { PageInfo } from '../../../../../core/shared/page-info.model';
+import { OrgUnitSearchResultGridElementComponent } from './org-unit-search-result-grid-element.component';
+import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
+
+const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithMetadata.hitHighlights = {};
+mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'organization.foundingDate': [
+ {
+ language: null,
+ value: '2015-06-26'
+ }
+ ],
+ 'organization.address.addressCountry': [
+ {
+ language: 'en_US',
+ value: 'Belgium'
+ }
+ ],
+ 'organization.address.addressLocality': [
+ {
+ language: 'en_US',
+ value: 'Brussels'
+ }
+ ]
+ }
+});
+
+const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithoutMetadata.hitHighlights = {};
+mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ]
+ }
+});
+
+describe('OrgUnitSearchResultGridElementComponent', getEntityGridElementTestComponent(OrgUnitSearchResultGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['date', 'country', 'city']));
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.ts
new file mode 100644
index 0000000000..64b4be4a11
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component.ts
@@ -0,0 +1,20 @@
+import { Component } from '@angular/core';
+import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { ViewMode } from '../../../../../core/shared/view-mode.model';
+import { focusShadow } from '../../../../../shared/animations/focus';
+import { SearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/search-result-grid-element.component';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+
+@listableObjectComponent('OrgUnitSearchResult', ViewMode.GridElement)
+@Component({
+ selector: 'ds-org-unit-search-result-grid-element',
+ styleUrls: ['./org-unit-search-result-grid-element.component.scss'],
+ templateUrl: './org-unit-search-result-grid-element.component.html',
+ animations: [focusShadow]
+})
+/**
+ * The component for displaying a grid element for an item search result of the type Organisation Unit
+ */
+export class OrgUnitSearchResultGridElementComponent extends SearchResultGridElementComponent {
+}
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html
new file mode 100644
index 0000000000..b7eed7c8b4
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.html
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.scss b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.spec.ts
new file mode 100644
index 0000000000..05baa7a63f
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.spec.ts
@@ -0,0 +1,49 @@
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/testing/utils';
+import { PaginatedList } from '../../../../../core/data/paginated-list';
+import { PageInfo } from '../../../../../core/shared/page-info.model';
+import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
+import { PersonSearchResultGridElementComponent } from './person-search-result-grid-element.component';
+
+const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithMetadata.hitHighlights = {};
+mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'person.email': [
+ {
+ language: 'en_US',
+ value: 'Smith-Donald@gmail.com'
+ }
+ ],
+ 'person.jobTitle': [
+ {
+ language: 'en_US',
+ value: 'Web Developer'
+ }
+ ]
+ }
+});
+
+const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithoutMetadata.hitHighlights = {};
+mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ]
+ }
+});
+
+describe('PersonSearchResultGridElementComponent', getEntityGridElementTestComponent(PersonSearchResultGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['email', 'jobtitle']));
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.ts
new file mode 100644
index 0000000000..55bc4f5a0d
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component.ts
@@ -0,0 +1,20 @@
+import { Component } from '@angular/core';
+import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { ViewMode } from '../../../../../core/shared/view-mode.model';
+import { focusShadow } from '../../../../../shared/animations/focus';
+import { SearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/search-result-grid-element.component';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+
+@listableObjectComponent('PersonSearchResult', ViewMode.GridElement)
+@Component({
+ selector: 'ds-person-search-result-grid-element',
+ styleUrls: ['./person-search-result-grid-element.component.scss'],
+ templateUrl: './person-search-result-grid-element.component.html',
+ animations: [focusShadow]
+})
+/**
+ * The component for displaying a grid element for an item search result of the type Person
+ */
+export class PersonSearchResultGridElementComponent extends SearchResultGridElementComponent {
+}
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html
new file mode 100644
index 0000000000..f3a0dea81f
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.html
@@ -0,0 +1,31 @@
+
+
+
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.scss b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.spec.ts b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.spec.ts
new file mode 100644
index 0000000000..15e7432b65
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.spec.ts
@@ -0,0 +1,43 @@
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/testing/utils';
+import { PaginatedList } from '../../../../../core/data/paginated-list';
+import { PageInfo } from '../../../../../core/shared/page-info.model';
+import { ProjectSearchResultGridElementComponent } from './project-search-result-grid-element.component';
+import { getEntityGridElementTestComponent } from '../../../../../shared/object-grid/search-result-grid-element/item-search-result/publication/publication-search-result-grid-element.component.spec';
+
+const mockItemWithMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithMetadata.hitHighlights = {};
+mockItemWithMetadata.indexableObject = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'dc.description': [
+ {
+ language: 'en_US',
+ value: 'The project description'
+ }
+ ]
+ }
+});
+
+const mockItemWithoutMetadata: ItemSearchResult = new ItemSearchResult();
+mockItemWithoutMetadata.hitHighlights = {};
+mockItemWithoutMetadata.indexableObject = Object.assign(new Item(), {
+ bitstreams: createSuccessfulRemoteDataObject$(new PaginatedList(new PageInfo(), [])),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ]
+ }
+});
+
+describe('ProjectSearchResultGridElementComponent', getEntityGridElementTestComponent(ProjectSearchResultGridElementComponent, mockItemWithMetadata, mockItemWithoutMetadata, ['description']));
diff --git a/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.ts b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.ts
new file mode 100644
index 0000000000..a352d2dcb0
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component.ts
@@ -0,0 +1,20 @@
+import { Component } from '@angular/core';
+import { SearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/search-result-grid-element.component';
+import { Item } from '../../../../../core/shared/item.model';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { ViewMode } from '../../../../../core/shared/view-mode.model';
+import { focusShadow } from '../../../../../shared/animations/focus';
+
+@listableObjectComponent('ProjectSearchResult', ViewMode.GridElement)
+@Component({
+ selector: 'ds-project-search-result-grid-element',
+ styleUrls: ['./project-search-result-grid-element.component.scss'],
+ templateUrl: './project-search-result-grid-element.component.html',
+ animations: [focusShadow]
+})
+/**
+ * The component for displaying a grid element for an item search result of the type Project
+ */
+export class ProjectSearchResultGridElementComponent extends SearchResultGridElementComponent {
+}
diff --git a/src/app/entity-groups/research-entities/item-list-elements/org-unit/org-unit-list-element.component.html b/src/app/entity-groups/research-entities/item-list-elements/org-unit/org-unit-list-element.component.html
new file mode 100644
index 0000000000..03ef45c7a4
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-list-elements/org-unit/org-unit-list-element.component.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/app/entity-groups/research-entities/item-list-elements/orgunit/orgunit-list-element.component.scss b/src/app/entity-groups/research-entities/item-list-elements/org-unit/org-unit-list-element.component.scss
similarity index 100%
rename from src/app/entity-groups/research-entities/item-list-elements/orgunit/orgunit-list-element.component.scss
rename to src/app/entity-groups/research-entities/item-list-elements/org-unit/org-unit-list-element.component.scss
diff --git a/src/app/entity-groups/research-entities/item-list-elements/org-unit/org-unit-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/org-unit/org-unit-list-element.component.spec.ts
new file mode 100644
index 0000000000..43ed9b0ec5
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-list-elements/org-unit/org-unit-list-element.component.spec.ts
@@ -0,0 +1,64 @@
+import { async, TestBed } from '@angular/core/testing';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { By } from '@angular/platform-browser';
+import { OrgUnitListElementComponent } from './org-unit-list-element.component';
+import { of as observableOf } from 'rxjs';
+import { Item } from '../../../../core/shared/item.model';
+import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
+import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
+
+const mockItem: Item = Object.assign(new Item(), {
+ bitstreams: observableOf({}),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'dc.description': [
+ {
+ language: 'en_US',
+ value: 'A description about the OrgUnit'
+ }
+ ]
+ }
+});
+
+describe('OrgUnitListElementComponent', () => {
+ let comp;
+ let fixture;
+
+ const truncatableServiceStub: any = {
+ isCollapsed: (id: number) => observableOf(true),
+ };
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [OrgUnitListElementComponent, TruncatePipe],
+ providers: [
+ { provide: TruncatableService, useValue: truncatableServiceStub },
+ ],
+ schemas: [NO_ERRORS_SCHEMA]
+ }).overrideComponent(OrgUnitListElementComponent, {
+ set: { changeDetection: ChangeDetectionStrategy.Default }
+ }).compileComponents();
+ }));
+
+ beforeEach(async(() => {
+ fixture = TestBed.createComponent(OrgUnitListElementComponent);
+ comp = fixture.componentInstance;
+ }));
+
+ describe(`when the org unit is rendered`, () => {
+ beforeEach(() => {
+ comp.object = mockItem;
+ fixture.detectChanges();
+ });
+
+ it(`should contain a OrgUnitListElementComponent`, () => {
+ const orgUnitListElement = fixture.debugElement.query(By.css(`ds-org-unit-search-result-list-element`));
+ expect(orgUnitListElement).not.toBeNull();
+ });
+ });
+});
diff --git a/src/app/entity-groups/research-entities/item-list-elements/org-unit/org-unit-list-element.component.ts b/src/app/entity-groups/research-entities/item-list-elements/org-unit/org-unit-list-element.component.ts
new file mode 100644
index 0000000000..32254595aa
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-list-elements/org-unit/org-unit-list-element.component.ts
@@ -0,0 +1,17 @@
+import { Component } from '@angular/core';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { AbstractListableElementComponent } from '../../../../shared/object-collection/shared/object-collection-element/abstract-listable-element.component';
+import { Item } from '../../../../core/shared/item.model';
+
+@listableObjectComponent('OrgUnit', ViewMode.ListElement)
+@Component({
+ selector: 'ds-org-unit-list-element',
+ styleUrls: ['./org-unit-list-element.component.scss'],
+ templateUrl: './org-unit-list-element.component.html'
+})
+/**
+ * The component for displaying a list element for an item of the type Organisation Unit
+ */
+export class OrgUnitListElementComponent extends AbstractListableElementComponent- {
+}
diff --git a/src/app/entity-groups/research-entities/item-list-elements/orgunit/orgunit-list-element.component.html b/src/app/entity-groups/research-entities/item-list-elements/orgunit/orgunit-list-element.component.html
deleted file mode 100644
index 8d312fb7c0..0000000000
--- a/src/app/entity-groups/research-entities/item-list-elements/orgunit/orgunit-list-element.component.html
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
- 0"
- class="item-list-orgunit-description">
-
-
-
-
-
-
-
diff --git a/src/app/entity-groups/research-entities/item-list-elements/orgunit/orgunit-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/orgunit/orgunit-list-element.component.spec.ts
deleted file mode 100644
index dd2b138abb..0000000000
--- a/src/app/entity-groups/research-entities/item-list-elements/orgunit/orgunit-list-element.component.spec.ts
+++ /dev/null
@@ -1,87 +0,0 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
-import { By } from '@angular/platform-browser';
-import { OrgUnitListElementComponent } from './orgunit-list-element.component';
-import { of as observableOf } from 'rxjs';
-import { Item } from '../../../../core/shared/item.model';
-import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
-import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
-import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
-
-let orgUnitListElementComponent: OrgUnitListElementComponent;
-let fixture: ComponentFixture;
-
-const mockItemWithMetadata: Item = Object.assign(new Item(), {
- bitstreams: observableOf({}),
- metadata: {
- 'dc.title': [
- {
- language: 'en_US',
- value: 'This is just another title'
- }
- ],
- 'dc.description': [
- {
- language: 'en_US',
- value: 'A description about the OrgUnit'
- }
- ]
- }
-});
-const mockItemWithoutMetadata: Item = Object.assign(new Item(), {
- bitstreams: observableOf({}),
- metadata: {
- 'dc.title': [
- {
- language: 'en_US',
- value: 'This is just another title'
- }
- ]
- }
-});
-
-describe('OrgUnitListElementComponent', () => {
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- declarations: [ OrgUnitListElementComponent , TruncatePipe],
- providers: [
- { provide: ITEM, useValue: mockItemWithMetadata},
- { provide: TruncatableService, useValue: {} }
- ],
-
- schemas: [ NO_ERRORS_SCHEMA ]
- }).overrideComponent(OrgUnitListElementComponent, {
- set: { changeDetection: ChangeDetectionStrategy.Default }
- }).compileComponents();
- }));
-
- beforeEach(async(() => {
- fixture = TestBed.createComponent(OrgUnitListElementComponent);
- orgUnitListElementComponent = fixture.componentInstance;
-
- }));
-
- describe('When the item has an orgunit description', () => {
- beforeEach(() => {
- orgUnitListElementComponent.item = mockItemWithMetadata;
- fixture.detectChanges();
- });
-
- it('should show the description span', () => {
- const orgunitDescriptionField = fixture.debugElement.query(By.css('span.item-list-orgunit-description'));
- expect(orgunitDescriptionField).not.toBeNull();
- });
- });
-
- describe('When the item has no orgunit description', () => {
- beforeEach(() => {
- orgUnitListElementComponent.item = mockItemWithoutMetadata;
- fixture.detectChanges();
- });
-
- it('should not show the description span', () => {
- const orgunitDescriptionField = fixture.debugElement.query(By.css('span.item-list-orgunit-description'));
- expect(orgunitDescriptionField).toBeNull();
- });
- });
-});
diff --git a/src/app/entity-groups/research-entities/item-list-elements/orgunit/orgunit-list-element.component.ts b/src/app/entity-groups/research-entities/item-list-elements/orgunit/orgunit-list-element.component.ts
deleted file mode 100644
index bec6f7e2f4..0000000000
--- a/src/app/entity-groups/research-entities/item-list-elements/orgunit/orgunit-list-element.component.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Component } from '@angular/core';
-import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
-import { TypedItemSearchResultListElementComponent } from '../../../../shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component';
-
-@rendersItemType('OrgUnit', ItemViewMode.Summary)
-@Component({
- selector: 'ds-orgunit-list-element',
- styleUrls: ['./orgunit-list-element.component.scss'],
- templateUrl: './orgunit-list-element.component.html'
-})
-/**
- * The component for displaying a list element for an item of the type Organisation Unit
- */
-export class OrgUnitListElementComponent extends TypedItemSearchResultListElementComponent {
-}
diff --git a/src/app/entity-groups/research-entities/item-list-elements/orgunit/orgunit-metadata-list-element.component.html b/src/app/entity-groups/research-entities/item-list-elements/orgunit/orgunit-metadata-list-element.component.html
deleted file mode 100644
index ea429e87c6..0000000000
--- a/src/app/entity-groups/research-entities/item-list-elements/orgunit/orgunit-metadata-list-element.component.html
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
- 0"
- class="item-list-job-title">
-
-
-
-
-
-
-
diff --git a/src/app/entity-groups/research-entities/item-list-elements/orgunit/orgunit-metadata-list-element.component.ts b/src/app/entity-groups/research-entities/item-list-elements/orgunit/orgunit-metadata-list-element.component.ts
deleted file mode 100644
index a40e12db29..0000000000
--- a/src/app/entity-groups/research-entities/item-list-elements/orgunit/orgunit-metadata-list-element.component.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Component } from '@angular/core';
-import { MetadataRepresentationType } from '../../../../core/shared/metadata-representation/metadata-representation.model';
-import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
-import { TypedItemSearchResultListElementComponent } from '../../../../shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component';
-
-@rendersItemType('OrgUnit', ItemViewMode.Summary, MetadataRepresentationType.Item)
-@Component({
- selector: 'ds-orgunit-metadata-list-element',
- templateUrl: './orgunit-metadata-list-element.component.html'
-})
-/**
- * The component for displaying a list element for an item of the type OrgUnit
- */
-export class OrgUnitMetadataListElementComponent extends TypedItemSearchResultListElementComponent {
-}
diff --git a/src/app/entity-groups/research-entities/item-list-elements/person/person-list-element.component.html b/src/app/entity-groups/research-entities/item-list-elements/person/person-list-element.component.html
index c88b77083d..dbc3a42a05 100644
--- a/src/app/entity-groups/research-entities/item-list-elements/person/person-list-element.component.html
+++ b/src/app/entity-groups/research-entities/item-list-elements/person/person-list-element.component.html
@@ -1,15 +1 @@
-
-
-
-
- 0"
- class="item-list-job-title">
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/src/app/entity-groups/research-entities/item-list-elements/person/person-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/person/person-list-element.component.spec.ts
index 3b6aeae45b..6366ba5735 100644
--- a/src/app/entity-groups/research-entities/item-list-elements/person/person-list-element.component.spec.ts
+++ b/src/app/entity-groups/research-entities/item-list-elements/person/person-list-element.component.spec.ts
@@ -5,13 +5,9 @@ import { PersonListElementComponent } from './person-list-element.component';
import { of as observableOf } from 'rxjs';
import { Item } from '../../../../core/shared/item.model';
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
-import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
-let personListElementComponent: PersonListElementComponent;
-let fixture: ComponentFixture;
-
-const mockItemWithMetadata: Item = Object.assign(new Item(), {
+const mockItem: Item = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
@@ -28,28 +24,22 @@ const mockItemWithMetadata: Item = Object.assign(new Item(), {
]
}
});
-const mockItemWithoutMetadata: Item = Object.assign(new Item(), {
- bitstreams: observableOf({}),
- metadata: {
- 'dc.title': [
- {
- language: 'en_US',
- value: 'This is just another title'
- }
- ]
- }
-});
describe('PersonListElementComponent', () => {
+ let comp;
+ let fixture;
+
+ const truncatableServiceStub: any = {
+ isCollapsed: (id: number) => observableOf(true),
+ };
+
beforeEach(async(() => {
TestBed.configureTestingModule({
- declarations: [ PersonListElementComponent , TruncatePipe],
+ declarations: [PersonListElementComponent, TruncatePipe],
providers: [
- { provide: ITEM, useValue: mockItemWithMetadata},
- { provide: TruncatableService, useValue: {} }
+ { provide: TruncatableService, useValue: truncatableServiceStub },
],
-
- schemas: [ NO_ERRORS_SCHEMA ]
+ schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(PersonListElementComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
@@ -57,31 +47,18 @@ describe('PersonListElementComponent', () => {
beforeEach(async(() => {
fixture = TestBed.createComponent(PersonListElementComponent);
- personListElementComponent = fixture.componentInstance;
-
+ comp = fixture.componentInstance;
}));
- describe('When the item has a job title', () => {
+ describe(`when the person is rendered`, () => {
beforeEach(() => {
- personListElementComponent.item = mockItemWithMetadata;
+ comp.object = mockItem;
fixture.detectChanges();
});
- it('should show the job title span', () => {
- const jobTitleField = fixture.debugElement.query(By.css('span.item-list-job-title'));
- expect(jobTitleField).not.toBeNull();
- });
- });
-
- describe('When the item has no job title', () => {
- beforeEach(() => {
- personListElementComponent.item = mockItemWithoutMetadata;
- fixture.detectChanges();
- });
-
- it('should not show the job title span', () => {
- const jobTitleField = fixture.debugElement.query(By.css('span.item-list-job-title'));
- expect(jobTitleField).toBeNull();
+ it(`should contain a PersonListElementComponent`, () => {
+ const personListElement = fixture.debugElement.query(By.css(`ds-person-search-result-list-element`));
+ expect(personListElement).not.toBeNull();
});
});
});
diff --git a/src/app/entity-groups/research-entities/item-list-elements/person/person-list-element.component.ts b/src/app/entity-groups/research-entities/item-list-elements/person/person-list-element.component.ts
index 269af97586..f35ed90c58 100644
--- a/src/app/entity-groups/research-entities/item-list-elements/person/person-list-element.component.ts
+++ b/src/app/entity-groups/research-entities/item-list-elements/person/person-list-element.component.ts
@@ -1,8 +1,10 @@
import { Component } from '@angular/core';
-import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
-import { TypedItemSearchResultListElementComponent } from '../../../../shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { AbstractListableElementComponent } from '../../../../shared/object-collection/shared/object-collection-element/abstract-listable-element.component';
+import { Item } from '../../../../core/shared/item.model';
-@rendersItemType('Person', ItemViewMode.Summary)
+@listableObjectComponent('Person', ViewMode.ListElement)
@Component({
selector: 'ds-person-list-element',
styleUrls: ['./person-list-element.component.scss'],
@@ -11,5 +13,5 @@ import { TypedItemSearchResultListElementComponent } from '../../../../shared/ob
/**
* The component for displaying a list element for an item of the type Person
*/
-export class PersonListElementComponent extends TypedItemSearchResultListElementComponent {
+export class PersonListElementComponent extends AbstractListableElementComponent
- {
}
diff --git a/src/app/entity-groups/research-entities/item-list-elements/person/person-metadata-list-element.component.html b/src/app/entity-groups/research-entities/item-list-elements/person/person-metadata-list-element.component.html
deleted file mode 100644
index 1125c2fb9b..0000000000
--- a/src/app/entity-groups/research-entities/item-list-elements/person/person-metadata-list-element.component.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
- 0"
- class="item-list-job-title">
-
-
-
-
-
-
-
-
-
diff --git a/src/app/entity-groups/research-entities/item-list-elements/person/person-metadata-list-element.component.ts b/src/app/entity-groups/research-entities/item-list-elements/person/person-metadata-list-element.component.ts
deleted file mode 100644
index 3845f8a8ae..0000000000
--- a/src/app/entity-groups/research-entities/item-list-elements/person/person-metadata-list-element.component.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Component } from '@angular/core';
-import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
-import { MetadataRepresentationType } from '../../../../core/shared/metadata-representation/metadata-representation.model';
-import { TypedItemSearchResultListElementComponent } from '../../../../shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component';
-
-@rendersItemType('Person', ItemViewMode.Summary, MetadataRepresentationType.Item)
-@Component({
- selector: 'ds-person-metadata-list-element',
- templateUrl: './person-metadata-list-element.component.html'
-})
-/**
- * The component for displaying a list element for an item of the type Person
- */
-export class PersonMetadataListElementComponent extends TypedItemSearchResultListElementComponent {
-}
diff --git a/src/app/entity-groups/research-entities/item-list-elements/project/project-list-element.component.html b/src/app/entity-groups/research-entities/item-list-elements/project/project-list-element.component.html
index 3e979b4e4d..8f74452eaa 100644
--- a/src/app/entity-groups/research-entities/item-list-elements/project/project-list-element.component.html
+++ b/src/app/entity-groups/research-entities/item-list-elements/project/project-list-element.component.html
@@ -1,16 +1 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/src/app/entity-groups/research-entities/item-list-elements/project/project-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/project/project-list-element.component.spec.ts
index 02dc3f6d73..dfa2f2506c 100644
--- a/src/app/entity-groups/research-entities/item-list-elements/project/project-list-element.component.spec.ts
+++ b/src/app/entity-groups/research-entities/item-list-elements/project/project-list-element.component.spec.ts
@@ -1,17 +1,13 @@
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { async, TestBed } from '@angular/core/testing';
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
import { By } from '@angular/platform-browser';
-import { ProjectListElementComponent } from './project-list-element.component';
import { of as observableOf } from 'rxjs';
import { Item } from '../../../../core/shared/item.model';
import { TruncatePipe } from '../../../../shared/utils/truncate.pipe';
-import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
+import { ProjectListElementComponent } from './project-list-element.component';
-let projectListElementComponent: ProjectListElementComponent;
-let fixture: ComponentFixture;
-
-const mockItemWithMetadata: Item = Object.assign(new Item(), {
+const mockItem: Item = Object.assign(new Item(), {
bitstreams: observableOf({}),
metadata: {
'dc.title': [
@@ -28,28 +24,22 @@ const mockItemWithMetadata: Item = Object.assign(new Item(), {
// ]
}
});
-const mockItemWithoutMetadata: Item = Object.assign(new Item(), {
- bitstreams: observableOf({}),
- metadata: {
- 'dc.title': [
- {
- language: 'en_US',
- value: 'This is just another title'
- }
- ]
- }
-});
describe('ProjectListElementComponent', () => {
+ let comp;
+ let fixture;
+
+ const truncatableServiceStub: any = {
+ isCollapsed: (id: number) => observableOf(true),
+ };
+
beforeEach(async(() => {
TestBed.configureTestingModule({
- declarations: [ ProjectListElementComponent , TruncatePipe],
+ declarations: [ProjectListElementComponent, TruncatePipe],
providers: [
- { provide: ITEM, useValue: mockItemWithMetadata},
- { provide: TruncatableService, useValue: {} }
+ { provide: TruncatableService, useValue: truncatableServiceStub },
],
-
- schemas: [ NO_ERRORS_SCHEMA ]
+ schemas: [NO_ERRORS_SCHEMA]
}).overrideComponent(ProjectListElementComponent, {
set: { changeDetection: ChangeDetectionStrategy.Default }
}).compileComponents();
@@ -57,31 +47,18 @@ describe('ProjectListElementComponent', () => {
beforeEach(async(() => {
fixture = TestBed.createComponent(ProjectListElementComponent);
- projectListElementComponent = fixture.componentInstance;
-
+ comp = fixture.componentInstance;
}));
- // describe('When the item has a status', () => {
- // beforeEach(() => {
- // projectListElementComponent.item = mockItemWithMetadata;
- // fixture.detectChanges();
- // });
- //
- // it('should show the status span', () => {
- // const statusField = fixture.debugElement.query(By.css('span.item-list-status'));
- // expect(statusField).not.toBeNull();
- // });
- // });
- //
- // describe('When the item has no status', () => {
- // beforeEach(() => {
- // projectListElementComponent.item = mockItemWithoutMetadata;
- // fixture.detectChanges();
- // });
- //
- // it('should not show the status span', () => {
- // const statusField = fixture.debugElement.query(By.css('span.item-list-status'));
- // expect(statusField).toBeNull();
- // });
- // });
+ describe(`when the project is rendered`, () => {
+ beforeEach(() => {
+ comp.object = mockItem;
+ fixture.detectChanges();
+ });
+
+ it(`should contain a ProjectListElementComponent`, () => {
+ const projectListElement = fixture.debugElement.query(By.css(`ds-project-search-result-list-element`));
+ expect(projectListElement).not.toBeNull();
+ });
+ });
});
diff --git a/src/app/entity-groups/research-entities/item-list-elements/project/project-list-element.component.ts b/src/app/entity-groups/research-entities/item-list-elements/project/project-list-element.component.ts
index 7e561dc650..5f158158d8 100644
--- a/src/app/entity-groups/research-entities/item-list-elements/project/project-list-element.component.ts
+++ b/src/app/entity-groups/research-entities/item-list-elements/project/project-list-element.component.ts
@@ -1,8 +1,10 @@
import { Component } from '@angular/core';
-import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
-import { TypedItemSearchResultListElementComponent } from '../../../../shared/object-list/item-list-element/item-types/typed-item-search-result-list-element.component';
+import { AbstractListableElementComponent } from '../../../../shared/object-collection/shared/object-collection-element/abstract-listable-element.component';
+import { Item } from '../../../../core/shared/item.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
-@rendersItemType('Project', ItemViewMode.Summary)
+@listableObjectComponent('Project', ViewMode.ListElement)
@Component({
selector: 'ds-project-list-element',
styleUrls: ['./project-list-element.component.scss'],
@@ -11,5 +13,5 @@ import { TypedItemSearchResultListElementComponent } from '../../../../shared/ob
/**
* The component for displaying a list element for an item of the type Project
*/
-export class ProjectListElementComponent extends TypedItemSearchResultListElementComponent {
+export class ProjectListElementComponent extends AbstractListableElementComponent
- {
}
diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.html b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.html
new file mode 100644
index 0000000000..f08d0fdc11
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ 0"
+ class="item-list-org-unit-description">
+
+
+
+
+
+
diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.scss b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts
new file mode 100644
index 0000000000..0597c0032d
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.spec.ts
@@ -0,0 +1,94 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { By } from '@angular/platform-browser';
+import { of as observableOf } from 'rxjs';
+import { OrgUnitSearchResultListElementComponent } from './org-unit-search-result-list-element.component';
+import { Item } from '../../../../../core/shared/item.model';
+import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
+import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+
+let orgUnitListElementComponent: OrgUnitSearchResultListElementComponent;
+let fixture: ComponentFixture;
+
+const mockItemWithMetadata: ItemSearchResult = Object.assign(
+ new ItemSearchResult(),
+ {
+ indexableObject: Object.assign(new Item(), {
+ bitstreams: observableOf({}),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'dc.description': [
+ {
+ language: 'en_US',
+ value: 'A description about the OrgUnit'
+ }
+ ]
+ }
+ })
+ });
+const mockItemWithoutMetadata: ItemSearchResult = Object.assign(
+ new ItemSearchResult(),
+ {
+ indexableObject: Object.assign(new Item(), {
+ bitstreams: observableOf({}),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ]
+ }
+ })
+ });
+
+describe('OrgUnitSearchResultListElementComponent', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ OrgUnitSearchResultListElementComponent , TruncatePipe],
+ providers: [
+ { provide: TruncatableService, useValue: {} }
+ ],
+
+ schemas: [ NO_ERRORS_SCHEMA ]
+ }).overrideComponent(OrgUnitSearchResultListElementComponent, {
+ set: { changeDetection: ChangeDetectionStrategy.Default }
+ }).compileComponents();
+ }));
+
+ beforeEach(async(() => {
+ fixture = TestBed.createComponent(OrgUnitSearchResultListElementComponent);
+ orgUnitListElementComponent = fixture.componentInstance;
+
+ }));
+
+ describe('When the item has an org unit description', () => {
+ beforeEach(() => {
+ orgUnitListElementComponent.object = mockItemWithMetadata;
+ fixture.detectChanges();
+ });
+
+ it('should show the description span', () => {
+ const orgUnitDescriptionField = fixture.debugElement.query(By.css('span.item-list-org-unit-description'));
+ expect(orgUnitDescriptionField).not.toBeNull();
+ });
+ });
+
+ describe('When the item has no org unit description', () => {
+ beforeEach(() => {
+ orgUnitListElementComponent.object = mockItemWithoutMetadata;
+ fixture.detectChanges();
+ });
+
+ it('should not show the description span', () => {
+ const orgUnitDescriptionField = fixture.debugElement.query(By.css('span.item-list-org-unit-description'));
+ expect(orgUnitDescriptionField).toBeNull();
+ });
+ });
+});
diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.ts
new file mode 100644
index 0000000000..5b50e5a78c
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component.ts
@@ -0,0 +1,18 @@
+import { Component } from '@angular/core';
+import { ViewMode } from '../../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+
+@listableObjectComponent('OrgUnitSearchResult', ViewMode.ListElement)
+@Component({
+ selector: 'ds-org-unit-search-result-list-element',
+ styleUrls: ['./org-unit-search-result-list-element.component.scss'],
+ templateUrl: './org-unit-search-result-list-element.component.html'
+})
+/**
+ * The component for displaying a list element for an item search result of the type Organisation Unit
+ */
+export class OrgUnitSearchResultListElementComponent extends SearchResultListElementComponent {
+}
diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.html b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.html
new file mode 100644
index 0000000000..b2791c4891
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ 0"
+ class="item-list-job-title">
+
+
+
+
+
+
+
diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.scss b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts
new file mode 100644
index 0000000000..a240d31bec
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.spec.ts
@@ -0,0 +1,94 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { By } from '@angular/platform-browser';
+import { of as observableOf } from 'rxjs';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { PersonSearchResultListElementComponent } from './person-search-result-list-element.component';
+import { Item } from '../../../../../core/shared/item.model';
+import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
+import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
+
+let personListElementComponent: PersonSearchResultListElementComponent;
+let fixture: ComponentFixture;
+
+const mockItemWithMetadata: ItemSearchResult = Object.assign(
+ new ItemSearchResult(),
+ {
+ indexableObject: Object.assign(new Item(), {
+ bitstreams: observableOf({}),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ 'person.jobTitle': [
+ {
+ language: 'en_US',
+ value: 'Developer'
+ }
+ ]
+ }
+ })
+ });
+const mockItemWithoutMetadata: ItemSearchResult = Object.assign(
+ new ItemSearchResult(),
+ {
+ indexableObject: Object.assign(new Item(), {
+ bitstreams: observableOf({}),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ]
+ }
+ })
+ });
+
+describe('PersonSearchResultListElementComponent', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [PersonSearchResultListElementComponent, TruncatePipe],
+ providers: [
+ { provide: TruncatableService, useValue: {} }
+ ],
+
+ schemas: [NO_ERRORS_SCHEMA]
+ }).overrideComponent(PersonSearchResultListElementComponent, {
+ set: { changeDetection: ChangeDetectionStrategy.Default }
+ }).compileComponents();
+ }));
+
+ beforeEach(async(() => {
+ fixture = TestBed.createComponent(PersonSearchResultListElementComponent);
+ personListElementComponent = fixture.componentInstance;
+
+ }));
+
+ describe('When the item has a job title', () => {
+ beforeEach(() => {
+ personListElementComponent.object = mockItemWithMetadata;
+ fixture.detectChanges();
+ });
+
+ it('should show the job title span', () => {
+ const jobTitleField = fixture.debugElement.query(By.css('span.item-list-job-title'));
+ expect(jobTitleField).not.toBeNull();
+ });
+ });
+
+ describe('When the item has no job title', () => {
+ beforeEach(() => {
+ personListElementComponent.object = mockItemWithoutMetadata;
+ fixture.detectChanges();
+ });
+
+ it('should not show the job title span', () => {
+ const jobTitleField = fixture.debugElement.query(By.css('span.item-list-job-title'));
+ expect(jobTitleField).toBeNull();
+ });
+ });
+});
diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.ts
new file mode 100644
index 0000000000..b4b4621261
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/person/person-search-result-list-element.component.ts
@@ -0,0 +1,18 @@
+import { Component } from '@angular/core';
+import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { ViewMode } from '../../../../../core/shared/view-mode.model';
+import { Item } from '../../../../../core/shared/item.model';
+
+@listableObjectComponent('PersonSearchResult', ViewMode.ListElement)
+@Component({
+ selector: 'ds-person-search-result-list-element',
+ styleUrls: ['./person-search-result-list-element.component.scss'],
+ templateUrl: './person-search-result-list-element.component.html'
+})
+/**
+ * The component for displaying a list element for an item search result of the type Person
+ */
+export class PersonSearchResultListElementComponent extends SearchResultListElementComponent {
+}
diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.html b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.html
new file mode 100644
index 0000000000..95bff99e7e
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.scss b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts
new file mode 100644
index 0000000000..128190a88b
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.spec.ts
@@ -0,0 +1,94 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { of as observableOf } from 'rxjs';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { ProjectSearchResultListElementComponent } from './project-search-result-list-element.component';
+import { Item } from '../../../../../core/shared/item.model';
+import { TruncatePipe } from '../../../../../shared/utils/truncate.pipe';
+import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
+
+let projectListElementComponent: ProjectSearchResultListElementComponent;
+let fixture: ComponentFixture;
+
+const mockItemWithMetadata: ItemSearchResult = Object.assign(
+ new ItemSearchResult(),
+ {
+ indexableObject: Object.assign(new Item(), {
+ bitstreams: observableOf({}),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ],
+ // 'project.identifier.status': [
+ // {
+ // language: 'en_US',
+ // value: 'A status about the project'
+ // }
+ // ]
+ }
+ })
+ });
+
+const mockItemWithoutMetadata: ItemSearchResult = Object.assign(
+ new ItemSearchResult(),
+ {
+ indexableObject: Object.assign(new Item(), {
+ bitstreams: observableOf({}),
+ metadata: {
+ 'dc.title': [
+ {
+ language: 'en_US',
+ value: 'This is just another title'
+ }
+ ]
+ }
+ })
+ });
+
+describe('ProjectSearchResultListElementComponent', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ProjectSearchResultListElementComponent, TruncatePipe],
+ providers: [
+ { provide: TruncatableService, useValue: {} }
+ ],
+
+ schemas: [NO_ERRORS_SCHEMA]
+ }).overrideComponent(ProjectSearchResultListElementComponent, {
+ set: { changeDetection: ChangeDetectionStrategy.Default }
+ }).compileComponents();
+ }));
+
+ beforeEach(async(() => {
+ fixture = TestBed.createComponent(ProjectSearchResultListElementComponent);
+ projectListElementComponent = fixture.componentInstance;
+
+ }));
+
+ // describe('When the item has a status', () => {
+ // beforeEach(() => {
+ // projectListElementComponent.item = mockItemWithMetadata;
+ // fixture.detectChanges();
+ // });
+ //
+ // it('should show the status span', () => {
+ // const statusField = fixture.debugElement.query(By.css('span.item-list-status'));
+ // expect(statusField).not.toBeNull();
+ // });
+ // });
+ //
+ // describe('When the item has no status', () => {
+ // beforeEach(() => {
+ // projectListElementComponent.item = mockItemWithoutMetadata;
+ // fixture.detectChanges();
+ // });
+ //
+ // it('should not show the status span', () => {
+ // const statusField = fixture.debugElement.query(By.css('span.item-list-status'));
+ // expect(statusField).toBeNull();
+ // });
+ // });
+});
diff --git a/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.ts b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.ts
new file mode 100644
index 0000000000..faa15add31
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-list-elements/search-result-list-elements/project/project-search-result-list-element.component.ts
@@ -0,0 +1,18 @@
+import { Component } from '@angular/core';
+import { SearchResultListElementComponent } from '../../../../../shared/object-list/search-result-list-element/search-result-list-element.component';
+import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
+import { Item } from '../../../../../core/shared/item.model';
+import { listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+import { ViewMode } from '../../../../../core/shared/view-mode.model';
+
+@listableObjectComponent('ProjectSearchResult', ViewMode.ListElement)
+@Component({
+ selector: 'ds-project-search-result-list-element',
+ styleUrls: ['./project-search-result-list-element.component.scss'],
+ templateUrl: './project-search-result-list-element.component.html'
+})
+/**
+ * The component for displaying a list element for an item search result of the type Project
+ */
+export class ProjectSearchResultListElementComponent extends SearchResultListElementComponent {
+}
diff --git a/src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
similarity index 76%
rename from src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html
rename to src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
index a3d2fedb10..e1bd4a90d6 100644
--- a/src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html
+++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
@@ -1,24 +1,24 @@
- {{'orgunit.page.titleprefix' | translate}}
+ {{'orgunit.page.titleprefix' | translate}}
-
+
-
-
-
-
@@ -39,12 +39,12 @@
[relationType]="'isPublicationOfOrgUnit'"
[label]="'relationships.isPublicationOf' | translate">
-
diff --git a/src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss
similarity index 100%
rename from src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss
rename to src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss
diff --git a/src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.spec.ts b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.spec.ts
similarity index 94%
rename from src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.spec.ts
rename to src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.spec.ts
index a49105b2e3..28b014ddba 100644
--- a/src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.spec.ts
+++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.spec.ts
@@ -2,7 +2,7 @@ import { Item } from '../../../../core/shared/item.model';
import { RemoteData } from '../../../../core/data/remote-data';
import { PaginatedList } from '../../../../core/data/paginated-list';
import { PageInfo } from '../../../../core/shared/page-info.model';
-import { OrgunitComponent } from './orgunit.component';
+import { OrgUnitComponent } from './org-unit.component';
import { of as observableOf } from 'rxjs';
import {
createRelationshipsObservable,
@@ -47,4 +47,4 @@ const mockItem: Item = Object.assign(new Item(), {
relationships: createRelationshipsObservable()
});
-describe('OrgUnitComponent', getItemPageFieldsTest(mockItem, OrgunitComponent));
+describe('OrgUnitComponent', getItemPageFieldsTest(mockItem, OrgUnitComponent));
diff --git a/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.ts b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.ts
new file mode 100644
index 0000000000..6df2d87503
--- /dev/null
+++ b/src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.ts
@@ -0,0 +1,16 @@
+import { Component } from '@angular/core';
+import { ItemComponent } from '../../../../+item-page/simple/item-types/shared/item.component';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
+
+@listableObjectComponent('OrgUnit', ViewMode.StandalonePage)
+@Component({
+ selector: 'ds-org-unit',
+ styleUrls: ['./org-unit.component.scss'],
+ templateUrl: './org-unit.component.html'
+})
+/**
+ * The component for displaying metadata and relations of an item of the type Organisation Unit
+ */
+export class OrgUnitComponent extends ItemComponent {
+}
diff --git a/src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.ts b/src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.ts
deleted file mode 100644
index e69359c9f5..0000000000
--- a/src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { Component } from '@angular/core';
-import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
-import { ItemComponent } from '../../../../+item-page/simple/item-types/shared/item.component';
-
-@rendersItemType('OrgUnit', ItemViewMode.Detail)
-@Component({
- selector: 'ds-orgunit',
- styleUrls: ['./orgunit.component.scss'],
- templateUrl: './orgunit.component.html'
-})
-/**
- * The component for displaying metadata and relations of an item of the type Organisation Unit
- */
-export class OrgunitComponent extends ItemComponent {
-}
diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.html b/src/app/entity-groups/research-entities/item-pages/person/person.component.html
index 3f0ca90368..13522acec9 100644
--- a/src/app/entity-groups/research-entities/item-pages/person/person.component.html
+++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.html
@@ -1,12 +1,12 @@
- {{'person.page.titleprefix' | translate}}
+ {{'person.page.titleprefix' | translate}}
-
+
-
@@ -14,7 +14,7 @@
-
@@ -34,26 +34,26 @@
[relationType]="'isOrgUnitOfPerson'"
[label]="'relationships.isOrgUnitOf' | translate">
-
-
-
-
diff --git a/src/app/entity-groups/research-entities/item-pages/person/person.component.ts b/src/app/entity-groups/research-entities/item-pages/person/person.component.ts
index 6482e50ff1..9972736b95 100644
--- a/src/app/entity-groups/research-entities/item-pages/person/person.component.ts
+++ b/src/app/entity-groups/research-entities/item-pages/person/person.component.ts
@@ -1,8 +1,9 @@
import { Component } from '@angular/core';
-import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
import { ItemComponent } from '../../../../+item-page/simple/item-types/shared/item.component';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
-@rendersItemType('Person', ItemViewMode.Detail)
+@listableObjectComponent('Person', ViewMode.StandalonePage)
@Component({
selector: 'ds-person',
styleUrls: ['./person.component.scss'],
diff --git a/src/app/entity-groups/research-entities/item-pages/project/project.component.html b/src/app/entity-groups/research-entities/item-pages/project/project.component.html
index 9ea0676f23..95b7b03ec7 100644
--- a/src/app/entity-groups/research-entities/item-pages/project/project.component.html
+++ b/src/app/entity-groups/research-entities/item-pages/project/project.component.html
@@ -1,10 +1,10 @@
- {{'project.page.titleprefix' | translate}}
+ {{'project.page.titleprefix' | translate}}
-
+
@@ -16,11 +16,11 @@
[metadataField]="'project.contributor.other'"
[label]="'project.page.contributor' | translate">
-
-
@@ -45,16 +45,16 @@
[relationType]="'isOrgUnitOfProject'"
[label]="'relationships.isOrgUnitOf' | translate">
-
-
diff --git a/src/app/entity-groups/research-entities/item-pages/project/project.component.ts b/src/app/entity-groups/research-entities/item-pages/project/project.component.ts
index 8123a28ec9..4e432e869e 100644
--- a/src/app/entity-groups/research-entities/item-pages/project/project.component.ts
+++ b/src/app/entity-groups/research-entities/item-pages/project/project.component.ts
@@ -1,8 +1,9 @@
import { Component } from '@angular/core';
-import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
import { ItemComponent } from '../../../../+item-page/simple/item-types/shared/item.component';
+import { ViewMode } from '../../../../core/shared/view-mode.model';
+import { listableObjectComponent } from '../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
-@rendersItemType('Project', ItemViewMode.Detail)
+@listableObjectComponent('Project', ViewMode.StandalonePage)
@Component({
selector: 'ds-project',
styleUrls: ['./project.component.scss'],
diff --git a/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.html b/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.html
new file mode 100644
index 0000000000..bdeb37dcd8
--- /dev/null
+++ b/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.html
@@ -0,0 +1,13 @@
+
+
+ 0"
+ class="item-list-job-title">
+
+
+
+
+
+
+
diff --git a/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.spec.ts b/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.spec.ts
new file mode 100644
index 0000000000..8616e4fa01
--- /dev/null
+++ b/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.spec.ts
@@ -0,0 +1,47 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { By } from '@angular/platform-browser';
+import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
+import { OrgUnitItemMetadataListElementComponent } from './org-unit-item-metadata-list-element.component';
+import { Item } from '../../../../core/shared/item.model';
+import { TooltipModule } from 'ngx-bootstrap';
+
+const description = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.';
+const organisation = 'Anonymous';
+const mockItem = Object.assign(new Item(), { metadata: { 'dc.description': [{ value: description }], 'organization.legalName': [{ value: organisation }] } });
+const mockItemMetadataRepresentation = Object.assign(new ItemMetadataRepresentation(), mockItem);
+
+describe('OrgUnitItemMetadataListElementComponent', () => {
+ let comp: OrgUnitItemMetadataListElementComponent;
+ let fixture: ComponentFixture
;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [TooltipModule.forRoot()],
+ declarations: [OrgUnitItemMetadataListElementComponent],
+ schemas: [NO_ERRORS_SCHEMA]
+ }).overrideComponent(OrgUnitItemMetadataListElementComponent, {
+ set: { changeDetection: ChangeDetectionStrategy.Default }
+ }).compileComponents();
+ }));
+
+ beforeEach(async(() => {
+ fixture = TestBed.createComponent(OrgUnitItemMetadataListElementComponent);
+ comp = fixture.componentInstance;
+ comp.metadataRepresentation = mockItemMetadataRepresentation;
+ fixture.detectChanges();
+ }));
+
+ it('should show the name of the organisation as a link', () => {
+ const linkText = fixture.debugElement.query(By.css('a')).nativeElement.textContent;
+ expect(linkText).toBe(organisation);
+ });
+
+ it('should show the description on hover over the link in a tooltip', () => {
+ const link = fixture.debugElement.query(By.css('a'));
+ link.triggerEventHandler('mouseover', null);
+ fixture.detectChanges();
+ const tooltip = fixture.debugElement.query(By.css('.item-list-job-title')).nativeElement.textContent;
+ expect(tooltip).toBe(description);
+ });
+});
diff --git a/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.ts b/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.ts
new file mode 100644
index 0000000000..e0bb542241
--- /dev/null
+++ b/src/app/entity-groups/research-entities/metadata-representations/org-unit/org-unit-item-metadata-list-element.component.ts
@@ -0,0 +1,16 @@
+import { Component } from '@angular/core';
+import { metadataRepresentationComponent } from '../../../../shared/metadata-representation/metadata-representation.decorator';
+import { MetadataRepresentationType } from '../../../../core/shared/metadata-representation/metadata-representation.model';
+import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
+
+@metadataRepresentationComponent('OrgUnit', MetadataRepresentationType.Item)
+@Component({
+ selector: 'ds-org-unit-item-metadata-list-element',
+ templateUrl: './org-unit-item-metadata-list-element.component.html'
+})
+/**
+ * The component for displaying an item of the type OrgUnit as a metadata field
+ */
+export class OrgUnitItemMetadataListElementComponent {
+ metadataRepresentation: ItemMetadataRepresentation;
+}
diff --git a/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.html b/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.html
new file mode 100644
index 0000000000..fdc9dd8943
--- /dev/null
+++ b/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.html
@@ -0,0 +1,15 @@
+
+
+ 0"
+ class="item-list-job-title">
+
+
+
+
+
+
+
+
+
diff --git a/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.spec.ts b/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.spec.ts
new file mode 100644
index 0000000000..0d4786f37e
--- /dev/null
+++ b/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.spec.ts
@@ -0,0 +1,48 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
+import { By } from '@angular/platform-browser';
+import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
+import { Item } from '../../../../core/shared/item.model';
+import { PersonItemMetadataListElementComponent } from './person-item-metadata-list-element.component';
+import { TooltipModule } from 'ngx-bootstrap';
+
+const jobTitle ='Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.';
+const firstName = 'Joe';
+const lastName = 'Anonymous';
+const mockItem = Object.assign(new Item(), { metadata: { 'person.jobTitle': [{ value: jobTitle }], 'person.givenName': [{ value: firstName }], 'person.familyName': [{ value: lastName }] } });
+const mockItemMetadataRepresentation = Object.assign(new ItemMetadataRepresentation(), mockItem);
+
+describe('PersonItemMetadataListElementComponent', () => {
+ let comp: PersonItemMetadataListElementComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [TooltipModule.forRoot()],
+ declarations: [PersonItemMetadataListElementComponent],
+ schemas: [NO_ERRORS_SCHEMA]
+ }).overrideComponent(PersonItemMetadataListElementComponent, {
+ set: { changeDetection: ChangeDetectionStrategy.Default }
+ }).compileComponents();
+ }));
+
+ beforeEach(async(() => {
+ fixture = TestBed.createComponent(PersonItemMetadataListElementComponent);
+ comp = fixture.componentInstance;
+ comp.metadataRepresentation = mockItemMetadataRepresentation;
+ fixture.detectChanges();
+ }));
+
+ it('should show the person\'s name as a link', () => {
+ const linkText = fixture.debugElement.query(By.css('a')).nativeElement.textContent;
+ expect(linkText).toBe(lastName + ', ' + firstName);
+ });
+
+ it('should show the description on hover over the link in a tooltip', () => {
+ const link = fixture.debugElement.query(By.css('a'));
+ link.triggerEventHandler('mouseover', null);
+ fixture.detectChanges();
+ const tooltip = fixture.debugElement.query(By.css('.item-list-job-title')).nativeElement.textContent;
+ expect(tooltip).toBe(jobTitle);
+ });
+});
diff --git a/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.ts b/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.ts
new file mode 100644
index 0000000000..e39eb05948
--- /dev/null
+++ b/src/app/entity-groups/research-entities/metadata-representations/person/person-item-metadata-list-element.component.ts
@@ -0,0 +1,16 @@
+import { Component } from '@angular/core';
+import { metadataRepresentationComponent } from '../../../../shared/metadata-representation/metadata-representation.decorator';
+import { MetadataRepresentationType } from '../../../../core/shared/metadata-representation/metadata-representation.model';
+import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
+
+@metadataRepresentationComponent('Person', MetadataRepresentationType.Item)
+@Component({
+ selector: 'ds-person-item-metadata-list-element',
+ templateUrl: './person-item-metadata-list-element.component.html'
+})
+/**
+ * The component for displaying an item of the type Person as a metadata field
+ */
+export class PersonItemMetadataListElementComponent {
+ metadataRepresentation: ItemMetadataRepresentation;
+}
diff --git a/src/app/entity-groups/research-entities/research-entities.module.ts b/src/app/entity-groups/research-entities/research-entities.module.ts
index ba28f174df..8829318f34 100644
--- a/src/app/entity-groups/research-entities/research-entities.module.ts
+++ b/src/app/entity-groups/research-entities/research-entities.module.ts
@@ -2,25 +2,43 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { SharedModule } from '../../shared/shared.module';
import { ItemPageModule } from '../../+item-page/item-page.module';
-import { OrgunitComponent } from './item-pages/orgunit/orgunit.component';
+import { OrgUnitComponent } from './item-pages/org-unit/org-unit.component';
import { PersonComponent } from './item-pages/person/person.component';
import { ProjectComponent } from './item-pages/project/project.component';
-import { OrgUnitListElementComponent } from './item-list-elements/orgunit/orgunit-list-element.component';
-import { OrgUnitMetadataListElementComponent } from './item-list-elements/orgunit/orgunit-metadata-list-element.component';
-import { PersonMetadataListElementComponent } from './item-list-elements/person/person-metadata-list-element.component';
+import { OrgUnitListElementComponent } from './item-list-elements/org-unit/org-unit-list-element.component';
import { PersonListElementComponent } from './item-list-elements/person/person-list-element.component';
import { ProjectListElementComponent } from './item-list-elements/project/project-list-element.component';
import { TooltipModule } from 'ngx-bootstrap';
+import { PersonGridElementComponent } from './item-grid-elements/person/person-grid-element.component';
+import { OrgUnitGridElementComponent } from './item-grid-elements/org-unit/org-unit-grid-element.component';
+import { ProjectGridElementComponent } from './item-grid-elements/project/project-grid-element.component';
+import { OrgUnitSearchResultListElementComponent } from './item-list-elements/search-result-list-elements/org-unit/org-unit-search-result-list-element.component';
+import { PersonSearchResultListElementComponent } from './item-list-elements/search-result-list-elements/person/person-search-result-list-element.component';
+import { ProjectSearchResultListElementComponent } from './item-list-elements/search-result-list-elements/project/project-search-result-list-element.component';
+import { PersonSearchResultGridElementComponent } from './item-grid-elements/search-result-grid-elements/person/person-search-result-grid-element.component';
+import { OrgUnitSearchResultGridElementComponent } from './item-grid-elements/search-result-grid-elements/org-unit/org-unit-search-result-grid-element.component';
+import { ProjectSearchResultGridElementComponent } from './item-grid-elements/search-result-grid-elements/project/project-search-result-grid-element.component';
+import { PersonItemMetadataListElementComponent } from './metadata-representations/person/person-item-metadata-list-element.component';
+import { OrgUnitItemMetadataListElementComponent } from './metadata-representations/org-unit/org-unit-item-metadata-list-element.component';
const ENTRY_COMPONENTS = [
- OrgunitComponent,
+ OrgUnitComponent,
PersonComponent,
ProjectComponent,
OrgUnitListElementComponent,
- OrgUnitMetadataListElementComponent,
+ OrgUnitItemMetadataListElementComponent,
PersonListElementComponent,
- PersonMetadataListElementComponent,
- ProjectListElementComponent
+ PersonItemMetadataListElementComponent,
+ ProjectListElementComponent,
+ PersonGridElementComponent,
+ OrgUnitGridElementComponent,
+ ProjectGridElementComponent,
+ OrgUnitSearchResultListElementComponent,
+ PersonSearchResultListElementComponent,
+ ProjectSearchResultListElementComponent,
+ PersonSearchResultGridElementComponent,
+ OrgUnitSearchResultGridElementComponent,
+ ProjectSearchResultGridElementComponent
];
@NgModule({
diff --git a/src/app/header/header.component.scss b/src/app/header/header.component.scss
index 4d25bd0d43..70c66f119d 100644
--- a/src/app/header/header.component.scss
+++ b/src/app/header/header.component.scss
@@ -8,3 +8,14 @@
background-image: none !important;
line-height: 1.5;
}
+
+.navbar ::ng-deep {
+ a {
+ color: $header-icon-color;
+
+ &:hover, &focus {
+ color: darken($header-icon-color, 15%);
+ }
+ }
+}
+
diff --git a/src/app/pagenotfound/pagenotfound.component.ts b/src/app/pagenotfound/pagenotfound.component.ts
index 6e173b4139..b11de58269 100644
--- a/src/app/pagenotfound/pagenotfound.component.ts
+++ b/src/app/pagenotfound/pagenotfound.component.ts
@@ -1,4 +1,4 @@
-import { ServerResponseService } from '../shared/services/server-response.service';
+import { ServerResponseService } from '../core/services/server-response.service';
import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core';
import { AuthService } from '../core/auth/auth.service';
diff --git a/src/app/shared/auth-nav-menu/auth-nav-menu.component.html b/src/app/shared/auth-nav-menu/auth-nav-menu.component.html
index b560283ad5..4df07880d8 100644
--- a/src/app/shared/auth-nav-menu/auth-nav-menu.component.html
+++ b/src/app/shared/auth-nav-menu/auth-nav-menu.component.html
@@ -3,7 +3,8 @@
diff --git a/src/app/shared/browse-by/browse-by.component.html b/src/app/shared/browse-by/browse-by.component.html
index bad9f3fe8c..f7cd7e0d35 100644
--- a/src/app/shared/browse-by/browse-by.component.html
+++ b/src/app/shared/browse-by/browse-by.component.html
@@ -25,7 +25,7 @@
diff --git a/src/app/shared/chips/models/chips-item.model.ts b/src/app/shared/chips/models/chips-item.model.ts
index 540f94166f..913232fa71 100644
--- a/src/app/shared/chips/models/chips-item.model.ts
+++ b/src/app/shared/chips/models/chips-item.model.ts
@@ -2,6 +2,7 @@ import { isObject, uniqueId } from 'lodash';
import { hasValue, isNotEmpty } from '../../empty.util';
import { FormFieldMetadataValueObject } from '../../form/builder/models/form-field-metadata-value.model';
import { ConfidenceType } from '../../../core/integration/models/confidence-type';
+import { PLACEHOLDER_PARENT_METADATA } from '../../form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model';
export interface ChipsItemIcon {
metadata: string;
@@ -62,7 +63,7 @@ export class ChipsItem {
if (this._item.hasOwnProperty(icon.metadata)
&& (((typeof this._item[icon.metadata] === 'string') && hasValue(this._item[icon.metadata]))
|| (this._item[icon.metadata] as FormFieldMetadataValueObject).hasValue())
- && !(this._item[icon.metadata] as FormFieldMetadataValueObject).hasPlaceholder()) {
+ && !this.hasPlaceholder(this._item[icon.metadata])) {
if ((icon.visibleWhenAuthorityEmpty
|| (this._item[icon.metadata] as FormFieldMetadataValueObject).confidence !== ConfidenceType.CF_UNSET)
&& isNotEmpty(icon.style)) {
@@ -109,4 +110,9 @@ export class ChipsItem {
this.display = value;
}
+
+ private hasPlaceholder(value: any) {
+ return (typeof value === 'string') ? (value === PLACEHOLDER_PARENT_METADATA) :
+ (value as FormFieldMetadataValueObject).hasPlaceholder()
+ }
}
diff --git a/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts
index 6a96892b06..8d1d5c1dca 100644
--- a/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts
+++ b/src/app/shared/comcol-forms/comcol-form/comcol-form.component.ts
@@ -9,6 +9,7 @@ import { DynamicFormControlModel } from '@ng-dynamic-forms/core/src/model/dynami
import { TranslateService } from '@ngx-translate/core';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
import { MetadataMap, MetadataValue } from '../../../core/shared/metadata.models';
+import { ResourceType } from '../../../core/shared/resource-type';
import { isNotEmpty } from '../../empty.util';
import { Community } from '../../../core/shared/community.model';
@@ -29,7 +30,7 @@ export class ComColFormComponent implements OnInit {
/**
* Type of DSpaceObject that the form represents
*/
- protected type;
+ protected type: ResourceType;
/**
* @type {string} Key prefix used to generate form labels
@@ -110,11 +111,11 @@ export class ComColFormComponent implements OnInit {
private updateFieldTranslations() {
this.formModel.forEach(
(fieldModel: DynamicInputModel) => {
- fieldModel.label = this.translate.instant(this.type + this.LABEL_KEY_PREFIX + fieldModel.id);
+ fieldModel.label = this.translate.instant(this.type.value + 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.type + this.ERROR_KEY_PREFIX + fieldModel.id + '.' + key);
+ fieldModel.errorMessages[key] = this.translate.instant(this.type.value + this.ERROR_KEY_PREFIX + fieldModel.id + '.' + key);
});
}
}
diff --git a/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.spec.ts b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.spec.ts
index 08f15ad052..6ad2e5b5e1 100644
--- a/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.spec.ts
+++ b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.spec.ts
@@ -1,6 +1,6 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CommunityDataService } from '../../../core/data/community-data.service';
-import { RouteService } from '../../services/route.service';
+import { RouteService } from '../../../core/services/route.service';
import { Router } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { of as observableOf } from 'rxjs';
diff --git a/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts
index c9fcfecb97..e07f2a5a0a 100644
--- a/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts
+++ b/src/app/shared/comcol-forms/create-comcol-page/create-comcol-page.component.ts
@@ -2,7 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { Community } from '../../../core/shared/community.model';
import { CommunityDataService } from '../../../core/data/community-data.service';
import { Observable } from 'rxjs';
-import { RouteService } from '../../services/route.service';
+import { RouteService } from '../../../core/services/route.service';
import { Router } from '@angular/router';
import { RemoteData } from '../../../core/data/remote-data';
import { isNotEmpty, isNotUndefined } from '../../empty.util';
diff --git a/src/app/shared/dso-selector/dso-selector/dso-selector.component.html b/src/app/shared/dso-selector/dso-selector/dso-selector.component.html
index 662144823d..e2eda4dcfd 100644
--- a/src/app/shared/dso-selector/dso-selector/dso-selector.component.html
+++ b/src/app/shared/dso-selector/dso-selector/dso-selector.component.html
@@ -15,6 +15,6 @@
class="list-group-item list-group-item-action border-0 list-entry"
title="{{ listEntry.indexableObject.name }}"
(click)="onSelect.emit(listEntry.indexableObject)" #listEntryElement>
-
+
diff --git a/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts b/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts
index 6b3f6768af..3c9d399f8b 100644
--- a/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts
+++ b/src/app/shared/dso-selector/dso-selector/dso-selector.component.ts
@@ -19,6 +19,7 @@ import { RemoteData } from '../../../core/data/remote-data';
import { PaginatedList } from '../../../core/data/paginated-list';
import { SearchResult } from '../../search/search-result.model';
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
+import { ViewMode } from '../../../core/shared/view-mode.model';
@Component({
selector: 'ds-dso-selector',
@@ -31,7 +32,10 @@ import { DSpaceObject } from '../../../core/shared/dspace-object.model';
* The user can search the list by using the input field
*/
export class DSOSelectorComponent implements OnInit {
-
+ /**
+ * The view mode of the listed objects
+ */
+ viewMode = ViewMode.ListElement;
/**
* The initially selected DSO's uuid
*/
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html
index a5e02f4a45..ca5d27884e 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html
@@ -14,7 +14,8 @@
-
+
{{ message | translate:model.validators }}
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts
index c9c2b10db2..b8c16c3c7e 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-concat.model.ts
@@ -1,4 +1,7 @@
import { DynamicFormControlLayout, DynamicFormGroupModel, DynamicFormGroupModelConfig, serializable } from '@ng-dynamic-forms/core';
+
+import { Subject } from 'rxjs';
+
import { isNotEmpty } from '../../../../empty.util';
import { DsDynamicInputModel } from './ds-dynamic-input.model';
import { FormFieldMetadataValueObject } from '../../models/form-field-metadata-value.model';
@@ -27,6 +30,7 @@ export class DynamicConcatModel extends DynamicFormGroupModel {
@serializable() repeatable?: boolean;
@serializable() required?: boolean;
isCustomGroup = true;
+ valueUpdates: Subject;
constructor(config: DynamicConcatModelConfig, layout?: DynamicFormControlLayout) {
@@ -37,6 +41,9 @@ export class DynamicConcatModel extends DynamicFormGroupModel {
this.workspaceItem = config.workspaceItem;
this.repeatable = config.repeatable;
this.required = config.required;
+
+ this.valueUpdates = new Subject();
+ this.valueUpdates.subscribe((value: string) => this.value = value);
}
get value() {
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts
index 8f84ea7a77..db68f18c3e 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model.ts
@@ -36,6 +36,8 @@ export class DsDynamicInputModel extends DynamicInputModel {
constructor(config: DsDynamicInputModelConfig, layout?: DynamicFormControlLayout) {
super(config, layout);
this.repeatable = config.repeatable;
+
+ this.hint = config.hint;
this.readOnly = config.readOnly;
this.value = config.value;
this.relationship = config.relationship;
@@ -67,11 +69,7 @@ export class DsDynamicInputModel extends DynamicInputModel {
}
get hasLanguages(): boolean {
- if (this.languageCodes && this.languageCodes.length > 1) {
- return true;
- } else {
- return false;
- }
+ return this.languageCodes && this.languageCodes.length > 1;
}
get language(): string {
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model.ts
index 5f19cb75e2..a2ed83f6c1 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-qualdrop.model.ts
@@ -1,5 +1,5 @@
-import { DynamicFormControlLayout, DynamicFormGroupModel, DynamicInputModelConfig, serializable } from '@ng-dynamic-forms/core';
-import { DsDynamicInputModel, DsDynamicInputModelConfig } from './ds-dynamic-input.model';
+import { DynamicFormControlLayout, DynamicFormGroupModel, serializable } from '@ng-dynamic-forms/core';
+import { DsDynamicInputModel } from './ds-dynamic-input.model';
import { Subject } from 'rxjs';
import { DynamicFormGroupModelConfig } from '@ng-dynamic-forms/core/src/model/form-group/dynamic-form-group.model';
import { LanguageCode } from '../../models/form-field-language-value.model';
@@ -13,6 +13,7 @@ export interface DsDynamicQualdropModelConfig extends DynamicFormGroupModelConfi
language?: string;
readOnly: boolean;
required: boolean;
+ hint?: string;
}
export class DynamicQualdropModel extends DynamicFormGroupModel {
@@ -21,6 +22,7 @@ export class DynamicQualdropModel extends DynamicFormGroupModel {
@serializable() languageUpdates: Subject;
@serializable() hasLanguages = false;
@serializable() readOnly: boolean;
+ @serializable() hint: string;
@serializable() required: boolean;
isCustomGroup = true;
@@ -36,6 +38,8 @@ export class DynamicQualdropModel extends DynamicFormGroupModel {
this.languageUpdates.subscribe((lang: string) => {
this.language = lang;
});
+
+ this.hint = config.hint;
}
get value() {
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.html
index cb2d1fe217..3cfb5980c6 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.html
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/lookup/dynamic-lookup.component.html
@@ -20,11 +20,10 @@
[disabled]="isInputDisabled()"
[placeholder]="model.placeholder | translate"
[readonly]="model.readOnly"
- (change)="$event.preventDefault()"
+ (change)="onChange($event)"
(blur)="onBlurEvent($event); $event.stopPropagation(); sdRef.close();"
(focus)="onFocusEvent($event); $event.stopPropagation(); sdRef.close();"
- (click)="$event.stopPropagation(); $event.stopPropagation(); sdRef.close();"
- (input)="onInput($event)">
+ (click)="$event.stopPropagation(); $event.stopPropagation(); sdRef.close();">
@@ -40,11 +39,10 @@
[disabled]="firstInputValue.length === 0 || isInputDisabled()"
[placeholder]="model.secondPlaceholder | translate"
[readonly]="model.readOnly"
- (change)="$event.preventDefault()"
+ (change)="onChange($event)"
(blur)="onBlurEvent($event); $event.stopPropagation(); sdRef.close();"
(focus)="onFocusEvent($event); $event.stopPropagation(); sdRef.close();"
- (click)="$event.stopPropagation(); sdRef.close();"
- (input)="onInput($event)">
+ (click)="$event.stopPropagation(); sdRef.close();">