mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Merge remote-tracking branch 'origin/main' into #1110
This commit is contained in:
26
.github/workflows/build.yml
vendored
26
.github/workflows/build.yml
vendored
@@ -16,6 +16,9 @@ jobs:
|
|||||||
DSPACE_REST_PORT: 8080
|
DSPACE_REST_PORT: 8080
|
||||||
DSPACE_REST_NAMESPACE: '/server'
|
DSPACE_REST_NAMESPACE: '/server'
|
||||||
DSPACE_REST_SSL: false
|
DSPACE_REST_SSL: false
|
||||||
|
# When Chrome version is specified, we pin to a specific version of Chrome & ChromeDriver
|
||||||
|
# Comment this out to use the latest release of both.
|
||||||
|
CHROME_VERSION: "90.0.4430.212-1"
|
||||||
strategy:
|
strategy:
|
||||||
# Create a matrix of Node versions to test against (in parallel)
|
# Create a matrix of Node versions to test against (in parallel)
|
||||||
matrix:
|
matrix:
|
||||||
@@ -34,10 +37,20 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
- name: Install latest Chrome (for e2e tests)
|
# If CHROME_VERSION env variable specified above, then pin to that version.
|
||||||
|
# Otherwise, just install latest version of Chrome.
|
||||||
|
- name: Install Chrome (for e2e tests)
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
if [[ -z "${CHROME_VERSION}" ]]
|
||||||
sudo apt-get --only-upgrade install google-chrome-stable -y
|
then
|
||||||
|
echo "Installing latest stable version"
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get --only-upgrade install google-chrome-stable -y
|
||||||
|
else
|
||||||
|
echo "Installing version ${CHROME_VERSION}"
|
||||||
|
wget -q "https://dl.google.com/linux/chrome/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}_amd64.deb"
|
||||||
|
sudo dpkg -i "google-chrome-stable_${CHROME_VERSION}_amd64.deb"
|
||||||
|
fi
|
||||||
google-chrome --version
|
google-chrome --version
|
||||||
|
|
||||||
# https://github.com/actions/cache/blob/main/examples.md#node---yarn
|
# https://github.com/actions/cache/blob/main/examples.md#node---yarn
|
||||||
@@ -53,8 +66,11 @@ jobs:
|
|||||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||||
restore-keys: ${{ runner.os }}-yarn-
|
restore-keys: ${{ runner.os }}-yarn-
|
||||||
|
|
||||||
- name: Install the latest chromedriver compatible with the installed chrome version
|
- name: Install latest ChromeDriver compatible with installed Chrome
|
||||||
run: yarn global add chromedriver --detect_chromedriver_version
|
# needs to be npm, the --detect_chromedriver_version flag doesn't work with yarn global
|
||||||
|
run: |
|
||||||
|
npm install -g chromedriver --detect_chromedriver_version
|
||||||
|
chromedriver -v
|
||||||
|
|
||||||
- name: Install Yarn dependencies
|
- name: Install Yarn dependencies
|
||||||
run: yarn install --frozen-lockfile
|
run: yarn install --frozen-lockfile
|
||||||
|
@@ -47,6 +47,7 @@
|
|||||||
"src/robots.txt"
|
"src/robots.txt"
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
|
"src/styles/startup.scss",
|
||||||
{
|
{
|
||||||
"input": "src/styles/base-theme.scss",
|
"input": "src/styles/base-theme.scss",
|
||||||
"inject": false,
|
"inject": false,
|
||||||
|
@@ -8,7 +8,11 @@ import { ItemPageFieldComponent } from '../item-page-field.component';
|
|||||||
templateUrl: '../item-page-field.component.html'
|
templateUrl: '../item-page-field.component.html'
|
||||||
})
|
})
|
||||||
/**
|
/**
|
||||||
* This component is used for displaying the author (dc.contributor.author, dc.creator and dc.contributor) metadata of an item
|
* This component is used for displaying the author (dc.contributor.author, dc.creator and
|
||||||
|
* dc.contributor) metadata of an item.
|
||||||
|
*
|
||||||
|
* Note that it purely deals with metadata. It won't turn related Person authors into links to their
|
||||||
|
* item page. For that use a {@link MetadataRepresentationListComponent} instead.
|
||||||
*/
|
*/
|
||||||
export class ItemPageAuthorFieldComponent extends ItemPageFieldComponent {
|
export class ItemPageAuthorFieldComponent extends ItemPageFieldComponent {
|
||||||
|
|
||||||
|
@@ -18,7 +18,12 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
<ds-item-page-file-section [item]="object"></ds-item-page-file-section>
|
<ds-item-page-file-section [item]="object"></ds-item-page-file-section>
|
||||||
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
||||||
<ds-item-page-author-field [item]="object"></ds-item-page-author-field>
|
<ds-metadata-representation-list class="ds-item-page-mixed-author-field"
|
||||||
|
[parentItem]="object"
|
||||||
|
[itemType]="'Person'"
|
||||||
|
[metadataFields]="['dc.contributor.author', 'dc.creator']"
|
||||||
|
[label]="'relationships.isAuthorOf' | translate">
|
||||||
|
</ds-metadata-representation-list>
|
||||||
<ds-generic-item-page-field [item]="object"
|
<ds-generic-item-page-field [item]="object"
|
||||||
[fields]="['journal.title']"
|
[fields]="['journal.title']"
|
||||||
[label]="'publication.page.journal-title'">
|
[label]="'publication.page.journal-title'">
|
||||||
@@ -37,12 +42,6 @@
|
|||||||
</ds-generic-item-page-field>
|
</ds-generic-item-page-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-md-6">
|
<div class="col-xs-12 col-md-6">
|
||||||
<ds-metadata-representation-list
|
|
||||||
[parentItem]="object"
|
|
||||||
[itemType]="'Person'"
|
|
||||||
[metadataField]="'dc.contributor.author'"
|
|
||||||
[label]="'relationships.isAuthorOf' | translate">
|
|
||||||
</ds-metadata-representation-list>
|
|
||||||
<ds-related-items
|
<ds-related-items
|
||||||
[parentItem]="object"
|
[parentItem]="object"
|
||||||
[relationType]="'isProjectOfPublication'"
|
[relationType]="'isProjectOfPublication'"
|
||||||
|
@@ -88,9 +88,14 @@ describe('PublicationComponent', () => {
|
|||||||
expect(fields.length).toBeGreaterThanOrEqual(1);
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain a component to display the author', () => {
|
it('should not contain a metadata only author field', () => {
|
||||||
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-author-field'));
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-author-field'));
|
||||||
expect(fields.length).toBeGreaterThanOrEqual(1);
|
expect(fields.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a mixed metadata and relationship field for authors', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('.ds-item-page-mixed-author-field'));
|
||||||
|
expect(fields.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain a component to display the abstract', () => {
|
it('should contain a component to display the abstract', () => {
|
||||||
|
@@ -18,7 +18,12 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
<ds-item-page-file-section [item]="object"></ds-item-page-file-section>
|
<ds-item-page-file-section [item]="object"></ds-item-page-file-section>
|
||||||
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
<ds-item-page-date-field [item]="object"></ds-item-page-date-field>
|
||||||
<ds-item-page-author-field [item]="object"></ds-item-page-author-field>
|
<ds-metadata-representation-list class="ds-item-page-mixed-author-field"
|
||||||
|
[parentItem]="object"
|
||||||
|
[itemType]="'Person'"
|
||||||
|
[metadataFields]="['dc.contributor.author', 'dc.creator']"
|
||||||
|
[label]="'relationships.isAuthorOf' | translate">
|
||||||
|
</ds-metadata-representation-list>
|
||||||
<ds-generic-item-page-field [item]="object"
|
<ds-generic-item-page-field [item]="object"
|
||||||
[fields]="['journal.title']"
|
[fields]="['journal.title']"
|
||||||
[label]="'item.page.journal-title'">
|
[label]="'item.page.journal-title'">
|
||||||
@@ -37,12 +42,6 @@
|
|||||||
</ds-generic-item-page-field>
|
</ds-generic-item-page-field>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-md-6">
|
<div class="col-xs-12 col-md-6">
|
||||||
<ds-metadata-representation-list
|
|
||||||
[parentItem]="object"
|
|
||||||
[itemType]="'Person'"
|
|
||||||
[metadataField]="'dc.contributor.author'"
|
|
||||||
[label]="'relationships.isAuthorOf' | translate">
|
|
||||||
</ds-metadata-representation-list>
|
|
||||||
<ds-item-page-abstract-field [item]="object"></ds-item-page-abstract-field>
|
<ds-item-page-abstract-field [item]="object"></ds-item-page-abstract-field>
|
||||||
<ds-generic-item-page-field [item]="object"
|
<ds-generic-item-page-field [item]="object"
|
||||||
[fields]="['dc.description']"
|
[fields]="['dc.description']"
|
||||||
|
@@ -89,9 +89,14 @@ describe('UntypedItemComponent', () => {
|
|||||||
expect(fields.length).toBeGreaterThanOrEqual(1);
|
expect(fields.length).toBeGreaterThanOrEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain a component to display the author', () => {
|
it('should not contain a metadata only author field', () => {
|
||||||
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-author-field'));
|
const fields = fixture.debugElement.queryAll(By.css('ds-item-page-author-field'));
|
||||||
expect(fields.length).toBeGreaterThanOrEqual(1);
|
expect(fields.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should contain a mixed metadata and relationship field for authors', () => {
|
||||||
|
const fields = fixture.debugElement.queryAll(By.css('.ds-item-page-mixed-author-field'));
|
||||||
|
expect(fields.length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain a component to display the abstract', () => {
|
it('should contain a component to display the abstract', () => {
|
||||||
|
@@ -5,13 +5,13 @@ import { MetadataRepresentationListComponent } from './metadata-representation-l
|
|||||||
import { RelationshipService } from '../../../core/data/relationship.service';
|
import { RelationshipService } from '../../../core/data/relationship.service';
|
||||||
import { Item } from '../../../core/shared/item.model';
|
import { Item } from '../../../core/shared/item.model';
|
||||||
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
|
import { Relationship } from '../../../core/shared/item-relationships/relationship.model';
|
||||||
import { createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
import { createSuccessfulRemoteDataObject$, createFailedRemoteDataObject$ } from '../../../shared/remote-data.utils';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { VarDirective } from '../../../shared/utils/var.directive';
|
import { VarDirective } from '../../../shared/utils/var.directive';
|
||||||
import { of as observableOf } from 'rxjs';
|
import { of as observableOf } from 'rxjs';
|
||||||
|
|
||||||
const itemType = 'Person';
|
const itemType = 'Person';
|
||||||
const metadataField = 'dc.contributor.author';
|
const metadataFields = ['dc.contributor.author', 'dc.creator'];
|
||||||
const parentItem: Item = Object.assign(new Item(), {
|
const parentItem: Item = Object.assign(new Item(), {
|
||||||
id: 'parent-item',
|
id: 'parent-item',
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -28,6 +28,20 @@ const parentItem: Item = Object.assign(new Item(), {
|
|||||||
place: 1
|
place: 1
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
'dc.creator': [
|
||||||
|
{
|
||||||
|
language: null,
|
||||||
|
value: 'Related Creator with authority',
|
||||||
|
authority: 'virtual::related-creator',
|
||||||
|
place: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
language: null,
|
||||||
|
value: 'Related Creator with authority - unauthorized',
|
||||||
|
authority: 'virtual::related-creator-unauthorized',
|
||||||
|
place: 4,
|
||||||
|
},
|
||||||
|
],
|
||||||
'dc.title': [
|
'dc.title': [
|
||||||
{
|
{
|
||||||
language: null,
|
language: null,
|
||||||
@@ -47,21 +61,49 @@ const relatedAuthor: Item = Object.assign(new Item(), {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const relation: Relationship = Object.assign(new Relationship(), {
|
const relatedCreator: Item = Object.assign(new Item(), {
|
||||||
|
id: 'related-creator',
|
||||||
|
metadata: {
|
||||||
|
'dc.title': [
|
||||||
|
{
|
||||||
|
language: null,
|
||||||
|
value: 'Related Creator'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'dspace.entity.type': 'Person',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const authorRelation: Relationship = Object.assign(new Relationship(), {
|
||||||
leftItem: createSuccessfulRemoteDataObject$(parentItem),
|
leftItem: createSuccessfulRemoteDataObject$(parentItem),
|
||||||
rightItem: createSuccessfulRemoteDataObject$(relatedAuthor)
|
rightItem: createSuccessfulRemoteDataObject$(relatedAuthor)
|
||||||
});
|
});
|
||||||
let relationshipService: RelationshipService;
|
const creatorRelation: Relationship = Object.assign(new Relationship(), {
|
||||||
|
leftItem: createSuccessfulRemoteDataObject$(parentItem),
|
||||||
|
rightItem: createSuccessfulRemoteDataObject$(relatedCreator),
|
||||||
|
});
|
||||||
|
const creatorRelationUnauthorized: Relationship = Object.assign(new Relationship(), {
|
||||||
|
leftItem: createSuccessfulRemoteDataObject$(parentItem),
|
||||||
|
rightItem: createFailedRemoteDataObject$('Unauthorized', 401),
|
||||||
|
});
|
||||||
|
let relationshipService;
|
||||||
|
|
||||||
describe('MetadataRepresentationListComponent', () => {
|
describe('MetadataRepresentationListComponent', () => {
|
||||||
let comp: MetadataRepresentationListComponent;
|
let comp: MetadataRepresentationListComponent;
|
||||||
let fixture: ComponentFixture<MetadataRepresentationListComponent>;
|
let fixture: ComponentFixture<MetadataRepresentationListComponent>;
|
||||||
|
|
||||||
relationshipService = jasmine.createSpyObj('relationshipService',
|
relationshipService = {
|
||||||
{
|
findById: (id: string) => {
|
||||||
findById: createSuccessfulRemoteDataObject$(relation)
|
if (id === 'related-author') {
|
||||||
}
|
return createSuccessfulRemoteDataObject$(authorRelation);
|
||||||
);
|
}
|
||||||
|
if (id === 'related-creator') {
|
||||||
|
return createSuccessfulRemoteDataObject$(creatorRelation);
|
||||||
|
}
|
||||||
|
if (id === 'related-creator-unauthorized') {
|
||||||
|
return createSuccessfulRemoteDataObject$(creatorRelationUnauthorized);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(waitForAsync(() => {
|
beforeEach(waitForAsync(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@@ -81,13 +123,13 @@ describe('MetadataRepresentationListComponent', () => {
|
|||||||
comp = fixture.componentInstance;
|
comp = fixture.componentInstance;
|
||||||
comp.parentItem = parentItem;
|
comp.parentItem = parentItem;
|
||||||
comp.itemType = itemType;
|
comp.itemType = itemType;
|
||||||
comp.metadataField = metadataField;
|
comp.metadataFields = metadataFields;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should load 2 ds-metadata-representation-loader components', () => {
|
it('should load 4 ds-metadata-representation-loader components', () => {
|
||||||
const fields = fixture.debugElement.queryAll(By.css('ds-metadata-representation-loader'));
|
const fields = fixture.debugElement.queryAll(By.css('ds-metadata-representation-loader'));
|
||||||
expect(fields.length).toBe(2);
|
expect(fields.length).toBe(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should contain one page of items', () => {
|
it('should contain one page of items', () => {
|
||||||
|
@@ -42,7 +42,7 @@ export class MetadataRepresentationListComponent extends AbstractIncrementalList
|
|||||||
/**
|
/**
|
||||||
* The metadata field to use for fetching metadata from the item
|
* The metadata field to use for fetching metadata from the item
|
||||||
*/
|
*/
|
||||||
@Input() metadataField: string;
|
@Input() metadataFields: string[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An i18n label to use as a title for the list
|
* An i18n label to use as a title for the list
|
||||||
@@ -70,7 +70,7 @@ export class MetadataRepresentationListComponent extends AbstractIncrementalList
|
|||||||
* @param page The page to fetch
|
* @param page The page to fetch
|
||||||
*/
|
*/
|
||||||
getPage(page: number): Observable<MetadataRepresentation[]> {
|
getPage(page: number): Observable<MetadataRepresentation[]> {
|
||||||
const metadata = this.parentItem.findMetadataSortedByPlace(this.metadataField);
|
const metadata = this.parentItem.findMetadataSortedByPlace(this.metadataFields);
|
||||||
this.total = metadata.length;
|
this.total = metadata.length;
|
||||||
return this.resolveMetadataRepresentations(metadata, page);
|
return this.resolveMetadataRepresentations(metadata, page);
|
||||||
}
|
}
|
||||||
@@ -91,9 +91,11 @@ export class MetadataRepresentationListComponent extends AbstractIncrementalList
|
|||||||
getFirstSucceededRemoteData(),
|
getFirstSucceededRemoteData(),
|
||||||
switchMap((relRD: RemoteData<Relationship>) =>
|
switchMap((relRD: RemoteData<Relationship>) =>
|
||||||
observableCombineLatest(relRD.payload.leftItem, relRD.payload.rightItem).pipe(
|
observableCombineLatest(relRD.payload.leftItem, relRD.payload.rightItem).pipe(
|
||||||
filter(([leftItem, rightItem]) => leftItem.hasSucceeded && rightItem.hasSucceeded),
|
filter(([leftItem, rightItem]) => leftItem.hasCompleted && rightItem.hasCompleted),
|
||||||
map(([leftItem, rightItem]) => {
|
map(([leftItem, rightItem]) => {
|
||||||
if (leftItem.payload.id === this.parentItem.id) {
|
if (!leftItem.hasSucceeded || !rightItem.hasSucceeded) {
|
||||||
|
return observableOf(Object.assign(new MetadatumRepresentation(this.itemType), metadatum));
|
||||||
|
} else if (rightItem.hasSucceeded && leftItem.payload.id === this.parentItem.id) {
|
||||||
return rightItem.payload;
|
return rightItem.payload;
|
||||||
} else if (rightItem.payload.id === this.parentItem.id) {
|
} else if (rightItem.payload.id === this.parentItem.id) {
|
||||||
return leftItem.payload;
|
return leftItem.payload;
|
||||||
|
@@ -1 +1,3 @@
|
|||||||
<ds-themed-root [isNotAuthBlocking]="isNotAuthBlocking$ | async" [isLoading]="isLoading$ | async"></ds-themed-root>
|
<ds-themed-root
|
||||||
|
[shouldShowFullscreenLoader]="(isAuthBlocking$ | async) || (isThemeLoading$ | async)"
|
||||||
|
[shouldShowRouteLoader]="isRouteLoading$ | async"></ds-themed-root>
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { delay, map, distinctUntilChanged, filter, take } from 'rxjs/operators';
|
import { delay, distinctUntilChanged, filter, take } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
AfterViewInit,
|
AfterViewInit,
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
Inject,
|
Inject,
|
||||||
OnInit,
|
OnInit,
|
||||||
Optional,
|
Optional,
|
||||||
|
PLATFORM_ID,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { NavigationCancel, NavigationEnd, NavigationStart, Router } from '@angular/router';
|
import { NavigationCancel, NavigationEnd, NavigationStart, Router } from '@angular/router';
|
||||||
|
|
||||||
@@ -32,7 +33,7 @@ import { LocaleService } from './core/locale/locale.service';
|
|||||||
import { hasValue, isNotEmpty } from './shared/empty.util';
|
import { hasValue, isNotEmpty } from './shared/empty.util';
|
||||||
import { KlaroService } from './shared/cookies/klaro.service';
|
import { KlaroService } from './shared/cookies/klaro.service';
|
||||||
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
||||||
import { DOCUMENT } from '@angular/common';
|
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
|
||||||
import { ThemeService } from './shared/theme-support/theme.service';
|
import { ThemeService } from './shared/theme-support/theme.service';
|
||||||
import { BASE_THEME_NAME } from './shared/theme-support/theme.constants';
|
import { BASE_THEME_NAME } from './shared/theme-support/theme.constants';
|
||||||
import { DEFAULT_THEME_CONFIG } from './shared/theme-support/theme.effects';
|
import { DEFAULT_THEME_CONFIG } from './shared/theme-support/theme.effects';
|
||||||
@@ -45,7 +46,6 @@ import { BreadcrumbsService } from './breadcrumbs/breadcrumbs.service';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit, AfterViewInit {
|
export class AppComponent implements OnInit, AfterViewInit {
|
||||||
isLoading$: BehaviorSubject<boolean> = new BehaviorSubject(true);
|
|
||||||
sidebarVisible: Observable<boolean>;
|
sidebarVisible: Observable<boolean>;
|
||||||
slideSidebarOver: Observable<boolean>;
|
slideSidebarOver: Observable<boolean>;
|
||||||
collapsedSidebarWidth: Observable<string>;
|
collapsedSidebarWidth: Observable<string>;
|
||||||
@@ -57,11 +57,23 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
/**
|
/**
|
||||||
* Whether or not the authentication is currently blocking the UI
|
* Whether or not the authentication is currently blocking the UI
|
||||||
*/
|
*/
|
||||||
isNotAuthBlocking$: Observable<boolean>;
|
isAuthBlocking$: Observable<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the app is in the process of rerouting
|
||||||
|
*/
|
||||||
|
isRouteLoading$: BehaviorSubject<boolean> = new BehaviorSubject(true);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the theme is in the process of being swapped
|
||||||
|
*/
|
||||||
|
isThemeLoading$: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
||||||
@Inject(DOCUMENT) private document: any,
|
@Inject(DOCUMENT) private document: any,
|
||||||
|
@Inject(PLATFORM_ID) private platformId: any,
|
||||||
private themeService: ThemeService,
|
private themeService: ThemeService,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private store: Store<HostWindowState>,
|
private store: Store<HostWindowState>,
|
||||||
@@ -83,6 +95,10 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
this.models = models;
|
this.models = models;
|
||||||
|
|
||||||
this.themeService.getThemeName$().subscribe((themeName: string) => {
|
this.themeService.getThemeName$().subscribe((themeName: string) => {
|
||||||
|
if (isPlatformBrowser(this.platformId)) {
|
||||||
|
// the theme css will never download server side, so this should only happen on the browser
|
||||||
|
this.isThemeLoading$.next(true);
|
||||||
|
}
|
||||||
if (hasValue(themeName)) {
|
if (hasValue(themeName)) {
|
||||||
this.setThemeCss(themeName);
|
this.setThemeCss(themeName);
|
||||||
} else if (hasValue(DEFAULT_THEME_CONFIG)) {
|
} else if (hasValue(DEFAULT_THEME_CONFIG)) {
|
||||||
@@ -118,13 +134,12 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.isNotAuthBlocking$ = this.store.pipe(select(isAuthenticationBlocking)).pipe(
|
this.isAuthBlocking$ = this.store.pipe(select(isAuthenticationBlocking)).pipe(
|
||||||
map((isBlocking: boolean) => isBlocking === false),
|
|
||||||
distinctUntilChanged()
|
distinctUntilChanged()
|
||||||
);
|
);
|
||||||
this.isNotAuthBlocking$
|
this.isAuthBlocking$
|
||||||
.pipe(
|
.pipe(
|
||||||
filter((notBlocking: boolean) => notBlocking),
|
filter((isBlocking: boolean) => isBlocking === false),
|
||||||
take(1)
|
take(1)
|
||||||
).subscribe(() => this.initializeKlaro());
|
).subscribe(() => this.initializeKlaro());
|
||||||
|
|
||||||
@@ -156,12 +171,12 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
delay(0)
|
delay(0)
|
||||||
).subscribe((event) => {
|
).subscribe((event) => {
|
||||||
if (event instanceof NavigationStart) {
|
if (event instanceof NavigationStart) {
|
||||||
this.isLoading$.next(true);
|
this.isRouteLoading$.next(true);
|
||||||
} else if (
|
} else if (
|
||||||
event instanceof NavigationEnd ||
|
event instanceof NavigationEnd ||
|
||||||
event instanceof NavigationCancel
|
event instanceof NavigationCancel
|
||||||
) {
|
) {
|
||||||
this.isLoading$.next(false);
|
this.isRouteLoading$.next(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -209,6 +224,8 @@ export class AppComponent implements OnInit, AfterViewInit {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// the fact that this callback is used, proves we're on the browser.
|
||||||
|
this.isThemeLoading$.next(false);
|
||||||
};
|
};
|
||||||
head.appendChild(link);
|
head.appendChild(link);
|
||||||
}
|
}
|
||||||
|
@@ -127,7 +127,8 @@ describe('BrowseService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getBrowseEntriesFor and findList', () => {
|
describe('getBrowseEntriesFor and findList', () => {
|
||||||
const mockAuthorName = 'Donald Smith';
|
// should contain special characters such that url encoding can be tested as well
|
||||||
|
const mockAuthorName = 'Donald Smith & Sons';
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
requestService = getMockRequestService(getRequestEntry$(true));
|
requestService = getMockRequestService(getRequestEntry$(true));
|
||||||
@@ -152,7 +153,7 @@ describe('BrowseService', () => {
|
|||||||
|
|
||||||
describe('when findList is called with a valid browse definition id', () => {
|
describe('when findList is called with a valid browse definition id', () => {
|
||||||
it('should call hrefOnlyDataService.findAllByHref with the expected href', () => {
|
it('should call hrefOnlyDataService.findAllByHref with the expected href', () => {
|
||||||
const expected = browseDefinitions[1]._links.items.href + '?filterValue=' + mockAuthorName;
|
const expected = browseDefinitions[1]._links.items.href + '?filterValue=' + encodeURIComponent(mockAuthorName);
|
||||||
|
|
||||||
scheduler.schedule(() => service.getBrowseItemsFor(mockAuthorName, new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe());
|
scheduler.schedule(() => service.getBrowseItemsFor(mockAuthorName, new BrowseEntrySearchOptions(browseDefinitions[1].id)).subscribe());
|
||||||
scheduler.flush();
|
scheduler.flush();
|
||||||
|
@@ -130,7 +130,7 @@ export class BrowseService {
|
|||||||
args.push(`startsWith=${options.startsWith}`);
|
args.push(`startsWith=${options.startsWith}`);
|
||||||
}
|
}
|
||||||
if (isNotEmpty(filterValue)) {
|
if (isNotEmpty(filterValue)) {
|
||||||
args.push(`filterValue=${filterValue}`);
|
args.push(`filterValue=${encodeURIComponent(filterValue)}`);
|
||||||
}
|
}
|
||||||
if (isNotEmpty(args)) {
|
if (isNotEmpty(args)) {
|
||||||
href = new URLCombiner(href, `?${args.join('&')}`).toString();
|
href = new URLCombiner(href, `?${args.join('&')}`).toString();
|
||||||
|
@@ -163,8 +163,8 @@ export class DSpaceObject extends ListableObject implements CacheableObject {
|
|||||||
* Find metadata on a specific field and order all of them using their "place" property.
|
* Find metadata on a specific field and order all of them using their "place" property.
|
||||||
* @param key
|
* @param key
|
||||||
*/
|
*/
|
||||||
findMetadataSortedByPlace(key: string): MetadataValue[] {
|
findMetadataSortedByPlace(keyOrKeys: string | string[]): MetadataValue[] {
|
||||||
return this.allMetadata([key]).sort((a: MetadataValue, b: MetadataValue) => {
|
return this.allMetadata(keyOrKeys).sort((a: MetadataValue, b: MetadataValue) => {
|
||||||
if (hasNoValue(a.place) && hasNoValue(b.place)) {
|
if (hasNoValue(a.place) && hasNoValue(b.place)) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@@ -22,7 +22,7 @@ import { isNotEmpty } from '../../shared/empty.util';
|
|||||||
@dataService(SUBMISSION_CC_LICENSE_URL)
|
@dataService(SUBMISSION_CC_LICENSE_URL)
|
||||||
export class SubmissionCcLicenseUrlDataService extends DataService<SubmissionCcLicenceUrl> {
|
export class SubmissionCcLicenseUrlDataService extends DataService<SubmissionCcLicenceUrl> {
|
||||||
|
|
||||||
protected linkPath = 'submissioncclicenseUrl-search';
|
protected linkPath = 'submissioncclicenseUrls-search';
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected comparator: DefaultChangeAnalyzer<SubmissionCcLicenceUrl>,
|
protected comparator: DefaultChangeAnalyzer<SubmissionCcLicenceUrl>,
|
||||||
|
@@ -18,7 +18,7 @@
|
|||||||
<ds-metadata-representation-list
|
<ds-metadata-representation-list
|
||||||
[parentItem]="object"
|
[parentItem]="object"
|
||||||
[itemType]="'OrgUnit'"
|
[itemType]="'OrgUnit'"
|
||||||
[metadataField]="'project.contributor.other'"
|
[metadataFields]="['project.contributor.other']"
|
||||||
[label]="'project.page.contributor' | translate">
|
[label]="'project.page.contributor' | translate">
|
||||||
</ds-metadata-representation-list>
|
</ds-metadata-representation-list>
|
||||||
<ds-generic-item-page-field [item]="object"
|
<ds-generic-item-page-field [item]="object"
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<div class="outer-wrapper" *ngIf="isNotAuthBlocking; else authLoader">
|
<div class="outer-wrapper" *ngIf="!shouldShowFullscreenLoader; else fullScreenLoader">
|
||||||
<ds-admin-sidebar></ds-admin-sidebar>
|
<ds-admin-sidebar></ds-admin-sidebar>
|
||||||
<div class="inner-wrapper" [@slideSidebarPadding]="{
|
<div class="inner-wrapper" [@slideSidebarPadding]="{
|
||||||
value: (!(sidebarVisible | async) ? 'hidden' : (slideSidebarOver | async) ? 'shown' : 'expanded'),
|
value: (!(sidebarVisible | async) ? 'hidden' : (slideSidebarOver | async) ? 'shown' : 'expanded'),
|
||||||
@@ -12,10 +12,10 @@
|
|||||||
<main class="main-content">
|
<main class="main-content">
|
||||||
<ds-themed-breadcrumbs></ds-themed-breadcrumbs>
|
<ds-themed-breadcrumbs></ds-themed-breadcrumbs>
|
||||||
|
|
||||||
<div class="container d-flex justify-content-center align-items-center h-100" *ngIf="isLoading">
|
<div class="container d-flex justify-content-center align-items-center h-100" *ngIf="shouldShowRouteLoader">
|
||||||
<ds-loading [showMessage]="false"></ds-loading>
|
<ds-loading [showMessage]="false"></ds-loading>
|
||||||
</div>
|
</div>
|
||||||
<div [class.d-none]="isLoading">
|
<div [class.d-none]="shouldShowRouteLoader">
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
@@ -23,8 +23,8 @@
|
|||||||
<ds-themed-footer></ds-themed-footer>
|
<ds-themed-footer></ds-themed-footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ng-template #authLoader>
|
<ng-template #fullScreenLoader>
|
||||||
<div class="text-center ds-full-screen-loader d-flex align-items-center flex-column justify-content-center">
|
<div class="ds-full-screen-loader">
|
||||||
<ds-loading [showMessage]="false"></ds-loading>
|
<ds-loading [showMessage]="false"></ds-loading>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@@ -27,6 +27,7 @@ import { provideMockStore } from '@ngrx/store/testing';
|
|||||||
import { RouteService } from '../core/services/route.service';
|
import { RouteService } from '../core/services/route.service';
|
||||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||||
import { MenuServiceStub } from '../shared/testing/menu-service.stub';
|
import { MenuServiceStub } from '../shared/testing/menu-service.stub';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
|
||||||
describe('RootComponent', () => {
|
describe('RootComponent', () => {
|
||||||
let component: RootComponent;
|
let component: RootComponent;
|
||||||
@@ -36,6 +37,7 @@ describe('RootComponent', () => {
|
|||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
NoopAnimationsModule,
|
||||||
StoreModule.forRoot(authReducer, storeModuleConfig),
|
StoreModule.forRoot(authReducer, storeModuleConfig),
|
||||||
TranslateModule.forRoot({
|
TranslateModule.forRoot({
|
||||||
loader: {
|
loader: {
|
||||||
|
@@ -38,14 +38,14 @@ export class RootComponent implements OnInit {
|
|||||||
models;
|
models;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the authentication is currently blocking the UI
|
* Whether or not to show a full screen loader
|
||||||
*/
|
*/
|
||||||
@Input() isNotAuthBlocking: boolean;
|
@Input() shouldShowFullscreenLoader: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the the application is loading;
|
* Whether or not to show a loader across the router outlet
|
||||||
*/
|
*/
|
||||||
@Input() isLoading: boolean;
|
@Input() shouldShowRouteLoader: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
||||||
|
@@ -11,14 +11,14 @@ export class ThemedRootComponent extends ThemedComponent<RootComponent> {
|
|||||||
/**
|
/**
|
||||||
* Whether or not the authentication is currently blocking the UI
|
* Whether or not the authentication is currently blocking the UI
|
||||||
*/
|
*/
|
||||||
@Input() isNotAuthBlocking: boolean;
|
@Input() shouldShowFullscreenLoader: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the the application is loading;
|
* Whether or not the the application is loading;
|
||||||
*/
|
*/
|
||||||
@Input() isLoading: boolean;
|
@Input() shouldShowRouteLoader: boolean;
|
||||||
|
|
||||||
protected inAndOutputNames: (keyof RootComponent & keyof this)[] = ['isLoading', 'isNotAuthBlocking'];
|
protected inAndOutputNames: (keyof RootComponent & keyof this)[] = ['shouldShowRouteLoader', 'shouldShowFullscreenLoader'];
|
||||||
|
|
||||||
protected getComponentName(): string {
|
protected getComponentName(): string {
|
||||||
return 'RootComponent';
|
return 'RootComponent';
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,6 @@
|
|||||||
<title>DSpace</title>
|
<title>DSpace</title>
|
||||||
<meta name="viewport" content="width=device-width,minimum-scale=1">
|
<meta name="viewport" content="width=device-width,minimum-scale=1">
|
||||||
<link rel="icon" type="image/x-icon" href="assets/images/favicon.ico" />
|
<link rel="icon" type="image/x-icon" href="assets/images/favicon.ico" />
|
||||||
<link class="theme-css" rel="stylesheet" href="/dspace-theme.css">
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
@@ -40,10 +40,6 @@ ds-admin-sidebar {
|
|||||||
z-index: var(--ds-sidebar-z-index);
|
z-index: var(--ds-sidebar-z-index);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ds-full-screen-loader {
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sticky-top {
|
.sticky-top {
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
8
src/styles/startup.scss
Normal file
8
src/styles/startup.scss
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.ds-full-screen-loader {
|
||||||
|
height: 100vh;
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
@@ -3,7 +3,7 @@
|
|||||||
<div class="jumbotron jumbotron-fluid">
|
<div class="jumbotron jumbotron-fluid">
|
||||||
<div class="d-flex flex-wrap">
|
<div class="d-flex flex-wrap">
|
||||||
<div>
|
<div>
|
||||||
<h1 class="display-3">DSpace 7 - Beta 5</h1>
|
<h1 class="display-3">DSpace 7</h1>
|
||||||
<p class="lead">DSpace is the world leading open source repository platform that enables
|
<p class="lead">DSpace is the world leading open source repository platform that enables
|
||||||
organisations to:</p>
|
organisations to:</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -20,10 +20,7 @@
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>Join an international community of <a href="https://wiki.lyrasis.org/display/DSPACE/DSpace+Positioning" target="_blank">leading institutions using DSpace</a>.</p>
|
<p>Join an international community of <a href="https://wiki.lyrasis.org/display/DSPACE/DSpace+Positioning" target="_blank">leading institutions using DSpace</a>.</p>
|
||||||
<p>Participate in the <a href="https://wiki.lyrasis.org/display/DSPACE/DSpace+Release+7.0+Testathon+Page"
|
<p>The test user accounts below have their password set to the name of this
|
||||||
target="_blank">official community Testathon</a>
|
|
||||||
from <strong>April 19th through May 7th</strong>. The test user accounts below have their password set to the name of
|
|
||||||
this
|
|
||||||
software in lowercase.</p>
|
software in lowercase.</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Demo Site Administrator = dspacedemo+admin@gmail.com</li>
|
<li>Demo Site Administrator = dspacedemo+admin@gmail.com</li>
|
||||||
|
Reference in New Issue
Block a user