mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge branch 'master' into w2p-62063_Configurable-Browse-By
This commit is contained in:
@@ -14,7 +14,7 @@ If you're looking for the 2016 Angular 2 DSpace UI prototype, you can find it [h
|
|||||||
Quick start
|
Quick start
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
**Ensure you're running [Node](https://nodejs.org) >= `v8.0.x`, [npm](https://www.npmjs.com/) >= `v3.x` and [yarn](https://yarnpkg.com) >= `v0.20.x`**
|
**Ensure you're running [Node](https://nodejs.org) >= `v8.0.x`, [npm](https://www.npmjs.com/) >= `v5.x` and [yarn](https://yarnpkg.com) >= `v1.x`**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# clone the repo
|
# clone the repo
|
||||||
@@ -65,7 +65,7 @@ Requirements
|
|||||||
------------
|
------------
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org), [npm](https://www.npmjs.com/), and [yarn](https://yarnpkg.com)
|
- [Node.js](https://nodejs.org), [npm](https://www.npmjs.com/), and [yarn](https://yarnpkg.com)
|
||||||
- Ensure you're running node >= `v5.x`, npm >= `v3.x` and yarn >= `v0.20.x`
|
- Ensure you're running node >= `v8.x`, npm >= `v5.x` and yarn >= `v1.x`
|
||||||
|
|
||||||
If you have [`nvm`](https://github.com/creationix/nvm#install-script) or [`nvm-windows`](https://github.com/coreybutler/nvm-windows) installed, which is highly recommended, you can run `nvm install --lts && nvm use` to install and start using the latest Node LTS.
|
If you have [`nvm`](https://github.com/creationix/nvm#install-script) or [`nvm-windows`](https://github.com/coreybutler/nvm-windows) installed, which is highly recommended, you can run `nvm install --lts && nvm use` to install and start using the latest Node LTS.
|
||||||
|
|
||||||
|
@@ -10,7 +10,7 @@ module.exports = {
|
|||||||
// The REST API server settings.
|
// The REST API server settings.
|
||||||
rest: {
|
rest: {
|
||||||
ssl: true,
|
ssl: true,
|
||||||
host: 'dspace7.4science.it',
|
host: 'dspace7.4science.cloud',
|
||||||
port: 443,
|
port: 443,
|
||||||
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
|
// NOTE: Space is capitalized because 'namespace' is a reserved string in TypeScript
|
||||||
nameSpace: '/dspace-spring-rest/api'
|
nameSpace: '/dspace-spring-rest/api'
|
||||||
|
10
package.json
10
package.json
@@ -23,9 +23,9 @@
|
|||||||
"prebuild": "yarn run clean:dist",
|
"prebuild": "yarn run clean:dist",
|
||||||
"prebuild:aot": "yarn run prebuild",
|
"prebuild:aot": "yarn run prebuild",
|
||||||
"prebuild:prod": "yarn run prebuild",
|
"prebuild:prod": "yarn run prebuild",
|
||||||
"build": "webpack --progress --mode development",
|
"build": "node ./webpack/run-webpack.js --progress --mode development",
|
||||||
"build:aot": "webpack --env.aot --env.server --mode development && webpack --env.aot --env.client --mode development",
|
"build:aot": "node ./webpack/run-webpack.js --env.aot --env.server --mode development && node ./webpack/run-webpack.js --env.aot --env.client --mode development",
|
||||||
"build:prod": "webpack --env.aot --env.server --mode production && webpack --env.aot --env.client --mode production",
|
"build:prod": "node ./webpack/run-webpack.js --env.aot --env.server --mode production && node ./webpack/run-webpack.js --env.aot --env.client --mode production",
|
||||||
"postbuild:prod": "yarn run rollup",
|
"postbuild:prod": "yarn run rollup",
|
||||||
"rollup": "rollup -c rollup.config.js",
|
"rollup": "rollup -c rollup.config.js",
|
||||||
"prestart": "yarn run build:prod",
|
"prestart": "yarn run build:prod",
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
"server": "node dist/server.js",
|
"server": "node dist/server.js",
|
||||||
"server:watch": "nodemon dist/server.js",
|
"server:watch": "nodemon dist/server.js",
|
||||||
"server:watch:debug": "nodemon --debug dist/server.js",
|
"server:watch:debug": "nodemon --debug dist/server.js",
|
||||||
"webpack:watch": "webpack -w --mode development",
|
"webpack:watch": "node ./webpack/run-webpack.js -w --mode development",
|
||||||
"watch": "yarn run build && npm-run-all -p webpack:watch server:watch",
|
"watch": "yarn run build && npm-run-all -p webpack:watch server:watch",
|
||||||
"watch:debug": "yarn run build && npm-run-all -p webpack:watch server:watch:debug",
|
"watch:debug": "yarn run build && npm-run-all -p webpack:watch server:watch:debug",
|
||||||
"predebug": "yarn run build",
|
"predebug": "yarn run build",
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
"jwt-decode": "^2.2.0",
|
"jwt-decode": "^2.2.0",
|
||||||
"methods": "1.1.2",
|
"methods": "1.1.2",
|
||||||
"moment": "^2.22.1",
|
"moment": "^2.22.1",
|
||||||
"morgan": "1.9.0",
|
"morgan": "^1.9.1",
|
||||||
"ng-mocks": "^6.2.1",
|
"ng-mocks": "^6.2.1",
|
||||||
"ng2-file-upload": "1.2.1",
|
"ng2-file-upload": "1.2.1",
|
||||||
"ng2-nouislider": "^1.7.11",
|
"ng2-nouislider": "^1.7.11",
|
||||||
|
@@ -91,12 +91,14 @@
|
|||||||
},
|
},
|
||||||
"item": {
|
"item": {
|
||||||
"page": {
|
"page": {
|
||||||
"author": "Author",
|
"author": "Authors",
|
||||||
"abstract": "Abstract",
|
"abstract": "Abstract",
|
||||||
"date": "Date",
|
"date": "Date",
|
||||||
"uri": "URI",
|
"uri": "URI",
|
||||||
"files": "Files",
|
"files": "Files",
|
||||||
"collections": "Collections",
|
"collections": "Collections",
|
||||||
|
"subject": "Keywords",
|
||||||
|
"citation": "Citation",
|
||||||
"filesection": {
|
"filesection": {
|
||||||
"download": "Download",
|
"download": "Download",
|
||||||
"name": "Name:",
|
"name": "Name:",
|
||||||
@@ -270,6 +272,114 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"relationships": {
|
||||||
|
"isPublicationOf": "Publications",
|
||||||
|
"isProjectOf": "Research Projects",
|
||||||
|
"isOrgUnitOf": "Organizational Units",
|
||||||
|
"isAuthorOf": "Authors",
|
||||||
|
"isPersonOf": "Authors",
|
||||||
|
"isJournalOf": "Journals",
|
||||||
|
"isSingleJournalOf": "Journal",
|
||||||
|
"isVolumeOf": "Journal Volumes",
|
||||||
|
"isSingleVolumeOf": "Journal Volume",
|
||||||
|
"isIssueOf": "Journal Issues",
|
||||||
|
"isJournalIssueOf": "Journal Issue",
|
||||||
|
"isPublicationOfJournalIssue": "Articles"
|
||||||
|
},
|
||||||
|
"person": {
|
||||||
|
"page": {
|
||||||
|
"titleprefix": "Person: ",
|
||||||
|
"jobtitle": "Job Title",
|
||||||
|
"lastname": "Last Name",
|
||||||
|
"firstname": "First Name",
|
||||||
|
"email": "Email Address",
|
||||||
|
"orcid": "ORCID",
|
||||||
|
"birthdate": "Birth Date",
|
||||||
|
"staffid": "Staff ID",
|
||||||
|
"link": {
|
||||||
|
"full": "Show all metadata"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"listelement": {
|
||||||
|
"badge": "Person"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"project": {
|
||||||
|
"page": {
|
||||||
|
"titleprefix": "Research Project: ",
|
||||||
|
"status": "Status",
|
||||||
|
"contributor": "Contributors",
|
||||||
|
"funder": "Funders",
|
||||||
|
"id": "ID",
|
||||||
|
"expectedcompletion": "Expected Completion",
|
||||||
|
"description": "Description",
|
||||||
|
"keyword": "Keywords"
|
||||||
|
},
|
||||||
|
"listelement": {
|
||||||
|
"badge": "Research Project"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"orgunit": {
|
||||||
|
"page": {
|
||||||
|
"titleprefix": "Organizational Unit: ",
|
||||||
|
"dateestablished": "Date established",
|
||||||
|
"city": "City",
|
||||||
|
"country": "Country",
|
||||||
|
"id": "ID",
|
||||||
|
"description": "Description"
|
||||||
|
},
|
||||||
|
"listelement": {
|
||||||
|
"badge": "Organizational Unit"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"journal": {
|
||||||
|
"page": {
|
||||||
|
"titleprefix": "Journal: ",
|
||||||
|
"issn": "ISSN",
|
||||||
|
"publisher": "Publisher",
|
||||||
|
"description": "Description",
|
||||||
|
"editor": "Editor-in-Chief"
|
||||||
|
},
|
||||||
|
"listelement": {
|
||||||
|
"badge": "Journal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"journalvolume": {
|
||||||
|
"page": {
|
||||||
|
"titleprefix": "Journal Volume: ",
|
||||||
|
"volume": "Volume",
|
||||||
|
"issuedate": "Issue Date",
|
||||||
|
"description": "Description"
|
||||||
|
},
|
||||||
|
"listelement": {
|
||||||
|
"badge": "Journal Volume"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"journalissue": {
|
||||||
|
"page": {
|
||||||
|
"titleprefix": "Journal Issue: ",
|
||||||
|
"number": "Number",
|
||||||
|
"issuedate": "Issue Date",
|
||||||
|
"description": "Description",
|
||||||
|
"keyword": "Keywords",
|
||||||
|
"journal-title": "Journal Title",
|
||||||
|
"journal-issn": "Journal ISSN"
|
||||||
|
},
|
||||||
|
"listelement": {
|
||||||
|
"badge": "Journal Issue"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"publication": {
|
||||||
|
"page": {
|
||||||
|
"titleprefix": "Publication: ",
|
||||||
|
"journal-title": "Journal Title",
|
||||||
|
"journal-issn": "Journal ISSN",
|
||||||
|
"volume-title": "Volume Title"
|
||||||
|
},
|
||||||
|
"listelement": {
|
||||||
|
"badge": "Publication"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nav": {
|
"nav": {
|
||||||
"browse": {
|
"browse": {
|
||||||
"header": "All of DSpace"
|
"header": "All of DSpace"
|
||||||
@@ -282,6 +392,7 @@
|
|||||||
},
|
},
|
||||||
"login": "Log In",
|
"login": "Log In",
|
||||||
"logout": "Log Out",
|
"logout": "Log Out",
|
||||||
|
"mydspace": "MyDSpace",
|
||||||
"language": "Language switch",
|
"language": "Language switch",
|
||||||
"search": "Search"
|
"search": "Search"
|
||||||
},
|
},
|
||||||
@@ -318,12 +429,82 @@
|
|||||||
"help": "Select a community to browse its collections."
|
"help": "Select a community to browse its collections."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"mydspace": {
|
||||||
|
"title": "MyDSpace",
|
||||||
|
"description": "",
|
||||||
|
"new-submission": "New submission",
|
||||||
|
"results": {
|
||||||
|
"head": "Your submissions",
|
||||||
|
"no-results": "There were no items to show",
|
||||||
|
"no-title": "No title",
|
||||||
|
"no-authors": "No Authors",
|
||||||
|
"no-date": "No Date",
|
||||||
|
"no-abstract": "No Abstract",
|
||||||
|
"no-files": "No Files",
|
||||||
|
"no-uri": "No Uri",
|
||||||
|
"no-collections": "No Collections"
|
||||||
|
},
|
||||||
|
"messages": {
|
||||||
|
"title": "Messages",
|
||||||
|
"to": "To",
|
||||||
|
"hide-msg": "Hide message",
|
||||||
|
"show-msg": "Show message",
|
||||||
|
"no-messages": "No messages yet.",
|
||||||
|
"no-content": "No content.",
|
||||||
|
"send-btn": "Send",
|
||||||
|
"subject-placeholder": "Subject...",
|
||||||
|
"description-placeholder": "Insert your message here...",
|
||||||
|
"mark-as-read": "Mark as read",
|
||||||
|
"mark-as-unread": "Mark as unread",
|
||||||
|
"submitter-help": "Select this option to send a message to controller.",
|
||||||
|
"controller-help": "Select this option to send a message to item's submitter."
|
||||||
|
},
|
||||||
|
"show": {
|
||||||
|
"workspace": "Your Submissions",
|
||||||
|
"workflow": "All tasks"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"workflow": "Workflow",
|
||||||
|
"validation": "Validation",
|
||||||
|
"waiting-for-controller": "Waiting for controller",
|
||||||
|
"workspace": "Workspace",
|
||||||
|
"archived": "Archived"
|
||||||
|
},
|
||||||
|
"view-btn": "View",
|
||||||
|
"general": {
|
||||||
|
"text-here": "HERE"
|
||||||
|
},
|
||||||
|
"upload": {
|
||||||
|
"upload-successful": "New workspace item created. Click {{here}} for edit it.",
|
||||||
|
"upload-multiple-successful": "{{qty}} new workspace items created.",
|
||||||
|
"upload-failed": "Error creating new workspace. Please verify the content uploaded before retry."
|
||||||
|
}
|
||||||
|
},
|
||||||
"search": {
|
"search": {
|
||||||
|
"journal": {
|
||||||
|
"title": "DSpace Angular :: Journal Search",
|
||||||
|
"results": {
|
||||||
|
"head": "Journal Search Results"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"person": {
|
||||||
|
"title": "DSpace Angular :: Person Search",
|
||||||
|
"results": {
|
||||||
|
"head": "Person Search Results"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"publication": {
|
||||||
|
"title": "DSpace Angular :: Publication Search",
|
||||||
|
"results": {
|
||||||
|
"head": "Publication Search Results"
|
||||||
|
}
|
||||||
|
},
|
||||||
"title": "DSpace Angular :: Search",
|
"title": "DSpace Angular :: Search",
|
||||||
"description": "",
|
"description": "",
|
||||||
"form": {
|
"form": {
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
"search_dspace": "Search DSpace"
|
"search_dspace": "Search DSpace",
|
||||||
|
"search_mydspace": "Search MyDSpace"
|
||||||
},
|
},
|
||||||
"results": {
|
"results": {
|
||||||
"head": "Search Results",
|
"head": "Search Results",
|
||||||
@@ -343,9 +524,13 @@
|
|||||||
"rpp": "Results per page"
|
"rpp": "Results per page"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"switch-configuration": {
|
||||||
|
"title":"Show"
|
||||||
|
},
|
||||||
"view-switch": {
|
"view-switch": {
|
||||||
"show-list": "Show as list",
|
"show-list": "Show as list",
|
||||||
"show-grid": "Show as grid"
|
"show-grid": "Show as grid",
|
||||||
|
"show-detail": "Show detail"
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"head": "Filters",
|
"head": "Filters",
|
||||||
@@ -355,7 +540,12 @@
|
|||||||
"f.dateIssued.min": "Start date",
|
"f.dateIssued.min": "Start date",
|
||||||
"f.dateIssued.max": "End date",
|
"f.dateIssued.max": "End date",
|
||||||
"f.subject": "Subject",
|
"f.subject": "Subject",
|
||||||
"f.has_content_in_original_bundle": "Has files"
|
"f.has_content_in_original_bundle": "Has files",
|
||||||
|
"f.entityType": "Item Type",
|
||||||
|
"f.namedresourcetype": "Status",
|
||||||
|
"f.dateSubmitted": "Date submitted",
|
||||||
|
"f.itemtype": "Type",
|
||||||
|
"f.submitter": "Submitter"
|
||||||
},
|
},
|
||||||
"filter": {
|
"filter": {
|
||||||
"show-more": "Show more",
|
"show-more": "Show more",
|
||||||
@@ -383,6 +573,30 @@
|
|||||||
},
|
},
|
||||||
"has_content_in_original_bundle": {
|
"has_content_in_original_bundle": {
|
||||||
"head": "Has files"
|
"head": "Has files"
|
||||||
|
},
|
||||||
|
"entityType": {
|
||||||
|
"placeholder": "Item Type",
|
||||||
|
"head": "Item Type"
|
||||||
|
},
|
||||||
|
"namedresourcetype": {
|
||||||
|
"placeholder": "Status",
|
||||||
|
"head": "Status"
|
||||||
|
},
|
||||||
|
"dateSubmitted": {
|
||||||
|
"placeholder": "Date submitted",
|
||||||
|
"head": "Date submitted"
|
||||||
|
},
|
||||||
|
"itemtype": {
|
||||||
|
"placeholder": "Type",
|
||||||
|
"head": "Type"
|
||||||
|
},
|
||||||
|
"submitter": {
|
||||||
|
"placeholder": "Submitter",
|
||||||
|
"head": "Submitter"
|
||||||
|
},
|
||||||
|
"objectpeople": {
|
||||||
|
"placeholder": "People",
|
||||||
|
"head": "People"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -603,6 +817,7 @@
|
|||||||
"item": "Loading item...",
|
"item": "Loading item...",
|
||||||
"objects": "Loading...",
|
"objects": "Loading...",
|
||||||
"search-results": "Loading search results...",
|
"search-results": "Loading search results...",
|
||||||
|
"mydspace-results": "Loading items...",
|
||||||
"browse-by": "Loading items...",
|
"browse-by": "Loading items...",
|
||||||
"browse-by-page": "Loading page..."
|
"browse-by-page": "Loading page..."
|
||||||
},
|
},
|
||||||
@@ -794,6 +1009,49 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"workflow": {
|
||||||
|
"generic": {
|
||||||
|
"delete": "Delete",
|
||||||
|
"delete-help": "If you would to discard this item, select \"Delete\". You will then be asked to confirm it.",
|
||||||
|
"edit": "Edit",
|
||||||
|
"edit-help": "Select this option to change the item's metadata.",
|
||||||
|
"view": "View",
|
||||||
|
"view-help": "Select this option to view the item's metadata."
|
||||||
|
},
|
||||||
|
"tasks": {
|
||||||
|
"generic": {
|
||||||
|
"processing": "Processing...",
|
||||||
|
"success": "Operation successful",
|
||||||
|
"error": "Error occurred during operation...",
|
||||||
|
"submitter": "Submitter"
|
||||||
|
},
|
||||||
|
"claimed": {
|
||||||
|
"approve": "Approve",
|
||||||
|
"approve_help": "If you have reviewed the item and it is suitable for inclusion in the collection, select \"Approve\".",
|
||||||
|
"edit": "Edit",
|
||||||
|
"edit_help": "Select this option to change the item's metadata.",
|
||||||
|
"reject": {
|
||||||
|
"submit": "Reject",
|
||||||
|
"reason": {
|
||||||
|
"submit": "Reject item",
|
||||||
|
"title": "Reason",
|
||||||
|
"info": "Please enter your reason for rejecting the submission into the box below, indicating whether the submitter may fix a problem and resubmit.",
|
||||||
|
"placeholder": "Describe the reason of reject"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"reject_help": "If you have reviewed the item and found it is <strong>not</strong> 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.",
|
||||||
|
"return": "Return to pool",
|
||||||
|
"return_help": "Return the task to the pool so that another user may perform the task."
|
||||||
|
|
||||||
|
},
|
||||||
|
"pool": {
|
||||||
|
"claim": "Claim",
|
||||||
|
"claim_help": "Assign this task to yourself.",
|
||||||
|
"show-detail": "Show detail",
|
||||||
|
"hide-detail": "Hide detail"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uploader": {
|
"uploader": {
|
||||||
|
13
resources/images/orgunit-placeholder.svg
Normal file
13
resources/images/orgunit-placeholder.svg
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" focusable="false" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
x="0px" y="0px" width="125.348px" height="161.348px" viewBox="62.326 80.326 125.348 161.348"
|
||||||
|
enable-background="new 62.326 80.326 125.348 161.348" xml:space="preserve">
|
||||||
|
<rect x="62.5" y="80.5" fill="#FFFFFF" stroke="#000000" stroke-width="0.3478" stroke-miterlimit="10" width="125" height="161"/>
|
||||||
|
<path fill="#43515F" d="M135.5,171.5h-21c-2.899,0-5.25,2.352-5.25,5.25v21c0,2.898,2.351,5.25,5.25,5.25h21
|
||||||
|
c2.898,0,5.25-2.352,5.25-5.25v-21C140.75,173.852,138.398,171.5,135.5,171.5z M104,124.25c0-2.899-2.351-5.25-5.25-5.25h-21
|
||||||
|
c-2.899,0-5.25,2.351-5.25,5.25v21c0,2.899,2.351,5.25,5.25,5.25h15.704l12.002,21.007c1.822-3.127,5.171-5.257,9.043-5.257h0.046
|
||||||
|
L104,147.794V140h36.75v-10.5H104V124.25z M172.25,119h-21c-2.898,0-5.25,2.351-5.25,5.25v21c0,2.899,2.352,5.25,5.25,5.25h21
|
||||||
|
c2.898,0,5.25-2.351,5.25-5.25v-21C177.5,121.351,175.148,119,172.25,119z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
13
resources/images/person-placeholder.svg
Normal file
13
resources/images/person-placeholder.svg
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" focusable="false" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
x="0px" y="0px" width="125.348px" height="161.348px" viewBox="62.674 80.674 125.348 161.348"
|
||||||
|
enable-background="new 62.674 80.674 125.348 161.348" xml:space="preserve">
|
||||||
|
<rect x="62.847" y="80.847" fill="#FFFFFF" stroke="#000000" stroke-width="0.3478" stroke-miterlimit="10" width="125" height="161"/>
|
||||||
|
<path fill="#43515F" d="M125.347,167.91c16.304,0,29.531-13.228,29.531-29.531c0-16.303-13.228-29.531-29.531-29.531
|
||||||
|
c-16.303,0-29.531,13.227-29.531,29.531S109.044,167.91,125.347,167.91z M151.597,174.472h-11.301
|
||||||
|
c-4.552,2.092-9.617,3.281-14.95,3.281c-5.332,0-10.377-1.189-14.95-3.281h-11.3c-14.499,0-26.25,11.751-26.25,26.25v3.281
|
||||||
|
c0,5.435,4.409,9.844,9.844,9.844h85.312c5.435,0,9.844-4.408,9.844-9.844v-3.281C177.847,186.223,166.096,174.472,151.597,174.472z
|
||||||
|
"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
118
resources/images/project-placeholder.svg
Normal file
118
resources/images/project-placeholder.svg
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
width="125.5px" height="161.5px" viewBox="62.25 80.25 125.5 161.5" enable-background="new 62.25 80.25 125.5 161.5"
|
||||||
|
xml:space="preserve">
|
||||||
|
<rect x="62.5" y="80.5" fill="#FFFFFF" stroke="#000000" stroke-width="0.5" stroke-miterlimit="10" width="125" height="161"/>
|
||||||
|
<g>
|
||||||
|
<path fill="#43515F" d="M176.007,108.235h-65.078c-0.972,0-1.759,0.788-1.759,1.759v7.035h-7.035c-0.972,0-1.759,0.788-1.759,1.759
|
||||||
|
v9.246c-5.482,3.937-8.794,10.281-8.794,17.136c0,3.284,0.808,6.52,2.278,9.449c-0.313,0.902-0.52,1.855-0.52,2.863
|
||||||
|
c0,0.32,0.062,0.625,0.095,0.936l-9.667,9.666c-0.323-0.029-0.65-0.049-0.981-0.049c-5.82,0-10.553,4.733-10.553,10.553
|
||||||
|
c0,3.115,1.364,5.91,3.517,7.844v25.574c0,0.971,0.787,1.759,1.759,1.759h10.553c0.973,0,1.759-0.788,1.759-1.759v-25.574
|
||||||
|
c2.153-1.932,3.518-4.727,3.518-7.844c0-1.349-0.264-2.635-0.727-3.82l7.762-7.764v45.002c0,0.971,0.786,1.759,1.759,1.759h65.077
|
||||||
|
c0.973,0,1.759-0.787,1.759-1.759v-5.275h7.036c0.972,0,1.759-0.789,1.759-1.76v-94.978
|
||||||
|
C177.765,109.023,176.979,108.235,176.007,108.235z M102.965,130.539c0.044-0.022,0.092-0.037,0.134-0.063l0.193-0.129l3.835,3.834
|
||||||
|
c-4.083,2.049-6.752,6.234-6.752,10.99c0,0.586,0.048,1.169,0.132,1.749c0.029,0.197,0.083,0.387,0.122,0.583
|
||||||
|
c0.074,0.376,0.148,0.752,0.257,1.12c0.014,0.051,0.02,0.104,0.035,0.153c-0.103,0.014-0.201,0.049-0.302,0.067
|
||||||
|
c-0.204,0.035-0.401,0.084-0.6,0.134c-0.3,0.075-0.592,0.167-0.881,0.274c-0.219,0.082-0.437,0.157-0.646,0.253
|
||||||
|
c-0.256,0.118-0.496,0.261-0.739,0.401c-0.371,0.214-0.718,0.454-1.052,0.719c-0.192,0.153-0.393,0.292-0.57,0.461
|
||||||
|
c-0.681-1.891-1.031-3.896-1.031-5.914C95.1,139.241,98.084,133.785,102.965,130.539z M106.321,154.306
|
||||||
|
c0.119,0.157,0.227,0.318,0.329,0.487c0.063,0.107,0.122,0.217,0.177,0.327c0.08,0.156,0.155,0.313,0.218,0.477
|
||||||
|
c0.116,0.304,0.208,0.611,0.268,0.914c0.06,0.317,0.099,0.639,0.099,0.971c0,0.406-0.058,0.803-0.148,1.191
|
||||||
|
c-0.028,0.118-0.072,0.227-0.105,0.341c-0.081,0.265-0.176,0.521-0.297,0.77c-0.06,0.122-0.126,0.242-0.197,0.362
|
||||||
|
c-0.135,0.229-0.291,0.448-0.459,0.653c-0.081,0.099-0.157,0.202-0.243,0.296c-0.266,0.281-0.556,0.544-0.883,0.767
|
||||||
|
c-0.843,0.566-1.855,0.898-2.944,0.898c-0.397,0-0.781-0.052-1.152-0.136c-0.009,0-0.018-0.007-0.026-0.01
|
||||||
|
c-0.115-0.027-0.222-0.069-0.333-0.102c-0.132-0.041-0.266-0.073-0.392-0.121c-0.164-0.064-0.318-0.145-0.473-0.225
|
||||||
|
c-0.103-0.052-0.202-0.105-0.301-0.163c-0.153-0.091-0.305-0.187-0.447-0.292c-0.075-0.056-0.146-0.119-0.218-0.181
|
||||||
|
c-0.259-0.215-0.496-0.45-0.708-0.709c-0.06-0.072-0.123-0.141-0.177-0.216c-0.105-0.143-0.203-0.294-0.294-0.449
|
||||||
|
c-0.059-0.098-0.111-0.198-0.164-0.301c-0.079-0.154-0.158-0.31-0.222-0.473c-0.056-0.146-0.097-0.299-0.141-0.45
|
||||||
|
c-0.026-0.094-0.063-0.181-0.084-0.276c-0.001-0.007-0.005-0.014-0.007-0.021c-0.084-0.369-0.137-0.754-0.137-1.152
|
||||||
|
c0-0.776,0.178-1.507,0.48-2.172c0.005-0.012,0.016-0.021,0.021-0.033c0.193-0.417,0.442-0.793,0.726-1.138
|
||||||
|
c0.049-0.058,0.095-0.119,0.146-0.176c0.127-0.142,0.264-0.274,0.404-0.401c0.077-0.069,0.158-0.134,0.241-0.198
|
||||||
|
c0.141-0.113,0.28-0.226,0.431-0.322c0.214-0.137,0.438-0.261,0.672-0.364c0.118-0.052,0.241-0.091,0.362-0.137
|
||||||
|
c0.178-0.065,0.359-0.124,0.544-0.169c0.107-0.026,0.214-0.052,0.323-0.072c0.303-0.053,0.608-0.093,0.925-0.093
|
||||||
|
c0.365,0,0.739,0.044,1.147,0.135h0.001h0.002c0.533,0.118,1.025,0.329,1.481,0.593c0.063,0.037,0.132,0.065,0.191,0.104
|
||||||
|
c0.179,0.115,0.343,0.248,0.506,0.383c0.078,0.063,0.156,0.122,0.229,0.19c0.149,0.139,0.287,0.29,0.421,0.443
|
||||||
|
C106.186,154.139,106.256,154.22,106.321,154.306z M110.928,157.357c0.58,0.083,1.168,0.125,1.76,0.125
|
||||||
|
c4.754,0,8.938-2.667,10.988-6.752l3.863,3.862c-3.22,5.069-8.797,8.166-14.851,8.166c-1.101,0-2.2-0.107-3.289-0.32
|
||||||
|
c0.009-0.012,0.014-0.026,0.023-0.038c0.007-0.011,0.012-0.024,0.021-0.035c0.06-0.09,0.105-0.19,0.164-0.281
|
||||||
|
c0.091-0.15,0.179-0.3,0.262-0.455c0.102-0.187,0.198-0.377,0.285-0.572c0.049-0.111,0.095-0.224,0.14-0.337
|
||||||
|
c0.052-0.131,0.104-0.263,0.15-0.399c0.088-0.255,0.152-0.517,0.216-0.781c0.032-0.13,0.074-0.253,0.099-0.385
|
||||||
|
c0.014-0.074,0.028-0.146,0.041-0.22c0.081-0.479,0.13-0.962,0.13-1.453C110.929,157.44,110.929,157.398,110.928,157.357z
|
||||||
|
M86.306,210.247h-7.035v-21.722c0.545,0.194,1.11,0.346,1.692,0.447c0.035,0.006,0.069,0.011,0.102,0.016
|
||||||
|
c0.563,0.094,1.136,0.153,1.724,0.153c0.587,0,1.161-0.06,1.722-0.153c0.035-0.006,0.069-0.009,0.103-0.016
|
||||||
|
c0.583-0.102,1.148-0.253,1.693-0.447V210.247z M86.978,184.207c-0.003,0.002-0.007,0.005-0.011,0.006
|
||||||
|
c-0.287,0.215-0.589,0.398-0.898,0.562c-0.039,0.021-0.078,0.044-0.118,0.063c-0.301,0.151-0.608,0.28-0.924,0.387
|
||||||
|
c-0.055,0.018-0.109,0.033-0.163,0.051c-1.353,0.421-2.8,0.421-4.153,0c-0.054-0.018-0.109-0.033-0.164-0.051
|
||||||
|
c-0.315-0.106-0.623-0.234-0.923-0.387c-0.041-0.021-0.079-0.043-0.118-0.063c-0.31-0.163-0.61-0.349-0.898-0.562
|
||||||
|
c-0.003-0.001-0.007-0.004-0.011-0.006c-1.715-1.283-2.844-3.314-2.844-5.619c0-3.879,3.155-7.034,7.036-7.034
|
||||||
|
c0.542,0,1.064,0.075,1.573,0.192c0.012,0.003,0.024,0.01,0.037,0.014c0.48,0.111,0.943,0.275,1.382,0.486
|
||||||
|
c0.004,0.002,0.007,0.004,0.011,0.006c1.319,0.632,2.427,1.678,3.148,3c0.003,0.006,0.009,0.01,0.012,0.015
|
||||||
|
c0.539,0.995,0.873,2.115,0.873,3.322C89.823,180.893,88.694,182.922,86.978,184.207z M90.738,171.67
|
||||||
|
c-0.875-1.003-1.932-1.84-3.124-2.456l7.077-7.079c0.009,0.016,0.024,0.027,0.034,0.042c0.695,1.095,1.62,2.018,2.714,2.714
|
||||||
|
c0.016,0.011,0.027,0.025,0.042,0.033L90.738,171.67z M165.453,210.247h-61.559v-44.148c0.002,0,0.003-0.002,0.005-0.002
|
||||||
|
c0.031-0.007,0.06-0.021,0.092-0.027c0.506-0.11,0.996-0.265,1.465-0.456c0.167-0.068,0.32-0.16,0.481-0.239
|
||||||
|
c0.101-0.05,0.201-0.097,0.299-0.147c2.11,0.684,4.275,1.05,6.451,1.05c7.784,0,14.913-4.267,18.602-11.138
|
||||||
|
c0.367-0.684,0.244-1.526-0.306-2.075l-6.775-6.775c-0.452-0.452-1.111-0.625-1.729-0.447c-0.616,0.178-1.085,0.674-1.228,1.296
|
||||||
|
c-0.921,4.021-4.442,6.827-8.563,6.827c-0.939,0-1.852-0.187-2.737-0.479c-0.07-0.135-0.161-0.257-0.239-0.389
|
||||||
|
c-0.144-0.248-0.283-0.495-0.45-0.725c-0.113-0.155-0.245-0.296-0.366-0.443c-0.348-0.424-0.731-0.811-1.154-1.163
|
||||||
|
c-0.168-0.143-0.331-0.293-0.512-0.422c-0.218-0.157-0.45-0.287-0.683-0.422c-0.218-0.128-0.436-0.251-0.664-0.362
|
||||||
|
c-0.236-0.113-0.476-0.209-0.721-0.301c-0.099-0.036-0.188-0.088-0.289-0.119c-0.05-0.095-0.102-0.188-0.147-0.287
|
||||||
|
c-0.063-0.135-0.123-0.272-0.18-0.41c-0.088-0.216-0.165-0.438-0.235-0.659c-0.042-0.134-0.086-0.268-0.122-0.403
|
||||||
|
c-0.065-0.244-0.113-0.494-0.155-0.744c-0.02-0.118-0.048-0.233-0.063-0.352c-0.049-0.371-0.078-0.742-0.078-1.115
|
||||||
|
c0-4.121,2.807-7.644,6.825-8.563c0.622-0.143,1.12-0.614,1.296-1.228c0.176-0.614,0.007-1.277-0.446-1.729l-6.775-6.775
|
||||||
|
c-0.132-0.132-0.283-0.237-0.443-0.318c-0.053-0.026-0.11-0.037-0.166-0.06c-0.097-0.037-0.19-0.084-0.292-0.104v-5.846h7.036
|
||||||
|
h54.523v89.7H165.453z M174.247,203.212h-5.276v-84.424c0-0.971-0.786-1.759-1.76-1.759h-54.523v-5.277h61.559V203.212
|
||||||
|
L174.247,203.212z"/>
|
||||||
|
<path fill="#43515F" d="M144.347,196.177c0-0.97-0.786-1.759-1.759-1.759h-5.277v-7.035c0-0.971-0.785-1.759-1.759-1.759h-5.276
|
||||||
|
v-12.312c0-0.972-0.786-1.76-1.759-1.76h-7.035c-0.973,0-1.759,0.788-1.759,1.76v8.793h-5.276c-0.973,0-1.759,0.788-1.759,1.76
|
||||||
|
v17.589h-3.518v3.517h38.695v-3.517h-3.518V196.177L144.347,196.177z M116.206,185.624h3.518v15.829h-3.518V185.624z
|
||||||
|
M123.241,183.864v-8.794h3.518v12.312v14.07h-3.518V183.864z M130.276,189.141h3.519v7.036v5.276h-3.519V189.141z
|
||||||
|
M137.312,201.453v-3.518h3.519v3.518H137.312z"/>
|
||||||
|
<path fill="#43515F" d="M161.844,169.476c0.008-0.064,0.021-0.128,0.029-0.193c0.042-0.411,0.062-0.829,0.062-1.247
|
||||||
|
c0-6.789-5.523-12.312-12.312-12.312c-6.787,0-12.312,5.523-12.312,12.312c0,0.418,0.021,0.836,0.063,1.247
|
||||||
|
c0.007,0.065,0.021,0.129,0.028,0.193c0.04,0.349,0.089,0.693,0.158,1.034c0.003,0.016,0.009,0.031,0.012,0.05
|
||||||
|
c0.481,2.325,1.625,4.464,3.346,6.182c0.009,0.009,0.02,0.011,0.029,0.02c2.227,2.215,5.294,3.587,8.675,3.587
|
||||||
|
s6.448-1.372,8.675-3.587c0.01-0.009,0.021-0.011,0.03-0.02c1.719-1.719,2.862-3.856,3.346-6.182
|
||||||
|
c0.003-0.018,0.009-0.034,0.011-0.05C161.754,170.169,161.804,169.824,161.844,169.476z M158.256,169.635
|
||||||
|
c-0.024,0.137-0.06,0.271-0.09,0.406c-0.097,0.414-0.221,0.817-0.376,1.206c-0.038,0.1-0.072,0.202-0.115,0.301
|
||||||
|
c-0.204,0.465-0.446,0.911-0.724,1.329l-2.565-2.566c0.017-0.037,0.023-0.08,0.042-0.119c0.094-0.209,0.159-0.43,0.227-0.654
|
||||||
|
c0.038-0.13,0.092-0.253,0.119-0.387c0.081-0.36,0.127-0.731,0.127-1.115c0-0.332-0.039-0.654-0.099-0.969
|
||||||
|
c-0.018-0.099-0.046-0.191-0.071-0.288c-0.052-0.216-0.116-0.429-0.196-0.634c-0.038-0.1-0.077-0.197-0.123-0.293
|
||||||
|
c-0.095-0.206-0.204-0.404-0.323-0.595c-0.046-0.073-0.084-0.149-0.134-0.22c-0.177-0.254-0.37-0.49-0.586-0.709
|
||||||
|
c-0.036-0.039-0.081-0.069-0.12-0.105c-0.184-0.176-0.38-0.34-0.589-0.488c-0.083-0.057-0.169-0.108-0.255-0.163
|
||||||
|
c-0.182-0.114-0.369-0.216-0.566-0.307c-0.095-0.045-0.188-0.089-0.287-0.129c-0.058-0.023-0.111-0.055-0.17-0.075v-3.641
|
||||||
|
c0.001,0,0.003,0,0.005,0c0.032,0.007,0.06,0.021,0.092,0.028c0.507,0.109,0.995,0.264,1.465,0.456
|
||||||
|
c0.168,0.068,0.32,0.16,0.482,0.239c0.321,0.156,0.64,0.318,0.939,0.51c0.174,0.113,0.334,0.239,0.499,0.365
|
||||||
|
c0.262,0.197,0.517,0.401,0.757,0.624c0.155,0.146,0.302,0.301,0.448,0.459c0.218,0.236,0.422,0.484,0.612,0.742
|
||||||
|
c0.126,0.173,0.251,0.347,0.366,0.53c0.177,0.278,0.33,0.571,0.473,0.868c0.09,0.187,0.186,0.369,0.263,0.563
|
||||||
|
c0.134,0.338,0.235,0.689,0.325,1.047c0.046,0.173,0.105,0.341,0.141,0.521c0.105,0.535,0.17,1.094,0.17,1.665
|
||||||
|
C158.418,168.582,158.353,169.113,158.256,169.635z M148.381,169.279L148.381,169.279c-0.166-0.164-0.291-0.355-0.379-0.562
|
||||||
|
c-0.088-0.21-0.137-0.44-0.137-0.682c0-0.969,0.788-1.758,1.759-1.758s1.759,0.789,1.759,1.758c0,0.241-0.049,0.472-0.137,0.682
|
||||||
|
c-0.088,0.207-0.213,0.398-0.378,0.562l0,0c-0.318,0.318-0.759,0.516-1.243,0.516C149.14,169.795,148.698,169.597,148.381,169.279z
|
||||||
|
M140.998,166.364c0.035-0.179,0.097-0.347,0.141-0.521c0.092-0.355,0.191-0.707,0.325-1.045c0.077-0.193,0.175-0.377,0.265-0.564
|
||||||
|
c0.144-0.297,0.297-0.592,0.47-0.869c0.114-0.18,0.239-0.355,0.368-0.529c0.192-0.259,0.395-0.506,0.612-0.742
|
||||||
|
c0.146-0.156,0.292-0.312,0.448-0.459c0.237-0.223,0.494-0.427,0.757-0.623c0.165-0.123,0.325-0.251,0.499-0.364
|
||||||
|
c0.299-0.192,0.615-0.354,0.938-0.51c0.162-0.079,0.315-0.171,0.482-0.24c0.469-0.193,0.958-0.346,1.465-0.456
|
||||||
|
c0.031-0.007,0.06-0.021,0.092-0.028c0.001,0,0.003,0,0.005,0v3.641c-0.06,0.021-0.112,0.053-0.171,0.076
|
||||||
|
c-0.098,0.038-0.191,0.082-0.286,0.126c-0.198,0.093-0.386,0.193-0.566,0.308c-0.087,0.055-0.173,0.105-0.255,0.164
|
||||||
|
c-0.209,0.147-0.405,0.311-0.59,0.487c-0.038,0.036-0.082,0.067-0.119,0.105c-0.217,0.218-0.409,0.456-0.586,0.709
|
||||||
|
c-0.05,0.071-0.088,0.146-0.134,0.22c-0.12,0.191-0.229,0.388-0.323,0.596c-0.044,0.097-0.084,0.193-0.121,0.292
|
||||||
|
c-0.079,0.206-0.145,0.417-0.198,0.636c-0.022,0.096-0.051,0.189-0.069,0.288c-0.061,0.317-0.099,0.64-0.099,0.972
|
||||||
|
c0,0.385,0.046,0.756,0.123,1.115c0.028,0.134,0.082,0.259,0.121,0.389c0.067,0.222,0.134,0.443,0.227,0.652
|
||||||
|
c0.018,0.039,0.023,0.082,0.043,0.119l-2.567,2.566c-0.277-0.418-0.519-0.863-0.722-1.329c-0.043-0.099-0.075-0.2-0.116-0.301
|
||||||
|
c-0.153-0.389-0.278-0.793-0.377-1.206c-0.031-0.136-0.064-0.27-0.089-0.406c-0.095-0.521-0.16-1.053-0.16-1.6
|
||||||
|
C140.83,167.464,140.893,166.906,140.998,166.364z M144.781,175.364l2.58-2.582c0.689,0.329,1.449,0.53,2.262,0.53
|
||||||
|
s1.572-0.2,2.261-0.53l2.58,2.582c-1.39,0.923-3.052,1.466-4.841,1.466C147.835,176.83,146.173,176.286,144.781,175.364z"/>
|
||||||
|
<path fill="#43515F" d="M156.658,124.064c-2.908,0-5.276,2.367-5.276,5.276c0,0.219,0.039,0.426,0.065,0.639l-8.724,4.362
|
||||||
|
c-0.949-0.913-2.233-1.483-3.653-1.483c-1.921,0-3.588,1.043-4.512,2.581l-7.875-1.576c-0.369-2.55-2.549-4.521-5.201-4.521
|
||||||
|
c-2.909,0-5.276,2.367-5.276,5.276s2.367,5.277,5.276,5.277c1.921,0,3.588-1.043,4.511-2.581l7.876,1.576
|
||||||
|
c0.369,2.55,2.549,4.521,5.201,4.521c2.908,0,5.276-2.367,5.276-5.276c0-0.218-0.038-0.427-0.064-0.64l8.724-4.362
|
||||||
|
c0.949,0.914,2.233,1.484,3.652,1.484c2.909,0,5.277-2.368,5.277-5.277S159.568,124.064,156.658,124.064z M121.482,136.376
|
||||||
|
c-0.971,0-1.759-0.79-1.759-1.759c0-0.969,0.788-1.759,1.759-1.759s1.759,0.79,1.759,1.759S122.453,136.376,121.482,136.376z
|
||||||
|
M139.071,139.894c-0.972,0-1.76-0.79-1.76-1.759c0-0.969,0.788-1.759,1.76-1.759s1.759,0.79,1.759,1.759
|
||||||
|
C140.83,139.104,140.042,139.894,139.071,139.894z M156.658,131.1c-0.971,0-1.758-0.788-1.758-1.759l0,0
|
||||||
|
c0-0.969,0.787-1.759,1.758-1.759s1.76,0.79,1.76,1.759S157.63,131.1,156.658,131.1z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 12 KiB |
@@ -28,6 +28,7 @@ describe('MetadataFieldFormComponent', () => {
|
|||||||
const registryServiceStub = {
|
const registryServiceStub = {
|
||||||
getActiveMetadataField: () => observableOf(undefined),
|
getActiveMetadataField: () => observableOf(undefined),
|
||||||
createOrUpdateMetadataField: (field: MetadataField) => observableOf(field),
|
createOrUpdateMetadataField: (field: MetadataField) => observableOf(field),
|
||||||
|
cancelEditMetadataField: () => {},
|
||||||
cancelEditMetadataSchema: () => {},
|
cancelEditMetadataSchema: () => {},
|
||||||
};
|
};
|
||||||
const formBuilderServiceStub = {
|
const formBuilderServiceStub = {
|
||||||
@@ -62,6 +63,11 @@ describe('MetadataFieldFormComponent', () => {
|
|||||||
registryService = s;
|
registryService = s;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
component = null;
|
||||||
|
registryService = null
|
||||||
|
})
|
||||||
|
|
||||||
describe('when submitting the form', () => {
|
describe('when submitting the form', () => {
|
||||||
const element = 'fakeElement';
|
const element = 'fakeElement';
|
||||||
const qualifier = 'fakeQualifier';
|
const qualifier = 'fakeQualifier';
|
||||||
|
@@ -138,13 +138,18 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
parentID: 'new',
|
parentID: 'new',
|
||||||
active: false,
|
active: false,
|
||||||
visible: true,
|
visible: true,
|
||||||
|
// model: {
|
||||||
|
// type: MenuItemType.ONCLICK,
|
||||||
|
// text: 'menu.section.new_item',
|
||||||
|
// function: () => {
|
||||||
|
// this.modalService.open(CreateItemParentSelectorComponent);
|
||||||
|
// }
|
||||||
|
// } as OnClickMenuItemModel,
|
||||||
model: {
|
model: {
|
||||||
type: MenuItemType.ONCLICK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.new_item',
|
text: 'menu.section.new_item',
|
||||||
function: () => {
|
link: '/submit'
|
||||||
this.modalService.open(CreateItemParentSelectorComponent);
|
} as LinkMenuItemModel,
|
||||||
}
|
|
||||||
} as OnClickMenuItemModel,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'new_item_version',
|
id: 'new_item_version',
|
||||||
@@ -154,7 +159,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.new_item_version',
|
text: 'menu.section.new_item_version',
|
||||||
link: '#'
|
link: ''
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -230,7 +235,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.import_metadata',
|
text: 'menu.section.import_metadata',
|
||||||
link: '#'
|
link: ''
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -241,7 +246,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.import_batch',
|
text: 'menu.section.import_batch',
|
||||||
link: '#'
|
link: ''
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
},
|
},
|
||||||
/* Export */
|
/* Export */
|
||||||
@@ -264,7 +269,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.export_community',
|
text: 'menu.section.export_community',
|
||||||
link: '#'
|
link: ''
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -275,7 +280,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.export_collection',
|
text: 'menu.section.export_collection',
|
||||||
link: '#'
|
link: ''
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -286,7 +291,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.export_item',
|
text: 'menu.section.export_item',
|
||||||
link: '#'
|
link: ''
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
}, {
|
}, {
|
||||||
id: 'export_metadata',
|
id: 'export_metadata',
|
||||||
@@ -296,7 +301,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.export_metadata',
|
text: 'menu.section.export_metadata',
|
||||||
link: '#'
|
link: ''
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -320,7 +325,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.access_control_people',
|
text: 'menu.section.access_control_people',
|
||||||
link: '#'
|
link: ''
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -331,7 +336,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.access_control_groups',
|
text: 'menu.section.access_control_groups',
|
||||||
link: '#'
|
link: ''
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -342,7 +347,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.access_control_authorizations',
|
text: 'menu.section.access_control_authorizations',
|
||||||
link: '#'
|
link: ''
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -377,7 +382,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.find_withdrawn_items',
|
text: 'menu.section.find_withdrawn_items',
|
||||||
link: '#'
|
link: ''
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -388,7 +393,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.find_private_items',
|
text: 'menu.section.find_private_items',
|
||||||
link: '/admin/items'
|
link: ''
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -435,7 +440,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.curation_task',
|
text: 'menu.section.curation_task',
|
||||||
link: '/curation'
|
link: ''
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
icon: 'filter',
|
icon: 'filter',
|
||||||
index: 7
|
index: 7
|
||||||
@@ -449,7 +454,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.statistics_task',
|
text: 'menu.section.statistics_task',
|
||||||
link: '#'
|
link: ''
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
icon: 'chart-bar',
|
icon: 'chart-bar',
|
||||||
index: 8
|
index: 8
|
||||||
@@ -463,7 +468,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
|
|||||||
model: {
|
model: {
|
||||||
type: MenuItemType.LINK,
|
type: MenuItemType.LINK,
|
||||||
text: 'menu.section.control_panel',
|
text: 'menu.section.control_panel',
|
||||||
link: '#'
|
link: ''
|
||||||
} as LinkMenuItemModel,
|
} as LinkMenuItemModel,
|
||||||
icon: 'cogs',
|
icon: 'cogs',
|
||||||
index: 9
|
index: 9
|
||||||
|
@@ -18,8 +18,8 @@ import { rendersSectionForMenu } from '../../../shared/menu/menu-section.decorat
|
|||||||
templateUrl: './expandable-admin-sidebar-section.component.html',
|
templateUrl: './expandable-admin-sidebar-section.component.html',
|
||||||
styleUrls: ['./expandable-admin-sidebar-section.component.scss'],
|
styleUrls: ['./expandable-admin-sidebar-section.component.scss'],
|
||||||
animations: [rotate, slide, bgColor]
|
animations: [rotate, slide, bgColor]
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
@rendersSectionForMenu(MenuID.ADMIN, true)
|
@rendersSectionForMenu(MenuID.ADMIN, true)
|
||||||
export class ExpandableAdminSidebarSectionComponent extends AdminSidebarSectionComponent implements OnInit {
|
export class ExpandableAdminSidebarSectionComponent extends AdminSidebarSectionComponent implements OnInit {
|
||||||
/**
|
/**
|
||||||
|
@@ -1,58 +1,62 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="collection-page"
|
<div class="collection-page"
|
||||||
*ngVar="(collectionRD$ | async) as collectionRD">
|
*ngVar="(collectionRD$ | async) as collectionRD">
|
||||||
<div *ngIf="collectionRD?.hasSucceeded" @fadeInOut>
|
<div *ngIf="collectionRD?.hasSucceeded" @fadeInOut>
|
||||||
<div *ngIf="collectionRD?.payload as collection">
|
<div *ngIf="collectionRD?.payload as collection">
|
||||||
<!-- Collection Name -->
|
<!-- Collection Name -->
|
||||||
<ds-comcol-page-header
|
<ds-comcol-page-header
|
||||||
[name]="collection.name">
|
[name]="collection.name">
|
||||||
</ds-comcol-page-header>
|
</ds-comcol-page-header>
|
||||||
<!-- Browse-By Links -->
|
<!-- Browse-By Links -->
|
||||||
<ds-comcol-page-browse-by [id]="collection.id"></ds-comcol-page-browse-by>
|
<ds-comcol-page-browse-by [id]="collection.id"></ds-comcol-page-browse-by>
|
||||||
<!-- Collection logo -->
|
<!-- Collection logo -->
|
||||||
<ds-comcol-page-logo *ngIf="logoRD$"
|
<ds-comcol-page-logo *ngIf="logoRD$"
|
||||||
[logo]="(logoRD$ | async)?.payload"
|
[logo]="(logoRD$ | async)?.payload"
|
||||||
[alternateText]="'Collection Logo'">
|
[alternateText]="'Collection Logo'">
|
||||||
</ds-comcol-page-logo>
|
</ds-comcol-page-logo>
|
||||||
<!-- Introductionary text -->
|
<!-- Introductionary text -->
|
||||||
<ds-comcol-page-content
|
<ds-comcol-page-content
|
||||||
[content]="collection.introductoryText"
|
[content]="collection.introductoryText"
|
||||||
[hasInnerHtml]="true">
|
[hasInnerHtml]="true">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
<!-- News -->
|
<!-- News -->
|
||||||
<ds-comcol-page-content
|
<ds-comcol-page-content
|
||||||
[content]="collection.sidebarText"
|
[content]="collection.sidebarText"
|
||||||
[hasInnerHtml]="true"
|
[hasInnerHtml]="true"
|
||||||
[title]="'community.page.news'">
|
[title]="'community.page.news'">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
<!-- Copyright -->
|
<!-- Copyright -->
|
||||||
<ds-comcol-page-content
|
<ds-comcol-page-content
|
||||||
[content]="collection.copyrightText"
|
[content]="collection.copyrightText"
|
||||||
[hasInnerHtml]="true">
|
[hasInnerHtml]="true">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
<!-- Licence -->
|
<!-- Licence -->
|
||||||
<ds-comcol-page-content
|
<ds-comcol-page-content
|
||||||
[content]="collection.dcLicense"
|
[content]="collection.dcLicense"
|
||||||
[title]="'collection.page.license'">
|
[title]="'collection.page.license'">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
</div>
|
</div>
|
||||||
|
<br>
|
||||||
|
<ng-container *ngVar="(itemRD$ | async) as itemRD">
|
||||||
|
<div *ngIf="itemRD?.hasSucceeded" @fadeIn>
|
||||||
|
<h2>{{'collection.page.browse.recent.head' | translate}}</h2>
|
||||||
|
<ds-viewable-collection
|
||||||
|
[config]="paginationConfig"
|
||||||
|
[sortConfig]="sortConfig"
|
||||||
|
[objects]="itemRD"
|
||||||
|
[hideGear]="true"
|
||||||
|
(paginationChange)="onPaginationChange($event)">
|
||||||
|
</ds-viewable-collection>
|
||||||
|
</div>
|
||||||
|
<ds-error *ngIf="itemRD?.hasFailed"
|
||||||
|
message="{{'error.recent-submissions' | translate}}"></ds-error>
|
||||||
|
<ds-loading *ngIf="!itemRD || itemRD.isLoading"
|
||||||
|
message="{{'loading.recent-submissions' | translate}}"></ds-loading>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
<ds-error *ngIf="collectionRD?.hasFailed"
|
||||||
|
message="{{'error.collection' | translate}}"></ds-error>
|
||||||
|
<ds-loading *ngIf="collectionRD?.isLoading"
|
||||||
|
message="{{'loading.collection' | translate}}"></ds-loading>
|
||||||
</div>
|
</div>
|
||||||
<ds-error *ngIf="collectionRD?.hasFailed" message="{{'error.collection' | translate}}"></ds-error>
|
|
||||||
<ds-loading *ngIf="collectionRD?.isLoading" message="{{'loading.collection' | translate}}"></ds-loading>
|
|
||||||
<br>
|
|
||||||
<ng-container *ngVar="(itemRD$ | async) as itemRD">
|
|
||||||
<div *ngIf="itemRD?.hasSucceeded" @fadeIn>
|
|
||||||
<h2>{{'collection.page.browse.recent.head' | translate}}</h2>
|
|
||||||
<ds-viewable-collection
|
|
||||||
[config]="paginationConfig"
|
|
||||||
[sortConfig]="sortConfig"
|
|
||||||
[objects]="itemRD"
|
|
||||||
[hideGear]="true"
|
|
||||||
(paginationChange)="onPaginationChange($event)">
|
|
||||||
</ds-viewable-collection>
|
|
||||||
</div>
|
|
||||||
<ds-error *ngIf="itemRD?.hasFailed" message="{{'error.recent-submissions' | translate}}"></ds-error>
|
|
||||||
<ds-loading *ngIf="!itemRD || itemRD.isLoading" message="{{'loading.recent-submissions' | translate}}"></ds-loading>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { BehaviorSubject, of as observableOf, Observable, Subject } from 'rxjs';
|
||||||
|
import { filter, flatMap, map, startWith, switchMap, take, tap } from 'rxjs/operators';
|
||||||
|
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model';
|
||||||
|
import { SearchService } from '../+search-page/search-service/search.service';
|
||||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
||||||
import { CollectionDataService } from '../core/data/collection-data.service';
|
import { CollectionDataService } from '../core/data/collection-data.service';
|
||||||
import { PaginatedList } from '../core/data/paginated-list';
|
import { PaginatedList } from '../core/data/paginated-list';
|
||||||
@@ -10,16 +13,17 @@ import { MetadataService } from '../core/metadata/metadata.service';
|
|||||||
import { Bitstream } from '../core/shared/bitstream.model';
|
import { Bitstream } from '../core/shared/bitstream.model';
|
||||||
|
|
||||||
import { Collection } from '../core/shared/collection.model';
|
import { Collection } from '../core/shared/collection.model';
|
||||||
|
import { DSpaceObjectType } from '../core/shared/dspace-object-type.model';
|
||||||
import { Item } from '../core/shared/item.model';
|
import { Item } from '../core/shared/item.model';
|
||||||
|
import {
|
||||||
|
getSucceededRemoteData,
|
||||||
|
redirectToPageNotFoundOn404,
|
||||||
|
toDSpaceObjectListRD
|
||||||
|
} from '../core/shared/operators';
|
||||||
|
|
||||||
import { fadeIn, fadeInOut } from '../shared/animations/fade';
|
import { fadeIn, fadeInOut } from '../shared/animations/fade';
|
||||||
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
import { hasNoValue, hasValue, isNotEmpty } from '../shared/empty.util';
|
||||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||||
import { filter, flatMap, map, tap } from 'rxjs/operators';
|
|
||||||
import { SearchService } from '../+search-page/search-service/search.service';
|
|
||||||
import { PaginatedSearchOptions } from '../+search-page/paginated-search-options.model';
|
|
||||||
import { toDSpaceObjectListRD } from '../core/shared/operators';
|
|
||||||
import { DSpaceObjectType } from '../core/shared/dspace-object-type.model';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-collection-page',
|
selector: 'ds-collection-page',
|
||||||
@@ -31,20 +35,23 @@ import { DSpaceObjectType } from '../core/shared/dspace-object-type.model';
|
|||||||
fadeInOut
|
fadeInOut
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class CollectionPageComponent implements OnInit, OnDestroy {
|
export class CollectionPageComponent implements OnInit {
|
||||||
collectionRD$: Observable<RemoteData<Collection>>;
|
collectionRD$: Observable<RemoteData<Collection>>;
|
||||||
itemRD$: Observable<RemoteData<PaginatedList<Item>>>;
|
itemRD$: Observable<RemoteData<PaginatedList<Item>>>;
|
||||||
logoRD$: Observable<RemoteData<Bitstream>>;
|
logoRD$: Observable<RemoteData<Bitstream>>;
|
||||||
paginationConfig: PaginationComponentOptions;
|
paginationConfig: PaginationComponentOptions;
|
||||||
sortConfig: SortOptions;
|
sortConfig: SortOptions;
|
||||||
private subs: Subscription[] = [];
|
private paginationChanges$: Subject<{
|
||||||
private collectionId: string;
|
paginationConfig: PaginationComponentOptions,
|
||||||
|
sortConfig: SortOptions
|
||||||
|
}>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private collectionDataService: CollectionDataService,
|
private collectionDataService: CollectionDataService,
|
||||||
private searchService: SearchService,
|
private searchService: SearchService,
|
||||||
private metadata: MetadataService,
|
private metadata: MetadataService,
|
||||||
private route: ActivatedRoute
|
private route: ActivatedRoute,
|
||||||
|
private router: Router
|
||||||
) {
|
) {
|
||||||
this.paginationConfig = new PaginationComponentOptions();
|
this.paginationConfig = new PaginationComponentOptions();
|
||||||
this.paginationConfig.id = 'collection-page-pagination';
|
this.paginationConfig.id = 'collection-page-pagination';
|
||||||
@@ -55,43 +62,43 @@ export class CollectionPageComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.collectionRD$ = this.route.data.pipe(
|
this.collectionRD$ = this.route.data.pipe(
|
||||||
map((data) => data.collection),
|
map((data) => data.collection as RemoteData<Collection>),
|
||||||
tap((data) => this.collectionId = data.payload.id)
|
redirectToPageNotFoundOn404(this.router),
|
||||||
|
take(1)
|
||||||
);
|
);
|
||||||
this.logoRD$ = this.collectionRD$.pipe(
|
this.logoRD$ = this.collectionRD$.pipe(
|
||||||
map((rd: RemoteData<Collection>) => rd.payload),
|
map((rd: RemoteData<Collection>) => rd.payload),
|
||||||
filter((collection: Collection) => hasValue(collection)),
|
filter((collection: Collection) => hasValue(collection)),
|
||||||
flatMap((collection: Collection) => collection.logo)
|
flatMap((collection: Collection) => collection.logo)
|
||||||
);
|
);
|
||||||
this.subs.push(
|
|
||||||
this.route.queryParams.subscribe((params) => {
|
|
||||||
this.metadata.processRemoteData(this.collectionRD$);
|
|
||||||
const page = +params.page || this.paginationConfig.currentPage;
|
|
||||||
const pageSize = +params.pageSize || this.paginationConfig.pageSize;
|
|
||||||
const pagination = Object.assign({},
|
|
||||||
this.paginationConfig,
|
|
||||||
{ currentPage: page, pageSize: pageSize }
|
|
||||||
);
|
|
||||||
this.updatePage({
|
|
||||||
pagination: pagination,
|
|
||||||
sort: this.sortConfig
|
|
||||||
});
|
|
||||||
}));
|
|
||||||
|
|
||||||
}
|
this.paginationChanges$ = new BehaviorSubject({
|
||||||
|
paginationConfig: this.paginationConfig,
|
||||||
|
sortConfig: this.sortConfig
|
||||||
|
});
|
||||||
|
|
||||||
updatePage(searchOptions) {
|
this.itemRD$ = this.paginationChanges$.pipe(
|
||||||
this.itemRD$ = this.searchService.search(
|
switchMap((dto) => this.collectionRD$.pipe(
|
||||||
new PaginatedSearchOptions({
|
getSucceededRemoteData(),
|
||||||
scope: this.collectionId,
|
map((rd) => rd.payload.id),
|
||||||
pagination: searchOptions.pagination,
|
switchMap((id: string) => {
|
||||||
sort: searchOptions.sort,
|
return this.searchService.search(
|
||||||
dsoType: DSpaceObjectType.ITEM
|
new PaginatedSearchOptions({
|
||||||
})).pipe(toDSpaceObjectListRD()) as Observable<RemoteData<PaginatedList<Item>>>;
|
scope: id,
|
||||||
}
|
pagination: dto.paginationConfig,
|
||||||
|
sort: dto.sortConfig,
|
||||||
|
dsoType: DSpaceObjectType.ITEM
|
||||||
|
})).pipe(toDSpaceObjectListRD()) as Observable<RemoteData<PaginatedList<Item>>>
|
||||||
|
}),
|
||||||
|
startWith(undefined) // Make sure switching pages shows loading component
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
this.route.queryParams.pipe(take(1)).subscribe((params) => {
|
||||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
this.metadata.processRemoteData(this.collectionRD$);
|
||||||
|
this.onPaginationChange(params);
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
isNotEmpty(object: any) {
|
isNotEmpty(object: any) {
|
||||||
@@ -99,15 +106,14 @@ export class CollectionPageComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onPaginationChange(event) {
|
onPaginationChange(event) {
|
||||||
this.updatePage({
|
this.paginationConfig.currentPage = +event.page || this.paginationConfig.currentPage;
|
||||||
pagination: {
|
this.paginationConfig.pageSize = +event.pageSize || this.paginationConfig.pageSize;
|
||||||
currentPage: event.page,
|
this.sortConfig.direction = event.sortDirection || this.sortConfig.direction;
|
||||||
pageSize: event.pageSize
|
this.sortConfig.field = event.sortField || this.sortConfig.field;
|
||||||
},
|
|
||||||
sort: {
|
this.paginationChanges$.next({
|
||||||
field: event.sortField,
|
paginationConfig: this.paginationConfig,
|
||||||
direction: event.sortDirection
|
sortConfig: this.sortConfig
|
||||||
}
|
});
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,9 +7,9 @@ import { CollectionPageComponent } from './collection-page.component';
|
|||||||
import { CollectionPageRoutingModule } from './collection-page-routing.module';
|
import { CollectionPageRoutingModule } from './collection-page-routing.module';
|
||||||
import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component';
|
import { CreateCollectionPageComponent } from './create-collection-page/create-collection-page.component';
|
||||||
import { CollectionFormComponent } from './collection-form/collection-form.component';
|
import { CollectionFormComponent } from './collection-form/collection-form.component';
|
||||||
import { SearchPageModule } from '../+search-page/search-page.module';
|
|
||||||
import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component';
|
import { EditCollectionPageComponent } from './edit-collection-page/edit-collection-page.component';
|
||||||
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
|
import { DeleteCollectionPageComponent } from './delete-collection-page/delete-collection-page.component';
|
||||||
|
import { SearchService } from '../+search-page/search-service/search.service';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -23,6 +23,9 @@ import { DeleteCollectionPageComponent } from './delete-collection-page/delete-c
|
|||||||
EditCollectionPageComponent,
|
EditCollectionPageComponent,
|
||||||
DeleteCollectionPageComponent,
|
DeleteCollectionPageComponent,
|
||||||
CollectionFormComponent
|
CollectionFormComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
SearchService
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class CollectionPageModule {
|
export class CollectionPageModule {
|
||||||
|
@@ -4,7 +4,8 @@ import { Collection } from '../core/shared/collection.model';
|
|||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { CollectionDataService } from '../core/data/collection-data.service';
|
import { CollectionDataService } from '../core/data/collection-data.service';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { getSucceededRemoteData } from '../core/shared/operators';
|
import { find } from 'rxjs/operators';
|
||||||
|
import { hasValue } from '../shared/empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a resolver that requests a specific collection before the route is activated
|
* This class represents a resolver that requests a specific collection before the route is activated
|
||||||
@@ -18,11 +19,12 @@ export class CollectionPageResolver implements Resolve<RemoteData<Collection>> {
|
|||||||
* Method for resolving a collection based on the parameters in the current route
|
* Method for resolving a collection based on the parameters in the current route
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||||
* @returns Observable<<RemoteData<Collection>> Emits the found collection based on the parameters in the current route
|
* @returns Observable<<RemoteData<Collection>> Emits the found collection based on the parameters in the current route,
|
||||||
|
* or an error if something went wrong
|
||||||
*/
|
*/
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Collection>> {
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Collection>> {
|
||||||
return this.collectionService.findById(route.params.id).pipe(
|
return this.collectionService.findById(route.params.id).pipe(
|
||||||
getSucceededRemoteData()
|
find((RD) => hasValue(RD.error) || RD.hasSucceeded),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component';
|
import { EditComColPageComponent } from '../../shared/comcol-forms/edit-comcol-page/edit-comcol-page.component';
|
||||||
import { NormalizedCollection } from '../../core/cache/models/normalized-collection.model';
|
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Collection } from '../../core/shared/collection.model';
|
||||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../core/data/collection-data.service';
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { mergeMap, filter, map } from 'rxjs/operators';
|
import { mergeMap, filter, map } from 'rxjs/operators';
|
||||||
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
import { Subscription, Observable } from 'rxjs';
|
import { Subscription, Observable } from 'rxjs';
|
||||||
import { CommunityDataService } from '../core/data/community-data.service';
|
import { CommunityDataService } from '../core/data/community-data.service';
|
||||||
@@ -13,6 +13,7 @@ import { MetadataService } from '../core/metadata/metadata.service';
|
|||||||
|
|
||||||
import { fadeInOut } from '../shared/animations/fade';
|
import { fadeInOut } from '../shared/animations/fade';
|
||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
|
import { redirectToPageNotFoundOn404 } from '../core/shared/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-community-page',
|
selector: 'ds-community-page',
|
||||||
@@ -37,13 +38,17 @@ export class CommunityPageComponent implements OnInit {
|
|||||||
constructor(
|
constructor(
|
||||||
private communityDataService: CommunityDataService,
|
private communityDataService: CommunityDataService,
|
||||||
private metadata: MetadataService,
|
private metadata: MetadataService,
|
||||||
private route: ActivatedRoute
|
private route: ActivatedRoute,
|
||||||
|
private router: Router
|
||||||
) {
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.communityRD$ = this.route.data.pipe(map((data) => data.community));
|
this.communityRD$ = this.route.data.pipe(
|
||||||
|
map((data) => data.community as RemoteData<Community>),
|
||||||
|
redirectToPageNotFoundOn404(this.router)
|
||||||
|
);
|
||||||
this.logoRD$ = this.communityRD$.pipe(
|
this.logoRD$ = this.communityRD$.pipe(
|
||||||
map((rd: RemoteData<Community>) => rd.payload),
|
map((rd: RemoteData<Community>) => rd.payload),
|
||||||
filter((community: Community) => hasValue(community)),
|
filter((community: Community) => hasValue(community)),
|
||||||
|
@@ -2,9 +2,10 @@ import { Injectable } from '@angular/core';
|
|||||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { getSucceededRemoteData } from '../core/shared/operators';
|
|
||||||
import { Community } from '../core/shared/community.model';
|
import { Community } from '../core/shared/community.model';
|
||||||
import { CommunityDataService } from '../core/data/community-data.service';
|
import { CommunityDataService } from '../core/data/community-data.service';
|
||||||
|
import { find } from 'rxjs/operators';
|
||||||
|
import { hasValue } from '../shared/empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a resolver that requests a specific community before the route is activated
|
* This class represents a resolver that requests a specific community before the route is activated
|
||||||
@@ -18,11 +19,12 @@ export class CommunityPageResolver implements Resolve<RemoteData<Community>> {
|
|||||||
* Method for resolving a community based on the parameters in the current route
|
* Method for resolving a community based on the parameters in the current route
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||||
* @returns Observable<<RemoteData<Community>> Emits the found community based on the parameters in the current route
|
* @returns Observable<<RemoteData<Community>> Emits the found community based on the parameters in the current route,
|
||||||
|
* or an error if something went wrong
|
||||||
*/
|
*/
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Community>> {
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Community>> {
|
||||||
return this.communityService.findById(route.params.id).pipe(
|
return this.communityService.findById(route.params.id).pipe(
|
||||||
getSucceededRemoteData()
|
find((RD) => hasValue(RD.error) || RD.hasSucceeded)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
<div class="d-flex flex-wrap">
|
<div class="d-flex flex-wrap">
|
||||||
<img class="mr-4 dspace-logo" src="assets/images/dspace-logo.svg" alt="" />
|
<img class="mr-4 dspace-logo" src="assets/images/dspace-logo.svg" alt="" />
|
||||||
<div>
|
<div>
|
||||||
<h1 class="display-3">Welcome to the DSpace 7 Preview Release</h1>
|
<h1 class="display-3">Welcome to the DSpace 7 Preview</h1>
|
||||||
<p class="lead">DSpace is the world leading open source repository platform that enables organisations to:</p>
|
<p class="lead">DSpace is the world leading open source repository platform that enables organisations to:</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<ds-home-news></ds-home-news>
|
<ds-home-news></ds-home-news>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<ds-search-form></ds-search-form>
|
<ds-search-form [inPlaceSearch]="false"></ds-search-form>
|
||||||
<ds-top-level-community-list></ds-top-level-community-list>
|
<ds-top-level-community-list></ds-top-level-community-list>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { fadeIn, fadeInOut } from '../../shared/animations/fade';
|
import { fadeIn, fadeInOut } from '../../shared/animations/fade';
|
||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<div class="simple-view-element" [class.d-none]="content.textContent.trim().length === 0">
|
<div class="simple-view-element" [class.d-none]="content.textContent.trim().length === 0 && hasNoValue(content.querySelector('img'))">
|
||||||
<h5 class="simple-view-element-header" *ngIf="label">{{ label }}</h5>
|
<h5 class="simple-view-element-header" *ngIf="label">{{ label }}</h5>
|
||||||
<div #content class="simple-view-element-body">
|
<div #content class="simple-view-element-body">
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
|
@@ -1,18 +1,41 @@
|
|||||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
import { Component } from '@angular/core';
|
||||||
import { By } from '@angular/platform-browser';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { Component, DebugElement } from '@angular/core';
|
|
||||||
|
|
||||||
import { MetadataFieldWrapperComponent } from './metadata-field-wrapper.component';
|
import { MetadataFieldWrapperComponent } from './metadata-field-wrapper.component';
|
||||||
|
|
||||||
|
/* tslint:disable:max-classes-per-file */
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-component-with-content',
|
selector: 'ds-component-without-content',
|
||||||
template: '<ds-metadata-field-wrapper [label]="\'test label\'">\n' +
|
template: '<ds-metadata-field-wrapper [label]="\'test label\'">\n' +
|
||||||
' <div class="my-content">\n' +
|
|
||||||
' <span></span>\n' +
|
|
||||||
' </div>\n' +
|
|
||||||
'</ds-metadata-field-wrapper>'
|
'</ds-metadata-field-wrapper>'
|
||||||
})
|
})
|
||||||
class ContentComponent {}
|
class NoContentComponent {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-component-with-empty-spans',
|
||||||
|
template: '<ds-metadata-field-wrapper [label]="\'test label\'">\n' +
|
||||||
|
' <span></span>\n' +
|
||||||
|
' <span></span>\n' +
|
||||||
|
'</ds-metadata-field-wrapper>'
|
||||||
|
})
|
||||||
|
class SpanContentComponent {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-component-with-text',
|
||||||
|
template: '<ds-metadata-field-wrapper [label]="\'test label\'">\n' +
|
||||||
|
' <span>The quick brown fox jumps over the lazy dog</span>\n' +
|
||||||
|
'</ds-metadata-field-wrapper>'
|
||||||
|
})
|
||||||
|
class TextContentComponent {}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-component-with-image',
|
||||||
|
template: '<ds-metadata-field-wrapper [label]="\'test label\'">\n' +
|
||||||
|
' <img src="https://some/image.png" alt="an alt text">\n' +
|
||||||
|
'</ds-metadata-field-wrapper>'
|
||||||
|
})
|
||||||
|
class ImgContentComponent {}
|
||||||
|
/* tslint:enable:max-classes-per-file */
|
||||||
|
|
||||||
describe('MetadataFieldWrapperComponent', () => {
|
describe('MetadataFieldWrapperComponent', () => {
|
||||||
let component: MetadataFieldWrapperComponent;
|
let component: MetadataFieldWrapperComponent;
|
||||||
@@ -20,7 +43,7 @@ describe('MetadataFieldWrapperComponent', () => {
|
|||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
declarations: [MetadataFieldWrapperComponent, ContentComponent]
|
declarations: [MetadataFieldWrapperComponent, NoContentComponent, SpanContentComponent, TextContentComponent, ImgContentComponent]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -30,23 +53,21 @@ describe('MetadataFieldWrapperComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const wrapperSelector = '.simple-view-element';
|
const wrapperSelector = '.simple-view-element';
|
||||||
const labelSelector = '.simple-view-element-header';
|
|
||||||
const contentSelector = '.my-content';
|
|
||||||
|
|
||||||
it('should create', () => {
|
it('should create', () => {
|
||||||
expect(component).toBeDefined();
|
expect(component).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not show the component when there is no content', () => {
|
it('should not show the component when there is no content', () => {
|
||||||
component.label = 'test label';
|
const parentFixture = TestBed.createComponent(NoContentComponent);
|
||||||
fixture.detectChanges();
|
parentFixture.detectChanges();
|
||||||
const parentNative = fixture.nativeElement;
|
const parentNative = parentFixture.nativeElement;
|
||||||
const nativeWrapper = parentNative.querySelector(wrapperSelector);
|
const nativeWrapper = parentNative.querySelector(wrapperSelector);
|
||||||
expect(nativeWrapper.classList.contains('d-none')).toBe(true);
|
expect(nativeWrapper.classList.contains('d-none')).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not show the component when there is DOM content but no text', () => {
|
it('should not show the component when there is DOM content but not text or an image', () => {
|
||||||
const parentFixture = TestBed.createComponent(ContentComponent);
|
const parentFixture = TestBed.createComponent(SpanContentComponent);
|
||||||
parentFixture.detectChanges();
|
parentFixture.detectChanges();
|
||||||
const parentNative = parentFixture.nativeElement;
|
const parentNative = parentFixture.nativeElement;
|
||||||
const nativeWrapper = parentNative.querySelector(wrapperSelector);
|
const nativeWrapper = parentNative.querySelector(wrapperSelector);
|
||||||
@@ -54,11 +75,18 @@ describe('MetadataFieldWrapperComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should show the component when there is text content', () => {
|
it('should show the component when there is text content', () => {
|
||||||
const parentFixture = TestBed.createComponent(ContentComponent);
|
const parentFixture = TestBed.createComponent(TextContentComponent);
|
||||||
|
parentFixture.detectChanges();
|
||||||
|
const parentNative = parentFixture.nativeElement;
|
||||||
|
const nativeWrapper = parentNative.querySelector(wrapperSelector);
|
||||||
|
parentFixture.detectChanges();
|
||||||
|
expect(nativeWrapper.classList.contains('d-none')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show the component when there is img content', () => {
|
||||||
|
const parentFixture = TestBed.createComponent(ImgContentComponent);
|
||||||
parentFixture.detectChanges();
|
parentFixture.detectChanges();
|
||||||
const parentNative = parentFixture.nativeElement;
|
const parentNative = parentFixture.nativeElement;
|
||||||
const nativeContent = parentNative.querySelector(contentSelector);
|
|
||||||
nativeContent.textContent = 'lorem ipsum';
|
|
||||||
const nativeWrapper = parentNative.querySelector(wrapperSelector);
|
const nativeWrapper = parentNative.querySelector(wrapperSelector);
|
||||||
parentFixture.detectChanges();
|
parentFixture.detectChanges();
|
||||||
expect(nativeWrapper.classList.contains('d-none')).toBe(false);
|
expect(nativeWrapper.classList.contains('d-none')).toBe(false);
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { hasNoValue } from '../../../shared/empty.util';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders any content inside this wrapper.
|
* This component renders any content inside this wrapper.
|
||||||
@@ -11,6 +12,15 @@ import { Component, Input } from '@angular/core';
|
|||||||
})
|
})
|
||||||
export class MetadataFieldWrapperComponent {
|
export class MetadataFieldWrapperComponent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The label (title) for the content
|
||||||
|
*/
|
||||||
@Input() label: string;
|
@Input() label: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make hasNoValue() available in the template
|
||||||
|
*/
|
||||||
|
hasNoValue(o: any): boolean {
|
||||||
|
return hasNoValue(o);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,97 @@
|
|||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ChangeDetectionStrategy, DebugElement, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { MockTranslateLoader } from '../../../shared/mocks/mock-translate-loader';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { MetadataUriValuesComponent } from './metadata-uri-values.component';
|
||||||
|
import { isNotEmpty } from '../../../shared/empty.util';
|
||||||
|
import { MetadataValue } from '../../../core/shared/metadata.models';
|
||||||
|
|
||||||
|
let comp: MetadataUriValuesComponent;
|
||||||
|
let fixture: ComponentFixture<MetadataUriValuesComponent>;
|
||||||
|
|
||||||
|
const mockMetadata = [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'http://fakelink.org'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'http://another.fakelink.org'
|
||||||
|
}
|
||||||
|
] as MetadataValue[];
|
||||||
|
const mockSeperator = '<br/>';
|
||||||
|
const mockLabel = 'fake.message';
|
||||||
|
const mockLinkText = 'fake link text';
|
||||||
|
|
||||||
|
describe('MetadataUriValuesComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [MetadataUriValuesComponent],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(MetadataUriValuesComponent, {
|
||||||
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(MetadataUriValuesComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.mdValues = mockMetadata;
|
||||||
|
comp.separator = mockSeperator;
|
||||||
|
comp.label = mockLabel;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should display all metadata values', () => {
|
||||||
|
const innerHTML = fixture.nativeElement.innerHTML;
|
||||||
|
for (const metadatum of mockMetadata) {
|
||||||
|
expect(innerHTML).toContain(metadatum.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain the correct hrefs', () => {
|
||||||
|
const links = fixture.debugElement.queryAll(By.css('a'));
|
||||||
|
for (const metadatum of mockMetadata) {
|
||||||
|
expect(containsHref(links, metadatum.value)).toBeTruthy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain separators equal to the amount of metadata values minus one', () => {
|
||||||
|
const separators = fixture.debugElement.queryAll(By.css('a span'));
|
||||||
|
expect(separators.length).toBe(mockMetadata.length - 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when linktext is defined', () => {
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.linktext = mockLinkText;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should replace the metadata value with the linktext', () => {
|
||||||
|
const link = fixture.debugElement.query(By.css('a'));
|
||||||
|
expect(link.nativeElement.textContent).toContain(mockLinkText);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
function containsHref(links: DebugElement[], href: string): boolean {
|
||||||
|
for (const link of links) {
|
||||||
|
const hrefAtt = link.properties.href;
|
||||||
|
if (isNotEmpty(hrefAtt)) {
|
||||||
|
if (hrefAtt === href) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
@@ -17,11 +17,24 @@ import { MetadataValue } from '../../../core/shared/metadata.models';
|
|||||||
})
|
})
|
||||||
export class MetadataUriValuesComponent extends MetadataValuesComponent {
|
export class MetadataUriValuesComponent extends MetadataValuesComponent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional text to replace the links with
|
||||||
|
* If undefined, the metadata value (uri) is displayed
|
||||||
|
*/
|
||||||
@Input() linktext: any;
|
@Input() linktext: any;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The metadata values to display
|
||||||
|
*/
|
||||||
@Input() mdValues: MetadataValue[];
|
@Input() mdValues: MetadataValue[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The seperator used to split the metadata values (can contain HTML)
|
||||||
|
*/
|
||||||
@Input() separator: string;
|
@Input() separator: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The label for this iteration of metadata values
|
||||||
|
*/
|
||||||
@Input() label: string;
|
@Input() label: string;
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,65 @@
|
|||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { MockTranslateLoader } from '../../../shared/mocks/mock-translate-loader';
|
||||||
|
import { MetadataValuesComponent } from './metadata-values.component';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { MetadataValue } from '../../../core/shared/metadata.models';
|
||||||
|
|
||||||
|
let comp: MetadataValuesComponent;
|
||||||
|
let fixture: ComponentFixture<MetadataValuesComponent>;
|
||||||
|
|
||||||
|
const mockMetadata = [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: '1234'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'a publisher'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'desc'
|
||||||
|
}] as MetadataValue[];
|
||||||
|
const mockSeperator = '<br/>';
|
||||||
|
const mockLabel = 'fake.message';
|
||||||
|
|
||||||
|
describe('MetadataValuesComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [MetadataValuesComponent],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(MetadataValuesComponent, {
|
||||||
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(MetadataValuesComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.mdValues = mockMetadata;
|
||||||
|
comp.separator = mockSeperator;
|
||||||
|
comp.label = mockLabel;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should display all metadata values', () => {
|
||||||
|
const innerHTML = fixture.nativeElement.innerHTML;
|
||||||
|
for (const metadatum of mockMetadata) {
|
||||||
|
expect(innerHTML).toContain(metadatum.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain separators equal to the amount of metadata values minus one', () => {
|
||||||
|
const separators = fixture.debugElement.queryAll(By.css('span>span'));
|
||||||
|
expect(separators.length).toBe(mockMetadata.length - 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -12,10 +12,19 @@ import { MetadataValue } from '../../../core/shared/metadata.models';
|
|||||||
})
|
})
|
||||||
export class MetadataValuesComponent {
|
export class MetadataValuesComponent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The metadata values to display
|
||||||
|
*/
|
||||||
@Input() mdValues: MetadataValue[];
|
@Input() mdValues: MetadataValue[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The seperator used to split the metadata values (can contain HTML)
|
||||||
|
*/
|
||||||
@Input() separator: string;
|
@Input() separator: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The label for this iteration of metadata values
|
||||||
|
*/
|
||||||
@Input() label: string;
|
@Input() label: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
78
src/app/+item-page/full/full-item-page.component.spec.ts
Normal file
78
src/app/+item-page/full/full-item-page.component.spec.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
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, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { TruncatePipe } from '../../shared/utils/truncate.pipe';
|
||||||
|
import { FullItemPageComponent } from './full-item-page.component';
|
||||||
|
import { MetadataService } from '../../core/metadata/metadata.service';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
|
||||||
|
import { VarDirective } from '../../shared/utils/var.directive';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
import { PageInfo } from '../../core/shared/page-info.model';
|
||||||
|
import { PaginatedList } from '../../core/data/paginated-list';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
|
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'test item'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const routeStub = Object.assign(new ActivatedRouteStub(), {
|
||||||
|
data: observableOf({ item: new RemoteData(false, false, true, null, mockItem) })
|
||||||
|
});
|
||||||
|
const metadataServiceStub = {
|
||||||
|
/* tslint:disable:no-empty */
|
||||||
|
processRemoteData: () => {}
|
||||||
|
/* tslint:enable:no-empty */
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('FullItemPageComponent', () => {
|
||||||
|
let comp: FullItemPageComponent;
|
||||||
|
let fixture: ComponentFixture<FullItemPageComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
}), RouterTestingModule.withRoutes([]), BrowserAnimationsModule],
|
||||||
|
declarations: [FullItemPageComponent, TruncatePipe, VarDirective],
|
||||||
|
providers: [
|
||||||
|
{provide: ActivatedRoute, useValue: routeStub},
|
||||||
|
{provide: ItemDataService, useValue: {}},
|
||||||
|
{provide: MetadataService, useValue: metadataServiceStub}
|
||||||
|
],
|
||||||
|
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(FullItemPageComponent, {
|
||||||
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(FullItemPageComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should display the item\'s metadata', () => {
|
||||||
|
const table = fixture.debugElement.query(By.css('table'));
|
||||||
|
for (const metadatum of mockItem.allMetadata([])) {
|
||||||
|
expect(table.nativeElement.innerHTML).toContain(metadatum.value);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
@@ -1,9 +1,8 @@
|
|||||||
|
|
||||||
import {filter, map} from 'rxjs/operators';
|
import {filter, map} from 'rxjs/operators';
|
||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable , BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
import { ItemPageComponent } from '../simple/item-page.component';
|
import { ItemPageComponent } from '../simple/item-page.component';
|
||||||
import { MetadataMap } from '../../core/shared/metadata.models';
|
import { MetadataMap } from '../../core/shared/metadata.models';
|
||||||
@@ -32,12 +31,12 @@ import { hasValue } from '../../shared/empty.util';
|
|||||||
})
|
})
|
||||||
export class FullItemPageComponent extends ItemPageComponent implements OnInit {
|
export class FullItemPageComponent extends ItemPageComponent implements OnInit {
|
||||||
|
|
||||||
itemRD$: Observable<RemoteData<Item>>;
|
itemRD$: BehaviorSubject<RemoteData<Item>>;
|
||||||
|
|
||||||
metadata$: Observable<MetadataMap>;
|
metadata$: Observable<MetadataMap>;
|
||||||
|
|
||||||
constructor(route: ActivatedRoute, items: ItemDataService, metadataService: MetadataService) {
|
constructor(route: ActivatedRoute, router: Router, items: ItemDataService, metadataService: MetadataService) {
|
||||||
super(route, items, metadataService);
|
super(route, router, items, metadataService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*** AoT inheritance fix, will hopefully be resolved in the near future **/
|
/*** AoT inheritance fix, will hopefully be resolved in the near future **/
|
||||||
|
@@ -1,47 +1,78 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from './../shared/shared.module';
|
||||||
|
import { GenericItemPageFieldComponent } from './simple/field-components/specific-field/generic/generic-item-page-field.component';
|
||||||
|
|
||||||
import { ItemPageComponent } from './simple/item-page.component';
|
import { ItemPageComponent } from './simple/item-page.component';
|
||||||
import { ItemPageRoutingModule } from './item-page-routing.module';
|
import { ItemPageRoutingModule } from './item-page-routing.module';
|
||||||
import { MetadataValuesComponent } from './field-components/metadata-values/metadata-values.component';
|
|
||||||
import { MetadataUriValuesComponent } from './field-components/metadata-uri-values/metadata-uri-values.component';
|
import { MetadataUriValuesComponent } from './field-components/metadata-uri-values/metadata-uri-values.component';
|
||||||
import { MetadataFieldWrapperComponent } from './field-components/metadata-field-wrapper/metadata-field-wrapper.component';
|
|
||||||
import { ItemPageAuthorFieldComponent } from './simple/field-components/specific-field/author/item-page-author-field.component';
|
import { ItemPageAuthorFieldComponent } from './simple/field-components/specific-field/author/item-page-author-field.component';
|
||||||
import { ItemPageDateFieldComponent } from './simple/field-components/specific-field/date/item-page-date-field.component';
|
import { ItemPageDateFieldComponent } from './simple/field-components/specific-field/date/item-page-date-field.component';
|
||||||
import { ItemPageAbstractFieldComponent } from './simple/field-components/specific-field/abstract/item-page-abstract-field.component';
|
import { ItemPageAbstractFieldComponent } from './simple/field-components/specific-field/abstract/item-page-abstract-field.component';
|
||||||
import { ItemPageUriFieldComponent } from './simple/field-components/specific-field/uri/item-page-uri-field.component';
|
import { ItemPageUriFieldComponent } from './simple/field-components/specific-field/uri/item-page-uri-field.component';
|
||||||
import { ItemPageTitleFieldComponent } from './simple/field-components/specific-field/title/item-page-title-field.component';
|
import { ItemPageTitleFieldComponent } from './simple/field-components/specific-field/title/item-page-title-field.component';
|
||||||
import { ItemPageSpecificFieldComponent } from './simple/field-components/specific-field/item-page-specific-field.component';
|
import { ItemPageFieldComponent } from './simple/field-components/specific-field/item-page-field.component';
|
||||||
import { FileSectionComponent } from './simple/field-components/file-section/file-section.component';
|
import { FileSectionComponent } from './simple/field-components/file-section/file-section.component';
|
||||||
import { CollectionsComponent } from './field-components/collections/collections.component';
|
import { CollectionsComponent } from './field-components/collections/collections.component';
|
||||||
import { FullItemPageComponent } from './full/full-item-page.component';
|
import { FullItemPageComponent } from './full/full-item-page.component';
|
||||||
import { FullFileSectionComponent } from './full/field-components/file-section/full-file-section.component';
|
import { FullFileSectionComponent } from './full/field-components/file-section/full-file-section.component';
|
||||||
|
import { RelatedItemsComponent } from './simple/related-items/related-items-component';
|
||||||
|
import { SearchPageModule } from '../+search-page/search-page.module';
|
||||||
|
import { PublicationComponent } from './simple/item-types/publication/publication.component';
|
||||||
|
import { PersonComponent } from './simple/item-types/person/person.component';
|
||||||
|
import { OrgunitComponent } from './simple/item-types/orgunit/orgunit.component';
|
||||||
|
import { ProjectComponent } from './simple/item-types/project/project.component';
|
||||||
|
import { JournalComponent } from './simple/item-types/journal/journal.component';
|
||||||
|
import { JournalVolumeComponent } from './simple/item-types/journal-volume/journal-volume.component';
|
||||||
|
import { JournalIssueComponent } from './simple/item-types/journal-issue/journal-issue.component';
|
||||||
|
import { ItemComponent } from './simple/item-types/shared/item.component';
|
||||||
import { EditItemPageModule } from './edit-item-page/edit-item-page.module';
|
import { EditItemPageModule } from './edit-item-page/edit-item-page.module';
|
||||||
|
import { MetadataRepresentationListComponent } from './simple/metadata-representation-list/metadata-representation-list.component';
|
||||||
|
import { RelatedEntitiesSearchComponent } from './simple/related-entities/related-entities-search/related-entities-search.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
EditItemPageModule,
|
EditItemPageModule,
|
||||||
ItemPageRoutingModule
|
ItemPageRoutingModule,
|
||||||
|
SearchPageModule
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
ItemPageComponent,
|
ItemPageComponent,
|
||||||
FullItemPageComponent,
|
FullItemPageComponent,
|
||||||
MetadataValuesComponent,
|
|
||||||
MetadataUriValuesComponent,
|
MetadataUriValuesComponent,
|
||||||
MetadataFieldWrapperComponent,
|
|
||||||
ItemPageAuthorFieldComponent,
|
ItemPageAuthorFieldComponent,
|
||||||
ItemPageDateFieldComponent,
|
ItemPageDateFieldComponent,
|
||||||
ItemPageAbstractFieldComponent,
|
ItemPageAbstractFieldComponent,
|
||||||
ItemPageUriFieldComponent,
|
ItemPageUriFieldComponent,
|
||||||
ItemPageTitleFieldComponent,
|
ItemPageTitleFieldComponent,
|
||||||
ItemPageSpecificFieldComponent,
|
ItemPageFieldComponent,
|
||||||
FileSectionComponent,
|
FileSectionComponent,
|
||||||
CollectionsComponent,
|
CollectionsComponent,
|
||||||
FullFileSectionComponent
|
FullFileSectionComponent,
|
||||||
|
PublicationComponent,
|
||||||
|
ProjectComponent,
|
||||||
|
OrgunitComponent,
|
||||||
|
PersonComponent,
|
||||||
|
RelatedItemsComponent,
|
||||||
|
ItemComponent,
|
||||||
|
GenericItemPageFieldComponent,
|
||||||
|
JournalComponent,
|
||||||
|
JournalIssueComponent,
|
||||||
|
JournalVolumeComponent,
|
||||||
|
MetadataRepresentationListComponent,
|
||||||
|
RelatedEntitiesSearchComponent
|
||||||
|
],
|
||||||
|
entryComponents: [
|
||||||
|
PublicationComponent,
|
||||||
|
ProjectComponent,
|
||||||
|
OrgunitComponent,
|
||||||
|
PersonComponent,
|
||||||
|
JournalComponent,
|
||||||
|
JournalIssueComponent,
|
||||||
|
JournalVolumeComponent
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class ItemPageModule {
|
export class ItemPageModule {
|
||||||
|
@@ -2,9 +2,10 @@ import { Injectable } from '@angular/core';
|
|||||||
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, Resolve, RouterStateSnapshot } from '@angular/router';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { getSucceededRemoteData } from '../core/shared/operators';
|
|
||||||
import { ItemDataService } from '../core/data/item-data.service';
|
import { ItemDataService } from '../core/data/item-data.service';
|
||||||
import { Item } from '../core/shared/item.model';
|
import { Item } from '../core/shared/item.model';
|
||||||
|
import { hasValue } from '../shared/empty.util';
|
||||||
|
import { find } from 'rxjs/operators';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class represents a resolver that requests a specific item before the route is activated
|
* This class represents a resolver that requests a specific item before the route is activated
|
||||||
@@ -18,11 +19,13 @@ export class ItemPageResolver implements Resolve<RemoteData<Item>> {
|
|||||||
* Method for resolving an item based on the parameters in the current route
|
* Method for resolving an item based on the parameters in the current route
|
||||||
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
|
||||||
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
|
||||||
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route
|
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
|
||||||
|
* or an error if something went wrong
|
||||||
*/
|
*/
|
||||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
|
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
|
||||||
return this.itemService.findById(route.params.id).pipe(
|
return this.itemService.findById(route.params.id)
|
||||||
getSucceededRemoteData()
|
.pipe(
|
||||||
);
|
find((RD) => hasValue(RD.error) || RD.hasSucceeded),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,41 @@
|
|||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { ItemPageAbstractFieldComponent } from './item-page-abstract-field.component';
|
||||||
|
import { MockTranslateLoader } from '../../../../../shared/testing/mock-translate-loader';
|
||||||
|
import { MetadataValuesComponent } from '../../../../field-components/metadata-values/metadata-values.component';
|
||||||
|
import { mockItemWithMetadataFieldAndValue } from '../item-page-field.component.spec';
|
||||||
|
|
||||||
|
let comp: ItemPageAbstractFieldComponent;
|
||||||
|
let fixture: ComponentFixture<ItemPageAbstractFieldComponent>;
|
||||||
|
|
||||||
|
const mockField = 'dc.description.abstract';
|
||||||
|
const mockValue = 'test value';
|
||||||
|
|
||||||
|
describe('ItemPageAbstractFieldComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [ItemPageAbstractFieldComponent, MetadataValuesComponent],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(ItemPageAbstractFieldComponent, {
|
||||||
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(ItemPageAbstractFieldComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.item = mockItemWithMetadataFieldAndValue(mockField, mockValue);
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should display display the correct metadata value', () => {
|
||||||
|
expect(fixture.nativeElement.innerHTML).toContain(mockValue);
|
||||||
|
});
|
||||||
|
});
|
@@ -1,22 +1,39 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { ItemPageSpecificFieldComponent } from '../item-page-specific-field.component';
|
import { ItemPageFieldComponent } from '../item-page-field.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-page-abstract-field',
|
selector: 'ds-item-page-abstract-field',
|
||||||
templateUrl: './../item-page-specific-field.component.html'
|
templateUrl: '../item-page-field.component.html'
|
||||||
})
|
})
|
||||||
export class ItemPageAbstractFieldComponent extends ItemPageSpecificFieldComponent {
|
/**
|
||||||
|
* This component is used for displaying the abstract (dc.description.abstract) of an item
|
||||||
|
*/
|
||||||
|
export class ItemPageAbstractFieldComponent extends ItemPageFieldComponent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item to display metadata for
|
||||||
|
*/
|
||||||
@Input() item: Item;
|
@Input() item: Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Separator string between multiple values of the metadata fields defined
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
separator: string;
|
separator: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fields (schema.element.qualifier) used to render their values.
|
||||||
|
* In this component, we want to display values for metadata 'dc.description.abstract'
|
||||||
|
*/
|
||||||
fields: string[] = [
|
fields: string[] = [
|
||||||
'dc.description.abstract'
|
'dc.description.abstract'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label i18n key for the rendered metadata
|
||||||
|
*/
|
||||||
label = 'item.page.abstract';
|
label = 'item.page.abstract';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,45 @@
|
|||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { MockTranslateLoader } from '../../../../../shared/testing/mock-translate-loader';
|
||||||
|
import { MetadataValuesComponent } from '../../../../field-components/metadata-values/metadata-values.component';
|
||||||
|
import { mockItemWithMetadataFieldAndValue } from '../item-page-field.component.spec';
|
||||||
|
import { ItemPageAuthorFieldComponent } from './item-page-author-field.component';
|
||||||
|
|
||||||
|
let comp: ItemPageAuthorFieldComponent;
|
||||||
|
let fixture: ComponentFixture<ItemPageAuthorFieldComponent>;
|
||||||
|
|
||||||
|
const mockFields = ['dc.contributor.author', 'dc.creator', 'dc.contributor'];
|
||||||
|
const mockValue = 'test value';
|
||||||
|
|
||||||
|
describe('ItemPageAuthorFieldComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [ItemPageAuthorFieldComponent, MetadataValuesComponent],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(ItemPageAuthorFieldComponent, {
|
||||||
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (const field of mockFields) {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(ItemPageAuthorFieldComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.item = mockItemWithMetadataFieldAndValue(field, mockValue);
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe(`when the item contains metadata for ${field}`, () => {
|
||||||
|
it('should display display the correct metadata value', () => {
|
||||||
|
expect(fixture.nativeElement.innerHTML).toContain(mockValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
@@ -1,24 +1,41 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { ItemPageSpecificFieldComponent } from '../item-page-specific-field.component';
|
import { ItemPageFieldComponent } from '../item-page-field.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-page-author-field',
|
selector: 'ds-item-page-author-field',
|
||||||
templateUrl: './../item-page-specific-field.component.html'
|
templateUrl: '../item-page-field.component.html'
|
||||||
})
|
})
|
||||||
export class ItemPageAuthorFieldComponent extends ItemPageSpecificFieldComponent {
|
/**
|
||||||
|
* This component is used for displaying the author (dc.contributor.author, dc.creator and dc.contributor) metadata of an item
|
||||||
|
*/
|
||||||
|
export class ItemPageAuthorFieldComponent extends ItemPageFieldComponent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item to display metadata for
|
||||||
|
*/
|
||||||
@Input() item: Item;
|
@Input() item: Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Separator string between multiple values of the metadata fields defined
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
separator: string;
|
separator: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fields (schema.element.qualifier) used to render their values.
|
||||||
|
* In this component, we want to display values for metadata 'dc.contributor.author', 'dc.creator' and 'dc.contributor'
|
||||||
|
*/
|
||||||
fields: string[] = [
|
fields: string[] = [
|
||||||
'dc.contributor.author',
|
'dc.contributor.author',
|
||||||
'dc.creator',
|
'dc.creator',
|
||||||
'dc.contributor'
|
'dc.contributor'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label i18n key for the rendered metadata
|
||||||
|
*/
|
||||||
label = 'item.page.author';
|
label = 'item.page.author';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,41 @@
|
|||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { MockTranslateLoader } from '../../../../../shared/testing/mock-translate-loader';
|
||||||
|
import { MetadataValuesComponent } from '../../../../field-components/metadata-values/metadata-values.component';
|
||||||
|
import { mockItemWithMetadataFieldAndValue } from '../item-page-field.component.spec';
|
||||||
|
import { ItemPageDateFieldComponent } from './item-page-date-field.component';
|
||||||
|
|
||||||
|
let comp: ItemPageDateFieldComponent;
|
||||||
|
let fixture: ComponentFixture<ItemPageDateFieldComponent>;
|
||||||
|
|
||||||
|
const mockField = 'dc.date.issued';
|
||||||
|
const mockValue = 'test value';
|
||||||
|
|
||||||
|
describe('ItemPageDateFieldComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [ItemPageDateFieldComponent, MetadataValuesComponent],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(ItemPageDateFieldComponent, {
|
||||||
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(ItemPageDateFieldComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.item = mockItemWithMetadataFieldAndValue(mockField, mockValue);
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should display display the correct metadata value', () => {
|
||||||
|
expect(fixture.nativeElement.innerHTML).toContain(mockValue);
|
||||||
|
});
|
||||||
|
});
|
@@ -1,22 +1,39 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { ItemPageSpecificFieldComponent } from '../item-page-specific-field.component';
|
import { ItemPageFieldComponent } from '../item-page-field.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-page-date-field',
|
selector: 'ds-item-page-date-field',
|
||||||
templateUrl: './../item-page-specific-field.component.html'
|
templateUrl: '../item-page-field.component.html'
|
||||||
})
|
})
|
||||||
export class ItemPageDateFieldComponent extends ItemPageSpecificFieldComponent {
|
/**
|
||||||
|
* This component is used for displaying the issue date (dc.date.issued) metadata of an item
|
||||||
|
*/
|
||||||
|
export class ItemPageDateFieldComponent extends ItemPageFieldComponent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item to display metadata for
|
||||||
|
*/
|
||||||
@Input() item: Item;
|
@Input() item: Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Separator string between multiple values of the metadata fields defined
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
separator = ', ';
|
separator = ', ';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fields (schema.element.qualifier) used to render their values.
|
||||||
|
* In this component, we want to display values for metadata 'dc.date.issued'
|
||||||
|
*/
|
||||||
fields: string[] = [
|
fields: string[] = [
|
||||||
'dc.date.issued'
|
'dc.date.issued'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label i18n key for the rendered metadata
|
||||||
|
*/
|
||||||
label = 'item.page.date';
|
label = 'item.page.date';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,45 @@
|
|||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { MockTranslateLoader } from '../../../../../shared/testing/mock-translate-loader';
|
||||||
|
import { MetadataValuesComponent } from '../../../../field-components/metadata-values/metadata-values.component';
|
||||||
|
import { mockItemWithMetadataFieldAndValue } from '../item-page-field.component.spec';
|
||||||
|
import { GenericItemPageFieldComponent } from './generic-item-page-field.component';
|
||||||
|
|
||||||
|
let comp: GenericItemPageFieldComponent;
|
||||||
|
let fixture: ComponentFixture<GenericItemPageFieldComponent>;
|
||||||
|
|
||||||
|
const mockValue = 'test value';
|
||||||
|
const mockField = 'dc.test';
|
||||||
|
const mockLabel = 'test label';
|
||||||
|
const mockFields = [mockField];
|
||||||
|
|
||||||
|
describe('GenericItemPageFieldComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [GenericItemPageFieldComponent, MetadataValuesComponent],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(GenericItemPageFieldComponent, {
|
||||||
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(GenericItemPageFieldComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.item = mockItemWithMetadataFieldAndValue(mockField, mockValue);
|
||||||
|
comp.fields = mockFields;
|
||||||
|
comp.label = mockLabel;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should display display the correct metadata value', () => {
|
||||||
|
expect(fixture.nativeElement.innerHTML).toContain(mockValue);
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,38 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
|
import { ItemPageFieldComponent } from '../item-page-field.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-generic-item-page-field',
|
||||||
|
templateUrl: '../item-page-field.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
export class GenericItemPageFieldComponent extends ItemPageFieldComponent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item to display metadata for
|
||||||
|
*/
|
||||||
|
@Input() item: Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Separator string between multiple values of the metadata fields defined
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
@Input() separator: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fields (schema.element.qualifier) used to render their values.
|
||||||
|
*/
|
||||||
|
@Input() fields: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label i18n key for the rendered metadata
|
||||||
|
*/
|
||||||
|
@Input() label: string;
|
||||||
|
|
||||||
|
}
|
@@ -1,3 +1,3 @@
|
|||||||
<div class="item-page-specific-field">
|
<div class="item-page-field">
|
||||||
<ds-metadata-values [mdValues]="item?.allMetadata(fields)" [separator]="separator" [label]="label"></ds-metadata-values>
|
<ds-metadata-values [mdValues]="item?.allMetadata(fields)" [separator]="separator" [label]="label"></ds-metadata-values>
|
||||||
</div>
|
</div>
|
@@ -0,0 +1,63 @@
|
|||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
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';
|
||||||
|
|
||||||
|
let comp: ItemPageFieldComponent;
|
||||||
|
let fixture: ComponentFixture<ItemPageFieldComponent>;
|
||||||
|
|
||||||
|
const mockValue = 'test value';
|
||||||
|
const mockField = 'dc.test';
|
||||||
|
const mockLabel = 'test label';
|
||||||
|
const mockFields = [mockField];
|
||||||
|
|
||||||
|
describe('ItemPageFieldComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [ItemPageFieldComponent, MetadataValuesComponent],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(ItemPageFieldComponent, {
|
||||||
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(ItemPageFieldComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.item = mockItemWithMetadataFieldAndValue(mockField, mockValue);
|
||||||
|
comp.fields = mockFields;
|
||||||
|
comp.label = mockLabel;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should display display the correct metadata value', () => {
|
||||||
|
expect(fixture.nativeElement.innerHTML).toContain(mockValue);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export function mockItemWithMetadataFieldAndValue(field: string, value: string): Item {
|
||||||
|
const item = Object.assign(new Item(), {
|
||||||
|
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
|
||||||
|
metadata: new MetadataMap()
|
||||||
|
});
|
||||||
|
item.metadata[field] = [{
|
||||||
|
language: 'en_US',
|
||||||
|
value: value
|
||||||
|
}] as MetadataValue[];
|
||||||
|
return item;
|
||||||
|
}
|
@@ -9,10 +9,13 @@ import { Item } from '../../../../core/shared/item.model';
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
templateUrl: './item-page-specific-field.component.html'
|
templateUrl: './item-page-field.component.html'
|
||||||
})
|
})
|
||||||
export class ItemPageSpecificFieldComponent {
|
export class ItemPageFieldComponent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item to display metadata for
|
||||||
|
*/
|
||||||
@Input() item: Item;
|
@Input() item: Item;
|
||||||
|
|
||||||
/**
|
/**
|
@@ -1,3 +1,6 @@
|
|||||||
<h2 class="item-page-title-field">
|
<h2 class="item-page-title-field">
|
||||||
|
<div *ngIf="item.firstMetadataValue('relationship.type') as type">
|
||||||
|
{{ type.toLowerCase() + '.page.titleprefix' | translate }}
|
||||||
|
</div>
|
||||||
<ds-metadata-values [mdValues]="item?.allMetadata(fields)"></ds-metadata-values>
|
<ds-metadata-values [mdValues]="item?.allMetadata(fields)"></ds-metadata-values>
|
||||||
</h2>
|
</h2>
|
||||||
|
@@ -0,0 +1,41 @@
|
|||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { MockTranslateLoader } from '../../../../../shared/testing/mock-translate-loader';
|
||||||
|
import { MetadataValuesComponent } from '../../../../field-components/metadata-values/metadata-values.component';
|
||||||
|
import { mockItemWithMetadataFieldAndValue } from '../item-page-field.component.spec';
|
||||||
|
import { ItemPageTitleFieldComponent } from './item-page-title-field.component';
|
||||||
|
|
||||||
|
let comp: ItemPageTitleFieldComponent;
|
||||||
|
let fixture: ComponentFixture<ItemPageTitleFieldComponent>;
|
||||||
|
|
||||||
|
const mockField = 'dc.title';
|
||||||
|
const mockValue = 'test value';
|
||||||
|
|
||||||
|
describe('ItemPageTitleFieldComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [ItemPageTitleFieldComponent, MetadataValuesComponent],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(ItemPageTitleFieldComponent, {
|
||||||
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(ItemPageTitleFieldComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.item = mockItemWithMetadataFieldAndValue(mockField, mockValue);
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should display display the correct metadata value', () => {
|
||||||
|
expect(fixture.nativeElement.innerHTML).toContain(mockValue);
|
||||||
|
});
|
||||||
|
});
|
@@ -1,18 +1,32 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { ItemPageSpecificFieldComponent } from '../item-page-specific-field.component';
|
import { ItemPageFieldComponent } from '../item-page-field.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-page-title-field',
|
selector: 'ds-item-page-title-field',
|
||||||
templateUrl: './item-page-title-field.component.html'
|
templateUrl: './item-page-title-field.component.html'
|
||||||
})
|
})
|
||||||
export class ItemPageTitleFieldComponent extends ItemPageSpecificFieldComponent {
|
/**
|
||||||
|
* This component is used for displaying the title (dc.title) of an item
|
||||||
|
*/
|
||||||
|
export class ItemPageTitleFieldComponent extends ItemPageFieldComponent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item to display metadata for
|
||||||
|
*/
|
||||||
@Input() item: Item;
|
@Input() item: Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Separator string between multiple values of the metadata fields defined
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
separator: string;
|
separator: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fields (schema.element.qualifier) used to render their values.
|
||||||
|
* In this component, we want to display values for metadata 'dc.title'
|
||||||
|
*/
|
||||||
fields: string[] = [
|
fields: string[] = [
|
||||||
'dc.title'
|
'dc.title'
|
||||||
];
|
];
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
<div class="item-page-specific-field">
|
<div class="item-page-field">
|
||||||
<ds-metadata-uri-values [mdValues]="item?.allMetadata(fields)" [separator]="separator" [label]="label"></ds-metadata-uri-values>
|
<ds-metadata-uri-values [mdValues]="item?.allMetadata(fields)" [separator]="separator" [label]="label"></ds-metadata-uri-values>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,41 @@
|
|||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { MockTranslateLoader } from '../../../../../shared/testing/mock-translate-loader';
|
||||||
|
import { mockItemWithMetadataFieldAndValue } from '../item-page-field.component.spec';
|
||||||
|
import { ItemPageUriFieldComponent } from './item-page-uri-field.component';
|
||||||
|
import { MetadataUriValuesComponent } from '../../../../field-components/metadata-uri-values/metadata-uri-values.component';
|
||||||
|
|
||||||
|
let comp: ItemPageUriFieldComponent;
|
||||||
|
let fixture: ComponentFixture<ItemPageUriFieldComponent>;
|
||||||
|
|
||||||
|
const mockField = 'dc.identifier.uri';
|
||||||
|
const mockValue = 'test value';
|
||||||
|
|
||||||
|
describe('ItemPageUriFieldComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [ItemPageUriFieldComponent, MetadataUriValuesComponent],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(ItemPageUriFieldComponent, {
|
||||||
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(ItemPageUriFieldComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.item = mockItemWithMetadataFieldAndValue(mockField, mockValue);
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should display display the correct metadata value', () => {
|
||||||
|
expect(fixture.nativeElement.innerHTML).toContain(mockValue);
|
||||||
|
});
|
||||||
|
});
|
@@ -1,22 +1,39 @@
|
|||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { ItemPageSpecificFieldComponent } from '../item-page-specific-field.component';
|
import { ItemPageFieldComponent } from '../item-page-field.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-item-page-uri-field',
|
selector: 'ds-item-page-uri-field',
|
||||||
templateUrl: './item-page-uri-field.component.html'
|
templateUrl: './item-page-uri-field.component.html'
|
||||||
})
|
})
|
||||||
export class ItemPageUriFieldComponent extends ItemPageSpecificFieldComponent {
|
/**
|
||||||
|
* This component is used for displaying the uri (dc.identifier.uri) metadata of an item
|
||||||
|
*/
|
||||||
|
export class ItemPageUriFieldComponent extends ItemPageFieldComponent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item to display metadata for
|
||||||
|
*/
|
||||||
@Input() item: Item;
|
@Input() item: Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Separator string between multiple values of the metadata fields defined
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
separator: string;
|
separator: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fields (schema.element.qualifier) used to render their values.
|
||||||
|
* In this component, we want to display values for metadata 'dc.identifier.uri'
|
||||||
|
*/
|
||||||
fields: string[] = [
|
fields: string[] = [
|
||||||
'dc.identifier.uri'
|
'dc.identifier.uri'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label i18n key for the rendered metadata
|
||||||
|
*/
|
||||||
label = 'item.page.uri';
|
label = 'item.page.uri';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,27 +1,7 @@
|
|||||||
<div class="container" *ngVar="(itemRD$ | async) as itemRD">
|
<div class="container" *ngVar="(itemRD$ | async) as itemRD">
|
||||||
<div class="item-page" *ngIf="itemRD?.hasSucceeded" @fadeInOut>
|
<div class="item-page" *ngIf="itemRD?.hasSucceeded" @fadeInOut>
|
||||||
<div *ngIf="itemRD?.payload as item">
|
<div *ngIf="itemRD?.payload as item">
|
||||||
<ds-item-page-title-field [item]="item"></ds-item-page-title-field>
|
<ds-item-type-switcher [object]="item" [viewMode]="viewMode"></ds-item-type-switcher>
|
||||||
<div class="row">
|
|
||||||
<div class="col-xs-12 col-md-4">
|
|
||||||
<ds-metadata-field-wrapper>
|
|
||||||
<ds-thumbnail [thumbnail]="thumbnail$ | async"></ds-thumbnail>
|
|
||||||
</ds-metadata-field-wrapper>
|
|
||||||
<ds-item-page-file-section [item]="item"></ds-item-page-file-section>
|
|
||||||
<ds-item-page-date-field [item]="item"></ds-item-page-date-field>
|
|
||||||
<ds-item-page-author-field [item]="item"></ds-item-page-author-field>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-12 col-md-6">
|
|
||||||
<ds-item-page-abstract-field [item]="item"></ds-item-page-abstract-field>
|
|
||||||
<ds-item-page-uri-field [item]="item"></ds-item-page-uri-field>
|
|
||||||
<ds-item-page-collections [item]="item"></ds-item-page-collections>
|
|
||||||
<div>
|
|
||||||
<a class="btn btn-outline-primary" [routerLink]="['/items/' + item.id + '/full']">
|
|
||||||
{{"item.page.link.full" | translate}}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ds-error *ngIf="itemRD?.hasFailed" message="{{'error.item' | translate}}"></ds-error>
|
<ds-error *ngIf="itemRD?.hasFailed" message="{{'error.item' | translate}}"></ds-error>
|
||||||
|
@@ -1 +1,9 @@
|
|||||||
@import '../../../styles/variables.scss';
|
@import '../../../styles/variables.scss';
|
||||||
|
@import '../../../styles/mixins.scss';
|
||||||
|
|
||||||
|
@include media-breakpoint-down(md) {
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
91
src/app/+item-page/simple/item-page.component.spec.ts
Normal file
91
src/app/+item-page/simple/item-page.component.spec.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader';
|
||||||
|
import { ItemDataService } from '../../core/data/item-data.service';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { ItemPageComponent } from './item-page.component';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { ActivatedRouteStub } from '../../shared/testing/active-router-stub';
|
||||||
|
import { MetadataService } from '../../core/metadata/metadata.service';
|
||||||
|
import { VarDirective } from '../../shared/utils/var.directive';
|
||||||
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
|
import { Item } from '../../core/shared/item.model';
|
||||||
|
import { PaginatedList } from '../../core/data/paginated-list';
|
||||||
|
import { PageInfo } from '../../core/shared/page-info.model';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { createRelationshipsObservable } from './item-types/shared/item.component.spec';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
|
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
|
||||||
|
metadata: [],
|
||||||
|
relationships: createRelationshipsObservable()
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ItemPageComponent', () => {
|
||||||
|
let comp: ItemPageComponent;
|
||||||
|
let fixture: ComponentFixture<ItemPageComponent>;
|
||||||
|
|
||||||
|
const mockMetadataService = {
|
||||||
|
/* tslint:disable:no-empty */
|
||||||
|
processRemoteData: () => {}
|
||||||
|
/* tslint:enable:no-empty */
|
||||||
|
};
|
||||||
|
const mockRoute = Object.assign(new ActivatedRouteStub(), {
|
||||||
|
data: observableOf({ item: new RemoteData(false, false, true, null, mockItem) })
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
}), BrowserAnimationsModule],
|
||||||
|
declarations: [ItemPageComponent, VarDirective],
|
||||||
|
providers: [
|
||||||
|
{provide: ActivatedRoute, useValue: mockRoute},
|
||||||
|
{provide: ItemDataService, useValue: {}},
|
||||||
|
{provide: MetadataService, useValue: mockMetadataService},
|
||||||
|
{provide: Router, useValue: {}}
|
||||||
|
],
|
||||||
|
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(ItemPageComponent, {
|
||||||
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(ItemPageComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('when the item is loading', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.itemRD$ = observableOf(new RemoteData(true, true, true, null, undefined));
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display a loading component', () => {
|
||||||
|
const loading = fixture.debugElement.query(By.css('ds-loading'));
|
||||||
|
expect(loading.nativeElement).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the item failed loading', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.itemRD$ = observableOf(new RemoteData(false, false, false, null, undefined));
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display an error component', () => {
|
||||||
|
const error = fixture.debugElement.query(By.css('ds-error'));
|
||||||
|
expect(error.nativeElement).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import {mergeMap, filter, map} from 'rxjs/operators';
|
import { mergeMap, filter, map, take, tap } from 'rxjs/operators';
|
||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { ItemDataService } from '../../core/data/item-data.service';
|
import { ItemDataService } from '../../core/data/item-data.service';
|
||||||
@@ -14,6 +14,8 @@ import { MetadataService } from '../../core/metadata/metadata.service';
|
|||||||
|
|
||||||
import { fadeInOut } from '../../shared/animations/fade';
|
import { fadeInOut } from '../../shared/animations/fade';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../shared/empty.util';
|
||||||
|
import { redirectToPageNotFoundOn404 } from '../../core/shared/operators';
|
||||||
|
import { ItemViewMode } from '../../shared/items/item-type-decorator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This component renders a simple item page.
|
* This component renders a simple item page.
|
||||||
@@ -29,28 +31,33 @@ import { hasValue } from '../../shared/empty.util';
|
|||||||
})
|
})
|
||||||
export class ItemPageComponent implements OnInit {
|
export class ItemPageComponent implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item's id
|
||||||
|
*/
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
private sub: any;
|
/**
|
||||||
|
* The item wrapped in a remote-data object
|
||||||
|
*/
|
||||||
itemRD$: Observable<RemoteData<Item>>;
|
itemRD$: Observable<RemoteData<Item>>;
|
||||||
|
|
||||||
thumbnail$: Observable<Bitstream>;
|
/**
|
||||||
|
* The view-mode we're currently on
|
||||||
|
*/
|
||||||
|
viewMode = ItemViewMode.Full;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
private router: Router,
|
||||||
private items: ItemDataService,
|
private items: ItemDataService,
|
||||||
private metadataService: MetadataService
|
private metadataService: MetadataService,
|
||||||
) {
|
) { }
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.itemRD$ = this.route.data.pipe(map((data) => data.item));
|
this.itemRD$ = this.route.data.pipe(
|
||||||
|
map((data) => data.item as RemoteData<Item>),
|
||||||
|
redirectToPageNotFoundOn404(this.router)
|
||||||
|
);
|
||||||
this.metadataService.processRemoteData(this.itemRD$);
|
this.metadataService.processRemoteData(this.itemRD$);
|
||||||
this.thumbnail$ = this.itemRD$.pipe(
|
|
||||||
map((rd: RemoteData<Item>) => rd.payload),
|
|
||||||
filter((item: Item) => hasValue(item)),
|
|
||||||
mergeMap((item: Item) => item.getThumbnail()),);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,50 @@
|
|||||||
|
<h2 class="item-page-title-field">
|
||||||
|
{{'journalissue.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="item?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||||
|
</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-md-4">
|
||||||
|
<ds-metadata-field-wrapper>
|
||||||
|
<ds-thumbnail [thumbnail]="this.item.getThumbnail() | async"></ds-thumbnail>
|
||||||
|
</ds-metadata-field-wrapper>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['journalissue.identifier.number']"
|
||||||
|
[label]="'journalissue.page.number'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['journalissue.issuedate']"
|
||||||
|
[label]="'journalissue.page.issuedate'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['journal.title']"
|
||||||
|
[label]="'journalissue.page.journal-title'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['journal.identifier.issn']"
|
||||||
|
[label]="'journalissue.page.journal-issn'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-6">
|
||||||
|
<ds-related-items
|
||||||
|
[items]="volumes$ | async"
|
||||||
|
[label]="'relationships.isSingleVolumeOf' | translate">
|
||||||
|
</ds-related-items>
|
||||||
|
<ds-related-items
|
||||||
|
class="mb-1 mt-1"
|
||||||
|
[items]="publications$ | async"
|
||||||
|
[label]="'relationships.isPublicationOfJournalIssue' | translate">
|
||||||
|
</ds-related-items>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['journalissue.identifier.description']"
|
||||||
|
[label]="'journalissue.page.description'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['journalissue.identifier.keyword']"
|
||||||
|
[label]="'journalissue.page.keyword'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-outline-primary" [routerLink]="['/items/' + item.id + '/full']">
|
||||||
|
{{"item.page.link.full" | translate}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1 @@
|
|||||||
|
@import '../../../../../styles/variables.scss';
|
@@ -0,0 +1,40 @@
|
|||||||
|
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 { createRelationshipsObservable, getItemPageFieldsTest } from '../shared/item.component.spec';
|
||||||
|
import { JournalIssueComponent } from './journal-issue.component';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
|
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
|
||||||
|
metadata: {
|
||||||
|
'journalissue.identifier.number': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: '1234'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'journalissue.issuedate': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: '2018'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'journalissue.identifier.description': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'desc'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'journalissue.identifier.keyword': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'keyword'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
relationships: createRelationshipsObservable()
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('JournalIssueComponent', getItemPageFieldsTest(mockItem, JournalIssueComponent));
|
@@ -0,0 +1,51 @@
|
|||||||
|
import { Component, Inject } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
|
||||||
|
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||||
|
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||||
|
import { ItemComponent } from '../shared/item.component';
|
||||||
|
import { filterRelationsByTypeLabel, relationsToItems } from '../shared/item-relationships-utils';
|
||||||
|
|
||||||
|
@rendersItemType('JournalIssue', ItemViewMode.Full)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-journal-issue',
|
||||||
|
styleUrls: ['./journal-issue.component.scss'],
|
||||||
|
templateUrl: './journal-issue.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* The component for displaying metadata and relations of an item of the type Journal Issue
|
||||||
|
*/
|
||||||
|
export class JournalIssueComponent extends ItemComponent {
|
||||||
|
/**
|
||||||
|
* The volumes related to this journal issue
|
||||||
|
*/
|
||||||
|
volumes$: Observable<Item[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The publications related to this journal issue
|
||||||
|
*/
|
||||||
|
publications$: Observable<Item[]>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(ITEM) public item: Item,
|
||||||
|
private ids: ItemDataService
|
||||||
|
) {
|
||||||
|
super(item);
|
||||||
|
}
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
|
||||||
|
if (isNotEmpty(this.resolvedRelsAndTypes$)) {
|
||||||
|
this.volumes$ = this.resolvedRelsAndTypes$.pipe(
|
||||||
|
filterRelationsByTypeLabel('isJournalVolumeOfIssue'),
|
||||||
|
relationsToItems(this.item.id, this.ids)
|
||||||
|
);
|
||||||
|
this.publications$ = this.resolvedRelsAndTypes$.pipe(
|
||||||
|
filterRelationsByTypeLabel('isPublicationOfJournalIssue'),
|
||||||
|
relationsToItems(this.item.id, this.ids)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,37 @@
|
|||||||
|
<h2 class="item-page-title-field">
|
||||||
|
{{'journalvolume.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="item?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||||
|
</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-md-4">
|
||||||
|
<ds-metadata-field-wrapper>
|
||||||
|
<ds-thumbnail [thumbnail]="this.item.getThumbnail() | async"></ds-thumbnail>
|
||||||
|
</ds-metadata-field-wrapper>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['journalvolume.identifier.volume']"
|
||||||
|
[label]="'journalvolume.page.volume'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['journalvolume.issuedate']"
|
||||||
|
[label]="'journalvolume.page.issuedate'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-6">
|
||||||
|
<ds-related-items
|
||||||
|
[items]="journals$ | async"
|
||||||
|
[label]="'relationships.isSingleJournalOf' | translate">
|
||||||
|
</ds-related-items>
|
||||||
|
<ds-related-items
|
||||||
|
[items]="issues$ | async"
|
||||||
|
[label]="'relationships.isIssueOf' | translate">
|
||||||
|
</ds-related-items>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['journalvolume.identifier.description']"
|
||||||
|
[label]="'journalvolume.page.description'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-outline-primary" [routerLink]="['/items/' + item.id + '/full']">
|
||||||
|
{{"item.page.link.full" | translate}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1 @@
|
|||||||
|
@import '../../../../../styles/variables.scss';
|
@@ -0,0 +1,34 @@
|
|||||||
|
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 { createRelationshipsObservable, getItemPageFieldsTest } from '../shared/item.component.spec';
|
||||||
|
import { JournalVolumeComponent } from './journal-volume.component';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
|
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
|
||||||
|
metadata: {
|
||||||
|
'journalvolume.identifier.volume': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: '1234'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'journalvolume.issuedate': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: '2018'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'journalvolume.identifier.description': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'desc'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
relationships: createRelationshipsObservable()
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('JournalVolumeComponent', getItemPageFieldsTest(mockItem, JournalVolumeComponent));
|
@@ -0,0 +1,51 @@
|
|||||||
|
import { Component, Inject } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
|
||||||
|
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||||
|
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||||
|
import { ItemComponent } from '../shared/item.component';
|
||||||
|
import { filterRelationsByTypeLabel, relationsToItems } from '../shared/item-relationships-utils';
|
||||||
|
|
||||||
|
@rendersItemType('JournalVolume', ItemViewMode.Full)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-journal-volume',
|
||||||
|
styleUrls: ['./journal-volume.component.scss'],
|
||||||
|
templateUrl: './journal-volume.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* The component for displaying metadata and relations of an item of the type Journal Volume
|
||||||
|
*/
|
||||||
|
export class JournalVolumeComponent extends ItemComponent {
|
||||||
|
/**
|
||||||
|
* The journals related to this journal volume
|
||||||
|
*/
|
||||||
|
journals$: Observable<Item[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The journal issues related to this journal volume
|
||||||
|
*/
|
||||||
|
issues$: Observable<Item[]>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(ITEM) public item: Item,
|
||||||
|
private ids: ItemDataService
|
||||||
|
) {
|
||||||
|
super(item);
|
||||||
|
}
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
|
||||||
|
if (isNotEmpty(this.resolvedRelsAndTypes$)) {
|
||||||
|
this.journals$ = this.resolvedRelsAndTypes$.pipe(
|
||||||
|
filterRelationsByTypeLabel('isJournalOfVolume'),
|
||||||
|
relationsToItems(this.item.id, this.ids)
|
||||||
|
);
|
||||||
|
this.issues$ = this.resolvedRelsAndTypes$.pipe(
|
||||||
|
filterRelationsByTypeLabel('isIssueOfJournalVolume'),
|
||||||
|
relationsToItems(this.item.id, this.ids)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,42 @@
|
|||||||
|
<h2 class="item-page-title-field">
|
||||||
|
{{'journal.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="item?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||||
|
</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-md-4">
|
||||||
|
<ds-metadata-field-wrapper>
|
||||||
|
<ds-thumbnail [thumbnail]="this.item.getThumbnail() | async"></ds-thumbnail>
|
||||||
|
</ds-metadata-field-wrapper>
|
||||||
|
<ds-generic-item-page-field class="item-page-fields" [item]="item"
|
||||||
|
[fields]="['journal.identifier.issn']"
|
||||||
|
[label]="'journal.page.issn'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field class="item-page-fields" [item]="item"
|
||||||
|
[fields]="['journal.publisher']"
|
||||||
|
[label]="'journal.page.publisher'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['journal.contributor.editor']"
|
||||||
|
[label]="'journal.page.editor'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-6">
|
||||||
|
<ds-related-items
|
||||||
|
[items]="volumes$ | async"
|
||||||
|
[label]="'relationships.isVolumeOf' | translate">
|
||||||
|
</ds-related-items>
|
||||||
|
<ds-generic-item-page-field class="item-page-fields" [item]="item"
|
||||||
|
[fields]="['journal.identifier.description']"
|
||||||
|
[label]="'journal.page.description'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-outline-primary" [routerLink]="['/items/' + item.id + '/full']">
|
||||||
|
{{"item.page.link.full" | translate}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 w-100">
|
||||||
|
<ds-related-entities-search [item]="item"
|
||||||
|
[relationType]="'isJournalOfPublication'">
|
||||||
|
</ds-related-entities-search>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1 @@
|
|||||||
|
@import '../../../../../styles/variables.scss';
|
@@ -0,0 +1,92 @@
|
|||||||
|
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 { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
|
||||||
|
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';
|
||||||
|
|
||||||
|
let comp: JournalComponent;
|
||||||
|
let fixture: ComponentFixture<JournalComponent>;
|
||||||
|
|
||||||
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
|
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
|
||||||
|
metadata: {
|
||||||
|
'journal.identifier.issn': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: '1234'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'journal.publisher': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'a publisher'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'journal.identifier.description': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'desc'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('JournalComponent', () => {
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [JournalComponent, GenericItemPageFieldComponent, TruncatePipe],
|
||||||
|
providers: [
|
||||||
|
{provide: ITEM, useValue: mockItem},
|
||||||
|
{provide: ItemDataService, useValue: {}},
|
||||||
|
{provide: TruncatableService, useValue: {}}
|
||||||
|
],
|
||||||
|
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(JournalComponent, {
|
||||||
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(JournalComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (const key of Object.keys(mockItem.metadata)) {
|
||||||
|
it(`should be calling a component with metadata field ${key}`, () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('.item-page-fields'));
|
||||||
|
expect(containsFieldInput(fields, key)).toBeTruthy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function containsFieldInput(fields: DebugElement[], metadataKey: string): boolean {
|
||||||
|
for (const field of fields) {
|
||||||
|
const fieldComp = field.componentInstance;
|
||||||
|
if (isNotEmpty(fieldComp.fields)) {
|
||||||
|
if (fieldComp.fields.indexOf(metadataKey) > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
@@ -0,0 +1,42 @@
|
|||||||
|
import { Component, Inject } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
|
||||||
|
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||||
|
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||||
|
import { ItemComponent } from '../shared/item.component';
|
||||||
|
import { filterRelationsByTypeLabel, relationsToItems } from '../shared/item-relationships-utils';
|
||||||
|
|
||||||
|
@rendersItemType('Journal', ItemViewMode.Full)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-journal',
|
||||||
|
styleUrls: ['./journal.component.scss'],
|
||||||
|
templateUrl: './journal.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* The component for displaying metadata and relations of an item of the type Journal
|
||||||
|
*/
|
||||||
|
export class JournalComponent extends ItemComponent {
|
||||||
|
/**
|
||||||
|
* The volumes related to this journal
|
||||||
|
*/
|
||||||
|
volumes$: Observable<Item[]>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(ITEM) public item: Item,
|
||||||
|
private ids: ItemDataService
|
||||||
|
) {
|
||||||
|
super(item);
|
||||||
|
}
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
|
||||||
|
if (isNotEmpty(this.resolvedRelsAndTypes$)) {
|
||||||
|
this.volumes$ = this.resolvedRelsAndTypes$.pipe(
|
||||||
|
filterRelationsByTypeLabel('isVolumeOfJournal'),
|
||||||
|
relationsToItems(this.item.id, this.ids)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,49 @@
|
|||||||
|
<h2 class="item-page-title-field">
|
||||||
|
{{'orgunit.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="item?.allMetadata(['orgunit.identifier.name'])"></ds-metadata-values>
|
||||||
|
</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-md-4">
|
||||||
|
<ds-metadata-field-wrapper>
|
||||||
|
<ds-thumbnail [thumbnail]="this.item.getThumbnail() | async" [defaultImage]="'assets/images/orgunit-placeholder.svg'"></ds-thumbnail>
|
||||||
|
</ds-metadata-field-wrapper>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['orgunit.identifier.dateestablished']"
|
||||||
|
[label]="'orgunit.page.dateestablished'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['orgunit.identifier.city']"
|
||||||
|
[label]="'orgunit.page.city'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['orgunit.identifier.country']"
|
||||||
|
[label]="'orgunit.page.country'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['orgunit.identifier.id']"
|
||||||
|
[label]="'orgunit.page.id'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-6">
|
||||||
|
<ds-related-items
|
||||||
|
[items]="people$ | async"
|
||||||
|
[label]="'relationships.isPersonOf' | translate">
|
||||||
|
</ds-related-items>
|
||||||
|
<ds-related-items
|
||||||
|
[items]="projects$ | async"
|
||||||
|
[label]="'relationships.isProjectOf' | translate">
|
||||||
|
</ds-related-items>
|
||||||
|
<ds-related-items
|
||||||
|
[items]="publications$ | async"
|
||||||
|
[label]="'relationships.isPublicationOf' | translate">
|
||||||
|
</ds-related-items>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['orgunit.identifier.description']"
|
||||||
|
[label]="'orgunit.page.description'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-outline-primary" [routerLink]="['/items/' + item.id + '/full']">
|
||||||
|
{{"item.page.link.full" | translate}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1 @@
|
|||||||
|
@import '../../../../../styles/variables.scss';
|
@@ -0,0 +1,46 @@
|
|||||||
|
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 { createRelationshipsObservable, getItemPageFieldsTest } from '../shared/item.component.spec';
|
||||||
|
import { OrgunitComponent } from './orgunit.component';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
|
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
|
||||||
|
metadata: {
|
||||||
|
'orgunit.identifier.dateestablished': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: '2018'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'orgunit.identifier.city': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'New York'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'orgunit.identifier.country': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'USA'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'orgunit.identifier.id': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: '1'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'orgunit.identifier.description': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'desc'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
relationships: createRelationshipsObservable()
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('OrgUnitComponent', getItemPageFieldsTest(mockItem, OrgunitComponent));
|
@@ -0,0 +1,62 @@
|
|||||||
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
|
||||||
|
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||||
|
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||||
|
import { ItemComponent } from '../shared/item.component';
|
||||||
|
import { filterRelationsByTypeLabel, relationsToItems } from '../shared/item-relationships-utils';
|
||||||
|
|
||||||
|
@rendersItemType('OrgUnit', ItemViewMode.Full)
|
||||||
|
@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 implements OnInit {
|
||||||
|
/**
|
||||||
|
* The people related to this organisation unit
|
||||||
|
*/
|
||||||
|
people$: Observable<Item[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The projects related to this organisation unit
|
||||||
|
*/
|
||||||
|
projects$: Observable<Item[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The publications related to this organisation unit
|
||||||
|
*/
|
||||||
|
publications$: Observable<Item[]>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(ITEM) public item: Item,
|
||||||
|
private ids: ItemDataService
|
||||||
|
) {
|
||||||
|
super(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
|
||||||
|
if (isNotEmpty(this.resolvedRelsAndTypes$)) {
|
||||||
|
this.people$ = this.resolvedRelsAndTypes$.pipe(
|
||||||
|
filterRelationsByTypeLabel('isPersonOfOrgUnit'),
|
||||||
|
relationsToItems(this.item.id, this.ids)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.projects$ = this.resolvedRelsAndTypes$.pipe(
|
||||||
|
filterRelationsByTypeLabel('isProjectOfOrgUnit'),
|
||||||
|
relationsToItems(this.item.id, this.ids)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.publications$ = this.resolvedRelsAndTypes$.pipe(
|
||||||
|
filterRelationsByTypeLabel('isPublicationOfOrgUnit'),
|
||||||
|
relationsToItems(this.item.id, this.ids)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
@@ -0,0 +1,58 @@
|
|||||||
|
<h2 class="item-page-title-field">
|
||||||
|
{{'person.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="item?.allMetadata(['dc.contributor.author'])"></ds-metadata-values>
|
||||||
|
</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-md-4">
|
||||||
|
<ds-metadata-field-wrapper>
|
||||||
|
<ds-thumbnail [thumbnail]="this.item.getThumbnail() | async" [defaultImage]="'assets/images/person-placeholder.svg'"></ds-thumbnail>
|
||||||
|
</ds-metadata-field-wrapper>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['person.identifier.email']"
|
||||||
|
[label]="'person.page.email'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['person.identifier.orcid']"
|
||||||
|
[label]="'person.page.orcid'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['person.identifier.birthdate']"
|
||||||
|
[label]="'person.page.birthdate'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['person.identifier.staffid']"
|
||||||
|
[label]="'person.page.staffid'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-6">
|
||||||
|
<ds-related-items
|
||||||
|
[items]="projects$ | async"
|
||||||
|
[label]="'relationships.isProjectOf' | translate">
|
||||||
|
</ds-related-items>
|
||||||
|
<ds-related-items
|
||||||
|
[items]="orgUnits$ | async"
|
||||||
|
[label]="'relationships.isOrgUnitOf' | translate">
|
||||||
|
</ds-related-items>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['person.identifier.jobtitle']"
|
||||||
|
[label]="'person.page.jobtitle'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['person.identifier.lastname']"
|
||||||
|
[label]="'person.page.lastname'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['person.identifier.firstname']"
|
||||||
|
[label]="'person.page.firstname'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-outline-primary" [routerLink]="['/items/' + item.id + '/full']">
|
||||||
|
{{"item.page.link.full" | translate}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5 w-100">
|
||||||
|
<ds-related-entities-search [item]="item"
|
||||||
|
[relationType]="'isAuthorOfPublication'">
|
||||||
|
</ds-related-entities-search>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1 @@
|
|||||||
|
@import '../../../../../styles/variables.scss';
|
@@ -0,0 +1,58 @@
|
|||||||
|
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 { createRelationshipsObservable, getItemPageFieldsTest } from '../shared/item.component.spec';
|
||||||
|
import { PersonComponent } from './person.component';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
|
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
|
||||||
|
metadata: {
|
||||||
|
'person.identifier.email': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'fake@email.com'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'person.identifier.orcid': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'ORCID-1'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'person.identifier.birthdate': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: '1993'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'person.identifier.staffid': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: '1'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'person.identifier.jobtitle': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'Developer'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'person.identifier.lastname': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'Doe'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'person.identifier.firstname': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'John'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
relationships: createRelationshipsObservable()
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PersonComponent', getItemPageFieldsTest(mockItem, PersonComponent));
|
@@ -0,0 +1,77 @@
|
|||||||
|
import { Component, Inject } from '@angular/core';
|
||||||
|
import { Observable , of as observableOf } from 'rxjs';
|
||||||
|
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
|
||||||
|
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||||
|
import { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
|
||||||
|
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||||
|
import { ItemComponent } from '../shared/item.component';
|
||||||
|
import { filterRelationsByTypeLabel, relationsToItems } from '../shared/item-relationships-utils';
|
||||||
|
|
||||||
|
@rendersItemType('Person', ItemViewMode.Full)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-person',
|
||||||
|
styleUrls: ['./person.component.scss'],
|
||||||
|
templateUrl: './person.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* The component for displaying metadata and relations of an item of the type Person
|
||||||
|
*/
|
||||||
|
export class PersonComponent extends ItemComponent {
|
||||||
|
/**
|
||||||
|
* The publications related to this person
|
||||||
|
*/
|
||||||
|
publications$: Observable<Item[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The projects related to this person
|
||||||
|
*/
|
||||||
|
projects$: Observable<Item[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The organisation units related to this person
|
||||||
|
*/
|
||||||
|
orgUnits$: Observable<Item[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The applied fixed filter
|
||||||
|
*/
|
||||||
|
fixedFilter$: Observable<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The query used for applying the fixed filter
|
||||||
|
*/
|
||||||
|
fixedFilterQuery: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(ITEM) public item: Item,
|
||||||
|
private ids: ItemDataService,
|
||||||
|
private fixedFilterService: SearchFixedFilterService
|
||||||
|
) {
|
||||||
|
super(item);
|
||||||
|
}
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
|
||||||
|
if (isNotEmpty(this.resolvedRelsAndTypes$)) {
|
||||||
|
this.publications$ = this.resolvedRelsAndTypes$.pipe(
|
||||||
|
filterRelationsByTypeLabel('isPublicationOfAuthor'),
|
||||||
|
relationsToItems(this.item.id, this.ids)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.projects$ = this.resolvedRelsAndTypes$.pipe(
|
||||||
|
filterRelationsByTypeLabel('isProjectOfPerson'),
|
||||||
|
relationsToItems(this.item.id, this.ids)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.orgUnits$ = this.resolvedRelsAndTypes$.pipe(
|
||||||
|
filterRelationsByTypeLabel('isOrgUnitOfPerson'),
|
||||||
|
relationsToItems(this.item.id, this.ids)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.fixedFilterQuery = this.fixedFilterService.getQueryByRelations('isAuthorOfPublication', this.item.id);
|
||||||
|
this.fixedFilter$ = observableOf('publication');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,57 @@
|
|||||||
|
<h2 class="item-page-title-field">
|
||||||
|
{{'project.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="item?.allMetadata(['project.identifier.name'])"></ds-metadata-values>
|
||||||
|
</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-md-4">
|
||||||
|
<ds-metadata-field-wrapper>
|
||||||
|
<ds-thumbnail [thumbnail]="this.item.getThumbnail() | async" [defaultImage]="'assets/images/project-placeholder.svg'"></ds-thumbnail>
|
||||||
|
</ds-metadata-field-wrapper>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['project.identifier.status']"
|
||||||
|
[label]="'project.page.status'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-metadata-representation-list
|
||||||
|
[label]="'project.page.contributor' | translate"
|
||||||
|
[representations]="contributors$ | async">
|
||||||
|
</ds-metadata-representation-list>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['project.identifier.funder']"
|
||||||
|
[label]="'project.page.funder'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['project.identifier.id']"
|
||||||
|
[label]="'project.page.id'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['project.identifier.expectedcompletion']"
|
||||||
|
[label]="'project.page.expectedcompletion'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-6">
|
||||||
|
<ds-related-items
|
||||||
|
[items]="people$ | async"
|
||||||
|
[label]="'relationships.isPersonOf' | translate">
|
||||||
|
</ds-related-items>
|
||||||
|
<ds-related-items
|
||||||
|
[items]="publications$ | async"
|
||||||
|
[label]="'relationships.isPublicationOf' | translate">
|
||||||
|
</ds-related-items>
|
||||||
|
<ds-related-items
|
||||||
|
[items]="orgUnits$ | async"
|
||||||
|
[label]="'relationships.isOrgUnitOf' | translate">
|
||||||
|
</ds-related-items>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['project.identifier.description']"
|
||||||
|
[label]="'project.page.description'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['project.identifier.keyword']"
|
||||||
|
[label]="'project.page.keyword'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-outline-primary" [routerLink]="['/items/' + item.id + '/full']">
|
||||||
|
{{"item.page.link.full" | translate}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1 @@
|
|||||||
|
@import '../../../../../styles/variables.scss';
|
@@ -0,0 +1,46 @@
|
|||||||
|
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 { createRelationshipsObservable, getItemPageFieldsTest } from '../shared/item.component.spec';
|
||||||
|
import { ProjectComponent } from './project.component';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
|
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
|
||||||
|
metadata: {
|
||||||
|
'project.identifier.status': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'published'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'project.identifier.id': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: '1'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'project.identifier.expectedcompletion': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'exp comp'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'project.identifier.description': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'keyword'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'project.identifier.keyword': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'keyword'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
relationships: createRelationshipsObservable()
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('ProjectComponent', getItemPageFieldsTest(mockItem, ProjectComponent));
|
@@ -0,0 +1,71 @@
|
|||||||
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { MetadataRepresentation } from '../../../../core/shared/metadata-representation/metadata-representation.model';
|
||||||
|
import { ItemViewMode, rendersItemType } from '../../../../shared/items/item-type-decorator';
|
||||||
|
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||||
|
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||||
|
import { ItemComponent } from '../shared/item.component';
|
||||||
|
import { filterRelationsByTypeLabel, relationsToItems } from '../shared/item-relationships-utils';
|
||||||
|
|
||||||
|
@rendersItemType('Project', ItemViewMode.Full)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-project',
|
||||||
|
styleUrls: ['./project.component.scss'],
|
||||||
|
templateUrl: './project.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* The component for displaying metadata and relations of an item of the type Project
|
||||||
|
*/
|
||||||
|
export class ProjectComponent extends ItemComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* The contributors related to this project
|
||||||
|
*/
|
||||||
|
contributors$: Observable<MetadataRepresentation[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The people related to this project
|
||||||
|
*/
|
||||||
|
people$: Observable<Item[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The publications related to this project
|
||||||
|
*/
|
||||||
|
publications$: Observable<Item[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The organisation units related to this project
|
||||||
|
*/
|
||||||
|
orgUnits$: Observable<Item[]>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(ITEM) public item: Item,
|
||||||
|
private ids: ItemDataService
|
||||||
|
) {
|
||||||
|
super(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
|
||||||
|
if (isNotEmpty(this.resolvedRelsAndTypes$)) {
|
||||||
|
this.contributors$ = this.buildRepresentations('OrgUnit', 'project.contributor.other', this.ids);
|
||||||
|
|
||||||
|
this.people$ = this.resolvedRelsAndTypes$.pipe(
|
||||||
|
filterRelationsByTypeLabel('isPersonOfProject'),
|
||||||
|
relationsToItems(this.item.id, this.ids)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.publications$ = this.resolvedRelsAndTypes$.pipe(
|
||||||
|
filterRelationsByTypeLabel('isPublicationOfProject'),
|
||||||
|
relationsToItems(this.item.id, this.ids)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.orgUnits$ = this.resolvedRelsAndTypes$.pipe(
|
||||||
|
filterRelationsByTypeLabel('isOrgUnitOfProject'),
|
||||||
|
relationsToItems(this.item.id, this.ids)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,60 @@
|
|||||||
|
<h2 class="item-page-title-field">
|
||||||
|
{{'publication.page.titleprefix' | translate}}<ds-metadata-values [mdValues]="item?.allMetadata(['dc.title'])"></ds-metadata-values>
|
||||||
|
</h2>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-md-4">
|
||||||
|
<ds-metadata-field-wrapper>
|
||||||
|
<ds-thumbnail [thumbnail]="this.item.getThumbnail() | async"></ds-thumbnail>
|
||||||
|
</ds-metadata-field-wrapper>
|
||||||
|
<ds-item-page-file-section [item]="item"></ds-item-page-file-section>
|
||||||
|
<ds-item-page-date-field [item]="item"></ds-item-page-date-field>
|
||||||
|
<ds-item-page-author-field *ngIf="!(authors$ | async)" [item]="item"></ds-item-page-author-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['journal.title']"
|
||||||
|
[label]="'publication.page.journal-title'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['journal.identifier.issn']"
|
||||||
|
[label]="'publication.page.journal-issn'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['journalvolume.identifier.name']"
|
||||||
|
[label]="'publication.page.volume-title'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-6">
|
||||||
|
<ds-metadata-representation-list
|
||||||
|
[label]="'relationships.isAuthorOf' | translate"
|
||||||
|
[representations]="authors$ | async">
|
||||||
|
</ds-metadata-representation-list>
|
||||||
|
<ds-related-items
|
||||||
|
[items]="projects$ | async"
|
||||||
|
[label]="'relationships.isProjectOf' | translate">
|
||||||
|
</ds-related-items>
|
||||||
|
<ds-related-items
|
||||||
|
[items]="orgUnits$ | async"
|
||||||
|
[label]="'relationships.isOrgUnitOf' | translate">
|
||||||
|
</ds-related-items>
|
||||||
|
<ds-related-items
|
||||||
|
[items]="journalIssues$ | async"
|
||||||
|
[label]="'relationships.isJournalIssueOf' | translate">
|
||||||
|
</ds-related-items>
|
||||||
|
<ds-item-page-abstract-field [item]="item"></ds-item-page-abstract-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['dc.subject']"
|
||||||
|
[separator]="','"
|
||||||
|
[label]="'item.page.subject'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-generic-item-page-field [item]="item"
|
||||||
|
[fields]="['dc.identifier.citation']"
|
||||||
|
[label]="'item.page.citation'">
|
||||||
|
</ds-generic-item-page-field>
|
||||||
|
<ds-item-page-uri-field [item]="item"></ds-item-page-uri-field>
|
||||||
|
<ds-item-page-collections [item]="item"></ds-item-page-collections>
|
||||||
|
<div>
|
||||||
|
<a class="btn btn-outline-primary" [routerLink]="['/items/' + item.id + '/full']">
|
||||||
|
{{"item.page.link.full" | translate}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
@@ -0,0 +1 @@
|
|||||||
|
@import '../../../../../styles/variables.scss';
|
@@ -0,0 +1,90 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
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 { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
|
||||||
|
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { createRelationshipsObservable } from '../shared/item.component.spec';
|
||||||
|
import { PublicationComponent } from './publication.component';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { MetadataMap } from '../../../../core/shared/metadata.models';
|
||||||
|
|
||||||
|
const mockItem: Item = Object.assign(new Item(), {
|
||||||
|
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
|
||||||
|
metadata: new MetadataMap(),
|
||||||
|
relationships: createRelationshipsObservable()
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PublicationComponent', () => {
|
||||||
|
let comp: PublicationComponent;
|
||||||
|
let fixture: ComponentFixture<PublicationComponent>;
|
||||||
|
|
||||||
|
const searchFixedFilterServiceStub = {
|
||||||
|
/* tslint:disable:no-empty */
|
||||||
|
getQueryByRelations: () => {}
|
||||||
|
/* tslint:enable:no-empty */
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [PublicationComponent, GenericItemPageFieldComponent, TruncatePipe],
|
||||||
|
providers: [
|
||||||
|
{provide: ITEM, useValue: mockItem},
|
||||||
|
{provide: ItemDataService, useValue: {}},
|
||||||
|
{provide: SearchFixedFilterService, useValue: searchFixedFilterServiceStub},
|
||||||
|
{provide: TruncatableService, useValue: {}}
|
||||||
|
],
|
||||||
|
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(PublicationComponent, {
|
||||||
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(PublicationComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should contain a component to display the date', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-date-field'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a component to display the author', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-author-field'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a component to display the abstract', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-abstract-field'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a component to display the uri', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-uri-field'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a component to display the collections', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-collections'));
|
||||||
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,74 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, Inject, OnInit } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import {
|
||||||
|
DEFAULT_ITEM_TYPE, ItemViewMode,
|
||||||
|
rendersItemType
|
||||||
|
} from '../../../../shared/items/item-type-decorator';
|
||||||
|
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||||
|
import { ItemComponent } from '../shared/item.component';
|
||||||
|
import { MetadataRepresentation } from '../../../../core/shared/metadata-representation/metadata-representation.model';
|
||||||
|
import { filterRelationsByTypeLabel, relationsToItems } from '../shared/item-relationships-utils';
|
||||||
|
|
||||||
|
@rendersItemType('Publication', ItemViewMode.Full)
|
||||||
|
@rendersItemType(DEFAULT_ITEM_TYPE, ItemViewMode.Full)
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-publication',
|
||||||
|
styleUrls: ['./publication.component.scss'],
|
||||||
|
templateUrl: './publication.component.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class PublicationComponent extends ItemComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* The authors related to this publication
|
||||||
|
*/
|
||||||
|
authors$: Observable<MetadataRepresentation[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The projects related to this publication
|
||||||
|
*/
|
||||||
|
projects$: Observable<Item[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The organisation units related to this publication
|
||||||
|
*/
|
||||||
|
orgUnits$: Observable<Item[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The journal issues related to this publication
|
||||||
|
*/
|
||||||
|
journalIssues$: Observable<Item[]>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(ITEM) public item: Item,
|
||||||
|
private ids: ItemDataService
|
||||||
|
) {
|
||||||
|
super(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
super.ngOnInit();
|
||||||
|
|
||||||
|
if (this.resolvedRelsAndTypes$) {
|
||||||
|
|
||||||
|
this.authors$ = this.buildRepresentations('Person', 'dc.contributor.author', this.ids);
|
||||||
|
|
||||||
|
this.projects$ = this.resolvedRelsAndTypes$.pipe(
|
||||||
|
filterRelationsByTypeLabel('isProjectOfPublication'),
|
||||||
|
relationsToItems(this.item.id, this.ids)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.orgUnits$ = this.resolvedRelsAndTypes$.pipe(
|
||||||
|
filterRelationsByTypeLabel('isOrgUnitOfPublication'),
|
||||||
|
relationsToItems(this.item.id, this.ids)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.journalIssues$ = this.resolvedRelsAndTypes$.pipe(
|
||||||
|
filterRelationsByTypeLabel('isJournalIssueOfPublication'),
|
||||||
|
relationsToItems(this.item.id, this.ids)
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,121 @@
|
|||||||
|
import { ItemMetadataRepresentation } from '../../../../core/shared/metadata-representation/item/item-metadata-representation.model';
|
||||||
|
import { MetadataRepresentation } from '../../../../core/shared/metadata-representation/metadata-representation.model';
|
||||||
|
import { MetadatumRepresentation } from '../../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
||||||
|
import { MetadataValue } from '../../../../core/shared/metadata.models';
|
||||||
|
import { getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||||
|
import { hasValue } from '../../../../shared/empty.util';
|
||||||
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
||||||
|
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
||||||
|
import { distinctUntilChanged, flatMap, map } from 'rxjs/operators';
|
||||||
|
import { of as observableOf, zip as observableZip } from 'rxjs';
|
||||||
|
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operator for comparing arrays using a mapping function
|
||||||
|
* The mapping function should turn the source array into an array of basic types, so that the array can
|
||||||
|
* be compared using these basic types.
|
||||||
|
* For example: "(o) => o.id" will compare the two arrays by comparing their content by id.
|
||||||
|
* @param mapFn Function for mapping the arrays
|
||||||
|
*/
|
||||||
|
export const compareArraysUsing = <T>(mapFn: (t: T) => any) =>
|
||||||
|
(a: T[], b: T[]): boolean => {
|
||||||
|
if (!Array.isArray(a) || ! Array.isArray(b)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const aIds = a.map(mapFn);
|
||||||
|
const bIds = b.map(mapFn);
|
||||||
|
|
||||||
|
return aIds.length === bIds.length &&
|
||||||
|
aIds.every((e) => bIds.includes(e)) &&
|
||||||
|
bIds.every((e) => aIds.includes(e));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operator for comparing arrays using the object's ids
|
||||||
|
*/
|
||||||
|
export const compareArraysUsingIds = <T extends { id: string }>() =>
|
||||||
|
compareArraysUsing((t: T) => hasValue(t) ? t.id : undefined);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the relationships which match the type label given
|
||||||
|
* @param {string} label Type label
|
||||||
|
* @returns {(source: Observable<[Relationship[] , RelationshipType[]]>) => Observable<Relationship[]>}
|
||||||
|
*/
|
||||||
|
export const filterRelationsByTypeLabel = (label: string) =>
|
||||||
|
(source: Observable<[Relationship[], RelationshipType[]]>): Observable<Relationship[]> =>
|
||||||
|
source.pipe(
|
||||||
|
map(([relsCurrentPage, relTypesCurrentPage]) =>
|
||||||
|
relsCurrentPage.filter((rel: Relationship, idx: number) =>
|
||||||
|
hasValue(relTypesCurrentPage[idx]) && (relTypesCurrentPage[idx].leftLabel === label ||
|
||||||
|
relTypesCurrentPage[idx].rightLabel === label)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
distinctUntilChanged(compareArraysUsingIds())
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operator for turning a list of relationships into a list of the relevant items
|
||||||
|
* @param {string} thisId The item's id of which the relations belong to
|
||||||
|
* @param {ItemDataService} ids The ItemDataService to fetch items from the REST API
|
||||||
|
* @returns {(source: Observable<Relationship[]>) => Observable<Item[]>}
|
||||||
|
*/
|
||||||
|
export const relationsToItems = (thisId: string, ids: ItemDataService) =>
|
||||||
|
(source: Observable<Relationship[]>): Observable<Item[]> =>
|
||||||
|
source.pipe(
|
||||||
|
flatMap((rels: Relationship[]) =>
|
||||||
|
observableZip(
|
||||||
|
...rels.map((rel: Relationship) => {
|
||||||
|
let queryId = rel.leftId;
|
||||||
|
if (rel.leftId === thisId) {
|
||||||
|
queryId = rel.rightId;
|
||||||
|
}
|
||||||
|
return ids.findById(queryId);
|
||||||
|
})
|
||||||
|
)
|
||||||
|
),
|
||||||
|
map((arr: Array<RemoteData<Item>>) =>
|
||||||
|
arr
|
||||||
|
.filter((d: RemoteData<Item>) => d.hasSucceeded)
|
||||||
|
.map((d: RemoteData<Item>) => d.payload)),
|
||||||
|
distinctUntilChanged(compareArraysUsingIds()),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Operator for turning a list of relationships into a list of metadatarepresentations given the original metadata
|
||||||
|
* @param parentId The id of the parent item
|
||||||
|
* @param itemType The type of relation this list resembles (for creating representations)
|
||||||
|
* @param metadata The list of original Metadatum objects
|
||||||
|
* @param ids The ItemDataService to use for fetching Items from the Rest API
|
||||||
|
*/
|
||||||
|
export const relationsToRepresentations = (parentId: string, itemType: string, metadata: MetadataValue[], ids: ItemDataService) =>
|
||||||
|
(source: Observable<Relationship[]>): Observable<MetadataRepresentation[]> =>
|
||||||
|
source.pipe(
|
||||||
|
flatMap((rels: Relationship[]) =>
|
||||||
|
observableZip(
|
||||||
|
...metadata
|
||||||
|
.map((metadatum: any) => Object.assign(new MetadataValue(), metadatum))
|
||||||
|
.map((metadatum: MetadataValue) => {
|
||||||
|
if (metadatum.isVirtual) {
|
||||||
|
const matchingRels = rels.filter((rel: Relationship) => ('' + rel.id) === metadatum.virtualValue);
|
||||||
|
if (matchingRels.length > 0) {
|
||||||
|
const matchingRel = matchingRels[0];
|
||||||
|
let queryId = matchingRel.leftId;
|
||||||
|
if (matchingRel.leftId === parentId) {
|
||||||
|
queryId = matchingRel.rightId;
|
||||||
|
}
|
||||||
|
return ids.findById(queryId).pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
map((d: RemoteData<Item>) => Object.assign(new ItemMetadataRepresentation(), d.payload))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return observableOf(Object.assign(new MetadatumRepresentation(itemType), metadatum));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
@@ -0,0 +1,428 @@
|
|||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { GenericItemPageFieldComponent } from '../../field-components/specific-field/generic/generic-item-page-field.component';
|
||||||
|
import { TruncatableService } from '../../../../shared/truncatable/truncatable.service';
|
||||||
|
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 { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
|
||||||
|
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
||||||
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
||||||
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
|
import { ItemComponent } from './item.component';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a generic test for an item-page-fields component using a mockItem and the type of component
|
||||||
|
* @param {Item} mockItem The item to use for testing. The item needs to contain just the metadata necessary to
|
||||||
|
* execute the tests for it's component.
|
||||||
|
* @param component The type of component to create test cases for.
|
||||||
|
* @returns {() => void} Returns a specDefinition for the test.
|
||||||
|
*/
|
||||||
|
export function getItemPageFieldsTest(mockItem: Item, component) {
|
||||||
|
return () => {
|
||||||
|
let comp: any;
|
||||||
|
let fixture: ComponentFixture<any>;
|
||||||
|
|
||||||
|
const searchFixedFilterServiceStub = {
|
||||||
|
/* tslint:disable:no-empty */
|
||||||
|
getQueryByRelations: () => {}
|
||||||
|
/* tslint:enable:no-empty */
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
})],
|
||||||
|
declarations: [component, GenericItemPageFieldComponent, TruncatePipe],
|
||||||
|
providers: [
|
||||||
|
{provide: ITEM, useValue: mockItem},
|
||||||
|
{provide: ItemDataService, useValue: {}},
|
||||||
|
{provide: SearchFixedFilterService, useValue: searchFixedFilterServiceStub},
|
||||||
|
{provide: TruncatableService, useValue: {}}
|
||||||
|
],
|
||||||
|
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(component, {
|
||||||
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(component);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (const key of Object.keys(mockItem.metadata)) {
|
||||||
|
it(`should be calling a component with metadata field ${key}`, () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-generic-item-page-field'));
|
||||||
|
expect(containsFieldInput(fields, key)).toBeTruthy();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether in a list of debug elements, at least one of them contains a specific metadata key in their
|
||||||
|
* fields property.
|
||||||
|
* @param {DebugElement[]} fields List of debug elements to check
|
||||||
|
* @param {string} metadataKey A metadata key to look for
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function containsFieldInput(fields: DebugElement[], metadataKey: string): boolean {
|
||||||
|
for (const field of fields) {
|
||||||
|
const fieldComp = field.componentInstance;
|
||||||
|
if (isNotEmpty(fieldComp.fields)) {
|
||||||
|
if (fieldComp.fields.indexOf(metadataKey) > -1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createRelationshipsObservable() {
|
||||||
|
return observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [
|
||||||
|
Object.assign(new Relationship(), {
|
||||||
|
relationshipType: observableOf(new RemoteData(false, false, true, null, new RelationshipType()))
|
||||||
|
})
|
||||||
|
])));
|
||||||
|
}
|
||||||
|
describe('ItemComponent', () => {
|
||||||
|
const arr1 = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'test'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'another test'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'one last test'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const arrWithWrongId = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'test'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 5, // Wrong id on purpose
|
||||||
|
name: 'another test'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'one last test'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const arrWithWrongName = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'test'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'wrong test' // Wrong name on purpose
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'one last test'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const arrWithDifferentOrder = [arr1[0], arr1[2], arr1[1]];
|
||||||
|
const arrWithOneMore = [...arr1, {
|
||||||
|
id: 4,
|
||||||
|
name: 'fourth test'
|
||||||
|
}];
|
||||||
|
const arrWithAddedProperties = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'test',
|
||||||
|
extra: 'extra property'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'another test',
|
||||||
|
extra: 'extra property'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'one last test',
|
||||||
|
extra: 'extra property'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const arrOfPrimitiveTypes = [1, 2, 3, 4];
|
||||||
|
const arrOfPrimitiveTypesWithOneWrong = [1, 5, 3, 4];
|
||||||
|
const arrOfPrimitiveTypesWithDifferentOrder = [1, 3, 2, 4];
|
||||||
|
const arrOfPrimitiveTypesWithOneMore = [1, 2, 3, 4, 5];
|
||||||
|
|
||||||
|
describe('when calling compareArraysUsing', () => {
|
||||||
|
|
||||||
|
describe('and comparing by id', () => {
|
||||||
|
const compare = compareArraysUsing<any>((o) => o.id);
|
||||||
|
|
||||||
|
it('should return true when comparing the same array', () => {
|
||||||
|
expect(compare(arr1, arr1)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true regardless of the order', () => {
|
||||||
|
expect(compare(arr1, arrWithDifferentOrder)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true regardless of other properties being different', () => {
|
||||||
|
expect(compare(arr1, arrWithWrongName)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true regardless of extra properties', () => {
|
||||||
|
expect(compare(arr1, arrWithAddedProperties)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when the ids don\'t match', () => {
|
||||||
|
expect(compare(arr1, arrWithWrongId)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when the sizes don\'t match', () => {
|
||||||
|
expect(compare(arr1, arrWithOneMore)).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and comparing by name', () => {
|
||||||
|
const compare = compareArraysUsing<any>((o) => o.name);
|
||||||
|
|
||||||
|
it('should return true when comparing the same array', () => {
|
||||||
|
expect(compare(arr1, arr1)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true regardless of the order', () => {
|
||||||
|
expect(compare(arr1, arrWithDifferentOrder)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true regardless of other properties being different', () => {
|
||||||
|
expect(compare(arr1, arrWithWrongId)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true regardless of extra properties', () => {
|
||||||
|
expect(compare(arr1, arrWithAddedProperties)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when the names don\'t match', () => {
|
||||||
|
expect(compare(arr1, arrWithWrongName)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when the sizes don\'t match', () => {
|
||||||
|
expect(compare(arr1, arrWithOneMore)).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and comparing by full objects', () => {
|
||||||
|
const compare = compareArraysUsing<any>((o) => o);
|
||||||
|
|
||||||
|
it('should return true when comparing the same array', () => {
|
||||||
|
expect(compare(arr1, arr1)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true regardless of the order', () => {
|
||||||
|
expect(compare(arr1, arrWithDifferentOrder)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when extra properties are added', () => {
|
||||||
|
expect(compare(arr1, arrWithAddedProperties)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when the ids don\'t match', () => {
|
||||||
|
expect(compare(arr1, arrWithWrongId)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when the names don\'t match', () => {
|
||||||
|
expect(compare(arr1, arrWithWrongName)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when the sizes don\'t match', () => {
|
||||||
|
expect(compare(arr1, arrWithOneMore)).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and comparing with primitive objects as source', () => {
|
||||||
|
const compare = compareArraysUsing<any>((o) => o);
|
||||||
|
|
||||||
|
it('should return true when comparing the same array', () => {
|
||||||
|
expect(compare(arrOfPrimitiveTypes, arrOfPrimitiveTypes)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true regardless of the order', () => {
|
||||||
|
expect(compare(arrOfPrimitiveTypes, arrOfPrimitiveTypesWithDifferentOrder)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when at least one is wrong', () => {
|
||||||
|
expect(compare(arrOfPrimitiveTypes, arrOfPrimitiveTypesWithOneWrong)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when the sizes don\'t match', () => {
|
||||||
|
expect(compare(arrOfPrimitiveTypes, arrOfPrimitiveTypesWithOneMore)).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when calling compareArraysUsingIds', () => {
|
||||||
|
const compare = compareArraysUsingIds();
|
||||||
|
|
||||||
|
it('should return true when comparing the same array', () => {
|
||||||
|
expect(compare(arr1 as any, arr1 as any)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true regardless of the order', () => {
|
||||||
|
expect(compare(arr1 as any, arrWithDifferentOrder as any)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true regardless of other properties being different', () => {
|
||||||
|
expect(compare(arr1 as any, arrWithWrongName as any)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true regardless of extra properties', () => {
|
||||||
|
expect(compare(arr1 as any, arrWithAddedProperties as any)).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when the ids don\'t match', () => {
|
||||||
|
expect(compare(arr1 as any, arrWithWrongId as any)).toBeFalsy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false when the sizes don\'t match', () => {
|
||||||
|
expect(compare(arr1 as any, arrWithOneMore as any)).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when calling buildRepresentations', () => {
|
||||||
|
let comp: ItemComponent;
|
||||||
|
let fixture: ComponentFixture<ItemComponent>;
|
||||||
|
|
||||||
|
const metadataField = 'dc.contributor.author';
|
||||||
|
const mockItem = Object.assign(new Item(), {
|
||||||
|
id: '1',
|
||||||
|
uuid: '1',
|
||||||
|
metadata: new MetadataMap(),
|
||||||
|
relationships: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), [
|
||||||
|
Object.assign(new Relationship(), {
|
||||||
|
uuid: '123',
|
||||||
|
id: '123',
|
||||||
|
leftId: '1',
|
||||||
|
rightId: '2',
|
||||||
|
relationshipType: observableOf(new RemoteData(false, false, true, null, new RelationshipType()))
|
||||||
|
})
|
||||||
|
])))
|
||||||
|
});
|
||||||
|
mockItem.metadata[metadataField] = [
|
||||||
|
{
|
||||||
|
value: 'Second value',
|
||||||
|
place: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'Third value',
|
||||||
|
place: 2,
|
||||||
|
authority: 'virtual::123'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'First value',
|
||||||
|
place: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'Fourth value',
|
||||||
|
place: 3,
|
||||||
|
authority: '123'
|
||||||
|
}
|
||||||
|
] as MetadataValue[];
|
||||||
|
const relatedItem = Object.assign(new Item(), {
|
||||||
|
id: '2',
|
||||||
|
metadata: Object.assign(new MetadataMap(), {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
language: 'en_US',
|
||||||
|
value: 'related item'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
const mockItemDataService = Object.assign({
|
||||||
|
findById: (id) => {
|
||||||
|
if (id === relatedItem.id) {
|
||||||
|
return observableOf(new RemoteData(false, false, true, null, relatedItem))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) as ItemDataService;
|
||||||
|
|
||||||
|
let representations: Observable<MetadataRepresentation[]>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
}), BrowserAnimationsModule],
|
||||||
|
declarations: [ItemComponent, VarDirective],
|
||||||
|
providers: [
|
||||||
|
{provide: ITEM, useValue: mockItem}
|
||||||
|
],
|
||||||
|
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(ItemComponent, {
|
||||||
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(ItemComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
representations = comp.buildRepresentations('bogus', metadataField, mockItemDataService);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should contain exactly 4 metadata-representations', () => {
|
||||||
|
representations.subscribe((reps: MetadataRepresentation[]) => {
|
||||||
|
expect(reps.length).toEqual(4);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have all the representations in the correct order', () => {
|
||||||
|
representations.subscribe((reps: MetadataRepresentation[]) => {
|
||||||
|
expect(reps[0].getValue()).toEqual('First value');
|
||||||
|
expect(reps[1].getValue()).toEqual('Second value');
|
||||||
|
expect(reps[2].getValue()).toEqual('related item');
|
||||||
|
expect(reps[3].getValue()).toEqual('Fourth value');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have created the correct MetadatumRepresentation and ItemMetadataRepresentation objects for the correct Metadata', () => {
|
||||||
|
representations.subscribe((reps: MetadataRepresentation[]) => {
|
||||||
|
expect(reps[0] instanceof MetadatumRepresentation).toEqual(true);
|
||||||
|
expect(reps[1] instanceof MetadatumRepresentation).toEqual(true);
|
||||||
|
expect(reps[2] instanceof ItemMetadataRepresentation).toEqual(true);
|
||||||
|
expect(reps[3] instanceof MetadatumRepresentation).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,79 @@
|
|||||||
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
|
import { combineLatest as observableCombineLatest, Observable, zip as observableZip } from 'rxjs';
|
||||||
|
import { distinctUntilChanged, filter, flatMap, map } from 'rxjs/operators';
|
||||||
|
import { ItemDataService } from '../../../../core/data/item-data.service';
|
||||||
|
import { PaginatedList } from '../../../../core/data/paginated-list';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
|
import { RelationshipType } from '../../../../core/shared/item-relationships/relationship-type.model';
|
||||||
|
import { Relationship } from '../../../../core/shared/item-relationships/relationship.model';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { MetadataRepresentation } from '../../../../core/shared/metadata-representation/metadata-representation.model';
|
||||||
|
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../../core/shared/operators';
|
||||||
|
import { ITEM } from '../../../../shared/items/switcher/item-type-switcher.component';
|
||||||
|
import { compareArraysUsingIds, relationsToRepresentations } from './item-relationships-utils';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-item',
|
||||||
|
template: ''
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* A generic component for displaying metadata and relations of an item
|
||||||
|
*/
|
||||||
|
export class ItemComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* Resolved relationships and types together in one observable
|
||||||
|
*/
|
||||||
|
resolvedRelsAndTypes$: Observable<[Relationship[], RelationshipType[]]>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(ITEM) public item: Item
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
const relationships$ = this.item.relationships;
|
||||||
|
if (relationships$) {
|
||||||
|
const relsCurrentPage$ = relationships$.pipe(
|
||||||
|
filter((rd: RemoteData<PaginatedList<Relationship>>) => rd.hasSucceeded),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
map((pl: PaginatedList<Relationship>) => pl.page),
|
||||||
|
distinctUntilChanged(compareArraysUsingIds())
|
||||||
|
);
|
||||||
|
|
||||||
|
const relTypesCurrentPage$ = relsCurrentPage$.pipe(
|
||||||
|
flatMap((rels: Relationship[]) =>
|
||||||
|
observableZip(...rels.map((rel: Relationship) => rel.relationshipType)).pipe(
|
||||||
|
map(([...arr]: Array<RemoteData<RelationshipType>>) => arr.map((d: RemoteData<RelationshipType>) => d.payload))
|
||||||
|
)
|
||||||
|
),
|
||||||
|
distinctUntilChanged(compareArraysUsingIds())
|
||||||
|
);
|
||||||
|
|
||||||
|
this.resolvedRelsAndTypes$ = observableCombineLatest(
|
||||||
|
relsCurrentPage$,
|
||||||
|
relTypesCurrentPage$
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a list of MetadataRepresentations for the current item. This combines all metadata and relationships of a
|
||||||
|
* certain type.
|
||||||
|
* @param itemType The type of item we're building representations of. Used for matching templates.
|
||||||
|
* @param metadataField The metadata field that resembles the item type.
|
||||||
|
* @param itemDataService ItemDataService to turn relations into items.
|
||||||
|
*/
|
||||||
|
buildRepresentations(itemType: string, metadataField: string, itemDataService: ItemDataService): Observable<MetadataRepresentation[]> {
|
||||||
|
const metadata = this.item.findMetadataSortedByPlace(metadataField);
|
||||||
|
const relsCurrentPage$ = this.item.relationships.pipe(
|
||||||
|
getSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
map((pl: PaginatedList<Relationship>) => pl.page),
|
||||||
|
distinctUntilChanged(compareArraysUsingIds())
|
||||||
|
);
|
||||||
|
|
||||||
|
return relsCurrentPage$.pipe(
|
||||||
|
relationsToRepresentations(this.item.id, itemType, metadata, itemDataService)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,5 @@
|
|||||||
|
<ds-metadata-field-wrapper *ngIf="representations && representations.length > 0" [label]="label">
|
||||||
|
<ds-item-type-switcher *ngFor="let rep of representations"
|
||||||
|
[object]="rep" [viewMode]="viewMode">
|
||||||
|
</ds-item-type-switcher>
|
||||||
|
</ds-metadata-field-wrapper>
|
@@ -0,0 +1,40 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { MetadataRepresentationListComponent } from './metadata-representation-list.component';
|
||||||
|
import { MetadatumRepresentation } from '../../../core/shared/metadata-representation/metadatum/metadatum-representation.model';
|
||||||
|
import { ItemMetadataRepresentation } from '../../../core/shared/metadata-representation/item/item-metadata-representation.model';
|
||||||
|
|
||||||
|
const itemType = 'type';
|
||||||
|
const metadataRepresentation1 = new MetadatumRepresentation(itemType);
|
||||||
|
const metadataRepresentation2 = new ItemMetadataRepresentation();
|
||||||
|
const representations = [metadataRepresentation1, metadataRepresentation2];
|
||||||
|
|
||||||
|
describe('MetadataRepresentationListComponent', () => {
|
||||||
|
let comp: MetadataRepresentationListComponent;
|
||||||
|
let fixture: ComponentFixture<MetadataRepresentationListComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [MetadataRepresentationListComponent],
|
||||||
|
providers: [],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(MetadataRepresentationListComponent, {
|
||||||
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(MetadataRepresentationListComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.representations = representations;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it(`should load ${representations.length} item-type-switcher components`, () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-type-switcher'));
|
||||||
|
expect(fields.length).toBe(representations.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,29 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { MetadataRepresentation } from '../../../core/shared/metadata-representation/metadata-representation.model';
|
||||||
|
import { ItemViewMode } from '../../../shared/items/item-type-decorator';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-metadata-representation-list',
|
||||||
|
templateUrl: './metadata-representation-list.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* This component is used for displaying metadata
|
||||||
|
* It expects a list of MetadataRepresentation objects and a label to put on top of the list
|
||||||
|
*/
|
||||||
|
export class MetadataRepresentationListComponent {
|
||||||
|
/**
|
||||||
|
* A list of metadata-representations to display
|
||||||
|
*/
|
||||||
|
@Input() representations: MetadataRepresentation[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An i18n label to use as a title for the list
|
||||||
|
*/
|
||||||
|
@Input() label: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The view-mode we're currently on
|
||||||
|
* @type {ElementViewMode}
|
||||||
|
*/
|
||||||
|
viewMode = ItemViewMode.Metadata;
|
||||||
|
}
|
@@ -0,0 +1,6 @@
|
|||||||
|
<ds-filtered-search-page
|
||||||
|
[fixedFilterQuery]="fixedFilter"
|
||||||
|
[fixedFilter$]="fixedFilter$"
|
||||||
|
[searchEnabled]="searchEnabled"
|
||||||
|
[sideBarWidth]="sideBarWidth">
|
||||||
|
</ds-filtered-search-page>
|
@@ -0,0 +1,56 @@
|
|||||||
|
import { RelatedEntitiesSearchComponent } from './related-entities-search.component';
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
|
||||||
|
describe('RelatedEntitiesSearchComponent', () => {
|
||||||
|
let comp: RelatedEntitiesSearchComponent;
|
||||||
|
let fixture: ComponentFixture<RelatedEntitiesSearchComponent>;
|
||||||
|
let fixedFilterService: SearchFixedFilterService;
|
||||||
|
|
||||||
|
const mockItem = Object.assign(new Item(), {
|
||||||
|
id: 'id1'
|
||||||
|
});
|
||||||
|
const mockRelationType = 'publicationsOfAuthor';
|
||||||
|
const mockRelationEntityType = 'publication';
|
||||||
|
const mockFilter= `f.${mockRelationType}=${mockItem.id}`;
|
||||||
|
const fixedFilterServiceStub = {
|
||||||
|
getFilterByRelation: () => mockFilter
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [TranslateModule.forRoot(), NoopAnimationsModule, FormsModule],
|
||||||
|
declarations: [RelatedEntitiesSearchComponent],
|
||||||
|
providers: [
|
||||||
|
{ provide: SearchFixedFilterService, useValue: fixedFilterServiceStub }
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(RelatedEntitiesSearchComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
fixedFilterService = (comp as any).fixedFilterService;
|
||||||
|
comp.relationType = mockRelationType;
|
||||||
|
comp.item = mockItem;
|
||||||
|
comp.relationEntityType = mockRelationEntityType;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a fixedFilter', () => {
|
||||||
|
expect(comp.fixedFilter).toEqual(mockFilter);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a fixedFilter$', () => {
|
||||||
|
comp.fixedFilter$.subscribe((fixedFilter) => {
|
||||||
|
expect(fixedFilter).toEqual(mockRelationEntityType);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,64 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { Item } from '../../../../core/shared/item.model';
|
||||||
|
import { SearchFixedFilterService } from '../../../../+search-page/search-filters/search-filter/search-fixed-filter.service';
|
||||||
|
import { isNotEmpty } from '../../../../shared/empty.util';
|
||||||
|
import { of } from 'rxjs/internal/observable/of';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-related-entities-search',
|
||||||
|
templateUrl: './related-entities-search.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* A component to show related items as search results.
|
||||||
|
* Related items can be facetted, or queried using an
|
||||||
|
* optional search box.
|
||||||
|
*/
|
||||||
|
export class RelatedEntitiesSearchComponent implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of relationship to fetch items for
|
||||||
|
* e.g. 'isAuthorOfPublication'
|
||||||
|
*/
|
||||||
|
@Input() relationType: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The item to render relationships for
|
||||||
|
*/
|
||||||
|
@Input() item: Item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entity type of the relationship items to be displayed
|
||||||
|
* e.g. 'publication'
|
||||||
|
* This determines the title of the search results (if search is enabled)
|
||||||
|
*/
|
||||||
|
@Input() relationEntityType: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the search bar and title should be displayed (defaults to true)
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
@Input() searchEnabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ratio of the sidebar's width compared to the search results (1-12) (defaults to 4)
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
@Input() sideBarWidth = 4;
|
||||||
|
|
||||||
|
fixedFilter: string;
|
||||||
|
fixedFilter$: Observable<string>;
|
||||||
|
|
||||||
|
constructor(private fixedFilterService: SearchFixedFilterService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (isNotEmpty(this.relationType) && isNotEmpty(this.item)) {
|
||||||
|
this.fixedFilter = this.fixedFilterService.getFilterByRelation(this.relationType, this.item.id);
|
||||||
|
}
|
||||||
|
if (isNotEmpty(this.relationEntityType)) {
|
||||||
|
this.fixedFilter$ = of(this.relationEntityType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,30 @@
|
|||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
import { ItemViewMode } from '../../../shared/items/item-type-decorator';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-related-items',
|
||||||
|
styleUrls: ['./related-items.component.scss'],
|
||||||
|
templateUrl: './related-items.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* This component is used for displaying relations between items
|
||||||
|
* It expects a list of items to display and a label to put on top
|
||||||
|
*/
|
||||||
|
export class RelatedItemsComponent {
|
||||||
|
/**
|
||||||
|
* A list of items to display
|
||||||
|
*/
|
||||||
|
@Input() items: Item[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An i18n label to use as a title for the list (usually describes the relation)
|
||||||
|
*/
|
||||||
|
@Input() label: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The view-mode we're currently on
|
||||||
|
* @type {ElementViewMode}
|
||||||
|
*/
|
||||||
|
viewMode = ItemViewMode.Element;
|
||||||
|
}
|
@@ -0,0 +1,5 @@
|
|||||||
|
<ds-metadata-field-wrapper *ngIf="items && items.length > 0" [label]="label">
|
||||||
|
<ds-item-type-switcher *ngFor="let item of items"
|
||||||
|
[object]="item" [viewMode]="viewMode">
|
||||||
|
</ds-item-type-switcher>
|
||||||
|
</ds-metadata-field-wrapper>
|
@@ -0,0 +1,51 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { ChangeDetectionStrategy, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { RelatedItemsComponent } from './related-items-component';
|
||||||
|
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 { By } from '@angular/platform-browser';
|
||||||
|
import { createRelationshipsObservable } from '../item-types/shared/item.component.spec';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
|
const mockItem1: Item = Object.assign(new Item(), {
|
||||||
|
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
|
||||||
|
metadata: [],
|
||||||
|
relationships: createRelationshipsObservable()
|
||||||
|
});
|
||||||
|
const mockItem2: Item = Object.assign(new Item(), {
|
||||||
|
bitstreams: observableOf(new RemoteData(false, false, true, null, new PaginatedList(new PageInfo(), []))),
|
||||||
|
metadata: [],
|
||||||
|
relationships: createRelationshipsObservable()
|
||||||
|
});
|
||||||
|
const mockItems = [mockItem1, mockItem2];
|
||||||
|
|
||||||
|
describe('RelatedItemsComponent', () => {
|
||||||
|
let comp: RelatedItemsComponent;
|
||||||
|
let fixture: ComponentFixture<RelatedItemsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [],
|
||||||
|
declarations: [RelatedItemsComponent],
|
||||||
|
providers: [],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).overrideComponent(RelatedItemsComponent, {
|
||||||
|
set: {changeDetection: ChangeDetectionStrategy.Default}
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
fixture = TestBed.createComponent(RelatedItemsComponent);
|
||||||
|
comp = fixture.componentInstance;
|
||||||
|
comp.items = mockItems;
|
||||||
|
fixture.detectChanges();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it(`should load ${mockItems.length} item-type-switcher components`, () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-type-switcher'));
|
||||||
|
expect(fields.length).toBe(mockItems.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,4 @@
|
|||||||
|
export enum MyDSpaceConfigurationValueType {
|
||||||
|
Workspace = 'workspace',
|
||||||
|
Workflow = 'workflow'
|
||||||
|
}
|
259
src/app/+my-dspace-page/my-dspace-configuration.service.spec.ts
Normal file
259
src/app/+my-dspace-page/my-dspace-configuration.service.spec.ts
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
|
import { MyDSpaceConfigurationService } from './my-dspace-configuration.service';
|
||||||
|
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 { SearchFilter } from '../+search-page/search-filter.model';
|
||||||
|
import { ActivatedRouteStub } from '../shared/testing/active-router-stub';
|
||||||
|
import { MockRoleService } from '../shared/mocks/mock-role-service';
|
||||||
|
import { cold, hot } from 'jasmine-marbles';
|
||||||
|
import { MyDSpaceConfigurationValueType } from './my-dspace-configuration-value-type';
|
||||||
|
|
||||||
|
describe('MyDSpaceConfigurationService', () => {
|
||||||
|
let service: MyDSpaceConfigurationService;
|
||||||
|
const value1 = 'random value';
|
||||||
|
const prefixFilter = {
|
||||||
|
'f.namedresourcetype': ['another value'],
|
||||||
|
'f.dateSubmitted.min': ['2013'],
|
||||||
|
'f.dateSubmitted.max': ['2018']
|
||||||
|
};
|
||||||
|
const defaults = new PaginatedSearchOptions({
|
||||||
|
pagination: Object.assign(new PaginationComponentOptions(), { currentPage: 1, pageSize: 20 }),
|
||||||
|
sort: new SortOptions('score', SortDirection.DESC),
|
||||||
|
query: '',
|
||||||
|
scope: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
const backendFilters = [new SearchFilter('f.namedresourcetype', ['another value']), new SearchFilter('f.dateSubmitted', ['[2013 TO 2018]'])];
|
||||||
|
|
||||||
|
const spy = jasmine.createSpyObj('RouteService', {
|
||||||
|
getQueryParameterValue: observableOf(value1),
|
||||||
|
getQueryParamsWithPrefix: observableOf(prefixFilter),
|
||||||
|
getRouteParameterValue: observableOf(''),
|
||||||
|
getRouteDataValue: observableOf({})
|
||||||
|
});
|
||||||
|
|
||||||
|
const activatedRoute: any = new ActivatedRouteStub();
|
||||||
|
|
||||||
|
const roleService: any = new MockRoleService();
|
||||||
|
|
||||||
|
const fixedFilterService = jasmine.createSpyObj('SearchFixedFilterService', {
|
||||||
|
getQueryByFilterName: observableOf(''),
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
service = new MyDSpaceConfigurationService(roleService, fixedFilterService, spy, activatedRoute);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when the scope is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.getCurrentScope('');
|
||||||
|
});
|
||||||
|
it('should call getQueryParameterValue on the routeService with parameter name \'scope\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('scope');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when getCurrentConfiguration is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.getCurrentConfiguration('');
|
||||||
|
});
|
||||||
|
it('should call getQueryParameterValue on the routeService with parameter name \'configuration\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('configuration');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when getCurrentQuery is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.getCurrentQuery('');
|
||||||
|
});
|
||||||
|
it('should call getQueryParameterValue on the routeService with parameter name \'query\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('query');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when getCurrentDSOType is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.getCurrentDSOType();
|
||||||
|
});
|
||||||
|
it('should call getQueryParameterValue on the routeService with parameter name \'dsoType\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('dsoType');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when getCurrentFrontendFilters is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.getCurrentFrontendFilters();
|
||||||
|
});
|
||||||
|
it('should call getQueryParamsWithPrefix on the routeService with parameter prefix \'f.\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParamsWithPrefix).toHaveBeenCalledWith('f.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when getCurrentFilters is called', () => {
|
||||||
|
let parsedValues$;
|
||||||
|
beforeEach(() => {
|
||||||
|
parsedValues$ = service.getCurrentFilters();
|
||||||
|
});
|
||||||
|
it('should call getQueryParamsWithPrefix on the routeService with parameter prefix \'f.\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParamsWithPrefix).toHaveBeenCalledWith('f.');
|
||||||
|
parsedValues$.subscribe((values) => {
|
||||||
|
expect(values).toEqual(backendFilters);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when getCurrentSort is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.getCurrentSort({} as any);
|
||||||
|
});
|
||||||
|
it('should call getQueryParameterValue on the routeService with parameter name \'sortDirection\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('sortDirection');
|
||||||
|
});
|
||||||
|
it('should call getQueryParameterValue on the routeService with parameter name \'sortField\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('sortField');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when getCurrentPagination is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
service.getCurrentPagination({ currentPage: 1, pageSize: 10 } as any);
|
||||||
|
});
|
||||||
|
it('should call getQueryParameterValue on the routeService with parameter name \'page\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('page');
|
||||||
|
});
|
||||||
|
it('should call getQueryParameterValue on the routeService with parameter name \'pageSize\'', () => {
|
||||||
|
expect((service as any).routeService.getQueryParameterValue).toHaveBeenCalledWith('pageSize');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when subscribeToSearchOptions or subscribeToPaginatedSearchOptions is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
spyOn(service, 'getCurrentPagination').and.callThrough();
|
||||||
|
spyOn(service, 'getCurrentSort').and.callThrough();
|
||||||
|
spyOn(service, 'getCurrentScope').and.callThrough();
|
||||||
|
spyOn(service, 'getCurrentConfiguration').and.callThrough();
|
||||||
|
spyOn(service, 'getCurrentQuery').and.callThrough();
|
||||||
|
spyOn(service, 'getCurrentDSOType').and.callThrough();
|
||||||
|
spyOn(service, 'getCurrentFilters').and.callThrough();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when subscribeToSearchOptions is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
(service as any).subscribeToSearchOptions(defaults)
|
||||||
|
});
|
||||||
|
it('should call all getters it needs, but not call any others', () => {
|
||||||
|
expect(service.getCurrentPagination).not.toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentSort).not.toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentScope).toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentConfiguration).toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentQuery).toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentDSOType).toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentFilters).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when subscribeToPaginatedSearchOptions is called', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
(service as any).subscribeToPaginatedSearchOptions(defaults);
|
||||||
|
});
|
||||||
|
it('should call all getters it needs', () => {
|
||||||
|
expect(service.getCurrentPagination).toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentSort).toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentScope).toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentConfiguration).toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentQuery).toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentDSOType).toHaveBeenCalled();
|
||||||
|
expect(service.getCurrentFilters).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when getAvailableConfigurationTypes is called', () => {
|
||||||
|
|
||||||
|
it('should return properly list when user is submitter', () => {
|
||||||
|
roleService.setSubmitter(true);
|
||||||
|
roleService.setController(false);
|
||||||
|
roleService.setAdmin(false);
|
||||||
|
|
||||||
|
const list$ = service.getAvailableConfigurationTypes();
|
||||||
|
|
||||||
|
expect(list$).toBeObservable(cold('(b|)', {
|
||||||
|
b: [
|
||||||
|
MyDSpaceConfigurationValueType.Workspace
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return properly list when user is controller', () => {
|
||||||
|
roleService.setSubmitter(false);
|
||||||
|
roleService.setController(true);
|
||||||
|
roleService.setAdmin(false);
|
||||||
|
|
||||||
|
const list$ = service.getAvailableConfigurationTypes();
|
||||||
|
|
||||||
|
expect(list$).toBeObservable(cold('(b|)', {
|
||||||
|
b: [
|
||||||
|
MyDSpaceConfigurationValueType.Workflow
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return properly list when user is admin', () => {
|
||||||
|
roleService.setSubmitter(false);
|
||||||
|
roleService.setController(false);
|
||||||
|
roleService.setAdmin(true);
|
||||||
|
|
||||||
|
const list$ = service.getAvailableConfigurationTypes();
|
||||||
|
|
||||||
|
expect(list$).toBeObservable(cold('(b|)', {
|
||||||
|
b: [
|
||||||
|
MyDSpaceConfigurationValueType.Workflow
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return properly list when user is submitter and controller', () => {
|
||||||
|
roleService.setSubmitter(true);
|
||||||
|
roleService.setController(true);
|
||||||
|
roleService.setAdmin(false);
|
||||||
|
|
||||||
|
const list$ = service.getAvailableConfigurationTypes();
|
||||||
|
|
||||||
|
expect(list$).toBeObservable(cold('(b|)', {
|
||||||
|
b: [
|
||||||
|
MyDSpaceConfigurationValueType.Workspace,
|
||||||
|
MyDSpaceConfigurationValueType.Workflow
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when getAvailableConfigurationOptions is called', () => {
|
||||||
|
|
||||||
|
it('should return properly options list', () => {
|
||||||
|
spyOn(service, 'getAvailableConfigurationTypes').and.returnValue(hot('a', {
|
||||||
|
a: [
|
||||||
|
MyDSpaceConfigurationValueType.Workspace,
|
||||||
|
MyDSpaceConfigurationValueType.Workflow
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
|
||||||
|
const list$ = service.getAvailableConfigurationOptions();
|
||||||
|
|
||||||
|
expect(list$).toBeObservable(cold('(b|)', {
|
||||||
|
b: [
|
||||||
|
{
|
||||||
|
value: MyDSpaceConfigurationValueType.Workspace,
|
||||||
|
label: `mydspace.show.${MyDSpaceConfigurationValueType.Workspace}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: MyDSpaceConfigurationValueType.Workflow,
|
||||||
|
label: `mydspace.show.${MyDSpaceConfigurationValueType.Workflow}`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
120
src/app/+my-dspace-page/my-dspace-configuration.service.ts
Normal file
120
src/app/+my-dspace-page/my-dspace-configuration.service.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
|
import { combineLatest, Observable } from 'rxjs';
|
||||||
|
import { first, map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { MyDSpaceConfigurationValueType } from './my-dspace-configuration-value-type';
|
||||||
|
import { RoleService } from '../core/roles/role.service';
|
||||||
|
import { SearchConfigurationOption } from '../+search-page/search-switch-configuration/search-configuration-option.model';
|
||||||
|
import { SearchConfigurationService } from '../+search-page/search-service/search-configuration.service';
|
||||||
|
import { RouteService } from '../shared/services/route.service';
|
||||||
|
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||||
|
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
||||||
|
import { SearchFixedFilterService } from '../+search-page/search-filters/search-filter/search-fixed-filter.service';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service that performs all actions that have to do with the current mydspace configuration
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class MyDSpaceConfigurationService extends SearchConfigurationService {
|
||||||
|
/**
|
||||||
|
* Default pagination settings
|
||||||
|
*/
|
||||||
|
protected defaultPagination = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: 'mydspace-page',
|
||||||
|
pageSize: 10,
|
||||||
|
currentPage: 1
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default sort settings
|
||||||
|
*/
|
||||||
|
protected defaultSort = new SortOptions('dc.date.issued', SortDirection.DESC);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default configuration parameter setting
|
||||||
|
*/
|
||||||
|
protected defaultConfiguration = 'workspace';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default scope setting
|
||||||
|
*/
|
||||||
|
protected defaultScope = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default query setting
|
||||||
|
*/
|
||||||
|
protected defaultQuery = '';
|
||||||
|
|
||||||
|
private isAdmin$: Observable<boolean>;
|
||||||
|
private isController$: Observable<boolean>;
|
||||||
|
private isSubmitter$: Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize class
|
||||||
|
*
|
||||||
|
* @param {roleService} roleService
|
||||||
|
* @param {SearchFixedFilterService} fixedFilterService
|
||||||
|
* @param {RouteService} routeService
|
||||||
|
* @param {ActivatedRoute} route
|
||||||
|
*/
|
||||||
|
constructor(protected roleService: RoleService,
|
||||||
|
protected fixedFilterService: SearchFixedFilterService,
|
||||||
|
protected routeService: RouteService,
|
||||||
|
protected route: ActivatedRoute) {
|
||||||
|
|
||||||
|
super(routeService, fixedFilterService, route);
|
||||||
|
|
||||||
|
// override parent class initialization
|
||||||
|
this._defaults = null;
|
||||||
|
this.initDefaults();
|
||||||
|
|
||||||
|
this.isSubmitter$ = this.roleService.isSubmitter();
|
||||||
|
this.isController$ = this.roleService.isController();
|
||||||
|
this.isAdmin$ = this.roleService.isAdmin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the list of available configuration depend on the user role
|
||||||
|
*
|
||||||
|
* @return {Observable<MyDSpaceConfigurationValueType[]>}
|
||||||
|
* Emits the available configuration list
|
||||||
|
*/
|
||||||
|
public getAvailableConfigurationTypes(): Observable<MyDSpaceConfigurationValueType[]> {
|
||||||
|
return combineLatest(this.isSubmitter$, this.isController$, this.isAdmin$).pipe(
|
||||||
|
first(),
|
||||||
|
map(([isSubmitter, isController, isAdmin]: [boolean, boolean, boolean]) => {
|
||||||
|
const availableConf: MyDSpaceConfigurationValueType[] = [];
|
||||||
|
if (isSubmitter) {
|
||||||
|
availableConf.push(MyDSpaceConfigurationValueType.Workspace);
|
||||||
|
}
|
||||||
|
if (isController || isAdmin) {
|
||||||
|
availableConf.push(MyDSpaceConfigurationValueType.Workflow);
|
||||||
|
}
|
||||||
|
return availableConf;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the select options for the available configuration list
|
||||||
|
*
|
||||||
|
* @return {Observable<SearchConfigurationOption[]>}
|
||||||
|
* Emits the select options list
|
||||||
|
*/
|
||||||
|
public getAvailableConfigurationOptions(): Observable<SearchConfigurationOption[]> {
|
||||||
|
return this.getAvailableConfigurationTypes().pipe(
|
||||||
|
first(),
|
||||||
|
map((availableConfigurationTypes: MyDSpaceConfigurationValueType[]) => {
|
||||||
|
const configurationOptions: SearchConfigurationOption[] = [];
|
||||||
|
availableConfigurationTypes.forEach((type) => {
|
||||||
|
const value = type;
|
||||||
|
const label = `mydspace.show.${value}`;
|
||||||
|
configurationOptions.push({ value, label });
|
||||||
|
});
|
||||||
|
return configurationOptions;
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,15 @@
|
|||||||
|
<div class="parent mb-3">
|
||||||
|
<div class="upload">
|
||||||
|
<ds-uploader *ngIf="uploadFilesOptions.url !== ''"
|
||||||
|
[uploadFilesOptions]="uploadFilesOptions"
|
||||||
|
(onCompleteItem)="onCompleteItem($event)"
|
||||||
|
(onUploadError)="onUploadError($event)"></ds-uploader>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="add">
|
||||||
|
<a class="btn btn-lg btn-primary mt-1 ml-2" [routerLink]="['/submit']" role="button">
|
||||||
|
<i class="fa fa-plus-circle" aria-hidden="true"></i> {{'mydspace.new-submission' | translate}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
@@ -0,0 +1,11 @@
|
|||||||
|
.parent {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload {
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add {
|
||||||
|
flex: initial;
|
||||||
|
}
|
@@ -0,0 +1,101 @@
|
|||||||
|
import { ChangeDetectorRef, Component, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
|
import { async, ComponentFixture, inject, TestBed } from '@angular/core/testing';
|
||||||
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
|
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { of as observableOf } from 'rxjs';
|
||||||
|
import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||||
|
import { ScrollToService } from '@nicky-lenaers/ngx-scroll-to';
|
||||||
|
|
||||||
|
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
|
||||||
|
import { AuthServiceStub } from '../../shared/testing/auth-service-stub';
|
||||||
|
import { AuthService } from '../../core/auth/auth.service';
|
||||||
|
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';
|
||||||
|
import { createTestComponent } from '../../shared/testing/utils';
|
||||||
|
import { MyDSpaceNewSubmissionComponent } from './my-dspace-new-submission.component';
|
||||||
|
import { AppState } from '../../app.reducer';
|
||||||
|
import { MockTranslateLoader } from '../../shared/mocks/mock-translate-loader';
|
||||||
|
import { getMockTranslateService } from '../../shared/mocks/mock-translate.service';
|
||||||
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
|
import { NotificationsServiceStub } from '../../shared/testing/notifications-service-stub';
|
||||||
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
import { getMockScrollToService } from '../../shared/mocks/mock-scroll-to-service';
|
||||||
|
import { UploaderService } from '../../shared/uploader/uploader.service';
|
||||||
|
|
||||||
|
describe('MyDSpaceNewSubmissionComponent test', () => {
|
||||||
|
|
||||||
|
const translateService: any = getMockTranslateService();
|
||||||
|
const store: Store<AppState> = jasmine.createSpyObj('store', {
|
||||||
|
/* tslint:disable:no-empty */
|
||||||
|
dispatch: {},
|
||||||
|
/* tslint:enable:no-empty */
|
||||||
|
pipe: observableOf(true)
|
||||||
|
});
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
RouterTestingModule,
|
||||||
|
SharedModule,
|
||||||
|
TranslateModule.forRoot({
|
||||||
|
loader: {
|
||||||
|
provide: TranslateLoader,
|
||||||
|
useClass: MockTranslateLoader
|
||||||
|
}
|
||||||
|
})
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
MyDSpaceNewSubmissionComponent,
|
||||||
|
TestComponent
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: AuthService, useClass: AuthServiceStub },
|
||||||
|
{ provide: HALEndpointService, useValue: new HALEndpointServiceStub('workspaceitems') },
|
||||||
|
{ provide: NotificationsService, useValue: new NotificationsServiceStub() },
|
||||||
|
{ provide: ScrollToService, useValue: getMockScrollToService() },
|
||||||
|
{ provide: Store, useValue: store },
|
||||||
|
{ provide: TranslateService, useValue: translateService },
|
||||||
|
ChangeDetectorRef,
|
||||||
|
MyDSpaceNewSubmissionComponent,
|
||||||
|
UploaderService
|
||||||
|
],
|
||||||
|
schemas: [NO_ERRORS_SCHEMA]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('', () => {
|
||||||
|
let testComp: TestComponent;
|
||||||
|
let testFixture: ComponentFixture<TestComponent>;
|
||||||
|
|
||||||
|
// synchronous beforeEach
|
||||||
|
beforeEach(() => {
|
||||||
|
const html = `
|
||||||
|
<ds-my-dspace-new-submission (uploadEnd)="reload($event)"></ds-my-dspace-new-submission>`;
|
||||||
|
|
||||||
|
testFixture = createTestComponent(html, TestComponent) as ComponentFixture<TestComponent>;
|
||||||
|
testComp = testFixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testFixture.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create MyDSpaceNewSubmissionComponent', inject([MyDSpaceNewSubmissionComponent], (app: MyDSpaceNewSubmissionComponent) => {
|
||||||
|
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// declare a test component
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-test-cmp',
|
||||||
|
template: ``
|
||||||
|
})
|
||||||
|
class TestComponent {
|
||||||
|
|
||||||
|
reload = (event) => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,118 @@
|
|||||||
|
import { ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
|
||||||
|
|
||||||
|
import { Subscription } from 'rxjs';
|
||||||
|
import { first } from 'rxjs/operators';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
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';
|
||||||
|
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';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This component represents the whole mydspace page header
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-my-dspace-new-submission',
|
||||||
|
styleUrls: ['./my-dspace-new-submission.component.scss'],
|
||||||
|
templateUrl: './my-dspace-new-submission.component.html'
|
||||||
|
})
|
||||||
|
export class MyDSpaceNewSubmissionComponent implements OnDestroy, OnInit {
|
||||||
|
@Output() uploadEnd = new EventEmitter<Array<MyDSpaceResult<DSpaceObject>>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The UploaderOptions object
|
||||||
|
*/
|
||||||
|
public uploadFilesOptions: UploaderOptions = {
|
||||||
|
url: '',
|
||||||
|
authToken: null,
|
||||||
|
disableMultipart: false,
|
||||||
|
itemAlias: null
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscription to unsubscribe from
|
||||||
|
*/
|
||||||
|
private sub: Subscription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize instance variables
|
||||||
|
*
|
||||||
|
* @param {AuthService} authService
|
||||||
|
* @param {ChangeDetectorRef} changeDetectorRef
|
||||||
|
* @param {HALEndpointService} halService
|
||||||
|
* @param {NotificationsService} notificationsService
|
||||||
|
* @param {Store<SubmissionState>} store
|
||||||
|
* @param {TranslateService} translate
|
||||||
|
*/
|
||||||
|
constructor(private authService: AuthService,
|
||||||
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
|
private halService: HALEndpointService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
|
private store: Store<SubmissionState>,
|
||||||
|
private translate: TranslateService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize url and Bearer token
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.sub = this.halService.getEndpoint('workspaceitems').pipe(first()).subscribe((url) => {
|
||||||
|
this.uploadFilesOptions.url = url;
|
||||||
|
this.uploadFilesOptions.authToken = this.authService.buildAuthHeader();
|
||||||
|
this.changeDetectorRef.detectChanges();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called when file upload is completed to notify upload status
|
||||||
|
*/
|
||||||
|
public onCompleteItem(res) {
|
||||||
|
if (res && res._embedded && res._embedded.workspaceitems && res._embedded.workspaceitems.length > 0) {
|
||||||
|
const workspaceitems = res._embedded.workspaceitems;
|
||||||
|
this.uploadEnd.emit(workspaceitems);
|
||||||
|
|
||||||
|
if (workspaceitems.length === 1) {
|
||||||
|
const options = new NotificationOptions();
|
||||||
|
options.timeOut = 0;
|
||||||
|
const link = '/workspaceitems/' + workspaceitems[0].id + '/edit';
|
||||||
|
this.notificationsService.notificationWithAnchor(
|
||||||
|
NotificationType.Success,
|
||||||
|
options,
|
||||||
|
link,
|
||||||
|
'mydspace.general.text-here',
|
||||||
|
'mydspace.upload.upload-successful',
|
||||||
|
'here');
|
||||||
|
} else if (workspaceitems.length > 1) {
|
||||||
|
this.notificationsService.success(null, this.translate.get('mydspace.upload.upload-multiple-successful', {qty: workspaceitems.length}));
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.notificationsService.error(null, this.translate.get('mydspace.upload.upload-failed'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method called on file upload error
|
||||||
|
*/
|
||||||
|
public onUploadError() {
|
||||||
|
this.notificationsService.error(null, this.translate.get('mydspace.upload.upload-failed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unsubscribe from the subscription
|
||||||
|
*/
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (hasValue(this.sub)) {
|
||||||
|
this.sub.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user