From 77a85793b6d688224514558a2de7bf4c0226b286 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 17 Mar 2021 13:08:52 +0100 Subject: [PATCH 01/31] delete mantis theme --- src/assets/images/dspace-logo-monochrome.svg | 37 -------- .../home-news/home-news.component.html | 21 ----- .../home-news/home-news.component.scss | 17 ---- .../app/+home-page/home-page.component.html | 10 --- .../app/+home-page/home-page.component.scss | 52 ----------- .../simple/item-page.component.html | 9 -- .../publication/publication.component.html | 87 ------------------ .../publication/publication.component.scss | 30 ------- .../journal-issue.component.html | 77 ---------------- .../journal-issue.component.scss | 30 ------- .../journal-volume.component.html | 62 ------------- .../journal-volume.component.scss | 30 ------- .../item-pages/journal/journal.component.html | 70 --------------- .../item-pages/journal/journal.component.scss | 38 -------- .../org-unit/org-unit.component.html | 81 ----------------- .../org-unit/org-unit.component.scss | 30 ------- .../item-pages/person/person.component.html | 88 ------------------- .../item-pages/person/person.component.scss | 38 -------- .../item-pages/project/project.component.html | 84 ------------------ .../item-pages/project/project.component.scss | 30 ------- .../mantis/app/navbar/navbar.component.html | 16 ---- .../mantis/app/navbar/navbar.component.scss | 7 -- .../search-form/search-form.component.html | 21 ----- .../search-facet-option.component.html | 9 -- .../search-facet-range-option.component.html | 8 -- .../search-filter.component.html | 7 -- .../search-filter.component.scss | 10 --- .../search-range-filter.component.scss | 5 -- .../search-filters.component.html | 7 -- .../search-settings.component.html | 24 ----- .../search-settings.component.scss | 10 --- src/themes/mantis/readme.md | 2 - .../styles/_themed_custom_variables.scss | 4 - 33 files changed, 1051 deletions(-) delete mode 100644 src/assets/images/dspace-logo-monochrome.svg delete mode 100644 src/themes/mantis/app/+home-page/home-news/home-news.component.html delete mode 100644 src/themes/mantis/app/+home-page/home-news/home-news.component.scss delete mode 100644 src/themes/mantis/app/+home-page/home-page.component.html delete mode 100644 src/themes/mantis/app/+home-page/home-page.component.scss delete mode 100644 src/themes/mantis/app/+item-page/simple/item-page.component.html delete mode 100644 src/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.html delete mode 100644 src/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.scss delete mode 100644 src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html delete mode 100644 src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss delete mode 100644 src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html delete mode 100644 src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss delete mode 100644 src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html delete mode 100644 src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss delete mode 100644 src/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html delete mode 100644 src/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss delete mode 100644 src/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html delete mode 100644 src/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.scss delete mode 100644 src/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.html delete mode 100644 src/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.scss delete mode 100644 src/themes/mantis/app/navbar/navbar.component.html delete mode 100644 src/themes/mantis/app/navbar/navbar.component.scss delete mode 100644 src/themes/mantis/app/shared/search-form/search-form.component.html delete mode 100644 src/themes/mantis/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html delete mode 100644 src/themes/mantis/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html delete mode 100644 src/themes/mantis/app/shared/search/search-filters/search-filter/search-filter.component.html delete mode 100644 src/themes/mantis/app/shared/search/search-filters/search-filter/search-filter.component.scss delete mode 100644 src/themes/mantis/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.scss delete mode 100644 src/themes/mantis/app/shared/search/search-filters/search-filters.component.html delete mode 100644 src/themes/mantis/app/shared/search/search-settings/search-settings.component.html delete mode 100644 src/themes/mantis/app/shared/search/search-settings/search-settings.component.scss delete mode 100644 src/themes/mantis/readme.md delete mode 100644 src/themes/mantis/styles/_themed_custom_variables.scss diff --git a/src/assets/images/dspace-logo-monochrome.svg b/src/assets/images/dspace-logo-monochrome.svg deleted file mode 100644 index 5a2204ad83..0000000000 --- a/src/assets/images/dspace-logo-monochrome.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - diff --git a/src/themes/mantis/app/+home-page/home-news/home-news.component.html b/src/themes/mantis/app/+home-page/home-news/home-news.component.html deleted file mode 100644 index 4da3ae12f7..0000000000 --- a/src/themes/mantis/app/+home-page/home-news/home-news.component.html +++ /dev/null @@ -1,21 +0,0 @@ -
-
-
-

DSpace 7

-

DSpace is the world leading open source repository platform that enables - organisations to:

-
-
- -

Join an international community of leading institutions using DSpace.

-
diff --git a/src/themes/mantis/app/+home-page/home-news/home-news.component.scss b/src/themes/mantis/app/+home-page/home-news/home-news.component.scss deleted file mode 100644 index b82d84a71e..0000000000 --- a/src/themes/mantis/app/+home-page/home-news/home-news.component.scss +++ /dev/null @@ -1,17 +0,0 @@ -@import 'src/app/+home-page/home-news/home-news.component.scss'; -:host { - --ds-home-news-link-color: #{$green}; - --ds-home-news-link-hover-color: #{darken($green, 15%)}; - - .jumbotron { - background-color: transparent; - } - - a { - color: var(--ds-home-news-link-color); - - @include hover { - color: var(--ds-home-news-link-hover-color); - } - } -} diff --git a/src/themes/mantis/app/+home-page/home-page.component.html b/src/themes/mantis/app/+home-page/home-page.component.html deleted file mode 100644 index 43edbee9ca..0000000000 --- a/src/themes/mantis/app/+home-page/home-page.component.html +++ /dev/null @@ -1,10 +0,0 @@ -
-
- - -
- Photo by @inspiredimages -
-
- -
diff --git a/src/themes/mantis/app/+home-page/home-page.component.scss b/src/themes/mantis/app/+home-page/home-page.component.scss deleted file mode 100644 index d4350c1e13..0000000000 --- a/src/themes/mantis/app/+home-page/home-page.component.scss +++ /dev/null @@ -1,52 +0,0 @@ -@import 'src/app/+home-page/home-page.component.scss'; - -div.background-image { - color: white; - background-color: var(--bs-info); - position: relative; - background-position-y: -200px; - background-image: url('/assets/images/banner.jpg'); - background-size: cover; - @media screen and (max-width: map-get($grid-breakpoints, lg)) { - background-position-y: 0; - } - - .container { - position: relative; - text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6); - - &:before, &:after { - content: ''; - display: block; - width: var(--ds-banner-background-gradient-width); - height: 100%; - top: 0; - position: absolute; - } - - &:before { - background: linear-gradient(to left, var(--ds-banner-text-background), transparent); - left: calc(-1 * var(--ds-banner-background-gradient-width)); - - } - - &:after { - background: linear-gradient(to right, var(--ds-banner-text-background), transparent); - right: calc(-1 * var(--ds-banner-background-gradient-width)); - } - - background-color: var(--ds-banner-text-background); - } - - - small.credits { - a { - color: inherit; - } - - opacity: 0.3; - position: absolute; - right: var(--bs-spacer); - bottom: 0; - } -} diff --git a/src/themes/mantis/app/+item-page/simple/item-page.component.html b/src/themes/mantis/app/+item-page/simple/item-page.component.html deleted file mode 100644 index 83f910e0cd..0000000000 --- a/src/themes/mantis/app/+item-page/simple/item-page.component.html +++ /dev/null @@ -1,9 +0,0 @@ -
-
-
- -
-
- - -
diff --git a/src/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.html b/src/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.html deleted file mode 100644 index 705d40a7c7..0000000000 --- a/src/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.html +++ /dev/null @@ -1,87 +0,0 @@ -a
-
-
- - -
- -
-
- - - - - - - - - - - -
-
- - - - - - < - - -
-
-
-
-
-
-
-
-
- - - - - - -
-
-
diff --git a/src/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.scss b/src/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.scss deleted file mode 100644 index 6d12fcec71..0000000000 --- a/src/themes/mantis/app/+item-page/simple/item-types/publication/publication.component.scss +++ /dev/null @@ -1,30 +0,0 @@ -@import 'src/app/+item-page/simple/item-types/publication/publication.component.scss'; - -:host { - > * { - display: block; - padding-top: var(--ds-content-spacing); - padding-bottom: var(--ds-content-spacing); - } - - .top-item-page { - background-color: var(--bs-gray-100); - margin-top: calc(-1 * var(--ds-content-spacing)); - } - - .relationships-item-page { - padding-bottom: calc(var(--ds-content-spacing) - var(--bs-spacer)); - } - - ds-metadata-field-wrapper { - @media screen and (max-width: map-get($grid-breakpoints, md)) { - flex: 1; - padding-right: calc(var(--bs-spacer) / 2); - } - - ds-thumbnail { - display: block; - max-width: var(--ds-thumbnail-max-width); - } - } -} diff --git a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html b/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html deleted file mode 100644 index eb872ca175..0000000000 --- a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.html +++ /dev/null @@ -1,77 +0,0 @@ -
-
-
- - -
-

- {{'journalissue.page.titleprefix' | translate}} - -

-
-
- - - - - - - - -
-
- - - - - - -
-
-
-
-
-
-
-
-
- - - - -
-
-
diff --git a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss b/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss deleted file mode 100644 index c0a314d715..0000000000 --- a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss +++ /dev/null @@ -1,30 +0,0 @@ -@import 'src/app/entity-groups/journal-entities/item-pages/journal-issue/journal-issue.component.scss'; - -:host { - > * { - display: block; - padding-top: var(--ds-content-spacing); - padding-bottom: var(--ds-content-spacing); - } - - .top-item-page { - background-color: var(--bs-gray-100); - margin-top: calc(-1 * var(--ds-content-spacing)); - } - - .relationships-item-page { - padding-bottom: calc(var(--ds-content-spacing) - var(--bs-spacer)); - } - - ds-metadata-field-wrapper { - @media screen and (max-width: map-get($grid-breakpoints, md)) { - flex: 1; - padding-right: calc(var(--bs-spacer) / 2); - } - - ds-thumbnail { - display: block; - max-width: var(--ds-thumbnail-max-width); - } - } -} diff --git a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html b/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html deleted file mode 100644 index bc11d4ba74..0000000000 --- a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html +++ /dev/null @@ -1,62 +0,0 @@ -
-
-
- - -
-

- {{'journalvolume.page.titleprefix' | translate}} - -

-
-
- - - - - -
-
- - -
-
-
-
-
-
-
-
-
- - - - -
-
-
diff --git a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss b/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss deleted file mode 100644 index 6e418258d3..0000000000 --- a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss +++ /dev/null @@ -1,30 +0,0 @@ -@import 'src/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.scss'; - -:host { - > * { - display: block; - padding-top: var(--ds-content-spacing); - padding-bottom: var(--ds-content-spacing); - } - - .top-item-page { - background-color: var(--bs-gray-100); - margin-top: calc(-1 * var(--ds-content-spacing)); - } - - .relationships-item-page { - padding-bottom: calc(var(--ds-content-spacing) - var(--bs-spacer)); - } - - ds-metadata-field-wrapper { - @media screen and (max-width: map-get($grid-breakpoints, md)) { - flex: 1; - padding-right: calc(var(--bs-spacer) / 2); - } - - ds-thumbnail { - display: block; - max-width: var(--ds-thumbnail-max-width); - } - } -} diff --git a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html b/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html deleted file mode 100644 index cf4cef27de..0000000000 --- a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.html +++ /dev/null @@ -1,70 +0,0 @@ -
-
-
- -
-

- {{'journal.page.titleprefix' | translate}} - -

-
-
- - - - - - - -
-
- - -
-
-
-
-
-
-
-
-
- - -
-
-
-
-
-

{{"item.page.journal.search.title" | translate}}

-
- - -
diff --git a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss b/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss deleted file mode 100644 index d2a7c8ca46..0000000000 --- a/src/themes/mantis/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss +++ /dev/null @@ -1,38 +0,0 @@ -@import 'src/app/entity-groups/journal-entities/item-pages/journal/journal.component.scss'; - -:host { - > * { - display: block; - padding-top: var(--ds-content-spacing); - padding-bottom: var(--ds-content-spacing); - } - - .top-item-page { - background-color: var(--bs-gray-100); - margin-top: calc(-1 * var(--ds-content-spacing)); - } - - .relationships-item-page { - padding-bottom: calc(var(--ds-content-spacing) - var(--bs-spacer)); - } - - ds-metadata-field-wrapper { - @media screen and (max-width: map-get($grid-breakpoints, md)) { - flex: 1; - padding-right: calc(var(--bs-spacer) / 2); - } - - ds-thumbnail { - display: block; - max-width: var(--ds-thumbnail-max-width); - } - } - - .search-container { - margin-bottom: var(--bs-spacer); - @media screen and (max-width: map-get($grid-breakpoints, lg)) { - width: 100%; - max-width: none; - } - } -} diff --git a/src/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html b/src/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html deleted file mode 100644 index 114443468a..0000000000 --- a/src/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html +++ /dev/null @@ -1,81 +0,0 @@ -
-
-
- - -
-

- {{'orgunit.page.titleprefix' | translate}} - -

-
-
- - - - - - -
-
- - - - -
-
-
-
-
-
-
-
-
- - -
-
-
-
-
- - -
-
diff --git a/src/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss b/src/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss deleted file mode 100644 index aff2622323..0000000000 --- a/src/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss +++ /dev/null @@ -1,30 +0,0 @@ -@import 'src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss'; - -:host { - > * { - display: block; - padding-top: var(--ds-content-spacing); - padding-bottom: var(--ds-content-spacing); - } - - .top-item-page { - background-color: var(--bs-gray-100); - margin-top: calc(-1 * var(--ds-content-spacing)); - } - - .relationships-item-page { - padding-bottom: calc(var(--ds-content-spacing) - var(--bs-spacer)); - } - - ds-metadata-field-wrapper { - @media screen and (max-width: map-get($grid-breakpoints, md)) { - flex: 1; - padding-right: calc(var(--bs-spacer) / 2); - } - - ds-thumbnail { - display: block; - max-width: var(--ds-thumbnail-max-width); - } - } -} diff --git a/src/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html b/src/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html deleted file mode 100644 index 077ac1b9f1..0000000000 --- a/src/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html +++ /dev/null @@ -1,88 +0,0 @@ -
-
-
- - -
-

- {{'person.page.titleprefix' | translate}} -

-
-
- - - - - - - - - - - - -
-
- - - - - - -
-
-
-
-
-
-
-
-
- - - - -
-
-
-
-
-

{{"item.page.person.search.title" | translate}}

-
- - -
diff --git a/src/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.scss b/src/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.scss deleted file mode 100644 index 51c15716f9..0000000000 --- a/src/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.scss +++ /dev/null @@ -1,38 +0,0 @@ -@import 'src/app/entity-groups/research-entities/item-pages/person/person.component.scss'; - -:host { - > * { - display: block; - padding-top: var(--ds-content-spacing); - padding-bottom: var(--ds-content-spacing); - } - - .top-item-page { - background-color: var(--bs-gray-100); - margin-top: calc(-1 * var(--ds-content-spacing)); - } - - .relationships-item-page { - padding-bottom: calc(var(--ds-content-spacing) - var(--bs-spacer)); - } - - ds-metadata-field-wrapper { - @media screen and (max-width: map-get($grid-breakpoints, md)) { - flex: 1; - padding-right: calc(var(--bs-spacer) / 2); - } - - ds-thumbnail { - display: block; - max-width: var(--ds-thumbnail-max-width); - } - } - - .search-container { - margin-bottom: var(--bs-spacer); - @media screen and (max-width: map-get($grid-breakpoints, lg)) { - width: 100%; - max-width: none; - } - } -} diff --git a/src/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.html b/src/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.html deleted file mode 100644 index e5b0884f14..0000000000 --- a/src/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.html +++ /dev/null @@ -1,84 +0,0 @@ -
-
-
- - -
-

- {{'project.page.titleprefix' | translate}} -

-
-
- - - - - - - - - - - - -
-
- - - - - - -
-
-
-
-
-
-
-
-
- - - - - - -
-
-
- diff --git a/src/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.scss b/src/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.scss deleted file mode 100644 index 076baad1a0..0000000000 --- a/src/themes/mantis/app/entity-groups/research-entities/item-pages/project/project.component.scss +++ /dev/null @@ -1,30 +0,0 @@ -@import 'src/app/entity-groups/research-entities/item-pages/project/project.component.scss'; - -:host { - > * { - display: block; - padding-top: var(--ds-content-spacing); - padding-bottom: var(--ds-content-spacing); - } - - .top-item-page { - background-color: var(--bs-gray-100); - margin-top: calc(-1 * var(--ds-content-spacing)); - } - - .relationships-item-page { - padding-bottom: calc(var(--ds-content-spacing) - var(--bs-spacer)); - } - - ds-metadata-field-wrapper { - @media screen and (max-width: map-get($grid-breakpoints, md)) { - flex: 1; - padding-right: calc(var(--bs-spacer) / 2); - } - - ds-thumbnail { - display: block; - max-width: var(--ds-thumbnail-max-width); - } - } -} diff --git a/src/themes/mantis/app/navbar/navbar.component.html b/src/themes/mantis/app/navbar/navbar.component.html deleted file mode 100644 index d06eceb222..0000000000 --- a/src/themes/mantis/app/navbar/navbar.component.html +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/src/themes/mantis/app/navbar/navbar.component.scss b/src/themes/mantis/app/navbar/navbar.component.scss deleted file mode 100644 index 1417acff59..0000000000 --- a/src/themes/mantis/app/navbar/navbar.component.scss +++ /dev/null @@ -1,7 +0,0 @@ -@import 'src/app/navbar/navbar.component.scss'; - -nav.navbar { - border-bottom: 5px $green solid; -} - - diff --git a/src/themes/mantis/app/shared/search-form/search-form.component.html b/src/themes/mantis/app/shared/search-form/search-form.component.html deleted file mode 100644 index ea2f54813e..0000000000 --- a/src/themes/mantis/app/shared/search-form/search-form.component.html +++ /dev/null @@ -1,21 +0,0 @@ -
-
- -
-
-
- - - - -
-
-
diff --git a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html b/src/themes/mantis/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html deleted file mode 100644 index 86076dfd10..0000000000 --- a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-option/search-facet-option.component.html +++ /dev/null @@ -1,9 +0,0 @@ - - - {{filterValue.value}} - - {{filterValue.count}} - - \ No newline at end of file diff --git a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html b/src/themes/mantis/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html deleted file mode 100644 index bdb37cb52d..0000000000 --- a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-facet-filter-options/search-facet-range-option/search-facet-range-option.component.html +++ /dev/null @@ -1,8 +0,0 @@ - - {{filterValue.value}} - - {{filterValue.count}} - - \ No newline at end of file diff --git a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-filter.component.html b/src/themes/mantis/app/shared/search/search-filters/search-filter/search-filter.component.html deleted file mode 100644 index 850447a39e..0000000000 --- a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-filter.component.html +++ /dev/null @@ -1,7 +0,0 @@ -
-
{{'search.filters.filter.' + filter.name + '.head'| translate}}
-
- -
-
diff --git a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-filter.component.scss b/src/themes/mantis/app/shared/search/search-filters/search-filter/search-filter.component.scss deleted file mode 100644 index 0e78c64629..0000000000 --- a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-filter.component.scss +++ /dev/null @@ -1,10 +0,0 @@ -@import 'src/app/shared/search/search-filters/search-filter/search-filter.component.scss'; - -.facet-filter { - background-color: var(--bs-light); - border-radius: var(--bs-border-radius); - - h5 { - font-size: 1.1rem - } -} diff --git a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.scss b/src/themes/mantis/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.scss deleted file mode 100644 index 158a0d3b4e..0000000000 --- a/src/themes/mantis/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.scss +++ /dev/null @@ -1,5 +0,0 @@ -@import 'src/app/shared/search/search-filters/search-filter/search-range-filter/search-range-filter.component.scss'; - -::ng-deep .noUi-connect { - background: var(--bs-info); -} diff --git a/src/themes/mantis/app/shared/search/search-filters/search-filters.component.html b/src/themes/mantis/app/shared/search/search-filters/search-filters.component.html deleted file mode 100644 index b7bb1bf50f..0000000000 --- a/src/themes/mantis/app/shared/search/search-filters/search-filters.component.html +++ /dev/null @@ -1,7 +0,0 @@ -

{{"search.filters.head" | translate}}

-
-
- -
-
-{{"search.filters.reset" | translate}} diff --git a/src/themes/mantis/app/shared/search/search-settings/search-settings.component.html b/src/themes/mantis/app/shared/search/search-settings/search-settings.component.html deleted file mode 100644 index 1321ced928..0000000000 --- a/src/themes/mantis/app/shared/search/search-settings/search-settings.component.html +++ /dev/null @@ -1,24 +0,0 @@ - -

{{ 'search.sidebar.settings.title' | translate}}

-
-
{{ 'search.sidebar.settings.sort-by' | translate}}
- -
- -
-
{{ 'search.sidebar.settings.rpp' | translate}}
- -
-
\ No newline at end of file diff --git a/src/themes/mantis/app/shared/search/search-settings/search-settings.component.scss b/src/themes/mantis/app/shared/search/search-settings/search-settings.component.scss deleted file mode 100644 index b3ee0ba60e..0000000000 --- a/src/themes/mantis/app/shared/search/search-settings/search-settings.component.scss +++ /dev/null @@ -1,10 +0,0 @@ -@import 'src/app/shared/search/search-settings/search-settings.component.scss'; - -.setting-option { - background-color: var(--bs-light); - border-radius: var(--bs-border-radius); - h5 { - font-size: 1.1rem - } -} - diff --git a/src/themes/mantis/readme.md b/src/themes/mantis/readme.md deleted file mode 100644 index 93bdf36f47..0000000000 --- a/src/themes/mantis/readme.md +++ /dev/null @@ -1,2 +0,0 @@ -#Note -For now the existing mantis theme has only been moved to the new themes folder, it has not yet been adapted to work as a dynamic theme. \ No newline at end of file diff --git a/src/themes/mantis/styles/_themed_custom_variables.scss b/src/themes/mantis/styles/_themed_custom_variables.scss deleted file mode 100644 index 60d248c4ae..0000000000 --- a/src/themes/mantis/styles/_themed_custom_variables.scss +++ /dev/null @@ -1,4 +0,0 @@ -:root { - --ds-banner-text-background: rgba(0, 0, 0, 0.35); - --ds-banner-background-gradient-width: 300px; -} From fb90bf64d93a021a197150fcb78f561fd2c4954e Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 17 Mar 2021 13:13:11 +0100 Subject: [PATCH 02/31] create simple dspace theme based on mantis --- angular.json | 5 + .../dspace/app/navbar/navbar.component.scss | 5 + .../dspace/app/navbar/navbar.component.ts | 15 +++ src/themes/dspace/assets/fonts/.gitkeep | 0 src/themes/dspace/assets/images/.gitkeep | 0 src/themes/dspace/entry-components.ts | 2 + src/themes/dspace/styles/_global-styles.scss | 23 ++++ .../styles/_theme_css_variable_overrides.scss | 9 ++ .../_theme_sass_variable_overrides.scss} | 13 ++- src/themes/dspace/styles/theme.scss | 12 ++ src/themes/dspace/theme.module.ts | 103 ++++++++++++++++++ 11 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 src/themes/dspace/app/navbar/navbar.component.scss create mode 100644 src/themes/dspace/app/navbar/navbar.component.ts create mode 100644 src/themes/dspace/assets/fonts/.gitkeep create mode 100644 src/themes/dspace/assets/images/.gitkeep create mode 100644 src/themes/dspace/entry-components.ts create mode 100644 src/themes/dspace/styles/_global-styles.scss create mode 100644 src/themes/dspace/styles/_theme_css_variable_overrides.scss rename src/themes/{mantis/styles/_themed_bootstrap_variables.scss => dspace/styles/_theme_sass_variable_overrides.scss} (56%) create mode 100644 src/themes/dspace/styles/theme.scss create mode 100644 src/themes/dspace/theme.module.ts diff --git a/angular.json b/angular.json index 19cbe94be6..00799dc33c 100644 --- a/angular.json +++ b/angular.json @@ -56,6 +56,11 @@ "input": "src/themes/custom/styles/theme.scss", "inject": false, "bundleName": "custom-theme" + }, + { + "input": "src/themes/dspace/styles/theme.scss", + "inject": false, + "bundleName": "dspace-theme" } ], "scripts": [] diff --git a/src/themes/dspace/app/navbar/navbar.component.scss b/src/themes/dspace/app/navbar/navbar.component.scss new file mode 100644 index 0000000000..463a4269ee --- /dev/null +++ b/src/themes/dspace/app/navbar/navbar.component.scss @@ -0,0 +1,5 @@ +@import 'src/app/navbar/navbar.component.scss'; + +nav.navbar { + border-bottom: 5px var(--bs-green) solid; +} diff --git a/src/themes/dspace/app/navbar/navbar.component.ts b/src/themes/dspace/app/navbar/navbar.component.ts new file mode 100644 index 0000000000..e375011683 --- /dev/null +++ b/src/themes/dspace/app/navbar/navbar.component.ts @@ -0,0 +1,15 @@ +import { Component } from '@angular/core'; +import { NavbarComponent as BaseComponent } from '../../../../app/navbar/navbar.component'; +import { slideMobileNav } from '../../../../app/shared/animations/slide'; + +/** + * Component representing the public navbar + */ +@Component({ + selector: 'ds-navbar', + styleUrls: ['./navbar.component.scss'], + templateUrl: '../../../../app/navbar/navbar.component.html', + animations: [slideMobileNav] +}) +export class NavbarComponent extends BaseComponent { +} diff --git a/src/themes/dspace/assets/fonts/.gitkeep b/src/themes/dspace/assets/fonts/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/dspace/assets/images/.gitkeep b/src/themes/dspace/assets/images/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/themes/dspace/entry-components.ts b/src/themes/dspace/entry-components.ts new file mode 100644 index 0000000000..2386ecb130 --- /dev/null +++ b/src/themes/dspace/entry-components.ts @@ -0,0 +1,2 @@ +export const ENTRY_COMPONENTS = [ +]; diff --git a/src/themes/dspace/styles/_global-styles.scss b/src/themes/dspace/styles/_global-styles.scss new file mode 100644 index 0000000000..1fb60b64a2 --- /dev/null +++ b/src/themes/dspace/styles/_global-styles.scss @@ -0,0 +1,23 @@ +// Add any global css for the theme here + +// imports the base global style +@import '../../../styles/_global-styles.scss'; + +.facet-filter,.setting-option { + background-color: var(--bs-light); + border-radius: var(--bs-border-radius); + + &.p-3 { + // Needs !important because the original bootstrap class uses it + padding-top: 0.5rem !important; + padding-bottom: 0.5rem !important; + } + + .badge-secondary { + background-color: var(--bs-primary); + } + + h5 { + font-size: 1.1rem + } +} diff --git a/src/themes/dspace/styles/_theme_css_variable_overrides.scss b/src/themes/dspace/styles/_theme_css_variable_overrides.scss new file mode 100644 index 0000000000..f95ab5c764 --- /dev/null +++ b/src/themes/dspace/styles/_theme_css_variable_overrides.scss @@ -0,0 +1,9 @@ +// Override or add CSS variables for your theme here + +:root { + --ds-banner-text-background: rgba(0, 0, 0, 0.35); + --ds-banner-background-gradient-width: 300px; + --ds-home-news-link-color: $green; + --ds-home-news-link-hover-color: #{darken($green, 15%)}; +} + diff --git a/src/themes/mantis/styles/_themed_bootstrap_variables.scss b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss similarity index 56% rename from src/themes/mantis/styles/_themed_bootstrap_variables.scss rename to src/themes/dspace/styles/_theme_sass_variable_overrides.scss index e606502e09..86d4092a53 100644 --- a/src/themes/mantis/styles/_themed_bootstrap_variables.scss +++ b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss @@ -1,13 +1,20 @@ +// DSpace works with CSS variables for its own components, and has a mapping of all bootstrap Sass +// variables to CSS equivalents (see src/styles/_bootstrap_variables_mapping.scss). However Bootstrap +// still uses Sass variables internally. So if you want to override bootstrap (or other sass +// variables) you can do so here. Their CSS counterparts will include the changes you make here + @import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro:200,200i,300,300i,400,400i,600,600i,700,700i,900,900i&subset=cyrillic,cyrillic-ext,greek,greek-ext,latin-ext,vietnamese'); $font-family-sans-serif: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; -$gray-100: #e8ebf3 !default; // #eee +$gray-100: #e8ebf3 !default; +$gray-400: #ced4da !default; $gray-800: #444444 !default; // #444 $navbar-dark-color: #FFFFFF; /* Reassign color vars to semantic color scheme */ $blue: #43515f !default; +//$green: #92C642 !default; $green: #92C642 !default; $cyan: #2e80a3 !default; $yellow: #ec9433 !default; @@ -16,4 +23,8 @@ $dark: #43515f !default; $body-color: $gray-800 !default; +$table-accent-bg: $gray-100 !default; +$table-hover-bg: $gray-400 !default; + $yiq-contrasted-threshold: 170 !default; + diff --git a/src/themes/dspace/styles/theme.scss b/src/themes/dspace/styles/theme.scss new file mode 100644 index 0000000000..e4cc9e45ed --- /dev/null +++ b/src/themes/dspace/styles/theme.scss @@ -0,0 +1,12 @@ +// This file combines the other scss files in to one. You usually shouldn't edit this file directly + +@import './_theme_sass_variable_overrides.scss'; +@import '../../../styles/_variables.scss'; +@import '../../../styles/_mixins.scss'; +@import '../../../styles/helpers/font_awesome_imports.scss'; +@import '../../../../node_modules/bootstrap/scss/bootstrap.scss'; +@import '../../../../node_modules/nouislider/distribute/nouislider.min'; +@import '../../../styles/_custom_variables.scss'; +@import './_theme_css_variable_overrides.scss'; +@import '../../../styles/bootstrap_variables_mapping.scss'; +@import './_global-styles.scss'; diff --git a/src/themes/dspace/theme.module.ts b/src/themes/dspace/theme.module.ts new file mode 100644 index 0000000000..2959728156 --- /dev/null +++ b/src/themes/dspace/theme.module.ts @@ -0,0 +1,103 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { AdminRegistriesModule } from '../../app/+admin/admin-registries/admin-registries.module'; +import { AdminSearchModule } from '../../app/+admin/admin-search-page/admin-search.module'; +import { AdminWorkflowModuleModule } from '../../app/+admin/admin-workflow-page/admin-workflow.module'; +import { BitstreamFormatsModule } from '../../app/+admin/admin-registries/bitstream-formats/bitstream-formats.module'; +import { BrowseByModule } from '../../app/+browse-by/browse-by.module'; +import { CollectionFormModule } from '../../app/+collection-page/collection-form/collection-form.module'; +import { CommunityFormModule } from '../../app/+community-page/community-form/community-form.module'; +import { CoreModule } from '../../app/core/core.module'; +import { DragDropModule } from '@angular/cdk/drag-drop'; +import { EditItemPageModule } from '../../app/+item-page/edit-item-page/edit-item-page.module'; +import { FormsModule } from '@angular/forms'; +import { HttpClientModule } from '@angular/common/http'; +import { IdlePreloadModule } from 'angular-idle-preload'; +import { JournalEntitiesModule } from '../../app/entity-groups/journal-entities/journal-entities.module'; +import { MyDspaceSearchModule } from '../../app/+my-dspace-page/my-dspace-search.module'; +import { MenuModule } from '../../app/shared/menu/menu.module'; +import { NavbarModule } from '../../app/navbar/navbar.module'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; +import { ProfilePageModule } from '../../app/profile-page/profile-page.module'; +import { RegisterEmailFormModule } from '../../app/register-email-form/register-email-form.module'; +import { ResearchEntitiesModule } from '../../app/entity-groups/research-entities/research-entities.module'; +import { ScrollToModule } from '@nicky-lenaers/ngx-scroll-to'; +import { SearchPageModule } from '../../app/+search-page/search-page.module'; +import { SharedModule } from '../../app/shared/shared.module'; +import { StatisticsModule } from '../../app/statistics/statistics.module'; +import { StoreModule } from '@ngrx/store'; +import { StoreRouterConnectingModule } from '@ngrx/router-store'; +import { TranslateModule } from '@ngx-translate/core'; +import { HomePageModule } from '../../app/+home-page/home-page.module'; +import { AppModule } from '../../app/app.module'; +import { ItemPageModule } from '../../app/+item-page/item-page.module'; +import { RouterModule } from '@angular/router'; +import { CommunityListPageModule } from '../../app/community-list-page/community-list-page.module'; +import { InfoModule } from '../../app/info/info.module'; +import { StatisticsPageModule } from '../../app/statistics-page/statistics-page.module'; +import { CommunityPageModule } from '../../app/+community-page/community-page.module'; +import { CollectionPageModule } from '../../app/+collection-page/collection-page.module'; +import { SubmissionModule } from '../../app/submission/submission.module'; +import { MyDSpacePageModule } from '../../app/+my-dspace-page/my-dspace-page.module'; +import { NavbarComponent } from './app/navbar/navbar.component'; + +const DECLARATIONS = [ + NavbarComponent +]; + +@NgModule({ + imports: [ + AdminRegistriesModule, + AdminSearchModule, + AdminWorkflowModuleModule, + AppModule, + BitstreamFormatsModule, + BrowseByModule, + CollectionFormModule, + CollectionPageModule, + CommonModule, + CommunityFormModule, + CommunityListPageModule, + CommunityPageModule, + CoreModule, + DragDropModule, + ItemPageModule, + EditItemPageModule, + FormsModule, + HomePageModule, + HttpClientModule, + IdlePreloadModule, + InfoModule, + JournalEntitiesModule, + MenuModule, + MyDspaceSearchModule, + NavbarModule, + NgbModule, + ProfilePageModule, + RegisterEmailFormModule, + ResearchEntitiesModule, + RouterModule, + ScrollToModule, + SearchPageModule, + SharedModule, + StatisticsModule, + StatisticsPageModule, + StoreModule, + StoreRouterConnectingModule, + TranslateModule, + SubmissionModule, + MyDSpacePageModule, + MyDspaceSearchModule, + ], + declarations: DECLARATIONS +}) + + /** + * This module serves as an index for all the components in this theme. + * It should import all other modules, so the compiler knows where to find any components referenced + * from a component in this theme + * It is purposefully not exported, it should never be imported anywhere else, its only purpose is + * to give lazily loaded components a context in which they can be compiled successfully + */ +class ThemeModule { +} From 619f4d74c2a7e115ba1c1a641b50d4dc6839a690 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 17 Mar 2021 13:30:25 +0100 Subject: [PATCH 03/31] make dspace the default theme --- src/environments/environment.common.ts | 8 ++++++-- src/index.html | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/environments/environment.common.ts b/src/environments/environment.common.ts index b8248890fc..4e246f7243 100644 --- a/src/environments/environment.common.ts +++ b/src/environments/environment.common.ts @@ -255,10 +255,14 @@ export const environment: GlobalConfig = { // // A theme with only a name will match every route // name: 'custom' // }, + // { + // // This theme will use the default bootstrap styling for DSpace components + // name: BASE_THEME_NAME + // }, { - // This theme will use the default bootstrap styling for DSpace components - name: BASE_THEME_NAME + // The default dspace theme + name: 'dspace' }, ], // Whether the UI should rewrite file download URLs to match its domain. Only necessary to enable when running UI and REST API on separate domains diff --git a/src/index.html b/src/index.html index 072938b5ef..cb5d35e4f5 100644 --- a/src/index.html +++ b/src/index.html @@ -7,7 +7,7 @@ DSpace - + From 60d917bb8a55671587b999f7b52fc50b49a6cb48 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 17 Mar 2021 13:31:11 +0100 Subject: [PATCH 04/31] style home-page-news --- .../home-news/home-news.component.html | 26 +++++++ .../home-news/home-news.component.scss | 69 ++++++++++++++++++ .../home-news/home-news.component.ts | 14 ++++ .../dspace}/assets/images/banner.jpg | Bin .../styles/_theme_css_variable_overrides.scss | 2 +- src/themes/dspace/theme.module.ts | 2 + 6 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 src/themes/dspace/app/+home-page/home-news/home-news.component.html create mode 100644 src/themes/dspace/app/+home-page/home-news/home-news.component.scss create mode 100644 src/themes/dspace/app/+home-page/home-news/home-news.component.ts rename src/{ => themes/dspace}/assets/images/banner.jpg (100%) diff --git a/src/themes/dspace/app/+home-page/home-news/home-news.component.html b/src/themes/dspace/app/+home-page/home-news/home-news.component.html new file mode 100644 index 0000000000..9063961ace --- /dev/null +++ b/src/themes/dspace/app/+home-page/home-news/home-news.component.html @@ -0,0 +1,26 @@ +
+
+
+
+
+

DSpace 7

+

DSpace is the world leading open source repository platform that enables + organisations to:

+
+
+
    +
  • easily ingest documents, audio, video, datasets and their corresponding Dublin Core + metadata +
  • +
  • open up this content to local and global audiences, thanks to the OAI-PMH interface and + Google Scholar optimizations +
  • +
  • issue permanent urls and trustworthy identifiers, including optional integrations with + handle.net and DataCite DOI +
  • +
+

Join an international community of leading institutions using DSpace.

+
+
+ Photo by @inspiredimages +
diff --git a/src/themes/dspace/app/+home-page/home-news/home-news.component.scss b/src/themes/dspace/app/+home-page/home-news/home-news.component.scss new file mode 100644 index 0000000000..b5a070e51e --- /dev/null +++ b/src/themes/dspace/app/+home-page/home-news/home-news.component.scss @@ -0,0 +1,69 @@ +:host { + display: block; + margin-top: calc(var(--ds-content-spacing) * -1); + + div.background-image { + color: white; + background-color: var(--bs-info); + position: relative; + background-position-y: -200px; + background-image: url('/assets/dspace/images/banner.jpg'); + background-size: cover; + @media screen and (max-width: map-get($grid-breakpoints, lg)) { + background-position-y: 0; + } + + .container { + position: relative; + text-shadow: 1px 1px 4px rgba(0, 0, 0, 0.6); + + &:before, &:after { + content: ''; + display: block; + width: var(--ds-banner-background-gradient-width); + height: 100%; + top: 0; + position: absolute; + } + + &:before { + background: linear-gradient(to left, var(--ds-banner-text-background), transparent); + left: calc(-1 * var(--ds-banner-background-gradient-width)); + + } + + &:after { + background: linear-gradient(to right, var(--ds-banner-text-background), transparent); + right: calc(-1 * var(--ds-banner-background-gradient-width)); + } + + background-color: var(--ds-banner-text-background); + } + + + small.credits { + a { + color: inherit; + } + + opacity: 0.3; + position: absolute; + right: var(--bs-spacer); + bottom: 0; + } + } + + .jumbotron { + background-color: transparent; + } + + a { + color: var(--ds-home-news-link-color); + + @include hover { + color: var(--ds-home-news-link-hover-color); + } + } +} + + diff --git a/src/themes/dspace/app/+home-page/home-news/home-news.component.ts b/src/themes/dspace/app/+home-page/home-news/home-news.component.ts new file mode 100644 index 0000000000..7f06295407 --- /dev/null +++ b/src/themes/dspace/app/+home-page/home-news/home-news.component.ts @@ -0,0 +1,14 @@ +import { Component } from '@angular/core'; +import { HomeNewsComponent as BaseComponent } from '../../../../../app/+home-page/home-news/home-news.component'; + +@Component({ + selector: 'ds-home-news', + styleUrls: ['./home-news.component.scss'], + templateUrl: './home-news.component.html' +}) + +/** + * Component to render the news section on the home page + */ +export class HomeNewsComponent extends BaseComponent {} + diff --git a/src/assets/images/banner.jpg b/src/themes/dspace/assets/images/banner.jpg similarity index 100% rename from src/assets/images/banner.jpg rename to src/themes/dspace/assets/images/banner.jpg diff --git a/src/themes/dspace/styles/_theme_css_variable_overrides.scss b/src/themes/dspace/styles/_theme_css_variable_overrides.scss index f95ab5c764..75d4fd9362 100644 --- a/src/themes/dspace/styles/_theme_css_variable_overrides.scss +++ b/src/themes/dspace/styles/_theme_css_variable_overrides.scss @@ -3,7 +3,7 @@ :root { --ds-banner-text-background: rgba(0, 0, 0, 0.35); --ds-banner-background-gradient-width: 300px; - --ds-home-news-link-color: $green; + --ds-home-news-link-color: #{$green}; --ds-home-news-link-hover-color: #{darken($green, 15%)}; } diff --git a/src/themes/dspace/theme.module.ts b/src/themes/dspace/theme.module.ts index 2959728156..ed840c2e25 100644 --- a/src/themes/dspace/theme.module.ts +++ b/src/themes/dspace/theme.module.ts @@ -28,6 +28,7 @@ import { StatisticsModule } from '../../app/statistics/statistics.module'; import { StoreModule } from '@ngrx/store'; import { StoreRouterConnectingModule } from '@ngrx/router-store'; import { TranslateModule } from '@ngx-translate/core'; +import { HomeNewsComponent } from './app/+home-page/home-news/home-news.component'; import { HomePageModule } from '../../app/+home-page/home-page.module'; import { AppModule } from '../../app/app.module'; import { ItemPageModule } from '../../app/+item-page/item-page.module'; @@ -42,6 +43,7 @@ import { MyDSpacePageModule } from '../../app/+my-dspace-page/my-dspace-page.mod import { NavbarComponent } from './app/navbar/navbar.component'; const DECLARATIONS = [ + HomeNewsComponent, NavbarComponent ]; From cbee776fa4920b547f3ec6067d9d711a190f75cc Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Wed, 24 Mar 2021 14:44:10 +0100 Subject: [PATCH 05/31] fix issues with path separators in windows --- src/styles/_mixins.scss | 1 - webpack/helpers.ts | 4 ++-- webpack/webpack.common.ts | 8 +++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss index 04347b3131..ae9e6082f5 100644 --- a/src/styles/_mixins.scss +++ b/src/styles/_mixins.scss @@ -1,4 +1,3 @@ -@import '../../node_modules/bootstrap/scss/functions.scss'; @import '../../node_modules/bootstrap/scss/mixins.scss'; @mixin word-wrap() { diff --git a/webpack/helpers.ts b/webpack/helpers.ts index 532b170bf3..43855f6c72 100644 --- a/webpack/helpers.ts +++ b/webpack/helpers.ts @@ -6,8 +6,8 @@ export const projectRoot = (relativePath) => { export const globalCSSImports = () => { return [ - projectRoot('src/styles/_variables.scss'), - projectRoot('src/styles/_mixins.scss'), + projectRoot(path.join('src', 'styles', '_variables.scss')), + projectRoot(path.join('src', 'styles', '_mixins.scss')), ]; }; diff --git a/webpack/webpack.common.ts b/webpack/webpack.common.ts index 926241e0bb..07e55d89d4 100644 --- a/webpack/webpack.common.ts +++ b/webpack/webpack.common.ts @@ -17,7 +17,9 @@ export const copyWebpackOptions = { to: 'assets', }, { - from: path.join(__dirname, '..', 'src', 'themes', '*', 'assets', '**', '*'), + // replace(/\\/g, '/') because glob patterns need forward slashes, even on windows: + // https://github.com/mrmlnc/fast-glob#how-to-write-patterns-on-windows + from: path.join(__dirname, '..', 'src', 'themes', '*', 'assets', '**', '*').replace(/\\/g, '/'), to: 'assets', noErrorOnMissing: true, transformPath(targetPath, absolutePath) { @@ -77,7 +79,7 @@ export const commonExports = { test: /\.scss$/, exclude: [ /node_modules/, - /(_exposed)?_variables.scss$|\/src\/themes\/[^/]+\/styles\/.+.scss$/ + /(_exposed)?_variables.scss$|[\/|\\]src[\/|\\]themes[\/|\\].+?[\/|\\]styles[\/|\\].+\.scss$/ ], use: [ ...SCSS_LOADERS, @@ -90,7 +92,7 @@ export const commonExports = { ] }, { - test: /(_exposed)?_variables.scss$|\/src\/themes\/[^/]+\/styles\/.+.scss$/, + test: /(_exposed)?_variables.scss$|[\/|\\]src[\/|\\]themes[\/|\\].+?[\/|\\]styles[\/|\\].+\.scss$/, exclude: [/node_modules/], use: [ ...SCSS_LOADERS, From 868b7ad37bbcb49183c992194556b27715444c35 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Mon, 25 Jan 2021 15:46:21 +0100 Subject: [PATCH 06/31] 76150: Add a bitstream download page --- docs/Configuration.md | 66 -------- .../bitstream-page-routing.module.ts | 8 + .../full-file-section.component.html | 4 +- .../file-section/file-section.component.html | 2 +- .../data/feature-authorization/feature-id.ts | 1 + .../core/metadata/metadata.service.spec.ts | 8 +- src/app/core/metadata/metadata.service.ts | 10 +- .../detail/process-detail.component.html | 2 +- .../bitstream-download-page.component.html | 3 + .../bitstream-download-page.component.spec.ts | 158 ++++++++++++++++++ .../bitstream-download-page.component.ts | 83 +++++++++ .../file-download-link.component.html | 4 +- .../file-download-link.component.spec.ts | 35 ++-- .../file-download-link.component.ts | 26 ++- src/app/shared/shared.module.ts | 10 ++ src/assets/i18n/en.json5 | 4 + 16 files changed, 308 insertions(+), 116 deletions(-) create mode 100644 src/app/shared/bitstream-download-page/bitstream-download-page.component.html create mode 100644 src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts create mode 100644 src/app/shared/bitstream-download-page/bitstream-download-page.component.ts diff --git a/docs/Configuration.md b/docs/Configuration.md index f918622568..f4fff1166c 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -64,69 +64,3 @@ In order to start using one of these services, select it from the [Angulartics P The Google Analytics script was added in [`main.browser.ts`](https://github.com/DSpace/dspace-angular/blob/ff04760f4af91ac3e7add5e7424a46cb2439e874/src/main.browser.ts#L33) instead of the `` tag in `index.html` to ensure events get sent when the page is shown in a client's browser, and not when it's rendered on the universal server. Likely you'll want to do the same when adding a new service. -## SEO when hosting REST Api and UI on different servers - -Indexers such as Google Scholar require that files are hosted on the same domain as the page that links them. In DSpace 7, Bitstreams are served from the REST server. So if you use different servers for the REST api and the UI you'll want to ensure that Bitstream downloads are proxied through the UI server. - -In order to achieve this we'll need to do two things: -- **Proxy the Bitstream downloads through the UI server.** You'll need to put a webserver such as httpd or nginx in front of the UI server in order to achieve this. [Below](#apache-http-server-config) you'll find a section explaining how to do it in httpd. -- **Update the URLs for Bitstream downloads to match the UI server.** This can be done using a setting in the UI environment file. - -### UI config -If you set the property `rewriteDownloadUrls` to `true` in your `environment.prod.ts` file, the [origin](https://developer.mozilla.org/en-US/docs/Glossary/Origin) of any download URL will be replaced by the origin of the UI. This will also happen for the `citation_pdf_url` `` tag on Item pages. - -The app will determine the UI origin currently in use, so the external UI URL doesn't need to be configured anywhere and rewrites will still work if you host the UI from multiple domains. - -### Apache HTTP Server config - -#### Basics -In order to be able to host bitstreams from the UI Server you'll need to enable mod_proxy and add the following to the httpd config of your UI server: - -``` -ProxyPassMatch "/server/api/core/bitstreams/([^/]+)/content" "http://rest.api/server/api/core/bitstreams/$1/content" -ProxyPassReverse "/server/api/core/bitstreams/([^/]+)/content" "http://rest.api/server/api/core/bitstreams/$1/content" -``` - -Replace http://rest.api in with the correct origin for your REST server. - -The `ProxyPassMatch` line forwards all requests matching the regular expression for a bitstream download URL to the corresponding path on the REST server - -The `ProxyPassReverse` ensures that if the REST server were to return redirect response, httpd would also swap out its hostname for the hostname of the UI before forwarding the response to the client. - -#### Using HTTPS -If your REST server uses https, you'll need to enable mod_ssl and ensure `SSLProxyEngine on` is part of your UI server's httpd config as well - -If the UI hostname doesn't match the CN in the SSL certificate of the REST server (which is likely if they're on different domains), you'll also need to add the following lines - -``` -SSLProxyCheckPeerCN off -SSLProxyCheckPeerName off -``` -These are two names for [the same directive](https://httpd.apache.org/docs/trunk/mod/mod_ssl.html#sslproxycheckpeername) that have been used for various versions of httpd, old versions need the former, then some in-between versions need both, and newer versions only need the latter. Keeping them both doesn't harm anything. - -So the entire config becomes: - -``` -SSLProxyEngine on -SSLProxyCheckPeerCN off -SSLProxyCheckPeerName off -ProxyPassMatch "/server/api/core/bitstreams/([^/]+)/content" "https://rest.api/server/api/core/bitstreams/$1/content" -ProxyPassReverse "/server/api/core/bitstreams/([^/]+)/content" "https://rest.api/server/api/core/bitstreams/$1/content" -``` - -If you don't want httpd to verify the certificate of the REST server, you can also turn all checks off with the following config: - -``` -SSLProxyEngine on -SSLProxyVerify none -SSLProxyCheckPeerCN off -SSLProxyCheckPeerName off -SSLProxyCheckPeerExpire off -ProxyPassMatch "/server/api/core/bitstreams/([^/]+)/content" "https://rest.api/server/api/core/bitstreams/$1/content" -ProxyPassReverse "/server/api/core/bitstreams/([^/]+)/content" "https://rest.api/server/api/core/bitstreams/$1/content" -``` - - - - - diff --git a/src/app/+bitstream-page/bitstream-page-routing.module.ts b/src/app/+bitstream-page/bitstream-page-routing.module.ts index 14d688064c..bbbd65f279 100644 --- a/src/app/+bitstream-page/bitstream-page-routing.module.ts +++ b/src/app/+bitstream-page/bitstream-page-routing.module.ts @@ -3,6 +3,7 @@ import { RouterModule } from '@angular/router'; import { EditBitstreamPageComponent } from './edit-bitstream-page/edit-bitstream-page.component'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { BitstreamPageResolver } from './bitstream-page.resolver'; +import { BitstreamDownloadPageComponent } from '../shared/bitstream-download-page/bitstream-download-page.component'; const EDIT_BITSTREAM_PATH = ':id/edit'; @@ -12,6 +13,13 @@ const EDIT_BITSTREAM_PATH = ':id/edit'; @NgModule({ imports: [ RouterModule.forChild([ + { + path:':id/download', + component: BitstreamDownloadPageComponent, + resolve: { + bitstream: BitstreamPageResolver + }, + }, { path: EDIT_BITSTREAM_PATH, component: EditBitstreamPageComponent, diff --git a/src/app/+item-page/full/field-components/file-section/full-file-section.component.html b/src/app/+item-page/full/field-components/file-section/full-file-section.component.html index 00218b66d1..a054d58bf5 100644 --- a/src/app/+item-page/full/field-components/file-section/full-file-section.component.html +++ b/src/app/+item-page/full/field-components/file-section/full-file-section.component.html @@ -34,7 +34,7 @@
- + {{"item.page.filesection.download" | translate}}
@@ -76,7 +76,7 @@
- + {{"item.page.filesection.download" | translate}}
diff --git a/src/app/+item-page/simple/field-components/file-section/file-section.component.html b/src/app/+item-page/simple/field-components/file-section/file-section.component.html index 1fdee6dc4d..0fa5daa012 100644 --- a/src/app/+item-page/simple/field-components/file-section/file-section.component.html +++ b/src/app/+item-page/simple/field-components/file-section/file-section.component.html @@ -1,7 +1,7 @@
- + {{file?.name}} ({{(file?.sizeBytes) | dsFileSize }}) diff --git a/src/app/core/data/feature-authorization/feature-id.ts b/src/app/core/data/feature-authorization/feature-id.ts index e3473a895e..dbdd794665 100644 --- a/src/app/core/data/feature-authorization/feature-id.ts +++ b/src/app/core/data/feature-authorization/feature-id.ts @@ -12,4 +12,5 @@ export enum FeatureID { CanManageGroups = 'canManageGroups', IsCollectionAdmin = 'isCollectionAdmin', IsCommunityAdmin = 'isCommunityAdmin', + CanDownload = 'canDownload', } diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 25165aba79..043bc3cc05 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -181,11 +181,7 @@ describe('MetadataService', () => { Meta, Title, // tslint:disable-next-line:no-empty - { provide: ItemDataService, useValue: { findById: () => { } } }, - { - provide: HardRedirectService, - useValue: { rewriteDownloadURL: (a) => a, getRequestOrigin: () => environment.ui.baseUrl } - }, + { provide: ItemDataService, useValue: { findById: () => {} } }, BrowseService, MetadataService ], @@ -225,7 +221,7 @@ describe('MetadataService', () => { tick(); expect(tagStore.get('citation_dissertation_name')[0].content).toEqual('Test PowerPoint Document'); expect(tagStore.get('citation_dissertation_institution')[0].content).toEqual('Mock Publisher'); - expect(tagStore.get('citation_abstract_html_url')[0].content).toEqual(new URLCombiner(environment.ui.baseUrl, router.url).toString()); + expect(tagStore.get('citation_abstract_html_url')[0].content).toEqual([environment.ui.baseUrl, router.url].join('')); expect(tagStore.get('citation_pdf_url')[0].content).toEqual('https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28/content'); })); diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index dac0a787fb..eed4228683 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Inject, Injectable } from '@angular/core'; import { Meta, MetaDefinition, Title } from '@angular/platform-browser'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; @@ -20,8 +20,7 @@ import { Bitstream } from '../shared/bitstream.model'; import { DSpaceObject } from '../shared/dspace-object.model'; import { Item } from '../shared/item.model'; import { getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteListPayload } from '../shared/operators'; -import { HardRedirectService } from '../services/hard-redirect.service'; -import { URLCombiner } from '../url-combiner/url-combiner'; +import { environment } from '../../../environments/environment'; import { RootDataService } from '../data/root-data.service'; @Injectable() @@ -262,7 +261,7 @@ export class MetadataService { */ private setCitationAbstractUrlTag(): void { if (this.currentObject.value instanceof Item) { - const value = new URLCombiner(this.redirectService.getRequestOrigin(), this.router.url).toString(); + const value = [environment.ui.baseUrl, this.router.url].join(''); this.addMetaTag('citation_abstract_html_url', value); } } @@ -287,8 +286,7 @@ export class MetadataService { getFirstSucceededRemoteDataPayload() ).subscribe((format: BitstreamFormat) => { if (format.mimetype === 'application/pdf') { - const rewrittenURL= this.redirectService.rewriteDownloadURL(bitstream._links.content.href); - this.addMetaTag('citation_pdf_url', rewrittenURL); + this.addMetaTag('citation_pdf_url', bitstream._links.content.href); } }); } diff --git a/src/app/process-page/detail/process-detail.component.html b/src/app/process-page/detail/process-detail.component.html index d59f93254e..ef099d4f9f 100644 --- a/src/app/process-page/detail/process-detail.component.html +++ b/src/app/process-page/detail/process-detail.component.html @@ -15,7 +15,7 @@
- + {{getFileName(file)}} ({{(file?.sizeBytes) | dsFileSize }}) diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.html b/src/app/shared/bitstream-download-page/bitstream-download-page.component.html new file mode 100644 index 0000000000..183449fc5e --- /dev/null +++ b/src/app/shared/bitstream-download-page/bitstream-download-page.component.html @@ -0,0 +1,3 @@ +
+

{{'bitstream.download.page' | translate:{bitstream: (bitstream$ | async)?.name} }}

+
diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts b/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts new file mode 100644 index 0000000000..ca5a079faa --- /dev/null +++ b/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts @@ -0,0 +1,158 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { AuthService } from '../../core/auth/auth.service'; +import { FileService } from '../../core/shared/file.service'; +import { of as observableOf } from 'rxjs'; +import { Bitstream } from '../../core/shared/bitstream.model'; +import { BitstreamDownloadPageComponent } from './bitstream-download-page.component'; +import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; +import { HardRedirectService } from '../../core/services/hard-redirect.service'; +import { createSuccessfulRemoteDataObject } from '../remote-data.utils'; +import { ActivatedRoute, Router } from '@angular/router'; +import { getForbiddenRoute } from '../../app-routing-paths'; +import { TranslateModule } from '@ngx-translate/core'; +import { CommonModule } from '@angular/common'; + +describe('BitstreamDownloadPageComponent', () => { + let component: BitstreamDownloadPageComponent; + let fixture: ComponentFixture; + + let authService: AuthService; + let fileService: FileService; + let authorizationService: AuthorizationDataService; + let hardRedirectService: HardRedirectService; + let activatedRoute; + let router; + + let bitstream: Bitstream; + + function init() { + authService = jasmine.createSpyObj('authService', { + isAuthenticated: observableOf(true), + setRedirectUrl: {} + }); + authorizationService = jasmine.createSpyObj('authorizationSerivice', { + isAuthorized: observableOf(true) + }); + + fileService = jasmine.createSpyObj('fileService', { + downloadFile: observableOf('content-url-with-headers') + }); + + hardRedirectService = jasmine.createSpyObj('fileService', { + redirect: {} + }); + bitstream = Object.assign(new Bitstream(), { + uuid: 'bitstreamUuid', + _links: { + content: {href: 'bitstream-content-link'}, + self: {href: 'bitstream-self-link'}, + } + }); + + activatedRoute = { + data: observableOf({ + bitstream: createSuccessfulRemoteDataObject( + bitstream + ) + }) + }; + + router = jasmine.createSpyObj('router', ['navigateByUrl']); + } + + function initTestbed() { + TestBed.configureTestingModule({ + imports: [CommonModule, TranslateModule.forRoot()], + declarations: [BitstreamDownloadPageComponent], + providers: [ + {provide: ActivatedRoute, useValue: activatedRoute}, + {provide: Router, useValue: router}, + {provide: AuthorizationDataService, useValue: authorizationService}, + {provide: AuthService, useValue: authService}, + {provide: FileService, useValue: fileService}, + {provide: HardRedirectService, useValue: hardRedirectService}, + ] + }) + .compileComponents(); + } + + describe('init', () => { + beforeEach(async(() => { + init(); + initTestbed(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(BitstreamDownloadPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + it('should init the comp', () => { + expect(component).toBeTruthy(); + }); + }); + + describe('bitstream retrieval', () => { + describe('when the user is authorized and not logged in', () => { + beforeEach(async(() => { + init(); + (authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false)); + + initTestbed(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(BitstreamDownloadPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + it('should redirect to the content link', () => { + expect(hardRedirectService.redirect).toHaveBeenCalledWith('bitstream-content-link'); + }); + }); + describe('when the user is authorized and logged in', () => { + beforeEach(async(() => { + init(); + initTestbed(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(BitstreamDownloadPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + it('should redirect to an updated content link', () => { + expect(hardRedirectService.redirect).toHaveBeenCalledWith('content-url-with-headers'); + }); + }); + describe('when the user is not authorized and logged in', () => { + beforeEach(async(() => { + init(); + (authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false)); + initTestbed(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(BitstreamDownloadPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + it('should navigate to the forbidden route', () => { + expect(router.navigateByUrl).toHaveBeenCalledWith(getForbiddenRoute(), {skipLocationChange: true}); + }); + }); + describe('when the user is not authorized and not logged in', () => { + beforeEach(async(() => { + init(); + (authService.isAuthenticated as jasmine.Spy).and.returnValue(observableOf(false)); + (authorizationService.isAuthorized as jasmine.Spy).and.returnValue(observableOf(false)); + initTestbed(); + })); + beforeEach(() => { + fixture = TestBed.createComponent(BitstreamDownloadPageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + it('should navigate to the login page', () => { + expect(authService.setRedirectUrl).toHaveBeenCalled(); + expect(router.navigateByUrl).toHaveBeenCalledWith('login'); + }); + }); + }); +}); diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts b/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts new file mode 100644 index 0000000000..ccaec76751 --- /dev/null +++ b/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts @@ -0,0 +1,83 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { map } from 'rxjs/operators'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Subscription } from 'rxjs/internal/Subscription'; +import { hasValue, isNotEmpty } from '../empty.util'; +import { getRemoteDataPayload, redirectOn4xx } from '../../core/shared/operators'; +import { Bitstream } from '../../core/shared/bitstream.model'; +import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; +import { FeatureID } from '../../core/data/feature-authorization/feature-id'; +import { AuthService } from '../../core/auth/auth.service'; +import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; +import { FileService } from '../../core/shared/file.service'; +import { HardRedirectService } from '../../core/services/hard-redirect.service'; +import { getForbiddenRoute } from '../../app-routing-paths'; +import { RemoteData } from '../../core/data/remote-data'; +import { tap } from 'rxjs/internal/operators/tap'; + +@Component({ + selector: 'ds-bitstream-download-page', + templateUrl: './bitstream-download-page.component.html' +}) +/** + * Page component for downloading a bitstream + */ +export class BitstreamDownloadPageComponent implements OnInit, OnDestroy { + + bitstream$: Observable; + bitstreamRD$: Observable>; + + subs: Subscription[] = []; + + constructor( + private route: ActivatedRoute, + protected router: Router, + private authorizationService: AuthorizationDataService, + private auth: AuthService, + private fileService: FileService, + private hardRedirectService: HardRedirectService, + ) { + + } + + ngOnInit(): void { + + this.bitstreamRD$ = this.route.data.pipe( + map((data) => data.bitstream)); + + this.bitstream$ = this.bitstreamRD$.pipe( + tap((v) => console.log('dfdf', v)), + redirectOn4xx(this.router, this.auth), + getRemoteDataPayload() + ); + + this.subs.push(this.bitstream$.subscribe((bitstream) => { + const isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanDownload, isNotEmpty(bitstream) ? bitstream.self : undefined); + const isLoggedIn$ = this.auth.isAuthenticated(); + + this.subs.push(observableCombineLatest(isAuthorized$, isLoggedIn$) + .subscribe(([isAuthorized, isLoggedIn]) => { + if (isAuthorized && isLoggedIn) { + const fileLink$ = this.fileService.downloadFile(bitstream._links.content.href); + this.subs.push(fileLink$.subscribe((fileLink) => { + this.hardRedirectService.redirect(fileLink); + })); + } else if (isAuthorized && !isLoggedIn) { + this.hardRedirectService.redirect(bitstream._links.content.href); + } else if (!isAuthorized && isLoggedIn) { + this.router.navigateByUrl(getForbiddenRoute(), {skipLocationChange: true}); + } else if (!isAuthorized && !isLoggedIn) { + this.auth.setRedirectUrl(this.router.url); + this.router.navigateByUrl('login'); + } + })); + })); + + } + + ngOnDestroy(): void { + this.subs + .filter((subscription) => hasValue(subscription)) + .forEach((subscription) => subscription.unsubscribe()); + } +} diff --git a/src/app/shared/file-download-link/file-download-link.component.html b/src/app/shared/file-download-link/file-download-link.component.html index 06624c8b40..f1843da5c6 100644 --- a/src/app/shared/file-download-link/file-download-link.component.html +++ b/src/app/shared/file-download-link/file-download-link.component.html @@ -1,5 +1,5 @@ - - + + diff --git a/src/app/shared/file-download-link/file-download-link.component.spec.ts b/src/app/shared/file-download-link/file-download-link.component.spec.ts index 4f3c17402f..53f39b104e 100644 --- a/src/app/shared/file-download-link/file-download-link.component.spec.ts +++ b/src/app/shared/file-download-link/file-download-link.component.spec.ts @@ -3,7 +3,10 @@ import { FileDownloadLinkComponent } from './file-download-link.component'; import { AuthService } from '../../core/auth/auth.service'; import { FileService } from '../../core/shared/file.service'; import { of as observableOf } from 'rxjs'; -import { HardRedirectService } from '../../core/services/hard-redirect.service'; +import { Bitstream } from '../../core/shared/bitstream.model'; +import { By } from '@angular/platform-browser'; +import { URLCombiner } from '../../core/url-combiner/url-combiner'; +import { getBitstreamModuleRoute } from '../../app-routing-paths'; describe('FileDownloadLinkComponent', () => { let component: FileDownloadLinkComponent; @@ -11,14 +14,16 @@ describe('FileDownloadLinkComponent', () => { let authService: AuthService; let fileService: FileService; - let href: string; + let bitstream: Bitstream; function init() { authService = jasmine.createSpyObj('authService', { isAuthenticated: observableOf(true) }); fileService = jasmine.createSpyObj('fileService', ['downloadFile']); - href = 'test-download-file-link'; + bitstream = Object.assign(new Bitstream(), { + uuid: 'bitstreamUuid', + }); } beforeEach(waitForAsync(() => { @@ -28,7 +33,6 @@ describe('FileDownloadLinkComponent', () => { providers: [ { provide: AuthService, useValue: authService }, { provide: FileService, useValue: fileService }, - { provide: HardRedirectService, useValue: { rewriteDownloadURL: (a) => a } }, ] }) .compileComponents(); @@ -37,23 +41,22 @@ describe('FileDownloadLinkComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(FileDownloadLinkComponent); component = fixture.componentInstance; - component.href = href; + component.bitstream = bitstream; fixture.detectChanges(); }); - describe('downloadFile', () => { - let result; + describe('init', () => { - beforeEach(() => { - result = component.downloadFile(); - }); + describe('getBitstreamPath', () => { + it('should set the bitstreamPath based on the input bitstream', () => { + expect(component.bitstreamPath).toEqual(new URLCombiner(getBitstreamModuleRoute(), bitstream.uuid, 'download').toString()); + }); + }) - it('should call fileService.downloadFile with the provided href', () => { - expect(fileService.downloadFile).toHaveBeenCalledWith(href); - }); + it('should init the component', () => { + const link = fixture.debugElement.query(By.css('a')).nativeElement; + expect(link.href).toContain(new URLCombiner(getBitstreamModuleRoute(), bitstream.uuid, 'download').toString()); + }) - it('should return false', () => { - expect(result).toEqual(false); - }); }); }); diff --git a/src/app/shared/file-download-link/file-download-link.component.ts b/src/app/shared/file-download-link/file-download-link.component.ts index f9861cd997..8a1fbd6e0e 100644 --- a/src/app/shared/file-download-link/file-download-link.component.ts +++ b/src/app/shared/file-download-link/file-download-link.component.ts @@ -2,7 +2,9 @@ import { Component, Input, OnInit } from '@angular/core'; import { FileService } from '../../core/shared/file.service'; import { Observable } from 'rxjs'; import { AuthService } from '../../core/auth/auth.service'; -import { HardRedirectService } from '../../core/services/hard-redirect.service'; +import { Bitstream } from '../../core/shared/bitstream.model'; +import { getBitstreamModuleRoute } from '../../app-routing-paths'; +import { URLCombiner } from '../../core/url-combiner/url-combiner'; @Component({ selector: 'ds-file-download-link', @@ -15,15 +17,12 @@ import { HardRedirectService } from '../../core/services/hard-redirect.service'; * ensuring the user is authorized to download the file. */ export class FileDownloadLinkComponent implements OnInit { - /** - * Href to link to - */ - @Input() href: string; /** - * Optional file name for the download + * Optional bitstream instead of href and file name */ - @Input() download: string; + @Input() bitstream: Bitstream; + bitstreamPath: string; /** * Whether or not the current user is authenticated @@ -32,20 +31,15 @@ export class FileDownloadLinkComponent implements OnInit { constructor(private fileService: FileService, private authService: AuthService, - private redirectService: HardRedirectService) { + ) { } ngOnInit() { this.isAuthenticated$ = this.authService.isAuthenticated(); - this.href = this.redirectService.rewriteDownloadURL(this.href); + this.bitstreamPath = this.getBitstreamPath(); } - /** - * Start a download of the file - * Return false to ensure the original href is displayed when the user hovers over the link - */ - downloadFile(): boolean { - this.fileService.downloadFile(this.href); - return false; + getBitstreamPath() { + return new URLCombiner(getBitstreamModuleRoute(), this.bitstream.uuid, 'download').toString(); } } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index e9f9d69918..4de0f2901e 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -223,6 +223,7 @@ import { SearchObjects } from './search/search-objects.model'; import { SearchResult } from './search/search-result.model'; import { FacetConfigResponse } from './search/facet-config-response.model'; import { FacetValues } from './search/facet-values.model'; +import { BitstreamDownloadPageComponent } from './bitstream-download-page/bitstream-download-page.component'; import { GenericItemPageFieldComponent } from '../+item-page/simple/field-components/specific-field/generic/generic-item-page-field.component'; import { MetadataRepresentationListComponent } from '../+item-page/simple/metadata-representation-list/metadata-representation-list.component'; import { RelatedItemsComponent } from '../+item-page/simple/related-items/related-items-component'; @@ -432,6 +433,7 @@ const COMPONENTS = [ EpersonSearchBoxComponent, GroupSearchBoxComponent, FileDownloadLinkComponent, + BitstreamDownloadPageComponent, CollectionDropdownComponent, ExportMetadataSelectorComponent, ConfirmationModalComponent, @@ -510,6 +512,14 @@ const ENTRY_COMPONENTS = [ ClaimedTaskActionsRejectComponent, ClaimedTaskActionsReturnToPoolComponent, ClaimedTaskActionsEditMetadataComponent, + CollectionDropdownComponent, + FileDownloadLinkComponent, + BitstreamDownloadPageComponent, + CurationFormComponent, + ExportMetadataSelectorComponent, + ConfirmationModalComponent, + VocabularyTreeviewComponent, + SidebarSearchListElementComponent, PublicationSidebarSearchListElementComponent, CollectionSidebarSearchListElementComponent, CommunitySidebarSearchListElementComponent, diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 2924a3a47e..1b9e252922 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -526,6 +526,10 @@ + "bitstream.download.page": "Now downloading {{bitstream}}..." , + + + "bitstream.edit.bitstream": "Bitstream: ", "bitstream.edit.form.description.hint": "Optionally, provide a brief description of the file, for example \"Main article\" or \"Experiment data readings\".", From 3137f3e6c11001b39acca9d0531718ca6b7fd257 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 29 Jan 2021 11:05:13 +0100 Subject: [PATCH 07/31] 76150: Implement feedback --- src/app/core/shared/file.service.ts | 14 ++-- .../bitstream-download-page.component.spec.ts | 2 +- .../bitstream-download-page.component.ts | 71 ++++++++++--------- .../item-detail-preview.component.ts | 2 +- .../section-upload-file.component.spec.ts | 4 +- .../file/section-upload-file.component.ts | 2 +- 6 files changed, 48 insertions(+), 47 deletions(-) diff --git a/src/app/core/shared/file.service.ts b/src/app/core/shared/file.service.ts index fcbd86161a..98c468e9a3 100644 --- a/src/app/core/shared/file.service.ts +++ b/src/app/core/shared/file.service.ts @@ -1,10 +1,11 @@ import { Inject, Injectable } from '@angular/core'; import { RawRestResponse } from '../dspace-rest/raw-rest-response.model'; import { AuthService } from '../auth/auth.service'; -import { take } from 'rxjs/operators'; +import { map, take } from 'rxjs/operators'; import { NativeWindowRef, NativeWindowService } from '../services/window.service'; import { URLCombiner } from '../url-combiner/url-combiner'; import { hasValue } from '../../shared/empty.util'; +import { Observable } from 'rxjs/internal/Observable'; /** * Provides utility methods to save files on the client-side. @@ -17,17 +18,16 @@ export class FileService { ) { } /** - * Combines an URL with a short-lived token and sets the current URL to the newly created one + * Combines an URL with a short-lived token and sets the current URL to the newly created one and returns it * * @param url * file url */ - downloadFile(url: string) { - this.authService.getShortlivedToken().pipe(take(1)).subscribe((token) => { - this._window.nativeWindow.location.href = hasValue(token) ? new URLCombiner(url, `?authentication-token=${token}`).toString() : url; - }); + retrieveFileDownloadLink(url: string): Observable { + return this.authService.getShortlivedToken().pipe(take(1), map((token) => + hasValue(token) ? new URLCombiner(url, `?authentication-token=${token}`).toString() : url + )); } - /** * Derives file name from the http response * by looking inside content-disposition diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts b/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts index ca5a079faa..2803a7f789 100644 --- a/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts +++ b/src/app/shared/bitstream-download-page/bitstream-download-page.component.spec.ts @@ -35,7 +35,7 @@ describe('BitstreamDownloadPageComponent', () => { }); fileService = jasmine.createSpyObj('fileService', { - downloadFile: observableOf('content-url-with-headers') + retrieveFileDownloadLink: observableOf('content-url-with-headers') }); hardRedirectService = jasmine.createSpyObj('fileService', { diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts b/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts index ccaec76751..5b82928a64 100644 --- a/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts +++ b/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts @@ -1,19 +1,17 @@ -import { Component, OnDestroy, OnInit } from '@angular/core'; -import { map } from 'rxjs/operators'; +import { Component, OnInit } from '@angular/core'; +import { filter, map, switchMap, take } from 'rxjs/operators'; import { ActivatedRoute, Router } from '@angular/router'; -import { Subscription } from 'rxjs/internal/Subscription'; import { hasValue, isNotEmpty } from '../empty.util'; import { getRemoteDataPayload, redirectOn4xx } from '../../core/shared/operators'; import { Bitstream } from '../../core/shared/bitstream.model'; import { AuthorizationDataService } from '../../core/data/feature-authorization/authorization-data.service'; import { FeatureID } from '../../core/data/feature-authorization/feature-id'; import { AuthService } from '../../core/auth/auth.service'; -import { combineLatest as observableCombineLatest, Observable } from 'rxjs'; +import { combineLatest as observableCombineLatest, Observable, of as observableOf } from 'rxjs'; import { FileService } from '../../core/shared/file.service'; import { HardRedirectService } from '../../core/services/hard-redirect.service'; import { getForbiddenRoute } from '../../app-routing-paths'; import { RemoteData } from '../../core/data/remote-data'; -import { tap } from 'rxjs/internal/operators/tap'; @Component({ selector: 'ds-bitstream-download-page', @@ -22,12 +20,11 @@ import { tap } from 'rxjs/internal/operators/tap'; /** * Page component for downloading a bitstream */ -export class BitstreamDownloadPageComponent implements OnInit, OnDestroy { +export class BitstreamDownloadPageComponent implements OnInit { bitstream$: Observable; bitstreamRD$: Observable>; - subs: Subscription[] = []; constructor( private route: ActivatedRoute, @@ -46,38 +43,42 @@ export class BitstreamDownloadPageComponent implements OnInit, OnDestroy { map((data) => data.bitstream)); this.bitstream$ = this.bitstreamRD$.pipe( - tap((v) => console.log('dfdf', v)), redirectOn4xx(this.router, this.auth), getRemoteDataPayload() ); - this.subs.push(this.bitstream$.subscribe((bitstream) => { - const isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanDownload, isNotEmpty(bitstream) ? bitstream.self : undefined); - const isLoggedIn$ = this.auth.isAuthenticated(); - - this.subs.push(observableCombineLatest(isAuthorized$, isLoggedIn$) - .subscribe(([isAuthorized, isLoggedIn]) => { - if (isAuthorized && isLoggedIn) { - const fileLink$ = this.fileService.downloadFile(bitstream._links.content.href); - this.subs.push(fileLink$.subscribe((fileLink) => { - this.hardRedirectService.redirect(fileLink); + this.bitstream$.pipe( + switchMap((bitstream: Bitstream) => { + const isAuthorized$ = this.authorizationService.isAuthorized(FeatureID.CanDownload, isNotEmpty(bitstream) ? bitstream.self : undefined); + const isLoggedIn$ = this.auth.isAuthenticated(); + return observableCombineLatest([isAuthorized$, isLoggedIn$, observableOf(bitstream)]); + }), + filter(([isAuthorized, isLoggedIn, bitstream]: [boolean, boolean, Bitstream]) => hasValue(isAuthorized) && hasValue(isLoggedIn)), + take(1), + switchMap(([isAuthorized, isLoggedIn, bitstream]: [boolean, boolean, Bitstream]) => { + if (isAuthorized && isLoggedIn) { + return this.fileService.retrieveFileDownloadLink(bitstream._links.content.href).pipe( + filter((fileLink) => hasValue(fileLink)), + take(1), + map((fileLink) => { + return [isAuthorized, isLoggedIn, bitstream, fileLink]; + // return [isAuthorized, isLoggedIn, bitstream]; })); - } else if (isAuthorized && !isLoggedIn) { - this.hardRedirectService.redirect(bitstream._links.content.href); - } else if (!isAuthorized && isLoggedIn) { - this.router.navigateByUrl(getForbiddenRoute(), {skipLocationChange: true}); - } else if (!isAuthorized && !isLoggedIn) { - this.auth.setRedirectUrl(this.router.url); - this.router.navigateByUrl('login'); - } - })); - })); - - } - - ngOnDestroy(): void { - this.subs - .filter((subscription) => hasValue(subscription)) - .forEach((subscription) => subscription.unsubscribe()); + } else { + return [[isAuthorized, isLoggedIn, bitstream, '']]; + } + }) + ).subscribe(([isAuthorized, isLoggedIn, bitstream, fileLink]: [boolean, boolean, Bitstream, string]) => { + if (isAuthorized && isLoggedIn && isNotEmpty(fileLink)) { + this.hardRedirectService.redirect(fileLink); + } else if (isAuthorized && !isLoggedIn) { + this.hardRedirectService.redirect(bitstream._links.content.href); + } else if (!isAuthorized && isLoggedIn) { + this.router.navigateByUrl(getForbiddenRoute(), {skipLocationChange: true}); + } else if (!isAuthorized && !isLoggedIn) { + this.auth.setRedirectUrl(this.router.url); + this.router.navigateByUrl('login'); + } + }); } } diff --git a/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.ts b/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.ts index b7e67ff2ec..a4dc0a1d3d 100644 --- a/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.ts +++ b/src/app/shared/object-detail/my-dspace-result-detail-element/item-detail-preview/item-detail-preview.component.ts @@ -82,7 +82,7 @@ export class ItemDetailPreviewComponent { first()) .subscribe((url) => { const fileUrl = `${url}/${uuid}/content`; - this.fileService.downloadFile(fileUrl); + this.fileService.retrieveFileDownloadLink(fileUrl); }); } diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts index 730a456faa..ac91e5eb3c 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts +++ b/src/app/submission/sections/upload/file/section-upload-file.component.spec.ts @@ -41,7 +41,7 @@ import { FormBuilderService } from '../../../../shared/form/builder/form-builder function getMockFileService(): FileService { return jasmine.createSpyObj('FileService', { - downloadFile: jasmine.createSpy('downloadFile'), + retrieveFileDownloadLink: jasmine.createSpy('retrieveFileDownloadLink'), getFileNameFromResponseContentDisposition: jasmine.createSpy('getFileNameFromResponseContentDisposition') }); } @@ -232,7 +232,7 @@ describe('SubmissionSectionUploadFileComponent test suite', () => { tick(); - expect(fileService.downloadFile).toHaveBeenCalled(); + expect(fileService.retrieveFileDownloadLink).toHaveBeenCalled(); })); it('should save Bitstream File data properly when form is valid', fakeAsync(() => { diff --git a/src/app/submission/sections/upload/file/section-upload-file.component.ts b/src/app/submission/sections/upload/file/section-upload-file.component.ts index 80945bc1fd..5a97140a70 100644 --- a/src/app/submission/sections/upload/file/section-upload-file.component.ts +++ b/src/app/submission/sections/upload/file/section-upload-file.component.ts @@ -224,7 +224,7 @@ export class SubmissionSectionUploadFileComponent implements OnChanges, OnInit { first()) .subscribe((url) => { const fileUrl = `${url}/${this.fileData.uuid}/content`; - this.fileService.downloadFile(fileUrl); + this.fileService.retrieveFileDownloadLink(fileUrl); }); } From a1abface1335762f4a23319837162595155402f2 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 29 Jan 2021 11:32:56 +0100 Subject: [PATCH 08/31] Fix lint issues --- .../file-download-link/file-download-link.component.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/shared/file-download-link/file-download-link.component.spec.ts b/src/app/shared/file-download-link/file-download-link.component.spec.ts index 53f39b104e..6f7f50e585 100644 --- a/src/app/shared/file-download-link/file-download-link.component.spec.ts +++ b/src/app/shared/file-download-link/file-download-link.component.spec.ts @@ -51,12 +51,12 @@ describe('FileDownloadLinkComponent', () => { it('should set the bitstreamPath based on the input bitstream', () => { expect(component.bitstreamPath).toEqual(new URLCombiner(getBitstreamModuleRoute(), bitstream.uuid, 'download').toString()); }); - }) + }); it('should init the component', () => { const link = fixture.debugElement.query(By.css('a')).nativeElement; expect(link.href).toContain(new URLCombiner(getBitstreamModuleRoute(), bitstream.uuid, 'download').toString()); - }) + }); }); }); From 69c29407a29b7870edfd6db4cb6221a0227321ef Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 29 Jan 2021 14:37:14 +0100 Subject: [PATCH 09/31] Remove comment --- .../bitstream-download-page/bitstream-download-page.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts b/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts index 5b82928a64..a09d7e8b3e 100644 --- a/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts +++ b/src/app/shared/bitstream-download-page/bitstream-download-page.component.ts @@ -62,7 +62,6 @@ export class BitstreamDownloadPageComponent implements OnInit { take(1), map((fileLink) => { return [isAuthorized, isLoggedIn, bitstream, fileLink]; - // return [isAuthorized, isLoggedIn, bitstream]; })); } else { return [[isAuthorized, isLoggedIn, bitstream, '']]; From 99a96451b121ab6ff500ff7fc70976e83e7dc3b3 Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 12 Feb 2021 10:01:08 +0100 Subject: [PATCH 10/31] Update metadataservice citation tag to refer to new download page --- src/app/app-routing-paths.ts | 5 +++++ .../core/metadata/metadata.service.spec.ts | 2 +- src/app/core/metadata/metadata.service.ts | 6 ++++-- .../file-download-link.component.ts | 19 ++----------------- 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/app/app-routing-paths.ts b/src/app/app-routing-paths.ts index 08f7b9585f..ecd9dc51d4 100644 --- a/src/app/app-routing-paths.ts +++ b/src/app/app-routing-paths.ts @@ -6,6 +6,7 @@ import { getCommunityPageRoute } from './+community-page/community-page-routing- import { getCollectionPageRoute } from './+collection-page/collection-page-routing-paths'; import { getItemPageRoute } from './+item-page/item-page-routing-paths'; import { hasValue } from './shared/empty.util'; +import { URLCombiner } from './core/url-combiner/url-combiner'; export const BITSTREAM_MODULE_PATH = 'bitstreams'; @@ -13,6 +14,10 @@ export function getBitstreamModuleRoute() { return `/${BITSTREAM_MODULE_PATH}`; } +export function getBitstreamDownloadRoute(bitstream) : string{ + return new URLCombiner(getBitstreamModuleRoute(), bitstream.uuid, 'download').toString(); +} + export const ADMIN_MODULE_PATH = 'admin'; export function getAdminModuleRoute() { diff --git a/src/app/core/metadata/metadata.service.spec.ts b/src/app/core/metadata/metadata.service.spec.ts index 043bc3cc05..18421dd489 100644 --- a/src/app/core/metadata/metadata.service.spec.ts +++ b/src/app/core/metadata/metadata.service.spec.ts @@ -222,7 +222,7 @@ describe('MetadataService', () => { expect(tagStore.get('citation_dissertation_name')[0].content).toEqual('Test PowerPoint Document'); expect(tagStore.get('citation_dissertation_institution')[0].content).toEqual('Mock Publisher'); expect(tagStore.get('citation_abstract_html_url')[0].content).toEqual([environment.ui.baseUrl, router.url].join('')); - expect(tagStore.get('citation_pdf_url')[0].content).toEqual('https://dspace7.4science.it/dspace-spring-rest/api/core/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28/content'); + expect(tagStore.get('citation_pdf_url')[0].content).toEqual('/bitstreams/99b00f3c-1cc6-4689-8158-91965bee6b28/download'); })); it('items page should set meta tags as published Technical Report', fakeAsync(() => { diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index eed4228683..5bbb90ac80 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -22,6 +22,8 @@ import { Item } from '../shared/item.model'; import { getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteListPayload } from '../shared/operators'; import { environment } from '../../../environments/environment'; import { RootDataService } from '../data/root-data.service'; +import { URLCombiner } from '../url-combiner/url-combiner'; +import { getBitstreamDownloadRoute, getBitstreamModuleRoute } from '../../app-routing-paths'; @Injectable() export class MetadataService { @@ -40,7 +42,6 @@ export class MetadataService { private dsoNameService: DSONameService, private bitstreamDataService: BitstreamDataService, private bitstreamFormatDataService: BitstreamFormatDataService, - private redirectService: HardRedirectService, private rootService: RootDataService ) { // TODO: determine what open graph meta tags are needed and whether @@ -286,7 +287,8 @@ export class MetadataService { getFirstSucceededRemoteDataPayload() ).subscribe((format: BitstreamFormat) => { if (format.mimetype === 'application/pdf') { - this.addMetaTag('citation_pdf_url', bitstream._links.content.href); + const bitstreamLink = getBitstreamDownloadRoute(bitstream); + this.addMetaTag('citation_pdf_url', bitstreamLink); } }); } diff --git a/src/app/shared/file-download-link/file-download-link.component.ts b/src/app/shared/file-download-link/file-download-link.component.ts index 8a1fbd6e0e..4423b6f5b7 100644 --- a/src/app/shared/file-download-link/file-download-link.component.ts +++ b/src/app/shared/file-download-link/file-download-link.component.ts @@ -1,10 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; -import { FileService } from '../../core/shared/file.service'; -import { Observable } from 'rxjs'; -import { AuthService } from '../../core/auth/auth.service'; import { Bitstream } from '../../core/shared/bitstream.model'; -import { getBitstreamModuleRoute } from '../../app-routing-paths'; -import { URLCombiner } from '../../core/url-combiner/url-combiner'; +import { getBitstreamDownloadRoute } from '../../app-routing-paths'; @Component({ selector: 'ds-file-download-link', @@ -24,22 +20,11 @@ export class FileDownloadLinkComponent implements OnInit { @Input() bitstream: Bitstream; bitstreamPath: string; - /** - * Whether or not the current user is authenticated - */ - isAuthenticated$: Observable; - - constructor(private fileService: FileService, - private authService: AuthService, - ) { - } - ngOnInit() { - this.isAuthenticated$ = this.authService.isAuthenticated(); this.bitstreamPath = this.getBitstreamPath(); } getBitstreamPath() { - return new URLCombiner(getBitstreamModuleRoute(), this.bitstream.uuid, 'download').toString(); + return getBitstreamDownloadRoute(this.bitstream); } } From 72ca74bdf343baebdf20099f4b5c1f8e85069a8c Mon Sep 17 00:00:00 2001 From: Yana De Pauw Date: Fri, 12 Feb 2021 10:31:17 +0100 Subject: [PATCH 11/31] Fix checkstyle issue --- src/app/app-routing-paths.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/app-routing-paths.ts b/src/app/app-routing-paths.ts index ecd9dc51d4..7dfdbd2c49 100644 --- a/src/app/app-routing-paths.ts +++ b/src/app/app-routing-paths.ts @@ -14,7 +14,7 @@ export function getBitstreamModuleRoute() { return `/${BITSTREAM_MODULE_PATH}`; } -export function getBitstreamDownloadRoute(bitstream) : string{ +export function getBitstreamDownloadRoute(bitstream): string { return new URLCombiner(getBitstreamModuleRoute(), bitstream.uuid, 'download').toString(); } From ff4bd59de0d280121420d5fd4721891ac30f7e6f Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 6 Apr 2021 16:45:06 +0200 Subject: [PATCH 12/31] use GET for shortlivedtoken requests on the server, POST on the client --- .../core/auth/auth-request.service.spec.ts | 74 +++++++++++++++++++ src/app/core/auth/auth-request.service.ts | 31 +++++--- .../auth/browser-auth-request.service.spec.ts | 29 ++++++++ .../core/auth/browser-auth-request.service.ts | 34 +++++++++ .../auth/server-auth-request.service.spec.ts | 34 +++++++++ .../core/auth/server-auth-request.service.ts | 36 +++++++++ src/app/core/core.module.ts | 2 - src/modules/app/browser-app.module.ts | 6 ++ src/modules/app/server-app.module.ts | 6 ++ 9 files changed, 238 insertions(+), 14 deletions(-) create mode 100644 src/app/core/auth/auth-request.service.spec.ts create mode 100644 src/app/core/auth/browser-auth-request.service.spec.ts create mode 100644 src/app/core/auth/browser-auth-request.service.ts create mode 100644 src/app/core/auth/server-auth-request.service.spec.ts create mode 100644 src/app/core/auth/server-auth-request.service.ts diff --git a/src/app/core/auth/auth-request.service.spec.ts b/src/app/core/auth/auth-request.service.spec.ts new file mode 100644 index 0000000000..707daf9e30 --- /dev/null +++ b/src/app/core/auth/auth-request.service.spec.ts @@ -0,0 +1,74 @@ +import { AuthRequestService } from './auth-request.service'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { RequestService } from '../data/request.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; +import { PostRequest } from '../data/request.models'; +import { TestScheduler } from 'rxjs/testing'; +import { createSuccessfulRemoteDataObject } from '../../shared/remote-data.utils'; +import { ShortLivedToken } from './models/short-lived-token.model'; +import { RemoteData } from '../data/remote-data'; + +describe(`AuthRequestService`, () => { + let halService: HALEndpointService; + let endpointURL: string; + let shortLivedToken: ShortLivedToken; + let shortLivedTokenRD: RemoteData; + let requestService: RequestService; + let rdbService: RemoteDataBuildService; + let service: AuthRequestService; + let testScheduler; + + class TestAuthRequestService extends AuthRequestService { + constructor( + hes: HALEndpointService, + rs: RequestService, + rdbs: RemoteDataBuildService + ) { + super(hes, rs, rdbs); + } + + protected createShortLivedTokenRequest(href: string): PostRequest { + return new PostRequest(this.requestService.generateRequestId(), href); + } + } + + const init = (cold: typeof TestScheduler.prototype.createColdObservable) => { + endpointURL = 'https://rest.api/auth'; + shortLivedToken = Object.assign(new ShortLivedToken(), { + value: 'some-token' + }); + shortLivedTokenRD = createSuccessfulRemoteDataObject(shortLivedToken); + + halService = jasmine.createSpyObj('halService', { + 'getEndpoint': cold('a', { a: endpointURL }) + }); + requestService = jasmine.createSpyObj('requestService', { + 'send': null + }); + rdbService = jasmine.createSpyObj('rdbService', { + 'buildFromRequestUUID': cold('a', { a: shortLivedTokenRD }) + }); + + service = new TestAuthRequestService(halService, requestService, rdbService); + }; + + beforeEach(() => { + testScheduler = new TestScheduler((actual, expected) => { + expect(actual).toEqual(expected); + }); + }); + + describe(`getShortlivedToken`, () => { + it(`should call createShortLivedTokenRequest with the url for the endpoint`, () => { + testScheduler.run(({ cold, expectObservable, flush }) => { + init(cold); + spyOn(service as any, 'createShortLivedTokenRequest'); + // expectObservable is needed to let testScheduler know to take it in to account, but since + // we're not testing the outcome in this test, a .toBe(…) isn't necessary + expectObservable(service.getShortlivedToken()); + flush(); + expect((service as any).createShortLivedTokenRequest).toHaveBeenCalledWith(`${endpointURL}/shortlivedtokens`); + }); + }); + }); +}); diff --git a/src/app/core/auth/auth-request.service.ts b/src/app/core/auth/auth-request.service.ts index 4315ddfea8..00a94822d3 100644 --- a/src/app/core/auth/auth-request.service.ts +++ b/src/app/core/auth/auth-request.service.ts @@ -1,14 +1,9 @@ import { Observable } from 'rxjs'; import { distinctUntilChanged, filter, map, mergeMap, switchMap, tap } from 'rxjs/operators'; -import { Injectable } from '@angular/core'; import { HALEndpointService } from '../shared/hal-endpoint.service'; import { RequestService } from '../data/request.service'; import { isNotEmpty } from '../../shared/empty.util'; -import { - GetRequest, - PostRequest, - RestRequest, -} from '../data/request.models'; +import { GetRequest, PostRequest, RestRequest, } from '../data/request.models'; import { HttpOptions } from '../dspace-rest/dspace-rest.service'; import { getFirstCompletedRemoteData } from '../shared/operators'; import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; @@ -17,8 +12,10 @@ import { AuthStatus } from './models/auth-status.model'; import { ShortLivedToken } from './models/short-lived-token.model'; import { URLCombiner } from '../url-combiner/url-combiner'; -@Injectable() -export class AuthRequestService { +/** + * Abstract service to send authentication requests + */ +export abstract class AuthRequestService { protected linkName = 'authn'; protected browseEndpoint = ''; protected shortlivedtokensEndpoint = 'shortlivedtokens'; @@ -62,16 +59,26 @@ export class AuthRequestService { } /** - * Send a POST request to retrieve a short-lived token which provides download access of restricted files + * Factory function to create the request object to send. This needs to be a POST client side and + * a GET server side. Due to CSRF validation, the server isn't allowed to send a POST, so we allow + * only the server IP to send a GET to this endpoint. + * + * @param href The href to send the request to + * @protected + */ + protected abstract createShortLivedTokenRequest(href: string): GetRequest | PostRequest; + + /** + * Send a request to retrieve a short-lived token which provides download access of restricted files */ public getShortlivedToken(): Observable { return this.halService.getEndpoint(this.linkName).pipe( filter((href: string) => isNotEmpty(href)), distinctUntilChanged(), map((href: string) => new URLCombiner(href, this.shortlivedtokensEndpoint).toString()), - map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL)), - tap((request: PostRequest) => this.requestService.send(request)), - switchMap((request: PostRequest) => this.rdbService.buildFromRequestUUID(request.uuid)), + map((endpointURL: string) => this.createShortLivedTokenRequest(endpointURL)), + tap((request: RestRequest) => this.requestService.send(request)), + switchMap((request: RestRequest) => this.rdbService.buildFromRequestUUID(request.uuid)), getFirstCompletedRemoteData(), map((response: RemoteData) => { if (response.hasSucceeded) { diff --git a/src/app/core/auth/browser-auth-request.service.spec.ts b/src/app/core/auth/browser-auth-request.service.spec.ts new file mode 100644 index 0000000000..18d27340af --- /dev/null +++ b/src/app/core/auth/browser-auth-request.service.spec.ts @@ -0,0 +1,29 @@ +import { AuthRequestService } from './auth-request.service'; +import { RequestService } from '../data/request.service'; +import { BrowserAuthRequestService } from './browser-auth-request.service'; + +describe(`BrowserAuthRequestService`, () => { + let href: string; + let requestService: RequestService; + let service: AuthRequestService; + + beforeEach(() => { + href = 'https://rest.api/auth/shortlivedtokens'; + requestService = jasmine.createSpyObj('requestService', { + 'generateRequestId': '8bb0582d-5013-4337-af9c-763beb25aae2' + }); + service = new BrowserAuthRequestService(null, requestService, null); + }); + + describe(`createShortLivedTokenRequest`, () => { + it(`should return a PostRequest`, () => { + const result = (service as any).createShortLivedTokenRequest(href); + expect(result.constructor.name).toBe('PostRequest'); + }); + + it(`should return a request with the given href`, () => { + const result = (service as any).createShortLivedTokenRequest(href); + expect(result.href).toBe(href) ; + }); + }); +}); diff --git a/src/app/core/auth/browser-auth-request.service.ts b/src/app/core/auth/browser-auth-request.service.ts new file mode 100644 index 0000000000..85d5f54340 --- /dev/null +++ b/src/app/core/auth/browser-auth-request.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { AuthRequestService } from './auth-request.service'; +import { PostRequest } from '../data/request.models'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { RequestService } from '../data/request.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; + +/** + * Client side version of the service to send authentication requests + */ +@Injectable() +export class BrowserAuthRequestService extends AuthRequestService { + + constructor( + halService: HALEndpointService, + requestService: RequestService, + rdbService: RemoteDataBuildService + ) { + super(halService, requestService, rdbService); + } + + /** + * Factory function to create the request object to send. This needs to be a POST client side and + * a GET server side. Due to CSRF validation, the server isn't allowed to send a POST, so we allow + * only the server IP to send a GET to this endpoint. + * + * @param href The href to send the request to + * @protected + */ + protected createShortLivedTokenRequest(href: string): PostRequest { + return new PostRequest(this.requestService.generateRequestId(), href); + } + +} diff --git a/src/app/core/auth/server-auth-request.service.spec.ts b/src/app/core/auth/server-auth-request.service.spec.ts new file mode 100644 index 0000000000..69053fbb3a --- /dev/null +++ b/src/app/core/auth/server-auth-request.service.spec.ts @@ -0,0 +1,34 @@ +import { AuthRequestService } from './auth-request.service'; +import { RequestService } from '../data/request.service'; +import { ServerAuthRequestService } from './server-auth-request.service'; + +describe(`ServerAuthRequestService`, () => { + let href: string; + let requestService: RequestService; + let service: AuthRequestService; + + beforeEach(() => { + href = 'https://rest.api/auth/shortlivedtokens'; + requestService = jasmine.createSpyObj('requestService', { + 'generateRequestId': '8bb0582d-5013-4337-af9c-763beb25aae2' + }); + service = new ServerAuthRequestService(null, requestService, null); + }); + + describe(`createShortLivedTokenRequest`, () => { + it(`should return a GetRequest`, () => { + const result = (service as any).createShortLivedTokenRequest(href); + expect(result.constructor.name).toBe('GetRequest'); + }); + + it(`should return a request with the given href`, () => { + const result = (service as any).createShortLivedTokenRequest(href); + expect(result.href).toBe(href) ; + }); + + it(`should have a responseMsToLive of 2 seconds`, () => { + const result = (service as any).createShortLivedTokenRequest(href); + expect(result.responseMsToLive).toBe(2 * 1000) ; + }); + }); +}); diff --git a/src/app/core/auth/server-auth-request.service.ts b/src/app/core/auth/server-auth-request.service.ts new file mode 100644 index 0000000000..751389f71d --- /dev/null +++ b/src/app/core/auth/server-auth-request.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@angular/core'; +import { AuthRequestService } from './auth-request.service'; +import { GetRequest } from '../data/request.models'; +import { HALEndpointService } from '../shared/hal-endpoint.service'; +import { RequestService } from '../data/request.service'; +import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service'; + +/** + * Server side version of the service to send authentication requests + */ +@Injectable() +export class ServerAuthRequestService extends AuthRequestService { + + constructor( + halService: HALEndpointService, + requestService: RequestService, + rdbService: RemoteDataBuildService + ) { + super(halService, requestService, rdbService); + } + + /** + * Factory function to create the request object to send. This needs to be a POST client side and + * a GET server side. Due to CSRF validation, the server isn't allowed to send a POST, so we allow + * only the server IP to send a GET to this endpoint. + * + * @param href The href to send the request to + * @protected + */ + protected createShortLivedTokenRequest(href: string): GetRequest { + return Object.assign(new GetRequest(this.requestService.generateRequestId(), href), { + responseMsToLive: 2 * 1000 // A short lived token is only valid for 2 seconds. + }); + } + +} diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index f73bfd0bdf..619a7dbadc 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -31,7 +31,6 @@ import { CSSVariableService } from '../shared/sass-helper/sass-helper.service'; import { SidebarService } from '../shared/sidebar/sidebar.service'; import { UploaderService } from '../shared/uploader/uploader.service'; import { SectionFormOperationsService } from '../submission/sections/form/section-form-operations.service'; -import { AuthRequestService } from './auth/auth-request.service'; import { AuthenticatedGuard } from './auth/authenticated.guard'; import { AuthStatus } from './auth/models/auth-status.model'; import { BrowseService } from './browse/browse.service'; @@ -188,7 +187,6 @@ const EXPORTS = []; const PROVIDERS = [ ApiService, AuthenticatedGuard, - AuthRequestService, CommunityDataService, CollectionDataService, SiteDataService, diff --git a/src/modules/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts index 4b6c5c813e..070d530dfe 100644 --- a/src/modules/app/browser-app.module.ts +++ b/src/modules/app/browser-app.module.ts @@ -31,6 +31,8 @@ import { import { LocaleService } from '../../app/core/locale/locale.service'; import { GoogleAnalyticsService } from '../../app/statistics/google-analytics.service'; import { RouterModule, NoPreloading } from '@angular/router'; +import { AuthRequestService } from '../../app/core/auth/auth-request.service'; +import { BrowserAuthRequestService } from '../../app/core/auth/browser-auth-request.service'; export const REQ_KEY = makeStateKey('req'); @@ -104,6 +106,10 @@ export function getRequest(transferState: TransferState): any { provide: GoogleAnalyticsService, useClass: GoogleAnalyticsService, }, + { + provide: AuthRequestService, + useClass: BrowserAuthRequestService, + }, { provide: LocationToken, useFactory: locationProvider, diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts index 906d4c5f35..dad3a60d5c 100644 --- a/src/modules/app/server-app.module.ts +++ b/src/modules/app/server-app.module.ts @@ -31,6 +31,8 @@ import { ServerHardRedirectService } from '../../app/core/services/server-hard-r import { Angulartics2 } from 'angulartics2'; import { Angulartics2Mock } from '../../app/shared/mocks/angulartics2.service.mock'; import { RouterModule } from '@angular/router'; +import { AuthRequestService } from '../../app/core/auth/auth-request.service'; +import { ServerAuthRequestService } from '../../app/core/auth/server-auth-request.service'; export function createTranslateLoader() { return new TranslateJson5UniversalLoader('dist/server/assets/i18n/', '.json5'); @@ -82,6 +84,10 @@ export function createTranslateLoader() { provide: SubmissionService, useClass: ServerSubmissionService }, + { + provide: AuthRequestService, + useClass: ServerAuthRequestService, + }, { provide: LocaleService, useClass: ServerLocaleService From 3c93777e845d27860e6d648c6bd078d17f188821 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 6 Apr 2021 16:45:35 +0200 Subject: [PATCH 13/31] don't use CSR fallback when the headers are changed after the response is sent --- server.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/server.ts b/server.ts index ada6c9f040..73b88cd0c6 100644 --- a/server.ts +++ b/server.ts @@ -160,6 +160,11 @@ function ngApp(req, res) { }, (err, data) => { if (hasNoValue(err) && hasValue(data)) { res.send(data); + } else if (hasValue(err) && err.code === 'ERR_HTTP_HEADERS_SENT') { + // When this error occurs we can't fall back to CSR because the response has already been + // sent. These errors occur for various reasons in universal, not all of which are in our + // control to solve. + console.warn('Warning [ERR_HTTP_HEADERS_SENT]: Tried to set headers after they were sent to the client'); } else { console.warn('Error in SSR, serving for direct CSR.'); if (hasValue(err)) { From 425f530f087eba079e12223109b23d9d1a55187d Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 6 Apr 2021 16:58:37 +0200 Subject: [PATCH 14/31] fix LGTM warnings --- src/app/core/metadata/metadata.service.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/core/metadata/metadata.service.ts b/src/app/core/metadata/metadata.service.ts index 5bbb90ac80..807f7a42ab 100644 --- a/src/app/core/metadata/metadata.service.ts +++ b/src/app/core/metadata/metadata.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable } from '@angular/core'; +import { Injectable } from '@angular/core'; import { Meta, MetaDefinition, Title } from '@angular/platform-browser'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; @@ -19,11 +19,13 @@ import { BitstreamFormat } from '../shared/bitstream-format.model'; import { Bitstream } from '../shared/bitstream.model'; import { DSpaceObject } from '../shared/dspace-object.model'; import { Item } from '../shared/item.model'; -import { getFirstSucceededRemoteDataPayload, getFirstSucceededRemoteListPayload } from '../shared/operators'; +import { + getFirstSucceededRemoteDataPayload, + getFirstSucceededRemoteListPayload +} from '../shared/operators'; import { environment } from '../../../environments/environment'; import { RootDataService } from '../data/root-data.service'; -import { URLCombiner } from '../url-combiner/url-combiner'; -import { getBitstreamDownloadRoute, getBitstreamModuleRoute } from '../../app-routing-paths'; +import { getBitstreamDownloadRoute } from '../../app-routing-paths'; @Injectable() export class MetadataService { From b8cb8f90b0d15bf82924a8eff52d83781b5f34b1 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 6 Apr 2021 17:26:00 +0200 Subject: [PATCH 15/31] increase contrast to improve accessibility --- src/themes/dspace/styles/_theme_sass_variable_overrides.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss index 86d4092a53..2d3b63dc82 100644 --- a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss +++ b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss @@ -8,7 +8,8 @@ $font-family-sans-serif: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; $gray-100: #e8ebf3 !default; $gray-400: #ced4da !default; -$gray-800: #444444 !default; // #444 +$gray-600: #959595 !default; +$gray-800: #444444 !default; $navbar-dark-color: #FFFFFF; @@ -16,7 +17,7 @@ $navbar-dark-color: #FFFFFF; $blue: #43515f !default; //$green: #92C642 !default; $green: #92C642 !default; -$cyan: #2e80a3 !default; +$cyan: #207698 !default; $yellow: #ec9433 !default; $red: #CF4444 !default; $dark: #43515f !default; From 6b7da66c81bc72690b576b74e16eabafa7fd69a2 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 6 Apr 2021 17:26:17 +0200 Subject: [PATCH 16/31] update link href --- .../dspace/app/+home-page/home-news/home-news.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/themes/dspace/app/+home-page/home-news/home-news.component.html b/src/themes/dspace/app/+home-page/home-news/home-news.component.html index 9063961ace..f58c307705 100644 --- a/src/themes/dspace/app/+home-page/home-news/home-news.component.html +++ b/src/themes/dspace/app/+home-page/home-news/home-news.component.html @@ -19,7 +19,7 @@ handle.net and DataCite DOI -

Join an international community of leading institutions using DSpace.

+

Join an international community of leading institutions using DSpace.

Photo by @inspiredimages From 7cbce550e214c296a7dcd44902e554d49a7c72ff Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 6 Apr 2021 17:28:19 +0200 Subject: [PATCH 17/31] remove commented out line --- src/themes/dspace/styles/_theme_sass_variable_overrides.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss index 2d3b63dc82..5a6420834d 100644 --- a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss +++ b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss @@ -15,7 +15,6 @@ $navbar-dark-color: #FFFFFF; /* Reassign color vars to semantic color scheme */ $blue: #43515f !default; -//$green: #92C642 !default; $green: #92C642 !default; $cyan: #207698 !default; $yellow: #ec9433 !default; From 9bf2465ee8b47019a0ff9388ea17dce742c124cc Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Thu, 8 Apr 2021 14:18:13 +0200 Subject: [PATCH 18/31] change the font and overlay color on the home-page background image to improve readability --- src/themes/dspace/styles/_theme_css_variable_overrides.scss | 2 +- src/themes/dspace/styles/_theme_sass_variable_overrides.scss | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/themes/dspace/styles/_theme_css_variable_overrides.scss b/src/themes/dspace/styles/_theme_css_variable_overrides.scss index 75d4fd9362..2a61babdb7 100644 --- a/src/themes/dspace/styles/_theme_css_variable_overrides.scss +++ b/src/themes/dspace/styles/_theme_css_variable_overrides.scss @@ -1,7 +1,7 @@ // Override or add CSS variables for your theme here :root { - --ds-banner-text-background: rgba(0, 0, 0, 0.35); + --ds-banner-text-background: rgba(0, 0, 0, 0.45); --ds-banner-background-gradient-width: 300px; --ds-home-news-link-color: #{$green}; --ds-home-news-link-hover-color: #{darken($green, 15%)}; diff --git a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss index 5a6420834d..70aa0b1850 100644 --- a/src/themes/dspace/styles/_theme_sass_variable_overrides.scss +++ b/src/themes/dspace/styles/_theme_sass_variable_overrides.scss @@ -3,9 +3,9 @@ // still uses Sass variables internally. So if you want to override bootstrap (or other sass // variables) you can do so here. Their CSS counterparts will include the changes you make here -@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro:200,200i,300,300i,400,400i,600,600i,700,700i,900,900i&subset=cyrillic,cyrillic-ext,greek,greek-ext,latin-ext,vietnamese'); +@import url('https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200;0,300;0,400;0,600;0,700;0,800;1,200;1,300;1,400;1,600;1,700;1,800&display=swap'); -$font-family-sans-serif: 'Source Sans Pro', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +$font-family-sans-serif: 'Nunito', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; $gray-100: #e8ebf3 !default; $gray-400: #ced4da !default; $gray-600: #959595 !default; From 4e21bdaf1fc4d4b19ef9e6fa4e24328ab87dab22 Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Fri, 9 Apr 2021 10:16:56 +0200 Subject: [PATCH 19/31] [CST-3782] repeatable with lookup fixes --- .../ds-dynamic-form-control-container.component.html | 2 ++ .../ds-dynamic-form-control-container.component.ts | 8 +++++--- .../existing-metadata-list-element.component.html | 1 + .../existing-metadata-list-element.component.spec.ts | 3 +++ .../existing-metadata-list-element.component.ts | 6 +++++- .../existing-relation-list-element.component.html | 1 + .../existing-relation-list-element.component.spec.ts | 3 +++ .../existing-relation-list-element.component.ts | 4 ++++ 8 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html index bfa9c214e9..7c0c08382a 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html @@ -54,6 +54,7 @@ [metadataFields]="model.metadataFields" [submissionId]="model.submissionId" [relationshipOptions]="model.relationship" + [canRemove]="canRemove()" (remove)="onRemove()" > @@ -66,6 +67,7 @@ [metadataFields]="model.metadataFields" [submissionId]="model.submissionId" [relationshipOptions]="model.relationship" + [canRemove]="canRemove()" > 1; + } + /** * Initialize this.item$ based on this.model.submissionId */ diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html index 07ea131a00..5275cef68a 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html @@ -9,6 +9,7 @@ diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts index 3a5623cfdd..2a38a07759 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts @@ -16,6 +16,8 @@ import { ItemSearchResult } from '../../../../object-collection/shared/item-sear import { of as observableOf } from 'rxjs'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoaderMock } from '../../../../testing/translate-loader.mock'; +import { SubmissionService } from '../../../../../submission/submission.service'; +import { SubmissionServiceStub } from '../../../../testing/submission-service.stub'; describe('ExistingMetadataListElementComponent', () => { let component: ExistingMetadataListElementComponent; @@ -79,6 +81,7 @@ describe('ExistingMetadataListElementComponent', () => { providers: [ { provide: SelectableListService, useValue: selectionService }, { provide: Store, useValue: store }, + { provide: SubmissionService, useClass: SubmissionServiceStub }, ], schemas: [NO_ERRORS_SCHEMA] }) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts index d0d4ad435b..927d5e04d2 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts @@ -19,6 +19,7 @@ import { FormFieldMetadataValueObject } from '../../models/form-field-metadata-v import { RelationshipOptions } from '../../models/relationship-options.model'; import { DynamicConcatModel } from '../models/ds-dynamic-concat.model'; import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions'; +import { SubmissionService } from '../../../../../submission/submission.service'; // tslint:disable:max-classes-per-file /** @@ -145,6 +146,7 @@ export class ExistingMetadataListElementComponent implements OnInit, OnChanges, @Input() metadataFields: string[]; @Input() relationshipOptions: RelationshipOptions; @Input() submissionId: string; + @Input() canRemove = true; metadataRepresentation$: BehaviorSubject = new BehaviorSubject(undefined); relatedItem: Item; @Output() remove: EventEmitter = new EventEmitter(); @@ -155,7 +157,8 @@ export class ExistingMetadataListElementComponent implements OnInit, OnChanges, constructor( private selectableListService: SelectableListService, - private store: Store + private store: Store, + private submissionService: SubmissionService ) { } @@ -194,6 +197,7 @@ export class ExistingMetadataListElementComponent implements OnInit, OnChanges, * Removes the selected relationship from the list */ removeSelection() { + this.submissionService.dispatchSave(this.submissionId); this.selectableListService.deselectSingle(this.listId, Object.assign(new ItemSearchResult(), { indexableObject: this.relatedItem })); this.store.dispatch(new RemoveRelationshipAction(this.submissionItem, this.relatedItem, this.relationshipOptions.relationshipType, this.submissionId)); this.remove.emit(); diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html index 15087d2553..ed29fd14e3 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html @@ -8,6 +8,7 @@
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.spec.ts index fddb960515..d733f503bb 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.spec.ts @@ -12,6 +12,8 @@ import { ItemSearchResult } from '../../../../object-collection/shared/item-sear import { of as observableOf } from 'rxjs'; import { ReorderableRelationship } from '../existing-metadata-list-element/existing-metadata-list-element.component'; import { createSuccessfulRemoteDataObject$ } from '../../../../remote-data.utils'; +import { SubmissionService } from '../../../../../submission/submission.service'; +import { SubmissionServiceStub } from '../../../../testing/submission-service.stub'; describe('ExistingRelationListElementComponent', () => { let component: ExistingRelationListElementComponent; @@ -67,6 +69,7 @@ describe('ExistingRelationListElementComponent', () => { providers: [ { provide: SelectableListService, useValue: selectionService }, { provide: Store, useValue: store }, + { provide: SubmissionService, useClass: SubmissionServiceStub }, ], schemas: [NO_ERRORS_SCHEMA] }) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts index 8a4a7120e8..6d7e846bcb 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts @@ -12,6 +12,7 @@ import { RelationshipOptions } from '../../models/relationship-options.model'; import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions'; import { ViewMode } from '../../../../../core/shared/view-mode.model'; import { ReorderableRelationship } from '../existing-metadata-list-element/existing-metadata-list-element.component'; +import { SubmissionService } from '../../../../../submission/submission.service'; // tslint:disable:max-classes-per-file /** @@ -61,6 +62,7 @@ export class ExistingRelationListElementComponent implements OnInit, OnChanges, @Input() metadataFields: string[]; @Input() relationshipOptions: RelationshipOptions; @Input() submissionId: string; + @Input() canRemove = true; relatedItem$: BehaviorSubject = new BehaviorSubject(undefined); viewType = ViewMode.ListElement; @Output() remove: EventEmitter = new EventEmitter(); @@ -72,6 +74,7 @@ export class ExistingRelationListElementComponent implements OnInit, OnChanges, constructor( private selectableListService: SelectableListService, + private submissionService: SubmissionService, private store: Store ) { } @@ -102,6 +105,7 @@ export class ExistingRelationListElementComponent implements OnInit, OnChanges, * Removes the selected relationship from the list */ removeSelection() { + this.submissionService.dispatchSave(this.submissionId); this.selectableListService.deselectSingle(this.listId, Object.assign(new ItemSearchResult(), { indexableObject: this.relatedItem$.getValue() })); this.store.dispatch(new RemoveRelationshipAction(this.submissionItem, this.relatedItem$.getValue(), this.relationshipOptions.relationshipType, this.submissionId)); } From b537f70d7bc8186e78b206c22e0d0935131524ad Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Mon, 12 Apr 2021 10:04:34 +0200 Subject: [PATCH 20/31] [CST-3782] possibility to remove the only relationship --- .../ds-dynamic-form-control-container.component.html | 2 -- .../ds-dynamic-form-control-container.component.ts | 7 +++---- .../existing-metadata-list-element.component.html | 1 - .../existing-metadata-list-element.component.ts | 1 - .../existing-relation-list-element.component.html | 1 - .../existing-relation-list-element.component.ts | 1 - 6 files changed, 3 insertions(+), 10 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html index 7c0c08382a..bfa9c214e9 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html @@ -54,7 +54,6 @@ [metadataFields]="model.metadataFields" [submissionId]="model.submissionId" [relationshipOptions]="model.relationship" - [canRemove]="canRemove()" (remove)="onRemove()" > @@ -67,7 +66,6 @@ [metadataFields]="model.metadataFields" [submissionId]="model.submissionId" [relationshipOptions]="model.relationship" - [canRemove]="canRemove()" > 1; - } - /** * Initialize this.item$ based on this.model.submissionId */ diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html index 5275cef68a..07ea131a00 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.html @@ -9,7 +9,6 @@ diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts index 927d5e04d2..29097d1337 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts @@ -146,7 +146,6 @@ export class ExistingMetadataListElementComponent implements OnInit, OnChanges, @Input() metadataFields: string[]; @Input() relationshipOptions: RelationshipOptions; @Input() submissionId: string; - @Input() canRemove = true; metadataRepresentation$: BehaviorSubject = new BehaviorSubject(undefined); relatedItem: Item; @Output() remove: EventEmitter = new EventEmitter(); diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html index ed29fd14e3..15087d2553 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.html @@ -8,7 +8,6 @@ diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts index 6d7e846bcb..8e55f32ee6 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-relation-list-element/existing-relation-list-element.component.ts @@ -62,7 +62,6 @@ export class ExistingRelationListElementComponent implements OnInit, OnChanges, @Input() metadataFields: string[]; @Input() relationshipOptions: RelationshipOptions; @Input() submissionId: string; - @Input() canRemove = true; relatedItem$: BehaviorSubject = new BehaviorSubject(undefined); viewType = ViewMode.ListElement; @Output() remove: EventEmitter = new EventEmitter(); From 31e4aa788ee484815c3588a7077e56d11af1068a Mon Sep 17 00:00:00 2001 From: Alessandro Martelli Date: Tue, 13 Apr 2021 09:51:05 +0200 Subject: [PATCH 21/31] [CST-3782] lookup click doesn't clear the plain value --- ...ynamic-form-control-container.component.ts | 19 +++++++++++++++++++ ...ng-metadata-list-element.component.spec.ts | 5 ++++- ...xisting-metadata-list-element.component.ts | 13 +++++++++---- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts index a2ed538ce6..b8024346f5 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.ts @@ -37,6 +37,7 @@ import { DynamicFormControl, DynamicFormControlContainerComponent, DynamicFormControlEvent, + DynamicFormControlEventType, DynamicFormControlModel, DynamicFormLayout, DynamicFormLayoutService, @@ -394,6 +395,24 @@ export class DsDynamicFormControlContainerComponent extends DynamicFormControlCo size: 'lg' }); + if (hasValue(this.model.value)) { + this.focus.emit({ + $event: new Event('focus'), + context: this.context, + control: this.control, + model: this.model, + type: DynamicFormControlEventType.Focus + } as DynamicFormControlEvent); + + this.change.emit({ + $event: new Event('change'), + context: this.context, + control: this.control, + model: this.model, + type: DynamicFormControlEventType.Change + } as DynamicFormControlEvent); + } + this.submissionService.dispatchSave(this.model.submissionId); const modalComp = this.modalRef.componentInstance; diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts index 2a38a07759..b5af2a8843 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.spec.ts @@ -38,6 +38,7 @@ describe('ExistingMetadataListElementComponent', () => { let relatedSearchResult; let submissionId; let relationshipService; + let submissionServiceStub; function init() { uuid1 = '91ce578d-2e63-4093-8c73-3faafd716000'; @@ -64,6 +65,8 @@ describe('ExistingMetadataListElementComponent', () => { relationship = Object.assign(new Relationship(), { leftItem: leftItemRD$, rightItem: rightItemRD$ }); submissionId = '1234'; reoRel = new ReorderableRelationship(relationship, true, {} as any, {} as any, submissionId); + submissionServiceStub = new SubmissionServiceStub(); + submissionServiceStub.getSubmissionObject.and.returnValue(observableOf({})); } beforeEach(waitForAsync(() => { @@ -81,7 +84,7 @@ describe('ExistingMetadataListElementComponent', () => { providers: [ { provide: SelectableListService, useValue: selectionService }, { provide: Store, useValue: store }, - { provide: SubmissionService, useClass: SubmissionServiceStub }, + { provide: SubmissionService, useValue: submissionServiceStub }, ], schemas: [NO_ERRORS_SCHEMA] }) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts index 29097d1337..899400eba0 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/existing-metadata-list-element/existing-metadata-list-element.component.ts @@ -3,7 +3,7 @@ import { FormControl } from '@angular/forms'; import { DynamicFormArrayGroupModel } from '@ng-dynamic-forms/core'; import { Store } from '@ngrx/store'; import { BehaviorSubject, Subscription } from 'rxjs'; -import { filter } from 'rxjs/operators'; +import { filter, take } from 'rxjs/operators'; import { AppState } from '../../../../../app.reducer'; import { RelationshipService } from '../../../../../core/data/relationship.service'; import { Relationship } from '../../../../../core/shared/item-relationships/relationship.model'; @@ -20,6 +20,7 @@ import { RelationshipOptions } from '../../models/relationship-options.model'; import { DynamicConcatModel } from '../models/ds-dynamic-concat.model'; import { RemoveRelationshipAction } from '../relation-lookup-modal/relationship.actions'; import { SubmissionService } from '../../../../../submission/submission.service'; +import { SubmissionObjectEntry } from '../../../../../submission/objects/submission-objects.reducer'; // tslint:disable:max-classes-per-file /** @@ -197,9 +198,13 @@ export class ExistingMetadataListElementComponent implements OnInit, OnChanges, */ removeSelection() { this.submissionService.dispatchSave(this.submissionId); - this.selectableListService.deselectSingle(this.listId, Object.assign(new ItemSearchResult(), { indexableObject: this.relatedItem })); - this.store.dispatch(new RemoveRelationshipAction(this.submissionItem, this.relatedItem, this.relationshipOptions.relationshipType, this.submissionId)); - this.remove.emit(); + this.submissionService.getSubmissionObject(this.submissionId).pipe( + filter((state: SubmissionObjectEntry) => !state.savePending && !state.isLoading), + take(1)).subscribe(() => { + this.selectableListService.deselectSingle(this.listId, Object.assign(new ItemSearchResult(), { indexableObject: this.relatedItem })); + this.store.dispatch(new RemoveRelationshipAction(this.submissionItem, this.relatedItem, this.relationshipOptions.relationshipType, this.submissionId)); + this.remove.emit(); + }); } /** From 59560b993cc5c0e04fe133f4daf82e9cb9730165 Mon Sep 17 00:00:00 2001 From: Bruno Roemers Date: Wed, 14 Apr 2021 10:45:43 +0200 Subject: [PATCH 22/31] 78407: Allow html in submission license section --- .../submission/sections/license/section-license.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/submission/sections/license/section-license.component.html b/src/app/submission/sections/license/section-license.component.html index b8d0f601d2..5ee8a2db6b 100644 --- a/src/app/submission/sections/license/section-license.component.html +++ b/src/app/submission/sections/license/section-license.component.html @@ -1,4 +1,4 @@ -{{ licenseText$ | async }} +

Date: Wed, 14 Apr 2021 16:42:11 +0200 Subject: [PATCH 23/31] use the chromedriver npm package to download the webdriver version compatible with the installed chrome version --- .github/workflows/build.yml | 7 ++++++- e2e/protractor-ci.conf.js | 4 ++++ package.json | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0b5b3f9d8c..d2e8b9fe5e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,6 +53,9 @@ jobs: key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: ${{ runner.os }}-yarn- + - name: Install the latest chromedriver compatible with the installed chrome version + run: yarn global add chromedriver --detect_chromedriver_version + - name: Install Yarn dependencies run: yarn install --frozen-lockfile @@ -94,7 +97,9 @@ jobs: run: curl http://localhost:8080/server/api - name: Run e2e tests (integration tests) - run: yarn run e2e:ci + run: | + chromedriver --url-base='/wd/hub' --port=4444 & + yarn run e2e:ci - name: Shutdown Docker containers run: docker-compose -f ./docker/docker-compose-ci.yml down diff --git a/e2e/protractor-ci.conf.js b/e2e/protractor-ci.conf.js index 63173e44e3..0cfc1f9eaf 100644 --- a/e2e/protractor-ci.conf.js +++ b/e2e/protractor-ci.conf.js @@ -7,4 +7,8 @@ config.capabilities = { } }; +// don't use protractor's webdriver, as it may be incompatible with the installed chrome version +config.directConnect = false; +config.seleniumAddress = 'http://localhost:4444/wd/hub'; + exports.config = config; diff --git a/package.json b/package.json index 4008bb0ac3..80af52e264 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "lint": "ng lint", "lint-fix": "ng lint --fix=true", "e2e": "ng e2e", - "e2e:ci": "ng e2e --protractor-config=./e2e/protractor-ci.conf.js", + "e2e:ci": "ng e2e --webdriver-update=false --protractor-config=./e2e/protractor-ci.conf.js", "compile:server": "webpack --config webpack.server.config.js --progress --color", "serve:ssr": "node dist/server", "clean:coverage": "rimraf coverage", From ef8e66546b31dcc55321b27113b86604f245cadd Mon Sep 17 00:00:00 2001 From: Tim Donohue Date: Tue, 13 Apr 2021 16:40:39 -0500 Subject: [PATCH 24/31] Minor updates to default and dspace theme for Testathon. Also updating DuraSpace to LYRASIS in several places. --- .../+home-page/home-news/home-news.component.html | 6 ++++-- src/app/footer/footer.component.html | 2 +- .../lang-switch/lang-switch.component.spec.ts | 2 +- src/assets/i18n/ar.json5 | 4 ++-- src/assets/i18n/cs.json5 | 4 ++-- src/assets/i18n/de.json5 | 4 ++-- src/assets/i18n/en.json5 | 2 +- src/assets/i18n/es.json5 | 4 ++-- src/assets/i18n/fi.json5 | 4 ++-- src/assets/i18n/fr.json5 | 4 ++-- src/assets/i18n/hu.json5 | 4 ++-- src/assets/i18n/ja.json5 | 4 ++-- src/assets/i18n/lv.json5 | 4 ++-- src/assets/i18n/nl.json5 | 4 ++-- src/assets/i18n/pl.json5 | 4 ++-- src/assets/i18n/pt-BR.json5 | 4 ++-- src/assets/i18n/pt-PT.json5 | 4 ++-- src/assets/i18n/sw.json5 | 4 ++-- src/assets/i18n/tr.json5 | 4 ++-- .../+home-page/home-news/home-news.component.html | 13 ++++++++++++- 20 files changed, 49 insertions(+), 36 deletions(-) diff --git a/src/app/+home-page/home-news/home-news.component.html b/src/app/+home-page/home-news/home-news.component.html index 812c38f798..6bee3cd76f 100644 --- a/src/app/+home-page/home-news/home-news.component.html +++ b/src/app/+home-page/home-news/home-news.component.html @@ -2,7 +2,7 @@
-

Welcome to the DSpace 7 Preview

+

DSpace 7

DSpace is the world leading open source repository platform that enables organisations to:

@@ -13,6 +13,8 @@
  • issue permanent urls and trustworthy identifiers, including optional integrations with handle.net and DataCite DOI
  • -

    Join an international community of leading institutions using DSpace.

    +

    Join an international community of leading institutions using DSpace. +

    diff --git a/src/app/footer/footer.component.html b/src/app/footer/footer.component.html index 3756bce188..bc407c2a97 100644 --- a/src/app/footer/footer.component.html +++ b/src/app/footer/footer.component.html @@ -56,7 +56,7 @@

    {{ 'footer.link.dspace' | translate}} {{ 'footer.copyright' | translate:{year: dateObj | date:'y'} }} - {{ 'footer.link.duraspace' | translate}} + {{ 'footer.link.lyrasis' | translate}}

    Join an international community of leading institutions using DSpace.

    +

    Participate in the official community Testathon + from April 19th through May 7th. The test user accounts below have their password set to the name of + this + software in lowercase.

    +
      +
    • Demo Site Administrator = dspacedemo+admin@gmail.com
    • +
    • Demo Community Administrator = dspacedemo+commadmin@gmail.com
    • +
    • Demo Collection Administrator = dspacedemo+colladmin@gmail.com
    • +
    • Demo Submitter = dspacedemo+submit@gmail.com
    • +
    Photo by @inspiredimages From a4a747e922b23c23b46aa931c202ac18b7b44ce8 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 13 Apr 2021 12:28:27 +0200 Subject: [PATCH 25/31] fix issue where the submission form wouldn't update after a relationship was added --- src/app/core/shared/operators.ts | 64 +++++++-------- .../edit/submission-edit.component.ts | 82 +++++++++++++------ .../submit/submission-submit.component.ts | 40 ++++++++- 3 files changed, 124 insertions(+), 62 deletions(-) diff --git a/src/app/core/shared/operators.ts b/src/app/core/shared/operators.ts index dd610b6ca7..3128538ea9 100644 --- a/src/app/core/shared/operators.ts +++ b/src/app/core/shared/operators.ts @@ -45,32 +45,32 @@ export const sendRequest = (requestService: RequestService) => (source: Observable): Observable => source.pipe(tap((request: RestRequest) => requestService.send(request))); -export const getRemoteDataPayload = () => - (source: Observable>): Observable => +export const getRemoteDataPayload = () => + (source: Observable>): Observable => source.pipe(map((remoteData: RemoteData) => remoteData.payload)); -export const getPaginatedListPayload = () => - (source: Observable>): Observable => +export const getPaginatedListPayload = () => + (source: Observable>): Observable => source.pipe(map((list: PaginatedList) => list.page)); -export const getAllCompletedRemoteData = () => - (source: Observable>): Observable> => +export const getAllCompletedRemoteData = () => + (source: Observable>): Observable> => source.pipe(filter((rd: RemoteData) => hasValue(rd) && rd.hasCompleted)); -export const getFirstCompletedRemoteData = () => - (source: Observable>): Observable> => +export const getFirstCompletedRemoteData = () => + (source: Observable>): Observable> => source.pipe(getAllCompletedRemoteData(), take(1)); -export const takeUntilCompletedRemoteData = () => - (source: Observable>): Observable> => +export const takeUntilCompletedRemoteData = () => + (source: Observable>): Observable> => source.pipe(takeWhile((rd: RemoteData) => hasNoValue(rd) || rd.isLoading, true)); -export const getFirstSucceededRemoteData = () => - (source: Observable>): Observable> => +export const getFirstSucceededRemoteData = () => + (source: Observable>): Observable> => source.pipe(filter((rd: RemoteData) => rd.hasSucceeded), take(1)); -export const getFirstSucceededRemoteWithNotEmptyData = () => - (source: Observable>): Observable> => +export const getFirstSucceededRemoteWithNotEmptyData = () => + (source: Observable>): Observable> => source.pipe(find((rd: RemoteData) => rd.hasSucceeded && isNotEmpty(rd.payload))); /** @@ -83,8 +83,8 @@ export const getFirstSucceededRemoteWithNotEmptyData = () => * These operators were created as a first step in refactoring * out all the instances where this is used incorrectly. */ -export const getFirstSucceededRemoteDataPayload = () => - (source: Observable>): Observable => +export const getFirstSucceededRemoteDataPayload = () => + (source: Observable>): Observable => source.pipe( getFirstSucceededRemoteData(), getRemoteDataPayload() @@ -100,8 +100,8 @@ export const getFirstSucceededRemoteDataPayload = () => * These operators were created as a first step in refactoring * out all the instances where this is used incorrectly. */ -export const getFirstSucceededRemoteDataWithNotEmptyPayload = () => - (source: Observable>): Observable => +export const getFirstSucceededRemoteDataWithNotEmptyPayload = () => + (source: Observable>): Observable => source.pipe( getFirstSucceededRemoteWithNotEmptyData(), getRemoteDataPayload() @@ -117,8 +117,8 @@ export const getFirstSucceededRemoteDataWithNotEmptyPayload = () => * These operators were created as a first step in refactoring * out all the instances where this is used incorrectly. */ -export const getAllSucceededRemoteDataPayload = () => - (source: Observable>): Observable => +export const getAllSucceededRemoteDataPayload = () => + (source: Observable>): Observable => source.pipe( getAllSucceededRemoteData(), getRemoteDataPayload() @@ -138,8 +138,8 @@ export const getAllSucceededRemoteDataPayload = () => * These operators were created as a first step in refactoring * out all the instances where this is used incorrectly. */ -export const getFirstSucceededRemoteListPayload = () => - (source: Observable>>): Observable => +export const getFirstSucceededRemoteListPayload = () => + (source: Observable>>): Observable => source.pipe( getFirstSucceededRemoteData(), getRemoteDataPayload(), @@ -160,8 +160,8 @@ export const getFirstSucceededRemoteListPayload = () => * These operators were created as a first step in refactoring * out all the instances where this is used incorrectly. */ -export const getAllSucceededRemoteListPayload = () => - (source: Observable>>): Observable => +export const getAllSucceededRemoteListPayload = () => + (source: Observable>>): Observable => source.pipe( getAllSucceededRemoteData(), getRemoteDataPayload(), @@ -174,8 +174,8 @@ export const getAllSucceededRemoteListPayload = () => * @param router The router used to navigate to a new page * @param authService Service to check if the user is authenticated */ -export const redirectOn4xx = (router: Router, authService: AuthService) => - (source: Observable>): Observable> => +export const redirectOn4xx = (router: Router, authService: AuthService) => + (source: Observable>): Observable> => observableCombineLatest(source, authService.isAuthenticated()).pipe( map(([rd, isAuthenticated]: [RemoteData, boolean]) => { if (rd.hasFailed) { @@ -229,16 +229,16 @@ export const returnEndUserAgreementUrlTreeOnFalse = (router: Router, redirect: s return hasAgreed ? hasAgreed : router.createUrlTree([getEndUserAgreementPath()], { queryParams }); })); -export const getFinishedRemoteData = () => - (source: Observable>): Observable> => +export const getFinishedRemoteData = () => + (source: Observable>): Observable> => source.pipe(find((rd: RemoteData) => !rd.isLoading)); -export const getAllSucceededRemoteData = () => - (source: Observable>): Observable> => +export const getAllSucceededRemoteData = () => + (source: Observable>): Observable> => source.pipe(filter((rd: RemoteData) => rd.hasSucceeded)); -export const toDSpaceObjectListRD = () => - (source: Observable>>>): Observable>> => +export const toDSpaceObjectListRD = () => + (source: Observable>>>): Observable>> => source.pipe( filter((rd: RemoteData>>) => rd.hasSucceeded), map((rd: RemoteData>>) => { diff --git a/src/app/submission/edit/submission-edit.component.ts b/src/app/submission/edit/submission-edit.component.ts index 7908a052b7..34fdcba104 100644 --- a/src/app/submission/edit/submission-edit.component.ts +++ b/src/app/submission/edit/submission-edit.component.ts @@ -2,11 +2,11 @@ import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { Subscription } from 'rxjs'; -import { filter, switchMap } from 'rxjs/operators'; +import { filter, switchMap, debounceTime } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { WorkspaceitemSectionsObject } from '../../core/submission/models/workspaceitem-sections.model'; -import { hasValue, isEmpty, isNotNull } from '../../shared/empty.util'; +import { hasValue, isEmpty, isNotNull, isNotEmptyOperator } from '../../shared/empty.util'; import { SubmissionDefinitionsModel } from '../../core/config/models/config-submission-definitions.model'; import { SubmissionService } from '../submission.service'; import { NotificationsService } from '../../shared/notifications/notifications.service'; @@ -14,6 +14,9 @@ import { SubmissionObject } from '../../core/submission/models/submission-object import { Collection } from '../../core/shared/collection.model'; import { RemoteData } from '../../core/data/remote-data'; import { Item } from '../../core/shared/item.model'; +import { getAllSucceededRemoteData } from '../../core/shared/operators'; +import { ItemDataService } from '../../core/data/item-data.service'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; /** * This component allows to edit an existing workspaceitem/workflowitem. @@ -60,6 +63,16 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { * @type {Array} */ private subs: Subscription[] = []; + + /** + * BehaviorSubject containing the self link to the item for this submission + * @private + */ + private itemLink$: BehaviorSubject = new BehaviorSubject(undefined); + + /** + * The item for this submission. + */ public item: Item; /** @@ -69,6 +82,7 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { * @param {NotificationsService} notificationsService * @param {ActivatedRoute} route * @param {Router} router + * @param {ItemDataService} itemDataService * @param {SubmissionService} submissionService * @param {TranslateService} translate */ @@ -76,6 +90,7 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { private notificationsService: NotificationsService, private route: ActivatedRoute, private router: Router, + private itemDataService: ItemDataService, private submissionService: SubmissionService, private translate: TranslateService) { } @@ -84,32 +99,47 @@ export class SubmissionEditComponent implements OnDestroy, OnInit { * Retrieve workspaceitem/workflowitem from server and initialize all instance variables */ ngOnInit() { - this.subs.push(this.route.paramMap.pipe( - switchMap((params: ParamMap) => this.submissionService.retrieveSubmission(params.get('id'))), - // NOTE new submission is retrieved on the browser side only, so get null on server side rendering - filter((submissionObjectRD: RemoteData) => isNotNull(submissionObjectRD)) - ).subscribe((submissionObjectRD: RemoteData) => { - if (submissionObjectRD.hasSucceeded) { - if (isEmpty(submissionObjectRD.payload)) { - this.notificationsService.info(null, this.translate.get('submission.general.cannot_submit')); - this.router.navigate(['/mydspace']); + this.subs.push( + this.route.paramMap.pipe( + switchMap((params: ParamMap) => this.submissionService.retrieveSubmission(params.get('id'))), + // NOTE new submission is retrieved on the browser side only, so get null on server side rendering + filter((submissionObjectRD: RemoteData) => isNotNull(submissionObjectRD)) + ).subscribe((submissionObjectRD: RemoteData) => { + if (submissionObjectRD.hasSucceeded) { + if (isEmpty(submissionObjectRD.payload)) { + this.notificationsService.info(null, this.translate.get('submission.general.cannot_submit')); + this.router.navigate(['/mydspace']); + } else { + this.submissionId = submissionObjectRD.payload.id.toString(); + this.collectionId = (submissionObjectRD.payload.collection as Collection).id; + this.selfUrl = submissionObjectRD.payload._links.self.href; + this.sections = submissionObjectRD.payload.sections; + this.itemLink$.next(submissionObjectRD.payload._links.item.href); + this.item = submissionObjectRD.payload.item; + this.submissionDefinition = (submissionObjectRD.payload.submissionDefinition as SubmissionDefinitionsModel); + } } else { - this.submissionId = submissionObjectRD.payload.id.toString(); - this.collectionId = (submissionObjectRD.payload.collection as Collection).id; - this.selfUrl = submissionObjectRD.payload._links.self.href; - this.sections = submissionObjectRD.payload.sections; - this.item = submissionObjectRD.payload.item as Item; - this.submissionDefinition = (submissionObjectRD.payload.submissionDefinition as SubmissionDefinitionsModel); - this.changeDetectorRef.detectChanges(); + if (submissionObjectRD.statusCode === 404) { + // redirect to not found page + this.router.navigate(['/404'], { skipLocationChange: true }); + } + // TODO handle generic error } - } else { - if (submissionObjectRD.statusCode === 404) { - // redirect to not found page - this.router.navigate(['/404'], { skipLocationChange: true }); - } - // TODO handle generic error - } - })); + }), + this.itemLink$.pipe( + isNotEmptyOperator(), + switchMap((itemLink: string) => + this.itemDataService.findByHref(itemLink) + ), + getAllSucceededRemoteData(), + // Multiple sources can update the item in quick succession. + // We only want to rerender the form if the item is unchanged for some time + debounceTime(300), + ).subscribe((itemRd: RemoteData) => { + this.item = itemRd.payload; + this.changeDetectorRef.detectChanges(); + }), + ); } /** diff --git a/src/app/submission/submit/submission-submit.component.ts b/src/app/submission/submit/submission-submit.component.ts index 003f5280a8..0c2172368a 100644 --- a/src/app/submission/submit/submission-submit.component.ts +++ b/src/app/submission/submit/submission-submit.component.ts @@ -3,7 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router'; import { Subscription } from 'rxjs'; -import { hasValue, isEmpty, isNotNull } from '../../shared/empty.util'; +import { hasValue, isEmpty, isNotNull, isNotEmptyOperator } from '../../shared/empty.util'; import { SubmissionDefinitionsModel } from '../../core/config/models/config-submission-definitions.model'; import { TranslateService } from '@ngx-translate/core'; import { NotificationsService } from '../../shared/notifications/notifications.service'; @@ -12,6 +12,11 @@ import { SubmissionObject } from '../../core/submission/models/submission-object import { Collection } from '../../core/shared/collection.model'; import { Item } from '../../core/shared/item.model'; import { WorkspaceitemSectionsObject } from '../../core/submission/models/workspaceitem-sections.model'; +import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; +import { switchMap, debounceTime } from 'rxjs/operators'; +import { getAllSucceededRemoteData } from '../../core/shared/operators'; +import { RemoteData } from '../../core/data/remote-data'; +import { ItemDataService } from '../../core/data/item-data.service'; /** * This component allows to submit a new workspaceitem. @@ -28,6 +33,16 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit { * @type {string} */ public collectionId: string; + + /** + * BehaviorSubject containing the self link to the item for this submission + * @private + */ + private itemLink$: BehaviorSubject = new BehaviorSubject(undefined); + + /** + * The item for this submission. + */ public item: Item; /** @@ -71,6 +86,7 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit { * * @param {ChangeDetectorRef} changeDetectorRef * @param {NotificationsService} notificationsService + * @param {ItemDataService} itemDataService * @param {SubmissionService} submissionService * @param {Router} router * @param {TranslateService} translate @@ -80,13 +96,16 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit { constructor(private changeDetectorRef: ChangeDetectorRef, private notificationsService: NotificationsService, private router: Router, + private itemDataService: ItemDataService, private submissionService: SubmissionService, private translate: TranslateService, private viewContainerRef: ViewContainerRef, private route: ActivatedRoute) { this.route .queryParams - .subscribe((params) => { this.collectionParam = (params.collection); }); + .subscribe((params) => { + this.collectionParam = (params.collection); + }); } /** @@ -108,11 +127,24 @@ export class SubmissionSubmitComponent implements OnDestroy, OnInit { this.selfUrl = submissionObject._links.self.href; this.submissionDefinition = (submissionObject.submissionDefinition as SubmissionDefinitionsModel); this.submissionId = submissionObject.id; + this.itemLink$.next(submissionObject._links.item.href); this.item = submissionObject.item as Item; - this.changeDetectorRef.detectChanges(); } } - }) + }), + this.itemLink$.pipe( + isNotEmptyOperator(), + switchMap((itemLink: string) => + this.itemDataService.findByHref(itemLink) + ), + getAllSucceededRemoteData(), + // Multiple sources can update the item in quick succession. + // We only want to rerender the form if the item is unchanged for some time + debounceTime(300), + ).subscribe((itemRd: RemoteData) => { + this.item = itemRd.payload; + this.changeDetectorRef.detectChanges(); + }) ); } From 56a54ae1c99b787ca3e9f1c4168a4ce90db2f365 Mon Sep 17 00:00:00 2001 From: Art Lowel Date: Tue, 13 Apr 2021 13:54:16 +0200 Subject: [PATCH 26/31] don't hide the lookup button for virtual relationship fields --- .../ds-dynamic-form-control-container.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html index bfa9c214e9..085ccb6248 100644 --- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html +++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.html @@ -35,7 +35,7 @@ -
    +