mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
Merge remote-tracking branch 'atmire/feature-process_polling' into process-admin-ui-redesign-8.0.0-next
This commit is contained in:
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -33,6 +33,8 @@ jobs:
|
|||||||
#CHROME_VERSION: "90.0.4430.212-1"
|
#CHROME_VERSION: "90.0.4430.212-1"
|
||||||
# Bump Node heap size (OOM in CI after upgrading to Angular 15)
|
# Bump Node heap size (OOM in CI after upgrading to Angular 15)
|
||||||
NODE_OPTIONS: '--max-old-space-size=4096'
|
NODE_OPTIONS: '--max-old-space-size=4096'
|
||||||
|
# Project name to use when running docker-compose prior to e2e tests
|
||||||
|
COMPOSE_PROJECT_NAME: 'ci'
|
||||||
strategy:
|
strategy:
|
||||||
# Create a matrix of Node versions to test against (in parallel)
|
# Create a matrix of Node versions to test against (in parallel)
|
||||||
matrix:
|
matrix:
|
||||||
|
@@ -5,9 +5,9 @@ describe('Browse By Author', () => {
|
|||||||
cy.visit('/browse/author');
|
cy.visit('/browse/author');
|
||||||
|
|
||||||
// Wait for <ds-browse-by-metadata-page> to be visible
|
// Wait for <ds-browse-by-metadata-page> to be visible
|
||||||
cy.get('ds-browse-by-metadata-page').should('be.visible');
|
cy.get('ds-browse-by-metadata').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-browse-by-metadata-page> for accessibility
|
// Analyze <ds-browse-by-metadata-page> for accessibility
|
||||||
testA11y('ds-browse-by-metadata-page');
|
testA11y('ds-browse-by-metadata');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -5,9 +5,9 @@ describe('Browse By Date Issued', () => {
|
|||||||
cy.visit('/browse/dateissued');
|
cy.visit('/browse/dateissued');
|
||||||
|
|
||||||
// Wait for <ds-browse-by-date-page> to be visible
|
// Wait for <ds-browse-by-date-page> to be visible
|
||||||
cy.get('ds-browse-by-date-page').should('be.visible');
|
cy.get('ds-browse-by-date').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-browse-by-date-page> for accessibility
|
// Analyze <ds-browse-by-date-page> for accessibility
|
||||||
testA11y('ds-browse-by-date-page');
|
testA11y('ds-browse-by-date');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -5,9 +5,9 @@ describe('Browse By Subject', () => {
|
|||||||
cy.visit('/browse/subject');
|
cy.visit('/browse/subject');
|
||||||
|
|
||||||
// Wait for <ds-browse-by-metadata-page> to be visible
|
// Wait for <ds-browse-by-metadata-page> to be visible
|
||||||
cy.get('ds-browse-by-metadata-page').should('be.visible');
|
cy.get('ds-browse-by-metadata').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-browse-by-metadata-page> for accessibility
|
// Analyze <ds-browse-by-metadata-page> for accessibility
|
||||||
testA11y('ds-browse-by-metadata-page');
|
testA11y('ds-browse-by-metadata');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -5,9 +5,9 @@ describe('Browse By Title', () => {
|
|||||||
cy.visit('/browse/title');
|
cy.visit('/browse/title');
|
||||||
|
|
||||||
// Wait for <ds-browse-by-title-page> to be visible
|
// Wait for <ds-browse-by-title-page> to be visible
|
||||||
cy.get('ds-browse-by-title-page').should('be.visible');
|
cy.get('ds-browse-by-title').should('be.visible');
|
||||||
|
|
||||||
// Analyze <ds-browse-by-title-page> for accessibility
|
// Analyze <ds-browse-by-title-page> for accessibility
|
||||||
testA11y('ds-browse-by-title-page');
|
testA11y('ds-browse-by-title');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -14,13 +14,8 @@
|
|||||||
# Therefore, it should be kept in sync with that file
|
# Therefore, it should be kept in sync with that file
|
||||||
version: "3.7"
|
version: "3.7"
|
||||||
|
|
||||||
networks:
|
|
||||||
dspacenet:
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
dspace-cli:
|
dspace-cli:
|
||||||
networks:
|
|
||||||
dspacenet: {}
|
|
||||||
environment:
|
environment:
|
||||||
# This assetstore zip is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
|
# This assetstore zip is available from https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data
|
||||||
- LOADASSETS=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/assetstore.tar.gz
|
- LOADASSETS=https://github.com/DSpace-Labs/AIP-Files/releases/download/demo-entities-data/assetstore.tar.gz
|
||||||
|
@@ -13,7 +13,13 @@
|
|||||||
#
|
#
|
||||||
# Therefore, it should be kept in sync with that file
|
# Therefore, it should be kept in sync with that file
|
||||||
version: "3.7"
|
version: "3.7"
|
||||||
|
networks:
|
||||||
|
# Default to using network named 'dspacenet' from docker-compose-rest.yml.
|
||||||
|
# Its full name will be prepended with the project name (e.g. "-p d7" means it will be named "d7_dspacenet")
|
||||||
|
# If COMPOSITE_PROJECT_NAME is missing, default value will be "docker" (name of folder this file is in)
|
||||||
|
default:
|
||||||
|
name: ${COMPOSE_PROJECT_NAME:-docker}_dspacenet
|
||||||
|
external: true
|
||||||
services:
|
services:
|
||||||
dspace-cli:
|
dspace-cli:
|
||||||
image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-latest}"
|
image: "${DOCKER_OWNER:-dspace}/dspace-cli:${DSPACE_VER:-latest}"
|
||||||
@@ -30,16 +36,12 @@ services:
|
|||||||
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
|
# solr.server: Ensure we are using the 'dspacesolr' image for Solr
|
||||||
solr__P__server: http://dspacesolr:8983/solr
|
solr__P__server: http://dspacesolr:8983/solr
|
||||||
volumes:
|
volumes:
|
||||||
- "assetstore:/dspace/assetstore"
|
# Keep DSpace assetstore directory between reboots
|
||||||
|
- assetstore:/dspace/assetstore
|
||||||
entrypoint: /dspace/bin/dspace
|
entrypoint: /dspace/bin/dspace
|
||||||
command: help
|
command: help
|
||||||
networks:
|
|
||||||
- dspacenet
|
|
||||||
tty: true
|
tty: true
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
assetstore:
|
assetstore:
|
||||||
|
|
||||||
networks:
|
|
||||||
dspacenet:
|
|
||||||
|
@@ -33,11 +33,11 @@ services:
|
|||||||
# Tell Statistics to commit all views immediately instead of waiting on Solr's autocommit.
|
# Tell Statistics to commit all views immediately instead of waiting on Solr's autocommit.
|
||||||
# This allows us to generate statistics in e2e tests so that statistics pages can be tested thoroughly.
|
# This allows us to generate statistics in e2e tests so that statistics pages can be tested thoroughly.
|
||||||
solr__D__statistics__P__autoCommit: 'false'
|
solr__D__statistics__P__autoCommit: 'false'
|
||||||
|
image: "${DOCKER_OWNER:-dspace}/dspace:${DSPACE_VER:-latest-test}"
|
||||||
depends_on:
|
depends_on:
|
||||||
- dspacedb
|
- dspacedb
|
||||||
image: dspace/dspace:latest-test
|
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
- dspacenet
|
||||||
ports:
|
ports:
|
||||||
- published: 8080
|
- published: 8080
|
||||||
target: 8080
|
target: 8080
|
||||||
@@ -45,8 +45,6 @@ services:
|
|||||||
tty: true
|
tty: true
|
||||||
volumes:
|
volumes:
|
||||||
- assetstore:/dspace/assetstore
|
- assetstore:/dspace/assetstore
|
||||||
# Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below)
|
|
||||||
- solr_configs:/dspace/solr
|
|
||||||
# Ensure that the database is ready BEFORE starting tomcat
|
# Ensure that the database is ready BEFORE starting tomcat
|
||||||
# 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep
|
# 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep
|
||||||
# 2. Then, run database migration to init database tables (including any out-of-order ignored migrations, if any)
|
# 2. Then, run database migration to init database tables (including any out-of-order ignored migrations, if any)
|
||||||
@@ -70,21 +68,18 @@ services:
|
|||||||
PGDATA: /pgdata
|
PGDATA: /pgdata
|
||||||
image: dspace/dspace-postgres-pgcrypto:loadsql
|
image: dspace/dspace-postgres-pgcrypto:loadsql
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
- dspacenet
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
volumes:
|
volumes:
|
||||||
|
# Keep Postgres data directory between reboots
|
||||||
- pgdata:/pgdata
|
- pgdata:/pgdata
|
||||||
# DSpace Solr container
|
# DSpace Solr container
|
||||||
dspacesolr:
|
dspacesolr:
|
||||||
container_name: dspacesolr
|
container_name: dspacesolr
|
||||||
# Uses official Solr image at https://hub.docker.com/_/solr/
|
image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-latest}"
|
||||||
image: solr:8.11-slim
|
|
||||||
# Needs main 'dspace' container to start first to guarantee access to solr_configs
|
|
||||||
depends_on:
|
|
||||||
- dspace
|
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
- dspacenet
|
||||||
ports:
|
ports:
|
||||||
- published: 8983
|
- published: 8983
|
||||||
target: 8983
|
target: 8983
|
||||||
@@ -92,9 +87,6 @@ services:
|
|||||||
tty: true
|
tty: true
|
||||||
working_dir: /var/solr/data
|
working_dir: /var/solr/data
|
||||||
volumes:
|
volumes:
|
||||||
# Mount our "solr_configs" volume available under the Solr's configsets folder (in a 'dspace' subfolder)
|
|
||||||
# This copies the Solr configs from main 'dspace' container into 'dspacesolr' via that volume
|
|
||||||
- solr_configs:/opt/solr/server/solr/configsets/dspace
|
|
||||||
# Keep Solr data directory between reboots
|
# Keep Solr data directory between reboots
|
||||||
- solr_data:/var/solr/data
|
- solr_data:/var/solr/data
|
||||||
# Initialize all DSpace Solr cores using the mounted configsets (see above), then start Solr
|
# Initialize all DSpace Solr cores using the mounted configsets (see above), then start Solr
|
||||||
@@ -103,14 +95,18 @@ services:
|
|||||||
- '-c'
|
- '-c'
|
||||||
- |
|
- |
|
||||||
init-var-solr
|
init-var-solr
|
||||||
precreate-core authority /opt/solr/server/solr/configsets/dspace/authority
|
precreate-core authority /opt/solr/server/solr/configsets/authority
|
||||||
precreate-core oai /opt/solr/server/solr/configsets/dspace/oai
|
cp -r /opt/solr/server/solr/configsets/authority/* authority
|
||||||
precreate-core search /opt/solr/server/solr/configsets/dspace/search
|
precreate-core oai /opt/solr/server/solr/configsets/oai
|
||||||
precreate-core statistics /opt/solr/server/solr/configsets/dspace/statistics
|
cp -r /opt/solr/server/solr/configsets/oai/* oai
|
||||||
|
precreate-core search /opt/solr/server/solr/configsets/search
|
||||||
|
cp -r /opt/solr/server/solr/configsets/search/* search
|
||||||
|
precreate-core statistics /opt/solr/server/solr/configsets/statistics
|
||||||
|
cp -r /opt/solr/server/solr/configsets/statistics/* statistics
|
||||||
|
precreate-core qaevent /opt/solr/server/solr/configsets/qaevent
|
||||||
|
cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent
|
||||||
exec solr -f
|
exec solr -f
|
||||||
volumes:
|
volumes:
|
||||||
assetstore:
|
assetstore:
|
||||||
pgdata:
|
pgdata:
|
||||||
solr_data:
|
solr_data:
|
||||||
# Special volume used to share Solr configs from 'dspace' to 'dspacesolr' container (see above)
|
|
||||||
solr_configs:
|
|
@@ -43,7 +43,7 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- dspacedb
|
- dspacedb
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
- dspacenet
|
||||||
ports:
|
ports:
|
||||||
- published: 8080
|
- published: 8080
|
||||||
target: 8080
|
target: 8080
|
||||||
@@ -51,8 +51,6 @@ services:
|
|||||||
tty: true
|
tty: true
|
||||||
volumes:
|
volumes:
|
||||||
- assetstore:/dspace/assetstore
|
- assetstore:/dspace/assetstore
|
||||||
# Mount DSpace's solr configs to a volume, so that we can share to 'dspacesolr' container (see below)
|
|
||||||
- solr_configs:/dspace/solr
|
|
||||||
# Ensure that the database is ready BEFORE starting tomcat
|
# Ensure that the database is ready BEFORE starting tomcat
|
||||||
# 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep
|
# 1. While a TCP connection to dspacedb port 5432 is not available, continue to sleep
|
||||||
# 2. Then, run database migration to init database tables
|
# 2. Then, run database migration to init database tables
|
||||||
@@ -69,25 +67,23 @@ services:
|
|||||||
container_name: dspacedb
|
container_name: dspacedb
|
||||||
environment:
|
environment:
|
||||||
PGDATA: /pgdata
|
PGDATA: /pgdata
|
||||||
image: dspace/dspace-postgres-pgcrypto
|
image: "${DOCKER_OWNER:-dspace}/dspace-postgres-pgcrypto:${DSPACE_VER:-latest}"
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
- dspacenet
|
||||||
ports:
|
ports:
|
||||||
- published: 5432
|
- published: 5432
|
||||||
target: 5432
|
target: 5432
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
volumes:
|
volumes:
|
||||||
|
# Keep Postgres data directory between reboots
|
||||||
- pgdata:/pgdata
|
- pgdata:/pgdata
|
||||||
# DSpace Solr container
|
# DSpace Solr container
|
||||||
dspacesolr:
|
dspacesolr:
|
||||||
container_name: dspacesolr
|
container_name: dspacesolr
|
||||||
image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-latest}"
|
image: "${DOCKER_OWNER:-dspace}/dspace-solr:${DSPACE_VER:-latest}"
|
||||||
# Needs main 'dspace' container to start first to guarantee access to solr_configs
|
|
||||||
depends_on:
|
|
||||||
- dspace
|
|
||||||
networks:
|
networks:
|
||||||
dspacenet:
|
- dspacenet
|
||||||
ports:
|
ports:
|
||||||
- published: 8983
|
- published: 8983
|
||||||
target: 8983
|
target: 8983
|
||||||
@@ -115,10 +111,10 @@ services:
|
|||||||
cp -r /opt/solr/server/solr/configsets/search/* search
|
cp -r /opt/solr/server/solr/configsets/search/* search
|
||||||
precreate-core statistics /opt/solr/server/solr/configsets/statistics
|
precreate-core statistics /opt/solr/server/solr/configsets/statistics
|
||||||
cp -r /opt/solr/server/solr/configsets/statistics/* statistics
|
cp -r /opt/solr/server/solr/configsets/statistics/* statistics
|
||||||
|
precreate-core qaevent /opt/solr/server/solr/configsets/qaevent
|
||||||
|
cp -r /opt/solr/server/solr/configsets/qaevent/* qaevent
|
||||||
exec solr -f
|
exec solr -f
|
||||||
volumes:
|
volumes:
|
||||||
assetstore:
|
assetstore:
|
||||||
pgdata:
|
pgdata:
|
||||||
solr_data:
|
solr_data:
|
||||||
# Special volume used to share Solr configs from 'dspace' to 'dspacesolr' container (see above)
|
|
||||||
solr_configs:
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
|
<h1>{{ 'admin.access-control.bulk-access.title' | translate }}</h1>
|
||||||
<ds-bulk-access-browse [listId]="listId"></ds-bulk-access-browse>
|
<ds-bulk-access-browse [listId]="listId"></ds-bulk-access-browse>
|
||||||
<div class="clearfix mb-3"></div>
|
<div class="clearfix mb-3"></div>
|
||||||
<ds-bulk-access-settings #dsBulkSettings ></ds-bulk-access-settings>
|
<ds-bulk-access-settings #dsBulkSettings ></ds-bulk-access-settings>
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<h2 id="header">{{'admin.batch-import.page.header' | translate}}</h2>
|
<h1 id="header">{{'admin.batch-import.page.header' | translate}}</h1>
|
||||||
<p>{{'admin.batch-import.page.help' | translate}}</p>
|
<p>{{'admin.batch-import.page.help' | translate}}</p>
|
||||||
<p *ngIf="dso">
|
<p *ngIf="dso">
|
||||||
selected collection: <b>{{getDspaceObjectName()}}</b>
|
selected collection: <b>{{getDspaceObjectName()}}</b>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<small class="form-text text-muted">
|
<small class="form-text text-muted">
|
||||||
{{'admin.batch-import.page.toggle.help' | translate}}
|
{{'admin.batch-import.page.toggle.help' | translate}}
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
|
||||||
<ds-file-dropzone-no-uploader
|
<ds-file-dropzone-no-uploader
|
||||||
*ngIf="isUpload"
|
*ngIf="isUpload"
|
||||||
|
@@ -19,7 +19,7 @@
|
|||||||
<table id="formats" class="table table-striped table-hover">
|
<table id="formats" class="table table-striped table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col" [attr.aria-label]="'admin.registries.bitstream-formats.select' | translate"></th>
|
<th scope="col"><span class="sr-only">{{'admin.registries.bitstream-formats.table.selected' | translate}}</span></th>
|
||||||
<th scope="col">{{'admin.registries.bitstream-formats.table.id' | translate}}</th>
|
<th scope="col">{{'admin.registries.bitstream-formats.table.id' | translate}}</th>
|
||||||
<th scope="col">{{'admin.registries.bitstream-formats.table.name' | translate}}</th>
|
<th scope="col">{{'admin.registries.bitstream-formats.table.name' | translate}}</th>
|
||||||
<th scope="col">{{'admin.registries.bitstream-formats.table.mimetype' | translate}}</th>
|
<th scope="col">{{'admin.registries.bitstream-formats.table.mimetype' | translate}}</th>
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
[checked]="isSelected(bitstreamFormat) | async"
|
[checked]="isSelected(bitstreamFormat) | async"
|
||||||
(change)="selectBitStreamFormat(bitstreamFormat, $event)"
|
(change)="selectBitStreamFormat(bitstreamFormat, $event)"
|
||||||
>
|
>
|
||||||
|
<span class="sr-only">{{'admin.registries.bitstream-formats.select' | translate}}}</span>
|
||||||
</label>
|
</label>
|
||||||
</td>
|
</td>
|
||||||
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.id}}</a></td>
|
<td><a [routerLink]="['/admin/registries/bitstream-formats', bitstreamFormat.id, 'edit']">{{bitstreamFormat.id}}</a></td>
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div class="metadata-registry row">
|
<div class="metadata-registry row">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
|
||||||
<h2 id="header" class="border-bottom pb-2">{{'admin.registries.metadata.head' | translate}}</h2>
|
<h1 id="header" class="border-bottom pb-2">{{'admin.registries.metadata.head' | translate}}</h1>
|
||||||
|
|
||||||
<p id="description" class="pb-2">{{'admin.registries.metadata.description' | translate}}</p>
|
<p id="description" class="pb-2">{{'admin.registries.metadata.description' | translate}}</p>
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<table id="metadata-schemas" class="table table-striped table-hover">
|
<table id="metadata-schemas" class="table table-striped table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col"></th>
|
<th scope="col"><span class="sr-only">{{'admin.registries.metadata.schemas.table.selected' | translate}}</span></th>
|
||||||
<th scope="col">{{'admin.registries.metadata.schemas.table.id' | translate}}</th>
|
<th scope="col">{{'admin.registries.metadata.schemas.table.id' | translate}}</th>
|
||||||
<th scope="col">{{'admin.registries.metadata.schemas.table.namespace' | translate}}</th>
|
<th scope="col">{{'admin.registries.metadata.schemas.table.namespace' | translate}}</th>
|
||||||
<th scope="col">{{'admin.registries.metadata.schemas.table.name' | translate}}</th>
|
<th scope="col">{{'admin.registries.metadata.schemas.table.name' | translate}}</th>
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
[checked]="isSelected(schema) | async"
|
[checked]="isSelected(schema) | async"
|
||||||
(change)="selectMetadataSchema(schema, $event)"
|
(change)="selectMetadataSchema(schema, $event)"
|
||||||
>
|
>
|
||||||
|
<span class="sr-only">{{((isSelected(schema) | async) ? 'admin.registries.metadata.schemas.deselect' : 'admin.registries.metadata.schemas.select') | translate}}</span>
|
||||||
</label>
|
</label>
|
||||||
</td>
|
</td>
|
||||||
<td class="selectable-row" (click)="editSchema(schema)"><a [routerLink]="[schema.prefix]">{{schema.id}}</a></td>
|
<td class="selectable-row" (click)="editSchema(schema)"><a [routerLink]="[schema.prefix]">{{schema.id}}</a></td>
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
<div *ngIf="registryService.getActiveMetadataSchema() | async; then editheader; else createHeader"></div>
|
<div *ngIf="registryService.getActiveMetadataSchema() | async; then editheader; else createHeader"></div>
|
||||||
|
|
||||||
<ng-template #createHeader>
|
<ng-template #createHeader>
|
||||||
<h4>{{messagePrefix + '.create' | translate}}</h4>
|
<h2>{{messagePrefix + '.create' | translate}}</h2>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #editheader>
|
<ng-template #editheader>
|
||||||
<h4>{{messagePrefix + '.edit' | translate}}</h4>
|
<h2>{{messagePrefix + '.edit' | translate}}</h2>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ds-form [formId]="formId"
|
<ds-form [formId]="formId"
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
<div *ngIf="registryService.getActiveMetadataField() | async; then editheader; else createHeader"></div>
|
<div *ngIf="registryService.getActiveMetadataField() | async; then editheader; else createHeader"></div>
|
||||||
|
|
||||||
<ng-template #createHeader>
|
<ng-template #createHeader>
|
||||||
<h4>{{messagePrefix + '.create' | translate}}</h4>
|
<h2>{{messagePrefix + '.create' | translate}}</h2>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #editheader>
|
<ng-template #editheader>
|
||||||
<h4>{{messagePrefix + '.edit' | translate}}</h4>
|
<h2>{{messagePrefix + '.edit' | translate}}</h2>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ds-form [formId]="formId"
|
<ds-form [formId]="formId"
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div class="metadata-schema row">
|
<div class="metadata-schema row">
|
||||||
<div class="col-12" *ngVar="(metadataSchema$ | async) as schema">
|
<div class="col-12" *ngVar="(metadataSchema$ | async) as schema">
|
||||||
|
|
||||||
<h2 id="header" class="border-bottom pb-2">{{'admin.registries.schema.head' | translate}}: "{{schema?.prefix}}"</h2>
|
<h1 id="header" class="border-bottom pb-2">{{'admin.registries.schema.head' | translate}}: "{{schema?.prefix}}"</h1>
|
||||||
|
|
||||||
<p id="description" class="pb-2">{{'admin.registries.schema.description' | translate:{ namespace: schema?.namespace } }}</p>
|
<p id="description" class="pb-2">{{'admin.registries.schema.description' | translate:{ namespace: schema?.namespace } }}</p>
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
[metadataSchema]="schema"
|
[metadataSchema]="schema"
|
||||||
(submitForm)="forceUpdateFields()"></ds-metadata-field-form>
|
(submitForm)="forceUpdateFields()"></ds-metadata-field-form>
|
||||||
|
|
||||||
<h3>{{'admin.registries.schema.fields.head' | translate}}</h3>
|
<h2>{{'admin.registries.schema.fields.head' | translate}}</h2>
|
||||||
|
|
||||||
<ng-container *ngVar="(metadataFields$ | async)?.payload as fields">
|
<ng-container *ngVar="(metadataFields$ | async)?.payload as fields">
|
||||||
<ds-pagination
|
<ds-pagination
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
<table id="metadata-fields" class="table table-striped table-hover">
|
<table id="metadata-fields" class="table table-striped table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th></th>
|
<th><span class="sr-only">{{'admin.registries.schema.fields.table.selected' | translate}}</span></th>
|
||||||
<th scope="col">{{'admin.registries.schema.fields.table.id' | translate}}</th>
|
<th scope="col">{{'admin.registries.schema.fields.table.id' | translate}}</th>
|
||||||
<th scope="col">{{'admin.registries.schema.fields.table.field' | translate}}</th>
|
<th scope="col">{{'admin.registries.schema.fields.table.field' | translate}}</th>
|
||||||
<th scope="col">{{'admin.registries.schema.fields.table.scopenote' | translate}}</th>
|
<th scope="col">{{'admin.registries.schema.fields.table.scopenote' | translate}}</th>
|
||||||
@@ -33,12 +33,11 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let field of fields?.page"
|
<tr *ngFor="let field of fields?.page"
|
||||||
[ngClass]="{'table-primary' : isActive(field) | async}">
|
[ngClass]="{'table-primary' : isActive(field) | async}">
|
||||||
<td>
|
<td *ngVar="(isSelected(field) | async) as selected">
|
||||||
<label class="mb-0">
|
<input type="checkbox"
|
||||||
<input type="checkbox"
|
[attr.aria-label]="(selected ? 'admin.registries.schema.fields.deselect' : 'admin.registries.schema.fields.select') | translate"
|
||||||
[checked]="isSelected(field) | async"
|
[checked]="selected"
|
||||||
(change)="selectMetadataField(field, $event)">
|
(change)="selectMetadataField(field, $event)">
|
||||||
</label>
|
|
||||||
</td>
|
</td>
|
||||||
<td class="selectable-row" (click)="editField(field)">{{field.id}}</td>
|
<td class="selectable-row" (click)="editField(field)">{{field.id}}</td>
|
||||||
<td class="selectable-row" (click)="editField(field)">{{schema?.prefix}}.{{field.element}}{{field.qualifier ? '.' + field.qualifier : ''}}</td>
|
<td class="selectable-row" (click)="editField(field)">{{schema?.prefix}}.{{field.element}}{{field.qualifier ? '.' + field.qualifier : ''}}</td>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<ng-template dsListableObject>
|
<ng-template dsDynamicComponentLoader>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div #badges>
|
<div #badges>
|
||||||
<ng-content></ng-content>
|
<ng-content></ng-content>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, ComponentFactoryResolver, ElementRef, OnInit, ViewChild } from '@angular/core';
|
import { Component, ComponentRef, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||||
import {
|
import {
|
||||||
@@ -11,9 +11,10 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g
|
|||||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||||
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
|
||||||
import { GenericConstructor } from '../../../../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../../../../core/shared/generic-constructor';
|
||||||
import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive';
|
import { DynamicComponentLoaderDirective } from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive';
|
||||||
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
|
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
|
||||||
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { hasValue } from '../../../../../shared/empty.util';
|
||||||
|
|
||||||
@listableObjectComponent(ItemSearchResult, ViewMode.GridElement, Context.AdminSearch)
|
@listableObjectComponent(ItemSearchResult, ViewMode.GridElement, Context.AdminSearch)
|
||||||
@Component({
|
@Component({
|
||||||
@@ -24,17 +25,18 @@ import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service
|
|||||||
/**
|
/**
|
||||||
* The component for displaying a list element for an item search result on the admin search page
|
* The component for displaying a list element for an item search result on the admin search page
|
||||||
*/
|
*/
|
||||||
export class ItemAdminSearchResultGridElementComponent extends SearchResultGridElementComponent<ItemSearchResult, Item> implements OnInit {
|
export class ItemAdminSearchResultGridElementComponent extends SearchResultGridElementComponent<ItemSearchResult, Item> implements OnDestroy, OnInit {
|
||||||
@ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective;
|
@ViewChild(DynamicComponentLoaderDirective, { static: true }) dynamicComponentLoaderDirective: DynamicComponentLoaderDirective;
|
||||||
@ViewChild('badges', { static: true }) badges: ElementRef;
|
@ViewChild('badges', { static: true }) badges: ElementRef;
|
||||||
@ViewChild('buttons', { static: true }) buttons: ElementRef;
|
@ViewChild('buttons', { static: true }) buttons: ElementRef;
|
||||||
|
|
||||||
|
protected compRef: ComponentRef<Component>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public dsoNameService: DSONameService,
|
public dsoNameService: DSONameService,
|
||||||
protected truncatableService: TruncatableService,
|
protected truncatableService: TruncatableService,
|
||||||
protected bitstreamDataService: BitstreamDataService,
|
protected bitstreamDataService: BitstreamDataService,
|
||||||
private themeService: ThemeService,
|
private themeService: ThemeService,
|
||||||
private componentFactoryResolver: ComponentFactoryResolver,
|
|
||||||
) {
|
) {
|
||||||
super(dsoNameService, truncatableService, bitstreamDataService);
|
super(dsoNameService, truncatableService, bitstreamDataService);
|
||||||
}
|
}
|
||||||
@@ -44,23 +46,32 @@ export class ItemAdminSearchResultGridElementComponent extends SearchResultGridE
|
|||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
super.ngOnInit();
|
super.ngOnInit();
|
||||||
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent());
|
const component: GenericConstructor<Component> = this.getComponent();
|
||||||
|
|
||||||
const viewContainerRef = this.listableObjectDirective.viewContainerRef;
|
const viewContainerRef = this.dynamicComponentLoaderDirective.viewContainerRef;
|
||||||
viewContainerRef.clear();
|
viewContainerRef.clear();
|
||||||
|
|
||||||
const componentRef = viewContainerRef.createComponent(
|
this.compRef = viewContainerRef.createComponent(
|
||||||
componentFactory,
|
component, {
|
||||||
0,
|
index: 0,
|
||||||
undefined,
|
injector: undefined,
|
||||||
[
|
projectableNodes: [
|
||||||
[this.badges.nativeElement],
|
[this.badges.nativeElement],
|
||||||
[this.buttons.nativeElement]
|
[this.buttons.nativeElement],
|
||||||
]);
|
],
|
||||||
(componentRef.instance as any).object = this.object;
|
},
|
||||||
(componentRef.instance as any).index = this.index;
|
);
|
||||||
(componentRef.instance as any).linkType = this.linkType;
|
this.compRef.setInput('object',this.object);
|
||||||
(componentRef.instance as any).listID = this.listID;
|
this.compRef.setInput('index', this.index);
|
||||||
|
this.compRef.setInput('linkType', this.linkType);
|
||||||
|
this.compRef.setInput('listID', this.listID);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (hasValue(this.compRef)) {
|
||||||
|
this.compRef.destroy();
|
||||||
|
this.compRef = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<ng-template dsListableObject>
|
<ng-template dsDynamicComponentLoader>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div #badges class="position-absolute ml-1">
|
<div #badges class="position-absolute ml-1">
|
||||||
<div class="workflow-badge">
|
<div class="workflow-badge">
|
||||||
|
@@ -18,8 +18,8 @@ import {
|
|||||||
ItemGridElementComponent
|
ItemGridElementComponent
|
||||||
} from '../../../../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component';
|
} from '../../../../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component';
|
||||||
import {
|
import {
|
||||||
ListableObjectDirective
|
DynamicComponentLoaderDirective
|
||||||
} from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive';
|
} from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive';
|
||||||
import {
|
import {
|
||||||
WorkflowItemSearchResult
|
WorkflowItemSearchResult
|
||||||
} from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
} from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
||||||
@@ -38,7 +38,7 @@ describe('WorkflowItemSearchResultAdminWorkflowGridElementComponent', () => {
|
|||||||
let itemRD$;
|
let itemRD$;
|
||||||
let linkService;
|
let linkService;
|
||||||
let object;
|
let object;
|
||||||
let themeService;
|
let themeService: ThemeService;
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
itemRD$ = createSuccessfulRemoteDataObject$(new Item());
|
itemRD$ = createSuccessfulRemoteDataObject$(new Item());
|
||||||
@@ -55,7 +55,11 @@ describe('WorkflowItemSearchResultAdminWorkflowGridElementComponent', () => {
|
|||||||
init();
|
init();
|
||||||
TestBed.configureTestingModule(
|
TestBed.configureTestingModule(
|
||||||
{
|
{
|
||||||
declarations: [WorkflowItemSearchResultAdminWorkflowGridElementComponent, ItemGridElementComponent, ListableObjectDirective],
|
declarations: [
|
||||||
|
WorkflowItemSearchResultAdminWorkflowGridElementComponent,
|
||||||
|
ItemGridElementComponent,
|
||||||
|
DynamicComponentLoaderDirective,
|
||||||
|
],
|
||||||
imports: [
|
imports: [
|
||||||
NoopAnimationsModule,
|
NoopAnimationsModule,
|
||||||
TranslateModule.forRoot(),
|
TranslateModule.forRoot(),
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, ComponentFactoryResolver, ElementRef, ViewChild } from '@angular/core';
|
import { Component, ElementRef, ViewChild, ComponentRef, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { Item } from '../../../../../core/shared/item.model';
|
import { Item } from '../../../../../core/shared/item.model';
|
||||||
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||||
import {
|
import {
|
||||||
@@ -10,7 +10,7 @@ import { SearchResultGridElementComponent } from '../../../../../shared/object-g
|
|||||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||||
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
|
||||||
import { GenericConstructor } from '../../../../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../../../../core/shared/generic-constructor';
|
||||||
import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive';
|
import { DynamicComponentLoaderDirective } from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive';
|
||||||
import { WorkflowItem } from '../../../../../core/submission/models/workflowitem.model';
|
import { WorkflowItem } from '../../../../../core/submission/models/workflowitem.model';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
||||||
@@ -24,6 +24,7 @@ import { take } from 'rxjs/operators';
|
|||||||
import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
||||||
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
|
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
|
||||||
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { hasValue } from '../../../../../shared/empty.util';
|
||||||
|
|
||||||
@listableObjectComponent(WorkflowItemSearchResult, ViewMode.GridElement, Context.AdminWorkflowSearch)
|
@listableObjectComponent(WorkflowItemSearchResult, ViewMode.GridElement, Context.AdminWorkflowSearch)
|
||||||
@Component({
|
@Component({
|
||||||
@@ -34,11 +35,11 @@ import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service
|
|||||||
/**
|
/**
|
||||||
* The component for displaying a grid element for an workflow item on the admin workflow search page
|
* The component for displaying a grid element for an workflow item on the admin workflow search page
|
||||||
*/
|
*/
|
||||||
export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends SearchResultGridElementComponent<WorkflowItemSearchResult, WorkflowItem> {
|
export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends SearchResultGridElementComponent<WorkflowItemSearchResult, WorkflowItem> implements OnDestroy, OnInit {
|
||||||
/**
|
/**
|
||||||
* Directive used to render the dynamic component in
|
* Directive used to render the dynamic component in
|
||||||
*/
|
*/
|
||||||
@ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective;
|
@ViewChild(DynamicComponentLoaderDirective, { static: true }) dynamicComponentLoaderDirective: DynamicComponentLoaderDirective;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The html child that contains the badges html
|
* The html child that contains the badges html
|
||||||
@@ -55,9 +56,10 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S
|
|||||||
*/
|
*/
|
||||||
public item$: Observable<Item>;
|
public item$: Observable<Item>;
|
||||||
|
|
||||||
|
protected compRef: ComponentRef<Component>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public dsoNameService: DSONameService,
|
public dsoNameService: DSONameService,
|
||||||
private componentFactoryResolver: ComponentFactoryResolver,
|
|
||||||
private linkService: LinkService,
|
private linkService: LinkService,
|
||||||
protected truncatableService: TruncatableService,
|
protected truncatableService: TruncatableService,
|
||||||
private themeService: ThemeService,
|
private themeService: ThemeService,
|
||||||
@@ -75,26 +77,34 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S
|
|||||||
this.dso = this.linkService.resolveLink(this.dso, followLink('item'));
|
this.dso = this.linkService.resolveLink(this.dso, followLink('item'));
|
||||||
this.item$ = (this.dso.item as Observable<RemoteData<Item>>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload());
|
this.item$ = (this.dso.item as Observable<RemoteData<Item>>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload());
|
||||||
this.item$.pipe(take(1)).subscribe((item: Item) => {
|
this.item$.pipe(take(1)).subscribe((item: Item) => {
|
||||||
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent(item));
|
const component: GenericConstructor<Component> = this.getComponent(item);
|
||||||
|
|
||||||
const viewContainerRef = this.listableObjectDirective.viewContainerRef;
|
const viewContainerRef = this.dynamicComponentLoaderDirective.viewContainerRef;
|
||||||
viewContainerRef.clear();
|
viewContainerRef.clear();
|
||||||
|
|
||||||
const componentRef = viewContainerRef.createComponent(
|
this.compRef = viewContainerRef.createComponent(
|
||||||
componentFactory,
|
component, {
|
||||||
0,
|
index: 0,
|
||||||
undefined,
|
injector: undefined,
|
||||||
[
|
projectableNodes: [
|
||||||
[this.badges.nativeElement],
|
[this.badges.nativeElement],
|
||||||
[this.buttons.nativeElement]
|
[this.buttons.nativeElement],
|
||||||
]);
|
],
|
||||||
(componentRef.instance as any).object = item;
|
},
|
||||||
(componentRef.instance as any).index = this.index;
|
);
|
||||||
(componentRef.instance as any).linkType = this.linkType;
|
this.compRef.setInput('object', item);
|
||||||
(componentRef.instance as any).listID = this.listID;
|
this.compRef.setInput('index', this.index);
|
||||||
componentRef.changeDetectorRef.detectChanges();
|
this.compRef.setInput('linkType', this.linkType);
|
||||||
}
|
this.compRef.setInput('listID', this.listID);
|
||||||
);
|
this.compRef.changeDetectorRef.detectChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (hasValue(this.compRef)) {
|
||||||
|
this.compRef.destroy();
|
||||||
|
this.compRef = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<ng-template dsListableObject>
|
<ng-template dsDynamicComponentLoader>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<div #badges class="position-absolute ml-1">
|
<div #badges class="position-absolute ml-1">
|
||||||
<div class="workflow-badge">
|
<div class="workflow-badge">
|
||||||
|
@@ -20,8 +20,8 @@ import {
|
|||||||
ItemGridElementComponent
|
ItemGridElementComponent
|
||||||
} from '../../../../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component';
|
} from '../../../../../shared/object-grid/item-grid-element/item-types/item/item-grid-element.component';
|
||||||
import {
|
import {
|
||||||
ListableObjectDirective
|
DynamicComponentLoaderDirective
|
||||||
} from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive';
|
} from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive';
|
||||||
import {
|
import {
|
||||||
WorkflowItemSearchResult
|
WorkflowItemSearchResult
|
||||||
} from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
} from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
||||||
@@ -45,7 +45,7 @@ describe('WorkspaceItemSearchResultAdminWorkflowGridElementComponent', () => {
|
|||||||
let itemRD$;
|
let itemRD$;
|
||||||
let linkService;
|
let linkService;
|
||||||
let object;
|
let object;
|
||||||
let themeService;
|
let themeService: ThemeService;
|
||||||
let supervisionOrderDataService;
|
let supervisionOrderDataService;
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
@@ -67,7 +67,11 @@ describe('WorkspaceItemSearchResultAdminWorkflowGridElementComponent', () => {
|
|||||||
init();
|
init();
|
||||||
TestBed.configureTestingModule(
|
TestBed.configureTestingModule(
|
||||||
{
|
{
|
||||||
declarations: [WorkspaceItemSearchResultAdminWorkflowGridElementComponent, ItemGridElementComponent, ListableObjectDirective],
|
declarations: [
|
||||||
|
WorkspaceItemSearchResultAdminWorkflowGridElementComponent,
|
||||||
|
ItemGridElementComponent,
|
||||||
|
DynamicComponentLoaderDirective,
|
||||||
|
],
|
||||||
imports: [
|
imports: [
|
||||||
NoopAnimationsModule,
|
NoopAnimationsModule,
|
||||||
TranslateModule.forRoot(),
|
TranslateModule.forRoot(),
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { Component, ComponentFactoryResolver, ElementRef, ViewChild, OnInit } from '@angular/core';
|
import { Component, ElementRef, ViewChild, OnInit, OnDestroy, ComponentRef } from '@angular/core';
|
||||||
|
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
import { map, mergeMap, take, tap } from 'rxjs/operators';
|
import { map, mergeMap, take, tap } from 'rxjs/operators';
|
||||||
@@ -16,9 +16,7 @@ import {
|
|||||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||||
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
|
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
|
||||||
import { GenericConstructor } from '../../../../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../../../../core/shared/generic-constructor';
|
||||||
import {
|
import { DynamicComponentLoaderDirective } from '../../../../../shared/abstract-component-loader/dynamic-component-loader.directive';
|
||||||
ListableObjectDirective
|
|
||||||
} from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive';
|
|
||||||
import { WorkspaceItem } from '../../../../../core/submission/models/workspaceitem.model';
|
import { WorkspaceItem } from '../../../../../core/submission/models/workspaceitem.model';
|
||||||
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
||||||
import { followLink } from '../../../../../shared/utils/follow-link-config.model';
|
import { followLink } from '../../../../../shared/utils/follow-link-config.model';
|
||||||
@@ -37,6 +35,7 @@ import { SupervisionOrder } from '../../../../../core/supervision-order/models/s
|
|||||||
import { PaginatedList } from '../../../../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../../../core/data/paginated-list.model';
|
||||||
import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service';
|
import { SupervisionOrderDataService } from '../../../../../core/supervision-order/supervision-order-data.service';
|
||||||
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { hasValue } from '../../../../../shared/empty.util';
|
||||||
|
|
||||||
@listableObjectComponent(WorkspaceItemSearchResult, ViewMode.GridElement, Context.AdminWorkflowSearch)
|
@listableObjectComponent(WorkspaceItemSearchResult, ViewMode.GridElement, Context.AdminWorkflowSearch)
|
||||||
@Component({
|
@Component({
|
||||||
@@ -47,7 +46,7 @@ import { DSONameService } from '../../../../../core/breadcrumbs/dso-name.service
|
|||||||
/**
|
/**
|
||||||
* The component for displaying a grid element for an workflow item on the admin workflow search page
|
* The component for displaying a grid element for an workflow item on the admin workflow search page
|
||||||
*/
|
*/
|
||||||
export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends SearchResultGridElementComponent<WorkspaceItemSearchResult, WorkspaceItem> implements OnInit {
|
export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends SearchResultGridElementComponent<WorkspaceItemSearchResult, WorkspaceItem> implements OnDestroy, OnInit {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The item linked to the workspace item
|
* The item linked to the workspace item
|
||||||
@@ -67,7 +66,7 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends
|
|||||||
/**
|
/**
|
||||||
* Directive used to render the dynamic component in
|
* Directive used to render the dynamic component in
|
||||||
*/
|
*/
|
||||||
@ViewChild(ListableObjectDirective, { static: true }) listableObjectDirective: ListableObjectDirective;
|
@ViewChild(DynamicComponentLoaderDirective, { static: true }) dynamicComponentLoaderDirective: DynamicComponentLoaderDirective;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The html child that contains the badges html
|
* The html child that contains the badges html
|
||||||
@@ -79,9 +78,13 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends
|
|||||||
*/
|
*/
|
||||||
@ViewChild('buttons', { static: true }) buttons: ElementRef;
|
@ViewChild('buttons', { static: true }) buttons: ElementRef;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The reference to the dynamic component
|
||||||
|
*/
|
||||||
|
protected compRef: ComponentRef<Component>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public dsoNameService: DSONameService,
|
public dsoNameService: DSONameService,
|
||||||
private componentFactoryResolver: ComponentFactoryResolver,
|
|
||||||
private linkService: LinkService,
|
private linkService: LinkService,
|
||||||
protected truncatableService: TruncatableService,
|
protected truncatableService: TruncatableService,
|
||||||
private themeService: ThemeService,
|
private themeService: ThemeService,
|
||||||
@@ -100,24 +103,24 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends
|
|||||||
this.dso = this.linkService.resolveLink(this.dso, followLink('item'));
|
this.dso = this.linkService.resolveLink(this.dso, followLink('item'));
|
||||||
this.item$ = (this.dso.item as Observable<RemoteData<Item>>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload());
|
this.item$ = (this.dso.item as Observable<RemoteData<Item>>).pipe(getAllSucceededRemoteData(), getRemoteDataPayload());
|
||||||
this.item$.pipe(take(1)).subscribe((item: Item) => {
|
this.item$.pipe(take(1)).subscribe((item: Item) => {
|
||||||
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.getComponent(item));
|
const component: GenericConstructor<Component> = this.getComponent(item);
|
||||||
|
|
||||||
const viewContainerRef = this.listableObjectDirective.viewContainerRef;
|
const viewContainerRef = this.dynamicComponentLoaderDirective.viewContainerRef;
|
||||||
viewContainerRef.clear();
|
viewContainerRef.clear();
|
||||||
|
|
||||||
const componentRef = viewContainerRef.createComponent(
|
this.compRef = viewContainerRef.createComponent(
|
||||||
componentFactory,
|
component, {
|
||||||
0,
|
index: 0,
|
||||||
undefined,
|
projectableNodes: [
|
||||||
[
|
|
||||||
[this.badges.nativeElement],
|
[this.badges.nativeElement],
|
||||||
[this.buttons.nativeElement]
|
[this.buttons.nativeElement]
|
||||||
]);
|
],
|
||||||
(componentRef.instance as any).object = item;
|
});
|
||||||
(componentRef.instance as any).index = this.index;
|
this.compRef.setInput('object', item);
|
||||||
(componentRef.instance as any).linkType = this.linkType;
|
this.compRef.setInput('index', this.index);
|
||||||
(componentRef.instance as any).listID = this.listID;
|
this.compRef.setInput('linkType', this.linkType);
|
||||||
componentRef.changeDetectorRef.detectChanges();
|
this.compRef.setInput('listID', this.listID);
|
||||||
|
this.compRef.changeDetectorRef.detectChanges();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -130,6 +133,13 @@ export class WorkspaceItemSearchResultAdminWorkflowGridElementComponent extends
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
if (hasValue(this.compRef)) {
|
||||||
|
this.compRef.destroy();
|
||||||
|
this.compRef = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the component depending on the item's entity type, view mode and context
|
* Fetch the component depending on the item's entity type, view mode and context
|
||||||
* @returns {GenericConstructor<Component>}
|
* @returns {GenericConstructor<Component>}
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, NoPreloading } from '@angular/router';
|
import { NoPreloading, RouterModule } from '@angular/router';
|
||||||
import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
|
import { AuthBlockingGuard } from './core/auth/auth-blocking.guard';
|
||||||
|
|
||||||
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
|
import { AuthenticatedGuard } from './core/auth/authenticated.guard';
|
||||||
@@ -40,6 +40,7 @@ import {
|
|||||||
import { ServerCheckGuard } from './core/server-check/server-check.guard';
|
import { ServerCheckGuard } from './core/server-check/server-check.guard';
|
||||||
import { MenuResolver } from './menu.resolver';
|
import { MenuResolver } from './menu.resolver';
|
||||||
import { ThemedPageErrorComponent } from './page-error/themed-page-error.component';
|
import { ThemedPageErrorComponent } from './page-error/themed-page-error.component';
|
||||||
|
import { ForgotPasswordCheckGuard } from './core/rest-property/forgot-password-check-guard.guard';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -94,7 +95,10 @@ import { ThemedPageErrorComponent } from './page-error/themed-page-error.compone
|
|||||||
path: FORGOT_PASSWORD_PATH,
|
path: FORGOT_PASSWORD_PATH,
|
||||||
loadChildren: () => import('./forgot-password/forgot-password.module')
|
loadChildren: () => import('./forgot-password/forgot-password.module')
|
||||||
.then((m) => m.ForgotPasswordModule),
|
.then((m) => m.ForgotPasswordModule),
|
||||||
canActivate: [EndUserAgreementCurrentUserGuard]
|
canActivate: [
|
||||||
|
ForgotPasswordCheckGuard,
|
||||||
|
EndUserAgreementCurrentUserGuard
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: COMMUNITY_MODULE_PATH,
|
path: COMMUNITY_MODULE_PATH,
|
||||||
|
@@ -1,29 +0,0 @@
|
|||||||
import {Component} from '@angular/core';
|
|
||||||
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
|
||||||
import { BrowseByDatePageComponent } from './browse-by-date-page.component';
|
|
||||||
import {BrowseByDataType, rendersBrowseBy} from '../browse-by-switcher/browse-by-decorator';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Themed wrapper for BrowseByDatePageComponent
|
|
||||||
* */
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-themed-browse-by-metadata-page',
|
|
||||||
styleUrls: [],
|
|
||||||
templateUrl: '../../shared/theme-support/themed.component.html',
|
|
||||||
})
|
|
||||||
|
|
||||||
@rendersBrowseBy(BrowseByDataType.Date)
|
|
||||||
export class ThemedBrowseByDatePageComponent
|
|
||||||
extends ThemedComponent<BrowseByDatePageComponent> {
|
|
||||||
protected getComponentName(): string {
|
|
||||||
return 'BrowseByDatePageComponent';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected importThemedComponent(themeName: string): Promise<any> {
|
|
||||||
return import(`../../../themes/${themeName}/app/browse-by/browse-by-date-page/browse-by-date-page.component`);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected importUnthemedComponent(): Promise<any> {
|
|
||||||
return import(`./browse-by-date-page.component`);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,4 +1,4 @@
|
|||||||
import { BrowseByDatePageComponent } from './browse-by-date-page.component';
|
import { BrowseByDateComponent } from './browse-by-date.component';
|
||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
@@ -15,7 +15,7 @@ import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
|
|||||||
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 { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
|
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
|
||||||
import { toRemoteData } from '../browse-by-metadata-page/browse-by-metadata-page.component.spec';
|
import { toRemoteData } from '../browse-by-metadata/browse-by-metadata.component.spec';
|
||||||
import { VarDirective } from '../../shared/utils/var.directive';
|
import { VarDirective } from '../../shared/utils/var.directive';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
@@ -23,10 +23,11 @@ import { PaginationServiceStub } from '../../shared/testing/pagination-service.s
|
|||||||
import { APP_CONFIG } from '../../../config/app-config.interface';
|
import { APP_CONFIG } from '../../../config/app-config.interface';
|
||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
import { SortDirection } from '../../core/cache/models/sort-options.model';
|
import { SortDirection } from '../../core/cache/models/sort-options.model';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
|
||||||
describe('BrowseByDatePageComponent', () => {
|
describe('BrowseByDateComponent', () => {
|
||||||
let comp: BrowseByDatePageComponent;
|
let comp: BrowseByDateComponent;
|
||||||
let fixture: ComponentFixture<BrowseByDatePageComponent>;
|
let fixture: ComponentFixture<BrowseByDateComponent>;
|
||||||
let route: ActivatedRoute;
|
let route: ActivatedRoute;
|
||||||
let paginationService;
|
let paginationService;
|
||||||
|
|
||||||
@@ -86,7 +87,7 @@ describe('BrowseByDatePageComponent', () => {
|
|||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
||||||
declarations: [BrowseByDatePageComponent, EnumKeysPipe, VarDirective],
|
declarations: [BrowseByDateComponent, EnumKeysPipe, VarDirective],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||||
{ provide: BrowseService, useValue: mockBrowseService },
|
{ provide: BrowseService, useValue: mockBrowseService },
|
||||||
@@ -101,7 +102,7 @@ describe('BrowseByDatePageComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(BrowseByDatePageComponent);
|
fixture = TestBed.createComponent(BrowseByDateComponent);
|
||||||
const browseService = fixture.debugElement.injector.get(BrowseService);
|
const browseService = fixture.debugElement.injector.get(BrowseService);
|
||||||
spyOn(browseService, 'getFirstItemFor')
|
spyOn(browseService, 'getFirstItemFor')
|
||||||
// ok to expect the default browse as first param since we just need the mock items obtained via sort direction.
|
// ok to expect the default browse as first param since we just need the mock items obtained via sort direction.
|
||||||
@@ -112,9 +113,13 @@ describe('BrowseByDatePageComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should initialize the list of items', () => {
|
it('should initialize the list of items', (done: DoneFn) => {
|
||||||
|
expect(comp.loading$).toBeObservable(cold('(a|)', {
|
||||||
|
a: false,
|
||||||
|
}));
|
||||||
comp.items$.subscribe((result) => {
|
comp.items$.subscribe((result) => {
|
||||||
expect(result.payload.page).toEqual([firstItem]);
|
expect(result.payload.page).toEqual([firstItem]);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@@ -1,10 +1,6 @@
|
|||||||
import { ChangeDetectorRef, Component, Inject } from '@angular/core';
|
import { ChangeDetectorRef, Component, Inject, OnInit } from '@angular/core';
|
||||||
import {
|
import { BrowseByMetadataComponent, browseParamsToOptions, getBrowseSearchOptions } from '../browse-by-metadata/browse-by-metadata.component';
|
||||||
BrowseByMetadataPageComponent,
|
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||||
browseParamsToOptions,
|
|
||||||
getBrowseSearchOptions
|
|
||||||
} from '../browse-by-metadata-page/browse-by-metadata-page.component';
|
|
||||||
import { combineLatest as observableCombineLatest } from 'rxjs';
|
|
||||||
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
import { hasValue, isNotEmpty } from '../../shared/empty.util';
|
||||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||||
import { BrowseService } from '../../core/browse/browse.service';
|
import { BrowseService } from '../../core/browse/browse.service';
|
||||||
@@ -19,32 +15,36 @@ import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
|
|||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { Item } from '../../core/shared/item.model';
|
import { Item } from '../../core/shared/item.model';
|
||||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||||
|
import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-browse-by-date-page',
|
selector: 'ds-browse-by-date',
|
||||||
styleUrls: ['../browse-by-metadata-page/browse-by-metadata-page.component.scss'],
|
styleUrls: ['../browse-by-metadata/browse-by-metadata.component.scss'],
|
||||||
templateUrl: '../browse-by-metadata-page/browse-by-metadata-page.component.html'
|
templateUrl: '../browse-by-metadata/browse-by-metadata.component.html',
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* Component for browsing items by metadata definition of type 'date'
|
* Component for browsing items by metadata definition of type 'date'
|
||||||
* A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields.
|
* A metadata definition (a.k.a. browse id) is a short term used to describe one or multiple metadata fields.
|
||||||
* An example would be 'dateissued' for 'dc.date.issued'
|
* An example would be 'dateissued' for 'dc.date.issued'
|
||||||
*/
|
*/
|
||||||
export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
@rendersBrowseBy(BrowseByDataType.Date)
|
||||||
|
export class BrowseByDateComponent extends BrowseByMetadataComponent implements OnInit {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default metadata keys to use for determining the lower limit of the StartsWith dropdown options
|
* The default metadata keys to use for determining the lower limit of the StartsWith dropdown options
|
||||||
*/
|
*/
|
||||||
defaultMetadataKeys = ['dc.date.issued'];
|
defaultMetadataKeys = ['dc.date.issued'];
|
||||||
|
|
||||||
public constructor(protected route: ActivatedRoute,
|
public constructor(
|
||||||
protected browseService: BrowseService,
|
protected route: ActivatedRoute,
|
||||||
protected dsoService: DSpaceObjectDataService,
|
protected browseService: BrowseService,
|
||||||
protected router: Router,
|
protected dsoService: DSpaceObjectDataService,
|
||||||
protected paginationService: PaginationService,
|
protected paginationService: PaginationService,
|
||||||
protected cdRef: ChangeDetectorRef,
|
protected router: Router,
|
||||||
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
||||||
public dsoNameService: DSONameService,
|
public dsoNameService: DSONameService,
|
||||||
|
protected cdRef: ChangeDetectorRef,
|
||||||
) {
|
) {
|
||||||
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService);
|
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService);
|
||||||
}
|
}
|
||||||
@@ -57,19 +57,17 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
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);
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
observableCombineLatest([this.route.params, this.route.queryParams, this.route.data,
|
observableCombineLatest([this.route.params, this.route.queryParams, this.scope$, this.route.data,
|
||||||
this.currentPagination$, this.currentSort$]).pipe(
|
this.currentPagination$, this.currentSort$]).pipe(
|
||||||
map(([routeParams, queryParams, data, currentPage, currentSort]) => {
|
map(([routeParams, queryParams, scope, data, currentPage, currentSort]) => {
|
||||||
return [Object.assign({}, routeParams, queryParams, data), currentPage, currentSort];
|
return [Object.assign({}, routeParams, queryParams, data), scope, currentPage, currentSort];
|
||||||
})
|
})
|
||||||
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
).subscribe(([params, scope, currentPage, currentSort]: [Params, string, PaginationComponentOptions, SortOptions]) => {
|
||||||
const metadataKeys = params.browseDefinition ? params.browseDefinition.metadataKeys : this.defaultMetadataKeys;
|
const metadataKeys = params.browseDefinition ? params.browseDefinition.metadataKeys : this.defaultMetadataKeys;
|
||||||
this.browseId = params.id || this.defaultBrowseId;
|
this.browseId = params.id || this.defaultBrowseId;
|
||||||
this.startsWith = +params.startsWith || params.startsWith;
|
this.startsWith = +params.startsWith || params.startsWith;
|
||||||
const searchOptions = browseParamsToOptions(params, currentPage, currentSort, this.browseId, this.fetchThumbnails);
|
const searchOptions = browseParamsToOptions(params, scope, currentPage, currentSort, this.browseId, this.fetchThumbnails);
|
||||||
this.updatePageWithItems(searchOptions, this.value, undefined);
|
this.updatePageWithItems(searchOptions, this.value, undefined);
|
||||||
this.updateParent(params.scope);
|
|
||||||
this.updateLogo();
|
|
||||||
this.updateStartsWithOptions(this.browseId, metadataKeys, params.scope);
|
this.updateStartsWithOptions(this.browseId, metadataKeys, params.scope);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -85,12 +83,21 @@ export class BrowseByDatePageComponent extends BrowseByMetadataPageComponent {
|
|||||||
* @param scope The scope under which to fetch the earliest item for
|
* @param scope The scope under which to fetch the earliest item for
|
||||||
*/
|
*/
|
||||||
updateStartsWithOptions(definition: string, metadataKeys: string[], scope?: string) {
|
updateStartsWithOptions(definition: string, metadataKeys: string[], scope?: string) {
|
||||||
const firstItemRD = this.browseService.getFirstItemFor(definition, scope, SortDirection.ASC);
|
const firstItemRD$: Observable<RemoteData<Item>> = this.browseService.getFirstItemFor(definition, scope, SortDirection.ASC);
|
||||||
const lastItemRD = this.browseService.getFirstItemFor(definition, scope, SortDirection.DESC);
|
const lastItemRD$: Observable<RemoteData<Item>> = this.browseService.getFirstItemFor(definition, scope, SortDirection.DESC);
|
||||||
|
this.loading$ = observableCombineLatest([
|
||||||
|
firstItemRD$,
|
||||||
|
lastItemRD$,
|
||||||
|
]).pipe(
|
||||||
|
map(([firstItemRD, lastItemRD]: [RemoteData<Item>, RemoteData<Item>]) => firstItemRD.isLoading || lastItemRD.isLoading)
|
||||||
|
);
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
observableCombineLatest([firstItemRD, lastItemRD]).subscribe(([firstItem, lastItem]) => {
|
observableCombineLatest([
|
||||||
let lowerLimit: number = this.getLimit(firstItem, metadataKeys, this.appConfig.browseBy.defaultLowerLimit);
|
firstItemRD$,
|
||||||
let upperLimit: number = this.getLimit(lastItem, metadataKeys, new Date().getUTCFullYear());
|
lastItemRD$,
|
||||||
|
]).subscribe(([firstItemRD, lastItemRD]: [RemoteData<Item>, RemoteData<Item>]) => {
|
||||||
|
let lowerLimit: number = this.getLimit(firstItemRD, metadataKeys, this.appConfig.browseBy.defaultLowerLimit);
|
||||||
|
let upperLimit: number = this.getLimit(lastItemRD, metadataKeys, new Date().getUTCFullYear());
|
||||||
const options: number[] = [];
|
const options: number[] = [];
|
||||||
const oneYearBreak: number = Math.floor((upperLimit - this.appConfig.browseBy.oneYearLimit) / 5) * 5;
|
const oneYearBreak: number = Math.floor((upperLimit - this.appConfig.browseBy.oneYearLimit) / 5) * 5;
|
||||||
const fiveYearBreak: number = Math.floor((upperLimit - this.appConfig.browseBy.fiveYearLimit) / 10) * 10;
|
const fiveYearBreak: number = Math.floor((upperLimit - this.appConfig.browseBy.fiveYearLimit) / 10) * 10;
|
@@ -1,17 +1,13 @@
|
|||||||
import { first } from 'rxjs/operators';
|
import { first } from 'rxjs/operators';
|
||||||
import { BrowseByGuard } from './browse-by-guard';
|
import { BrowseByGuard } from './browse-by-guard';
|
||||||
import { of as observableOf } from 'rxjs';
|
|
||||||
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
import { createFailedRemoteDataObject$, createSuccessfulRemoteDataObject$ } from '../shared/remote-data.utils';
|
||||||
import { BrowseByDataType } from './browse-by-switcher/browse-by-decorator';
|
import { BrowseByDataType } from './browse-by-switcher/browse-by-data-type';
|
||||||
import { ValueListBrowseDefinition } from '../core/shared/value-list-browse-definition.model';
|
import { ValueListBrowseDefinition } from '../core/shared/value-list-browse-definition.model';
|
||||||
import { DSONameServiceMock } from '../shared/mocks/dso-name.service.mock';
|
|
||||||
import { DSONameService } from '../core/breadcrumbs/dso-name.service';
|
|
||||||
import { RouterStub } from '../shared/testing/router.stub';
|
import { RouterStub } from '../shared/testing/router.stub';
|
||||||
|
|
||||||
describe('BrowseByGuard', () => {
|
describe('BrowseByGuard', () => {
|
||||||
describe('canActivate', () => {
|
describe('canActivate', () => {
|
||||||
let guard: BrowseByGuard;
|
let guard: BrowseByGuard;
|
||||||
let dsoService: any;
|
|
||||||
let translateService: any;
|
let translateService: any;
|
||||||
let browseDefinitionService: any;
|
let browseDefinitionService: any;
|
||||||
let router: any;
|
let router: any;
|
||||||
@@ -25,10 +21,6 @@ describe('BrowseByGuard', () => {
|
|||||||
const browseDefinition = Object.assign(new ValueListBrowseDefinition(), { type: BrowseByDataType.Metadata, metadataKeys: ['dc.contributor'] });
|
const browseDefinition = Object.assign(new ValueListBrowseDefinition(), { type: BrowseByDataType.Metadata, metadataKeys: ['dc.contributor'] });
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
dsoService = {
|
|
||||||
findById: (dsoId: string) => observableOf({ payload: { name: name }, hasSucceeded: true })
|
|
||||||
};
|
|
||||||
|
|
||||||
translateService = {
|
translateService = {
|
||||||
instant: () => field
|
instant: () => field
|
||||||
};
|
};
|
||||||
@@ -39,7 +31,7 @@ describe('BrowseByGuard', () => {
|
|||||||
|
|
||||||
router = new RouterStub() as any;
|
router = new RouterStub() as any;
|
||||||
|
|
||||||
guard = new BrowseByGuard(dsoService, translateService, browseDefinitionService, new DSONameServiceMock() as DSONameService, router);
|
guard = new BrowseByGuard(translateService, browseDefinitionService, router);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return true, and sets up the data correctly, with a scope and value', () => {
|
it('should return true, and sets up the data correctly, with a scope and value', () => {
|
||||||
@@ -48,6 +40,7 @@ describe('BrowseByGuard', () => {
|
|||||||
title: field,
|
title: field,
|
||||||
browseDefinition,
|
browseDefinition,
|
||||||
},
|
},
|
||||||
|
parent: null,
|
||||||
params: {
|
params: {
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
@@ -64,7 +57,7 @@ describe('BrowseByGuard', () => {
|
|||||||
title,
|
title,
|
||||||
id,
|
id,
|
||||||
browseDefinition,
|
browseDefinition,
|
||||||
collection: name,
|
scope,
|
||||||
field,
|
field,
|
||||||
value: '"' + value + '"'
|
value: '"' + value + '"'
|
||||||
};
|
};
|
||||||
@@ -97,7 +90,7 @@ describe('BrowseByGuard', () => {
|
|||||||
title,
|
title,
|
||||||
id,
|
id,
|
||||||
browseDefinition,
|
browseDefinition,
|
||||||
collection: name,
|
scope,
|
||||||
field,
|
field,
|
||||||
value: ''
|
value: ''
|
||||||
};
|
};
|
||||||
@@ -108,12 +101,48 @@ describe('BrowseByGuard', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return true, and sets up the data correctly using the community/collection page id, with a scope and without value', () => {
|
||||||
|
const scopedNoValueRoute = {
|
||||||
|
data: {
|
||||||
|
title: field,
|
||||||
|
browseDefinition,
|
||||||
|
},
|
||||||
|
parent: {
|
||||||
|
params: {
|
||||||
|
id: scope,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
params: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
queryParams: {
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
guard.canActivate(scopedNoValueRoute as any, undefined).pipe(
|
||||||
|
first(),
|
||||||
|
).subscribe((canActivate) => {
|
||||||
|
const result = {
|
||||||
|
title,
|
||||||
|
id,
|
||||||
|
browseDefinition,
|
||||||
|
scope,
|
||||||
|
field,
|
||||||
|
value: '',
|
||||||
|
};
|
||||||
|
expect(scopedNoValueRoute.data).toEqual(result);
|
||||||
|
expect(router.navigate).not.toHaveBeenCalled();
|
||||||
|
expect(canActivate).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should return true, and sets up the data correctly, without a scope and with a value', () => {
|
it('should return true, and sets up the data correctly, without a scope and with a value', () => {
|
||||||
const route = {
|
const route = {
|
||||||
data: {
|
data: {
|
||||||
title: field,
|
title: field,
|
||||||
browseDefinition,
|
browseDefinition,
|
||||||
},
|
},
|
||||||
|
parent: null,
|
||||||
params: {
|
params: {
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
@@ -129,7 +158,7 @@ describe('BrowseByGuard', () => {
|
|||||||
title,
|
title,
|
||||||
id,
|
id,
|
||||||
browseDefinition,
|
browseDefinition,
|
||||||
collection: '',
|
scope: undefined,
|
||||||
field,
|
field,
|
||||||
value: '"' + value + '"'
|
value: '"' + value + '"'
|
||||||
};
|
};
|
||||||
@@ -147,6 +176,7 @@ describe('BrowseByGuard', () => {
|
|||||||
data: {
|
data: {
|
||||||
title: field,
|
title: field,
|
||||||
},
|
},
|
||||||
|
parent: null,
|
||||||
params: {
|
params: {
|
||||||
id,
|
id,
|
||||||
},
|
},
|
||||||
|
@@ -1,15 +1,12 @@
|
|||||||
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, Data } from '@angular/router';
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { DSpaceObjectDataService } from '../core/data/dspace-object-data.service';
|
|
||||||
import { hasNoValue, hasValue } from '../shared/empty.util';
|
import { hasNoValue, hasValue } from '../shared/empty.util';
|
||||||
import { map, switchMap } from 'rxjs/operators';
|
import { map, switchMap } from 'rxjs/operators';
|
||||||
import { getFirstCompletedRemoteData, getFirstSucceededRemoteDataPayload } from '../core/shared/operators';
|
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { Observable, of as observableOf } from 'rxjs';
|
import { Observable, of as observableOf } from 'rxjs';
|
||||||
import { BrowseDefinitionDataService } from '../core/browse/browse-definition-data.service';
|
import { BrowseDefinitionDataService } from '../core/browse/browse-definition-data.service';
|
||||||
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
import { BrowseDefinition } from '../core/shared/browse-definition.model';
|
||||||
import { DSONameService } from '../core/breadcrumbs/dso-name.service';
|
|
||||||
import { DSpaceObject } from '../core/shared/dspace-object.model';
|
|
||||||
import { RemoteData } from '../core/data/remote-data';
|
import { RemoteData } from '../core/data/remote-data';
|
||||||
import { PAGE_NOT_FOUND_PATH } from '../app-routing-paths';
|
import { PAGE_NOT_FOUND_PATH } from '../app-routing-paths';
|
||||||
|
|
||||||
@@ -19,11 +16,10 @@ import { PAGE_NOT_FOUND_PATH } from '../app-routing-paths';
|
|||||||
*/
|
*/
|
||||||
export class BrowseByGuard implements CanActivate {
|
export class BrowseByGuard implements CanActivate {
|
||||||
|
|
||||||
constructor(protected dsoService: DSpaceObjectDataService,
|
constructor(
|
||||||
protected translate: TranslateService,
|
protected translate: TranslateService,
|
||||||
protected browseDefinitionService: BrowseDefinitionDataService,
|
protected browseDefinitionService: BrowseDefinitionDataService,
|
||||||
protected dsoNameService: DSONameService,
|
protected router: Router,
|
||||||
protected router: Router,
|
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,25 +35,14 @@ export class BrowseByGuard implements CanActivate {
|
|||||||
} else {
|
} else {
|
||||||
browseDefinition$ = observableOf(route.data.browseDefinition);
|
browseDefinition$ = observableOf(route.data.browseDefinition);
|
||||||
}
|
}
|
||||||
const scope = route.queryParams.scope;
|
const scope = route.queryParams.scope ?? route.parent?.params.id;
|
||||||
const value = route.queryParams.value;
|
const value = route.queryParams.value;
|
||||||
const metadataTranslated = this.translate.instant(`browse.metadata.${id}`);
|
const metadataTranslated = this.translate.instant(`browse.metadata.${id}`);
|
||||||
return browseDefinition$.pipe(
|
return browseDefinition$.pipe(
|
||||||
switchMap((browseDefinition: BrowseDefinition | undefined) => {
|
switchMap((browseDefinition: BrowseDefinition | undefined) => {
|
||||||
if (hasValue(browseDefinition)) {
|
if (hasValue(browseDefinition)) {
|
||||||
if (hasValue(scope)) {
|
route.data = this.createData(title, id, browseDefinition, metadataTranslated, value, route, scope);
|
||||||
const dso$: Observable<DSpaceObject> = this.dsoService.findById(scope).pipe(getFirstSucceededRemoteDataPayload());
|
return observableOf(true);
|
||||||
return dso$.pipe(
|
|
||||||
map((dso: DSpaceObject) => {
|
|
||||||
const name = this.dsoNameService.getName(dso);
|
|
||||||
route.data = this.createData(title, id, browseDefinition, name, metadataTranslated, value, route);
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
route.data = this.createData(title, id, browseDefinition, '', metadataTranslated, value, route);
|
|
||||||
return observableOf(true);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
void this.router.navigate([PAGE_NOT_FOUND_PATH]);
|
void this.router.navigate([PAGE_NOT_FOUND_PATH]);
|
||||||
return observableOf(false);
|
return observableOf(false);
|
||||||
@@ -66,14 +51,14 @@ export class BrowseByGuard implements CanActivate {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private createData(title, id, browseDefinition, collection, field, value, route) {
|
private createData(title: string, id: string, browseDefinition: BrowseDefinition, field: string, value: string, route: ActivatedRouteSnapshot, scope: string): Data {
|
||||||
return Object.assign({}, route.data, {
|
return Object.assign({}, route.data, {
|
||||||
title: title,
|
title: title,
|
||||||
id: id,
|
id: id,
|
||||||
browseDefinition: browseDefinition,
|
browseDefinition: browseDefinition,
|
||||||
collection: collection,
|
|
||||||
field: field,
|
field: field,
|
||||||
value: hasValue(value) ? `"${value}"` : ''
|
value: hasValue(value) ? `"${value}"` : '',
|
||||||
|
scope: scope,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,67 +0,0 @@
|
|||||||
<div class="container">
|
|
||||||
<ng-container *ngVar="(parent$ | async) as parent">
|
|
||||||
<ng-container *ngIf="parent?.payload as parentContext">
|
|
||||||
<div class="d-flex flex-row border-bottom mb-4 pb-4">
|
|
||||||
|
|
||||||
<header class="comcol-header mr-auto">
|
|
||||||
<!-- Parent Name -->
|
|
||||||
<ds-comcol-page-header [name]="dsoNameService.getName(parentContext)">
|
|
||||||
</ds-comcol-page-header>
|
|
||||||
<!-- Collection logo -->
|
|
||||||
<ds-comcol-page-logo *ngIf="logo$"
|
|
||||||
[logo]="(logo$ | async)?.payload"
|
|
||||||
[alternateText]="'Community or Collection Logo'">
|
|
||||||
</ds-comcol-page-logo>
|
|
||||||
<!-- Handle -->
|
|
||||||
<ds-themed-comcol-page-handle
|
|
||||||
[content]="parentContext.handle"
|
|
||||||
[title]="parentContext.type+'.page.handle'" >
|
|
||||||
</ds-themed-comcol-page-handle>
|
|
||||||
<!-- Introductory text -->
|
|
||||||
<ds-comcol-page-content [content]="parentContext.introductoryText" [hasInnerHtml]="true">
|
|
||||||
</ds-comcol-page-content>
|
|
||||||
<!-- News -->
|
|
||||||
<ds-comcol-page-content [content]="parentContext.sidebarText" [hasInnerHtml]="true" [title]="'community.page.news'">
|
|
||||||
</ds-comcol-page-content>
|
|
||||||
</header>
|
|
||||||
<ds-dso-edit-menu></ds-dso-edit-menu>
|
|
||||||
</div>
|
|
||||||
<!-- Browse-By Links -->
|
|
||||||
<ds-themed-comcol-page-browse-by [id]="parentContext.id" [contentType]="parentContext.type"></ds-themed-comcol-page-browse-by>
|
|
||||||
</ng-container></ng-container>
|
|
||||||
|
|
||||||
<section class="comcol-page-browse-section">
|
|
||||||
<div class="browse-by-metadata w-100">
|
|
||||||
<ds-themed-browse-by *ngIf="startsWithOptions" class="col-xs-12 w-100"
|
|
||||||
title="{{'browse.title' | translate:
|
|
||||||
{
|
|
||||||
collection: dsoNameService.getName((parent$ | async)?.payload),
|
|
||||||
field: 'browse.metadata.' + browseId | translate,
|
|
||||||
startsWith: (startsWith)? ('browse.startsWith' | translate: { startsWith: '"' + startsWith + '"' }) : '',
|
|
||||||
value: (value)? '"' + value + '"': ''
|
|
||||||
} }}"
|
|
||||||
parentname="{{dsoNameService.getName((parent$ | async)?.payload)}}"
|
|
||||||
[objects$]="(items$ !== undefined)? items$ : browseEntries$"
|
|
||||||
[paginationConfig]="(currentPagination$ |async)"
|
|
||||||
[sortConfig]="(currentSort$ |async)"
|
|
||||||
[type]="startsWithType"
|
|
||||||
[startsWithOptions]="startsWithOptions"
|
|
||||||
(prev)="goPrev()"
|
|
||||||
(next)="goNext()">
|
|
||||||
</ds-themed-browse-by>
|
|
||||||
<ds-themed-loading *ngIf="!startsWithOptions" message="{{'loading.browse-by-page' | translate}}"></ds-themed-loading>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<ng-container *ngVar="(parent$ | async) as parent">
|
|
||||||
<ng-container *ngIf="parent?.payload as parentContext">
|
|
||||||
<footer *ngIf="parentContext.copyrightText" class="border-top my-5 pt-4">
|
|
||||||
<div >
|
|
||||||
|
|
||||||
<!-- Copyright -->
|
|
||||||
<ds-comcol-page-content [content]="parentContext.copyrightText" [hasInnerHtml]="true">
|
|
||||||
</ds-comcol-page-content>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
@@ -1,29 +0,0 @@
|
|||||||
import {Component} from '@angular/core';
|
|
||||||
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
|
||||||
import { BrowseByMetadataPageComponent } from './browse-by-metadata-page.component';
|
|
||||||
import {BrowseByDataType, rendersBrowseBy} from '../browse-by-switcher/browse-by-decorator';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Themed wrapper for BrowseByMetadataPageComponent
|
|
||||||
**/
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-themed-browse-by-metadata-page',
|
|
||||||
styleUrls: [],
|
|
||||||
templateUrl: '../../shared/theme-support/themed.component.html',
|
|
||||||
})
|
|
||||||
|
|
||||||
@rendersBrowseBy(BrowseByDataType.Metadata)
|
|
||||||
export class ThemedBrowseByMetadataPageComponent
|
|
||||||
extends ThemedComponent<BrowseByMetadataPageComponent> {
|
|
||||||
protected getComponentName(): string {
|
|
||||||
return 'BrowseByMetadataPageComponent';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected importThemedComponent(themeName: string): Promise<any> {
|
|
||||||
return import(`../../../themes/${themeName}/app/browse-by/browse-by-metadata-page/browse-by-metadata-page.component`);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected importUnthemedComponent(): Promise<any> {
|
|
||||||
return import(`./browse-by-metadata-page.component`);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -0,0 +1,21 @@
|
|||||||
|
<section class="comcol-page-browse-section">
|
||||||
|
<div class="browse-by-metadata w-100">
|
||||||
|
<ds-themed-browse-by *ngIf="!(loading$ | async)" class="col-xs-12 w-100"
|
||||||
|
title="{{'browse.title' | translate:{
|
||||||
|
field: 'browse.metadata.' + browseId | translate,
|
||||||
|
startsWith: (startsWith)? ('browse.startsWith' | translate: { startsWith: '"' + startsWith + '"' }) : '',
|
||||||
|
value: (value)? '"' + value + '"': ''
|
||||||
|
} }}"
|
||||||
|
[displayTitle]="displayTitle"
|
||||||
|
[objects$]="(items$ !== undefined)? items$ : browseEntries$"
|
||||||
|
[paginationConfig]="(currentPagination$ |async)"
|
||||||
|
[sortConfig]="(currentSort$ |async)"
|
||||||
|
[type]="startsWithType"
|
||||||
|
[startsWithOptions]="startsWithOptions"
|
||||||
|
(prev)="goPrev()"
|
||||||
|
(next)="goNext()">
|
||||||
|
</ds-themed-browse-by>
|
||||||
|
<ds-themed-loading *ngIf="loading$ | async"
|
||||||
|
message="{{'loading.browse-by-page' | translate}}"></ds-themed-loading>
|
||||||
|
</div>
|
||||||
|
</section>
|
@@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
BrowseByMetadataPageComponent,
|
BrowseByMetadataComponent,
|
||||||
browseParamsToOptions,
|
browseParamsToOptions,
|
||||||
getBrowseSearchOptions
|
getBrowseSearchOptions
|
||||||
} from './browse-by-metadata-page.component';
|
} from './browse-by-metadata.component';
|
||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { BrowseService } from '../../core/browse/browse.service';
|
import { BrowseService } from '../../core/browse/browse.service';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
@@ -30,10 +30,11 @@ import { PaginationService } from '../../core/pagination/pagination.service';
|
|||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
||||||
import { APP_CONFIG } from '../../../config/app-config.interface';
|
import { APP_CONFIG } from '../../../config/app-config.interface';
|
||||||
|
import { cold } from 'jasmine-marbles';
|
||||||
|
|
||||||
describe('BrowseByMetadataPageComponent', () => {
|
describe('BrowseByMetadataComponent', () => {
|
||||||
let comp: BrowseByMetadataPageComponent;
|
let comp: BrowseByMetadataComponent;
|
||||||
let fixture: ComponentFixture<BrowseByMetadataPageComponent>;
|
let fixture: ComponentFixture<BrowseByMetadataComponent>;
|
||||||
let browseService: BrowseService;
|
let browseService: BrowseService;
|
||||||
let route: ActivatedRoute;
|
let route: ActivatedRoute;
|
||||||
let paginationService;
|
let paginationService;
|
||||||
@@ -103,7 +104,7 @@ describe('BrowseByMetadataPageComponent', () => {
|
|||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
||||||
declarations: [BrowseByMetadataPageComponent, EnumKeysPipe, VarDirective],
|
declarations: [BrowseByMetadataComponent, EnumKeysPipe, VarDirective],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||||
{ provide: BrowseService, useValue: mockBrowseService },
|
{ provide: BrowseService, useValue: mockBrowseService },
|
||||||
@@ -117,7 +118,7 @@ describe('BrowseByMetadataPageComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(BrowseByMetadataPageComponent);
|
fixture = TestBed.createComponent(BrowseByMetadataComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
browseService = (comp as any).browseService;
|
browseService = (comp as any).browseService;
|
||||||
@@ -144,20 +145,18 @@ describe('BrowseByMetadataPageComponent', () => {
|
|||||||
|
|
||||||
route.params = observableOf(paramsWithValue);
|
route.params = observableOf(paramsWithValue);
|
||||||
comp.ngOnInit();
|
comp.ngOnInit();
|
||||||
comp.updateParent('fake-scope');
|
|
||||||
comp.updateLogo();
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fetch items', () => {
|
it('should fetch items', (done: DoneFn) => {
|
||||||
|
expect(comp.loading$).toBeObservable(cold('(a|)', {
|
||||||
|
a: false,
|
||||||
|
}));
|
||||||
comp.items$.subscribe((result) => {
|
comp.items$.subscribe((result) => {
|
||||||
expect(result.payload.page).toEqual(mockItems);
|
expect(result.payload.page).toEqual(mockItems);
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fetch the logo', () => {
|
|
||||||
expect(comp.logo$).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when calling browseParamsToOptions', () => {
|
describe('when calling browseParamsToOptions', () => {
|
||||||
@@ -176,7 +175,7 @@ describe('BrowseByMetadataPageComponent', () => {
|
|||||||
field: 'fake-field',
|
field: 'fake-field',
|
||||||
};
|
};
|
||||||
|
|
||||||
result = browseParamsToOptions(paramsScope, paginationOptions, sortOptions, 'author', comp.fetchThumbnails);
|
result = browseParamsToOptions(paramsScope, 'fake-scope', paginationOptions, sortOptions, 'author', comp.fetchThumbnails);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return BrowseEntrySearchOptions with the correct properties', () => {
|
it('should return BrowseEntrySearchOptions with the correct properties', () => {
|
@@ -1,5 +1,5 @@
|
|||||||
import { combineLatest as observableCombineLatest, Observable, Subscription } from 'rxjs';
|
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subscription, of as observableOf } from 'rxjs';
|
||||||
import { Component, Inject, OnInit, OnDestroy } from '@angular/core';
|
import { Component, Inject, OnInit, OnDestroy, Input, OnChanges } from '@angular/core';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../core/data/remote-data';
|
||||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../core/data/paginated-list.model';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
@@ -12,23 +12,21 @@ import { Item } from '../../core/shared/item.model';
|
|||||||
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
|
import { BrowseEntrySearchOptions } from '../../core/browse/browse-entry-search-options.model';
|
||||||
import { getFirstSucceededRemoteData } from '../../core/shared/operators';
|
import { getFirstSucceededRemoteData } from '../../core/shared/operators';
|
||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
import { DSpaceObject } from '../../core/shared/dspace-object.model';
|
|
||||||
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
import { StartsWithType } from '../../shared/starts-with/starts-with-decorator';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../core/pagination/pagination.service';
|
||||||
import { filter, map, mergeMap } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
|
||||||
import { Bitstream } from '../../core/shared/bitstream.model';
|
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
|
||||||
import { Community } from '../../core/shared/community.model';
|
|
||||||
import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
|
import { APP_CONFIG, AppConfig } from '../../../config/app-config.interface';
|
||||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
||||||
|
import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||||
|
import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type';
|
||||||
|
import { Context } from '../../core/shared/context.model';
|
||||||
|
|
||||||
export const BBM_PAGINATION_ID = 'bbm';
|
export const BBM_PAGINATION_ID = 'bbm';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-browse-by-metadata-page',
|
selector: 'ds-browse-by-metadata',
|
||||||
styleUrls: ['./browse-by-metadata-page.component.scss'],
|
styleUrls: ['./browse-by-metadata.component.scss'],
|
||||||
templateUrl: './browse-by-metadata-page.component.html'
|
templateUrl: './browse-by-metadata.component.html',
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* Component for browsing (items) by metadata definition.
|
* Component for browsing (items) by metadata definition.
|
||||||
@@ -36,7 +34,30 @@ export const BBM_PAGINATION_ID = 'bbm';
|
|||||||
* or multiple metadata fields. An example would be 'author' for
|
* or multiple metadata fields. An example would be 'author' for
|
||||||
* 'dc.contributor.*'
|
* 'dc.contributor.*'
|
||||||
*/
|
*/
|
||||||
export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
@rendersBrowseBy(BrowseByDataType.Metadata)
|
||||||
|
export class BrowseByMetadataComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The optional context
|
||||||
|
*/
|
||||||
|
@Input() context: Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link BrowseByDataType} of this Component
|
||||||
|
*/
|
||||||
|
@Input() browseByType: BrowseByDataType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the {@link Community} or {@link Collection} of the scope to display
|
||||||
|
*/
|
||||||
|
@Input() scope: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the h1 title in the section
|
||||||
|
*/
|
||||||
|
@Input() displayTitle = true;
|
||||||
|
|
||||||
|
scope$: BehaviorSubject<string> = new BehaviorSubject(undefined);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of browse-entries to display
|
* The list of browse-entries to display
|
||||||
@@ -48,16 +69,6 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
items$: Observable<RemoteData<PaginatedList<Item>>>;
|
items$: Observable<RemoteData<PaginatedList<Item>>>;
|
||||||
|
|
||||||
/**
|
|
||||||
* The current Community or Collection we're browsing metadata/items in
|
|
||||||
*/
|
|
||||||
parent$: Observable<RemoteData<DSpaceObject>>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The logo of the current Community or Collection
|
|
||||||
*/
|
|
||||||
logo$: Observable<RemoteData<Bitstream>>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The pagination config used to display the values
|
* The pagination config used to display the values
|
||||||
*/
|
*/
|
||||||
@@ -98,7 +109,7 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
* The list of StartsWith options
|
* The list of StartsWith options
|
||||||
* Should be defined after ngOnInit is called!
|
* Should be defined after ngOnInit is called!
|
||||||
*/
|
*/
|
||||||
startsWithOptions;
|
startsWithOptions: (string | number)[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The value we're browsing items for
|
* The value we're browsing items for
|
||||||
@@ -122,6 +133,11 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
fetchThumbnails: boolean;
|
fetchThumbnails: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Observable determining if the loading animation needs to be shown
|
||||||
|
*/
|
||||||
|
loading$ = observableOf(true);
|
||||||
|
|
||||||
public constructor(protected route: ActivatedRoute,
|
public constructor(protected route: ActivatedRoute,
|
||||||
protected browseService: BrowseService,
|
protected browseService: BrowseService,
|
||||||
protected dsoService: DSpaceObjectDataService,
|
protected dsoService: DSpaceObjectDataService,
|
||||||
@@ -130,7 +146,6 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
||||||
public dsoNameService: DSONameService,
|
public dsoNameService: DSONameService,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
this.fetchThumbnails = this.appConfig.browseBy.showThumbnails;
|
this.fetchThumbnails = this.appConfig.browseBy.showThumbnails;
|
||||||
this.paginationConfig = Object.assign(new PaginationComponentOptions(), {
|
this.paginationConfig = Object.assign(new PaginationComponentOptions(), {
|
||||||
id: BBM_PAGINATION_ID,
|
id: BBM_PAGINATION_ID,
|
||||||
@@ -147,12 +162,12 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
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);
|
||||||
this.subs.push(
|
this.subs.push(
|
||||||
observableCombineLatest([this.route.params, this.route.queryParams, this.currentPagination$, this.currentSort$]).pipe(
|
observableCombineLatest([this.route.params, this.route.queryParams, this.scope$, this.currentPagination$, this.currentSort$]).pipe(
|
||||||
map(([routeParams, queryParams, currentPage, currentSort]) => {
|
map(([routeParams, queryParams, scope, currentPage, currentSort]) => {
|
||||||
return [Object.assign({}, routeParams, queryParams),currentPage,currentSort];
|
return [Object.assign({}, routeParams, queryParams), scope, currentPage, currentSort];
|
||||||
})
|
})
|
||||||
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
).subscribe(([params, scope, currentPage, currentSort]: [Params, string, PaginationComponentOptions, SortOptions]) => {
|
||||||
this.browseId = params.id || this.defaultBrowseId;
|
this.browseId = params.id || this.defaultBrowseId;
|
||||||
this.authority = params.authority;
|
this.authority = params.authority;
|
||||||
|
|
||||||
if (typeof params.value === 'string'){
|
if (typeof params.value === 'string'){
|
||||||
@@ -170,18 +185,19 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isNotEmpty(this.value)) {
|
if (isNotEmpty(this.value)) {
|
||||||
this.updatePageWithItems(
|
this.updatePageWithItems(browseParamsToOptions(params, scope, currentPage, currentSort, this.browseId, this.fetchThumbnails), this.value, this.authority);
|
||||||
browseParamsToOptions(params, currentPage, currentSort, this.browseId, this.fetchThumbnails), this.value, this.authority);
|
|
||||||
} else {
|
} else {
|
||||||
this.updatePage(browseParamsToOptions(params, currentPage, currentSort, this.browseId, false));
|
this.updatePage(browseParamsToOptions(params, scope, currentPage, currentSort, this.browseId, false));
|
||||||
}
|
}
|
||||||
this.updateParent(params.scope);
|
|
||||||
this.updateLogo();
|
|
||||||
}));
|
}));
|
||||||
this.updateStartsWithTextOptions();
|
this.updateStartsWithTextOptions();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngOnChanges(): void {
|
||||||
|
this.scope$.next(this.scope);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the StartsWith options with text values
|
* Update the StartsWith options with text values
|
||||||
* It adds the value "0-9" as well as all letters from A to Z
|
* It adds the value "0-9" as well as all letters from A to Z
|
||||||
@@ -200,6 +216,9 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
updatePage(searchOptions: BrowseEntrySearchOptions) {
|
updatePage(searchOptions: BrowseEntrySearchOptions) {
|
||||||
this.browseEntries$ = this.browseService.getBrowseEntriesFor(searchOptions);
|
this.browseEntries$ = this.browseService.getBrowseEntriesFor(searchOptions);
|
||||||
|
this.loading$ = this.browseEntries$.pipe(
|
||||||
|
map((browseEntriesRD: RemoteData<PaginatedList<BrowseEntry>>) => browseEntriesRD.isLoading),
|
||||||
|
);
|
||||||
this.items$ = undefined;
|
this.items$ = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,37 +233,9 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
*/
|
*/
|
||||||
updatePageWithItems(searchOptions: BrowseEntrySearchOptions, value: string, authority: string) {
|
updatePageWithItems(searchOptions: BrowseEntrySearchOptions, value: string, authority: string) {
|
||||||
this.items$ = this.browseService.getBrowseItemsFor(value, authority, searchOptions);
|
this.items$ = this.browseService.getBrowseItemsFor(value, authority, searchOptions);
|
||||||
}
|
this.loading$ = this.items$.pipe(
|
||||||
|
map((itemsRD: RemoteData<PaginatedList<Item>>) => itemsRD.isLoading),
|
||||||
/**
|
);
|
||||||
* Update the parent Community or Collection using their scope
|
|
||||||
* @param scope The UUID of the Community or Collection to fetch
|
|
||||||
*/
|
|
||||||
updateParent(scope: string) {
|
|
||||||
if (hasValue(scope)) {
|
|
||||||
const linksToFollow = () => {
|
|
||||||
return [followLink('logo')];
|
|
||||||
};
|
|
||||||
this.parent$ = this.dsoService.findById(scope,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
...linksToFollow() as FollowLinkConfig<DSpaceObject>[]).pipe(
|
|
||||||
getFirstSucceededRemoteData()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the parent Community or Collection logo
|
|
||||||
*/
|
|
||||||
updateLogo() {
|
|
||||||
if (hasValue(this.parent$)) {
|
|
||||||
this.logo$ = this.parent$.pipe(
|
|
||||||
map((rd: RemoteData<Collection | Community>) => rd.payload),
|
|
||||||
filter((collectionOrCommunity: Collection | Community) => hasValue(collectionOrCommunity.logo)),
|
|
||||||
mergeMap((collectionOrCommunity: Collection | Community) => collectionOrCommunity.logo)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -278,7 +269,7 @@ export class BrowseByMetadataPageComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
this.subs.filter((sub: Subscription) => hasValue(sub)).forEach((sub: Subscription) => sub.unsubscribe());
|
||||||
this.paginationService.clearPagination(this.paginationConfig.id);
|
this.paginationService.clearPagination(this.paginationConfig.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,12 +298,14 @@ export function getBrowseSearchOptions(defaultBrowseId: string,
|
|||||||
/**
|
/**
|
||||||
* Function to transform query and url parameters into searchOptions used to fetch browse entries or items
|
* Function to transform query and url parameters into searchOptions used to fetch browse entries or items
|
||||||
* @param params URL and query parameters
|
* @param params URL and query parameters
|
||||||
|
* @param scope The scope to show the results
|
||||||
* @param paginationConfig Pagination configuration
|
* @param paginationConfig Pagination configuration
|
||||||
* @param sortConfig Sorting configuration
|
* @param sortConfig Sorting configuration
|
||||||
* @param metadata Optional metadata definition to fetch browse entries/items for
|
* @param metadata Optional metadata definition to fetch browse entries/items for
|
||||||
* @param fetchThumbnail Optional parameter for requesting thumbnail images
|
* @param fetchThumbnail Optional parameter for requesting thumbnail images
|
||||||
*/
|
*/
|
||||||
export function browseParamsToOptions(params: any,
|
export function browseParamsToOptions(params: any,
|
||||||
|
scope: string,
|
||||||
paginationConfig: PaginationComponentOptions,
|
paginationConfig: PaginationComponentOptions,
|
||||||
sortConfig: SortOptions,
|
sortConfig: SortOptions,
|
||||||
metadata?: string,
|
metadata?: string,
|
||||||
@@ -322,7 +315,7 @@ export function browseParamsToOptions(params: any,
|
|||||||
paginationConfig,
|
paginationConfig,
|
||||||
sortConfig,
|
sortConfig,
|
||||||
params.startsWith,
|
params.startsWith,
|
||||||
params.scope,
|
scope,
|
||||||
fetchThumbnail
|
fetchThumbnail
|
||||||
);
|
);
|
||||||
}
|
}
|
@@ -5,12 +5,19 @@ import { ItemDataService } from '../core/data/item-data.service';
|
|||||||
import { BrowseService } from '../core/browse/browse.service';
|
import { BrowseService } from '../core/browse/browse.service';
|
||||||
import { BrowseByGuard } from './browse-by-guard';
|
import { BrowseByGuard } from './browse-by-guard';
|
||||||
import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.module';
|
import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.module';
|
||||||
|
import { BrowseByPageComponent } from './browse-by-page/browse-by-page.component';
|
||||||
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
|
||||||
|
const DECLARATIONS = [
|
||||||
|
BrowseByPageComponent,
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
SharedBrowseByModule,
|
SharedBrowseByModule,
|
||||||
BrowseByRoutingModule,
|
BrowseByRoutingModule,
|
||||||
BrowseByModule.withEntryComponents(),
|
BrowseByModule,
|
||||||
|
SharedModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ItemDataService,
|
ItemDataService,
|
||||||
@@ -18,8 +25,11 @@ import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.modul
|
|||||||
BrowseByGuard,
|
BrowseByGuard,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
|
...DECLARATIONS,
|
||||||
]
|
],
|
||||||
|
exports: [
|
||||||
|
...DECLARATIONS,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class BrowseByPageModule {
|
export class BrowseByPageModule {
|
||||||
|
|
||||||
|
@@ -0,0 +1,4 @@
|
|||||||
|
<div class="container">
|
||||||
|
<ds-browse-by-switcher [browseByType]="browseByType$ | async">
|
||||||
|
</ds-browse-by-switcher>
|
||||||
|
</div>
|
@@ -0,0 +1,69 @@
|
|||||||
|
// eslint-disable-next-line max-classes-per-file
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { BrowseByPageComponent } from './browse-by-page.component';
|
||||||
|
import { BrowseBySwitcherComponent } from '../browse-by-switcher/browse-by-switcher.component';
|
||||||
|
import { DynamicComponentLoaderDirective } from '../../shared/abstract-component-loader/dynamic-component-loader.directive';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { ActivatedRouteStub } from '../../shared/testing/active-router.stub';
|
||||||
|
import { getMockThemeService } from '../../shared/mocks/theme-service.mock';
|
||||||
|
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||||
|
import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
||||||
|
import { By } from '@angular/platform-browser';
|
||||||
|
import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type';
|
||||||
|
|
||||||
|
@rendersBrowseBy('BrowseByPageComponent' as BrowseByDataType)
|
||||||
|
@Component({
|
||||||
|
// eslint-disable-next-line @angular-eslint/component-selector
|
||||||
|
selector: '',
|
||||||
|
template: '<span id="BrowseByTestComponent"></span>',
|
||||||
|
})
|
||||||
|
class BrowseByTestComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestBrowseByPageBrowseDefinition extends BrowseDefinition {
|
||||||
|
getRenderType(): BrowseByDataType {
|
||||||
|
return 'BrowseByPageComponent' as BrowseByDataType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('BrowseByPageComponent', () => {
|
||||||
|
let component: BrowseByPageComponent;
|
||||||
|
let fixture: ComponentFixture<BrowseByPageComponent>;
|
||||||
|
|
||||||
|
let activatedRoute: ActivatedRouteStub;
|
||||||
|
let themeService: ThemeService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
activatedRoute = new ActivatedRouteStub();
|
||||||
|
themeService = getMockThemeService();
|
||||||
|
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [
|
||||||
|
BrowseByPageComponent,
|
||||||
|
BrowseBySwitcherComponent,
|
||||||
|
DynamicComponentLoaderDirective,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
BrowseByTestComponent,
|
||||||
|
{ provide: ActivatedRoute, useValue: activatedRoute },
|
||||||
|
{ provide: ThemeService, useValue: themeService },
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(BrowseByPageComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create the correct browse section based on the route browseDefinition', () => {
|
||||||
|
activatedRoute.testData = {
|
||||||
|
browseDefinition: new TestBrowseByPageBrowseDefinition(),
|
||||||
|
};
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
expect(fixture.debugElement.query(By.css('#BrowseByTestComponent'))).not.toBeNull();
|
||||||
|
});
|
||||||
|
});
|
31
src/app/browse-by/browse-by-page/browse-by-page.component.ts
Normal file
31
src/app/browse-by/browse-by-page/browse-by-page.component.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-browse-by-page',
|
||||||
|
templateUrl: './browse-by-page.component.html',
|
||||||
|
styleUrls: ['./browse-by-page.component.scss'],
|
||||||
|
})
|
||||||
|
export class BrowseByPageComponent implements OnInit {
|
||||||
|
|
||||||
|
browseByType$: Observable<BrowseByDataType>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the correct browse-by component by using the relevant config from the route data
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.browseByType$ = this.route.data.pipe(
|
||||||
|
map((data: { browseDefinition: BrowseDefinition }) => data.browseDefinition.getRenderType()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -3,7 +3,7 @@ import { NgModule } from '@angular/core';
|
|||||||
import { BrowseByGuard } from './browse-by-guard';
|
import { BrowseByGuard } from './browse-by-guard';
|
||||||
import { BrowseByDSOBreadcrumbResolver } from './browse-by-dso-breadcrumb.resolver';
|
import { BrowseByDSOBreadcrumbResolver } from './browse-by-dso-breadcrumb.resolver';
|
||||||
import { BrowseByI18nBreadcrumbResolver } from './browse-by-i18n-breadcrumb.resolver';
|
import { BrowseByI18nBreadcrumbResolver } from './browse-by-i18n-breadcrumb.resolver';
|
||||||
import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component';
|
import { BrowseByPageComponent } from './browse-by-page/browse-by-page.component';
|
||||||
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -18,7 +18,7 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: ':id',
|
path: ':id',
|
||||||
component: ThemedBrowseBySwitcherComponent,
|
component: BrowseByPageComponent,
|
||||||
canActivate: [BrowseByGuard],
|
canActivate: [BrowseByGuard],
|
||||||
resolve: { breadcrumb: BrowseByI18nBreadcrumbResolver },
|
resolve: { breadcrumb: BrowseByI18nBreadcrumbResolver },
|
||||||
data: { title: 'browse.title.page', breadcrumbKey: 'browse.metadata' }
|
data: { title: 'browse.title.page', breadcrumbKey: 'browse.metadata' }
|
||||||
|
@@ -0,0 +1,6 @@
|
|||||||
|
export enum BrowseByDataType {
|
||||||
|
Title = 'title',
|
||||||
|
Metadata = 'text',
|
||||||
|
Date = 'date',
|
||||||
|
Hierarchy = 'hierarchy',
|
||||||
|
}
|
@@ -1,4 +1,5 @@
|
|||||||
import { BrowseByDataType, rendersBrowseBy } from './browse-by-decorator';
|
import { BrowseByDataType } from './browse-by-data-type';
|
||||||
|
import { rendersBrowseBy } from './browse-by-decorator';
|
||||||
|
|
||||||
describe('BrowseByDecorator', () => {
|
describe('BrowseByDecorator', () => {
|
||||||
const titleDecorator = rendersBrowseBy(BrowseByDataType.Title);
|
const titleDecorator = rendersBrowseBy(BrowseByDataType.Title);
|
||||||
|
@@ -1,40 +1,36 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
import { hasNoValue } from '../../shared/empty.util';
|
import { hasNoValue } from '../../shared/empty.util';
|
||||||
import { InjectionToken } from '@angular/core';
|
import { DEFAULT_THEME, resolveTheme } from '../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||||
|
import { Context } from '../../core/shared/context.model';
|
||||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||||
import {
|
import { BrowseByDataType } from './browse-by-data-type';
|
||||||
DEFAULT_THEME,
|
|
||||||
resolveTheme
|
|
||||||
} from '../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
|
||||||
|
|
||||||
export enum BrowseByDataType {
|
|
||||||
Title = 'title',
|
|
||||||
Metadata = 'text',
|
|
||||||
Date = 'date'
|
|
||||||
}
|
|
||||||
|
|
||||||
export const DEFAULT_BROWSE_BY_TYPE = BrowseByDataType.Metadata;
|
export const DEFAULT_BROWSE_BY_TYPE = BrowseByDataType.Metadata;
|
||||||
|
export const DEFAULT_BROWSE_BY_CONTEXT = Context.Any;
|
||||||
|
|
||||||
export const BROWSE_BY_COMPONENT_FACTORY = new InjectionToken<(browseByType, theme) => GenericConstructor<any>>('getComponentByBrowseByType', {
|
const map: Map<BrowseByDataType, Map<Context, Map<string, GenericConstructor<Component>>>> = new Map();
|
||||||
providedIn: 'root',
|
|
||||||
factory: () => getComponentByBrowseByType
|
|
||||||
});
|
|
||||||
|
|
||||||
const map = new Map();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decorator used for rendering Browse-By pages by type
|
* Decorator used for rendering Browse-By pages by type
|
||||||
* @param browseByType The type of page
|
* @param browseByType The type of page
|
||||||
|
* @param context The optional context for the component
|
||||||
* @param theme The optional theme for the component
|
* @param theme The optional theme for the component
|
||||||
*/
|
*/
|
||||||
export function rendersBrowseBy(browseByType: string, theme = DEFAULT_THEME) {
|
export function rendersBrowseBy(browseByType: BrowseByDataType, context = DEFAULT_BROWSE_BY_CONTEXT, theme = DEFAULT_THEME) {
|
||||||
return function decorator(component: any) {
|
return function decorator(component: any) {
|
||||||
|
if (hasNoValue(browseByType)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (hasNoValue(map.get(browseByType))) {
|
if (hasNoValue(map.get(browseByType))) {
|
||||||
map.set(browseByType, new Map());
|
map.set(browseByType, new Map());
|
||||||
}
|
}
|
||||||
if (hasNoValue(map.get(browseByType).get(theme))) {
|
if (hasNoValue(map.get(browseByType).get(context))) {
|
||||||
map.get(browseByType).set(theme, component);
|
map.get(browseByType).set(context, new Map());
|
||||||
|
}
|
||||||
|
if (hasNoValue(map.get(browseByType).get(context).get(theme))) {
|
||||||
|
map.get(browseByType).get(context).set(theme, component);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`There can't be more than one component to render Browse-By of type "${browseByType}" and theme "${theme}"`);
|
throw new Error(`There can't be more than one component to render Browse-By of type "${browseByType}", context "${context}" and theme "${theme}"`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -42,12 +38,17 @@ export function rendersBrowseBy(browseByType: string, theme = DEFAULT_THEME) {
|
|||||||
/**
|
/**
|
||||||
* Get the component used for rendering a Browse-By page by type
|
* Get the component used for rendering a Browse-By page by type
|
||||||
* @param browseByType The type of page
|
* @param browseByType The type of page
|
||||||
|
* @param context The context to match
|
||||||
* @param theme the theme to match
|
* @param theme the theme to match
|
||||||
*/
|
*/
|
||||||
export function getComponentByBrowseByType(browseByType, theme) {
|
export function getComponentByBrowseByType(browseByType: BrowseByDataType, context: Context, theme: string): GenericConstructor<Component> {
|
||||||
let themeMap = map.get(browseByType);
|
let contextMap: Map<Context, Map<string, GenericConstructor<Component>>> = map.get(browseByType);
|
||||||
|
if (hasNoValue(contextMap)) {
|
||||||
|
contextMap = map.get(DEFAULT_BROWSE_BY_TYPE);
|
||||||
|
}
|
||||||
|
let themeMap: Map<string, GenericConstructor<Component>> = contextMap.get(context);
|
||||||
if (hasNoValue(themeMap)) {
|
if (hasNoValue(themeMap)) {
|
||||||
themeMap = map.get(DEFAULT_BROWSE_BY_TYPE);
|
themeMap = contextMap.get(DEFAULT_BROWSE_BY_CONTEXT);
|
||||||
}
|
}
|
||||||
const comp = resolveTheme(themeMap, theme);
|
const comp = resolveTheme(themeMap, theme);
|
||||||
if (hasNoValue(comp)) {
|
if (hasNoValue(comp)) {
|
||||||
|
@@ -1 +0,0 @@
|
|||||||
<ng-container *ngComponentOutlet="browseByComponent | async"></ng-container>
|
|
@@ -1,13 +1,23 @@
|
|||||||
import { BrowseBySwitcherComponent } from './browse-by-switcher.component';
|
import { BrowseBySwitcherComponent } from './browse-by-switcher.component';
|
||||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { SimpleChange, Component } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { rendersBrowseBy } from './browse-by-decorator';
|
||||||
import { BROWSE_BY_COMPONENT_FACTORY, BrowseByDataType } from './browse-by-decorator';
|
|
||||||
import { BehaviorSubject } from 'rxjs';
|
|
||||||
import { ThemeService } from '../../shared/theme-support/theme.service';
|
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||||
import { FlatBrowseDefinition } from '../../core/shared/flat-browse-definition.model';
|
import { FlatBrowseDefinition } from '../../core/shared/flat-browse-definition.model';
|
||||||
import { ValueListBrowseDefinition } from '../../core/shared/value-list-browse-definition.model';
|
import { ValueListBrowseDefinition } from '../../core/shared/value-list-browse-definition.model';
|
||||||
import { NonHierarchicalBrowseDefinition } from '../../core/shared/non-hierarchical-browse-definition';
|
import { NonHierarchicalBrowseDefinition } from '../../core/shared/non-hierarchical-browse-definition';
|
||||||
|
import { getMockThemeService } from '../../shared/mocks/theme-service.mock';
|
||||||
|
import { DynamicComponentLoaderDirective } from '../../shared/abstract-component-loader/dynamic-component-loader.directive';
|
||||||
|
import { BrowseByDataType } from './browse-by-data-type';
|
||||||
|
|
||||||
|
@rendersBrowseBy('BrowseBySwitcherComponent' as BrowseByDataType)
|
||||||
|
@Component({
|
||||||
|
// eslint-disable-next-line @angular-eslint/component-selector
|
||||||
|
selector: '',
|
||||||
|
template: '<span id="BrowseByTestComponent"></span>',
|
||||||
|
})
|
||||||
|
class BrowseByTestComponent {
|
||||||
|
}
|
||||||
|
|
||||||
describe('BrowseBySwitcherComponent', () => {
|
describe('BrowseBySwitcherComponent', () => {
|
||||||
let comp: BrowseBySwitcherComponent;
|
let comp: BrowseBySwitcherComponent;
|
||||||
@@ -41,46 +51,45 @@ describe('BrowseBySwitcherComponent', () => {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
const data = new BehaviorSubject(createDataWithBrowseDefinition(new FlatBrowseDefinition()));
|
|
||||||
|
|
||||||
const activatedRouteStub = {
|
|
||||||
data
|
|
||||||
};
|
|
||||||
|
|
||||||
let themeService: ThemeService;
|
let themeService: ThemeService;
|
||||||
let themeName: string;
|
const themeName = 'dspace';
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
themeName = 'dspace';
|
themeService = getMockThemeService(themeName);
|
||||||
themeService = jasmine.createSpyObj('themeService', {
|
|
||||||
getThemeName: themeName,
|
|
||||||
});
|
|
||||||
|
|
||||||
TestBed.configureTestingModule({
|
void TestBed.configureTestingModule({
|
||||||
declarations: [BrowseBySwitcherComponent],
|
declarations: [
|
||||||
providers: [
|
BrowseBySwitcherComponent,
|
||||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
DynamicComponentLoaderDirective,
|
||||||
{ provide: ThemeService, useValue: themeService },
|
],
|
||||||
{ provide: BROWSE_BY_COMPONENT_FACTORY, useValue: jasmine.createSpy('getComponentByBrowseByType').and.returnValue(null) }
|
providers: [
|
||||||
|
BrowseByTestComponent,
|
||||||
|
{ provide: ThemeService, useValue: themeService },
|
||||||
],
|
],
|
||||||
schemas: [NO_ERRORS_SCHEMA]
|
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
fixture = TestBed.createComponent(BrowseBySwitcherComponent);
|
fixture = TestBed.createComponent(BrowseBySwitcherComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
|
spyOn(comp, 'getComponent').and.returnValue(BrowseByTestComponent);
|
||||||
|
spyOn(comp, 'connectInputsAndOutputs').and.callThrough();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
types.forEach((type: NonHierarchicalBrowseDefinition) => {
|
types.forEach((type: NonHierarchicalBrowseDefinition) => {
|
||||||
describe(`when switching to a browse-by page for "${type.id}"`, () => {
|
describe(`when switching to a browse-by page for "${type.id}"`, () => {
|
||||||
beforeEach(() => {
|
beforeEach(async () => {
|
||||||
data.next(createDataWithBrowseDefinition(type));
|
comp.browseByType = type.dataType;
|
||||||
|
comp.ngOnChanges({
|
||||||
|
browseByType: new SimpleChange(undefined, type.dataType, true),
|
||||||
|
});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should call getComponentByBrowseByType with type "${type.dataType}"`, () => {
|
it(`should call getComponent with type "${type.dataType}"`, () => {
|
||||||
expect((comp as any).getComponentByBrowseByType).toHaveBeenCalledWith(type.dataType, themeName);
|
expect(comp.getComponent).toHaveBeenCalled();
|
||||||
|
expect(comp.connectInputsAndOutputs).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,38 +1,38 @@
|
|||||||
import { Component, Inject, OnInit } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { getComponentByBrowseByType } from './browse-by-decorator';
|
||||||
import { Observable } from 'rxjs';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
import { BROWSE_BY_COMPONENT_FACTORY } from './browse-by-decorator';
|
|
||||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||||
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
import { AbstractComponentLoaderComponent } from '../../shared/abstract-component-loader/abstract-component-loader.component';
|
||||||
import { ThemeService } from '../../shared/theme-support/theme.service';
|
import { BrowseByDataType } from './browse-by-data-type';
|
||||||
|
import { Context } from '../../core/shared/context.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-browse-by-switcher',
|
selector: 'ds-browse-by-switcher',
|
||||||
templateUrl: './browse-by-switcher.component.html'
|
templateUrl: '../../shared/abstract-component-loader/abstract-component-loader.component.html'
|
||||||
})
|
})
|
||||||
/**
|
export class BrowseBySwitcherComponent extends AbstractComponentLoaderComponent<Component> {
|
||||||
* Component for determining what Browse-By component to use depending on the metadata (browse ID) provided
|
|
||||||
*/
|
|
||||||
export class BrowseBySwitcherComponent implements OnInit {
|
|
||||||
|
|
||||||
/**
|
@Input() context: Context;
|
||||||
* Resolved browse-by component
|
|
||||||
*/
|
|
||||||
browseByComponent: Observable<any>;
|
|
||||||
|
|
||||||
public constructor(protected route: ActivatedRoute,
|
@Input() browseByType: BrowseByDataType;
|
||||||
protected themeService: ThemeService,
|
|
||||||
@Inject(BROWSE_BY_COMPONENT_FACTORY) private getComponentByBrowseByType: (browseByType, theme) => GenericConstructor<any>) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
@Input() displayTitle: boolean;
|
||||||
* Fetch the correct browse-by component by using the relevant config from the route data
|
|
||||||
*/
|
@Input() scope: string;
|
||||||
ngOnInit(): void {
|
|
||||||
this.browseByComponent = this.route.data.pipe(
|
protected inputNamesDependentForComponent: (keyof this & string)[] = [
|
||||||
map((data: { browseDefinition: BrowseDefinition }) => this.getComponentByBrowseByType(data.browseDefinition.getRenderType(), this.themeService.getThemeName()))
|
'context',
|
||||||
);
|
'browseByType',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected inputNames: (keyof this & string)[] = [
|
||||||
|
'context',
|
||||||
|
'browseByType',
|
||||||
|
'displayTitle',
|
||||||
|
'scope',
|
||||||
|
];
|
||||||
|
|
||||||
|
public getComponent(): GenericConstructor<Component> {
|
||||||
|
return getComponentByBrowseByType(this.browseByType, this.context, this.themeService.getThemeName());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import { Directive, ViewContainerRef } from '@angular/core';
|
import { Directive, ViewContainerRef } from '@angular/core';
|
||||||
|
|
||||||
@Directive({
|
|
||||||
selector: '[dsAdvancedWorkflowActions]',
|
|
||||||
})
|
|
||||||
/**
|
/**
|
||||||
* Directive used as a hook to know where to inject the dynamic Advanced Claimed Task Actions component
|
* Directive used as a hook to know where to inject the dynamic loaded component
|
||||||
*/
|
*/
|
||||||
export class AdvancedWorkflowActionsDirective {
|
@Directive({
|
||||||
|
selector: '[dsDynamicComponentLoader]'
|
||||||
|
})
|
||||||
|
export class DynamicComponentLoaderDirective {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public viewContainerRef: ViewContainerRef,
|
public viewContainerRef: ViewContainerRef,
|
@@ -1,28 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
|
|
||||||
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
|
||||||
import { BrowseBySwitcherComponent } from './browse-by-switcher.component';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Themed wrapper for BrowseBySwitcherComponent
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-themed-browse-by-switcher',
|
|
||||||
styleUrls: [],
|
|
||||||
templateUrl: '../../shared/theme-support/themed.component.html'
|
|
||||||
})
|
|
||||||
export class ThemedBrowseBySwitcherComponent extends ThemedComponent<BrowseBySwitcherComponent> {
|
|
||||||
protected getComponentName(): string {
|
|
||||||
return 'BrowseBySwitcherComponent';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected importThemedComponent(themeName: string): Promise<any> {
|
|
||||||
return import(`../../../themes/${themeName}/app/browse-by/browse-by-switcher/browse-by-switcher.component`);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected importUnthemedComponent(): Promise<any> {
|
|
||||||
return import(`./browse-by-switcher.component`);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@@ -1,28 +0,0 @@
|
|||||||
import { Component } from '@angular/core';
|
|
||||||
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
|
||||||
import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
|
||||||
import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page.component';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-themed-browse-by-taxonomy-page',
|
|
||||||
templateUrl: '../../shared/theme-support/themed.component.html',
|
|
||||||
styleUrls: []
|
|
||||||
})
|
|
||||||
/**
|
|
||||||
* Themed wrapper for BrowseByTaxonomyPageComponent
|
|
||||||
*/
|
|
||||||
@rendersBrowseBy('hierarchy')
|
|
||||||
export class ThemedBrowseByTaxonomyPageComponent extends ThemedComponent<BrowseByTaxonomyPageComponent>{
|
|
||||||
|
|
||||||
protected getComponentName(): string {
|
|
||||||
return 'BrowseByTaxonomyPageComponent';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected importThemedComponent(themeName: string): Promise<any> {
|
|
||||||
return import(`../../../themes/${themeName}/app/browse-by/browse-by-taxonomy-page/browse-by-taxonomy-page.component`);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected importUnthemedComponent(): Promise<any> {
|
|
||||||
return import(`./browse-by-taxonomy-page.component`);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,5 +1,11 @@
|
|||||||
<div class="container">
|
<section>
|
||||||
<h1>{{ ('browse.taxonomy_' + vocabularyName + '.title') | translate }}</h1>
|
<h1 *ngIf="displayTitle">
|
||||||
|
{{ ('browse.title') | translate:{
|
||||||
|
field: 'browse.metadata.' + vocabularyName | translate,
|
||||||
|
startsWith: '',
|
||||||
|
value: '',
|
||||||
|
} }}
|
||||||
|
</h1>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<ds-vocabulary-treeview [vocabularyOptions]=vocabularyOptions
|
<ds-vocabulary-treeview [vocabularyOptions]=vocabularyOptions
|
||||||
[multiSelect]="true"
|
[multiSelect]="true"
|
||||||
@@ -12,4 +18,4 @@
|
|||||||
[queryParams]="queryParams"
|
[queryParams]="queryParams"
|
||||||
[queryParamsHandling]="'merge'">
|
[queryParamsHandling]="'merge'">
|
||||||
{{ 'browse.taxonomy.button' | translate }}</a>
|
{{ 'browse.taxonomy.button' | translate }}</a>
|
||||||
</div>
|
</section>
|
@@ -1,6 +1,5 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { BrowseByTaxonomyComponent } from './browse-by-taxonomy.component';
|
||||||
import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page.component';
|
|
||||||
import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
@@ -10,9 +9,9 @@ import { createDataWithBrowseDefinition } from '../browse-by-switcher/browse-by-
|
|||||||
import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model';
|
import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model';
|
||||||
import { ThemeService } from '../../shared/theme-support/theme.service';
|
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||||
|
|
||||||
describe('BrowseByTaxonomyPageComponent', () => {
|
describe('BrowseByTaxonomyComponent', () => {
|
||||||
let component: BrowseByTaxonomyPageComponent;
|
let component: BrowseByTaxonomyComponent;
|
||||||
let fixture: ComponentFixture<BrowseByTaxonomyPageComponent>;
|
let fixture: ComponentFixture<BrowseByTaxonomyComponent>;
|
||||||
let themeService: ThemeService;
|
let themeService: ThemeService;
|
||||||
let detail1: VocabularyEntryDetail;
|
let detail1: VocabularyEntryDetail;
|
||||||
let detail2: VocabularyEntryDetail;
|
let detail2: VocabularyEntryDetail;
|
||||||
@@ -29,7 +28,9 @@ describe('BrowseByTaxonomyPageComponent', () => {
|
|||||||
|
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [ TranslateModule.forRoot() ],
|
imports: [ TranslateModule.forRoot() ],
|
||||||
declarations: [ BrowseByTaxonomyPageComponent ],
|
declarations: [
|
||||||
|
BrowseByTaxonomyComponent,
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||||
{ provide: ThemeService, useValue: themeService },
|
{ provide: ThemeService, useValue: themeService },
|
||||||
@@ -40,8 +41,9 @@ describe('BrowseByTaxonomyPageComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(BrowseByTaxonomyPageComponent);
|
fixture = TestBed.createComponent(BrowseByTaxonomyComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
|
spyOn(component, 'updateQueryParams').and.callThrough();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
detail1 = new VocabularyEntryDetail();
|
detail1 = new VocabularyEntryDetail();
|
||||||
detail2 = new VocabularyEntryDetail();
|
detail2 = new VocabularyEntryDetail();
|
||||||
@@ -61,6 +63,7 @@ describe('BrowseByTaxonomyPageComponent', () => {
|
|||||||
expect(component.selectedItems).toContain(detail1);
|
expect(component.selectedItems).toContain(detail1);
|
||||||
expect(component.selectedItems.length).toBe(1);
|
expect(component.selectedItems.length).toBe(1);
|
||||||
expect(component.filterValues).toEqual(['HUMANITIES and RELIGION,equals'] );
|
expect(component.filterValues).toEqual(['HUMANITIES and RELIGION,equals'] );
|
||||||
|
expect(component.updateQueryParams).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle select event with multiple selected items', () => {
|
it('should handle select event with multiple selected items', () => {
|
||||||
@@ -70,6 +73,7 @@ describe('BrowseByTaxonomyPageComponent', () => {
|
|||||||
expect(component.selectedItems).toContain(detail1, detail2);
|
expect(component.selectedItems).toContain(detail1, detail2);
|
||||||
expect(component.selectedItems.length).toBe(2);
|
expect(component.selectedItems.length).toBe(2);
|
||||||
expect(component.filterValues).toEqual(['HUMANITIES and RELIGION,equals', 'TECHNOLOGY,equals'] );
|
expect(component.filterValues).toEqual(['HUMANITIES and RELIGION,equals', 'TECHNOLOGY,equals'] );
|
||||||
|
expect(component.updateQueryParams).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle deselect event', () => {
|
it('should handle deselect event', () => {
|
||||||
@@ -82,6 +86,33 @@ describe('BrowseByTaxonomyPageComponent', () => {
|
|||||||
expect(component.selectedItems).toContain(detail2);
|
expect(component.selectedItems).toContain(detail2);
|
||||||
expect(component.selectedItems.length).toBe(1);
|
expect(component.selectedItems.length).toBe(1);
|
||||||
expect(component.filterValues).toEqual(['TECHNOLOGY,equals'] );
|
expect(component.filterValues).toEqual(['TECHNOLOGY,equals'] );
|
||||||
|
expect(component.updateQueryParams).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateQueryParams', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
component.facetType = 'subject';
|
||||||
|
component.filterValues = ['HUMANITIES and RELIGION,equals', 'TECHNOLOGY,equals'];
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update the queryParams with the selected filterValues', () => {
|
||||||
|
component.updateQueryParams();
|
||||||
|
|
||||||
|
expect(component.queryParams).toEqual({
|
||||||
|
'f.subject': ['HUMANITIES and RELIGION,equals', 'TECHNOLOGY,equals'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include the scope if present', () => {
|
||||||
|
component.scope = '67f849f1-2499-4872-8c61-9e2b47d71068';
|
||||||
|
|
||||||
|
component.updateQueryParams();
|
||||||
|
|
||||||
|
expect(component.queryParams).toEqual({
|
||||||
|
'f.subject': ['HUMANITIES and RELIGION,equals', 'TECHNOLOGY,equals'],
|
||||||
|
'scope': '67f849f1-2499-4872-8c61-9e2b47d71068',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
@@ -1,24 +1,48 @@
|
|||||||
import { Component, OnInit, Inject, OnDestroy } from '@angular/core';
|
import { Component, OnInit, OnChanges, OnDestroy, Input } from '@angular/core';
|
||||||
import { VocabularyOptions } from '../../core/submission/vocabularies/models/vocabulary-options.model';
|
import { VocabularyOptions } from '../../core/submission/vocabularies/models/vocabulary-options.model';
|
||||||
import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
import { VocabularyEntryDetail } from '../../core/submission/vocabularies/models/vocabulary-entry-detail.model';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute, Params } from '@angular/router';
|
||||||
import { Observable, Subscription } from 'rxjs';
|
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||||
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
import { BrowseDefinition } from '../../core/shared/browse-definition.model';
|
||||||
import { GenericConstructor } from '../../core/shared/generic-constructor';
|
import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||||
import { BROWSE_BY_COMPONENT_FACTORY } from '../browse-by-switcher/browse-by-decorator';
|
|
||||||
import { map } from 'rxjs/operators';
|
import { map } from 'rxjs/operators';
|
||||||
import { ThemeService } from 'src/app/shared/theme-support/theme.service';
|
|
||||||
import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model';
|
import { HierarchicalBrowseDefinition } from '../../core/shared/hierarchical-browse-definition.model';
|
||||||
|
import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type';
|
||||||
|
import { Context } from '../../core/shared/context.model';
|
||||||
|
import { hasValue } from '../../shared/empty.util';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-browse-by-taxonomy-page',
|
selector: 'ds-browse-by-taxonomy',
|
||||||
templateUrl: './browse-by-taxonomy-page.component.html',
|
templateUrl: './browse-by-taxonomy.component.html',
|
||||||
styleUrls: ['./browse-by-taxonomy-page.component.scss']
|
styleUrls: ['./browse-by-taxonomy.component.scss'],
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* Component for browsing items by metadata in a hierarchical controlled vocabulary
|
* Component for browsing items by metadata in a hierarchical controlled vocabulary
|
||||||
*/
|
*/
|
||||||
export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy {
|
@rendersBrowseBy(BrowseByDataType.Hierarchy)
|
||||||
|
export class BrowseByTaxonomyComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The optional context
|
||||||
|
*/
|
||||||
|
@Input() context: Context;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link BrowseByDataType} of this Component
|
||||||
|
*/
|
||||||
|
@Input() browseByType: BrowseByDataType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the {@link Community} or {@link Collection} of the scope to display
|
||||||
|
*/
|
||||||
|
@Input() scope: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display the h1 title in the section
|
||||||
|
*/
|
||||||
|
@Input() displayTitle = true;
|
||||||
|
|
||||||
|
scope$: BehaviorSubject<string> = new BehaviorSubject(undefined);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link VocabularyOptions} object
|
* The {@link VocabularyOptions} object
|
||||||
@@ -48,35 +72,41 @@ export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy {
|
|||||||
/**
|
/**
|
||||||
* The parameters used in the URL
|
* The parameters used in the URL
|
||||||
*/
|
*/
|
||||||
queryParams: any;
|
queryParams: Params;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolved browse-by component
|
* Resolved browse-by definition
|
||||||
*/
|
*/
|
||||||
browseByComponent: Observable<any>;
|
browseDefinition$: Observable<BrowseDefinition>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscriptions to track
|
* Subscriptions to track
|
||||||
*/
|
*/
|
||||||
browseByComponentSubs: Subscription[] = [];
|
subs: Subscription[] = [];
|
||||||
|
|
||||||
public constructor( protected route: ActivatedRoute,
|
public constructor(
|
||||||
protected themeService: ThemeService,
|
protected route: ActivatedRoute,
|
||||||
@Inject(BROWSE_BY_COMPONENT_FACTORY) private getComponentByBrowseByType: (browseByType, theme) => GenericConstructor<any>) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.browseByComponent = this.route.data.pipe(
|
this.browseDefinition$ = this.route.data.pipe(
|
||||||
map((data: { browseDefinition: BrowseDefinition }) => {
|
map((data: { browseDefinition: BrowseDefinition }) => {
|
||||||
this.getComponentByBrowseByType(data.browseDefinition.getRenderType(), this.themeService.getThemeName());
|
|
||||||
return data.browseDefinition;
|
return data.browseDefinition;
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
this.browseByComponentSubs.push(this.browseByComponent.subscribe((browseDefinition: HierarchicalBrowseDefinition) => {
|
this.subs.push(this.browseDefinition$.subscribe((browseDefinition: HierarchicalBrowseDefinition) => {
|
||||||
this.facetType = browseDefinition.facetType;
|
this.facetType = browseDefinition.facetType;
|
||||||
this.vocabularyName = browseDefinition.vocabulary;
|
this.vocabularyName = browseDefinition.vocabulary;
|
||||||
this.vocabularyOptions = { name: this.vocabularyName, closed: true };
|
this.vocabularyOptions = { name: this.vocabularyName, closed: true };
|
||||||
}));
|
}));
|
||||||
|
this.subs.push(this.scope$.subscribe(() => {
|
||||||
|
this.updateQueryParams();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(): void {
|
||||||
|
this.scope$.next(this.scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,9 +116,9 @@ export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy {
|
|||||||
* @param detail VocabularyEntryDetail to be added
|
* @param detail VocabularyEntryDetail to be added
|
||||||
*/
|
*/
|
||||||
onSelect(detail: VocabularyEntryDetail): void {
|
onSelect(detail: VocabularyEntryDetail): void {
|
||||||
this.selectedItems.push(detail);
|
this.selectedItems.push(detail);
|
||||||
this.filterValues = this.selectedItems
|
this.filterValues = this.selectedItems
|
||||||
.map((item: VocabularyEntryDetail) => `${item.value},equals`);
|
.map((item: VocabularyEntryDetail) => `${item.value},equals`);
|
||||||
this.updateQueryParams();
|
this.updateQueryParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,21 +128,28 @@ export class BrowseByTaxonomyPageComponent implements OnInit, OnDestroy {
|
|||||||
* @param detail VocabularyEntryDetail to be removed
|
* @param detail VocabularyEntryDetail to be removed
|
||||||
*/
|
*/
|
||||||
onDeselect(detail: VocabularyEntryDetail): void {
|
onDeselect(detail: VocabularyEntryDetail): void {
|
||||||
this.selectedItems = this.selectedItems.filter((entry: VocabularyEntryDetail) => { return entry.id !== detail.id; });
|
this.selectedItems = this.selectedItems.filter((entry: VocabularyEntryDetail) => {
|
||||||
this.filterValues = this.filterValues.filter((value: string) => { return value !== `${detail.value},equals`; });
|
return entry.id !== detail.id;
|
||||||
|
});
|
||||||
|
this.filterValues = this.filterValues.filter((value: string) => {
|
||||||
|
return value !== `${detail.value},equals`;
|
||||||
|
});
|
||||||
this.updateQueryParams();
|
this.updateQueryParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates queryParams based on the current facetType and filterValues.
|
* Updates queryParams based on the current facetType and filterValues.
|
||||||
*/
|
*/
|
||||||
private updateQueryParams(): void {
|
updateQueryParams(): void {
|
||||||
this.queryParams = {
|
this.queryParams = {
|
||||||
['f.' + this.facetType]: this.filterValues
|
['f.' + this.facetType]: this.filterValues
|
||||||
};
|
};
|
||||||
|
if (hasValue(this.scope)) {
|
||||||
|
this.queryParams.scope = this.scope;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.browseByComponentSubs.forEach((sub: Subscription) => sub.unsubscribe());
|
this.subs.forEach((sub: Subscription) => sub.unsubscribe());
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,64 +0,0 @@
|
|||||||
import { combineLatest as observableCombineLatest } from 'rxjs';
|
|
||||||
import { Component, Inject } from '@angular/core';
|
|
||||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
|
||||||
import { hasValue } from '../../shared/empty.util';
|
|
||||||
import {
|
|
||||||
BrowseByMetadataPageComponent,
|
|
||||||
browseParamsToOptions, getBrowseSearchOptions
|
|
||||||
} from '../browse-by-metadata-page/browse-by-metadata-page.component';
|
|
||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
|
||||||
import { BrowseService } from '../../core/browse/browse.service';
|
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
|
||||||
import { map } from 'rxjs/operators';
|
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
|
||||||
import { AppConfig, APP_CONFIG } from '../../../config/app-config.interface';
|
|
||||||
import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-browse-by-title-page',
|
|
||||||
styleUrls: ['../browse-by-metadata-page/browse-by-metadata-page.component.scss'],
|
|
||||||
templateUrl: '../browse-by-metadata-page/browse-by-metadata-page.component.html'
|
|
||||||
})
|
|
||||||
/**
|
|
||||||
* Component for browsing items by title (dc.title)
|
|
||||||
*/
|
|
||||||
export class BrowseByTitlePageComponent extends BrowseByMetadataPageComponent {
|
|
||||||
|
|
||||||
public constructor(protected route: ActivatedRoute,
|
|
||||||
protected browseService: BrowseService,
|
|
||||||
protected dsoService: DSpaceObjectDataService,
|
|
||||||
protected paginationService: PaginationService,
|
|
||||||
protected router: Router,
|
|
||||||
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
|
||||||
public dsoNameService: DSONameService,
|
|
||||||
) {
|
|
||||||
super(route, browseService, dsoService, paginationService, router, appConfig, dsoNameService);
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
|
||||||
const sortConfig = new SortOptions('dc.title', SortDirection.ASC);
|
|
||||||
// include the thumbnail configuration in browse search options
|
|
||||||
this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig, this.fetchThumbnails));
|
|
||||||
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
|
||||||
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
|
|
||||||
this.subs.push(
|
|
||||||
observableCombineLatest([this.route.params, this.route.queryParams, this.currentPagination$, this.currentSort$]).pipe(
|
|
||||||
map(([routeParams, queryParams, currentPage, currentSort]) => {
|
|
||||||
return [Object.assign({}, routeParams, queryParams),currentPage,currentSort];
|
|
||||||
})
|
|
||||||
).subscribe(([params, currentPage, currentSort]: [Params, PaginationComponentOptions, SortOptions]) => {
|
|
||||||
this.startsWith = +params.startsWith || params.startsWith;
|
|
||||||
this.browseId = params.id || this.defaultBrowseId;
|
|
||||||
this.updatePageWithItems(browseParamsToOptions(params, currentPage, currentSort, this.browseId, this.fetchThumbnails), undefined, undefined);
|
|
||||||
this.updateParent(params.scope);
|
|
||||||
this.updateLogo();
|
|
||||||
}));
|
|
||||||
this.updateStartsWithTextOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.subs.filter((sub) => hasValue(sub)).forEach((sub) => sub.unsubscribe());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
import {Component} from '@angular/core';
|
|
||||||
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
|
||||||
import { BrowseByTitlePageComponent } from './browse-by-title-page.component';
|
|
||||||
import {BrowseByDataType, rendersBrowseBy} from '../browse-by-switcher/browse-by-decorator';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Themed wrapper for BrowseByTitlePageComponent
|
|
||||||
*/
|
|
||||||
@Component({
|
|
||||||
selector: 'ds-themed-browse-by-title-page',
|
|
||||||
styleUrls: [],
|
|
||||||
templateUrl: '../../shared/theme-support/themed.component.html',
|
|
||||||
})
|
|
||||||
|
|
||||||
@rendersBrowseBy(BrowseByDataType.Title)
|
|
||||||
export class ThemedBrowseByTitlePageComponent
|
|
||||||
extends ThemedComponent<BrowseByTitlePageComponent> {
|
|
||||||
protected getComponentName(): string {
|
|
||||||
return 'BrowseByTitlePageComponent';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected importThemedComponent(themeName: string): Promise<any> {
|
|
||||||
return import(`../../../themes/${themeName}/app/browse-by/browse-by-title-page/browse-by-title-page.component`);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected importUnthemedComponent(): Promise<any> {
|
|
||||||
return import(`./browse-by-title-page.component`);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -9,8 +9,8 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe';
|
import { EnumKeysPipe } from '../../shared/utils/enum-keys-pipe';
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { toRemoteData } from '../browse-by-metadata-page/browse-by-metadata-page.component.spec';
|
import { toRemoteData } from '../browse-by-metadata/browse-by-metadata.component.spec';
|
||||||
import { BrowseByTitlePageComponent } from './browse-by-title-page.component';
|
import { BrowseByTitleComponent } from './browse-by-title.component';
|
||||||
import { ItemDataService } from '../../core/data/item-data.service';
|
import { ItemDataService } from '../../core/data/item-data.service';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
import { DSpaceObjectDataService } from '../../core/data/dspace-object-data.service';
|
||||||
@@ -24,9 +24,9 @@ import { APP_CONFIG } from '../../../config/app-config.interface';
|
|||||||
import { environment } from '../../../environments/environment';
|
import { environment } from '../../../environments/environment';
|
||||||
|
|
||||||
|
|
||||||
describe('BrowseByTitlePageComponent', () => {
|
describe('BrowseByTitleComponent', () => {
|
||||||
let comp: BrowseByTitlePageComponent;
|
let comp: BrowseByTitleComponent;
|
||||||
let fixture: ComponentFixture<BrowseByTitlePageComponent>;
|
let fixture: ComponentFixture<BrowseByTitleComponent>;
|
||||||
let itemDataService: ItemDataService;
|
let itemDataService: ItemDataService;
|
||||||
let route: ActivatedRoute;
|
let route: ActivatedRoute;
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ describe('BrowseByTitlePageComponent', () => {
|
|||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
imports: [CommonModule, RouterTestingModule.withRoutes([]), TranslateModule.forRoot(), NgbModule],
|
||||||
declarations: [BrowseByTitlePageComponent, EnumKeysPipe, VarDirective],
|
declarations: [BrowseByTitleComponent, EnumKeysPipe, VarDirective],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
{ provide: ActivatedRoute, useValue: activatedRouteStub },
|
||||||
{ provide: BrowseService, useValue: mockBrowseService },
|
{ provide: BrowseService, useValue: mockBrowseService },
|
||||||
@@ -85,7 +85,7 @@ describe('BrowseByTitlePageComponent', () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(BrowseByTitlePageComponent);
|
fixture = TestBed.createComponent(BrowseByTitleComponent);
|
||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
itemDataService = (comp as any).itemDataService;
|
itemDataService = (comp as any).itemDataService;
|
@@ -0,0 +1,44 @@
|
|||||||
|
import { combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Params } from '@angular/router';
|
||||||
|
import {
|
||||||
|
BrowseByMetadataComponent,
|
||||||
|
browseParamsToOptions, getBrowseSearchOptions
|
||||||
|
} from '../browse-by-metadata/browse-by-metadata.component';
|
||||||
|
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
||||||
|
import { rendersBrowseBy } from '../browse-by-switcher/browse-by-decorator';
|
||||||
|
import { BrowseByDataType } from '../browse-by-switcher/browse-by-data-type';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-browse-by-title',
|
||||||
|
styleUrls: ['../browse-by-metadata/browse-by-metadata.component.scss'],
|
||||||
|
templateUrl: '../browse-by-metadata/browse-by-metadata.component.html'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Component for browsing items by title (dc.title)
|
||||||
|
*/
|
||||||
|
@rendersBrowseBy(BrowseByDataType.Title)
|
||||||
|
export class BrowseByTitleComponent extends BrowseByMetadataComponent implements OnInit {
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
const sortConfig = new SortOptions('dc.title', SortDirection.ASC);
|
||||||
|
// include the thumbnail configuration in browse search options
|
||||||
|
this.updatePage(getBrowseSearchOptions(this.defaultBrowseId, this.paginationConfig, sortConfig, this.fetchThumbnails));
|
||||||
|
this.currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
||||||
|
this.currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, sortConfig);
|
||||||
|
this.subs.push(
|
||||||
|
observableCombineLatest([this.route.params, this.route.queryParams, this.scope$, this.currentPagination$, this.currentSort$]).pipe(
|
||||||
|
map(([routeParams, queryParams, scope, currentPage, currentSort]) => {
|
||||||
|
return [Object.assign({}, routeParams, queryParams), scope, currentPage, currentSort];
|
||||||
|
})
|
||||||
|
).subscribe(([params, scope, currentPage, currentSort]: [Params, string, PaginationComponentOptions, SortOptions]) => {
|
||||||
|
this.startsWith = +params.startsWith || params.startsWith;
|
||||||
|
this.browseId = params.id || this.defaultBrowseId;
|
||||||
|
this.updatePageWithItems(browseParamsToOptions(params, scope, currentPage, currentSort, this.browseId, this.fetchThumbnails), undefined, undefined);
|
||||||
|
}));
|
||||||
|
this.updateStartsWithTextOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,50 +1,42 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { BrowseByTitlePageComponent } from './browse-by-title-page/browse-by-title-page.component';
|
import { BrowseByTitleComponent } from './browse-by-title/browse-by-title.component';
|
||||||
import { BrowseByMetadataPageComponent } from './browse-by-metadata-page/browse-by-metadata-page.component';
|
import { BrowseByMetadataComponent } from './browse-by-metadata/browse-by-metadata.component';
|
||||||
import { BrowseByDatePageComponent } from './browse-by-date-page/browse-by-date-page.component';
|
import { BrowseByDateComponent } from './browse-by-date/browse-by-date.component';
|
||||||
import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switcher.component';
|
import { BrowseBySwitcherComponent } from './browse-by-switcher/browse-by-switcher.component';
|
||||||
import { BrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page/browse-by-taxonomy-page.component';
|
import { BrowseByTaxonomyComponent } from './browse-by-taxonomy/browse-by-taxonomy.component';
|
||||||
import { ThemedBrowseBySwitcherComponent } from './browse-by-switcher/themed-browse-by-switcher.component';
|
|
||||||
import { ComcolModule } from '../shared/comcol/comcol.module';
|
|
||||||
import { ThemedBrowseByMetadataPageComponent } from './browse-by-metadata-page/themed-browse-by-metadata-page.component';
|
|
||||||
import { ThemedBrowseByDatePageComponent } from './browse-by-date-page/themed-browse-by-date-page.component';
|
|
||||||
import { ThemedBrowseByTitlePageComponent } from './browse-by-title-page/themed-browse-by-title-page.component';
|
|
||||||
import { ThemedBrowseByTaxonomyPageComponent } from './browse-by-taxonomy-page/themed-browse-by-taxonomy-page.component';
|
|
||||||
import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.module';
|
import { SharedBrowseByModule } from '../shared/browse-by/shared-browse-by.module';
|
||||||
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
||||||
import { FormModule } from '../shared/form/form.module';
|
import { FormModule } from '../shared/form/form.module';
|
||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
|
||||||
|
const DECLARATIONS = [
|
||||||
|
BrowseBySwitcherComponent,
|
||||||
|
];
|
||||||
|
|
||||||
const ENTRY_COMPONENTS = [
|
const ENTRY_COMPONENTS = [
|
||||||
// put only entry components that use custom decorator
|
// put only entry components that use custom decorator
|
||||||
BrowseByTitlePageComponent,
|
BrowseByTitleComponent,
|
||||||
BrowseByMetadataPageComponent,
|
BrowseByMetadataComponent,
|
||||||
BrowseByDatePageComponent,
|
BrowseByDateComponent,
|
||||||
BrowseByTaxonomyPageComponent,
|
BrowseByTaxonomyComponent,
|
||||||
|
|
||||||
ThemedBrowseByMetadataPageComponent,
|
|
||||||
ThemedBrowseByDatePageComponent,
|
|
||||||
ThemedBrowseByTitlePageComponent,
|
|
||||||
ThemedBrowseByTaxonomyPageComponent,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
SharedBrowseByModule,
|
SharedBrowseByModule,
|
||||||
CommonModule,
|
CommonModule,
|
||||||
ComcolModule,
|
|
||||||
DsoPageModule,
|
DsoPageModule,
|
||||||
FormModule,
|
FormModule,
|
||||||
SharedModule,
|
SharedModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
BrowseBySwitcherComponent,
|
...DECLARATIONS,
|
||||||
ThemedBrowseBySwitcherComponent,
|
|
||||||
...ENTRY_COMPONENTS
|
...ENTRY_COMPONENTS
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
BrowseBySwitcherComponent
|
...DECLARATIONS,
|
||||||
|
...ENTRY_COMPONENTS,
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class BrowseByModule {
|
export class BrowseByModule {
|
||||||
|
@@ -22,6 +22,10 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
|||||||
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
|
import { ThemedCollectionPageComponent } from './themed-collection-page.component';
|
||||||
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
||||||
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||||
|
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
|
||||||
|
import { BrowseByGuard } from '../browse-by/browse-by-guard';
|
||||||
|
import { BrowseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
|
||||||
|
import { CollectionRecentlyAddedComponent } from './sections/recently-added/collection-recently-added.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -65,7 +69,23 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
|||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: ThemedCollectionPageComponent,
|
component: ThemedCollectionPageComponent,
|
||||||
pathMatch: 'full',
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
pathMatch: 'full',
|
||||||
|
component: CollectionRecentlyAddedComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'browse/:id',
|
||||||
|
pathMatch: 'full',
|
||||||
|
component: ComcolBrowseByComponent,
|
||||||
|
canActivate: [BrowseByGuard],
|
||||||
|
resolve: {
|
||||||
|
breadcrumb: BrowseByI18nBreadcrumbResolver,
|
||||||
|
},
|
||||||
|
data: { breadcrumbKey: 'browse.metadata' },
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
data: {
|
data: {
|
||||||
|
@@ -1,78 +1,61 @@
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="collection-page"
|
<div class="collection-page"
|
||||||
*ngVar="(collectionRD$ | async) as collectionRD">
|
*ngVar="(collectionRD$ | async) as collectionRD">
|
||||||
<div *ngIf="collectionRD?.hasSucceeded" @fadeInOut>
|
<div *ngIf="collectionRD?.hasSucceeded" @fadeInOut>
|
||||||
<div *ngIf="collectionRD?.payload as collection">
|
<div *ngIf="collectionRD?.payload as collection">
|
||||||
<ds-view-tracker [object]="collection"></ds-view-tracker>
|
<ds-view-tracker [object]="collection"></ds-view-tracker>
|
||||||
<div class="d-flex flex-row border-bottom mb-4 pb-4">
|
<div class="d-flex flex-row border-bottom mb-4 pb-4">
|
||||||
<header class="comcol-header mr-auto">
|
<header class="comcol-header mr-auto">
|
||||||
<!-- Collection Name -->
|
<!-- Collection Name -->
|
||||||
<ds-comcol-page-header
|
<ds-comcol-page-header
|
||||||
[name]="dsoNameService.getName(collection)">
|
[name]="dsoNameService.getName(collection)">
|
||||||
</ds-comcol-page-header>
|
</ds-comcol-page-header>
|
||||||
<!-- Collection logo -->
|
<!-- Collection logo -->
|
||||||
<ds-comcol-page-logo *ngIf="logoRD$"
|
<ds-comcol-page-logo *ngIf="logoRD$"
|
||||||
[logo]="(logoRD$ | async)?.payload"
|
[logo]="(logoRD$ | async)?.payload"
|
||||||
[alternateText]="'Collection Logo'">
|
[alternateText]="'collection.logo' | translate">
|
||||||
</ds-comcol-page-logo>
|
</ds-comcol-page-logo>
|
||||||
|
|
||||||
<!-- Handle -->
|
<!-- Handle -->
|
||||||
<ds-themed-comcol-page-handle
|
<ds-themed-comcol-page-handle
|
||||||
[content]="collection.handle"
|
[content]="collection.handle"
|
||||||
[title]="'collection.page.handle'" >
|
[title]="'collection.page.handle'">
|
||||||
</ds-themed-comcol-page-handle>
|
</ds-themed-comcol-page-handle>
|
||||||
<!-- Introductory text -->
|
<!-- Introductory text -->
|
||||||
<ds-comcol-page-content
|
<ds-comcol-page-content
|
||||||
[content]="collection.introductoryText"
|
[content]="collection.introductoryText"
|
||||||
[hasInnerHtml]="true">
|
[hasInnerHtml]="true">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
<!-- News -->
|
<!-- News -->
|
||||||
<ds-comcol-page-content
|
<ds-comcol-page-content
|
||||||
[content]="collection.sidebarText"
|
[content]="collection.sidebarText"
|
||||||
[hasInnerHtml]="true"
|
[hasInnerHtml]="true"
|
||||||
[title]="'collection.page.news'">
|
[title]="'collection.page.news'">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
</header>
|
</header>
|
||||||
<ds-dso-edit-menu></ds-dso-edit-menu>
|
<ds-dso-edit-menu></ds-dso-edit-menu>
|
||||||
</div>
|
</div>
|
||||||
<section class="comcol-page-browse-section">
|
<section class="comcol-page-browse-section">
|
||||||
<!-- Browse-By Links -->
|
<!-- Browse-By Links -->
|
||||||
<ds-themed-comcol-page-browse-by
|
<ds-themed-comcol-page-browse-by
|
||||||
[id]="collection.id"
|
[id]="collection.id"
|
||||||
[contentType]="collection.type">
|
[contentType]="collection.type">
|
||||||
</ds-themed-comcol-page-browse-by>
|
</ds-themed-comcol-page-browse-by>
|
||||||
|
|
||||||
<ng-container *ngVar="(itemRD$ | async) as itemRD">
|
<router-outlet></router-outlet>
|
||||||
<div class="mt-4" *ngIf="itemRD?.hasSucceeded" @fadeIn>
|
</section>
|
||||||
<h3 class="sr-only">{{'collection.page.browse.recent.head' | translate}}</h3>
|
<footer *ngIf="collection.copyrightText" class="border-top my-5 pt-4">
|
||||||
<ds-viewable-collection
|
|
||||||
[config]="paginationConfig"
|
|
||||||
[sortConfig]="sortConfig"
|
|
||||||
[objects]="itemRD"
|
|
||||||
[hideGear]="true">
|
|
||||||
</ds-viewable-collection>
|
|
||||||
</div>
|
|
||||||
<ds-error *ngIf="itemRD?.hasFailed"
|
|
||||||
message="{{'error.recent-submissions' | translate}}"></ds-error>
|
|
||||||
<ds-themed-loading *ngIf="!itemRD || itemRD.isLoading"
|
|
||||||
message="{{'loading.recent-submissions' | translate}}"></ds-themed-loading>
|
|
||||||
<div *ngIf="!itemRD?.isLoading && itemRD?.payload?.page.length === 0" class="alert alert-info w-100" role="alert">
|
|
||||||
{{'collection.page.browse.recent.empty' | translate}}
|
|
||||||
</div>
|
|
||||||
</ng-container>
|
|
||||||
</section>
|
|
||||||
<footer *ngIf="collection.copyrightText" class="border-top my-5 pt-4">
|
|
||||||
<!-- Copyright -->
|
<!-- Copyright -->
|
||||||
<ds-comcol-page-content
|
<ds-comcol-page-content
|
||||||
[content]="collection.copyrightText"
|
[content]="collection.copyrightText"
|
||||||
[hasInnerHtml]="true">
|
[hasInnerHtml]="true">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ds-error *ngIf="collectionRD?.hasFailed"
|
<ds-error *ngIf="collectionRD?.hasFailed"
|
||||||
message="{{'error.collection' | translate}}"></ds-error>
|
message="{{'error.collection' | translate}}"></ds-error>
|
||||||
<ds-themed-loading *ngIf="collectionRD?.isLoading"
|
<ds-themed-loading *ngIf="collectionRD?.isLoading"
|
||||||
message="{{'loading.collection' | translate}}"></ds-themed-loading>
|
message="{{'loading.collection' | translate}}"></ds-themed-loading>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,36 +1,21 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit, Inject } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Observable, Subject } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { filter, map, mergeMap, startWith, switchMap, take } from 'rxjs/operators';
|
import { filter, map, mergeMap, take } from 'rxjs/operators';
|
||||||
import { PaginatedSearchOptions } from '../shared/search/models/paginated-search-options.model';
|
import { SortOptions } from '../core/cache/models/sort-options.model';
|
||||||
import { SearchService } from '../core/shared/search/search.service';
|
|
||||||
import { SortDirection, SortOptions } from '../core/cache/models/sort-options.model';
|
|
||||||
import { CollectionDataService } from '../core/data/collection-data.service';
|
|
||||||
import { PaginatedList } from '../core/data/paginated-list.model';
|
|
||||||
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 { Collection } from '../core/shared/collection.model';
|
import { Collection } from '../core/shared/collection.model';
|
||||||
import { DSpaceObjectType } from '../core/shared/dspace-object-type.model';
|
import { getAllSucceededRemoteDataPayload } from '../core/shared/operators';
|
||||||
import { Item } from '../core/shared/item.model';
|
|
||||||
import {
|
|
||||||
getAllSucceededRemoteDataPayload,
|
|
||||||
getFirstSucceededRemoteData,
|
|
||||||
toDSpaceObjectListRD
|
|
||||||
} from '../core/shared/operators';
|
|
||||||
|
|
||||||
import { fadeIn, fadeInOut } from '../shared/animations/fade';
|
import { fadeIn, fadeInOut } from '../shared/animations/fade';
|
||||||
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
import { hasValue, isNotEmpty } from '../shared/empty.util';
|
||||||
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../shared/pagination/pagination-component-options.model';
|
||||||
import { AuthService } from '../core/auth/auth.service';
|
import { AuthService } from '../core/auth/auth.service';
|
||||||
import { PaginationService } from '../core/pagination/pagination.service';
|
|
||||||
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
|
import { AuthorizationDataService } from '../core/data/feature-authorization/authorization-data.service';
|
||||||
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
import { FeatureID } from '../core/data/feature-authorization/feature-id';
|
||||||
import { getCollectionPageRoute } from './collection-page-routing-paths';
|
import { getCollectionPageRoute } from './collection-page-routing-paths';
|
||||||
import { redirectOn4xx } from '../core/shared/authorized.operators';
|
import { redirectOn4xx } from '../core/shared/authorized.operators';
|
||||||
import { BROWSE_LINKS_TO_FOLLOW } from '../core/browse/browse.service';
|
|
||||||
import { DSONameService } from '../core/breadcrumbs/dso-name.service';
|
import { DSONameService } from '../core/breadcrumbs/dso-name.service';
|
||||||
import { APP_CONFIG, AppConfig } from '../../../src/config/app-config.interface';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-collection-page',
|
selector: 'ds-collection-page',
|
||||||
@@ -44,14 +29,9 @@ import { APP_CONFIG, AppConfig } from '../../../src/config/app-config.interface'
|
|||||||
})
|
})
|
||||||
export class CollectionPageComponent implements OnInit {
|
export class CollectionPageComponent implements OnInit {
|
||||||
collectionRD$: Observable<RemoteData<Collection>>;
|
collectionRD$: Observable<RemoteData<Collection>>;
|
||||||
itemRD$: Observable<RemoteData<PaginatedList<Item>>>;
|
|
||||||
logoRD$: Observable<RemoteData<Bitstream>>;
|
logoRD$: Observable<RemoteData<Bitstream>>;
|
||||||
paginationConfig: PaginationComponentOptions;
|
paginationConfig: PaginationComponentOptions;
|
||||||
sortConfig: SortOptions;
|
sortConfig: SortOptions;
|
||||||
private paginationChanges$: Subject<{
|
|
||||||
paginationConfig: PaginationComponentOptions,
|
|
||||||
sortConfig: SortOptions
|
|
||||||
}>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the current user is a Community admin
|
* Whether the current user is a Community admin
|
||||||
@@ -64,23 +44,12 @@ export class CollectionPageComponent implements OnInit {
|
|||||||
collectionPageRoute$: Observable<string>;
|
collectionPageRoute$: Observable<string>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private collectionDataService: CollectionDataService,
|
protected route: ActivatedRoute,
|
||||||
private searchService: SearchService,
|
protected router: Router,
|
||||||
private route: ActivatedRoute,
|
protected authService: AuthService,
|
||||||
private router: Router,
|
protected authorizationDataService: AuthorizationDataService,
|
||||||
private authService: AuthService,
|
|
||||||
private paginationService: PaginationService,
|
|
||||||
private authorizationDataService: AuthorizationDataService,
|
|
||||||
public dsoNameService: DSONameService,
|
public dsoNameService: DSONameService,
|
||||||
@Inject(APP_CONFIG) public appConfig: AppConfig,
|
|
||||||
) {
|
) {
|
||||||
this.paginationConfig = Object.assign(new PaginationComponentOptions(), {
|
|
||||||
id: 'cp',
|
|
||||||
currentPage: 1,
|
|
||||||
pageSize: this.appConfig.browseBy.pageSize,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.sortConfig = new SortOptions('dc.date.accessioned', SortDirection.DESC);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@@ -96,33 +65,6 @@ export class CollectionPageComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
this.isCollectionAdmin$ = this.authorizationDataService.isAuthorized(FeatureID.IsCollectionAdmin);
|
this.isCollectionAdmin$ = this.authorizationDataService.isAuthorized(FeatureID.IsCollectionAdmin);
|
||||||
|
|
||||||
this.paginationChanges$ = new BehaviorSubject({
|
|
||||||
paginationConfig: this.paginationConfig,
|
|
||||||
sortConfig: this.sortConfig
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentPagination$ = this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig);
|
|
||||||
const currentSort$ = this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig);
|
|
||||||
|
|
||||||
this.itemRD$ = observableCombineLatest([currentPagination$, currentSort$]).pipe(
|
|
||||||
switchMap(([currentPagination, currentSort]) => this.collectionRD$.pipe(
|
|
||||||
getFirstSucceededRemoteData(),
|
|
||||||
map((rd) => rd.payload.id),
|
|
||||||
switchMap((id: string) => {
|
|
||||||
return this.searchService.search<Item>(
|
|
||||||
new PaginatedSearchOptions({
|
|
||||||
scope: id,
|
|
||||||
pagination: currentPagination,
|
|
||||||
sort: currentSort,
|
|
||||||
dsoTypes: [DSpaceObjectType.ITEM]
|
|
||||||
}), null, true, true, ...BROWSE_LINKS_TO_FOLLOW)
|
|
||||||
.pipe(toDSpaceObjectListRD()) as Observable<RemoteData<PaginatedList<Item>>>;
|
|
||||||
}),
|
|
||||||
startWith(undefined) // Make sure switching pages shows loading component
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.collectionPageRoute$ = this.collectionRD$.pipe(
|
this.collectionPageRoute$ = this.collectionRD$.pipe(
|
||||||
getAllSucceededRemoteDataPayload(),
|
getAllSucceededRemoteDataPayload(),
|
||||||
map((collection) => getCollectionPageRoute(collection.id))
|
map((collection) => getCollectionPageRoute(collection.id))
|
||||||
@@ -133,9 +75,5 @@ export class CollectionPageComponent implements OnInit {
|
|||||||
return isNotEmpty(object);
|
return isNotEmpty(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
|
||||||
this.paginationService.clearPagination(this.paginationConfig.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,19 @@ import { ThemedCollectionPageComponent } from './themed-collection-page.componen
|
|||||||
import { ComcolModule } from '../shared/comcol/comcol.module';
|
import { ComcolModule } from '../shared/comcol/comcol.module';
|
||||||
import { DsoSharedModule } from '../dso-shared/dso-shared.module';
|
import { DsoSharedModule } from '../dso-shared/dso-shared.module';
|
||||||
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
||||||
|
import { BrowseByPageModule } from '../browse-by/browse-by-page.module';
|
||||||
|
import { CollectionRecentlyAddedComponent } from './sections/recently-added/collection-recently-added.component';
|
||||||
|
|
||||||
|
const DECLARATIONS = [
|
||||||
|
CollectionPageComponent,
|
||||||
|
ThemedCollectionPageComponent,
|
||||||
|
CreateCollectionPageComponent,
|
||||||
|
DeleteCollectionPageComponent,
|
||||||
|
EditItemTemplatePageComponent,
|
||||||
|
ThemedEditItemTemplatePageComponent,
|
||||||
|
CollectionItemMapperComponent,
|
||||||
|
CollectionRecentlyAddedComponent,
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -30,15 +43,10 @@ import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
|||||||
ComcolModule,
|
ComcolModule,
|
||||||
DsoSharedModule,
|
DsoSharedModule,
|
||||||
DsoPageModule,
|
DsoPageModule,
|
||||||
|
BrowseByPageModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
CollectionPageComponent,
|
...DECLARATIONS,
|
||||||
ThemedCollectionPageComponent,
|
|
||||||
CreateCollectionPageComponent,
|
|
||||||
DeleteCollectionPageComponent,
|
|
||||||
EditItemTemplatePageComponent,
|
|
||||||
ThemedEditItemTemplatePageComponent,
|
|
||||||
CollectionItemMapperComponent
|
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SearchService,
|
SearchService,
|
||||||
|
@@ -1,17 +1,17 @@
|
|||||||
<div class="container-fluid mb-2" *ngVar="(itemTemplateRD$ | async) as itemTemplateRD">
|
<div class="container-fluid mb-2" *ngVar="(itemTemplateRD$ | async) as itemTemplateRD">
|
||||||
<label>{{ 'collection.edit.template.label' | translate}}</label>
|
<span class="d-inline-block mb-2">{{ 'collection.edit.template.label' | translate}}</span>
|
||||||
<div class="button-row space-children-mr">
|
<div class="button-row space-children-mr">
|
||||||
<button *ngIf="!itemTemplateRD?.payload" class="btn btn-success" (click)="addItemTemplate()">
|
<button *ngIf="!itemTemplateRD?.payload" class="btn btn-success" (click)="addItemTemplate()">
|
||||||
<i class="fas fa-plus"></i>
|
<i class="fas fa-plus" aria-hidden="true"></i>
|
||||||
<span class="d-none d-sm-inline"> {{"collection.edit.template.add-button" | translate}}</span>
|
<span class="d-none d-sm-inline"> {{"collection.edit.template.add-button" | translate}}</span>
|
||||||
</button>
|
</button>
|
||||||
<button *ngIf="itemTemplateRD?.payload" class="btn btn-danger" (click)="deleteItemTemplate()">
|
<button *ngIf="itemTemplateRD?.payload" class="btn btn-danger" (click)="deleteItemTemplate()">
|
||||||
<i class="fas fa-trash-alt"></i>
|
<i class="fas fa-trash-alt" aria-hidden="true"></i>
|
||||||
<span class="d-none d-sm-inline"> {{"collection.edit.template.delete-button" | translate}}</span>
|
<span class="d-none d-sm-inline"> {{"collection.edit.template.delete-button" | translate}}</span>
|
||||||
</button>
|
</button>
|
||||||
<button *ngIf="itemTemplateRD?.payload" class="btn btn-primary"
|
<button *ngIf="itemTemplateRD?.payload" class="btn btn-primary"
|
||||||
[routerLink]="'/collections/' + (dsoRD$ | async)?.payload.uuid + '/itemtemplate'">
|
[routerLink]="'/collections/' + (dsoRD$ | async)?.payload.uuid + '/itemtemplate'">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit" aria-hidden="true"></i>
|
||||||
<span class="d-none d-sm-inline"> {{"collection.edit.template.edit-button" | translate}}</span>
|
<span class="d-none d-sm-inline"> {{"collection.edit.template.edit-button" | translate}}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -0,0 +1,18 @@
|
|||||||
|
<ng-container *ngVar="(itemRD$ | async) as itemRD">
|
||||||
|
<div class="mt-4" *ngIf="itemRD?.hasSucceeded" @fadeIn>
|
||||||
|
<h3 class="sr-only">{{'collection.page.browse.recent.head' | translate}}</h3>
|
||||||
|
<ds-viewable-collection
|
||||||
|
[config]="paginationConfig"
|
||||||
|
[sortConfig]="sortConfig"
|
||||||
|
[objects]="itemRD"
|
||||||
|
[hideGear]="true">
|
||||||
|
</ds-viewable-collection>
|
||||||
|
</div>
|
||||||
|
<ds-error *ngIf="itemRD?.hasFailed"
|
||||||
|
message="{{'error.recent-submissions' | translate}}"></ds-error>
|
||||||
|
<ds-themed-loading *ngIf="!itemRD || itemRD.isLoading"
|
||||||
|
message="{{'loading.recent-submissions' | translate}}"></ds-themed-loading>
|
||||||
|
<div *ngIf="!itemRD?.isLoading && itemRD?.payload?.page.length === 0" class="alert alert-info w-100" role="alert">
|
||||||
|
{{'collection.page.browse.recent.empty' | translate}}
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
@@ -0,0 +1,53 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { CollectionRecentlyAddedComponent } from './collection-recently-added.component';
|
||||||
|
import { APP_CONFIG } from '../../../../config/app-config.interface';
|
||||||
|
import { environment } from '../../../../environments/environment.test';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||||
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
|
import { PaginationServiceStub } from '../../../shared/testing/pagination-service.stub';
|
||||||
|
import { SearchServiceStub } from '../../../shared/testing/search-service.stub';
|
||||||
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
|
import { VarDirective } from '../../../shared/utils/var.directive';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
|
|
||||||
|
describe('CollectionRecentlyAddedComponent', () => {
|
||||||
|
let component: CollectionRecentlyAddedComponent;
|
||||||
|
let fixture: ComponentFixture<CollectionRecentlyAddedComponent>;
|
||||||
|
|
||||||
|
let activatedRoute: ActivatedRouteStub;
|
||||||
|
let paginationService: PaginationServiceStub;
|
||||||
|
let searchService: SearchServiceStub;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
activatedRoute = new ActivatedRouteStub();
|
||||||
|
paginationService = new PaginationServiceStub();
|
||||||
|
searchService = new SearchServiceStub();
|
||||||
|
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [
|
||||||
|
CollectionRecentlyAddedComponent,
|
||||||
|
VarDirective,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
TranslateModule.forRoot(),
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: ActivatedRoute, useValue: activatedRoute },
|
||||||
|
{ provide: APP_CONFIG, useValue: environment },
|
||||||
|
{ provide: PaginationService, useValue: paginationService },
|
||||||
|
{ provide: SearchService, useValue: SearchServiceStub },
|
||||||
|
],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(CollectionRecentlyAddedComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,82 @@
|
|||||||
|
import { Component, OnInit, Inject, OnDestroy } from '@angular/core';
|
||||||
|
import { Observable, combineLatest as observableCombineLatest } from 'rxjs';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { PaginatedList } from '../../../core/data/paginated-list.model';
|
||||||
|
import { Item } from '../../../core/shared/item.model';
|
||||||
|
import { switchMap, map, startWith, take } from 'rxjs/operators';
|
||||||
|
import { getFirstSucceededRemoteData, toDSpaceObjectListRD } from '../../../core/shared/operators';
|
||||||
|
import { PaginatedSearchOptions } from '../../../shared/search/models/paginated-search-options.model';
|
||||||
|
import { DSpaceObjectType } from '../../../core/shared/dspace-object-type.model';
|
||||||
|
import { BROWSE_LINKS_TO_FOLLOW } from '../../../core/browse/browse.service';
|
||||||
|
import { PaginationService } from '../../../core/pagination/pagination.service';
|
||||||
|
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
|
||||||
|
import { SortOptions, SortDirection } from '../../../core/cache/models/sort-options.model';
|
||||||
|
import { APP_CONFIG, AppConfig } from '../../../../config/app-config.interface';
|
||||||
|
import { SearchService } from '../../../core/shared/search/search.service';
|
||||||
|
import { Collection } from '../../../core/shared/collection.model';
|
||||||
|
import { ActivatedRoute, Data } from '@angular/router';
|
||||||
|
import { fadeIn } from '../../../shared/animations/fade';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-collection-recently-added',
|
||||||
|
templateUrl: './collection-recently-added.component.html',
|
||||||
|
styleUrls: ['./collection-recently-added.component.scss'],
|
||||||
|
animations: [fadeIn],
|
||||||
|
})
|
||||||
|
export class CollectionRecentlyAddedComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
paginationConfig: PaginationComponentOptions;
|
||||||
|
|
||||||
|
sortConfig: SortOptions;
|
||||||
|
|
||||||
|
collectionRD$: Observable<RemoteData<Collection>>;
|
||||||
|
|
||||||
|
itemRD$: Observable<RemoteData<PaginatedList<Item>>>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(APP_CONFIG) protected appConfig: AppConfig,
|
||||||
|
protected paginationService: PaginationService,
|
||||||
|
protected route: ActivatedRoute,
|
||||||
|
protected searchService: SearchService,
|
||||||
|
) {
|
||||||
|
this.paginationConfig = Object.assign(new PaginationComponentOptions(), {
|
||||||
|
id: 'cp',
|
||||||
|
currentPage: 1,
|
||||||
|
pageSize: this.appConfig.browseBy.pageSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.sortConfig = new SortOptions('dc.date.accessioned', SortDirection.DESC);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.collectionRD$ = this.route.data.pipe(
|
||||||
|
map((data: Data) => data.dso as RemoteData<Collection>),
|
||||||
|
take(1),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.itemRD$ = observableCombineLatest([
|
||||||
|
this.paginationService.getCurrentPagination(this.paginationConfig.id, this.paginationConfig),
|
||||||
|
this.paginationService.getCurrentSort(this.paginationConfig.id, this.sortConfig),
|
||||||
|
]).pipe(
|
||||||
|
switchMap(([currentPagination, currentSort]: [PaginationComponentOptions, SortOptions]) => this.collectionRD$.pipe(
|
||||||
|
getFirstSucceededRemoteData(),
|
||||||
|
map((rd: RemoteData<Collection>) => rd.payload.id),
|
||||||
|
switchMap((id: string) => this.searchService.search<Item>(
|
||||||
|
new PaginatedSearchOptions({
|
||||||
|
scope: id,
|
||||||
|
pagination: currentPagination,
|
||||||
|
sort: currentSort,
|
||||||
|
dsoTypes: [DSpaceObjectType.ITEM]
|
||||||
|
}), null, true, true, ...BROWSE_LINKS_TO_FOLLOW).pipe(
|
||||||
|
toDSpaceObjectListRD()
|
||||||
|
) as Observable<RemoteData<PaginatedList<Item>>>),
|
||||||
|
startWith(undefined), // Make sure switching pages shows loading component
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.paginationService.clearPagination(this.paginationConfig.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -4,9 +4,9 @@
|
|||||||
<cdk-tree-node *cdkTreeNodeDef="let node; when: isShowMore" cdkTreeNodePadding
|
<cdk-tree-node *cdkTreeNodeDef="let node; when: isShowMore" cdkTreeNodePadding
|
||||||
class="example-tree-node show-more-node">
|
class="example-tree-node show-more-node">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button type="button" class="btn btn-default" cdkTreeNodeToggle>
|
<span aria-hidden="true" class="btn btn-default invisible" cdkTreeNodeToggle>
|
||||||
<span class="fa fa-chevron-right invisible" aria-hidden="true"></span>
|
<span class="fa fa-chevron-right"></span>
|
||||||
</button>
|
</span>
|
||||||
<div class="align-middle pt-2">
|
<div class="align-middle pt-2">
|
||||||
<button *ngIf="!(dataSource.loading$ | async)" (click)="getNextPage(node)"
|
<button *ngIf="!(dataSource.loading$ | async)" (click)="getNextPage(node)"
|
||||||
class="btn btn-outline-primary btn-sm" role="button">
|
class="btn btn-outline-primary btn-sm" role="button">
|
||||||
@@ -24,15 +24,18 @@
|
|||||||
<cdk-tree-node *cdkTreeNodeDef="let node; when: hasChild" cdkTreeNodePadding
|
<cdk-tree-node *cdkTreeNodeDef="let node; when: hasChild" cdkTreeNodePadding
|
||||||
class="example-tree-node expandable-node">
|
class="example-tree-node expandable-node">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button type="button" class="btn btn-default" cdkTreeNodeToggle
|
<button *ngIf="hasChild(null, node) | async" type="button" class="btn btn-default" cdkTreeNodeToggle
|
||||||
[title]="'toggle ' + dsoNameService.getName(node.payload)"
|
[attr.aria-label]="(node.isExpanded ? 'communityList.collapse' : 'communityList.expand') | translate:{ name: dsoNameService.getName(node.payload) }"
|
||||||
[attr.aria-label]="'toggle ' + dsoNameService.getName(node.payload)"
|
|
||||||
(click)="toggleExpanded(node)"
|
(click)="toggleExpanded(node)"
|
||||||
[ngClass]="(hasChild(null, node)| async) ? 'visible' : 'invisible'"
|
data-test="expand-button">
|
||||||
[attr.data-test]="(hasChild(null, node)| async) ? 'expand-button' : ''">
|
|
||||||
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
||||||
aria-hidden="true"></span>
|
aria-hidden="true"></span>
|
||||||
|
<span class="sr-only">{{ (node.isExpanded ? 'communityList.collapse' : 'communityList.expand') | translate:{ name: dsoNameService.getName(node.payload) } }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
<!--Don't render the button when non-expandable otherwise it's still accessible, instead render this placeholder-->
|
||||||
|
<span *ngIf="!(hasChild(null, node) | async)" aria-hidden="true" class="btn btn-default invisible">
|
||||||
|
<span class="fa fa-chevron-right"></span>
|
||||||
|
</span>
|
||||||
<div class="d-flex flex-row">
|
<div class="d-flex flex-row">
|
||||||
<span class="align-middle pt-2 lead">
|
<span class="align-middle pt-2 lead">
|
||||||
<a [routerLink]="node.route" class="lead">{{ dsoNameService.getName(node.payload) }}</a>
|
<a [routerLink]="node.route" class="lead">{{ dsoNameService.getName(node.payload) }}</a>
|
||||||
@@ -44,10 +47,9 @@
|
|||||||
<ds-truncatable [id]="node.id">
|
<ds-truncatable [id]="node.id">
|
||||||
<div class="text-muted" cdkTreeNodePadding>
|
<div class="text-muted" cdkTreeNodePadding>
|
||||||
<div class="d-flex" *ngIf="node.payload.shortDescription">
|
<div class="d-flex" *ngIf="node.payload.shortDescription">
|
||||||
<button type="button" class="btn btn-default invisible">
|
<span aria-hidden="true" class="btn btn-default invisible">
|
||||||
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
<span class="fa fa-chevron-right"></span>
|
||||||
aria-hidden="true"></span>
|
</span>
|
||||||
</button>
|
|
||||||
<ds-truncatable-part [id]="node.id" [minLines]="3">
|
<ds-truncatable-part [id]="node.id" [minLines]="3">
|
||||||
<span>{{node.payload.shortDescription}}</span>
|
<span>{{node.payload.shortDescription}}</span>
|
||||||
</ds-truncatable-part>
|
</ds-truncatable-part>
|
||||||
@@ -56,10 +58,9 @@
|
|||||||
</ds-truncatable>
|
</ds-truncatable>
|
||||||
<div class="d-flex" *ngIf="node===loadingNode && dataSource.loading$ | async"
|
<div class="d-flex" *ngIf="node===loadingNode && dataSource.loading$ | async"
|
||||||
cdkTreeNodePadding>
|
cdkTreeNodePadding>
|
||||||
<button type="button" class="btn btn-default invisible">
|
<span aria-hidden="true" class="btn btn-default invisible">
|
||||||
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
<span class="fa fa-chevron-right"></span>
|
||||||
aria-hidden="true"></span>
|
</span>
|
||||||
</button>
|
|
||||||
<ds-themed-loading class="ds-themed-loading"></ds-themed-loading>
|
<ds-themed-loading class="ds-themed-loading"></ds-themed-loading>
|
||||||
</div>
|
</div>
|
||||||
</cdk-tree-node>
|
</cdk-tree-node>
|
||||||
@@ -67,9 +68,9 @@
|
|||||||
<cdk-tree-node *cdkTreeNodeDef="let node; when: !(hasChild && isShowMore)" cdkTreeNodePadding
|
<cdk-tree-node *cdkTreeNodeDef="let node; when: !(hasChild && isShowMore)" cdkTreeNodePadding
|
||||||
class="example-tree-node childless-node">
|
class="example-tree-node childless-node">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button type="button" class="btn btn-default" cdkTreeNodeToggle>
|
<span aria-hidden="true" class="btn btn-default invisible" cdkTreeNodeToggle>
|
||||||
<span class="fa fa-chevron-right invisible" aria-hidden="true"></span>
|
<span class="fa fa-chevron-right"></span>
|
||||||
</button>
|
</span>
|
||||||
<h6 class="align-middle pt-2">
|
<h6 class="align-middle pt-2">
|
||||||
<a [routerLink]="node.route" class="lead">{{ dsoNameService.getName(node.payload) }}</a>
|
<a [routerLink]="node.route" class="lead">{{ dsoNameService.getName(node.payload) }}</a>
|
||||||
</h6>
|
</h6>
|
||||||
@@ -77,10 +78,9 @@
|
|||||||
<ds-truncatable [id]="node.id">
|
<ds-truncatable [id]="node.id">
|
||||||
<div class="text-muted" cdkTreeNodePadding>
|
<div class="text-muted" cdkTreeNodePadding>
|
||||||
<div class="d-flex" *ngIf="node.payload.shortDescription">
|
<div class="d-flex" *ngIf="node.payload.shortDescription">
|
||||||
<button type="button" class="btn btn-default invisible">
|
<span aria-hidden="true" class="btn btn-default invisible">
|
||||||
<span class="{{node.isExpanded ? 'fa fa-chevron-down' : 'fa fa-chevron-right'}}"
|
<span class="fa fa-chevron-right"></span>
|
||||||
aria-hidden="true"></span>
|
</span>
|
||||||
</button>
|
|
||||||
<ds-truncatable-part [id]="node.id" [minLines]="3">
|
<ds-truncatable-part [id]="node.id" [minLines]="3">
|
||||||
<span>{{node.payload.shortDescription}}</span>
|
<span>{{node.payload.shortDescription}}</span>
|
||||||
</ds-truncatable-part>
|
</ds-truncatable-part>
|
||||||
|
@@ -0,0 +1,4 @@
|
|||||||
|
::ng-deep .fa-chevron-right::before {
|
||||||
|
display: block;
|
||||||
|
width: 16px;
|
||||||
|
}
|
@@ -5,7 +5,7 @@ import { CommunityListService, showMoreFlatNode, toFlatNode } from '../community
|
|||||||
import { CdkTreeModule } from '@angular/cdk/tree';
|
import { CdkTreeModule } from '@angular/cdk/tree';
|
||||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||||
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
|
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA, DebugElement } from '@angular/core';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../core/shared/community.model';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
||||||
@@ -300,12 +300,14 @@ describe('CommunityListComponent', () => {
|
|||||||
|
|
||||||
describe('second top community node is expanded and has more children (collections) than page size of collection', () => {
|
describe('second top community node is expanded and has more children (collections) than page size of collection', () => {
|
||||||
describe('children of second top com are added (page-limited pageSize 2)', () => {
|
describe('children of second top com are added (page-limited pageSize 2)', () => {
|
||||||
let allNodes;
|
let allNodes: DebugElement[];
|
||||||
beforeEach(fakeAsync(() => {
|
beforeEach(fakeAsync(() => {
|
||||||
const chevronExpand = fixture.debugElement.queryAll(By.css('.expandable-node button'));
|
const toggleButtons: DebugElement[] = fixture.debugElement.queryAll(By.css('.expandable-node button'));
|
||||||
const chevronExpandSpan = fixture.debugElement.queryAll(By.css('.expandable-node button span'));
|
const toggleButtonText: DebugElement = toggleButtons[1].query(By.css('span'));
|
||||||
if (chevronExpandSpan[1].nativeElement.classList.contains('fa-chevron-right')) {
|
expect(toggleButtonText).not.toBeNull();
|
||||||
chevronExpand[1].nativeElement.click();
|
|
||||||
|
if (toggleButtonText.nativeElement.classList.contains('fa-chevron-right')) {
|
||||||
|
toggleButtons[1].nativeElement.click();
|
||||||
tick();
|
tick();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}
|
}
|
||||||
@@ -315,17 +317,18 @@ describe('CommunityListComponent', () => {
|
|||||||
allNodes = [...expandableNodesFound, ...childlessNodesFound];
|
allNodes = [...expandableNodesFound, ...childlessNodesFound];
|
||||||
}));
|
}));
|
||||||
it('tree contains 2 (page-limited) top com, 2 (page-limited) coll of 2nd top com, a show more for those page-limited coll and show more for page-limited top com', () => {
|
it('tree contains 2 (page-limited) top com, 2 (page-limited) coll of 2nd top com, a show more for those page-limited coll and show more for page-limited top com', () => {
|
||||||
mockTopFlatnodesUnexpanded.slice(0, 2).map((topFlatnode: FlatNode) => {
|
const allNodeNames: string[] = allNodes.map((node: DebugElement) => node.nativeElement.innerText.trim());
|
||||||
expect(allNodes.find((foundEl) => {
|
|
||||||
return (foundEl.nativeElement.textContent.trim() === topFlatnode.name);
|
|
||||||
})).toBeTruthy();
|
|
||||||
});
|
|
||||||
mockCollectionsPage1.map((coll) => {
|
|
||||||
expect(allNodes.find((foundEl) => {
|
|
||||||
return (foundEl.nativeElement.textContent.trim() === coll.name);
|
|
||||||
})).toBeTruthy();
|
|
||||||
});
|
|
||||||
expect(allNodes.length).toEqual(4);
|
expect(allNodes.length).toEqual(4);
|
||||||
|
const flatNodes: string[] = mockTopFlatnodesUnexpanded.slice(0, 2).map((flatNode: FlatNode) => flatNode.name);
|
||||||
|
for (const flatNode of flatNodes) {
|
||||||
|
expect(allNodeNames).toContain(flatNode);
|
||||||
|
}
|
||||||
|
expect(flatNodes.length).toBe(2);
|
||||||
|
const page1CollectionNames: string[] = mockCollectionsPage1.map((collection: Collection) => collection.name);
|
||||||
|
for (const collectionName of page1CollectionNames) {
|
||||||
|
expect(allNodeNames).toContain(collectionName);
|
||||||
|
}
|
||||||
|
expect(page1CollectionNames.length).toBe(2);
|
||||||
const showMoreEl = fixture.debugElement.queryAll(By.css('.show-more-node'));
|
const showMoreEl = fixture.debugElement.queryAll(By.css('.show-more-node'));
|
||||||
expect(showMoreEl.length).toEqual(2);
|
expect(showMoreEl.length).toEqual(2);
|
||||||
});
|
});
|
||||||
|
@@ -19,6 +19,7 @@ import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
|
|||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-community-list',
|
selector: 'ds-community-list',
|
||||||
templateUrl: './community-list.component.html',
|
templateUrl: './community-list.component.html',
|
||||||
|
styleUrls: ['./community-list.component.scss'],
|
||||||
})
|
})
|
||||||
export class CommunityListComponent implements OnInit, OnDestroy {
|
export class CommunityListComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
|
@@ -15,6 +15,10 @@ import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
|||||||
import { ThemedCommunityPageComponent } from './themed-community-page.component';
|
import { ThemedCommunityPageComponent } from './themed-community-page.component';
|
||||||
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
import { MenuItemType } from '../shared/menu/menu-item-type.model';
|
||||||
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
||||||
|
import { SubComColSectionComponent } from './sections/sub-com-col-section/sub-com-col-section.component';
|
||||||
|
import { BrowseByI18nBreadcrumbResolver } from '../browse-by/browse-by-i18n-breadcrumb.resolver';
|
||||||
|
import { BrowseByGuard } from '../browse-by/browse-by-guard';
|
||||||
|
import { ComcolBrowseByComponent } from '../shared/comcol/sections/comcol-browse-by/comcol-browse-by.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -48,7 +52,23 @@ import { DSOEditMenuResolver } from '../shared/dso-page/dso-edit-menu.resolver';
|
|||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: ThemedCommunityPageComponent,
|
component: ThemedCommunityPageComponent,
|
||||||
pathMatch: 'full',
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
pathMatch: 'full',
|
||||||
|
component: SubComColSectionComponent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'browse/:id',
|
||||||
|
pathMatch: 'full',
|
||||||
|
component: ComcolBrowseByComponent,
|
||||||
|
canActivate: [BrowseByGuard],
|
||||||
|
resolve: {
|
||||||
|
breadcrumb: BrowseByI18nBreadcrumbResolver,
|
||||||
|
},
|
||||||
|
data: { breadcrumbKey: 'browse.metadata' },
|
||||||
|
},
|
||||||
|
],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
data: {
|
data: {
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
<!-- Community name -->
|
<!-- Community name -->
|
||||||
<ds-comcol-page-header [name]="dsoNameService.getName(communityPayload)"></ds-comcol-page-header>
|
<ds-comcol-page-header [name]="dsoNameService.getName(communityPayload)"></ds-comcol-page-header>
|
||||||
<!-- Community logo -->
|
<!-- Community logo -->
|
||||||
<ds-comcol-page-logo *ngIf="logoRD$" [logo]="(logoRD$ | async)?.payload" [alternateText]="'Community Logo'">
|
<ds-comcol-page-logo *ngIf="logoRD$" [logo]="(logoRD$ | async)?.payload" [alternateText]="'community.logo' | translate">
|
||||||
</ds-comcol-page-logo>
|
</ds-comcol-page-logo>
|
||||||
<!-- Handle -->
|
<!-- Handle -->
|
||||||
<ds-themed-comcol-page-handle [content]="communityPayload.handle" [title]="'community.page.handle'">
|
<ds-themed-comcol-page-handle [content]="communityPayload.handle" [title]="'community.page.handle'">
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
<!-- News -->
|
<!-- News -->
|
||||||
<ds-comcol-page-content [content]="communityPayload.sidebarText" [hasInnerHtml]="true"
|
<ds-comcol-page-content [content]="communityPayload.sidebarText" [hasInnerHtml]="true"
|
||||||
[title]="'community.page.news'">
|
[title]="'community.page.news'">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
</header>
|
</header>
|
||||||
<ds-dso-edit-menu></ds-dso-edit-menu>
|
<ds-dso-edit-menu></ds-dso-edit-menu>
|
||||||
@@ -28,10 +28,9 @@
|
|||||||
<ds-themed-comcol-page-browse-by [id]="communityPayload.id" [contentType]="communityPayload.type">
|
<ds-themed-comcol-page-browse-by [id]="communityPayload.id" [contentType]="communityPayload.type">
|
||||||
</ds-themed-comcol-page-browse-by>
|
</ds-themed-comcol-page-browse-by>
|
||||||
|
|
||||||
<ds-themed-community-page-sub-community-list [community]="communityPayload"></ds-themed-community-page-sub-community-list>
|
<router-outlet></router-outlet>
|
||||||
<ds-themed-community-page-sub-collection-list [community]="communityPayload"></ds-themed-community-page-sub-collection-list>
|
|
||||||
</section>
|
</section>
|
||||||
<footer *ngIf="communityPayload.copyrightText" class="border-top my-5 pt-4">
|
<footer *ngIf="communityPayload.copyrightText" class="border-top my-5 pt-4">
|
||||||
<!-- Copyright -->
|
<!-- Copyright -->
|
||||||
<ds-comcol-page-content [content]="communityPayload.copyrightText" [hasInnerHtml]="true">
|
<ds-comcol-page-content [content]="communityPayload.copyrightText" [hasInnerHtml]="true">
|
||||||
</ds-comcol-page-content>
|
</ds-comcol-page-content>
|
||||||
|
@@ -1,16 +1,10 @@
|
|||||||
import { mergeMap, filter, map } from 'rxjs/operators';
|
import { mergeMap, filter, map } from 'rxjs/operators';
|
||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { CommunityDataService } from '../core/data/community-data.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 { Community } from '../core/shared/community.model';
|
import { Community } from '../core/shared/community.model';
|
||||||
|
|
||||||
import { MetadataService } from '../core/metadata/metadata.service';
|
|
||||||
|
|
||||||
import { fadeInOut } from '../shared/animations/fade';
|
import { fadeInOut } from '../shared/animations/fade';
|
||||||
import { hasValue } from '../shared/empty.util';
|
import { hasValue } from '../shared/empty.util';
|
||||||
import { getAllSucceededRemoteDataPayload} from '../core/shared/operators';
|
import { getAllSucceededRemoteDataPayload} from '../core/shared/operators';
|
||||||
@@ -53,8 +47,6 @@ export class CommunityPageComponent implements OnInit {
|
|||||||
communityPageRoute$: Observable<string>;
|
communityPageRoute$: Observable<string>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private communityDataService: CommunityDataService,
|
|
||||||
private metadata: MetadataService,
|
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
|
@@ -4,9 +4,9 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { SharedModule } from '../shared/shared.module';
|
import { SharedModule } from '../shared/shared.module';
|
||||||
|
|
||||||
import { CommunityPageComponent } from './community-page.component';
|
import { CommunityPageComponent } from './community-page.component';
|
||||||
import { CommunityPageSubCollectionListComponent } from './sub-collection-list/community-page-sub-collection-list.component';
|
import { CommunityPageSubCollectionListComponent } from './sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component';
|
||||||
import { CommunityPageRoutingModule } from './community-page-routing.module';
|
import { CommunityPageRoutingModule } from './community-page-routing.module';
|
||||||
import { CommunityPageSubCommunityListComponent } from './sub-community-list/community-page-sub-community-list.component';
|
import { CommunityPageSubCommunityListComponent } from './sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component';
|
||||||
import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component';
|
import { CreateCommunityPageComponent } from './create-community-page/create-community-page.component';
|
||||||
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
|
import { DeleteCommunityPageComponent } from './delete-community-page/delete-community-page.component';
|
||||||
import { StatisticsModule } from '../statistics/statistics.module';
|
import { StatisticsModule } from '../statistics/statistics.module';
|
||||||
@@ -15,20 +15,25 @@ import { ThemedCommunityPageComponent } from './themed-community-page.component'
|
|||||||
import { ComcolModule } from '../shared/comcol/comcol.module';
|
import { ComcolModule } from '../shared/comcol/comcol.module';
|
||||||
import {
|
import {
|
||||||
ThemedCommunityPageSubCommunityListComponent
|
ThemedCommunityPageSubCommunityListComponent
|
||||||
} from './sub-community-list/themed-community-page-sub-community-list.component';
|
} from './sections/sub-com-col-section/sub-community-list/themed-community-page-sub-community-list.component';
|
||||||
import {
|
import {
|
||||||
ThemedCollectionPageSubCollectionListComponent
|
ThemedCollectionPageSubCollectionListComponent
|
||||||
} from './sub-collection-list/themed-community-page-sub-collection-list.component';
|
} from './sections/sub-com-col-section/sub-collection-list/themed-community-page-sub-collection-list.component';
|
||||||
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
import { DsoPageModule } from '../shared/dso-page/dso-page.module';
|
||||||
|
import { SubComColSectionComponent } from './sections/sub-com-col-section/sub-com-col-section.component';
|
||||||
|
import { BrowseByPageModule } from '../browse-by/browse-by-page.module';
|
||||||
|
|
||||||
const DECLARATIONS = [CommunityPageComponent,
|
const DECLARATIONS = [
|
||||||
|
CommunityPageComponent,
|
||||||
ThemedCommunityPageComponent,
|
ThemedCommunityPageComponent,
|
||||||
ThemedCommunityPageSubCommunityListComponent,
|
ThemedCommunityPageSubCommunityListComponent,
|
||||||
CommunityPageSubCollectionListComponent,
|
CommunityPageSubCollectionListComponent,
|
||||||
ThemedCollectionPageSubCollectionListComponent,
|
ThemedCollectionPageSubCollectionListComponent,
|
||||||
CommunityPageSubCommunityListComponent,
|
CommunityPageSubCommunityListComponent,
|
||||||
CreateCommunityPageComponent,
|
CreateCommunityPageComponent,
|
||||||
DeleteCommunityPageComponent];
|
DeleteCommunityPageComponent,
|
||||||
|
SubComColSectionComponent,
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -39,6 +44,7 @@ const DECLARATIONS = [CommunityPageComponent,
|
|||||||
CommunityFormModule,
|
CommunityFormModule,
|
||||||
ComcolModule,
|
ComcolModule,
|
||||||
DsoPageModule,
|
DsoPageModule,
|
||||||
|
BrowseByPageModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
...DECLARATIONS
|
...DECLARATIONS
|
||||||
|
@@ -7,11 +7,11 @@
|
|||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<div class="col text-right space-children-mr">
|
<div class="col text-right space-children-mr">
|
||||||
<button class="btn btn-outline-secondary" (click)="onCancel(dso)" [disabled]="(processing$ | async)">
|
<button class="btn btn-outline-secondary" (click)="onCancel(dso)" [disabled]="(processing$ | async)">
|
||||||
<i class="fas fa-times"></i> {{'community.delete.cancel' | translate}}
|
<i class="fas fa-times" aria-hidden="true"></i> {{'community.delete.cancel' | translate}}
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-danger" (click)="onConfirm(dso)" [disabled]="(processing$ | async)">
|
<button class="btn btn-danger" (click)="onConfirm(dso)" [disabled]="(processing$ | async)">
|
||||||
<span *ngIf="processing$ | async"><i class='fas fa-circle-notch fa-spin'></i> {{'community.delete.processing' | translate}}</span>
|
<span *ngIf="processing$ | async"><i class='fas fa-circle-notch fa-spin' aria-hidden="true"></i> {{'community.delete.processing' | translate}}</span>
|
||||||
<span *ngIf="!(processing$ | async)"><i class="fas fa-trash"></i> {{'community.delete.confirm' | translate}}</span>
|
<span *ngIf="!(processing$ | async)"><i class="fas fa-trash" aria-hidden="true"></i> {{'community.delete.confirm' | translate}}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<ng-container *ngVar="(subCollectionsRDObs | async) as subCollectionsRD">
|
<ng-container *ngVar="(subCollectionsRDObs | async) as subCollectionsRD">
|
||||||
<div *ngIf="subCollectionsRD?.hasSucceeded && subCollectionsRD?.payload.totalElements > 0" @fadeIn>
|
<div *ngIf="subCollectionsRD?.hasSucceeded && subCollectionsRD?.payload.totalElements > 0" @fadeIn>
|
||||||
<h2>{{'community.sub-collection-list.head' | translate}}</h2>
|
<h3>{{'community.sub-collection-list.head' | translate}}</h3>
|
||||||
<ds-viewable-collection
|
<ds-viewable-collection
|
||||||
[config]="config"
|
[config]="config"
|
||||||
[sortConfig]="sortConfig"
|
[sortConfig]="sortConfig"
|
@@ -8,27 +8,27 @@ import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
|||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
import { CommunityPageSubCollectionListComponent } from './community-page-sub-collection-list.component';
|
import { CommunityPageSubCollectionListComponent } from './community-page-sub-collection-list.component';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../../../shared/shared.module';
|
||||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
import { CollectionDataService } from '../../../../core/data/collection-data.service';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
|
||||||
import { buildPaginatedList } from '../../core/data/paginated-list.model';
|
import { buildPaginatedList } from '../../../../core/data/paginated-list.model';
|
||||||
import { PageInfo } from '../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
import { HostWindowService } from '../../shared/host-window.service';
|
import { HostWindowService } from '../../../../shared/host-window.service';
|
||||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
|
import { HostWindowServiceStub } from '../../../../shared/testing/host-window-service.stub';
|
||||||
import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service';
|
import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||||
import { getMockThemeService } from '../../shared/mocks/theme-service.mock';
|
import { getMockThemeService } from '../../../../shared/mocks/theme-service.mock';
|
||||||
import { ThemeService } from '../../shared/theme-support/theme.service';
|
import { ThemeService } from '../../../../shared/theme-support/theme.service';
|
||||||
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub';
|
||||||
import { FindListOptions } from '../../core/data/find-list-options.model';
|
import { FindListOptions } from '../../../../core/data/find-list-options.model';
|
||||||
import { GroupDataService } from '../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
import { LinkHeadService } from '../../core/services/link-head.service';
|
import { LinkHeadService } from '../../../../core/services/link-head.service';
|
||||||
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
|
import { ConfigurationDataService } from '../../../../core/data/configuration-data.service';
|
||||||
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service';
|
||||||
import { ConfigurationProperty } from '../../core/shared/configuration-property.model';
|
import { ConfigurationProperty } from '../../../../core/shared/configuration-property.model';
|
||||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../../../shared/testing/utils.test';
|
||||||
import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-service.stub';
|
import { SearchConfigurationServiceStub } from '../../../../shared/testing/search-configuration-service.stub';
|
||||||
|
|
||||||
describe('CommunityPageSubCollectionList Component', () => {
|
describe('CommunityPageSubCollectionList Component', () => {
|
||||||
let comp: CommunityPageSubCollectionListComponent;
|
let comp: CommunityPageSubCollectionListComponent;
|
@@ -1,19 +1,17 @@
|
|||||||
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription } from 'rxjs';
|
import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription } from 'rxjs';
|
||||||
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { Collection } from '../../../../core/shared/collection.model';
|
||||||
import { Collection } from '../../core/shared/collection.model';
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { fadeIn } from '../../../../shared/animations/fade';
|
||||||
import { fadeIn } from '../../shared/animations/fade';
|
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
||||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model';
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { CollectionDataService } from '../../../../core/data/collection-data.service';
|
||||||
import { CollectionDataService } from '../../core/data/collection-data.service';
|
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../../../shared/empty.util';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-community-page-sub-collection-list',
|
selector: 'ds-community-page-sub-collection-list',
|
@@ -1,12 +1,12 @@
|
|||||||
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
import { ThemedComponent } from '../../../../shared/theme-support/themed.component';
|
||||||
import { CommunityPageSubCollectionListComponent } from './community-page-sub-collection-list.component';
|
import { CommunityPageSubCollectionListComponent } from './community-page-sub-collection-list.component';
|
||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-themed-community-page-sub-collection-list',
|
selector: 'ds-themed-community-page-sub-collection-list',
|
||||||
styleUrls: [],
|
styleUrls: [],
|
||||||
templateUrl: '../../shared/theme-support/themed.component.html',
|
templateUrl: '../../../../shared/theme-support/themed.component.html',
|
||||||
})
|
})
|
||||||
export class ThemedCollectionPageSubCollectionListComponent extends ThemedComponent<CommunityPageSubCollectionListComponent> {
|
export class ThemedCollectionPageSubCollectionListComponent extends ThemedComponent<CommunityPageSubCollectionListComponent> {
|
||||||
@Input() community: Community;
|
@Input() community: Community;
|
||||||
@@ -18,7 +18,7 @@ export class ThemedCollectionPageSubCollectionListComponent extends ThemedCompon
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected importThemedComponent(themeName: string): Promise<any> {
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
return import(`../../../themes/${themeName}/app/community-page/sub-collection-list/community-page-sub-collection-list.component`);
|
return import(`../../../../../themes/${themeName}/app/community-page/sections/sub-com-col-section/sub-collection-list/community-page-sub-collection-list.component`);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected importUnthemedComponent(): Promise<any> {
|
protected importUnthemedComponent(): Promise<any> {
|
@@ -0,0 +1,8 @@
|
|||||||
|
<ng-container *ngIf="(community$ | async) as community">
|
||||||
|
<ds-themed-community-page-sub-community-list
|
||||||
|
[community]="community">
|
||||||
|
</ds-themed-community-page-sub-community-list>
|
||||||
|
<ds-themed-community-page-sub-collection-list
|
||||||
|
[community]="community">
|
||||||
|
</ds-themed-community-page-sub-collection-list>
|
||||||
|
</ng-container>
|
@@ -0,0 +1,32 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { SubComColSectionComponent } from './sub-com-col-section.component';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
import { ActivatedRouteStub } from '../../../shared/testing/active-router.stub';
|
||||||
|
|
||||||
|
describe('SubComColSectionComponent', () => {
|
||||||
|
let component: SubComColSectionComponent;
|
||||||
|
let fixture: ComponentFixture<SubComColSectionComponent>;
|
||||||
|
|
||||||
|
let activatedRoute: ActivatedRouteStub;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
activatedRoute = new ActivatedRouteStub();
|
||||||
|
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [
|
||||||
|
SubComColSectionComponent,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: ActivatedRoute, useValue: activatedRoute },
|
||||||
|
],
|
||||||
|
}).compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(SubComColSectionComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@@ -0,0 +1,28 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { RemoteData } from '../../../core/data/remote-data';
|
||||||
|
import { Community } from '../../../core/shared/community.model';
|
||||||
|
import { ActivatedRoute, Data } from '@angular/router';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'ds-sub-com-col-section',
|
||||||
|
templateUrl: './sub-com-col-section.component.html',
|
||||||
|
styleUrls: ['./sub-com-col-section.component.scss'],
|
||||||
|
})
|
||||||
|
export class SubComColSectionComponent implements OnInit {
|
||||||
|
|
||||||
|
community$: Observable<Community>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.community$ = this.route.data.pipe(
|
||||||
|
map((data: Data) => (data.dso as RemoteData<Community>).payload),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,6 +1,6 @@
|
|||||||
<ng-container *ngVar="(subCommunitiesRDObs | async) as subCommunitiesRD">
|
<ng-container *ngVar="(subCommunitiesRDObs | async) as subCommunitiesRD">
|
||||||
<div *ngIf="subCommunitiesRD?.hasSucceeded && subCommunitiesRD?.payload.totalElements > 0" @fadeIn>
|
<div *ngIf="subCommunitiesRD?.hasSucceeded && subCommunitiesRD?.payload.totalElements > 0" @fadeIn>
|
||||||
<h2>{{'community.sub-community-list.head' | translate}}</h2>
|
<h3>{{'community.sub-community-list.head' | translate}}</h3>
|
||||||
<ds-viewable-collection
|
<ds-viewable-collection
|
||||||
[config]="config"
|
[config]="config"
|
||||||
[sortConfig]="sortConfig"
|
[sortConfig]="sortConfig"
|
@@ -8,27 +8,27 @@ import { By } from '@angular/platform-browser';
|
|||||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
||||||
|
|
||||||
import { CommunityPageSubCommunityListComponent } from './community-page-sub-community-list.component';
|
import { CommunityPageSubCommunityListComponent } from './community-page-sub-community-list.component';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
import { buildPaginatedList } from '../../core/data/paginated-list.model';
|
import { buildPaginatedList } from '../../../../core/data/paginated-list.model';
|
||||||
import { PageInfo } from '../../core/shared/page-info.model';
|
import { PageInfo } from '../../../../core/shared/page-info.model';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../../../shared/shared.module';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$ } from '../../../../shared/remote-data.utils';
|
||||||
import { HostWindowService } from '../../shared/host-window.service';
|
import { HostWindowService } from '../../../../shared/host-window.service';
|
||||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
|
import { HostWindowServiceStub } from '../../../../shared/testing/host-window-service.stub';
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../../../core/data/community-data.service';
|
||||||
import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service';
|
import { SelectableListService } from '../../../../shared/object-list/selectable-list/selectable-list.service';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||||
import { getMockThemeService } from '../../shared/mocks/theme-service.mock';
|
import { getMockThemeService } from '../../../../shared/mocks/theme-service.mock';
|
||||||
import { ThemeService } from '../../shared/theme-support/theme.service';
|
import { ThemeService } from '../../../../shared/theme-support/theme.service';
|
||||||
import { PaginationServiceStub } from '../../shared/testing/pagination-service.stub';
|
import { PaginationServiceStub } from '../../../../shared/testing/pagination-service.stub';
|
||||||
import { FindListOptions } from '../../core/data/find-list-options.model';
|
import { FindListOptions } from '../../../../core/data/find-list-options.model';
|
||||||
import { GroupDataService } from '../../core/eperson/group-data.service';
|
import { GroupDataService } from '../../../../core/eperson/group-data.service';
|
||||||
import { LinkHeadService } from '../../core/services/link-head.service';
|
import { LinkHeadService } from '../../../../core/services/link-head.service';
|
||||||
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
|
import { ConfigurationDataService } from '../../../../core/data/configuration-data.service';
|
||||||
import { SearchConfigurationService } from '../../core/shared/search/search-configuration.service';
|
import { SearchConfigurationService } from '../../../../core/shared/search/search-configuration.service';
|
||||||
import { SearchConfigurationServiceStub } from '../../shared/testing/search-configuration-service.stub';
|
import { SearchConfigurationServiceStub } from '../../../../shared/testing/search-configuration-service.stub';
|
||||||
import { ConfigurationProperty } from '../../core/shared/configuration-property.model';
|
import { ConfigurationProperty } from '../../../../core/shared/configuration-property.model';
|
||||||
import { createPaginatedList } from '../../shared/testing/utils.test';
|
import { createPaginatedList } from '../../../../shared/testing/utils.test';
|
||||||
|
|
||||||
describe('CommunityPageSubCommunityListComponent Component', () => {
|
describe('CommunityPageSubCommunityListComponent Component', () => {
|
||||||
let comp: CommunityPageSubCommunityListComponent;
|
let comp: CommunityPageSubCommunityListComponent;
|
@@ -3,16 +3,16 @@ import { ActivatedRoute } from '@angular/router';
|
|||||||
|
|
||||||
import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription } from 'rxjs';
|
import { BehaviorSubject, combineLatest as observableCombineLatest, Subscription } from 'rxjs';
|
||||||
|
|
||||||
import { RemoteData } from '../../core/data/remote-data';
|
import { RemoteData } from '../../../../core/data/remote-data';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
import { fadeIn } from '../../shared/animations/fade';
|
import { fadeIn } from '../../../../shared/animations/fade';
|
||||||
import { PaginatedList } from '../../core/data/paginated-list.model';
|
import { PaginatedList } from '../../../../core/data/paginated-list.model';
|
||||||
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
|
import { PaginationComponentOptions } from '../../../../shared/pagination/pagination-component-options.model';
|
||||||
import { SortDirection, SortOptions } from '../../core/cache/models/sort-options.model';
|
import { SortDirection, SortOptions } from '../../../../core/cache/models/sort-options.model';
|
||||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
import { CommunityDataService } from '../../../../core/data/community-data.service';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { switchMap } from 'rxjs/operators';
|
||||||
import { PaginationService } from '../../core/pagination/pagination.service';
|
import { PaginationService } from '../../../../core/pagination/pagination.service';
|
||||||
import { hasValue } from '../../shared/empty.util';
|
import { hasValue } from '../../../../shared/empty.util';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-community-page-sub-community-list',
|
selector: 'ds-community-page-sub-community-list',
|
@@ -1,12 +1,12 @@
|
|||||||
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
import { ThemedComponent } from '../../../../shared/theme-support/themed.component';
|
||||||
import { CommunityPageSubCommunityListComponent } from './community-page-sub-community-list.component';
|
import { CommunityPageSubCommunityListComponent } from './community-page-sub-community-list.component';
|
||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { Community } from '../../core/shared/community.model';
|
import { Community } from '../../../../core/shared/community.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ds-themed-community-page-sub-community-list',
|
selector: 'ds-themed-community-page-sub-community-list',
|
||||||
styleUrls: [],
|
styleUrls: [],
|
||||||
templateUrl: '../../shared/theme-support/themed.component.html',
|
templateUrl: '../../../../shared/theme-support/themed.component.html',
|
||||||
})
|
})
|
||||||
export class ThemedCommunityPageSubCommunityListComponent extends ThemedComponent<CommunityPageSubCommunityListComponent> {
|
export class ThemedCommunityPageSubCommunityListComponent extends ThemedComponent<CommunityPageSubCommunityListComponent> {
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ export class ThemedCommunityPageSubCommunityListComponent extends ThemedComponen
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected importThemedComponent(themeName: string): Promise<any> {
|
protected importThemedComponent(themeName: string): Promise<any> {
|
||||||
return import(`../../../themes/${themeName}/app/community-page/sub-community-list/community-page-sub-community-list.component`);
|
return import(`../../../../../themes/${themeName}/app/community-page/sections/sub-com-col-section/sub-community-list/community-page-sub-community-list.component`);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected importUnthemedComponent(): Promise<any> {
|
protected importUnthemedComponent(): Promise<any> {
|
@@ -105,7 +105,7 @@ export class BrowseService {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (options.fetchThumbnail ) {
|
if (options.fetchThumbnail ) {
|
||||||
return this.hrefOnlyDataService.findListByHref<BrowseEntry>(href$, {}, null, null, ...BROWSE_LINKS_TO_FOLLOW);
|
return this.hrefOnlyDataService.findListByHref<BrowseEntry>(href$, {}, undefined, undefined, ...BROWSE_LINKS_TO_FOLLOW);
|
||||||
}
|
}
|
||||||
return this.hrefOnlyDataService.findListByHref<BrowseEntry>(href$);
|
return this.hrefOnlyDataService.findListByHref<BrowseEntry>(href$);
|
||||||
}
|
}
|
||||||
@@ -153,7 +153,7 @@ export class BrowseService {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
if (options.fetchThumbnail) {
|
if (options.fetchThumbnail) {
|
||||||
return this.hrefOnlyDataService.findListByHref<Item>(href$, {}, null, null, ...BROWSE_LINKS_TO_FOLLOW);
|
return this.hrefOnlyDataService.findListByHref<Item>(href$, {}, undefined, undefined, ...BROWSE_LINKS_TO_FOLLOW);
|
||||||
}
|
}
|
||||||
return this.hrefOnlyDataService.findListByHref<Item>(href$);
|
return this.hrefOnlyDataService.findListByHref<Item>(href$);
|
||||||
}
|
}
|
||||||
|
@@ -78,6 +78,8 @@ describe('BaseDataService', () => {
|
|||||||
const msToLive = 15 * 60 * 1000;
|
const msToLive = 15 * 60 * 1000;
|
||||||
const payload = {
|
const payload = {
|
||||||
foo: 'bar',
|
foo: 'bar',
|
||||||
|
followLink1: {},
|
||||||
|
followLink2: {},
|
||||||
_links: {
|
_links: {
|
||||||
self: Object.assign(new HALLink(), {
|
self: Object.assign(new HALLink(), {
|
||||||
href: 'self-test-link',
|
href: 'self-test-link',
|
||||||
@@ -413,7 +415,7 @@ describe('BaseDataService', () => {
|
|||||||
|
|
||||||
expectObservable(service.findByHref(selfLink, false, false, ...linksToFollow)).toBe(expected, values);
|
expectObservable(service.findByHref(selfLink, false, false, ...linksToFollow)).toBe(expected, values);
|
||||||
flush();
|
flush();
|
||||||
expect(objectCache.addDependency).toHaveBeenCalledTimes(4);
|
expect(objectCache.addDependency).toHaveBeenCalledTimes(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -621,7 +623,7 @@ describe('BaseDataService', () => {
|
|||||||
|
|
||||||
expectObservable(service.findListByHref(selfLink, findListOptions, false, false, ...linksToFollow)).toBe(expected, values);
|
expectObservable(service.findListByHref(selfLink, findListOptions, false, false, ...linksToFollow)).toBe(expected, values);
|
||||||
flush();
|
flush();
|
||||||
expect(objectCache.addDependency).toHaveBeenCalledTimes(4);
|
expect(objectCache.addDependency).toHaveBeenCalledTimes(3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -282,12 +282,15 @@ export class BaseDataService<T extends CacheableObject> implements HALDataServic
|
|||||||
// Ensure all followLinks from the cached object are automatically invalidated when invalidating the cached object
|
// Ensure all followLinks from the cached object are automatically invalidated when invalidating the cached object
|
||||||
tap((remoteDataObject: RemoteData<T>) => {
|
tap((remoteDataObject: RemoteData<T>) => {
|
||||||
if (hasValue(remoteDataObject?.payload?._links)) {
|
if (hasValue(remoteDataObject?.payload?._links)) {
|
||||||
for (const followLink of Object.values(remoteDataObject.payload._links)) {
|
for (const followLinkName of Object.keys(remoteDataObject.payload._links)) {
|
||||||
// followLink can be either an individual HALLink or a HALLink[]
|
// only add the followLinks if they are embedded
|
||||||
const followLinksList: HALLink[] = [].concat(followLink);
|
if (hasValue(remoteDataObject.payload[followLinkName]) && followLinkName !== 'self') {
|
||||||
for (const individualFollowLink of followLinksList) {
|
// followLink can be either an individual HALLink or a HALLink[]
|
||||||
if (hasValue(individualFollowLink?.href)) {
|
const followLinksList: HALLink[] = [].concat(remoteDataObject.payload._links[followLinkName]);
|
||||||
this.addDependency(response$, individualFollowLink.href);
|
for (const individualFollowLink of followLinksList) {
|
||||||
|
if (hasValue(individualFollowLink?.href)) {
|
||||||
|
this.addDependency(response$, individualFollowLink.href);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -334,12 +337,15 @@ export class BaseDataService<T extends CacheableObject> implements HALDataServic
|
|||||||
if (hasValue(remoteDataObject?.payload?.page)) {
|
if (hasValue(remoteDataObject?.payload?.page)) {
|
||||||
for (const object of remoteDataObject.payload.page) {
|
for (const object of remoteDataObject.payload.page) {
|
||||||
if (hasValue(object?._links)) {
|
if (hasValue(object?._links)) {
|
||||||
for (const followLink of Object.values(object._links)) {
|
for (const followLinkName of Object.keys(object._links)) {
|
||||||
// followLink can be either an individual HALLink or a HALLink[]
|
// only add the followLinks if they are embedded
|
||||||
const followLinksList: HALLink[] = [].concat(followLink);
|
if (hasValue(object[followLinkName]) && followLinkName !== 'self') {
|
||||||
for (const individualFollowLink of followLinksList) {
|
// followLink can be either an individual HALLink or a HALLink[]
|
||||||
if (hasValue(individualFollowLink?.href)) {
|
const followLinksList: HALLink[] = [].concat(object._links[followLinkName]);
|
||||||
this.addDependency(response$, individualFollowLink.href);
|
for (const individualFollowLink of followLinksList) {
|
||||||
|
if (hasValue(individualFollowLink?.href)) {
|
||||||
|
this.addDependency(response$, individualFollowLink.href);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -34,5 +34,6 @@ export enum FeatureID {
|
|||||||
CanEditItem = 'canEditItem',
|
CanEditItem = 'canEditItem',
|
||||||
CanRegisterDOI = 'canRegisterDOI',
|
CanRegisterDOI = 'canRegisterDOI',
|
||||||
CanSubscribe = 'canSubscribeDso',
|
CanSubscribe = 'canSubscribeDso',
|
||||||
CanSeeQA = 'canSeeQA'
|
EPersonForgotPassword = 'epersonForgotPassword',
|
||||||
|
CanSeeQA = 'canSeeQA',
|
||||||
}
|
}
|
||||||
|
@@ -245,12 +245,14 @@ export class ProcessDataService extends IdentifiableDataService<Process> impleme
|
|||||||
)
|
)
|
||||||
).subscribe((processRD: RemoteData<Process>) => {
|
).subscribe((processRD: RemoteData<Process>) => {
|
||||||
this.clearCurrentTimeout(processId);
|
this.clearCurrentTimeout(processId);
|
||||||
const nextTimeout = this.timer(() => {
|
if (processRD.hasSucceeded) {
|
||||||
this.activelyBeingPolled.delete(processId);
|
const nextTimeout = this.timer(() => {
|
||||||
this.invalidateByHref(processRD.payload._links.self.href);
|
this.activelyBeingPolled.delete(processId);
|
||||||
}, pollingIntervalInMs);
|
this.invalidateByHref(processRD.payload._links.self.href);
|
||||||
|
}, pollingIntervalInMs);
|
||||||
|
|
||||||
this.activelyBeingPolled.set(processId, nextTimeout);
|
this.activelyBeingPolled.set(processId, nextTimeout);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.subs.set(processId, sub);
|
this.subs.set(processId, sub);
|
||||||
|
@@ -351,7 +351,28 @@ function addOperationToList(body: JsonPatchOperationObject[], actionType, target
|
|||||||
newBody.push(makeOperationEntry({ op: JsonPatchOperationType.move, from: fromPath, path: targetPath }));
|
newBody.push(makeOperationEntry({ op: JsonPatchOperationType.move, from: fromPath, path: targetPath }));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return newBody;
|
return dedupeOperationEntries(newBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dedupe operation entries by op and path. This prevents processing unnecessary patches in a single PATCH request.
|
||||||
|
*
|
||||||
|
* @param body JSON patch operation object entries
|
||||||
|
* @returns deduped JSON patch operation object entries
|
||||||
|
*/
|
||||||
|
function dedupeOperationEntries(body: JsonPatchOperationObject[]): JsonPatchOperationObject[] {
|
||||||
|
const ops = new Map<string, any>();
|
||||||
|
for (let i = body.length - 1; i >= 0; i--) {
|
||||||
|
const patch = body[i].operation;
|
||||||
|
const key = `${patch.op}-${patch.path}`;
|
||||||
|
if (!ops.has(key)) {
|
||||||
|
ops.set(key, patch);
|
||||||
|
} else {
|
||||||
|
body.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeOperationEntry(operation) {
|
function makeOperationEntry(operation) {
|
||||||
|
@@ -0,0 +1,31 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
|
||||||
|
import { Observable, of } from 'rxjs';
|
||||||
|
import { AuthorizationDataService } from '../data/feature-authorization/authorization-data.service';
|
||||||
|
import { FeatureID } from '../data/feature-authorization/feature-id';
|
||||||
|
import {
|
||||||
|
SingleFeatureAuthorizationGuard
|
||||||
|
} from '../data/feature-authorization/feature-authorization-guard/single-feature-authorization.guard';
|
||||||
|
import { AuthService } from '../auth/auth.service';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
/**
|
||||||
|
* Guard that checks if the forgot-password feature is enabled
|
||||||
|
*/
|
||||||
|
export class ForgotPasswordCheckGuard extends SingleFeatureAuthorizationGuard {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected readonly authorizationService: AuthorizationDataService,
|
||||||
|
protected readonly router: Router,
|
||||||
|
protected readonly authService: AuthService
|
||||||
|
) {
|
||||||
|
super(authorizationService, router, authService);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFeatureID(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<FeatureID> {
|
||||||
|
return of(FeatureID.EPersonForgotPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
import { autoserialize } from 'cerialize';
|
import { autoserialize } from 'cerialize';
|
||||||
import { CacheableObject } from '../cache/cacheable-object.model';
|
import { CacheableObject } from '../cache/cacheable-object.model';
|
||||||
|
import { BrowseByDataType } from '../../browse-by/browse-by-switcher/browse-by-data-type';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for BrowseDefinition models
|
* Base class for BrowseDefinition models
|
||||||
@@ -12,5 +13,5 @@ export abstract class BrowseDefinition extends CacheableObject {
|
|||||||
/**
|
/**
|
||||||
* Get the render type of the BrowseDefinition model
|
* Get the render type of the BrowseDefinition model
|
||||||
*/
|
*/
|
||||||
abstract getRenderType(): string;
|
abstract getRenderType(): BrowseByDataType;
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user