mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 18:14:17 +00:00
Merge branch 'main' into w2p-119612_export-item-limit
This commit is contained in:
@@ -23,6 +23,16 @@ ssr:
|
|||||||
# Determining which styles are critical is a relatively expensive operation; this option is
|
# Determining which styles are critical is a relatively expensive operation; this option is
|
||||||
# disabled (false) by default to boost server performance at the expense of loading smoothness.
|
# disabled (false) by default to boost server performance at the expense of loading smoothness.
|
||||||
inlineCriticalCss: false
|
inlineCriticalCss: false
|
||||||
|
# Path prefixes to enable SSR for. By default these are limited to paths of primary DSpace objects.
|
||||||
|
paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ]
|
||||||
|
# Whether to enable rendering of Search component on SSR.
|
||||||
|
# If set to true the component will be included in the HTML returned from the server side rendering.
|
||||||
|
# If set to false the component will not be included in the HTML returned from the server side rendering.
|
||||||
|
enableSearchComponent: false,
|
||||||
|
# Whether to enable rendering of Browse component on SSR.
|
||||||
|
# If set to true the component will be included in the HTML returned from the server side rendering.
|
||||||
|
# If set to false the component will not be included in the HTML returned from the server side rendering.
|
||||||
|
enableBrowseComponent: false,
|
||||||
|
|
||||||
# The REST API server settings
|
# The REST API server settings
|
||||||
# NOTE: these settings define which (publicly available) REST API to use. They are usually
|
# NOTE: these settings define which (publicly available) REST API to use. They are usually
|
||||||
@@ -448,6 +458,12 @@ search:
|
|||||||
enabled: false
|
enabled: false
|
||||||
# List of filters to enable in "Advanced Search" dropdown
|
# List of filters to enable in "Advanced Search" dropdown
|
||||||
filter: [ 'title', 'author', 'subject', 'entityType' ]
|
filter: [ 'title', 'author', 'subject', 'entityType' ]
|
||||||
|
#
|
||||||
|
# Number used to render n UI elements called loading skeletons that act as placeholders.
|
||||||
|
# These elements indicate that some content will be loaded in their stead.
|
||||||
|
# Since we don't know how many filters will be loaded before we receive a response from the server we use this parameter for the skeletons count.
|
||||||
|
# e.g. If we set 5 then 5 loading skeletons will be visualized before the actual filters are retrieved.
|
||||||
|
defaultFiltersCount: 5
|
||||||
|
|
||||||
|
|
||||||
# Notify metrics
|
# Notify metrics
|
||||||
|
@@ -9,6 +9,7 @@ describe('Admin Add New Modals', () => {
|
|||||||
|
|
||||||
it('Add new Community modal should pass accessibility tests', () => {
|
it('Add new Community modal should pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
|
cy.get('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
||||||
|
|
||||||
// Click on entry of menu
|
// Click on entry of menu
|
||||||
@@ -23,6 +24,7 @@ describe('Admin Add New Modals', () => {
|
|||||||
|
|
||||||
it('Add new Collection modal should pass accessibility tests', () => {
|
it('Add new Collection modal should pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
|
cy.get('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
||||||
|
|
||||||
// Click on entry of menu
|
// Click on entry of menu
|
||||||
@@ -37,6 +39,7 @@ describe('Admin Add New Modals', () => {
|
|||||||
|
|
||||||
it('Add new Item modal should pass accessibility tests', () => {
|
it('Add new Item modal should pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
|
cy.get('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
||||||
|
|
||||||
// Click on entry of menu
|
// Click on entry of menu
|
||||||
|
@@ -9,6 +9,7 @@ describe('Admin Edit Modals', () => {
|
|||||||
|
|
||||||
it('Edit Community modal should pass accessibility tests', () => {
|
it('Edit Community modal should pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
|
cy.get('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
||||||
|
|
||||||
// Click on entry of menu
|
// Click on entry of menu
|
||||||
@@ -23,6 +24,7 @@ describe('Admin Edit Modals', () => {
|
|||||||
|
|
||||||
it('Edit Collection modal should pass accessibility tests', () => {
|
it('Edit Collection modal should pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
|
cy.get('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
||||||
|
|
||||||
// Click on entry of menu
|
// Click on entry of menu
|
||||||
@@ -37,6 +39,7 @@ describe('Admin Edit Modals', () => {
|
|||||||
|
|
||||||
it('Edit Item modal should pass accessibility tests', () => {
|
it('Edit Item modal should pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
|
cy.get('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
||||||
|
|
||||||
// Click on entry of menu
|
// Click on entry of menu
|
||||||
|
@@ -9,6 +9,7 @@ describe('Admin Export Modals', () => {
|
|||||||
|
|
||||||
it('Export metadata modal should pass accessibility tests', () => {
|
it('Export metadata modal should pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
|
cy.get('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
||||||
|
|
||||||
// Click on entry of menu
|
// Click on entry of menu
|
||||||
@@ -23,6 +24,7 @@ describe('Admin Export Modals', () => {
|
|||||||
|
|
||||||
it('Export batch modal should pass accessibility tests', () => {
|
it('Export batch modal should pass accessibility tests', () => {
|
||||||
// Pin the sidebar open
|
// Pin the sidebar open
|
||||||
|
cy.get('[data-test="sidebar-collapse-toggle"]').trigger('mouseover');
|
||||||
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
cy.get('[data-test="sidebar-collapse-toggle"]').click();
|
||||||
|
|
||||||
// Click on entry of menu
|
// Click on entry of menu
|
||||||
|
275
package-lock.json
generated
275
package-lock.json
generated
@@ -39,22 +39,22 @@
|
|||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
"compression": "^1.7.5",
|
"compression": "^1.7.5",
|
||||||
"cookie-parser": "1.4.7",
|
"cookie-parser": "1.4.7",
|
||||||
"core-js": "^3.39.0",
|
"core-js": "^3.40.0",
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
"date-fns-tz": "^1.3.7",
|
"date-fns-tz": "^1.3.7",
|
||||||
"deepmerge": "^4.3.1",
|
"deepmerge": "^4.3.1",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^4.21.1",
|
"express": "^4.21.2",
|
||||||
"express-rate-limit": "^5.1.3",
|
"express-rate-limit": "^5.1.3",
|
||||||
"fast-json-patch": "^3.1.1",
|
"fast-json-patch": "^3.1.1",
|
||||||
"filesize": "^6.1.0",
|
"filesize": "^6.1.0",
|
||||||
"http-proxy-middleware": "^2.0.7",
|
"http-proxy-middleware": "^2.0.7",
|
||||||
"http-terminator": "^3.2.0",
|
"http-terminator": "^3.2.0",
|
||||||
"isbot": "^5.1.17",
|
"isbot": "^5.1.21",
|
||||||
"js-cookie": "2.2.1",
|
"js-cookie": "2.2.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
"jsonschema": "1.4.1",
|
"jsonschema": "1.5.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lru-cache": "^7.14.1",
|
"lru-cache": "^7.14.1",
|
||||||
@@ -67,6 +67,7 @@
|
|||||||
"ng2-nouislider": "^2.0.0",
|
"ng2-nouislider": "^2.0.0",
|
||||||
"ngx-infinite-scroll": "^16.0.0",
|
"ngx-infinite-scroll": "^16.0.0",
|
||||||
"ngx-pagination": "6.0.3",
|
"ngx-pagination": "6.0.3",
|
||||||
|
"ngx-skeleton-loader": "^9.0.0",
|
||||||
"ngx-ui-switch": "^14.1.0",
|
"ngx-ui-switch": "^14.1.0",
|
||||||
"nouislider": "^15.7.1",
|
"nouislider": "^15.7.1",
|
||||||
"orejime": "^2.3.1",
|
"orejime": "^2.3.1",
|
||||||
@@ -99,7 +100,7 @@
|
|||||||
"@types/grecaptcha": "^3.0.9",
|
"@types/grecaptcha": "^3.0.9",
|
||||||
"@types/jasmine": "~3.6.0",
|
"@types/jasmine": "~3.6.0",
|
||||||
"@types/js-cookie": "2.2.6",
|
"@types/js-cookie": "2.2.6",
|
||||||
"@types/lodash": "^4.17.13",
|
"@types/lodash": "^4.17.14",
|
||||||
"@types/node": "^14.14.9",
|
"@types/node": "^14.14.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||||
"@typescript-eslint/parser": "^7.18.0",
|
"@typescript-eslint/parser": "^7.18.0",
|
||||||
@@ -134,20 +135,20 @@
|
|||||||
"karma-jasmine": "~4.0.0",
|
"karma-jasmine": "~4.0.0",
|
||||||
"karma-jasmine-html-reporter": "^1.5.0",
|
"karma-jasmine-html-reporter": "^1.5.0",
|
||||||
"karma-mocha-reporter": "2.2.5",
|
"karma-mocha-reporter": "2.2.5",
|
||||||
"ng-mocks": "^14.13.1",
|
"ng-mocks": "^14.13.2",
|
||||||
"ngx-mask": "14.2.4",
|
"ngx-mask": "14.2.4",
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^2.0.22",
|
||||||
"postcss": "^8.4",
|
"postcss": "^8.5",
|
||||||
"postcss-import": "^14.0.0",
|
"postcss-import": "^14.0.0",
|
||||||
"postcss-loader": "^4.0.3",
|
"postcss-loader": "^4.0.3",
|
||||||
"postcss-preset-env": "^7.4.2",
|
"postcss-preset-env": "^7.4.2",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"sass": "~1.83.1",
|
"sass": "~1.83.4",
|
||||||
"sass-loader": "^12.6.0",
|
"sass-loader": "^12.6.0",
|
||||||
"sass-resources-loader": "^2.2.5",
|
"sass-resources-loader": "^2.2.5",
|
||||||
"ts-node": "^8.10.2",
|
"ts-node": "^8.10.2",
|
||||||
"typescript": "~5.4.5",
|
"typescript": "~5.4.5",
|
||||||
"webpack": "5.96.1",
|
"webpack": "5.97.1",
|
||||||
"webpack-cli": "^5.1.4",
|
"webpack-cli": "^5.1.4",
|
||||||
"webpack-dev-server": "^4.15.1"
|
"webpack-dev-server": "^4.15.1"
|
||||||
}
|
}
|
||||||
@@ -6897,9 +6898,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/lodash": {
|
"node_modules/@types/lodash": {
|
||||||
"version": "4.17.13",
|
"version": "4.17.14",
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.14.tgz",
|
||||||
"integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==",
|
"integrity": "sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/mime": {
|
"node_modules/@types/mime": {
|
||||||
@@ -7587,148 +7588,148 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@webassemblyjs/ast": {
|
"node_modules/@webassemblyjs/ast": {
|
||||||
"version": "1.12.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
|
||||||
"integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==",
|
"integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@webassemblyjs/helper-numbers": "1.11.6",
|
"@webassemblyjs/helper-numbers": "1.13.2",
|
||||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6"
|
"@webassemblyjs/helper-wasm-bytecode": "1.13.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@webassemblyjs/floating-point-hex-parser": {
|
"node_modules/@webassemblyjs/floating-point-hex-parser": {
|
||||||
"version": "1.11.6",
|
"version": "1.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
|
||||||
"integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
|
"integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@webassemblyjs/helper-api-error": {
|
"node_modules/@webassemblyjs/helper-api-error": {
|
||||||
"version": "1.11.6",
|
"version": "1.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
|
||||||
"integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
|
"integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@webassemblyjs/helper-buffer": {
|
"node_modules/@webassemblyjs/helper-buffer": {
|
||||||
"version": "1.12.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
|
||||||
"integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==",
|
"integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@webassemblyjs/helper-numbers": {
|
"node_modules/@webassemblyjs/helper-numbers": {
|
||||||
"version": "1.11.6",
|
"version": "1.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
|
||||||
"integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
|
"integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@webassemblyjs/floating-point-hex-parser": "1.11.6",
|
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
|
||||||
"@webassemblyjs/helper-api-error": "1.11.6",
|
"@webassemblyjs/helper-api-error": "1.13.2",
|
||||||
"@xtuc/long": "4.2.2"
|
"@xtuc/long": "4.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
|
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
|
||||||
"version": "1.11.6",
|
"version": "1.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
|
||||||
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
|
"integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@webassemblyjs/helper-wasm-section": {
|
"node_modules/@webassemblyjs/helper-wasm-section": {
|
||||||
"version": "1.12.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
|
||||||
"integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==",
|
"integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@webassemblyjs/ast": "1.12.1",
|
"@webassemblyjs/ast": "1.14.1",
|
||||||
"@webassemblyjs/helper-buffer": "1.12.1",
|
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
|
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||||
"@webassemblyjs/wasm-gen": "1.12.1"
|
"@webassemblyjs/wasm-gen": "1.14.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@webassemblyjs/ieee754": {
|
"node_modules/@webassemblyjs/ieee754": {
|
||||||
"version": "1.11.6",
|
"version": "1.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
|
||||||
"integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
|
"integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xtuc/ieee754": "^1.2.0"
|
"@xtuc/ieee754": "^1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@webassemblyjs/leb128": {
|
"node_modules/@webassemblyjs/leb128": {
|
||||||
"version": "1.11.6",
|
"version": "1.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
|
||||||
"integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
|
"integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xtuc/long": "4.2.2"
|
"@xtuc/long": "4.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@webassemblyjs/utf8": {
|
"node_modules/@webassemblyjs/utf8": {
|
||||||
"version": "1.11.6",
|
"version": "1.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
|
||||||
"integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==",
|
"integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@webassemblyjs/wasm-edit": {
|
"node_modules/@webassemblyjs/wasm-edit": {
|
||||||
"version": "1.12.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
|
||||||
"integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==",
|
"integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@webassemblyjs/ast": "1.12.1",
|
"@webassemblyjs/ast": "1.14.1",
|
||||||
"@webassemblyjs/helper-buffer": "1.12.1",
|
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
|
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||||
"@webassemblyjs/helper-wasm-section": "1.12.1",
|
"@webassemblyjs/helper-wasm-section": "1.14.1",
|
||||||
"@webassemblyjs/wasm-gen": "1.12.1",
|
"@webassemblyjs/wasm-gen": "1.14.1",
|
||||||
"@webassemblyjs/wasm-opt": "1.12.1",
|
"@webassemblyjs/wasm-opt": "1.14.1",
|
||||||
"@webassemblyjs/wasm-parser": "1.12.1",
|
"@webassemblyjs/wasm-parser": "1.14.1",
|
||||||
"@webassemblyjs/wast-printer": "1.12.1"
|
"@webassemblyjs/wast-printer": "1.14.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@webassemblyjs/wasm-gen": {
|
"node_modules/@webassemblyjs/wasm-gen": {
|
||||||
"version": "1.12.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
|
||||||
"integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==",
|
"integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@webassemblyjs/ast": "1.12.1",
|
"@webassemblyjs/ast": "1.14.1",
|
||||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
|
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||||
"@webassemblyjs/ieee754": "1.11.6",
|
"@webassemblyjs/ieee754": "1.13.2",
|
||||||
"@webassemblyjs/leb128": "1.11.6",
|
"@webassemblyjs/leb128": "1.13.2",
|
||||||
"@webassemblyjs/utf8": "1.11.6"
|
"@webassemblyjs/utf8": "1.13.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@webassemblyjs/wasm-opt": {
|
"node_modules/@webassemblyjs/wasm-opt": {
|
||||||
"version": "1.12.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
|
||||||
"integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==",
|
"integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@webassemblyjs/ast": "1.12.1",
|
"@webassemblyjs/ast": "1.14.1",
|
||||||
"@webassemblyjs/helper-buffer": "1.12.1",
|
"@webassemblyjs/helper-buffer": "1.14.1",
|
||||||
"@webassemblyjs/wasm-gen": "1.12.1",
|
"@webassemblyjs/wasm-gen": "1.14.1",
|
||||||
"@webassemblyjs/wasm-parser": "1.12.1"
|
"@webassemblyjs/wasm-parser": "1.14.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@webassemblyjs/wasm-parser": {
|
"node_modules/@webassemblyjs/wasm-parser": {
|
||||||
"version": "1.12.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
|
||||||
"integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==",
|
"integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@webassemblyjs/ast": "1.12.1",
|
"@webassemblyjs/ast": "1.14.1",
|
||||||
"@webassemblyjs/helper-api-error": "1.11.6",
|
"@webassemblyjs/helper-api-error": "1.13.2",
|
||||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
|
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
|
||||||
"@webassemblyjs/ieee754": "1.11.6",
|
"@webassemblyjs/ieee754": "1.13.2",
|
||||||
"@webassemblyjs/leb128": "1.11.6",
|
"@webassemblyjs/leb128": "1.13.2",
|
||||||
"@webassemblyjs/utf8": "1.11.6"
|
"@webassemblyjs/utf8": "1.13.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@webassemblyjs/wast-printer": {
|
"node_modules/@webassemblyjs/wast-printer": {
|
||||||
"version": "1.12.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
|
||||||
"integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==",
|
"integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@webassemblyjs/ast": "1.12.1",
|
"@webassemblyjs/ast": "1.14.1",
|
||||||
"@xtuc/long": "4.2.2"
|
"@xtuc/long": "4.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -9920,9 +9921,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/core-js": {
|
"node_modules/core-js": {
|
||||||
"version": "3.39.0",
|
"version": "3.40.0",
|
||||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz",
|
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.40.0.tgz",
|
||||||
"integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==",
|
"integrity": "sha512-7vsMc/Lty6AGnn7uFpYT56QesI5D2Y/UkgKounk87OP9Z2H9Z8kj6jzcSGAxFmUtDOS0ntK6lbQz+Nsa0Jj6mQ==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -12486,9 +12487,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/express": {
|
"node_modules/express": {
|
||||||
"version": "4.21.1",
|
"version": "4.21.2",
|
||||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||||
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
|
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "~1.3.8",
|
"accepts": "~1.3.8",
|
||||||
"array-flatten": "1.1.1",
|
"array-flatten": "1.1.1",
|
||||||
@@ -12509,7 +12510,7 @@
|
|||||||
"methods": "~1.1.2",
|
"methods": "~1.1.2",
|
||||||
"on-finished": "2.4.1",
|
"on-finished": "2.4.1",
|
||||||
"parseurl": "~1.3.3",
|
"parseurl": "~1.3.3",
|
||||||
"path-to-regexp": "0.1.10",
|
"path-to-regexp": "0.1.12",
|
||||||
"proxy-addr": "~2.0.7",
|
"proxy-addr": "~2.0.7",
|
||||||
"qs": "6.13.0",
|
"qs": "6.13.0",
|
||||||
"range-parser": "~1.2.1",
|
"range-parser": "~1.2.1",
|
||||||
@@ -12524,6 +12525,10 @@
|
|||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10.0"
|
"node": ">= 0.10.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express-rate-limit": {
|
"node_modules/express-rate-limit": {
|
||||||
@@ -14474,9 +14479,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/isbot": {
|
"node_modules/isbot": {
|
||||||
"version": "5.1.17",
|
"version": "5.1.21",
|
||||||
"resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.17.tgz",
|
"resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.21.tgz",
|
||||||
"integrity": "sha512-/wch8pRKZE+aoVhRX/hYPY1C7dMCeeMyhkQLNLNlYAbGQn9bkvMB8fOUXNnk5I0m4vDYbBJ9ciVtkr9zfBJ7qA==",
|
"integrity": "sha512-0q3naRVpENL0ReKHeNcwn/G7BDynp0DqZUckKyFtM9+hmpnPqgm8+8wbjiVZ0XNhq1wPQV28/Pb8Snh5adeUHA==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
@@ -15016,9 +15021,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/jsonschema": {
|
"node_modules/jsonschema": {
|
||||||
"version": "1.4.1",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.5.0.tgz",
|
||||||
"integrity": "sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==",
|
"integrity": "sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
@@ -16798,9 +16803,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.7",
|
"version": "3.3.8",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -16865,18 +16870,18 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/ng-mocks": {
|
"node_modules/ng-mocks": {
|
||||||
"version": "14.13.1",
|
"version": "14.13.2",
|
||||||
"resolved": "https://registry.npmjs.org/ng-mocks/-/ng-mocks-14.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/ng-mocks/-/ng-mocks-14.13.2.tgz",
|
||||||
"integrity": "sha512-eyfnjXeC108SqVD09i/cBwCpKkK0JjBoAg8jp7oQS2HS081K3WJTttFpgLGeLDYKmZsZ6nYpI+HHNQ3OksaJ7A==",
|
"integrity": "sha512-ItAB72Pc0uznL1j4TPsFp1wehhitVp7DARkc67aafeIk1FDgwnAZvzJwntMnIp/IWMSbzrEQ6kl3cc5euX1NRA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/help-me-mom"
|
"url": "https://github.com/sponsors/help-me-mom"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@angular/common": "5.0.0-alpha - 5 || 6.0.0-alpha - 6 || 7.0.0-alpha - 7 || 8.0.0-alpha - 8 || 9.0.0-alpha - 9 || 10.0.0-alpha - 10 || 11.0.0-alpha - 11 || 12.0.0-alpha - 12 || 13.0.0-alpha - 13 || 14.0.0-alpha - 14 || 15.0.0-alpha - 15 || 16.0.0-alpha - 16 || 17.0.0-alpha - 17 || 18.0.0-alpha - 18",
|
"@angular/common": "5.0.0-alpha - 5 || 6.0.0-alpha - 6 || 7.0.0-alpha - 7 || 8.0.0-alpha - 8 || 9.0.0-alpha - 9 || 10.0.0-alpha - 10 || 11.0.0-alpha - 11 || 12.0.0-alpha - 12 || 13.0.0-alpha - 13 || 14.0.0-alpha - 14 || 15.0.0-alpha - 15 || 16.0.0-alpha - 16 || 17.0.0-alpha - 17 || 18.0.0-alpha - 18 || 19.0.0-alpha - 19",
|
||||||
"@angular/core": "5.0.0-alpha - 5 || 6.0.0-alpha - 6 || 7.0.0-alpha - 7 || 8.0.0-alpha - 8 || 9.0.0-alpha - 9 || 10.0.0-alpha - 10 || 11.0.0-alpha - 11 || 12.0.0-alpha - 12 || 13.0.0-alpha - 13 || 14.0.0-alpha - 14 || 15.0.0-alpha - 15 || 16.0.0-alpha - 16 || 17.0.0-alpha - 17 || 18.0.0-alpha - 18",
|
"@angular/core": "5.0.0-alpha - 5 || 6.0.0-alpha - 6 || 7.0.0-alpha - 7 || 8.0.0-alpha - 8 || 9.0.0-alpha - 9 || 10.0.0-alpha - 10 || 11.0.0-alpha - 11 || 12.0.0-alpha - 12 || 13.0.0-alpha - 13 || 14.0.0-alpha - 14 || 15.0.0-alpha - 15 || 16.0.0-alpha - 16 || 17.0.0-alpha - 17 || 18.0.0-alpha - 18 || 19.0.0-alpha - 19",
|
||||||
"@angular/forms": "5.0.0-alpha - 5 || 6.0.0-alpha - 6 || 7.0.0-alpha - 7 || 8.0.0-alpha - 8 || 9.0.0-alpha - 9 || 10.0.0-alpha - 10 || 11.0.0-alpha - 11 || 12.0.0-alpha - 12 || 13.0.0-alpha - 13 || 14.0.0-alpha - 14 || 15.0.0-alpha - 15 || 16.0.0-alpha - 16 || 17.0.0-alpha - 17 || 18.0.0-alpha - 18",
|
"@angular/forms": "5.0.0-alpha - 5 || 6.0.0-alpha - 6 || 7.0.0-alpha - 7 || 8.0.0-alpha - 8 || 9.0.0-alpha - 9 || 10.0.0-alpha - 10 || 11.0.0-alpha - 11 || 12.0.0-alpha - 12 || 13.0.0-alpha - 13 || 14.0.0-alpha - 14 || 15.0.0-alpha - 15 || 16.0.0-alpha - 16 || 17.0.0-alpha - 17 || 18.0.0-alpha - 18 || 19.0.0-alpha - 19",
|
||||||
"@angular/platform-browser": "5.0.0-alpha - 5 || 6.0.0-alpha - 6 || 7.0.0-alpha - 7 || 8.0.0-alpha - 8 || 9.0.0-alpha - 9 || 10.0.0-alpha - 10 || 11.0.0-alpha - 11 || 12.0.0-alpha - 12 || 13.0.0-alpha - 13 || 14.0.0-alpha - 14 || 15.0.0-alpha - 15 || 16.0.0-alpha - 16 || 17.0.0-alpha - 17 || 18.0.0-alpha - 18"
|
"@angular/platform-browser": "5.0.0-alpha - 5 || 6.0.0-alpha - 6 || 7.0.0-alpha - 7 || 8.0.0-alpha - 8 || 9.0.0-alpha - 9 || 10.0.0-alpha - 10 || 11.0.0-alpha - 11 || 12.0.0-alpha - 12 || 13.0.0-alpha - 13 || 14.0.0-alpha - 14 || 15.0.0-alpha - 15 || 16.0.0-alpha - 16 || 17.0.0-alpha - 17 || 18.0.0-alpha - 18 || 19.0.0-alpha - 19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ng2-file-upload": {
|
"node_modules/ng2-file-upload": {
|
||||||
@@ -16941,6 +16946,19 @@
|
|||||||
"@angular/core": ">=13.0.0"
|
"@angular/core": ">=13.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ngx-skeleton-loader": {
|
||||||
|
"version": "9.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ngx-skeleton-loader/-/ngx-skeleton-loader-9.0.0.tgz",
|
||||||
|
"integrity": "sha512-aO4/V6oGdZGNcTjasTg/fwzJJYl/ZmNKgCukOEQdUK3GSFOZtB/3GGULMJuZ939hk3Hzqh1OBiLfIM1SqTfhqg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tslib": "^2.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@angular/common": ">=16.0.0",
|
||||||
|
"@angular/core": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ngx-ui-switch": {
|
"node_modules/ngx-ui-switch": {
|
||||||
"version": "14.1.0",
|
"version": "14.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/ngx-ui-switch/-/ngx-ui-switch-14.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ngx-ui-switch/-/ngx-ui-switch-14.1.0.tgz",
|
||||||
@@ -18188,9 +18206,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/path-to-regexp": {
|
"node_modules/path-to-regexp": {
|
||||||
"version": "0.1.10",
|
"version": "0.1.12",
|
||||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||||
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
|
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
|
||||||
},
|
},
|
||||||
"node_modules/path-type": {
|
"node_modules/path-type": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
@@ -18408,9 +18426,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.49",
|
"version": "8.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
|
||||||
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
|
"integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -18426,7 +18444,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.8",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"source-map-js": "^1.2.1"
|
"source-map-js": "^1.2.1"
|
||||||
},
|
},
|
||||||
@@ -20477,10 +20495,11 @@
|
|||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
|
||||||
},
|
},
|
||||||
"node_modules/sass": {
|
"node_modules/sass": {
|
||||||
"version": "1.83.1",
|
"version": "1.83.4",
|
||||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.83.1.tgz",
|
"resolved": "https://registry.npmjs.org/sass/-/sass-1.83.4.tgz",
|
||||||
"integrity": "sha512-EVJbDaEs4Rr3F0glJzFSOvtg2/oy2V/YrGFPqPY24UqcLDWcI9ZY5sN+qyO3c/QCZwzgfirvhXvINiJCE/OLcA==",
|
"integrity": "sha512-B1bozCeNQiOgDcLd33e2Cs2U60wZwjUUXzh900ZyQF5qUasvMdDZYbQ566LJu7cqR+sAHlAfO6RMkaID5s6qpA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^4.0.0",
|
"chokidar": "^4.0.0",
|
||||||
"immutable": "^5.0.2",
|
"immutable": "^5.0.2",
|
||||||
@@ -22624,16 +22643,16 @@
|
|||||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||||
},
|
},
|
||||||
"node_modules/webpack": {
|
"node_modules/webpack": {
|
||||||
"version": "5.96.1",
|
"version": "5.97.1",
|
||||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.96.1.tgz",
|
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz",
|
||||||
"integrity": "sha512-l2LlBSvVZGhL4ZrPwyr8+37AunkcYj5qh8o6u2/2rzoPc8gxFJkLj1WxNgooi9pnoc06jh0BjuXnamM4qlujZA==",
|
"integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/eslint-scope": "^3.7.7",
|
"@types/eslint-scope": "^3.7.7",
|
||||||
"@types/estree": "^1.0.6",
|
"@types/estree": "^1.0.6",
|
||||||
"@webassemblyjs/ast": "^1.12.1",
|
"@webassemblyjs/ast": "^1.14.1",
|
||||||
"@webassemblyjs/wasm-edit": "^1.12.1",
|
"@webassemblyjs/wasm-edit": "^1.14.1",
|
||||||
"@webassemblyjs/wasm-parser": "^1.12.1",
|
"@webassemblyjs/wasm-parser": "^1.14.1",
|
||||||
"acorn": "^8.14.0",
|
"acorn": "^8.14.0",
|
||||||
"browserslist": "^4.24.0",
|
"browserslist": "^4.24.0",
|
||||||
"chrome-trace-event": "^1.0.2",
|
"chrome-trace-event": "^1.0.2",
|
||||||
|
19
package.json
19
package.json
@@ -126,22 +126,22 @@
|
|||||||
"colors": "^1.4.0",
|
"colors": "^1.4.0",
|
||||||
"compression": "^1.7.5",
|
"compression": "^1.7.5",
|
||||||
"cookie-parser": "1.4.7",
|
"cookie-parser": "1.4.7",
|
||||||
"core-js": "^3.39.0",
|
"core-js": "^3.40.0",
|
||||||
"date-fns": "^2.29.3",
|
"date-fns": "^2.29.3",
|
||||||
"date-fns-tz": "^1.3.7",
|
"date-fns-tz": "^1.3.7",
|
||||||
"deepmerge": "^4.3.1",
|
"deepmerge": "^4.3.1",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^4.21.1",
|
"express": "^4.21.2",
|
||||||
"express-rate-limit": "^5.1.3",
|
"express-rate-limit": "^5.1.3",
|
||||||
"fast-json-patch": "^3.1.1",
|
"fast-json-patch": "^3.1.1",
|
||||||
"filesize": "^6.1.0",
|
"filesize": "^6.1.0",
|
||||||
"http-proxy-middleware": "^2.0.7",
|
"http-proxy-middleware": "^2.0.7",
|
||||||
"http-terminator": "^3.2.0",
|
"http-terminator": "^3.2.0",
|
||||||
"isbot": "^5.1.17",
|
"isbot": "^5.1.21",
|
||||||
"js-cookie": "2.2.1",
|
"js-cookie": "2.2.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
"jsonschema": "1.4.1",
|
"jsonschema": "1.5.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lru-cache": "^7.14.1",
|
"lru-cache": "^7.14.1",
|
||||||
@@ -154,6 +154,7 @@
|
|||||||
"ng2-nouislider": "^2.0.0",
|
"ng2-nouislider": "^2.0.0",
|
||||||
"ngx-infinite-scroll": "^16.0.0",
|
"ngx-infinite-scroll": "^16.0.0",
|
||||||
"ngx-pagination": "6.0.3",
|
"ngx-pagination": "6.0.3",
|
||||||
|
"ngx-skeleton-loader": "^9.0.0",
|
||||||
"ngx-ui-switch": "^14.1.0",
|
"ngx-ui-switch": "^14.1.0",
|
||||||
"nouislider": "^15.7.1",
|
"nouislider": "^15.7.1",
|
||||||
"orejime": "^2.3.1",
|
"orejime": "^2.3.1",
|
||||||
@@ -186,7 +187,7 @@
|
|||||||
"@types/grecaptcha": "^3.0.9",
|
"@types/grecaptcha": "^3.0.9",
|
||||||
"@types/jasmine": "~3.6.0",
|
"@types/jasmine": "~3.6.0",
|
||||||
"@types/js-cookie": "2.2.6",
|
"@types/js-cookie": "2.2.6",
|
||||||
"@types/lodash": "^4.17.13",
|
"@types/lodash": "^4.17.14",
|
||||||
"@types/node": "^14.14.9",
|
"@types/node": "^14.14.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||||
"@typescript-eslint/parser": "^7.18.0",
|
"@typescript-eslint/parser": "^7.18.0",
|
||||||
@@ -221,20 +222,20 @@
|
|||||||
"karma-jasmine": "~4.0.0",
|
"karma-jasmine": "~4.0.0",
|
||||||
"karma-jasmine-html-reporter": "^1.5.0",
|
"karma-jasmine-html-reporter": "^1.5.0",
|
||||||
"karma-mocha-reporter": "2.2.5",
|
"karma-mocha-reporter": "2.2.5",
|
||||||
"ng-mocks": "^14.13.1",
|
"ng-mocks": "^14.13.2",
|
||||||
"ngx-mask": "14.2.4",
|
"ngx-mask": "14.2.4",
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^2.0.22",
|
||||||
"postcss": "^8.4",
|
"postcss": "^8.5",
|
||||||
"postcss-import": "^14.0.0",
|
"postcss-import": "^14.0.0",
|
||||||
"postcss-loader": "^4.0.3",
|
"postcss-loader": "^4.0.3",
|
||||||
"postcss-preset-env": "^7.4.2",
|
"postcss-preset-env": "^7.4.2",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"sass": "~1.83.1",
|
"sass": "~1.83.4",
|
||||||
"sass-loader": "^12.6.0",
|
"sass-loader": "^12.6.0",
|
||||||
"sass-resources-loader": "^2.2.5",
|
"sass-resources-loader": "^2.2.5",
|
||||||
"ts-node": "^8.10.2",
|
"ts-node": "^8.10.2",
|
||||||
"typescript": "~5.4.5",
|
"typescript": "~5.4.5",
|
||||||
"webpack": "5.96.1",
|
"webpack": "5.97.1",
|
||||||
"webpack-cli": "^5.1.4",
|
"webpack-cli": "^5.1.4",
|
||||||
"webpack-dev-server": "^4.15.1"
|
"webpack-dev-server": "^4.15.1"
|
||||||
}
|
}
|
||||||
|
@@ -218,7 +218,7 @@ export function app() {
|
|||||||
* The callback function to serve server side angular
|
* The callback function to serve server side angular
|
||||||
*/
|
*/
|
||||||
function ngApp(req, res, next) {
|
function ngApp(req, res, next) {
|
||||||
if (environment.ssr.enabled) {
|
if (environment.ssr.enabled && req.method === 'GET' && (req.path === '/' || environment.ssr.paths.some(pathPrefix => req.path.startsWith(pathPrefix)))) {
|
||||||
// Render the page to user via SSR (server side rendering)
|
// Render the page to user via SSR (server side rendering)
|
||||||
serverSideRender(req, res, next);
|
serverSideRender(req, res, next);
|
||||||
} else {
|
} else {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<ng-container *ngVar="(bitstreamRD$ | async) as bitstreamRD">
|
<ng-container *ngVar="(bitstreamRD$ | async) as bitstreamRD">
|
||||||
<div class="container" *ngVar="(bitstreamFormatsRD$ | async) as formatsRD">
|
<div class="container">
|
||||||
<div class="row" *ngIf="bitstreamRD?.hasSucceeded && formatsRD?.hasSucceeded">
|
<div class="row" *ngIf="bitstreamRD?.hasSucceeded">
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
<ds-thumbnail [thumbnail]="bitstreamRD?.payload"></ds-thumbnail>
|
<ds-thumbnail [thumbnail]="bitstreamRD?.payload"></ds-thumbnail>
|
||||||
</div>
|
</div>
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ds-error *ngIf="bitstreamRD?.hasFailed" message="{{'error.bitstream' | translate}}"></ds-error>
|
<ds-error *ngIf="bitstreamRD?.hasFailed" message="{{'error.bitstream' | translate}}"></ds-error>
|
||||||
<ds-loading *ngIf="!bitstreamRD || !formatsRD || bitstreamRD?.isLoading || formatsRD?.isLoading"
|
<ds-loading *ngIf="!bitstreamRD || bitstreamRD?.isLoading"
|
||||||
message="{{'loading.bitstream' | translate}}"></ds-loading>
|
message="{{'loading.bitstream' | translate}}"></ds-loading>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@@ -261,7 +261,7 @@ describe('EditBitstreamPageComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should select the correct format', () => {
|
it('should select the correct format', () => {
|
||||||
expect(rawForm.formatContainer.selectedFormat).toEqual(selectedFormat.id);
|
expect(rawForm.formatContainer.selectedFormat).toEqual(selectedFormat.shortDescription);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should put the \"New Format\" input on invisible', () => {
|
it('should put the \"New Format\" input on invisible', () => {
|
||||||
@@ -292,7 +292,13 @@ describe('EditBitstreamPageComponent', () => {
|
|||||||
|
|
||||||
describe('when an unknown format is selected', () => {
|
describe('when an unknown format is selected', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
comp.updateNewFormatLayout(allFormats[0].id);
|
comp.onChange({
|
||||||
|
model: {
|
||||||
|
id: 'selectedFormat',
|
||||||
|
value: allFormats[0],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
comp.updateNewFormatLayout();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove the invisible class from the \"New Format\" input', () => {
|
it('should remove the invisible class from the \"New Format\" input', () => {
|
||||||
@@ -394,9 +400,10 @@ describe('EditBitstreamPageComponent', () => {
|
|||||||
|
|
||||||
describe('when selected format has changed', () => {
|
describe('when selected format has changed', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
comp.formGroup.patchValue({
|
comp.onChange({
|
||||||
formatContainer: {
|
model: {
|
||||||
selectedFormat: allFormats[2].id,
|
id: 'selectedFormat',
|
||||||
|
value: allFormats[2],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
@@ -21,7 +21,6 @@ import {
|
|||||||
DynamicFormLayout,
|
DynamicFormLayout,
|
||||||
DynamicFormService,
|
DynamicFormService,
|
||||||
DynamicInputModel,
|
DynamicInputModel,
|
||||||
DynamicSelectModel,
|
|
||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
import {
|
import {
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
@@ -39,23 +38,24 @@ import {
|
|||||||
filter,
|
filter,
|
||||||
map,
|
map,
|
||||||
switchMap,
|
switchMap,
|
||||||
|
take,
|
||||||
tap,
|
tap,
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
|
|
||||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { FindAllDataImpl } from '../../core/data/base/find-all-data';
|
||||||
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
|
||||||
import { BitstreamFormatDataService } from '../../core/data/bitstream-format-data.service';
|
import { BitstreamFormatDataService } from '../../core/data/bitstream-format-data.service';
|
||||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
|
||||||
import { PrimaryBitstreamService } from '../../core/data/primary-bitstream.service';
|
import { PrimaryBitstreamService } from '../../core/data/primary-bitstream.service';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { Bitstream } from '../../core/shared/bitstream.model';
|
import { Bitstream } from '../../core/shared/bitstream.model';
|
||||||
import { BitstreamFormat } from '../../core/shared/bitstream-format.model';
|
import { BitstreamFormat } from '../../core/shared/bitstream-format.model';
|
||||||
|
import { BITSTREAM_FORMAT } from '../../core/shared/bitstream-format.resource-type';
|
||||||
import { BitstreamFormatSupportLevel } from '../../core/shared/bitstream-format-support-level';
|
import { BitstreamFormatSupportLevel } from '../../core/shared/bitstream-format-support-level';
|
||||||
import { Bundle } from '../../core/shared/bundle.model';
|
import { Bundle } from '../../core/shared/bundle.model';
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
import { Metadata } from '../../core/shared/metadata.utils';
|
import { Metadata } from '../../core/shared/metadata.utils';
|
||||||
import {
|
import {
|
||||||
getAllSucceededRemoteDataPayload,
|
|
||||||
getFirstCompletedRemoteData,
|
getFirstCompletedRemoteData,
|
||||||
getFirstSucceededRemoteData,
|
getFirstSucceededRemoteData,
|
||||||
getFirstSucceededRemoteDataPayload,
|
getFirstSucceededRemoteDataPayload,
|
||||||
@@ -72,6 +72,7 @@ import { ErrorComponent } from '../../shared/error/error.component';
|
|||||||
import { DynamicCustomSwitchModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
|
import { DynamicCustomSwitchModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
|
||||||
import { DsDynamicInputModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model';
|
import { DsDynamicInputModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-input.model';
|
||||||
import { DsDynamicTextAreaModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-textarea.model';
|
import { DsDynamicTextAreaModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/ds-dynamic-textarea.model';
|
||||||
|
import { DynamicScrollableDropdownModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.model';
|
||||||
import { FormComponent } from '../../shared/form/form.component';
|
import { FormComponent } from '../../shared/form/form.component';
|
||||||
import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component';
|
import { ThemedLoadingComponent } from '../../shared/loading/themed-loading.component';
|
||||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||||
@@ -109,12 +110,6 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
bitstreamRD$: Observable<RemoteData<Bitstream>>;
|
bitstreamRD$: Observable<RemoteData<Bitstream>>;
|
||||||
|
|
||||||
/**
|
|
||||||
* The formats their remote data observable
|
|
||||||
* Tracks changes and updates the view
|
|
||||||
*/
|
|
||||||
bitstreamFormatsRD$: Observable<RemoteData<PaginatedList<BitstreamFormat>>>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The UUID of the primary bitstream for this bundle
|
* The UUID of the primary bitstream for this bundle
|
||||||
*/
|
*/
|
||||||
@@ -130,11 +125,6 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
originalFormat: BitstreamFormat;
|
originalFormat: BitstreamFormat;
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of all available bitstream formats
|
|
||||||
*/
|
|
||||||
formats: BitstreamFormat[];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {string} Key prefix used to generate form messages
|
* @type {string} Key prefix used to generate form messages
|
||||||
*/
|
*/
|
||||||
@@ -178,7 +168,10 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* Options for fetching all bitstream formats
|
* Options for fetching all bitstream formats
|
||||||
*/
|
*/
|
||||||
findAllOptions = { elementsPerPage: 9999 };
|
findAllOptions = {
|
||||||
|
elementsPerPage: 20,
|
||||||
|
currentPage: 1,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Dynamic Input Model for the file's name
|
* The Dynamic Input Model for the file's name
|
||||||
@@ -218,9 +211,22 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* The Dynamic Input Model for the selected format
|
* The Dynamic Input Model for the selected format
|
||||||
*/
|
*/
|
||||||
selectedFormatModel = new DynamicSelectModel({
|
selectedFormatModel = new DynamicScrollableDropdownModel({
|
||||||
id: 'selectedFormat',
|
id: 'selectedFormat',
|
||||||
name: 'selectedFormat',
|
name: 'selectedFormat',
|
||||||
|
displayKey: 'shortDescription',
|
||||||
|
repeatable: false,
|
||||||
|
metadataFields: [],
|
||||||
|
submissionId: '',
|
||||||
|
hasSelectableMetadata: false,
|
||||||
|
resourceType: BITSTREAM_FORMAT,
|
||||||
|
formatFunction: (format: BitstreamFormat | string) => {
|
||||||
|
if (format instanceof BitstreamFormat) {
|
||||||
|
return hasValue(format) && format.supportLevel === BitstreamFormatSupportLevel.Unknown ? this.translate.instant(this.KEY_PREFIX + 'selectedFormat.unknown') : format.shortDescription;
|
||||||
|
} else {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -438,6 +444,11 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
private bundle: Bundle;
|
private bundle: Bundle;
|
||||||
|
/**
|
||||||
|
* The currently selected format
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private selectedFormat: BitstreamFormat;
|
||||||
|
|
||||||
constructor(private route: ActivatedRoute,
|
constructor(private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
@@ -463,18 +474,12 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
this.itemId = this.route.snapshot.queryParams.itemId;
|
this.itemId = this.route.snapshot.queryParams.itemId;
|
||||||
this.entityType = this.route.snapshot.queryParams.entityType;
|
this.entityType = this.route.snapshot.queryParams.entityType;
|
||||||
this.bitstreamRD$ = this.route.data.pipe(map((data: any) => data.bitstream));
|
this.bitstreamRD$ = this.route.data.pipe(map((data: any) => data.bitstream));
|
||||||
this.bitstreamFormatsRD$ = this.bitstreamFormatService.findAll(this.findAllOptions);
|
|
||||||
|
|
||||||
const bitstream$ = this.bitstreamRD$.pipe(
|
const bitstream$ = this.bitstreamRD$.pipe(
|
||||||
getFirstSucceededRemoteData(),
|
getFirstSucceededRemoteData(),
|
||||||
getRemoteDataPayload(),
|
getRemoteDataPayload(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const allFormats$ = this.bitstreamFormatsRD$.pipe(
|
|
||||||
getFirstSucceededRemoteData(),
|
|
||||||
getRemoteDataPayload(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const bundle$ = bitstream$.pipe(
|
const bundle$ = bitstream$.pipe(
|
||||||
switchMap((bitstream: Bitstream) => bitstream.bundle),
|
switchMap((bitstream: Bitstream) => bitstream.bundle),
|
||||||
getFirstSucceededRemoteDataPayload(),
|
getFirstSucceededRemoteDataPayload(),
|
||||||
@@ -490,24 +495,31 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
switchMap((bundle: Bundle) => bundle.item),
|
switchMap((bundle: Bundle) => bundle.item),
|
||||||
getFirstSucceededRemoteDataPayload(),
|
getFirstSucceededRemoteDataPayload(),
|
||||||
);
|
);
|
||||||
|
const format$ = bitstream$.pipe(
|
||||||
|
switchMap(bitstream => bitstream.format),
|
||||||
|
getFirstSucceededRemoteDataPayload(),
|
||||||
|
);
|
||||||
|
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
observableCombineLatest(
|
observableCombineLatest(
|
||||||
bitstream$,
|
bitstream$,
|
||||||
allFormats$,
|
|
||||||
bundle$,
|
bundle$,
|
||||||
primaryBitstream$,
|
primaryBitstream$,
|
||||||
item$,
|
item$,
|
||||||
).pipe()
|
format$,
|
||||||
.subscribe(([bitstream, allFormats, bundle, primaryBitstream, item]) => {
|
).subscribe(([bitstream, bundle, primaryBitstream, item, format]) => {
|
||||||
this.bitstream = bitstream as Bitstream;
|
this.bitstream = bitstream as Bitstream;
|
||||||
this.formats = allFormats.page;
|
|
||||||
this.bundle = bundle;
|
this.bundle = bundle;
|
||||||
|
this.selectedFormat = format;
|
||||||
// hasValue(primaryBitstream) because if there's no primaryBitstream on the bundle it will
|
// hasValue(primaryBitstream) because if there's no primaryBitstream on the bundle it will
|
||||||
// be a success response, but empty
|
// be a success response, but empty
|
||||||
this.primaryBitstreamUUID = hasValue(primaryBitstream) ? primaryBitstream.uuid : null;
|
this.primaryBitstreamUUID = hasValue(primaryBitstream) ? primaryBitstream.uuid : null;
|
||||||
this.itemId = item.uuid;
|
this.itemId = item.uuid;
|
||||||
this.setIiifStatus(this.bitstream);
|
this.setIiifStatus(this.bitstream);
|
||||||
}),
|
}),
|
||||||
|
format$.pipe(take(1)).subscribe(
|
||||||
|
(format) => this.originalFormat = format,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
@@ -523,7 +535,6 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
setForm() {
|
setForm() {
|
||||||
this.formGroup = this.formService.createFormGroup(this.formModel);
|
this.formGroup = this.formService.createFormGroup(this.formModel);
|
||||||
this.updateFormatModel();
|
|
||||||
this.updateForm(this.bitstream);
|
this.updateForm(this.bitstream);
|
||||||
this.updateFieldTranslations();
|
this.updateFieldTranslations();
|
||||||
}
|
}
|
||||||
@@ -542,6 +553,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
description: bitstream.firstMetadataValue('dc.description'),
|
description: bitstream.firstMetadataValue('dc.description'),
|
||||||
},
|
},
|
||||||
formatContainer: {
|
formatContainer: {
|
||||||
|
selectedFormat: this.selectedFormat.shortDescription,
|
||||||
newFormat: hasValue(bitstream.firstMetadata('dc.format')) ? bitstream.firstMetadata('dc.format').value : undefined,
|
newFormat: hasValue(bitstream.firstMetadata('dc.format')) ? bitstream.firstMetadata('dc.format').value : undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -561,36 +573,16 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.bitstream.format.pipe(
|
this.updateNewFormatLayout();
|
||||||
getAllSucceededRemoteDataPayload(),
|
|
||||||
).subscribe((format: BitstreamFormat) => {
|
|
||||||
this.originalFormat = format;
|
|
||||||
this.formGroup.patchValue({
|
|
||||||
formatContainer: {
|
|
||||||
selectedFormat: format.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.updateNewFormatLayout(format.id);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create the list of unknown format IDs an add options to the selectedFormatModel
|
|
||||||
*/
|
|
||||||
updateFormatModel() {
|
|
||||||
this.selectedFormatModel.options = this.formats.map((format: BitstreamFormat) =>
|
|
||||||
Object.assign({
|
|
||||||
value: format.id,
|
|
||||||
label: this.isUnknownFormat(format.id) ? this.translate.instant(this.KEY_PREFIX + 'selectedFormat.unknown') : format.shortDescription,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the layout of the "Other Format" input depending on the selected format
|
* Update the layout of the "Other Format" input depending on the selected format
|
||||||
* @param selectedId
|
* @param selectedId
|
||||||
*/
|
*/
|
||||||
updateNewFormatLayout(selectedId: string) {
|
updateNewFormatLayout() {
|
||||||
if (this.isUnknownFormat(selectedId)) {
|
if (this.isUnknownFormat()) {
|
||||||
this.formLayout.newFormat.grid.host = this.newFormatBaseLayout;
|
this.formLayout.newFormat.grid.host = this.newFormatBaseLayout;
|
||||||
} else {
|
} else {
|
||||||
this.formLayout.newFormat.grid.host = this.newFormatBaseLayout + ' invisible';
|
this.formLayout.newFormat.grid.host = this.newFormatBaseLayout + ' invisible';
|
||||||
@@ -601,9 +593,8 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
* Is the provided format (id) part of the list of unknown formats?
|
* Is the provided format (id) part of the list of unknown formats?
|
||||||
* @param id
|
* @param id
|
||||||
*/
|
*/
|
||||||
isUnknownFormat(id: string): boolean {
|
isUnknownFormat(): boolean {
|
||||||
const format = this.formats.find((f: BitstreamFormat) => f.id === id);
|
return hasValue(this.selectedFormat) && this.selectedFormat.supportLevel === BitstreamFormatSupportLevel.Unknown;
|
||||||
return hasValue(format) && format.supportLevel === BitstreamFormatSupportLevel.Unknown;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -635,7 +626,8 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
onChange(event) {
|
onChange(event) {
|
||||||
const model = event.model;
|
const model = event.model;
|
||||||
if (model.id === this.selectedFormatModel.id) {
|
if (model.id === this.selectedFormatModel.id) {
|
||||||
this.updateNewFormatLayout(model.value);
|
this.selectedFormat = model.value;
|
||||||
|
this.updateNewFormatLayout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,8 +637,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
onSubmit() {
|
onSubmit() {
|
||||||
const updatedValues = this.formGroup.getRawValue();
|
const updatedValues = this.formGroup.getRawValue();
|
||||||
const updatedBitstream = this.formToBitstream(updatedValues);
|
const updatedBitstream = this.formToBitstream(updatedValues);
|
||||||
const selectedFormat = this.formats.find((f: BitstreamFormat) => f.id === updatedValues.formatContainer.selectedFormat);
|
const isNewFormat = this.selectedFormat.id !== this.originalFormat.id;
|
||||||
const isNewFormat = selectedFormat.id !== this.originalFormat.id;
|
|
||||||
const isPrimary = updatedValues.fileNamePrimaryContainer.primaryBitstream;
|
const isPrimary = updatedValues.fileNamePrimaryContainer.primaryBitstream;
|
||||||
const wasPrimary = this.primaryBitstreamUUID === this.bitstream.uuid;
|
const wasPrimary = this.primaryBitstreamUUID === this.bitstream.uuid;
|
||||||
|
|
||||||
@@ -698,7 +689,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
bundle$ = observableOf(this.bundle);
|
bundle$ = observableOf(this.bundle);
|
||||||
}
|
}
|
||||||
if (isNewFormat) {
|
if (isNewFormat) {
|
||||||
bitstream$ = this.bitstreamService.updateFormat(this.bitstream, selectedFormat).pipe(
|
bitstream$ = this.bitstreamService.updateFormat(this.bitstream, this.selectedFormat).pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
map((formatResponse: RemoteData<Bitstream>) => {
|
map((formatResponse: RemoteData<Bitstream>) => {
|
||||||
if (hasValue(formatResponse) && formatResponse.hasFailed) {
|
if (hasValue(formatResponse) && formatResponse.hasFailed) {
|
||||||
@@ -856,4 +847,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
|
|||||||
.forEach((subscription) => subscription.unsubscribe());
|
.forEach((subscription) => subscription.unsubscribe());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
findAllFormatsServiceFactory() {
|
||||||
|
return () => this.bitstreamFormatService as any as FindAllDataImpl<BitstreamFormat>;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -2,10 +2,13 @@ import { CommonModule } from '@angular/common';
|
|||||||
import {
|
import {
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
NO_ERRORS_SCHEMA,
|
NO_ERRORS_SCHEMA,
|
||||||
|
PLATFORM_ID,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ComponentFixture,
|
ComponentFixture,
|
||||||
|
fakeAsync,
|
||||||
TestBed,
|
TestBed,
|
||||||
|
tick,
|
||||||
waitForAsync,
|
waitForAsync,
|
||||||
} from '@angular/core/testing';
|
} from '@angular/core/testing';
|
||||||
import {
|
import {
|
||||||
@@ -26,6 +29,7 @@ import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-
|
|||||||
import { SortDirection } from '../../core/cache/models/sort-options.model';
|
import { SortDirection } from '../../core/cache/models/sort-options.model';
|
||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
|
import { BrowseEntry } from '../../core/shared/browse-entry.model';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
import { ThemedBrowseByComponent } from '../../shared/browse-by/themed-browse-by.component';
|
import { ThemedBrowseByComponent } from '../../shared/browse-by/themed-browse-by.component';
|
||||||
@@ -123,6 +127,7 @@ describe('BrowseByDateComponent', () => {
|
|||||||
{ provide: ChangeDetectorRef, useValue: mockCdRef },
|
{ provide: ChangeDetectorRef, useValue: mockCdRef },
|
||||||
{ provide: Store, useValue: {} },
|
{ provide: Store, useValue: {} },
|
||||||
{ provide: APP_CONFIG, useValue: environment },
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
|
{ provide: PLATFORM_ID, useValue: 'browser' },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA],
|
schemas: [NO_ERRORS_SCHEMA],
|
||||||
})
|
})
|
||||||
@@ -172,4 +177,33 @@ describe('BrowseByDateComponent', () => {
|
|||||||
//expect(comp.startsWithOptions[0]).toEqual(new Date().getUTCFullYear());
|
//expect(comp.startsWithOptions[0]).toEqual(new Date().getUTCFullYear());
|
||||||
expect(comp.startsWithOptions[0]).toEqual(1960);
|
expect(comp.startsWithOptions[0]).toEqual(1960);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when rendered in SSR', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.platformId = 'server';
|
||||||
|
spyOn((comp as any).browseService, 'getBrowseItemsFor');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not call getBrowseItemsFor on init', (done) => {
|
||||||
|
comp.ngOnInit();
|
||||||
|
expect((comp as any).browseService.getBrowseItemsFor).not.toHaveBeenCalled();
|
||||||
|
comp.loading$.subscribe((res) => {
|
||||||
|
expect(res).toBeFalsy();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when rendered in CSR', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.platformId = 'browser';
|
||||||
|
spyOn((comp as any).browseService, 'getBrowseItemsFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry()));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call getBrowseItemsFor on init', fakeAsync(() => {
|
||||||
|
comp.ngOnInit();
|
||||||
|
tick(100);
|
||||||
|
expect((comp as any).browseService.getBrowseItemsFor).toHaveBeenCalled();
|
||||||
|
}));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
|
isPlatformServer,
|
||||||
NgIf,
|
NgIf,
|
||||||
} from '@angular/common';
|
} from '@angular/common';
|
||||||
import {
|
import {
|
||||||
@@ -7,6 +8,7 @@ import {
|
|||||||
Component,
|
Component,
|
||||||
Inject,
|
Inject,
|
||||||
OnInit,
|
OnInit,
|
||||||
|
PLATFORM_ID,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRoute,
|
ActivatedRoute,
|
||||||
@@ -17,6 +19,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import {
|
import {
|
||||||
combineLatest as observableCombineLatest,
|
combineLatest as observableCombineLatest,
|
||||||
Observable,
|
Observable,
|
||||||
|
of as observableOf,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
map,
|
map,
|
||||||
@@ -28,6 +31,7 @@ import {
|
|||||||
APP_CONFIG,
|
APP_CONFIG,
|
||||||
AppConfig,
|
AppConfig,
|
||||||
} from '../../../config/app-config.interface';
|
} from '../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
import { BrowseService } from '../../core/browse/browse.service';
|
import { BrowseService } from '../../core/browse/browse.service';
|
||||||
import {
|
import {
|
||||||
@@ -99,11 +103,16 @@ export class BrowseByDateComponent extends BrowseByMetadataComponent implements
|
|||||||
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
||||||
public dsoNameService: DSONameService,
|
public dsoNameService: DSONameService,
|
||||||
protected cdRef: ChangeDetectorRef,
|
protected cdRef: ChangeDetectorRef,
|
||||||
|
@Inject(PLATFORM_ID) public platformId: any,
|
||||||
) {
|
) {
|
||||||
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService);
|
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService, platformId);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (!this.renderOnServerSide && !environment.ssr.enableBrowseComponent && isPlatformServer(this.platformId)) {
|
||||||
|
this.loading$ = observableOf(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sortConfig = new SortOptions('default', SortDirection.ASC);
|
const sortConfig = new SortOptions('default', SortDirection.ASC);
|
||||||
this.startsWithType = StartsWithType.date;
|
this.startsWithType = StartsWithType.date;
|
||||||
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<section class="comcol-page-browse-section">
|
<section class="comcol-page-browse-section" *ngIf="(!ssrRenderingDisabled)">
|
||||||
<div class="browse-by-metadata w-100">
|
<div class="browse-by-metadata w-100">
|
||||||
<ds-browse-by *ngIf="(loading$ | async) !== true" class="col-xs-12 w-100"
|
<ds-browse-by *ngIf="(loading$ | async) !== true" class="col-xs-12 w-100"
|
||||||
title="{{'browse.title' | translate:{
|
title="{{'browse.title' | translate:{
|
||||||
|
@@ -1,8 +1,13 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import {
|
||||||
|
NO_ERRORS_SCHEMA,
|
||||||
|
PLATFORM_ID,
|
||||||
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ComponentFixture,
|
ComponentFixture,
|
||||||
|
fakeAsync,
|
||||||
TestBed,
|
TestBed,
|
||||||
|
tick,
|
||||||
waitForAsync,
|
waitForAsync,
|
||||||
} from '@angular/core/testing';
|
} from '@angular/core/testing';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
@@ -147,6 +152,7 @@ describe('BrowseByMetadataComponent', () => {
|
|||||||
{ provide: ThemeService, useValue: getMockThemeService() },
|
{ provide: ThemeService, useValue: getMockThemeService() },
|
||||||
{ provide: SelectableListService, useValue: {} },
|
{ provide: SelectableListService, useValue: {} },
|
||||||
{ provide: HostWindowService, useValue: {} },
|
{ provide: HostWindowService, useValue: {} },
|
||||||
|
{ provide: PLATFORM_ID, useValue: 'browser' },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA],
|
schemas: [NO_ERRORS_SCHEMA],
|
||||||
})
|
})
|
||||||
@@ -259,6 +265,35 @@ describe('BrowseByMetadataComponent', () => {
|
|||||||
expect(result.fetchThumbnail).toBeTrue();
|
expect(result.fetchThumbnail).toBeTrue();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when rendered in SSR', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.ssrRenderingDisabled = true;
|
||||||
|
spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(null));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not call getBrowseEntriesFor on init', (done) => {
|
||||||
|
comp.ngOnInit();
|
||||||
|
expect((comp as any).browseService.getBrowseEntriesFor).not.toHaveBeenCalled();
|
||||||
|
comp.loading$.subscribe((res) => {
|
||||||
|
expect(res).toBeFalsy();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when rendered in CSR', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.ssrRenderingDisabled = false;
|
||||||
|
spyOn((comp as any).browseService, 'getBrowseEntriesFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry()));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call getBrowseEntriesFor on init', fakeAsync(() => {
|
||||||
|
comp.ngOnInit();
|
||||||
|
tick(100);
|
||||||
|
expect((comp as any).browseService.getBrowseEntriesFor).toHaveBeenCalled();
|
||||||
|
}));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
export function toRemoteData(objects: any[]): Observable<RemoteData<PaginatedList<any>>> {
|
export function toRemoteData(objects: any[]): Observable<RemoteData<PaginatedList<any>>> {
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
|
isPlatformServer,
|
||||||
NgIf,
|
NgIf,
|
||||||
} from '@angular/common';
|
} from '@angular/common';
|
||||||
import {
|
import {
|
||||||
@@ -9,6 +10,7 @@ import {
|
|||||||
OnChanges,
|
OnChanges,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
|
PLATFORM_ID,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRoute,
|
ActivatedRoute,
|
||||||
@@ -33,6 +35,7 @@ import {
|
|||||||
APP_CONFIG,
|
APP_CONFIG,
|
||||||
AppConfig,
|
AppConfig,
|
||||||
} from '../../../config/app-config.interface';
|
} from '../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
import { BrowseService } from '../../core/browse/browse.service';
|
import { BrowseService } from '../../core/browse/browse.service';
|
||||||
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
|
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
|
||||||
@@ -114,6 +117,11 @@ export class BrowseByMetadataComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
@Input() displayTitle = true;
|
@Input() displayTitle = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines whether to fetch search results during SSR execution
|
||||||
|
*/
|
||||||
|
@Input() renderOnServerSide: boolean;
|
||||||
|
|
||||||
scope$: BehaviorSubject<string> = new BehaviorSubject(undefined);
|
scope$: BehaviorSubject<string> = new BehaviorSubject(undefined);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -194,6 +202,10 @@ export class BrowseByMetadataComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
* Observable determining if the loading animation needs to be shown
|
* Observable determining if the loading animation needs to be shown
|
||||||
*/
|
*/
|
||||||
loading$ = observableOf(true);
|
loading$ = observableOf(true);
|
||||||
|
/**
|
||||||
|
* Whether this component should be rendered or not in SSR
|
||||||
|
*/
|
||||||
|
ssrRenderingDisabled = false;
|
||||||
|
|
||||||
public constructor(protected route: ActivatedRoute,
|
public constructor(protected route: ActivatedRoute,
|
||||||
protected browseService: BrowseService,
|
protected browseService: BrowseService,
|
||||||
@@ -202,6 +214,7 @@ export class BrowseByMetadataComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
protected router: Router,
|
protected router: Router,
|
||||||
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
||||||
public dsoNameService: DSONameService,
|
public dsoNameService: DSONameService,
|
||||||
|
@Inject(PLATFORM_ID) public platformId: any,
|
||||||
) {
|
) {
|
||||||
this.fetchThumbnails = this.appConfig.browseBy.showThumbnails;
|
this.fetchThumbnails = this.appConfig.browseBy.showThumbnails;
|
||||||
this.paginationConfig = Object.assign(new PaginationComponentOptions(), {
|
this.paginationConfig = Object.assign(new PaginationComponentOptions(), {
|
||||||
@@ -209,11 +222,15 @@ export class BrowseByMetadataComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
currentPage: 1,
|
currentPage: 1,
|
||||||
pageSize: this.appConfig.browseBy.pageSize,
|
pageSize: this.appConfig.browseBy.pageSize,
|
||||||
});
|
});
|
||||||
|
this.ssrRenderingDisabled = !this.renderOnServerSide && !environment.ssr.enableBrowseComponent && isPlatformServer(this.platformId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (this.ssrRenderingDisabled) {
|
||||||
|
this.loading$ = observableOf(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sortConfig = new SortOptions('default', SortDirection.ASC);
|
const sortConfig = new SortOptions('default', SortDirection.ASC);
|
||||||
this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig));
|
this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig));
|
||||||
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
||||||
@@ -336,7 +353,6 @@ export class BrowseByMetadataComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.paginationService.clearPagination(this.paginationConfig.id);
|
this.paginationService.clearPagination(this.paginationConfig.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -5,7 +5,9 @@ import {
|
|||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ComponentFixture,
|
ComponentFixture,
|
||||||
|
fakeAsync,
|
||||||
TestBed,
|
TestBed,
|
||||||
|
tick,
|
||||||
waitForAsync,
|
waitForAsync,
|
||||||
} from '@angular/core/testing';
|
} from '@angular/core/testing';
|
||||||
import {
|
import {
|
||||||
@@ -23,6 +25,7 @@ import { BrowseService } from '../../core/browse/browse.service';
|
|||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
import { ItemDataService } from '../../core/data/item-data.service';
|
import { ItemDataService } from '../../core/data/item-data.service';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
|
import { BrowseEntry } from '../../core/shared/browse-entry.model';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
import { ThemedBrowseByComponent } from '../../shared/browse-by/themed-browse-by.component';
|
import { ThemedBrowseByComponent } from '../../shared/browse-by/themed-browse-by.component';
|
||||||
@@ -81,6 +84,7 @@ describe('BrowseByTitleComponent', () => {
|
|||||||
|
|
||||||
const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {
|
const activatedRouteStub = Object.assign(new ActivatedRouteStub(), {
|
||||||
params: observableOf({}),
|
params: observableOf({}),
|
||||||
|
queryParams: observableOf({}),
|
||||||
data: observableOf({ metadata: 'title' }),
|
data: observableOf({ metadata: 'title' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -127,4 +131,35 @@ describe('BrowseByTitleComponent', () => {
|
|||||||
expect(result.payload.page).toEqual(mockItems);
|
expect(result.payload.page).toEqual(mockItems);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when rendered in SSR', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.platformId = 'server';
|
||||||
|
spyOn((comp as any).browseService, 'getBrowseItemsFor');
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not call getBrowseItemsFor on init', (done) => {
|
||||||
|
comp.ngOnInit();
|
||||||
|
expect((comp as any).browseService.getBrowseItemsFor).not.toHaveBeenCalled();
|
||||||
|
comp.loading$.subscribe((res) => {
|
||||||
|
expect(res).toBeFalsy();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when rendered in CSR', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.platformId = 'browser';
|
||||||
|
fixture.detectChanges();
|
||||||
|
spyOn((comp as any).browseService, 'getBrowseItemsFor').and.returnValue(createSuccessfulRemoteDataObject$(new BrowseEntry()));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call getBrowseItemsFor on init', fakeAsync(() => {
|
||||||
|
comp.ngOnInit();
|
||||||
|
tick(100);
|
||||||
|
expect((comp as any).browseService.getBrowseItemsFor).toHaveBeenCalled();
|
||||||
|
}));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
|
isPlatformServer,
|
||||||
NgIf,
|
NgIf,
|
||||||
} from '@angular/common';
|
} from '@angular/common';
|
||||||
import {
|
import {
|
||||||
@@ -8,12 +9,16 @@ import {
|
|||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { Params } from '@angular/router';
|
import { Params } from '@angular/router';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { combineLatest as observableCombineLatest } from 'rxjs';
|
import {
|
||||||
|
combineLatest as observableCombineLatest,
|
||||||
|
of as observableOf,
|
||||||
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
map,
|
map,
|
||||||
take,
|
take,
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
import {
|
import {
|
||||||
SortDirection,
|
SortDirection,
|
||||||
SortOptions,
|
SortOptions,
|
||||||
@@ -59,6 +64,10 @@ import {
|
|||||||
export class BrowseByTitleComponent extends BrowseByMetadataComponent implements OnInit {
|
export class BrowseByTitleComponent extends BrowseByMetadataComponent implements OnInit {
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (!this.renderOnServerSide && !environment.ssr.enableBrowseComponent && isPlatformServer(this.platformId)) {
|
||||||
|
this.loading$ = observableOf(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const sortConfig = new SortOptions('dc.title', SortDirection.ASC);
|
const sortConfig = new SortOptions('dc.title', SortDirection.ASC);
|
||||||
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
||||||
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
|
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
|
||||||
|
@@ -16,6 +16,7 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
|
|||||||
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service.stub';
|
||||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||||
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
|
||||||
|
import { RequestParam } from '../cache/models/request-param.model';
|
||||||
import { ObjectCacheService } from '../cache/object-cache.service';
|
import { ObjectCacheService } from '../cache/object-cache.service';
|
||||||
import { Bitstream } from '../shared/bitstream.model';
|
import { Bitstream } from '../shared/bitstream.model';
|
||||||
import { BitstreamFormat } from '../shared/bitstream-format.model';
|
import { BitstreamFormat } from '../shared/bitstream-format.model';
|
||||||
@@ -176,4 +177,30 @@ describe('BitstreamDataService', () => {
|
|||||||
expect(service.invalidateByHref).toHaveBeenCalledWith('fake-bitstream2-self');
|
expect(service.invalidateByHref).toHaveBeenCalledWith('fake-bitstream2-self');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('findByItemHandle', () => {
|
||||||
|
it('should encode the filename correctly in the search parameters', () => {
|
||||||
|
const handle = '123456789/1234';
|
||||||
|
const sequenceId = '5';
|
||||||
|
const filename = 'file with spaces.pdf';
|
||||||
|
const searchParams = [
|
||||||
|
new RequestParam('handle', handle),
|
||||||
|
new RequestParam('sequenceId', sequenceId),
|
||||||
|
new RequestParam('filename', filename),
|
||||||
|
];
|
||||||
|
const linksToFollow: FollowLinkConfig<Bitstream>[] = [];
|
||||||
|
|
||||||
|
spyOn(service as any, 'getSearchByHref').and.callThrough();
|
||||||
|
|
||||||
|
service.getSearchByHref('byItemHandle', { searchParams }, ...linksToFollow).subscribe((href) => {
|
||||||
|
expect(service.getSearchByHref).toHaveBeenCalledWith(
|
||||||
|
'byItemHandle',
|
||||||
|
{ searchParams },
|
||||||
|
...linksToFollow,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(href).toBe(`${url}/bitstreams/search/byItemHandle?handle=123456789%2F1234&sequenceId=5&filename=file%20with%20spaces.pdf`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -241,11 +241,12 @@ export class BitstreamDataService extends IdentifiableDataService<Bitstream> imp
|
|||||||
* no valid cached version. Defaults to true
|
* no valid cached version. Defaults to true
|
||||||
* @param reRequestOnStale Whether or not the request should automatically be re-
|
* @param reRequestOnStale Whether or not the request should automatically be re-
|
||||||
* requested after the response becomes stale
|
* requested after the response becomes stale
|
||||||
|
* @param options the {@link FindListOptions} for the request
|
||||||
* @return {Observable<Bitstream | null>}
|
* @return {Observable<Bitstream | null>}
|
||||||
* Return an observable that contains primary bitstream information or null
|
* Return an observable that contains primary bitstream information or null
|
||||||
*/
|
*/
|
||||||
public findPrimaryBitstreamByItemAndName(item: Item, bundleName: string, useCachedVersionIfAvailable = true, reRequestOnStale = true): Observable<Bitstream | null> {
|
public findPrimaryBitstreamByItemAndName(item: Item, bundleName: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, options?: FindListOptions): Observable<Bitstream | null> {
|
||||||
return this.bundleService.findByItemAndName(item, bundleName, useCachedVersionIfAvailable, reRequestOnStale, followLink('primaryBitstream')).pipe(
|
return this.bundleService.findByItemAndName(item, bundleName, useCachedVersionIfAvailable, reRequestOnStale, options, followLink('primaryBitstream')).pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
switchMap((rd: RemoteData<Bundle>) => {
|
switchMap((rd: RemoteData<Bundle>) => {
|
||||||
if (!rd.hasSucceeded) {
|
if (!rd.hasSucceeded) {
|
||||||
|
@@ -78,10 +78,14 @@ export class BundleDataService extends IdentifiableDataService<Bundle> implement
|
|||||||
* requested after the response becomes stale
|
* requested after the response becomes stale
|
||||||
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which
|
||||||
* {@link HALLink}s should be automatically resolved
|
* {@link HALLink}s should be automatically resolved
|
||||||
|
* @param options the {@link FindListOptions} for the request
|
||||||
*/
|
*/
|
||||||
// TODO should be implemented rest side
|
// TODO should be implemented rest side
|
||||||
findByItemAndName(item: Item, bundleName: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, ...linksToFollow: FollowLinkConfig<Bundle>[]): Observable<RemoteData<Bundle>> {
|
findByItemAndName(item: Item, bundleName: string, useCachedVersionIfAvailable = true, reRequestOnStale = true, options?: FindListOptions, ...linksToFollow: FollowLinkConfig<Bundle>[]): Observable<RemoteData<Bundle>> {
|
||||||
return this.findAllByItem(item, { elementsPerPage: 9999 }, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe(
|
//Since we filter by bundleName where the pagination options are not indicated we need to load all the possible bundles.
|
||||||
|
// This is a workaround, in substitution of the previously recursive call with expand
|
||||||
|
const paginationOptions = options ?? { elementsPerPage: 9999 };
|
||||||
|
return this.findAllByItem(item, paginationOptions, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow).pipe(
|
||||||
map((rd: RemoteData<PaginatedList<Bundle>>) => {
|
map((rd: RemoteData<PaginatedList<Bundle>>) => {
|
||||||
if (hasValue(rd.payload) && hasValue(rd.payload.page)) {
|
if (hasValue(rd.payload) && hasValue(rd.payload.page)) {
|
||||||
const matchingBundle = rd.payload.page.find((bundle: Bundle) =>
|
const matchingBundle = rd.payload.page.find((bundle: Bundle) =>
|
||||||
|
@@ -50,6 +50,7 @@ import { coreSelector } from '../core.selectors';
|
|||||||
import { CoreState } from '../core-state.model';
|
import { CoreState } from '../core-state.model';
|
||||||
import { BundleDataService } from '../data/bundle-data.service';
|
import { BundleDataService } from '../data/bundle-data.service';
|
||||||
import { AuthorizationDataService } from '../data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../data/feature-authorization/authorization-data.service';
|
||||||
|
import { FindListOptions } from '../data/find-list-options.model';
|
||||||
import { PaginatedList } from '../data/paginated-list.model';
|
import { PaginatedList } from '../data/paginated-list.model';
|
||||||
import { RemoteData } from '../data/remote-data';
|
import { RemoteData } from '../data/remote-data';
|
||||||
import { RootDataService } from '../data/root-data.service';
|
import { RootDataService } from '../data/root-data.service';
|
||||||
@@ -331,6 +332,7 @@ export class HeadTagService {
|
|||||||
'ORIGINAL',
|
'ORIGINAL',
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
|
new FindListOptions(),
|
||||||
followLink('primaryBitstream'),
|
followLink('primaryBitstream'),
|
||||||
followLink('bitstreams', {
|
followLink('bitstreams', {
|
||||||
findListOptions: {
|
findListOptions: {
|
||||||
|
@@ -12,4 +12,9 @@
|
|||||||
<a [routerLink]="[itemPageRoute]"
|
<a [routerLink]="[itemPageRoute]"
|
||||||
[innerHTML]="mdRepresentation.getValue()"
|
[innerHTML]="mdRepresentation.getValue()"
|
||||||
[ngbTooltip]="mdRepresentation.allMetadata(['person.jobTitle']).length > 0 ? descTemplate : null"></a>
|
[ngbTooltip]="mdRepresentation.allMetadata(['person.jobTitle']).length > 0 ? descTemplate : null"></a>
|
||||||
|
<ds-orcid-badge-and-tooltip class="ml-1"
|
||||||
|
*ngIf="mdRepresentation.firstMetadata('person.identifier.orcid')"
|
||||||
|
[orcid]="mdRepresentation.firstMetadata('person.identifier.orcid')"
|
||||||
|
[authenticatedTimestamp]="mdRepresentation.firstMetadata('dspace.orcid.authenticated')">
|
||||||
|
</ds-orcid-badge-and-tooltip>
|
||||||
</ds-truncatable>
|
</ds-truncatable>
|
||||||
|
@@ -7,13 +7,14 @@ import { RouterLink } from '@angular/router';
|
|||||||
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
import { ItemMetadataRepresentationListElementComponent } from '../../../../shared/object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component';
|
import { ItemMetadataRepresentationListElementComponent } from '../../../../shared/object-list/metadata-representation-list-element/item/item-metadata-representation-list-element.component';
|
||||||
|
import { OrcidBadgeAndTooltipComponent } from '../../../../shared/orcid-badge-and-tooltip/orcid-badge-and-tooltip.component';
|
||||||
import { TruncatableComponent } from '../../../../shared/truncatable/truncatable.component';
|
import { TruncatableComponent } from '../../../../shared/truncatable/truncatable.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-person-item-metadata-list-element',
|
selector: 'ds-person-item-metadata-list-element',
|
||||||
templateUrl: './person-item-metadata-list-element.component.html',
|
templateUrl: './person-item-metadata-list-element.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [NgIf, NgFor, TruncatableComponent, RouterLink, NgbTooltipModule],
|
imports: [NgIf, NgFor, TruncatableComponent, RouterLink, NgbTooltipModule, OrcidBadgeAndTooltipComponent],
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* The component for displaying an item of the type Person as a metadata field
|
* The component for displaying an item of the type Person as a metadata field
|
||||||
|
@@ -39,6 +39,9 @@
|
|||||||
[isFirstTable]="isFirst"
|
[isFirstTable]="isFirst"
|
||||||
aria-describedby="reorder-description">
|
aria-describedby="reorder-description">
|
||||||
</ds-item-edit-bitstream-bundle>
|
</ds-item-edit-bitstream-bundle>
|
||||||
|
<div class="d-flex justify-content-center" *ngIf="showLoadMoreLink$ | async">
|
||||||
|
<button class="btn btn-link my-3" (click)="loadBundles()"> {{'item.edit.bitstreams.load-more.link' | translate}}</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="bundles?.length === 0"
|
<div *ngIf="bundles?.length === 0"
|
||||||
class="alert alert-info w-100 d-inline-block mt-4" role="alert">
|
class="alert alert-info w-100 d-inline-block mt-4" role="alert">
|
||||||
|
@@ -1,4 +1,9 @@
|
|||||||
import { CommonModule } from '@angular/common';
|
import {
|
||||||
|
AsyncPipe,
|
||||||
|
CommonModule,
|
||||||
|
NgForOf,
|
||||||
|
NgIf,
|
||||||
|
} from '@angular/common';
|
||||||
import {
|
import {
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
@@ -15,16 +20,22 @@ import {
|
|||||||
TranslateModule,
|
TranslateModule,
|
||||||
TranslateService,
|
TranslateService,
|
||||||
} from '@ngx-translate/core';
|
} from '@ngx-translate/core';
|
||||||
|
import { Operation } from 'fast-json-patch';
|
||||||
import {
|
import {
|
||||||
|
BehaviorSubject,
|
||||||
combineLatest,
|
combineLatest,
|
||||||
Observable,
|
Observable,
|
||||||
Subscription,
|
Subscription,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
|
filter,
|
||||||
map,
|
map,
|
||||||
switchMap,
|
switchMap,
|
||||||
take,
|
take,
|
||||||
|
tap,
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
|
import { AlertComponent } from 'src/app/shared/alert/alert.component';
|
||||||
|
import { AlertType } from 'src/app/shared/alert/alert-type';
|
||||||
|
|
||||||
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
import { ObjectCacheService } from '../../../core/cache/object-cache.service';
|
||||||
import { BitstreamDataService } from '../../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../../core/data/bitstream-data.service';
|
||||||
@@ -40,10 +51,13 @@ import {
|
|||||||
getFirstSucceededRemoteData,
|
getFirstSucceededRemoteData,
|
||||||
getRemoteDataPayload,
|
getRemoteDataPayload,
|
||||||
} from '../../../core/shared/operators';
|
} from '../../../core/shared/operators';
|
||||||
import { AlertComponent } from '../../../shared/alert/alert.component';
|
import {
|
||||||
import { AlertType } from '../../../shared/alert/alert-type';
|
hasValue,
|
||||||
|
isNotEmpty,
|
||||||
|
} from '../../../shared/empty.util';
|
||||||
import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component';
|
import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component';
|
||||||
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
import { NotificationsService } from '../../../shared/notifications/notifications.service';
|
||||||
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
import { ResponsiveTableSizes } from '../../../shared/responsive-table-sizes/responsive-table-sizes';
|
import { ResponsiveTableSizes } from '../../../shared/responsive-table-sizes/responsive-table-sizes';
|
||||||
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||||
import { ObjectValuesPipe } from '../../../shared/utils/object-values-pipe';
|
import { ObjectValuesPipe } from '../../../shared/utils/object-values-pipe';
|
||||||
@@ -58,10 +72,13 @@ import { ItemEditBitstreamBundleComponent } from './item-edit-bitstream-bundle/i
|
|||||||
templateUrl: './item-bitstreams.component.html',
|
templateUrl: './item-bitstreams.component.html',
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
AsyncPipe,
|
||||||
TranslateModule,
|
TranslateModule,
|
||||||
ItemEditBitstreamBundleComponent,
|
ItemEditBitstreamBundleComponent,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
|
NgIf,
|
||||||
VarDirective,
|
VarDirective,
|
||||||
|
NgForOf,
|
||||||
ThemedLoadingComponent,
|
ThemedLoadingComponent,
|
||||||
AlertComponent,
|
AlertComponent,
|
||||||
],
|
],
|
||||||
@@ -77,9 +94,18 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
|||||||
protected readonly AlertType = AlertType;
|
protected readonly AlertType = AlertType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The currently listed bundles
|
* All bundles for the current item
|
||||||
*/
|
*/
|
||||||
bundles$: Observable<Bundle[]>;
|
private bundlesSubject = new BehaviorSubject<Bundle[]>([]);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The page options to use for fetching the bundles
|
||||||
|
*/
|
||||||
|
bundlesOptions: PaginationComponentOptions = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: 'bundles-pagination-options',
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The bootstrap sizes used for the columns within this table
|
* The bootstrap sizes used for the columns within this table
|
||||||
@@ -98,6 +124,18 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
|||||||
*/
|
*/
|
||||||
itemUpdateSubscription: Subscription;
|
itemUpdateSubscription: Subscription;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The flag indicating to show the load more link
|
||||||
|
*/
|
||||||
|
showLoadMoreLink$: BehaviorSubject<boolean> = new BehaviorSubject(true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of bundles for the current item as an observable
|
||||||
|
*/
|
||||||
|
get bundles$(): Observable<Bundle[]> {
|
||||||
|
return this.bundlesSubject.asObservable();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An observable which emits a boolean which represents whether the service is currently handling a 'move' request
|
* An observable which emits a boolean which represents whether the service is currently handling a 'move' request
|
||||||
*/
|
*/
|
||||||
@@ -127,14 +165,7 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
|||||||
* Actions to perform after the item has been initialized
|
* Actions to perform after the item has been initialized
|
||||||
*/
|
*/
|
||||||
postItemInit(): void {
|
postItemInit(): void {
|
||||||
const bundlesOptions = this.itemBitstreamsService.getInitialBundlesPaginationOptions();
|
this.loadBundles(1);
|
||||||
this.isProcessingMoveRequest = this.itemBitstreamsService.getPerformingMoveRequest$();
|
|
||||||
|
|
||||||
this.bundles$ = this.itemService.getBundles(this.item.id, new PaginatedSearchOptions({ pagination: bundlesOptions })).pipe(
|
|
||||||
getFirstSucceededRemoteData(),
|
|
||||||
getRemoteDataPayload(),
|
|
||||||
map((bundlePage: PaginatedList<Bundle>) => bundlePage.page),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -199,6 +230,26 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
|||||||
this.notificationsPrefix = 'item.edit.bitstreams.notifications.';
|
this.notificationsPrefix = 'item.edit.bitstreams.notifications.';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load bundles for the current item
|
||||||
|
* @param currentPage The current page to load
|
||||||
|
*/
|
||||||
|
loadBundles(currentPage?: number) {
|
||||||
|
this.bundlesOptions = Object.assign(new PaginationComponentOptions(), this.bundlesOptions, {
|
||||||
|
currentPage: currentPage || this.bundlesOptions.currentPage + 1,
|
||||||
|
});
|
||||||
|
this.itemService.getBundles(this.item.id, new PaginatedSearchOptions({ pagination: this.bundlesOptions })).pipe(
|
||||||
|
getFirstSucceededRemoteData(),
|
||||||
|
getRemoteDataPayload(),
|
||||||
|
tap((bundlesPL: PaginatedList<Bundle>) =>
|
||||||
|
this.showLoadMoreLink$.next(bundlesPL.pageInfo.currentPage < bundlesPL.pageInfo.totalPages),
|
||||||
|
),
|
||||||
|
map((bundlePage: PaginatedList<Bundle>) => bundlePage.page),
|
||||||
|
).subscribe((bundles: Bundle[]) => {
|
||||||
|
this.bundlesSubject.next([...this.bundlesSubject.getValue(), ...bundles]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submit the current changes
|
* Submit the current changes
|
||||||
@@ -208,7 +259,7 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
|||||||
submit() {
|
submit() {
|
||||||
this.submitting = true;
|
this.submitting = true;
|
||||||
|
|
||||||
const removedResponses$ = this.itemBitstreamsService.removeMarkedBitstreams(this.bundles$);
|
const removedResponses$ = this.itemBitstreamsService.removeMarkedBitstreams(this.bundles$.pipe(take(1)));
|
||||||
|
|
||||||
// Perform the setup actions from above in order and display notifications
|
// Perform the setup actions from above in order and display notifications
|
||||||
removedResponses$.subscribe((responses: RemoteData<NoContent>) => {
|
removedResponses$.subscribe((responses: RemoteData<NoContent>) => {
|
||||||
@@ -217,6 +268,56 @@ export class ItemBitstreamsComponent extends AbstractItemUpdateComponent impleme
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bitstream was dropped in a new location. Send out a Move Patch request to the REST API, display notifications,
|
||||||
|
* refresh the bundle's cache (so the lists can properly reload) and call the event's callback function (which will
|
||||||
|
* navigate the user to the correct page)
|
||||||
|
* @param bundle The bundle to send patch requests to
|
||||||
|
* @param event The event containing the index the bitstream came from and was dropped to
|
||||||
|
*/
|
||||||
|
dropBitstream(bundle: Bundle, event: any) {
|
||||||
|
this.zone.runOutsideAngular(() => {
|
||||||
|
if (hasValue(event) && hasValue(event.fromIndex) && hasValue(event.toIndex) && hasValue(event.finish)) {
|
||||||
|
const moveOperation = {
|
||||||
|
op: 'move',
|
||||||
|
from: `/_links/bitstreams/${event.fromIndex}/href`,
|
||||||
|
path: `/_links/bitstreams/${event.toIndex}/href`,
|
||||||
|
} as Operation;
|
||||||
|
this.bundleService.patch(bundle, [moveOperation]).pipe(take(1)).subscribe((response: RemoteData<Bundle>) => {
|
||||||
|
this.zone.run(() => {
|
||||||
|
this.displayNotifications('item.edit.bitstreams.notifications.move', [response]);
|
||||||
|
// Remove all cached requests from this bundle and call the event's callback when the requests are cleared
|
||||||
|
this.requestService.removeByHrefSubstring(bundle.self).pipe(
|
||||||
|
filter((isCached) => isCached),
|
||||||
|
take(1),
|
||||||
|
).subscribe(() => event.finish());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display notifications
|
||||||
|
* - Error notification for each failed response with their message
|
||||||
|
* - Success notification in case there's at least one successful response
|
||||||
|
* @param key The i18n key for the notification messages
|
||||||
|
* @param responses The returned responses to display notifications for
|
||||||
|
*/
|
||||||
|
displayNotifications(key: string, responses: RemoteData<any>[]) {
|
||||||
|
if (isNotEmpty(responses)) {
|
||||||
|
const failedResponses = responses.filter((response: RemoteData<Bundle>) => hasValue(response) && response.hasFailed);
|
||||||
|
const successfulResponses = responses.filter((response: RemoteData<Bundle>) => hasValue(response) && response.hasSucceeded);
|
||||||
|
|
||||||
|
failedResponses.forEach((response: RemoteData<Bundle>) => {
|
||||||
|
this.notificationsService.error(this.translateService.instant(`${key}.failed.title`), response.errorMessage);
|
||||||
|
});
|
||||||
|
if (successfulResponses.length > 0) {
|
||||||
|
this.notificationsService.success(this.translateService.instant(`${key}.saved.title`), this.translateService.instant(`${key}.saved.content`));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request the object updates service to discard all current changes to this item
|
* Request the object updates service to discard all current changes to this item
|
||||||
* Shows a notification to remind the user that they can undo this
|
* Shows a notification to remind the user that they can undo this
|
||||||
|
@@ -1,20 +1,47 @@
|
|||||||
<div class="form-group" *ngIf="scripts$ | async">
|
<div class="d-flex w-100 flex-column gap-3">
|
||||||
<label for="process-script">{{'process.new.select-script' | translate}}</label>
|
<div>
|
||||||
<select required id="process-script"
|
<div ngbDropdown class="d-flex">
|
||||||
|
<input id="process-script"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
name="script"
|
required
|
||||||
[(ngModel)]="selectedScript"
|
[ngModel]="selectedScript"
|
||||||
|
placeholder="{{'process.new.select-script.placeholder' | translate}}"
|
||||||
|
[ngModelOptions]="{standalone: true}"
|
||||||
|
ngbDropdownToggle
|
||||||
|
role="combobox"
|
||||||
#script="ngModel">
|
#script="ngModel">
|
||||||
<option [ngValue]="undefined">{{'process.new.select-script.placeholder' | translate}}</option>
|
<div ngbDropdownMenu aria-labelledby="process-script" class="w-100 scrollable-menu"
|
||||||
<option *ngFor="let script of scripts$ | async" [ngValue]="script.id">
|
role="menu"
|
||||||
{{script.name}}
|
(scroll)="onScroll($event)"
|
||||||
</option>
|
infiniteScroll
|
||||||
</select>
|
[infiniteScrollDistance]="5"
|
||||||
|
[infiniteScrollThrottle]="300"
|
||||||
|
[infiniteScrollUpDistance]="1.5"
|
||||||
|
[fromRoot]="true"
|
||||||
|
[scrollWindow]="false">
|
||||||
|
<button class="dropdown-item"
|
||||||
|
*ngFor="let script of scripts"
|
||||||
|
role="menuitem"
|
||||||
|
type="button"
|
||||||
|
title="{{ script.name }}"
|
||||||
|
(click)="onSelect(script);">
|
||||||
|
<span class="text-truncate">{{ script.name }}</span>
|
||||||
|
</button>
|
||||||
|
<ng-container *ngIf="(isLoading$ | async)">
|
||||||
|
<button class="dropdown-item disabled" role="menuitem">
|
||||||
|
<ds-loading message="{{'loading.default' | translate}}">
|
||||||
|
</ds-loading>
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
<div *ngIf="script.invalid && (script.dirty || script.touched)"
|
<div *ngIf="script.invalid && (script.dirty || script.touched)"
|
||||||
class="alert alert-danger validation-error">
|
class="alert alert-danger validation-error">
|
||||||
<div *ngIf="script.errors.required">
|
<div *ngIf="script.errors.required">
|
||||||
{{'process.new.select-script.required' | translate}}
|
{{ 'process.new.select-script.required' | translate }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,23 @@
|
|||||||
|
.dropdown-item {
|
||||||
|
padding: 0.35rem 1rem;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable-menu {
|
||||||
|
height: auto;
|
||||||
|
max-height: var(--ds-dropdown-menu-max-height);
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
li:not(:last-of-type) .dropdown-item {
|
||||||
|
border-bottom: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#entityControlsDropdownMenu {
|
||||||
|
outline: 0;
|
||||||
|
left: 0 !important;
|
||||||
|
box-shadow: var(--bs-btn-focus-box-shadow);
|
||||||
|
}
|
||||||
|
@@ -87,7 +87,7 @@ describe('ScriptsSelectComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
const select = fixture.debugElement.query(By.css('select'));
|
const select = fixture.debugElement.query(By.css('#process-script'));
|
||||||
select.triggerEventHandler('blur', null);
|
select.triggerEventHandler('blur', null);
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -101,7 +101,7 @@ describe('ScriptsSelectComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
tick();
|
tick();
|
||||||
|
|
||||||
const select = fixture.debugElement.query(By.css('select'));
|
const select = fixture.debugElement.query(By.css('#process-script'));
|
||||||
select.triggerEventHandler('blur', null);
|
select.triggerEventHandler('blur', null);
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
@@ -19,32 +19,29 @@ import {
|
|||||||
} from '@angular/forms';
|
} from '@angular/forms';
|
||||||
import {
|
import {
|
||||||
ActivatedRoute,
|
ActivatedRoute,
|
||||||
Params,
|
|
||||||
Router,
|
Router,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
|
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||||
import {
|
import {
|
||||||
Observable,
|
BehaviorSubject,
|
||||||
Subscription,
|
Subscription,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
distinctUntilChanged,
|
|
||||||
filter,
|
|
||||||
map,
|
map,
|
||||||
switchMap,
|
tap,
|
||||||
take,
|
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
import { ScriptDataService } from '../../../core/data/processes/script-data.service';
|
import { ScriptDataService } from '../../../core/data/processes/script-data.service';
|
||||||
import {
|
import {
|
||||||
getFirstSucceededRemoteData,
|
getFirstCompletedRemoteData,
|
||||||
getRemoteDataPayload,
|
getRemoteDataPayload,
|
||||||
} from '../../../core/shared/operators';
|
} from '../../../core/shared/operators';
|
||||||
import {
|
import { hasValue } from '../../../shared/empty.util';
|
||||||
hasNoValue,
|
import { ThemedLoadingComponent } from '../../../shared/loading/themed-loading.component';
|
||||||
hasValue,
|
|
||||||
} from '../../../shared/empty.util';
|
|
||||||
import { Script } from '../../scripts/script.model';
|
import { Script } from '../../scripts/script.model';
|
||||||
import { controlContainerFactory } from '../process-form-factory';
|
import { controlContainerFactory } from '../process-form-factory';
|
||||||
|
|
||||||
@@ -61,7 +58,7 @@ const SCRIPT_QUERY_PARAMETER = 'script';
|
|||||||
useFactory: controlContainerFactory,
|
useFactory: controlContainerFactory,
|
||||||
deps: [[new Optional(), NgForm]] }],
|
deps: [[new Optional(), NgForm]] }],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [NgIf, FormsModule, NgFor, AsyncPipe, TranslateModule],
|
imports: [NgIf, FormsModule, NgFor, AsyncPipe, TranslateModule, InfiniteScrollModule, ThemedLoadingComponent, NgbDropdownModule],
|
||||||
})
|
})
|
||||||
export class ScriptsSelectComponent implements OnInit, OnDestroy {
|
export class ScriptsSelectComponent implements OnInit, OnDestroy {
|
||||||
/**
|
/**
|
||||||
@@ -71,9 +68,19 @@ export class ScriptsSelectComponent implements OnInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* All available scripts
|
* All available scripts
|
||||||
*/
|
*/
|
||||||
scripts$: Observable<Script[]>;
|
scripts: Script[] = [];
|
||||||
|
|
||||||
private _selectedScript: Script;
|
private _selectedScript: Script;
|
||||||
private routeSub: Subscription;
|
private subscription: Subscription;
|
||||||
|
|
||||||
|
private _isLastPage = false;
|
||||||
|
|
||||||
|
scriptOptions: FindListOptions = {
|
||||||
|
elementsPerPage: 20,
|
||||||
|
currentPage: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
isLoading$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private scriptService: ScriptDataService,
|
private scriptService: ScriptDataService,
|
||||||
@@ -87,33 +94,48 @@ export class ScriptsSelectComponent implements OnInit, OnDestroy {
|
|||||||
* Checks if the route contains a script ID and auto selects this scripts
|
* Checks if the route contains a script ID and auto selects this scripts
|
||||||
*/
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.scripts$ = this.scriptService.findAll({ elementsPerPage: 9999 })
|
this.loadScripts();
|
||||||
.pipe(
|
}
|
||||||
getFirstSucceededRemoteData(),
|
|
||||||
getRemoteDataPayload(),
|
|
||||||
map((paginatedList: PaginatedList<Script>) => paginatedList.page),
|
|
||||||
);
|
|
||||||
|
|
||||||
this.routeSub = this.route.queryParams
|
/**
|
||||||
.pipe(
|
* Load the scripts and check if the route contains a script
|
||||||
filter((params: Params) => hasNoValue(params.id)),
|
*/
|
||||||
map((params: Params) => params[SCRIPT_QUERY_PARAMETER]),
|
loadScripts() {
|
||||||
distinctUntilChanged(),
|
if (this.isLoading$.value) {return;}
|
||||||
switchMap((id: string) =>
|
this.isLoading$.next(true);
|
||||||
this.scripts$
|
|
||||||
.pipe(
|
this.subscription = this.scriptService.findAll(this.scriptOptions).pipe(
|
||||||
take(1),
|
getFirstCompletedRemoteData(),
|
||||||
map((scripts) =>
|
getRemoteDataPayload(),
|
||||||
scripts.find((script) => script.id === id),
|
tap((paginatedList: PaginatedList<Script>) => {
|
||||||
),
|
this._isLastPage = paginatedList?.pageInfo?.currentPage >= paginatedList?.pageInfo?.totalPages;
|
||||||
),
|
}),
|
||||||
),
|
map((paginatedList: PaginatedList<Script>) => paginatedList.page),
|
||||||
).subscribe((script: Script) => {
|
).subscribe((newScripts: Script[]) => {
|
||||||
this._selectedScript = script;
|
this.scripts = [...this.scripts, ...newScripts];
|
||||||
this.select.emit(script);
|
this.isLoading$.next(false);
|
||||||
|
|
||||||
|
const param = this.route.snapshot.queryParams[SCRIPT_QUERY_PARAMETER];
|
||||||
|
if (hasValue(param)) {
|
||||||
|
this._selectedScript = this.scripts.find((script) => script.id === param);
|
||||||
|
this.select.emit(this._selectedScript);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load more scripts when the user scrolls to the bottom of the list
|
||||||
|
* @param event The scroll event
|
||||||
|
*/
|
||||||
|
onScroll(event: any) {
|
||||||
|
if (event.target.scrollTop + event.target.clientHeight >= event.target.scrollHeight) {
|
||||||
|
if (!this.isLoading$.value && !this._isLastPage) {
|
||||||
|
this.scriptOptions.currentPage++;
|
||||||
|
this.loadScripts();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the identifier of the selected script
|
* Returns the identifier of the selected script
|
||||||
*/
|
*/
|
||||||
@@ -133,14 +155,25 @@ export class ScriptsSelectComponent implements OnInit, OnDestroy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
selectScript(script: Script) {
|
||||||
|
this._selectedScript = script;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelect(newScript: Script) {
|
||||||
|
this.selectScript(newScript);
|
||||||
|
// this._selectedScript = newScript;
|
||||||
|
this.select.emit(newScript);
|
||||||
|
this.selectedScript = newScript.name;
|
||||||
|
}
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
set script(value: Script) {
|
set script(value: Script) {
|
||||||
this._selectedScript = value;
|
this._selectedScript = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
if (hasValue(this.routeSub)) {
|
if (hasValue(this.subscription)) {
|
||||||
this.routeSub.unsubscribe();
|
this.subscription.unsubscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -7,6 +7,7 @@ import {
|
|||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
Inject,
|
Inject,
|
||||||
|
PLATFORM_ID,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
@@ -57,7 +58,8 @@ export class ConfigurationSearchPageComponent extends SearchComponent {
|
|||||||
protected routeService: RouteService,
|
protected routeService: RouteService,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
|
@Inject(PLATFORM_ID) public platformId: any,
|
||||||
) {
|
) {
|
||||||
super(service, sidebarService, windowService, searchConfigService, routeService, router, appConfig);
|
super(service, sidebarService, windowService, searchConfigService, routeService, router, appConfig, platformId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -26,7 +26,7 @@ import { BrowseDefinition } from '../../../../core/shared/browse-definition.mode
|
|||||||
})
|
})
|
||||||
export class ComcolBrowseByComponent implements OnInit {
|
export class ComcolBrowseByComponent implements OnInit {
|
||||||
|
|
||||||
browseByType$: Observable<BrowseByDataType>;
|
browseByType$: Observable<{type: BrowseByDataType }>;
|
||||||
|
|
||||||
scope$: Observable<string>;
|
scope$: Observable<string>;
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ export class ComcolBrowseByComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.browseByType$ = this.route.data.pipe(
|
this.browseByType$ = this.route.data.pipe(
|
||||||
map((data: { browseDefinition: BrowseDefinition }) => data.browseDefinition.getRenderType()),
|
map((data: { browseDefinition: BrowseDefinition }) => ({ type: data.browseDefinition.getRenderType() })),
|
||||||
);
|
);
|
||||||
this.scope$ = this.route.data.pipe(
|
this.scope$ = this.route.data.pipe(
|
||||||
map((data: Data) => data.scope),
|
map((data: Data) => data.scope),
|
||||||
|
@@ -28,9 +28,7 @@
|
|||||||
<span [ngClass]="model.layout.element?.label" [innerHTML]="item.label"></span>
|
<span [ngClass]="model.layout.element?.label" [innerHTML]="item.label"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="!model.repeatable"
|
<div *ngIf="!model.repeatable"
|
||||||
@@ -60,8 +58,10 @@
|
|||||||
<span [ngClass]="model.layout.element?.label" [innerHTML]="item.label"></span>
|
<span [ngClass]="model.layout.element?.label" [innerHTML]="item.label"></span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-center" *ngIf="(isLoading$ | async)">
|
||||||
|
<ds-loading></ds-loading>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
AsyncPipe,
|
||||||
NgClass,
|
NgClass,
|
||||||
NgForOf,
|
NgForOf,
|
||||||
NgIf,
|
NgIf,
|
||||||
@@ -8,6 +9,7 @@ import {
|
|||||||
Component,
|
Component,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
Input,
|
Input,
|
||||||
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
Output,
|
Output,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
@@ -25,7 +27,16 @@ import {
|
|||||||
DynamicFormLayoutService,
|
DynamicFormLayoutService,
|
||||||
DynamicFormValidationService,
|
DynamicFormValidationService,
|
||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import findKey from 'lodash/findKey';
|
import findKey from 'lodash/findKey';
|
||||||
|
import {
|
||||||
|
BehaviorSubject,
|
||||||
|
Subscription,
|
||||||
|
} from 'rxjs';
|
||||||
|
import {
|
||||||
|
map,
|
||||||
|
tap,
|
||||||
|
} from 'rxjs/operators';
|
||||||
|
|
||||||
import { PaginatedList } from '../../../../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../../../../core/data/paginated-list.model';
|
||||||
import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators';
|
import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators';
|
||||||
@@ -36,6 +47,7 @@ import {
|
|||||||
hasValue,
|
hasValue,
|
||||||
isNotEmpty,
|
isNotEmpty,
|
||||||
} from '../../../../../empty.util';
|
} from '../../../../../empty.util';
|
||||||
|
import { ThemedLoadingComponent } from '../../../../../loading/themed-loading.component';
|
||||||
import { FormBuilderService } from '../../../form-builder.service';
|
import { FormBuilderService } from '../../../form-builder.service';
|
||||||
import { DynamicListCheckboxGroupModel } from './dynamic-list-checkbox-group.model';
|
import { DynamicListCheckboxGroupModel } from './dynamic-list-checkbox-group.model';
|
||||||
import { DynamicListRadioGroupModel } from './dynamic-list-radio-group.model';
|
import { DynamicListRadioGroupModel } from './dynamic-list-radio-group.model';
|
||||||
@@ -60,10 +72,13 @@ export interface ListItem {
|
|||||||
NgbButtonsModule,
|
NgbButtonsModule,
|
||||||
NgForOf,
|
NgForOf,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
|
AsyncPipe,
|
||||||
|
TranslateModule,
|
||||||
|
ThemedLoadingComponent,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class DsDynamicListComponent extends DynamicFormControlComponent implements OnInit {
|
export class DsDynamicListComponent extends DynamicFormControlComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
@Input() group: UntypedFormGroup;
|
@Input() group: UntypedFormGroup;
|
||||||
@Input() model: any;
|
@Input() model: any;
|
||||||
@@ -73,7 +88,10 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
|
|||||||
@Output() focus: EventEmitter<any> = new EventEmitter<any>();
|
@Output() focus: EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
|
||||||
public items: ListItem[][] = [];
|
public items: ListItem[][] = [];
|
||||||
protected optionsList: VocabularyEntry[];
|
public isLoading$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(true);
|
||||||
|
protected optionsList: VocabularyEntry[] = [];
|
||||||
|
private nextPageInfo: PageInfo;
|
||||||
|
private subs: Subscription[] = [];
|
||||||
|
|
||||||
constructor(private vocabularyService: VocabularyService,
|
constructor(private vocabularyService: VocabularyService,
|
||||||
private cdr: ChangeDetectorRef,
|
private cdr: ChangeDetectorRef,
|
||||||
@@ -89,7 +107,13 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
|
|||||||
*/
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (this.model.vocabularyOptions && hasValue(this.model.vocabularyOptions.name)) {
|
if (this.model.vocabularyOptions && hasValue(this.model.vocabularyOptions.name)) {
|
||||||
this.setOptionsFromVocabulary();
|
this.initOptionsFromVocabulary();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
if (this.subs.length > 0) {
|
||||||
|
this.subs.forEach(sub => sub.unsubscribe());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,34 +160,76 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
|
|||||||
/**
|
/**
|
||||||
* Setting up the field options from vocabulary
|
* Setting up the field options from vocabulary
|
||||||
*/
|
*/
|
||||||
protected setOptionsFromVocabulary() {
|
protected initOptionsFromVocabulary() {
|
||||||
if (this.model.vocabularyOptions.name && this.model.vocabularyOptions.name.length > 0) {
|
if (this.model.vocabularyOptions.name && this.model.vocabularyOptions.name.length > 0) {
|
||||||
const listGroup = this.group.controls[this.model.id] as UntypedFormGroup;
|
const listGroup = this.group.controls[this.model.id] as UntypedFormGroup;
|
||||||
if (this.model.repeatable && this.model.required) {
|
if (this.model.repeatable && this.model.required) {
|
||||||
listGroup.addValidators(this.hasAtLeastOneVocabularyEntry());
|
listGroup.addValidators(this.hasAtLeastOneVocabularyEntry());
|
||||||
}
|
}
|
||||||
const pageInfo: PageInfo = new PageInfo({
|
|
||||||
elementsPerPage: 9999, currentPage: 1,
|
this.nextPageInfo = new PageInfo({
|
||||||
|
elementsPerPage: 20, currentPage: 1,
|
||||||
} as PageInfo);
|
} as PageInfo);
|
||||||
this.vocabularyService.getVocabularyEntries(this.model.vocabularyOptions, pageInfo).pipe(
|
|
||||||
|
this.loadEntries(listGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if at least one {@link VocabularyEntry} has been selected.
|
||||||
|
*/
|
||||||
|
hasAtLeastOneVocabularyEntry(): ValidatorFn {
|
||||||
|
return (control: AbstractControl): ValidationErrors | null => {
|
||||||
|
return control && control.value && Object.values(control.value).find((checked: boolean) => checked === true) ? null : this.model.errorMessages;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update current page state to keep track of which one to load next
|
||||||
|
* @param response
|
||||||
|
*/
|
||||||
|
setPaginationInfo(response: PaginatedList<VocabularyEntry>) {
|
||||||
|
if (response.pageInfo.currentPage < response.pageInfo.totalPages) {
|
||||||
|
this.nextPageInfo = Object.assign(new PageInfo(), response.pageInfo, { currentPage: response.currentPage + 1 });
|
||||||
|
this.isLoading$.next(true);
|
||||||
|
} else {
|
||||||
|
this.isLoading$.next(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load entries page
|
||||||
|
*
|
||||||
|
* @param listGroup
|
||||||
|
*/
|
||||||
|
loadEntries(listGroup?: UntypedFormGroup) {
|
||||||
|
if (!hasValue(listGroup)) {
|
||||||
|
listGroup = this.group.controls[this.model.id] as UntypedFormGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.subs.push(
|
||||||
|
this.vocabularyService.getVocabularyEntries(this.model.vocabularyOptions, this.nextPageInfo).pipe(
|
||||||
getFirstSucceededRemoteDataPayload(),
|
getFirstSucceededRemoteDataPayload(),
|
||||||
).subscribe((entries: PaginatedList<VocabularyEntry>) => {
|
tap((response) => this.setPaginationInfo(response)),
|
||||||
let groupCounter = 0;
|
map(entries => entries.page),
|
||||||
|
).subscribe((allEntries: VocabularyEntry[]) => {
|
||||||
|
this.optionsList = [...this.optionsList, ...allEntries];
|
||||||
|
let groupCounter = this.items.length;
|
||||||
let itemsPerGroup = 0;
|
let itemsPerGroup = 0;
|
||||||
let tempList: ListItem[] = [];
|
let tempList: ListItem[] = [];
|
||||||
this.optionsList = entries.page;
|
|
||||||
// Make a list of available options (checkbox/radio) and split in groups of 'model.groupLength'
|
// Make a list of available options (checkbox/radio) and split in groups of 'model.groupLength'
|
||||||
entries.page.forEach((option: VocabularyEntry, key: number) => {
|
allEntries.forEach((option: VocabularyEntry) => {
|
||||||
const value = option.authority || option.value;
|
const value = option.authority || option.value;
|
||||||
const checked: boolean = isNotEmpty(findKey(
|
const checked: boolean = isNotEmpty(findKey(
|
||||||
this.model.value,
|
this.model.value,
|
||||||
(v) => v.value === option.value));
|
(v) => v?.value === option.value));
|
||||||
|
|
||||||
const item: ListItem = {
|
const item: ListItem = {
|
||||||
id: `${this.model.id}_${value}`,
|
id: `${this.model.id}_${value}`,
|
||||||
label: option.display,
|
label: option.display,
|
||||||
value: checked,
|
value: checked,
|
||||||
index: key,
|
index: this.optionsList.indexOf(option),
|
||||||
};
|
};
|
||||||
if (this.model.repeatable) {
|
if (this.model.repeatable) {
|
||||||
this.formBuilderService.addFormGroupControl(listGroup, (this.model as DynamicListCheckboxGroupModel), new DynamicCheckboxModel(item));
|
this.formBuilderService.addFormGroupControl(listGroup, (this.model as DynamicListCheckboxGroupModel), new DynamicCheckboxModel(item));
|
||||||
@@ -183,18 +249,11 @@ export class DsDynamicListComponent extends DynamicFormControlComponent implemen
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.cdr.markForCheck();
|
this.cdr.markForCheck();
|
||||||
});
|
// If the paginated request did not reach the end keep loading the entries in the background
|
||||||
|
if (this.isLoading$.value) {
|
||||||
|
this.loadEntries();
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if at least one {@link VocabularyEntry} has been selected.
|
|
||||||
*/
|
|
||||||
hasAtLeastOneVocabularyEntry(): ValidatorFn {
|
|
||||||
return (control: AbstractControl): ValidationErrors | null => {
|
|
||||||
return control && control.value && Object.values(control.value).find((checked: boolean) => checked === true) ? null : this.model.errorMessages;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -52,8 +52,8 @@
|
|||||||
<button class="dropdown-item collection-item text-truncate" *ngFor="let listEntry of optionsList; let i = index"
|
<button class="dropdown-item collection-item text-truncate" *ngFor="let listEntry of optionsList; let i = index"
|
||||||
[class.active]="i === selectedIndex"
|
[class.active]="i === selectedIndex"
|
||||||
(keydown.enter)="onSelect(listEntry); sdRef.close()" (mousedown)="onSelect(listEntry); sdRef.close()"
|
(keydown.enter)="onSelect(listEntry); sdRef.close()" (mousedown)="onSelect(listEntry); sdRef.close()"
|
||||||
title="{{ listEntry.display }}" role="option" type="button"
|
title="{{ inputFormatter(listEntry) }}" role="option" type="button"
|
||||||
[attr.id]="listEntry.display === (currentValue|async) ? ('combobox_' + id + '_selected') : null">
|
[attr.id]="inputFormatter(listEntry) === (currentValue|async) ? ('combobox_' + id + '_selected') : null">
|
||||||
{{inputFormatter(listEntry)}}
|
{{inputFormatter(listEntry)}}
|
||||||
</button>
|
</button>
|
||||||
<div class="scrollable-dropdown-loading text-center" *ngIf="loading"><p>{{'form.loading' | translate}}</p></div>
|
<div class="scrollable-dropdown-loading text-center" *ngIf="loading"><p>{{'form.loading' | translate}}</p></div>
|
||||||
|
@@ -3,6 +3,7 @@ import {
|
|||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
CUSTOM_ELEMENTS_SCHEMA,
|
CUSTOM_ELEMENTS_SCHEMA,
|
||||||
|
Injector,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ComponentFixture,
|
ComponentFixture,
|
||||||
@@ -29,6 +30,7 @@ import { DynamicFormsNGBootstrapUIModule } from '@ng-dynamic-forms/ui-ng-bootstr
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||||
|
|
||||||
|
import { APP_DATA_SERVICES_MAP } from '../../../../../../../config/app-config.interface';
|
||||||
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
|
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
|
||||||
import { VocabularyOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
import { VocabularyOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||||
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
|
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
|
||||||
@@ -96,11 +98,13 @@ describe('Dynamic Dynamic Scrollable Dropdown component', () => {
|
|||||||
TestComponent,
|
TestComponent,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
|
Injector,
|
||||||
ChangeDetectorRef,
|
ChangeDetectorRef,
|
||||||
DsDynamicScrollableDropdownComponent,
|
DsDynamicScrollableDropdownComponent,
|
||||||
{ provide: VocabularyService, useValue: vocabularyServiceStub },
|
{ provide: VocabularyService, useValue: vocabularyServiceStub },
|
||||||
{ provide: DynamicFormLayoutService, useValue: mockDynamicFormLayoutService },
|
{ provide: DynamicFormLayoutService, useValue: mockDynamicFormLayoutService },
|
||||||
{ provide: DynamicFormValidationService, useValue: mockDynamicFormValidationService },
|
{ provide: DynamicFormValidationService, useValue: mockDynamicFormValidationService },
|
||||||
|
{ provide: APP_DATA_SERVICES_MAP, useValue: {} },
|
||||||
],
|
],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
});
|
});
|
||||||
|
@@ -8,6 +8,8 @@ import {
|
|||||||
Component,
|
Component,
|
||||||
ElementRef,
|
ElementRef,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
|
Inject,
|
||||||
|
Injector,
|
||||||
Input,
|
Input,
|
||||||
OnInit,
|
OnInit,
|
||||||
Output,
|
Output,
|
||||||
@@ -27,23 +29,35 @@ import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
|||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf,
|
of as observableOf,
|
||||||
|
of,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import {
|
import {
|
||||||
catchError,
|
catchError,
|
||||||
distinctUntilChanged,
|
distinctUntilChanged,
|
||||||
map,
|
map,
|
||||||
|
take,
|
||||||
tap,
|
tap,
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
|
import {
|
||||||
|
APP_DATA_SERVICES_MAP,
|
||||||
|
LazyDataServicesMap,
|
||||||
|
} from 'src/config/app-config.interface';
|
||||||
|
|
||||||
|
import { CacheableObject } from '../../../../../../core/cache/cacheable-object.model';
|
||||||
|
import { FindAllDataImpl } from '../../../../../../core/data/base/find-all-data';
|
||||||
import {
|
import {
|
||||||
buildPaginatedList,
|
buildPaginatedList,
|
||||||
PaginatedList,
|
PaginatedList,
|
||||||
} from '../../../../../../core/data/paginated-list.model';
|
} from '../../../../../../core/data/paginated-list.model';
|
||||||
|
import { RemoteData } from '../../../../../../core/data/remote-data';
|
||||||
|
import { lazyDataService } from '../../../../../../core/lazy-data-service';
|
||||||
import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators';
|
import { getFirstSucceededRemoteDataPayload } from '../../../../../../core/shared/operators';
|
||||||
import { PageInfo } from '../../../../../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../../../core/shared/page-info.model';
|
||||||
import { VocabularyEntry } from '../../../../../../core/submission/vocabularies/models/vocabulary-entry.model';
|
|
||||||
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
|
import { VocabularyService } from '../../../../../../core/submission/vocabularies/vocabulary.service';
|
||||||
import { isEmpty } from '../../../../../empty.util';
|
import {
|
||||||
|
hasValue,
|
||||||
|
isEmpty,
|
||||||
|
} from '../../../../../empty.util';
|
||||||
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
|
import { FormFieldMetadataValueObject } from '../../../models/form-field-metadata-value.model';
|
||||||
import { DsDynamicVocabularyComponent } from '../dynamic-vocabulary.component';
|
import { DsDynamicVocabularyComponent } from '../dynamic-vocabulary.component';
|
||||||
import { DynamicScrollableDropdownModel } from './dynamic-scrollable-dropdown.model';
|
import { DynamicScrollableDropdownModel } from './dynamic-scrollable-dropdown.model';
|
||||||
@@ -84,10 +98,28 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
|
|||||||
public selectedIndex = 0;
|
public selectedIndex = 0;
|
||||||
public acceptableKeys = ['Space', 'NumpadMultiply', 'NumpadAdd', 'NumpadSubtract', 'NumpadDecimal', 'Semicolon', 'Equal', 'Comma', 'Minus', 'Period', 'Quote', 'Backquote'];
|
public acceptableKeys = ['Space', 'NumpadMultiply', 'NumpadAdd', 'NumpadSubtract', 'NumpadDecimal', 'Semicolon', 'Equal', 'Comma', 'Minus', 'Period', 'Quote', 'Backquote'];
|
||||||
|
|
||||||
constructor(protected vocabularyService: VocabularyService,
|
/**
|
||||||
|
* If true the component can rely on the findAll method for data loading.
|
||||||
|
* This is a behaviour activated by dependency injection through the dropdown config.
|
||||||
|
* If a service that implements findAll is not provided in the config the component falls back on the standard vocabulary service.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private useFindAllService: boolean;
|
||||||
|
/**
|
||||||
|
* A service that implements FindAllData.
|
||||||
|
* If is provided in the config will be used for data loading in stead of the VocabularyService
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private findAllService: FindAllDataImpl<CacheableObject>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected vocabularyService: VocabularyService,
|
||||||
protected cdr: ChangeDetectorRef,
|
protected cdr: ChangeDetectorRef,
|
||||||
protected layoutService: DynamicFormLayoutService,
|
protected layoutService: DynamicFormLayoutService,
|
||||||
protected validationService: DynamicFormValidationService,
|
protected validationService: DynamicFormValidationService,
|
||||||
|
protected parentInjector: Injector,
|
||||||
|
@Inject(APP_DATA_SERVICES_MAP) private dataServiceMap: LazyDataServicesMap,
|
||||||
) {
|
) {
|
||||||
super(vocabularyService, layoutService, validationService);
|
super(vocabularyService, layoutService, validationService);
|
||||||
}
|
}
|
||||||
@@ -96,21 +128,41 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
|
|||||||
* Initialize the component, setting up the init form value
|
* Initialize the component, setting up the init form value
|
||||||
*/
|
*/
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
const lazyProvider$: Observable<Cache> = hasValue(this.model.resourceType) ?
|
||||||
|
lazyDataService(this.dataServiceMap, this.model.resourceType.value, this.parentInjector) : of(null);
|
||||||
|
|
||||||
|
lazyProvider$.pipe(take(1)).subscribe((dataService) => {
|
||||||
|
this.findAllService = dataService as unknown as FindAllDataImpl<CacheableObject>;
|
||||||
|
this.useFindAllService = hasValue(this.findAllService?.findAll) && typeof this.findAllService.findAll === 'function';
|
||||||
this.updatePageInfo(this.model.maxOptions, 1);
|
this.updatePageInfo(this.model.maxOptions, 1);
|
||||||
this.loadOptions(true);
|
this.loadOptions(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
this.group.get(this.model.id).valueChanges.pipe(distinctUntilChanged())
|
this.group.get(this.model.id).valueChanges.pipe(distinctUntilChanged())
|
||||||
.subscribe((value) => {
|
.subscribe((value) => {
|
||||||
this.setCurrentValue(value);
|
this.setCurrentValue(value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get service and method to use to retrieve dropdown options
|
||||||
|
*/
|
||||||
|
getDataFromService(): Observable<RemoteData<PaginatedList<CacheableObject>>> {
|
||||||
|
if (this.useFindAllService) {
|
||||||
|
return this.findAllService.findAll({ elementsPerPage: this.pageInfo.elementsPerPage, currentPage: this.pageInfo.currentPage });
|
||||||
|
} else {
|
||||||
|
return this.vocabularyService.getVocabularyEntriesByValue(this.inputText, false, this.model.vocabularyOptions, this.pageInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
loadOptions(fromInit: boolean) {
|
loadOptions(fromInit: boolean) {
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.vocabularyService.getVocabularyEntriesByValue(this.inputText, false, this.model.vocabularyOptions, this.pageInfo).pipe(
|
this.getDataFromService().pipe(
|
||||||
getFirstSucceededRemoteDataPayload(),
|
getFirstSucceededRemoteDataPayload(),
|
||||||
catchError(() => observableOf(buildPaginatedList(new PageInfo(), []))),
|
catchError(() => observableOf(buildPaginatedList(new PageInfo(), []))),
|
||||||
tap(() => this.loading = false),
|
tap(() => this.loading = false),
|
||||||
).subscribe((list: PaginatedList<VocabularyEntry>) => {
|
).subscribe((list: PaginatedList<CacheableObject>) => {
|
||||||
this.optionsList = list.page;
|
this.optionsList = list.page;
|
||||||
if (fromInit && this.model.value) {
|
if (fromInit && this.model.value) {
|
||||||
this.setCurrentValue(this.model.value, true);
|
this.setCurrentValue(this.model.value, true);
|
||||||
@@ -130,7 +182,7 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
|
|||||||
/**
|
/**
|
||||||
* Converts an item from the result list to a `string` to display in the `<input>` field.
|
* Converts an item from the result list to a `string` to display in the `<input>` field.
|
||||||
*/
|
*/
|
||||||
inputFormatter = (x: VocabularyEntry): string => x.display || x.value;
|
inputFormatter = (x: any): string => (this.model.formatFunction ? this.model.formatFunction(x) : (x.display || x.value));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens dropdown menu
|
* Opens dropdown menu
|
||||||
@@ -233,7 +285,7 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
|
|||||||
this.pageInfo.totalElements,
|
this.pageInfo.totalElements,
|
||||||
this.pageInfo.totalPages,
|
this.pageInfo.totalPages,
|
||||||
);
|
);
|
||||||
this.vocabularyService.getVocabularyEntriesByValue(this.inputText, false, this.model.vocabularyOptions, this.pageInfo).pipe(
|
this.getDataFromService().pipe(
|
||||||
getFirstSucceededRemoteDataPayload(),
|
getFirstSucceededRemoteDataPayload(),
|
||||||
catchError(() => observableOf(buildPaginatedList(
|
catchError(() => observableOf(buildPaginatedList(
|
||||||
new PageInfo(),
|
new PageInfo(),
|
||||||
@@ -241,7 +293,7 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
|
|||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
tap(() => this.loading = false))
|
tap(() => this.loading = false))
|
||||||
.subscribe((list: PaginatedList<VocabularyEntry>) => {
|
.subscribe((list: PaginatedList<any>) => {
|
||||||
this.optionsList = this.optionsList.concat(list.page);
|
this.optionsList = this.optionsList.concat(list.page);
|
||||||
this.updatePageInfo(
|
this.updatePageInfo(
|
||||||
list.pageInfo.elementsPerPage,
|
list.pageInfo.elementsPerPage,
|
||||||
@@ -272,7 +324,7 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
|
|||||||
setCurrentValue(value: any, init = false): void {
|
setCurrentValue(value: any, init = false): void {
|
||||||
let result: Observable<string>;
|
let result: Observable<string>;
|
||||||
|
|
||||||
if (init) {
|
if (init && !this.useFindAllService) {
|
||||||
result = this.getInitValueFromModel().pipe(
|
result = this.getInitValueFromModel().pipe(
|
||||||
map((formValue: FormFieldMetadataValueObject) => formValue.display),
|
map((formValue: FormFieldMetadataValueObject) => formValue.display),
|
||||||
);
|
);
|
||||||
@@ -281,6 +333,8 @@ export class DsDynamicScrollableDropdownComponent extends DsDynamicVocabularyCom
|
|||||||
result = observableOf('');
|
result = observableOf('');
|
||||||
} else if (typeof value === 'string') {
|
} else if (typeof value === 'string') {
|
||||||
result = observableOf(value);
|
result = observableOf(value);
|
||||||
|
} else if (this.useFindAllService) {
|
||||||
|
result = observableOf(value[this.model.displayKey]);
|
||||||
} else {
|
} else {
|
||||||
result = observableOf(value.display);
|
result = observableOf(value.display);
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ import {
|
|||||||
serializable,
|
serializable,
|
||||||
} from '@ng-dynamic-forms/core';
|
} from '@ng-dynamic-forms/core';
|
||||||
|
|
||||||
|
import { ResourceType } from '../../../../../../core/shared/resource-type';
|
||||||
import { VocabularyOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
import { VocabularyOptions } from '../../../../../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||||
import {
|
import {
|
||||||
DsDynamicInputModel,
|
DsDynamicInputModel,
|
||||||
@@ -13,15 +14,27 @@ import {
|
|||||||
export const DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN = 'SCROLLABLE_DROPDOWN';
|
export const DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN = 'SCROLLABLE_DROPDOWN';
|
||||||
|
|
||||||
export interface DynamicScrollableDropdownModelConfig extends DsDynamicInputModelConfig {
|
export interface DynamicScrollableDropdownModelConfig extends DsDynamicInputModelConfig {
|
||||||
vocabularyOptions: VocabularyOptions;
|
vocabularyOptions?: VocabularyOptions;
|
||||||
maxOptions?: number;
|
maxOptions?: number;
|
||||||
value?: any;
|
value?: any;
|
||||||
|
displayKey?: string;
|
||||||
|
formatFunction?: (value: any) => string;
|
||||||
|
resourceType?: ResourceType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DynamicScrollableDropdownModel extends DsDynamicInputModel {
|
export class DynamicScrollableDropdownModel extends DsDynamicInputModel {
|
||||||
|
|
||||||
@serializable() maxOptions: number;
|
@serializable() maxOptions: number;
|
||||||
@serializable() readonly type: string = DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN;
|
@serializable() readonly type: string = DYNAMIC_FORM_CONTROL_TYPE_SCROLLABLE_DROPDOWN;
|
||||||
|
@serializable() displayKey: string;
|
||||||
|
/**
|
||||||
|
* Configurable function for display value formatting in input
|
||||||
|
*/
|
||||||
|
formatFunction: (value: any) => string;
|
||||||
|
/**
|
||||||
|
* Resource type to match data service
|
||||||
|
*/
|
||||||
|
resourceType: ResourceType;
|
||||||
|
|
||||||
constructor(config: DynamicScrollableDropdownModelConfig, layout?: DynamicFormControlLayout) {
|
constructor(config: DynamicScrollableDropdownModelConfig, layout?: DynamicFormControlLayout) {
|
||||||
|
|
||||||
@@ -30,6 +43,9 @@ export class DynamicScrollableDropdownModel extends DsDynamicInputModel {
|
|||||||
this.autoComplete = AUTOCOMPLETE_OFF;
|
this.autoComplete = AUTOCOMPLETE_OFF;
|
||||||
this.vocabularyOptions = config.vocabularyOptions;
|
this.vocabularyOptions = config.vocabularyOptions;
|
||||||
this.maxOptions = config.maxOptions || 10;
|
this.maxOptions = config.maxOptions || 10;
|
||||||
|
this.displayKey = config.displayKey || 'display';
|
||||||
|
this.formatFunction = config.formatFunction;
|
||||||
|
this.resourceType = config.resourceType;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -164,18 +164,6 @@ describe('DsDynamicLookupRelationSearchTabComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('selectAll', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
spyOn(component.selectObject, 'emit');
|
|
||||||
component.selectAll();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should emit the page filtered from already selected objects and call select on the service for all objects', () => {
|
|
||||||
expect(component.selectObject.emit).toHaveBeenCalledWith(searchResult3);
|
|
||||||
expect(selectableListService.select).toHaveBeenCalledWith(listID, [searchResult1, searchResult2, searchResult3]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('deselectAll', () => {
|
describe('deselectAll', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(component.deselectObject, 'emit');
|
spyOn(component.deselectObject, 'emit');
|
||||||
|
@@ -16,13 +16,7 @@ import {
|
|||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
Observable,
|
Observable,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import {
|
import { take } from 'rxjs/operators';
|
||||||
map,
|
|
||||||
mapTo,
|
|
||||||
switchMap,
|
|
||||||
take,
|
|
||||||
tap,
|
|
||||||
} from 'rxjs/operators';
|
|
||||||
|
|
||||||
import { LookupRelationService } from '../../../../../../core/data/lookup-relation.service';
|
import { LookupRelationService } from '../../../../../../core/data/lookup-relation.service';
|
||||||
import { PaginatedList } from '../../../../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../../../../core/data/paginated-list.model';
|
||||||
@@ -44,7 +38,6 @@ import { hasValue } from '../../../../../empty.util';
|
|||||||
import { CollectionElementLinkType } from '../../../../../object-collection/collection-element-link.type';
|
import { CollectionElementLinkType } from '../../../../../object-collection/collection-element-link.type';
|
||||||
import { ListableObject } from '../../../../../object-collection/shared/listable-object.model';
|
import { ListableObject } from '../../../../../object-collection/shared/listable-object.model';
|
||||||
import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.service';
|
import { SelectableListService } from '../../../../../object-list/selectable-list/selectable-list.service';
|
||||||
import { PaginationComponentOptions } from '../../../../../pagination/pagination-component-options.model';
|
|
||||||
import { SearchObjects } from '../../../../../search/models/search-objects.model';
|
import { SearchObjects } from '../../../../../search/models/search-objects.model';
|
||||||
import { SearchResult } from '../../../../../search/models/search-result.model';
|
import { SearchResult } from '../../../../../search/models/search-result.model';
|
||||||
import { ThemedSearchComponent } from '../../../../../search/themed-search.component';
|
import { ThemedSearchComponent } from '../../../../../search/themed-search.component';
|
||||||
@@ -240,35 +233,6 @@ export class DsDynamicLookupRelationSearchTabComponent implements OnInit, OnDest
|
|||||||
this.selectableListService.deselect(this.listId, page);
|
this.selectableListService.deselect(this.listId, page);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Select all items that were found using the current search query
|
|
||||||
*/
|
|
||||||
selectAll() {
|
|
||||||
this.allSelected = true;
|
|
||||||
this.selectAllLoading = true;
|
|
||||||
const fullPagination = Object.assign(new PaginationComponentOptions(), {
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: 9999,
|
|
||||||
});
|
|
||||||
const fullSearchConfig = Object.assign(this.lookupRelationService.searchConfig, { pagination: fullPagination });
|
|
||||||
const results$ = this.searchService.search<Item>(fullSearchConfig);
|
|
||||||
results$.pipe(
|
|
||||||
getFirstSucceededRemoteData(),
|
|
||||||
map((resultsRD) => resultsRD.payload.page),
|
|
||||||
tap(() => this.selectAllLoading = false),
|
|
||||||
switchMap((results) => this.selection$.pipe(
|
|
||||||
take(1),
|
|
||||||
tap((selection: SearchResult<Item>[]) => {
|
|
||||||
const filteredResults = results.filter((pageItem) => selection.findIndex((selected) => selected.equals(pageItem)) < 0);
|
|
||||||
this.selectObject.emit(...filteredResults);
|
|
||||||
}),
|
|
||||||
mapTo(results),
|
|
||||||
)),
|
|
||||||
).subscribe((results) => {
|
|
||||||
this.selectableListService.select(this.listId, results);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* setSelectedIds select all the items from the results that have relationship
|
* setSelectedIds select all the items from the results that have relationship
|
||||||
* @param idOfItems the uuid of items that are being checked
|
* @param idOfItems the uuid of items that are being checked
|
||||||
|
@@ -0,0 +1,11 @@
|
|||||||
|
<img placement="top"
|
||||||
|
[ngbTooltip]="orcidTooltipTemplate"
|
||||||
|
class="orcid-icon"
|
||||||
|
[ngClass]="{'not-authenticated': !authenticatedTimestamp}"
|
||||||
|
alt="ORCID {{ orcidTooltip }}"
|
||||||
|
src="assets/images/orcid.logo.icon.svg"
|
||||||
|
data-test="orcidIcon"/>
|
||||||
|
|
||||||
|
<ng-template #orcidTooltipTemplate>
|
||||||
|
<span class="text-muted">{{ orcidTooltip }}</span>
|
||||||
|
</ng-template>
|
@@ -0,0 +1,11 @@
|
|||||||
|
:host {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orcid-icon {
|
||||||
|
height: 1.2rem;
|
||||||
|
|
||||||
|
&.not-authenticated {
|
||||||
|
filter: grayscale(100%);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,71 @@
|
|||||||
|
import {
|
||||||
|
NgClass,
|
||||||
|
NgIf,
|
||||||
|
} from '@angular/common';
|
||||||
|
import {
|
||||||
|
ComponentFixture,
|
||||||
|
TestBed,
|
||||||
|
} from '@angular/core/testing';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { MetadataValue } from '../../core/shared/metadata.models';
|
||||||
|
import { OrcidBadgeAndTooltipComponent } from './orcid-badge-and-tooltip.component';
|
||||||
|
|
||||||
|
describe('OrcidBadgeAndTooltipComponent', () => {
|
||||||
|
let component: OrcidBadgeAndTooltipComponent;
|
||||||
|
let fixture: ComponentFixture<OrcidBadgeAndTooltipComponent>;
|
||||||
|
let translateService: TranslateService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [
|
||||||
|
OrcidBadgeAndTooltipComponent,
|
||||||
|
NgbTooltipModule,
|
||||||
|
NgClass,
|
||||||
|
NgIf,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: TranslateService, useValue: { instant: (key: string) => key } },
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(OrcidBadgeAndTooltipComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
translateService = TestBed.inject(TranslateService);
|
||||||
|
|
||||||
|
component.orcid = { value: '0000-0002-1825-0097' } as MetadataValue;
|
||||||
|
component.authenticatedTimestamp = { value: '2023-10-01' } as MetadataValue;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set orcidTooltip when authenticatedTimestamp is provided', () => {
|
||||||
|
component.ngOnInit();
|
||||||
|
expect(component.orcidTooltip).toBe('person.orcid-tooltip.authenticated');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set orcidTooltip when authenticatedTimestamp is not provided', () => {
|
||||||
|
component.authenticatedTimestamp = null;
|
||||||
|
component.ngOnInit();
|
||||||
|
expect(component.orcidTooltip).toBe('person.orcid-tooltip.not-authenticated');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the ORCID icon', () => {
|
||||||
|
const badgeIcon = fixture.debugElement.query(By.css('img[data-test="orcidIcon"]'));
|
||||||
|
expect(badgeIcon).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should display the ORCID icon in greyscale if there is no authenticated timestamp', () => {
|
||||||
|
component.authenticatedTimestamp = null;
|
||||||
|
fixture.detectChanges();
|
||||||
|
const badgeIcon = fixture.debugElement.query(By.css('img[data-test="orcidIcon"]'));
|
||||||
|
expect(badgeIcon.nativeElement.classList).toContain('not-authenticated');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@@ -0,0 +1,65 @@
|
|||||||
|
import {
|
||||||
|
NgClass,
|
||||||
|
NgIf,
|
||||||
|
} from '@angular/common';
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
Input,
|
||||||
|
OnInit,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { MetadataValue } from '../../core/shared/metadata.models';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component to display an ORCID badge with a tooltip.
|
||||||
|
* The tooltip text changes based on whether the ORCID is authenticated.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-orcid-badge-and-tooltip',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
NgIf,
|
||||||
|
NgbTooltipModule,
|
||||||
|
NgClass,
|
||||||
|
],
|
||||||
|
templateUrl: './orcid-badge-and-tooltip.component.html',
|
||||||
|
styleUrl: './orcid-badge-and-tooltip.component.scss',
|
||||||
|
})
|
||||||
|
export class OrcidBadgeAndTooltipComponent implements OnInit {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ORCID value to be displayed.
|
||||||
|
*/
|
||||||
|
@Input() orcid: MetadataValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp indicating when the ORCID was authenticated.
|
||||||
|
*/
|
||||||
|
@Input() authenticatedTimestamp: MetadataValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The tooltip text to be displayed.
|
||||||
|
*/
|
||||||
|
orcidTooltip: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor to inject the TranslateService.
|
||||||
|
* @param translateService - Service for translation.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private translateService: TranslateService,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the component.
|
||||||
|
* Sets the tooltip text based on the presence of the authenticated timestamp.
|
||||||
|
*/
|
||||||
|
ngOnInit() {
|
||||||
|
this.orcidTooltip = this.authenticatedTimestamp ?
|
||||||
|
this.translateService.instant('person.orcid-tooltip.authenticated', { orcid: this.orcid.value }) :
|
||||||
|
this.translateService.instant('person.orcid-tooltip.not-authenticated', { orcid: this.orcid.value });
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -6,11 +6,13 @@ import {
|
|||||||
} from '@angular/common';
|
} from '@angular/common';
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
|
EventEmitter,
|
||||||
Inject,
|
Inject,
|
||||||
Input,
|
Input,
|
||||||
OnChanges,
|
OnChanges,
|
||||||
OnDestroy,
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
|
Output,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import {
|
import {
|
||||||
@@ -78,6 +80,8 @@ export class SearchFilterComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
@Input() scope: string;
|
@Input() scope: string;
|
||||||
|
|
||||||
|
@Output() isVisibilityComputed = new EventEmitter<boolean>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* True when the filter is 100% collapsed in the UI
|
* True when the filter is 100% collapsed in the UI
|
||||||
*/
|
*/
|
||||||
@@ -136,11 +140,16 @@ export class SearchFilterComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.active$ = this.isActive();
|
this.active$ = this.isActive();
|
||||||
this.collapsed$ = this.isCollapsed();
|
this.collapsed$ = this.isCollapsed();
|
||||||
this.initializeFilter();
|
this.initializeFilter();
|
||||||
this.subs.push(this.appliedFilters$.pipe(take(1)).subscribe((selectedValues: AppliedFilter[]) => {
|
this.subs.push(
|
||||||
|
this.appliedFilters$.subscribe((selectedValues: AppliedFilter[]) => {
|
||||||
if (isNotEmpty(selectedValues)) {
|
if (isNotEmpty(selectedValues)) {
|
||||||
this.filterService.expand(this.filter.name);
|
this.filterService.expand(this.filter.name);
|
||||||
}
|
}
|
||||||
}));
|
}),
|
||||||
|
this.getIsActive().pipe(take(1)).subscribe(() => {
|
||||||
|
this.isVisibilityComputed.emit(true);
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnChanges(): void {
|
ngOnChanges(): void {
|
||||||
@@ -223,6 +232,16 @@ export class SearchFilterComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
* @returns {Observable<boolean>} Emits true whenever a given filter config should be shown
|
* @returns {Observable<boolean>} Emits true whenever a given filter config should be shown
|
||||||
*/
|
*/
|
||||||
isActive(): Observable<boolean> {
|
isActive(): Observable<boolean> {
|
||||||
|
return this.getIsActive().pipe(
|
||||||
|
startWith(true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return current filter visibility
|
||||||
|
* @returns {Observable<boolean>} Emits true whenever a given filter config should be shown
|
||||||
|
*/
|
||||||
|
private getIsActive(): Observable<boolean> {
|
||||||
return combineLatest([
|
return combineLatest([
|
||||||
this.appliedFilters$,
|
this.appliedFilters$,
|
||||||
this.searchConfigService.searchOptions,
|
this.searchConfigService.searchOptions,
|
||||||
@@ -243,7 +262,6 @@ export class SearchFilterComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
startWith(true),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,24 @@
|
|||||||
<h3 *ngIf="inPlaceSearch">{{filterLabel+'.filters.head' | translate}}</h3>
|
@if (inPlaceSearch) {
|
||||||
<h2 *ngIf="!inPlaceSearch">{{filterLabel+'.filters.head' | translate}}</h2>
|
<h3>{{filterLabel+'.filters.head' | translate}}</h3>
|
||||||
<div *ngIf="(filters | async)?.hasSucceeded">
|
} @else {
|
||||||
<div *ngFor="let filter of (filters | async)?.payload; trackBy: trackUpdate">
|
<h2>{{filterLabel+'.filters.head' | translate}}</h2>
|
||||||
<ds-search-filter [scope]="currentScope" [filter]="filter" [inPlaceSearch]="inPlaceSearch" [refreshFilters]="refreshFilters"></ds-search-filter>
|
}
|
||||||
|
|
||||||
|
@if ((filters | async)?.hasSucceeded) {
|
||||||
|
<div [class.visually-hidden]="filtersWithComputedVisibility !== (filters | async)?.payload?.length">
|
||||||
|
@for (filter of (filters | async)?.payload; track filter.name) {
|
||||||
|
<ds-search-filter (isVisibilityComputed)="countFiltersWithComputedVisibility($event)" [scope]="currentScope" [filter]="filter" [inPlaceSearch]="inPlaceSearch" [refreshFilters]="refreshFilters"></ds-search-filter>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
}
|
||||||
<button *ngIf="inPlaceSearch" class="btn btn-primary" [routerLink]="[searchLink]" [queryParams]="clearParams | async" (click)="minimizeFilters()" queryParamsHandling="merge" role="button">
|
|
||||||
|
@if(filtersWithComputedVisibility !== (filters | async)?.payload?.length) {
|
||||||
|
<ngx-skeleton-loader [count]="defaultFilterCount"/>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (inPlaceSearch) {
|
||||||
|
<button class="btn btn-primary" [routerLink]="[searchLink]" [queryParams]="clearParams | async" (click)="minimizeFilters()" queryParamsHandling="merge" role="button">
|
||||||
<i class="fas fa-undo"></i> {{"search.filters.reset" | translate}}
|
<i class="fas fa-undo"></i> {{"search.filters.reset" | translate}}
|
||||||
</button>
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
@@ -1,2 +1,12 @@
|
|||||||
@import '../../../../styles/variables';
|
@import '../../../../styles/variables';
|
||||||
@import '../../../../styles/mixins';
|
@import '../../../../styles/mixins';
|
||||||
|
|
||||||
|
:host ::ng-deep {
|
||||||
|
ngx-skeleton-loader .skeleton-loader {
|
||||||
|
height: var(--ds-filters-skeleton-height);
|
||||||
|
margin-bottom: var(--ds-filters-skeleton-spacing);
|
||||||
|
padding: var(--ds-filters-skeleton-spacing);
|
||||||
|
background-color: var(--bs-light);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -11,6 +11,8 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
|||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
|
import { APP_CONFIG } from '../../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../../environments/environment';
|
||||||
import { SearchService } from '../../../core/shared/search/search.service';
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
import { SearchFilterService } from '../../../core/shared/search/search-filter.service';
|
import { SearchFilterService } from '../../../core/shared/search/search-filter.service';
|
||||||
import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-configuration.service';
|
import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-configuration.service';
|
||||||
@@ -41,6 +43,7 @@ describe('SearchFiltersComponent', () => {
|
|||||||
{ provide: SearchService, useValue: searchService },
|
{ provide: SearchService, useValue: searchService },
|
||||||
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
|
{ provide: SEARCH_CONFIG_SERVICE, useValue: new SearchConfigurationServiceStub() },
|
||||||
{ provide: SearchFilterService, useValue: searchFilters },
|
{ provide: SearchFilterService, useValue: searchFilters },
|
||||||
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
],
|
],
|
||||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
}).overrideComponent(SearchFiltersComponent, {
|
}).overrideComponent(SearchFiltersComponent, {
|
||||||
|
@@ -1,8 +1,4 @@
|
|||||||
import {
|
import { AsyncPipe } from '@angular/common';
|
||||||
AsyncPipe,
|
|
||||||
NgFor,
|
|
||||||
NgIf,
|
|
||||||
} from '@angular/common';
|
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
Inject,
|
Inject,
|
||||||
@@ -14,19 +10,23 @@ import {
|
|||||||
RouterLink,
|
RouterLink,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
Observable,
|
Observable,
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import {
|
||||||
|
APP_CONFIG,
|
||||||
|
AppConfig,
|
||||||
|
} from '../../../../config/app-config.interface';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { SearchService } from '../../../core/shared/search/search.service';
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
||||||
import { SearchFilterService } from '../../../core/shared/search/search-filter.service';
|
import { SearchFilterService } from '../../../core/shared/search/search-filter.service';
|
||||||
import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-configuration.service';
|
import { SEARCH_CONFIG_SERVICE } from '../../../my-dspace-page/my-dspace-configuration.service';
|
||||||
import { currentPath } from '../../utils/route.utils';
|
import { currentPath } from '../../utils/route.utils';
|
||||||
import { AdvancedSearchComponent } from '../advanced-search/advanced-search.component';
|
|
||||||
import { AppliedFilter } from '../models/applied-filter.model';
|
import { AppliedFilter } from '../models/applied-filter.model';
|
||||||
import { SearchFilterConfig } from '../models/search-filter-config.model';
|
import { SearchFilterConfig } from '../models/search-filter-config.model';
|
||||||
import { SearchFilterComponent } from './search-filter/search-filter.component';
|
import { SearchFilterComponent } from './search-filter/search-filter.component';
|
||||||
@@ -36,7 +36,7 @@ import { SearchFilterComponent } from './search-filter/search-filter.component';
|
|||||||
styleUrls: ['./search-filters.component.scss'],
|
styleUrls: ['./search-filters.component.scss'],
|
||||||
templateUrl: './search-filters.component.html',
|
templateUrl: './search-filters.component.html',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [NgIf, NgFor, SearchFilterComponent, RouterLink, AsyncPipe, TranslateModule, AdvancedSearchComponent],
|
imports: [SearchFilterComponent, RouterLink, AsyncPipe, TranslateModule, NgxSkeletonLoaderModule],
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,15 +81,23 @@ export class SearchFiltersComponent implements OnInit {
|
|||||||
*/
|
*/
|
||||||
searchLink: string;
|
searchLink: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters for which visibility has been computed
|
||||||
|
*/
|
||||||
|
filtersWithComputedVisibility = 0;
|
||||||
|
|
||||||
subs = [];
|
subs = [];
|
||||||
filterLabel = 'search';
|
filterLabel = 'search';
|
||||||
|
defaultFilterCount: number;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected searchService: SearchService,
|
protected searchService: SearchService,
|
||||||
protected searchFilterService: SearchFilterService,
|
protected searchFilterService: SearchFilterService,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
@Inject(SEARCH_CONFIG_SERVICE) protected searchConfigService: SearchConfigurationService,
|
@Inject(SEARCH_CONFIG_SERVICE) protected searchConfigService: SearchConfigurationService,
|
||||||
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
) {
|
) {
|
||||||
|
this.defaultFilterCount = this.appConfig.search.filterPlaceholdersCount ?? 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@@ -125,4 +133,10 @@ export class SearchFiltersComponent implements OnInit {
|
|||||||
this.searchFilterService.minimizeAll();
|
this.searchFilterService.minimizeAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
countFiltersWithComputedVisibility(computed: boolean) {
|
||||||
|
if (computed) {
|
||||||
|
this.filtersWithComputedVisibility += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,38 @@
|
|||||||
|
<div class="row flex-nowrap">
|
||||||
|
<div [class.mb-2]="(viewMode$ | async) === ViewMode.ListElement" class="info-skeleton col-12">
|
||||||
|
<ngx-skeleton-loader/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if((viewMode$ | async) === ViewMode.ListElement) {
|
||||||
|
@for (result of loadingResults; track result; let first = $first) {
|
||||||
|
<div [class.my-4]="!first" class="row">
|
||||||
|
@if(showThumbnails) {
|
||||||
|
<div class="col-3 col-md-2">
|
||||||
|
<div class="thumbnail-skeleton position-relative">
|
||||||
|
<ngx-skeleton-loader/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<div [class.col-9]="showThumbnails" [class.col-md-10]="showThumbnails" [class.col-md-12]="!showThumbnails">
|
||||||
|
<div class="badge-skeleton">
|
||||||
|
<ngx-skeleton-loader/>
|
||||||
|
</div>
|
||||||
|
<div class="text-skeleton">
|
||||||
|
<ngx-skeleton-loader [count]="textLineCount"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
} @else if ((viewMode$ | async) === ViewMode.GridElement) {
|
||||||
|
<div class="card-columns row">
|
||||||
|
@for (result of loadingResults; track result) {
|
||||||
|
<div class="card-column col col-sm-6 col-lg-4">
|
||||||
|
<div class="card-skeleton">
|
||||||
|
<ngx-skeleton-loader/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
}
|
@@ -0,0 +1,56 @@
|
|||||||
|
:host ::ng-deep {
|
||||||
|
--ds-wrapper-grid-spacing: calc(var(--bs-spacer) / 2);
|
||||||
|
|
||||||
|
.info-skeleton, .badge-skeleton, .text-skeleton{
|
||||||
|
ngx-skeleton-loader .skeleton-loader {
|
||||||
|
height: var(--ds-search-skeleton-text-height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-skeleton, .info-skeleton {
|
||||||
|
ngx-skeleton-loader .skeleton-loader {
|
||||||
|
width: var(--ds-search-skeleton-badge-width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-skeleton {
|
||||||
|
ngx-skeleton-loader .skeleton-loader {
|
||||||
|
width: var(--ds-search-skeleton-info-width);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail-skeleton {
|
||||||
|
max-width: var(--ds-thumbnail-max-width);
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
ngx-skeleton-loader .skeleton-loader {
|
||||||
|
margin-right: var(--ds-search-skeleton-thumbnail-margin);
|
||||||
|
border-radius: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-skeleton {
|
||||||
|
ngx-skeleton-loader .skeleton-loader {
|
||||||
|
height: var(--ds-search-skeleton-card-height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngx-skeleton-loader .skeleton-loader {
|
||||||
|
background-color: var(--bs-light);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-columns {
|
||||||
|
margin-left: calc(-1 * var(--ds-wrapper-grid-spacing));
|
||||||
|
margin-right: calc(-1 * var(--ds-wrapper-grid-spacing));
|
||||||
|
column-gap: 0;
|
||||||
|
|
||||||
|
.card-column {
|
||||||
|
padding-left: var(--ds-wrapper-grid-spacing);
|
||||||
|
padding-right: var(--ds-wrapper-grid-spacing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@@ -0,0 +1,32 @@
|
|||||||
|
import {
|
||||||
|
ComponentFixture,
|
||||||
|
TestBed,
|
||||||
|
} from '@angular/core/testing';
|
||||||
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
|
||||||
|
import { SearchService } from '../../../../core/shared/search/search.service';
|
||||||
|
import { SearchServiceStub } from '../../../testing/search-service.stub';
|
||||||
|
import { SearchResultsSkeletonComponent } from './search-results-skeleton.component';
|
||||||
|
|
||||||
|
describe('SearchResultsSkeletonComponent', () => {
|
||||||
|
let component: SearchResultsSkeletonComponent;
|
||||||
|
let fixture: ComponentFixture<SearchResultsSkeletonComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [SearchResultsSkeletonComponent, NgxSkeletonLoaderModule],
|
||||||
|
providers: [
|
||||||
|
{ provide: SearchService, useValue: new SearchServiceStub() },
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(SearchResultsSkeletonComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,70 @@
|
|||||||
|
import {
|
||||||
|
AsyncPipe,
|
||||||
|
NgForOf,
|
||||||
|
} from '@angular/common';
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
Input,
|
||||||
|
OnInit,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { SearchService } from '../../../../core/shared/search/search.service';
|
||||||
|
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||||
|
import { hasValue } from '../../../empty.util';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-search-results-skeleton',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
NgxSkeletonLoaderModule,
|
||||||
|
AsyncPipe,
|
||||||
|
NgForOf,
|
||||||
|
],
|
||||||
|
templateUrl: './search-results-skeleton.component.html',
|
||||||
|
styleUrl: './search-results-skeleton.component.scss',
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component to show placeholders for search results while loading, to give a loading feedback to the user without layout shifting.
|
||||||
|
*/
|
||||||
|
export class SearchResultsSkeletonComponent implements OnInit {
|
||||||
|
/**
|
||||||
|
* Whether the search result contains thumbnail
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
showThumbnails: boolean;
|
||||||
|
/**
|
||||||
|
* The number of search result loaded in the current page
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
numberOfResults = 0;
|
||||||
|
/**
|
||||||
|
* How many placeholder are displayed for the search result text
|
||||||
|
*/
|
||||||
|
@Input()
|
||||||
|
textLineCount = 2;
|
||||||
|
/**
|
||||||
|
* The view mode of the search page
|
||||||
|
*/
|
||||||
|
public viewMode$: Observable<ViewMode>;
|
||||||
|
/**
|
||||||
|
* Array built from numberOfResults to count and iterate based on index
|
||||||
|
*/
|
||||||
|
public loadingResults: number[];
|
||||||
|
|
||||||
|
protected readonly ViewMode = ViewMode;
|
||||||
|
|
||||||
|
constructor(private searchService: SearchService) {
|
||||||
|
this.viewMode$ = this.searchService.getViewMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.loadingResults = Array.from({ length: this.numberOfResults }, (_, i) => i + 1);
|
||||||
|
|
||||||
|
if (!hasValue(this.showThumbnails)) {
|
||||||
|
// this is needed as the default value of show thumbnails is true but set in lower levels of the DOM.
|
||||||
|
this.showThumbnails = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,3 +1,15 @@
|
|||||||
|
@if ((activeFilters$ | async).length > 0 && (appliedFilters$ | async).length === 0) {
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="filters-badge-skeleton-container">
|
||||||
|
<div class="filter-badge-skeleton">
|
||||||
|
<ngx-skeleton-loader [count]="(activeFilters$ | async).length" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<h1 *ngIf="!disableHeader">{{ (configuration ? configuration + '.search.results.head' : 'search.results.head') | translate }}</h1>
|
<h1 *ngIf="!disableHeader">{{ (configuration ? configuration + '.search.results.head' : 'search.results.head') | translate }}</h1>
|
||||||
<ds-search-export-csv *ngIf="showCsvExport" [total]="searchResults?.payload?.totalElements"
|
<ds-search-export-csv *ngIf="showCsvExport" [total]="searchResults?.payload?.totalElements"
|
||||||
@@ -20,7 +32,13 @@
|
|||||||
(selectObject)="selectObject.emit($event)">
|
(selectObject)="selectObject.emit($event)">
|
||||||
</ds-viewable-collection>
|
</ds-viewable-collection>
|
||||||
</div>
|
</div>
|
||||||
<ds-loading *ngIf="isLoading()" message="{{'loading.search-results' | translate}}"></ds-loading>
|
|
||||||
|
<ds-search-results-skeleton
|
||||||
|
*ngIf="isLoading()"
|
||||||
|
[showThumbnails]="showThumbnails"
|
||||||
|
[numberOfResults]="searchConfig.pagination.pageSize"
|
||||||
|
></ds-search-results-skeleton>
|
||||||
|
|
||||||
<ds-error *ngIf="showError()"
|
<ds-error *ngIf="showError()"
|
||||||
message="{{errorMessageLabel() | translate}}"></ds-error>
|
message="{{errorMessageLabel() | translate}}"></ds-error>
|
||||||
<div *ngIf="searchResults?.payload?.page.length === 0 || searchResults?.statusCode === 400">
|
<div *ngIf="searchResults?.payload?.page.length === 0 || searchResults?.statusCode === 400">
|
||||||
|
@@ -0,0 +1,17 @@
|
|||||||
|
:host ::ng-deep {
|
||||||
|
.filter-badge-skeleton {
|
||||||
|
ngx-skeleton-loader .skeleton-loader {
|
||||||
|
background-color: var(--bs-light);
|
||||||
|
box-shadow: none;
|
||||||
|
width: var(--ds-search-skeleton-filter-badge-width);
|
||||||
|
height: var(--ds-search-skeleton-badge-height);
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-right: calc(var(--bs-spacer) / 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.filters-badge-skeleton-container {
|
||||||
|
display: flex;
|
||||||
|
max-height: var(--ds-search-skeleton-badge-height);
|
||||||
|
}
|
||||||
|
}
|
@@ -13,16 +13,20 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { Community } from '../../../core/shared/community.model';
|
import { Community } from '../../../core/shared/community.model';
|
||||||
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
|
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
||||||
import { ErrorComponent } from '../../error/error.component';
|
import { ErrorComponent } from '../../error/error.component';
|
||||||
import { ThemedLoadingComponent } from '../../loading/themed-loading.component';
|
|
||||||
import { getMockThemeService } from '../../mocks/theme-service.mock';
|
import { getMockThemeService } from '../../mocks/theme-service.mock';
|
||||||
import { ObjectCollectionComponent } from '../../object-collection/object-collection.component';
|
import { ObjectCollectionComponent } from '../../object-collection/object-collection.component';
|
||||||
import { createFailedRemoteDataObject } from '../../remote-data.utils';
|
import { createFailedRemoteDataObject } from '../../remote-data.utils';
|
||||||
import { ActivatedRouteStub } from '../../testing/active-router.stub';
|
import { ActivatedRouteStub } from '../../testing/active-router.stub';
|
||||||
import { QueryParamsDirectiveStub } from '../../testing/query-params-directive.stub';
|
import { QueryParamsDirectiveStub } from '../../testing/query-params-directive.stub';
|
||||||
|
import { SearchConfigurationServiceStub } from '../../testing/search-configuration-service.stub';
|
||||||
|
import { SearchServiceStub } from '../../testing/search-service.stub';
|
||||||
import { ThemeService } from '../../theme-support/theme.service';
|
import { ThemeService } from '../../theme-support/theme.service';
|
||||||
import { SearchExportCsvComponent } from '../search-export-csv/search-export-csv.component';
|
import { SearchExportCsvComponent } from '../search-export-csv/search-export-csv.component';
|
||||||
import { SearchResultsComponent } from './search-results.component';
|
import { SearchResultsComponent } from './search-results.component';
|
||||||
|
import { SearchResultsSkeletonComponent } from './search-results-skeleton/search-results-skeleton.component';
|
||||||
|
|
||||||
describe('SearchResultsComponent', () => {
|
describe('SearchResultsComponent', () => {
|
||||||
let comp: SearchResultsComponent;
|
let comp: SearchResultsComponent;
|
||||||
@@ -35,6 +39,11 @@ describe('SearchResultsComponent', () => {
|
|||||||
providers: [
|
providers: [
|
||||||
{ provide: ActivatedRoute, useValue: new ActivatedRouteStub() },
|
{ provide: ActivatedRoute, useValue: new ActivatedRouteStub() },
|
||||||
{ provide: ThemeService, useValue: getMockThemeService() },
|
{ provide: ThemeService, useValue: getMockThemeService() },
|
||||||
|
{ provide: SearchService, useValue: new SearchServiceStub() },
|
||||||
|
{
|
||||||
|
provide: SearchConfigurationService,
|
||||||
|
useValue: new SearchConfigurationServiceStub(),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
TranslateModule.forRoot(),
|
TranslateModule.forRoot(),
|
||||||
@@ -48,8 +57,8 @@ describe('SearchResultsComponent', () => {
|
|||||||
imports: [
|
imports: [
|
||||||
SearchExportCsvComponent,
|
SearchExportCsvComponent,
|
||||||
ObjectCollectionComponent,
|
ObjectCollectionComponent,
|
||||||
ThemedLoadingComponent,
|
|
||||||
ErrorComponent,
|
ErrorComponent,
|
||||||
|
SearchResultsSkeletonComponent,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
add: { imports: [QueryParamsDirectiveStub] },
|
add: { imports: [QueryParamsDirectiveStub] },
|
||||||
@@ -96,7 +105,7 @@ describe('SearchResultsComponent', () => {
|
|||||||
|
|
||||||
it('should display link with new search where query is quoted if search return a error 400', () => {
|
it('should display link with new search where query is quoted if search return a error 400', () => {
|
||||||
(comp as any).searchResults = createFailedRemoteDataObject('Error', 400);
|
(comp as any).searchResults = createFailedRemoteDataObject('Error', 400);
|
||||||
(comp as any).searchConfig = { query: 'foobar' };
|
(comp as any).searchConfig = { query: 'foobar', pagination: { pageSize: 10 } };
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const linkDes = fixture.debugElement.queryAll(By.directive(QueryParamsDirectiveStub));
|
const linkDes = fixture.debugElement.queryAll(By.directive(QueryParamsDirectiveStub));
|
||||||
|
@@ -1,4 +1,7 @@
|
|||||||
import { NgIf } from '@angular/common';
|
import {
|
||||||
|
AsyncPipe,
|
||||||
|
NgIf,
|
||||||
|
} from '@angular/common';
|
||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
EventEmitter,
|
EventEmitter,
|
||||||
@@ -7,12 +10,19 @@ import {
|
|||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
import {
|
||||||
|
BehaviorSubject,
|
||||||
|
Observable,
|
||||||
|
} from 'rxjs';
|
||||||
|
|
||||||
import { SortOptions } from '../../../core/cache/models/sort-options.model';
|
import { SortOptions } from '../../../core/cache/models/sort-options.model';
|
||||||
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
import { RemoteData } from '../../../core/data/remote-data';
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
import { Context } from '../../../core/shared/context.model';
|
import { Context } from '../../../core/shared/context.model';
|
||||||
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
import { DSpaceObject } from '../../../core/shared/dspace-object.model';
|
||||||
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
|
import { SearchConfigurationService } from '../../../core/shared/search/search-configuration.service';
|
||||||
import { ViewMode } from '../../../core/shared/view-mode.model';
|
import { ViewMode } from '../../../core/shared/view-mode.model';
|
||||||
import {
|
import {
|
||||||
fadeIn,
|
fadeIn,
|
||||||
@@ -23,13 +33,15 @@ import {
|
|||||||
isNotEmpty,
|
isNotEmpty,
|
||||||
} from '../../empty.util';
|
} from '../../empty.util';
|
||||||
import { ErrorComponent } from '../../error/error.component';
|
import { ErrorComponent } from '../../error/error.component';
|
||||||
import { ThemedLoadingComponent } from '../../loading/themed-loading.component';
|
|
||||||
import { CollectionElementLinkType } from '../../object-collection/collection-element-link.type';
|
import { CollectionElementLinkType } from '../../object-collection/collection-element-link.type';
|
||||||
import { ObjectCollectionComponent } from '../../object-collection/object-collection.component';
|
import { ObjectCollectionComponent } from '../../object-collection/object-collection.component';
|
||||||
import { ListableObject } from '../../object-collection/shared/listable-object.model';
|
import { ListableObject } from '../../object-collection/shared/listable-object.model';
|
||||||
|
import { AppliedFilter } from '../models/applied-filter.model';
|
||||||
import { PaginatedSearchOptions } from '../models/paginated-search-options.model';
|
import { PaginatedSearchOptions } from '../models/paginated-search-options.model';
|
||||||
|
import { SearchFilter } from '../models/search-filter.model';
|
||||||
import { SearchResult } from '../models/search-result.model';
|
import { SearchResult } from '../models/search-result.model';
|
||||||
import { SearchExportCsvComponent } from '../search-export-csv/search-export-csv.component';
|
import { SearchExportCsvComponent } from '../search-export-csv/search-export-csv.component';
|
||||||
|
import { SearchResultsSkeletonComponent } from './search-results-skeleton/search-results-skeleton.component';
|
||||||
|
|
||||||
export interface SelectionConfig {
|
export interface SelectionConfig {
|
||||||
repeatable: boolean;
|
repeatable: boolean;
|
||||||
@@ -39,12 +51,13 @@ export interface SelectionConfig {
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-base-search-results',
|
selector: 'ds-base-search-results',
|
||||||
templateUrl: './search-results.component.html',
|
templateUrl: './search-results.component.html',
|
||||||
|
styleUrls: ['./search-results.component.scss'],
|
||||||
animations: [
|
animations: [
|
||||||
fadeIn,
|
fadeIn,
|
||||||
fadeInOut,
|
fadeInOut,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [NgIf, SearchExportCsvComponent, ObjectCollectionComponent, ThemedLoadingComponent, ErrorComponent, RouterLink, TranslateModule],
|
imports: [NgIf, SearchExportCsvComponent, ObjectCollectionComponent, ErrorComponent, RouterLink, TranslateModule, SearchResultsSkeletonComponent, AsyncPipe, NgxSkeletonLoaderModule],
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,6 +65,15 @@ export interface SelectionConfig {
|
|||||||
*/
|
*/
|
||||||
export class SearchResultsComponent {
|
export class SearchResultsComponent {
|
||||||
hasNoValue = hasNoValue;
|
hasNoValue = hasNoValue;
|
||||||
|
/**
|
||||||
|
* Currently active filters in url
|
||||||
|
*/
|
||||||
|
activeFilters$: Observable<SearchFilter[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter applied to show labels, once populated the activeFilters$ will be loaded
|
||||||
|
*/
|
||||||
|
appliedFilters$: BehaviorSubject<AppliedFilter[]>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The link type of the listed search results
|
* The link type of the listed search results
|
||||||
@@ -125,10 +147,18 @@ export class SearchResultsComponent {
|
|||||||
|
|
||||||
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
@Output() selectObject: EventEmitter<ListableObject> = new EventEmitter<ListableObject>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected searchConfigService: SearchConfigurationService,
|
||||||
|
protected searchService: SearchService,
|
||||||
|
) {
|
||||||
|
this.activeFilters$ = this.searchConfigService.getCurrentFilters();
|
||||||
|
this.appliedFilters$ = this.searchService.appliedFilters$;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if search results are loading
|
* Check if search results are loading
|
||||||
*/
|
*/
|
||||||
isLoading() {
|
isLoading(): boolean {
|
||||||
return !this.showError() && (hasNoValue(this.searchResults) || hasNoValue(this.searchResults.payload) || this.searchResults.isLoading);
|
return !this.showError() && (hasNoValue(this.searchResults) || hasNoValue(this.searchResults.payload) || this.searchResults.isLoading);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
NO_ERRORS_SCHEMA,
|
NO_ERRORS_SCHEMA,
|
||||||
|
PLATFORM_ID,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ComponentFixture,
|
ComponentFixture,
|
||||||
@@ -246,6 +247,7 @@ export function configureSearchComponentTestingModule(compType, additionalDeclar
|
|||||||
},
|
},
|
||||||
{ provide: APP_DATA_SERVICES_MAP, useValue: {} },
|
{ provide: APP_DATA_SERVICES_MAP, useValue: {} },
|
||||||
{ provide: APP_CONFIG, useValue: environment },
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
|
{ provide: PLATFORM_ID, useValue: 'browser' },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA],
|
schemas: [NO_ERRORS_SCHEMA],
|
||||||
}).overrideComponent(compType, {
|
}).overrideComponent(compType, {
|
||||||
@@ -415,5 +417,34 @@ describe('SearchComponent', () => {
|
|||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('when rendered in SSR', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.platformId = 'server';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not call search method on init', (done) => {
|
||||||
|
comp.ngOnInit();
|
||||||
|
//Check that the first method from which the search depend upon is not being called
|
||||||
|
expect(searchConfigurationServiceStub.getCurrentConfiguration).not.toHaveBeenCalled();
|
||||||
|
comp.initialized$.subscribe((res) => {
|
||||||
|
expect(res).toBeTruthy();
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('when rendered in CSR', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
comp.platformId = 'browser';
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call search method on init', fakeAsync(() => {
|
||||||
|
comp.ngOnInit();
|
||||||
|
tick(100);
|
||||||
|
//Check that the last method from which the search depend upon is being called
|
||||||
|
expect(searchServiceStub.search).toHaveBeenCalled();
|
||||||
|
}));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
AsyncPipe,
|
AsyncPipe,
|
||||||
|
isPlatformServer,
|
||||||
NgIf,
|
NgIf,
|
||||||
NgTemplateOutlet,
|
NgTemplateOutlet,
|
||||||
} from '@angular/common';
|
} from '@angular/common';
|
||||||
@@ -12,6 +13,7 @@ import {
|
|||||||
OnDestroy,
|
OnDestroy,
|
||||||
OnInit,
|
OnInit,
|
||||||
Output,
|
Output,
|
||||||
|
PLATFORM_ID,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
NavigationStart,
|
NavigationStart,
|
||||||
@@ -37,6 +39,7 @@ import {
|
|||||||
APP_CONFIG,
|
APP_CONFIG,
|
||||||
AppConfig,
|
AppConfig,
|
||||||
} from '../../../config/app-config.interface';
|
} from '../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../environments/environment';
|
||||||
import { COLLECTION_MODULE_PATH } from '../../collection-page/collection-page-routing-paths';
|
import { COLLECTION_MODULE_PATH } from '../../collection-page/collection-page-routing-paths';
|
||||||
import { COMMUNITY_MODULE_PATH } from '../../community-page/community-page-routing-paths';
|
import { COMMUNITY_MODULE_PATH } from '../../community-page/community-page-routing-paths';
|
||||||
import { SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
@@ -236,6 +239,11 @@ export class SearchComponent implements OnDestroy, OnInit {
|
|||||||
*/
|
*/
|
||||||
@Input() hideScopeInUrl: boolean;
|
@Input() hideScopeInUrl: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines whether to fetch search results during SSR execution
|
||||||
|
*/
|
||||||
|
@Input() renderOnServerSide: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current configuration used during the search
|
* The current configuration used during the search
|
||||||
*/
|
*/
|
||||||
@@ -251,6 +259,7 @@ export class SearchComponent implements OnDestroy, OnInit {
|
|||||||
*/
|
*/
|
||||||
currentScope$: Observable<string>;
|
currentScope$: Observable<string>;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The current sort options used
|
* The current sort options used
|
||||||
*/
|
*/
|
||||||
@@ -345,6 +354,7 @@ export class SearchComponent implements OnDestroy, OnInit {
|
|||||||
protected routeService: RouteService,
|
protected routeService: RouteService,
|
||||||
protected router: Router,
|
protected router: Router,
|
||||||
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
|
@Inject(PLATFORM_ID) public platformId: any,
|
||||||
) {
|
) {
|
||||||
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
this.isXsOrSm$ = this.windowService.isXsOrSm();
|
||||||
}
|
}
|
||||||
@@ -357,6 +367,14 @@ export class SearchComponent implements OnDestroy, OnInit {
|
|||||||
* If something changes, update the list of scopes for the dropdown
|
* If something changes, update the list of scopes for the dropdown
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
if (!this.renderOnServerSide && !environment.ssr.enableSearchComponent && isPlatformServer(this.platformId)) {
|
||||||
|
this.subs.push(this.getSearchOptions().pipe(distinctUntilChanged()).subscribe((options) => {
|
||||||
|
this.searchOptions$.next(options);
|
||||||
|
}));
|
||||||
|
this.initialized$.next(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.useUniquePageId) {
|
if (this.useUniquePageId) {
|
||||||
// Create an unique pagination id related to the instance of the SearchComponent
|
// Create an unique pagination id related to the instance of the SearchComponent
|
||||||
this.paginationId = uniqueId(this.paginationId);
|
this.paginationId = uniqueId(this.paginationId);
|
||||||
|
@@ -26,6 +26,10 @@ export class SearchConfigurationServiceStub {
|
|||||||
return observableOf([]);
|
return observableOf([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCurrentFilters() {
|
||||||
|
return observableOf([]);
|
||||||
|
}
|
||||||
|
|
||||||
getCurrentScope(a) {
|
getCurrentScope(a) {
|
||||||
return observableOf('test-id');
|
return observableOf('test-id');
|
||||||
}
|
}
|
||||||
|
@@ -1,38 +1,39 @@
|
|||||||
<div class="mb-4 ccLicense-select">
|
@if (submissionCcLicenses) {
|
||||||
<ds-select
|
<div class="mb-4 ccLicense-select">
|
||||||
[disabled]="!submissionCcLicenses">
|
<div ngbDropdown>
|
||||||
|
<input id="cc-license-dropdown"
|
||||||
|
class="form-control"
|
||||||
|
[(ngModel)]="selectedCcLicense.name"
|
||||||
|
placeholder="{{ !storedCcLicenseLink ? ('submission.sections.ccLicense.select' | translate) : ('submission.sections.ccLicense.change' | translate)}}"
|
||||||
|
[ngModelOptions]="{standalone: true}"
|
||||||
|
ngbDropdownToggle
|
||||||
|
role="combobox"
|
||||||
|
#script="ngModel">
|
||||||
|
<div ngbDropdownMenu aria-labelledby="cc-license-dropdown" class="w-100 scrollable-menu"
|
||||||
|
role="menu"
|
||||||
|
infiniteScroll
|
||||||
|
(scroll)="onScroll($event)"
|
||||||
|
[infiniteScrollDistance]="5"
|
||||||
|
[infiniteScrollThrottle]="300"
|
||||||
|
[infiniteScrollUpDistance]="1.5"
|
||||||
|
[fromRoot]="true"
|
||||||
|
[scrollWindow]="false">
|
||||||
|
|
||||||
<ng-container class="selection">
|
@if(submissionCcLicenses?.length === 0) {
|
||||||
<span *ngIf="!submissionCcLicenses">
|
<button class="dropdown-item disabled">
|
||||||
<ds-loading></ds-loading>
|
|
||||||
</span>
|
|
||||||
<span *ngIf="getSelectedCcLicense()">
|
|
||||||
{{ getSelectedCcLicense().name }}
|
|
||||||
</span>
|
|
||||||
<span *ngIf="submissionCcLicenses && !getSelectedCcLicense()">
|
|
||||||
<ng-container *ngIf="storedCcLicenseLink">
|
|
||||||
{{ 'submission.sections.ccLicense.change' | translate }}
|
|
||||||
</ng-container>
|
|
||||||
<ng-container *ngIf="!storedCcLicenseLink">
|
|
||||||
{{ 'submission.sections.ccLicense.select' | translate }}
|
|
||||||
</ng-container>
|
|
||||||
</span>
|
|
||||||
</ng-container>
|
|
||||||
|
|
||||||
<ng-container class="menu">
|
|
||||||
<button *ngIf="submissionCcLicenses?.length === 0"
|
|
||||||
class="dropdown-item disabled">
|
|
||||||
{{ 'submission.sections.ccLicense.none' | translate }}
|
{{ 'submission.sections.ccLicense.none' | translate }}
|
||||||
</button>
|
</button>
|
||||||
<button *ngFor="let license of submissionCcLicenses"
|
} @else {
|
||||||
class="dropdown-item"
|
@for(license of submissionCcLicenses; track license.id) {
|
||||||
(click)="selectCcLicense(license)">
|
<button class="dropdown-item" (click)="selectCcLicense(license)">
|
||||||
{{ license.name }}
|
{{ license.name }}
|
||||||
</button>
|
</button>
|
||||||
</ng-container>
|
}
|
||||||
|
}
|
||||||
</ds-select>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<ng-container *ngIf="getSelectedCcLicense()">
|
<ng-container *ngIf="getSelectedCcLicense()">
|
||||||
|
|
||||||
|
@@ -1,3 +1,13 @@
|
|||||||
.options-select-menu {
|
.options-select-menu {
|
||||||
max-height: 25vh;
|
max-height: 25vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ccLicense-select {
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollable-menu {
|
||||||
|
height: auto;
|
||||||
|
max-height: var(--ds-dropdown-menu-max-height);
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
@@ -209,10 +209,10 @@ describe('SubmissionSectionCcLicensesComponent', () => {
|
|||||||
|
|
||||||
it('should display a dropdown with the different cc licenses', () => {
|
it('should display a dropdown with the different cc licenses', () => {
|
||||||
expect(
|
expect(
|
||||||
de.query(By.css('.ccLicense-select ds-select .dropdown-menu button:nth-child(1)')).nativeElement.innerText,
|
de.query(By.css('.ccLicense-select .scrollable-menu button:nth-child(1)')).nativeElement.innerText,
|
||||||
).toContain('test license name 1');
|
).toContain('test license name 1');
|
||||||
expect(
|
expect(
|
||||||
de.query(By.css('.ccLicense-select ds-select .dropdown-menu button:nth-child(2)')).nativeElement.innerText,
|
de.query(By.css('.ccLicense-select .scrollable-menu button:nth-child(2)')).nativeElement.innerText,
|
||||||
).toContain('test license name 2');
|
).toContain('test license name 2');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -226,9 +226,7 @@ describe('SubmissionSectionCcLicensesComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should display the selected cc license', () => {
|
it('should display the selected cc license', () => {
|
||||||
expect(
|
expect(component.selectedCcLicense.name).toContain('test license name 2');
|
||||||
de.query(By.css('.ccLicense-select ds-select button.selection')).nativeElement.innerText,
|
|
||||||
).toContain('test license name 2');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display all field labels of the selected cc license only', () => {
|
it('should display all field labels of the selected cc license only', () => {
|
||||||
|
@@ -4,14 +4,18 @@ import {
|
|||||||
NgIf,
|
NgIf,
|
||||||
} from '@angular/common';
|
} from '@angular/common';
|
||||||
import {
|
import {
|
||||||
|
ChangeDetectorRef,
|
||||||
Component,
|
Component,
|
||||||
Inject,
|
Inject,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
import {
|
import {
|
||||||
|
NgbDropdownModule,
|
||||||
NgbModal,
|
NgbModal,
|
||||||
NgbModalRef,
|
NgbModalRef,
|
||||||
} from '@ng-bootstrap/ng-bootstrap';
|
} from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
|
||||||
import {
|
import {
|
||||||
Observable,
|
Observable,
|
||||||
of as observableOf,
|
of as observableOf,
|
||||||
@@ -22,14 +26,16 @@ import {
|
|||||||
filter,
|
filter,
|
||||||
map,
|
map,
|
||||||
take,
|
take,
|
||||||
|
tap,
|
||||||
} from 'rxjs/operators';
|
} from 'rxjs/operators';
|
||||||
|
|
||||||
import { ConfigurationDataService } from '../../../core/data/configuration-data.service';
|
import { ConfigurationDataService } from '../../../core/data/configuration-data.service';
|
||||||
|
import { FindListOptions } from '../../../core/data/find-list-options.model';
|
||||||
import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner';
|
import { JsonPatchOperationPathCombiner } from '../../../core/json-patch/builder/json-patch-operation-path-combiner';
|
||||||
import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder';
|
import { JsonPatchOperationsBuilder } from '../../../core/json-patch/builder/json-patch-operations-builder';
|
||||||
import {
|
import {
|
||||||
getFirstCompletedRemoteData,
|
getFirstCompletedRemoteData,
|
||||||
getFirstSucceededRemoteData,
|
getFirstSucceededRemoteDataPayload,
|
||||||
getRemoteDataPayload,
|
getRemoteDataPayload,
|
||||||
} from '../../../core/shared/operators';
|
} from '../../../core/shared/operators';
|
||||||
import {
|
import {
|
||||||
@@ -64,6 +70,9 @@ import { SectionsType } from '../sections-type';
|
|||||||
VarDirective,
|
VarDirective,
|
||||||
NgForOf,
|
NgForOf,
|
||||||
DsSelectComponent,
|
DsSelectComponent,
|
||||||
|
NgbDropdownModule,
|
||||||
|
FormsModule,
|
||||||
|
InfiniteScrollModule,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
})
|
})
|
||||||
@@ -95,7 +104,7 @@ export class SubmissionSectionCcLicensesComponent extends SectionModelComponent
|
|||||||
/**
|
/**
|
||||||
* Cache of the available Creative Commons licenses.
|
* Cache of the available Creative Commons licenses.
|
||||||
*/
|
*/
|
||||||
submissionCcLicenses: SubmissionCcLicence[];
|
submissionCcLicenses: SubmissionCcLicence[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reference to NgbModal
|
* Reference to NgbModal
|
||||||
@@ -107,6 +116,25 @@ export class SubmissionSectionCcLicensesComponent extends SectionModelComponent
|
|||||||
*/
|
*/
|
||||||
defaultJurisdiction: string;
|
defaultJurisdiction: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The currently selected cc licence
|
||||||
|
*/
|
||||||
|
selectedCcLicense: SubmissionCcLicence = new SubmissionCcLicence();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for paginated data loading
|
||||||
|
*/
|
||||||
|
ccLicenceOptions: FindListOptions = {
|
||||||
|
elementsPerPage: 20,
|
||||||
|
currentPage: 1,
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Check to stop paginated search
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private _isLastPage: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Creative Commons link saved in the workspace item.
|
* The Creative Commons link saved in the workspace item.
|
||||||
*/
|
*/
|
||||||
@@ -131,6 +159,7 @@ export class SubmissionSectionCcLicensesComponent extends SectionModelComponent
|
|||||||
protected submissionCcLicenseUrlDataService: SubmissionCcLicenseUrlDataService,
|
protected submissionCcLicenseUrlDataService: SubmissionCcLicenseUrlDataService,
|
||||||
protected operationsBuilder: JsonPatchOperationsBuilder,
|
protected operationsBuilder: JsonPatchOperationsBuilder,
|
||||||
protected configService: ConfigurationDataService,
|
protected configService: ConfigurationDataService,
|
||||||
|
protected ref: ChangeDetectorRef,
|
||||||
@Inject('collectionIdProvider') public injectedCollectionId: string,
|
@Inject('collectionIdProvider') public injectedCollectionId: string,
|
||||||
@Inject('sectionDataProvider') public injectedSectionData: SectionDataObject,
|
@Inject('sectionDataProvider') public injectedSectionData: SectionDataObject,
|
||||||
@Inject('submissionIdProvider') public injectedSubmissionId: string,
|
@Inject('submissionIdProvider') public injectedSubmissionId: string,
|
||||||
@@ -154,9 +183,10 @@ export class SubmissionSectionCcLicensesComponent extends SectionModelComponent
|
|||||||
* @param ccLicense the Creative Commons license to select.
|
* @param ccLicense the Creative Commons license to select.
|
||||||
*/
|
*/
|
||||||
selectCcLicense(ccLicense: SubmissionCcLicence) {
|
selectCcLicense(ccLicense: SubmissionCcLicence) {
|
||||||
if (!!this.getSelectedCcLicense() && this.getSelectedCcLicense().id === ccLicense.id) {
|
if (this.selectedCcLicense.id === ccLicense.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.selectedCcLicense = ccLicense;
|
||||||
this.setAccepted(false);
|
this.setAccepted(false);
|
||||||
this.updateSectionData({
|
this.updateSectionData({
|
||||||
ccLicense: {
|
ccLicense: {
|
||||||
@@ -300,13 +330,6 @@ export class SubmissionSectionCcLicensesComponent extends SectionModelComponent
|
|||||||
}
|
}
|
||||||
this.sectionData.data = data;
|
this.sectionData.data = data;
|
||||||
}),
|
}),
|
||||||
this.submissionCcLicensesDataService.findAll({ elementsPerPage: 9999 }).pipe(
|
|
||||||
getFirstSucceededRemoteData(),
|
|
||||||
getRemoteDataPayload(),
|
|
||||||
map((list) => list.page),
|
|
||||||
).subscribe(
|
|
||||||
(licenses) => this.submissionCcLicenses = licenses,
|
|
||||||
),
|
|
||||||
this.configService.findByPropertyName('cc.license.jurisdiction').pipe(
|
this.configService.findByPropertyName('cc.license.jurisdiction').pipe(
|
||||||
getFirstCompletedRemoteData(),
|
getFirstCompletedRemoteData(),
|
||||||
getRemoteDataPayload(),
|
getRemoteDataPayload(),
|
||||||
@@ -319,6 +342,7 @@ export class SubmissionSectionCcLicensesComponent extends SectionModelComponent
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
this.loadCcLicences();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -338,4 +362,31 @@ export class SubmissionSectionCcLicensesComponent extends SectionModelComponent
|
|||||||
updateSectionData(data: WorkspaceitemSectionCcLicenseObject) {
|
updateSectionData(data: WorkspaceitemSectionCcLicenseObject) {
|
||||||
this.sectionService.updateSectionData(this.submissionId, this.sectionData.id, Object.assign({}, this.data, data));
|
this.sectionService.updateSectionData(this.submissionId, this.sectionData.id, Object.assign({}, this.data, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onScroll(event) {
|
||||||
|
if (event.target.scrollTop + event.target.clientHeight >= event.target.scrollHeight) {
|
||||||
|
if (!this.isLoading && !this._isLastPage) {
|
||||||
|
this.ccLicenceOptions.currentPage++;
|
||||||
|
this.loadCcLicences();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadCcLicences() {
|
||||||
|
this.isLoading = true;
|
||||||
|
|
||||||
|
this.subscriptions.push(
|
||||||
|
this.submissionCcLicensesDataService.findAll(this.ccLicenceOptions).pipe(
|
||||||
|
getFirstSucceededRemoteDataPayload(),
|
||||||
|
tap((response) => this._isLastPage = response.pageInfo.currentPage === response.pageInfo.totalPages),
|
||||||
|
map((list) => list.page),
|
||||||
|
).subscribe(
|
||||||
|
(licenses) => {
|
||||||
|
this.submissionCcLicenses = [...this.submissionCcLicenses, ...licenses];
|
||||||
|
this.isLoading = false;
|
||||||
|
this.ref.detectChanges();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1684,6 +1684,8 @@
|
|||||||
|
|
||||||
"deny-request-copy.success": "Successfully denied item request",
|
"deny-request-copy.success": "Successfully denied item request",
|
||||||
|
|
||||||
|
"dynamic-list.load-more": "Load more",
|
||||||
|
|
||||||
"dropdown.clear": "Clear selection",
|
"dropdown.clear": "Clear selection",
|
||||||
|
|
||||||
"dropdown.clear.tooltip": "Clear the selected option",
|
"dropdown.clear.tooltip": "Clear the selected option",
|
||||||
@@ -2292,6 +2294,8 @@
|
|||||||
|
|
||||||
"item.edit.bitstreams.upload-button": "Upload",
|
"item.edit.bitstreams.upload-button": "Upload",
|
||||||
|
|
||||||
|
"item.edit.bitstreams.load-more.link": "Load more",
|
||||||
|
|
||||||
"item.edit.delete.cancel": "Cancel",
|
"item.edit.delete.cancel": "Cancel",
|
||||||
|
|
||||||
"item.edit.delete.confirm": "Delete",
|
"item.edit.delete.confirm": "Delete",
|
||||||
@@ -5998,6 +6002,10 @@
|
|||||||
|
|
||||||
"person.orcid.registry.auth": "ORCID Authorizations",
|
"person.orcid.registry.auth": "ORCID Authorizations",
|
||||||
|
|
||||||
|
"person.orcid-tooltip.authenticated": "{{orcid}}",
|
||||||
|
|
||||||
|
"person.orcid-tooltip.not-authenticated": "{{orcid}} (unconfirmed)",
|
||||||
|
|
||||||
"home.recent-submissions.head": "Recent Submissions",
|
"home.recent-submissions.head": "Recent Submissions",
|
||||||
|
|
||||||
"listable-notification-object.default-message": "This object couldn't be retrieved",
|
"listable-notification-object.default-message": "This object couldn't be retrieved",
|
||||||
|
@@ -516,6 +516,7 @@ export class DefaultAppConfig implements AppConfig {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
filter: ['title', 'author', 'subject', 'entityType'],
|
filter: ['title', 'author', 'subject', 'entityType'],
|
||||||
},
|
},
|
||||||
|
filterPlaceholdersCount: 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
notifyMetrics: AdminNotifyMetricsRow[] = [
|
notifyMetrics: AdminNotifyMetricsRow[] = [
|
||||||
|
@@ -8,5 +8,11 @@ export interface SearchConfig extends Config {
|
|||||||
* Used by {@link UploadBitstreamComponent}.
|
* Used by {@link UploadBitstreamComponent}.
|
||||||
*/
|
*/
|
||||||
advancedFilters: AdvancedSearchConfig;
|
advancedFilters: AdvancedSearchConfig;
|
||||||
|
/**
|
||||||
|
* Number used to render n UI elements called loading skeletons that act as placeholders.
|
||||||
|
* These elements indicate that some content will be loaded in their stead.
|
||||||
|
* Since we don't know how many filters will be loaded before we receive a response from the server we use this parameter for the skeletons count.
|
||||||
|
* For instance if we set 5 then 5 loading skeletons will be visualized before the actual filters are retrieved.
|
||||||
|
*/
|
||||||
|
filterPlaceholdersCount?: number;
|
||||||
}
|
}
|
||||||
|
@@ -20,4 +20,19 @@ export interface SSRConfig extends Config {
|
|||||||
* For improved SSR performance, DSpace defaults this to false (disabled).
|
* For improved SSR performance, DSpace defaults this to false (disabled).
|
||||||
*/
|
*/
|
||||||
inlineCriticalCss: boolean;
|
inlineCriticalCss: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Paths to enable SSR for. Defaults to the home page and paths in the sitemap.
|
||||||
|
*/
|
||||||
|
paths: Array<string>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to enable rendering of search component on SSR
|
||||||
|
*/
|
||||||
|
enableSearchComponent: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to enable rendering of browse component on SSR
|
||||||
|
*/
|
||||||
|
enableBrowseComponent: boolean;
|
||||||
}
|
}
|
||||||
|
@@ -8,5 +8,8 @@ export const environment: Partial<BuildConfig> = {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
enablePerformanceProfiler: false,
|
enablePerformanceProfiler: false,
|
||||||
inlineCriticalCss: false,
|
inlineCriticalCss: false,
|
||||||
|
paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ],
|
||||||
|
enableSearchComponent: false,
|
||||||
|
enableBrowseComponent: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@@ -12,6 +12,9 @@ export const environment: BuildConfig = {
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
enablePerformanceProfiler: false,
|
enablePerformanceProfiler: false,
|
||||||
inlineCriticalCss: false,
|
inlineCriticalCss: false,
|
||||||
|
paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ],
|
||||||
|
enableSearchComponent: false,
|
||||||
|
enableBrowseComponent: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Angular express server settings.
|
// Angular express server settings.
|
||||||
|
@@ -13,6 +13,9 @@ export const environment: Partial<BuildConfig> = {
|
|||||||
enabled: false,
|
enabled: false,
|
||||||
enablePerformanceProfiler: false,
|
enablePerformanceProfiler: false,
|
||||||
inlineCriticalCss: false,
|
inlineCriticalCss: false,
|
||||||
|
paths: [ '/home', '/items/', '/entities/', '/collections/', '/communities/', '/bitstream/', '/bitstreams/', '/handle/' ],
|
||||||
|
enableSearchComponent: false,
|
||||||
|
enableBrowseComponent: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -220,7 +220,6 @@
|
|||||||
--bs-table-dark-hover-color: #{$table-dark-hover-color};
|
--bs-table-dark-hover-color: #{$table-dark-hover-color};
|
||||||
--bs-table-dark-hover-bg: #{$table-dark-hover-bg};
|
--bs-table-dark-hover-bg: #{$table-dark-hover-bg};
|
||||||
--bs-table-dark-border-color: #{$table-dark-border-color};
|
--bs-table-dark-border-color: #{$table-dark-border-color};
|
||||||
--bs-table-dark-color: #{$table-dark-color};
|
|
||||||
--bs-table-striped-order: #{$table-striped-order};
|
--bs-table-striped-order: #{$table-striped-order};
|
||||||
--bs-table-caption-color: #{$table-caption-color};
|
--bs-table-caption-color: #{$table-caption-color};
|
||||||
--bs-table-bg-level: #{$table-bg-level};
|
--bs-table-bg-level: #{$table-bg-level};
|
||||||
|
@@ -150,4 +150,16 @@
|
|||||||
|
|
||||||
--green1: #1c710a; // This variable represents the success color for the Orejime cookie banner
|
--green1: #1c710a; // This variable represents the success color for the Orejime cookie banner
|
||||||
--button-text-color-cookie: #fff; // This variable represents the text color for buttons in the Orejime cookie banner
|
--button-text-color-cookie: #fff; // This variable represents the text color for buttons in the Orejime cookie banner
|
||||||
|
|
||||||
|
--ds-search-skeleton-text-height: 20px;
|
||||||
|
--ds-search-skeleton-badge-height: 18px;
|
||||||
|
--ds-search-skeleton-thumbnail-margin: 1em;
|
||||||
|
--ds-search-skeleton-text-line-count: 2;
|
||||||
|
--ds-search-skeleton-badge-width: 75px;
|
||||||
|
--ds-search-skeleton-filter-badge-width: 200px;
|
||||||
|
--ds-search-skeleton-info-width: 200px;
|
||||||
|
--ds-search-skeleton-card-height: 435px;
|
||||||
|
|
||||||
|
--ds-filters-skeleton-height: 40px;
|
||||||
|
--ds-filters-skeleton-spacing: 12px;
|
||||||
}
|
}
|
||||||
|
@@ -5,18 +5,14 @@
|
|||||||
*
|
*
|
||||||
* https://www.atmire.com/software-license/
|
* https://www.atmire.com/software-license/
|
||||||
*/
|
*/
|
||||||
import {
|
import { AsyncPipe } from '@angular/common';
|
||||||
AsyncPipe,
|
|
||||||
NgFor,
|
|
||||||
NgIf,
|
|
||||||
} from '@angular/common';
|
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
|
||||||
import { SearchConfigurationService } from '../../../../../../app/core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../../../../../app/core/shared/search/search-configuration.service';
|
||||||
import { SEARCH_CONFIG_SERVICE } from '../../../../../../app/my-dspace-page/my-dspace-configuration.service';
|
import { SEARCH_CONFIG_SERVICE } from '../../../../../../app/my-dspace-page/my-dspace-configuration.service';
|
||||||
import { AdvancedSearchComponent } from '../../../../../../app/shared/search/advanced-search/advanced-search.component';
|
|
||||||
import { SearchFilterComponent } from '../../../../../../app/shared/search/search-filters/search-filter/search-filter.component';
|
import { SearchFilterComponent } from '../../../../../../app/shared/search/search-filters/search-filter/search-filter.component';
|
||||||
import { SearchFiltersComponent as BaseComponent } from '../../../../../../app/shared/search/search-filters/search-filters.component';
|
import { SearchFiltersComponent as BaseComponent } from '../../../../../../app/shared/search/search-filters/search-filters.component';
|
||||||
|
|
||||||
@@ -34,7 +30,7 @@ import { SearchFiltersComponent as BaseComponent } from '../../../../../../app/s
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [NgIf, NgFor, SearchFilterComponent, RouterLink, AsyncPipe, TranslateModule, AdvancedSearchComponent],
|
imports: [SearchFilterComponent, RouterLink, AsyncPipe, TranslateModule, NgxSkeletonLoaderModule],
|
||||||
})
|
})
|
||||||
|
|
||||||
export class SearchFiltersComponent extends BaseComponent {
|
export class SearchFiltersComponent extends BaseComponent {
|
||||||
|
@@ -1,29 +1,33 @@
|
|||||||
import { NgIf } from '@angular/common';
|
import {
|
||||||
|
AsyncPipe,
|
||||||
|
NgIf,
|
||||||
|
} from '@angular/common';
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { RouterLink } from '@angular/router';
|
import { RouterLink } from '@angular/router';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
fadeIn,
|
fadeIn,
|
||||||
fadeInOut,
|
fadeInOut,
|
||||||
} from '../../../../../../app/shared/animations/fade';
|
} from '../../../../../../app/shared/animations/fade';
|
||||||
import { ErrorComponent } from '../../../../../../app/shared/error/error.component';
|
import { ErrorComponent } from '../../../../../../app/shared/error/error.component';
|
||||||
import { ThemedLoadingComponent } from '../../../../../../app/shared/loading/themed-loading.component';
|
|
||||||
import { ObjectCollectionComponent } from '../../../../../../app/shared/object-collection/object-collection.component';
|
import { ObjectCollectionComponent } from '../../../../../../app/shared/object-collection/object-collection.component';
|
||||||
import { SearchExportCsvComponent } from '../../../../../../app/shared/search/search-export-csv/search-export-csv.component';
|
import { SearchExportCsvComponent } from '../../../../../../app/shared/search/search-export-csv/search-export-csv.component';
|
||||||
import { SearchResultsComponent as BaseComponent } from '../../../../../../app/shared/search/search-results/search-results.component';
|
import { SearchResultsComponent as BaseComponent } from '../../../../../../app/shared/search/search-results/search-results.component';
|
||||||
|
import { SearchResultsSkeletonComponent } from '../../../../../../app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-themed-search-results',
|
selector: 'ds-themed-search-results',
|
||||||
// templateUrl: './search-results.component.html',
|
// templateUrl: './search-results.component.html',
|
||||||
templateUrl: '../../../../../../app/shared/search/search-results/search-results.component.html',
|
templateUrl: '../../../../../../app/shared/search/search-results/search-results.component.html',
|
||||||
// styleUrls: ['./search-results.component.scss'],
|
styleUrls: ['../../../../../../app/shared/search/search-results/search-results.component.scss'],
|
||||||
animations: [
|
animations: [
|
||||||
fadeIn,
|
fadeIn,
|
||||||
fadeInOut,
|
fadeInOut,
|
||||||
],
|
],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [NgIf, SearchExportCsvComponent, ObjectCollectionComponent, ThemedLoadingComponent, ErrorComponent, RouterLink, TranslateModule],
|
imports: [NgIf, SearchExportCsvComponent, ObjectCollectionComponent, ErrorComponent, RouterLink, TranslateModule, SearchResultsSkeletonComponent, SearchResultsSkeletonComponent, AsyncPipe, NgxSkeletonLoaderModule],
|
||||||
})
|
})
|
||||||
export class SearchResultsComponent extends BaseComponent {
|
export class SearchResultsComponent extends BaseComponent {
|
||||||
|
|
||||||
|
@@ -12,6 +12,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to';
|
import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to';
|
||||||
|
|
||||||
import { RootModule } from '../../app/root.module';
|
import { RootModule } from '../../app/root.module';
|
||||||
|
import { SearchResultsSkeletonComponent } from '../../app/shared/search/search-results/search-results-skeleton/search-results-skeleton.component';
|
||||||
import { MetadataImportPageComponent } from './app/admin/admin-import-metadata-page/metadata-import-page.component';
|
import { MetadataImportPageComponent } from './app/admin/admin-import-metadata-page/metadata-import-page.component';
|
||||||
import { AdminSearchPageComponent } from './app/admin/admin-search-page/admin-search-page.component';
|
import { AdminSearchPageComponent } from './app/admin/admin-search-page/admin-search-page.component';
|
||||||
import { AdminSidebarComponent } from './app/admin/admin-sidebar/admin-sidebar.component';
|
import { AdminSidebarComponent } from './app/admin/admin-sidebar/admin-sidebar.component';
|
||||||
@@ -105,6 +106,7 @@ import { WorkflowItemDeleteComponent } from './app/workflowitems-edit-page/workf
|
|||||||
import { WorkflowItemSendBackComponent } from './app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component';
|
import { WorkflowItemSendBackComponent } from './app/workflowitems-edit-page/workflow-item-send-back/workflow-item-send-back.component';
|
||||||
import { WorkspaceItemsDeletePageComponent } from './app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component';
|
import { WorkspaceItemsDeletePageComponent } from './app/workspaceitems-edit-page/workspaceitems-delete-page/workspaceitems-delete-page.component';
|
||||||
|
|
||||||
|
|
||||||
const DECLARATIONS = [
|
const DECLARATIONS = [
|
||||||
FileSectionComponent,
|
FileSectionComponent,
|
||||||
HomePageComponent,
|
HomePageComponent,
|
||||||
@@ -198,6 +200,7 @@ const DECLARATIONS = [
|
|||||||
ComcolPageContentComponent,
|
ComcolPageContentComponent,
|
||||||
AdminSearchPageComponent,
|
AdminSearchPageComponent,
|
||||||
AdminWorkflowPageComponent,
|
AdminWorkflowPageComponent,
|
||||||
|
SearchResultsSkeletonComponent,
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
/* set the next two properties as `--ds-header-navbar-border-bottom-*`
|
/* set the next two properties as `--ds-header-navbar-border-bottom-*`
|
||||||
in order to keep the bottom border of the header when navbar is expanded */
|
in order to keep the bottom border of the header when navbar is expanded */
|
||||||
|
|
||||||
--ds-expandable-navbar-border-top-color: #{$white};
|
--ds-expandable-navbar-border-top-color: #{$white};
|
||||||
--ds-expandable-navbar-border-top-height: 0;
|
--ds-expandable-navbar-border-top-height: 0;
|
||||||
--ds-expandable-navbar-padding-top: 0;
|
--ds-expandable-navbar-padding-top: 0;
|
||||||
|
Reference in New Issue
Block a user