1
0

Merge branch 'main' into iiif-mirador

This commit is contained in:
Michael Spalti
2021-04-02 14:41:50 -07:00
175 changed files with 1288 additions and 653 deletions

View File

@@ -1,3 +1,7 @@
{ {
"typescript.check.workspaceVersion": false "typescript.check.workspaceVersion": false,
"i18n-ally.localesPaths": [
"src/assets/i18n",
"src/app/core/locale"
]
} }

View File

@@ -14,22 +14,22 @@ export class ProtractorPage {
} }
getCurrentQuery(): promise.Promise<string> { getCurrentQuery(): promise.Promise<string> {
return element(by.css('#search-navbar-container form input')).getAttribute('value'); return element(by.css('.navbar-container #search-navbar-container form input')).getAttribute('value');
} }
expandAndFocusSearchBox() { expandAndFocusSearchBox() {
element(by.css('#search-navbar-container form a')).click(); element(by.css('.navbar-container #search-navbar-container form a')).click();
} }
setCurrentQuery(query: string) { setCurrentQuery(query: string) {
element(by.css('#search-navbar-container form input[name="query"]')).sendKeys(query); element(by.css('.navbar-container #search-navbar-container form input[name="query"]')).sendKeys(query);
} }
submitNavbarSearchForm() { submitNavbarSearchForm() {
element(by.css('#search-navbar-container form .submit-icon')).click(); element(by.css('.navbar-container #search-navbar-container form .submit-icon')).click();
} }
submitByPressingEnter() { submitByPressingEnter() {
element(by.css('#search-navbar-container form input[name="query"]')).sendKeys(protractor.Key.ENTER); element(by.css('.navbar-container #search-navbar-container form input[name="query"]')).sendKeys(protractor.Key.ENTER);
} }
} }

View File

@@ -129,7 +129,7 @@
"rxjs": "^6.6.3", "rxjs": "^6.6.3",
"rxjs-spy": "^7.5.3", "rxjs-spy": "^7.5.3",
"sass-resources-loader": "^2.1.1", "sass-resources-loader": "^2.1.1",
"sortablejs": "1.10.1", "sortablejs": "1.13.0",
"tslib": "^2.0.0", "tslib": "^2.0.0",
"webfontloader": "1.6.28", "webfontloader": "1.6.28",
"zone.js": "^0.10.3", "zone.js": "^0.10.3",

View File

@@ -61,7 +61,7 @@ export class ItemAdminSearchResultGridElementComponent extends SearchResultGridE
} }
/** /**
* Fetch the component depending on the item's relationship type, view mode and context * Fetch the component depending on the item's entity type, view mode and context
* @returns {GenericConstructor<Component>} * @returns {GenericConstructor<Component>}
*/ */
private getComponent(): GenericConstructor<Component> { private getComponent(): GenericConstructor<Component> {

View File

@@ -1,27 +1,28 @@
<a [ngClass]="{'btn-sm': small}" class="btn btn-light my-1 edit-link" [routerLink]="[getEditRoute()]" [title]="'admin.search.item.edit' | translate"> <a [ngClass]="{'btn-sm': small}" class="btn btn-secondary my-1 move-link" [routerLink]="[getMoveRoute()]" [title]="'admin.search.item.move' | translate">
<i class="fa fa-edit"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.edit" | translate}}</span> <i class="fa fa-arrow-circle-right"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.move" | translate}}</span>
</a> </a>
<a [ngClass]="{'btn-sm': small}" *ngIf="item && !item.isWithdrawn" class="btn btn-light my-1 withdraw-link" [routerLink]="[getWithdrawRoute()]" [title]="'admin.search.item.withdraw' | translate"> <a [ngClass]="{'btn-sm': small}" *ngIf="item && item.isDiscoverable" class="btn btn-secondary my-1 private-link" [routerLink]="[getPrivateRoute()]" [title]="'admin.search.item.make-private' | translate">
<i class="fa fa-ban"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.withdraw" | translate}}</span>
</a>
<a [ngClass]="{'btn-sm': small}" *ngIf="item && item.isWithdrawn" class="btn btn-light my-1 reinstate-link" [routerLink]="[getReinstateRoute()]" [title]="'admin.search.item.reinstate' | translate">
<i class="fa fa-undo"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.reinstate" | translate}}</span>
</a>
<a [ngClass]="{'btn-sm': small}" *ngIf="item && item.isDiscoverable" class="btn btn-light my-1 private-link" [routerLink]="[getPrivateRoute()]" [title]="'admin.search.item.make-private' | translate">
<i class="fa fa-eye-slash"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.make-private" | translate}}</span> <i class="fa fa-eye-slash"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.make-private" | translate}}</span>
</a> </a>
<a [ngClass]="{'btn-sm': small}" *ngIf="item && !item.isDiscoverable" class="btn btn-light my-1 public-link" [routerLink]="[getPublicRoute()]" [title]="'admin.search.item.make-public' | translate"> <a [ngClass]="{'btn-sm': small}" *ngIf="item && !item.isDiscoverable" class="btn btn-secondary my-1 public-link" [routerLink]="[getPublicRoute()]" [title]="'admin.search.item.make-public' | translate">
<i class="fa fa-eye"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.make-public" | translate}}</span> <i class="fa fa-eye"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.make-public" | translate}}</span>
</a> </a>
<a [ngClass]="{'btn-sm': small}" class="btn btn-light my-1 delete-link" [routerLink]="[getDeleteRoute()]" [title]="'admin.search.item.delete' | translate"> <a [ngClass]="{'btn-sm': small}" class="btn btn-secondary my-1 edit-link" [routerLink]="[getEditRoute()]" [title]="'admin.search.item.edit' | translate">
<i class="fa fa-edit"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.edit" | translate}}</span>
</a>
<a [ngClass]="{'btn-sm': small}" *ngIf="item && !item.isWithdrawn" class="btn btn-warning t my-1 withdraw-link" [routerLink]="[getWithdrawRoute()]" [title]="'admin.search.item.withdraw' | translate">
<i class="fa fa-ban"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.withdraw" | translate}}</span>
</a>
<a [ngClass]="{'btn-sm': small}" *ngIf="item && item.isWithdrawn" class="btn btn-warning my-1 reinstate-link" [routerLink]="[getReinstateRoute()]" [title]="'admin.search.item.reinstate' | translate">
<i class="fa fa-undo"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.reinstate" | translate}}</span>
</a>
<a [ngClass]="{'btn-sm': small}" class="btn btn-danger my-1 delete-link" [routerLink]="[getDeleteRoute()]" [title]="'admin.search.item.delete' | translate">
<i class="fa fa-trash"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.delete" | translate}}</span> <i class="fa fa-trash"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.delete" | translate}}</span>
</a> </a>
<a [ngClass]="{'btn-sm': small}" class="btn btn-light my-1 move-link" [routerLink]="[getMoveRoute()]" [title]="'admin.search.item.move' | translate">
<i class="fa fa-arrow-circle-right"></i><span *ngIf="!small" class="d-none d-sm-inline"> {{"admin.search.item.move" | translate}}</span>
</a>

View File

@@ -1,11 +1,11 @@
<li class="sidebar-section"> <li class="sidebar-section">
<a class="nav-item nav-link shortcut-icon" [routerLink]="itemModel.link"> <a class="nav-item nav-link shortcut-icon" attr.aria-labelledby="sidebarName-{{section.id}}" [title]="('menu.section.icon.' + section.id) | translate" [routerLink]="itemModel.link">
<i class="fas fa-{{section.icon}} fa-fw" [title]="('menu.section.icon.' + section.id) | translate"></i> <i class="fas fa-{{section.icon}} fa-fw"></i>
</a> </a>
<div class="sidebar-collapsible"> <div class="sidebar-collapsible">
<span class="section-header-text"> <span id="sidebarName-{{section.id}}" class="section-header-text">
<ng-container <ng-container
*ngComponentOutlet="(sectionMap$ | async).get(section.id).component; injector: (sectionMap$ | async).get(section.id).injector;"></ng-container> *ngComponentOutlet="(sectionMap$ | async).get(section.id).component; injector: (sectionMap$ | async).get(section.id).injector;"></ng-container>
</span> </span>
</div> </div>
</li> </li>

View File

@@ -1,11 +1,12 @@
<nav @slideHorizontal class="navbar navbar-dark p-0" <nav @slideHorizontal class="navbar navbar-dark p-0"
[ngClass]="{'active': sidebarOpen, 'inactive': sidebarClosed}" [ngClass]="{'active': sidebarOpen, 'inactive': sidebarClosed}"
[@slideSidebar]="{ [@slideSidebar]="{
value: (!(sidebarExpanded | async) ? 'collapsed' : 'expanded'), value: (!(sidebarExpanded | async) ? 'collapsed' : 'expanded'),
params: {sidebarWidth: (sidebarWidth | async)} params: {sidebarWidth: (sidebarWidth | async)}
}" (@slideSidebar.done)="finishSlide($event)" (@slideSidebar.start)="startSlide($event)" }" (@slideSidebar.done)="finishSlide($event)" (@slideSidebar.start)="startSlide($event)"
*ngIf="menuVisible | async" (mouseenter)="expandPreview($event)" *ngIf="menuVisible | async" (mouseenter)="expandPreview($event)"
(mouseleave)="collapsePreview($event)"> (mouseleave)="collapsePreview($event)"
role="navigation" [attr.aria-label]="'menu.header.admin.description' |translate">
<div class="sidebar-top-level-items"> <div class="sidebar-top-level-items">
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="admin-menu-header sidebar-section"> <li class="admin-menu-header sidebar-section">

View File

@@ -126,7 +126,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
type: MenuItemType.TEXT, type: MenuItemType.TEXT,
text: 'menu.section.new' text: 'menu.section.new'
} as TextMenuItemModel, } as TextMenuItemModel,
icon: 'plus-circle', icon: 'plus',
index: 0 index: 0
}, },
{ {
@@ -320,7 +320,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
type: MenuItemType.TEXT, type: MenuItemType.TEXT,
text: 'menu.section.export' text: 'menu.section.export'
} as TextMenuItemModel, } as TextMenuItemModel,
icon: 'sign-out-alt', icon: 'file-export',
index: 3, index: 3,
shouldPersistOnRouteChange: true shouldPersistOnRouteChange: true
}, },
@@ -403,7 +403,7 @@ export class AdminSidebarComponent extends MenuComponent implements OnInit {
type: MenuItemType.TEXT, type: MenuItemType.TEXT,
text: 'menu.section.import' text: 'menu.section.import'
} as TextMenuItemModel, } as TextMenuItemModel,
icon: 'sign-in-alt', icon: 'file-import',
index: 2 index: 2
}, },
{ {

View File

@@ -3,14 +3,14 @@
value: ((expanded | async) ? 'endBackground' : 'startBackground'), value: ((expanded | async) ? 'endBackground' : 'startBackground'),
params: {endColor: (sidebarActiveBg | async)}}"> params: {endColor: (sidebarActiveBg | async)}}">
<div class="icon-wrapper"> <div class="icon-wrapper">
<a class="nav-item nav-link shortcut-icon" (click)="toggleSection($event)" href="#"> <a class="nav-item nav-link shortcut-icon" attr.aria.labelledby="sidebarName-{{section.id}}" [title]="('menu.section.icon.' + section.id) | translate" (click)="toggleSection($event)" href="#">
<i class="fas fa-{{section.icon}} fa-fw" [title]="('menu.section.icon.' + section.id) | translate"></i> <i class="fas fa-{{section.icon}} fa-fw"></i>
</a> </a>
</div> </div>
<div class="sidebar-collapsible"> <div class="sidebar-collapsible">
<a class="nav-item nav-link" href="#" <a class="nav-item nav-link" href="#"
(click)="toggleSection($event)"> (click)="toggleSection($event)">
<span class="section-header-text"> <span id="sidebarName-{{section.id}}" class="section-header-text">
<ng-container <ng-container
*ngComponentOutlet="(sectionMap$ | async).get(section.id).component; injector: (sectionMap$ | async).get(section.id).injector;"></ng-container> *ngComponentOutlet="(sectionMap$ | async).get(section.id).component; injector: (sectionMap$ | async).get(section.id).injector;"></ng-container>
</span> </span>

View File

@@ -16,4 +16,8 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
li.sidebar-section.expanded {
background-color: var(--ds-admin-sidebar-active-bg) !important;
}
} }

View File

@@ -96,7 +96,7 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S
} }
/** /**
* Fetch the component depending on the item's relationship type, view mode and context * Fetch the component depending on the item's entity type, view mode and context
* @returns {GenericConstructor<Component>} * @returns {GenericConstructor<Component>}
*/ */
private getComponent(item: Item): GenericConstructor<Component> { private getComponent(item: Item): GenericConstructor<Component> {

View File

@@ -7,7 +7,6 @@ import {
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
import { Collection } from '../../core/shared/collection.model'; import { Collection } from '../../core/shared/collection.model';
import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component'; import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component';
import { Location } from '@angular/common';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
import { CommunityDataService } from '../../core/data/community-data.service'; import { CommunityDataService } from '../../core/data/community-data.service';
@@ -76,14 +75,13 @@ export class CollectionFormComponent extends ComColFormComponent<Collection> {
}), }),
]; ];
public constructor(protected location: Location, public constructor(protected formService: DynamicFormService,
protected formService: DynamicFormService,
protected translate: TranslateService, protected translate: TranslateService,
protected notificationsService: NotificationsService, protected notificationsService: NotificationsService,
protected authService: AuthService, protected authService: AuthService,
protected dsoService: CommunityDataService, protected dsoService: CommunityDataService,
protected requestService: RequestService, protected requestService: RequestService,
protected objectCache: ObjectCacheService) { protected objectCache: ObjectCacheService) {
super(location, formService, translate, notificationsService, authService, requestService, objectCache); super(formService, translate, notificationsService, authService, requestService, objectCache);
} }
} }

View File

@@ -31,6 +31,7 @@
[scope]="(searchOptions$ | async)?.scope" [scope]="(searchOptions$ | async)?.scope"
[currentUrl]="'./'" [currentUrl]="'./'"
[inPlaceSearch]="true" [inPlaceSearch]="true"
[searchPlaceholder]="'collection.edit.item-mapper.search-form.placeholder' | translate"
(submitSearch)="performedSearch = true"> (submitSearch)="performedSearch = true">
</ds-search-form> </ds-search-form>
</div> </div>

View File

@@ -4,5 +4,7 @@
<h2 id="sub-header" class="border-bottom pb-2">{{'collection.create.sub-head' | translate:{ parent: (parentRD$| async)?.payload.name } }}</h2> <h2 id="sub-header" class="border-bottom pb-2">{{'collection.create.sub-head' | translate:{ parent: (parentRD$| async)?.payload.name } }}</h2>
</div> </div>
</div> </div>
<ds-collection-form (submitForm)="onSubmit($event)" (finish)="navigateToNewPage()"></ds-collection-form> <ds-collection-form (submitForm)="onSubmit($event)"
(back)="navigateToHome()"
(finish)="navigateToNewPage()"></ds-collection-form>
</div> </div>

View File

@@ -2,15 +2,18 @@
<div class="row"> <div class="row">
<ng-container *ngVar="(dsoRD$ | async)?.payload as dso"> <ng-container *ngVar="(dsoRD$ | async)?.payload as dso">
<div class="col-12 pb-4"> <div class="col-12 pb-4">
<h2 id="header" class="border-bottom pb-2">{{ 'community.delete.head' | translate <h2 id="header" class="border-bottom pb-2">{{ 'community.delete.head' | translate}}</h2>
}}</h2>
<p class="pb-2">{{ 'community.delete.text' | translate:{ dso: dso.name } }}</p> <p class="pb-2">{{ 'community.delete.text' | translate:{ dso: dso.name } }}</p>
<button class="btn btn-primary mr-2" (click)="onConfirm(dso)"> <div class="form-group row">
{{'community.delete.confirm' | <div class="col text-right">
translate}} <button class="btn btn-outline-secondary" (click)="onCancel(dso)">
</button> <i class="fas fa-times"></i> {{'community.delete.cancel' | translate}}
<button class="btn btn-primary" (click)="onCancel(dso)">{{'community.delete.cancel' | translate}} </button>
</button> <button class="btn btn-danger mr-2" (click)="onConfirm(dso)">
<i class="fas fa-trash"></i> {{'community.delete.confirm' | translate}}
</button>
</div>
</div>
</div> </div>
</ng-container> </ng-container>

View File

@@ -16,9 +16,7 @@
</button> </button>
</div> </div>
</div> </div>
<ds-collection-form (submitForm)="onSubmit($event)" <ds-collection-form [dso]="(dsoRD$ | async)?.payload"
[dso]="(dsoRD$ | async)?.payload" (submitForm)="onSubmit($event)"
(back)="navigateToHomePage()"
(finish)="navigateToHomePage()"></ds-collection-form> (finish)="navigateToHomePage()"></ds-collection-form>
<a class="btn btn-danger"
[routerLink]="'/collections/' + (dsoRD$ | async)?.payload.uuid + '/delete'">{{'collection.edit.delete'
| translate}}</a>

View File

@@ -11,6 +11,7 @@ import { GroupDataService } from '../../../core/eperson/group-data.service';
import { RequestService } from '../../../core/data/request.service'; import { RequestService } from '../../../core/data/request.service';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
describe('CollectionRolesComponent', () => { describe('CollectionRolesComponent', () => {
@@ -67,6 +68,7 @@ describe('CollectionRolesComponent', () => {
SharedModule, SharedModule,
RouterTestingModule.withRoutes([]), RouterTestingModule.withRoutes([]),
TranslateModule.forRoot(), TranslateModule.forRoot(),
NoopAnimationsModule
], ],
declarations: [ declarations: [
CollectionRolesComponent, CollectionRolesComponent,

View File

@@ -31,6 +31,7 @@
[formModel]="formModel" [formModel]="formModel"
[formLayout]="formLayout" [formLayout]="formLayout"
[displaySubmit]="false" [displaySubmit]="false"
[displayCancel]="false"
(dfChange)="onChange($event)" (dfChange)="onChange($event)"
(submitForm)="onSubmit()" (submitForm)="onSubmit()"
(cancel)="onCancel()"></ds-form> (cancel)="onCancel()"></ds-form>

View File

@@ -18,7 +18,14 @@ describe('EditCollectionPageComponent', () => {
dso: { payload: {} } dso: { payload: {} }
}), }),
routeConfig: { routeConfig: {
children: [] children: [
{
path: 'mockUrl',
data: {
hideReturnButton: false
}
}
]
}, },
snapshot: { snapshot: {
firstChild: { firstChild: {

View File

@@ -93,7 +93,7 @@ import { CollectionAdministratorGuard } from '../../core/data/feature-authorizat
{ {
path: 'mapper', path: 'mapper',
component: CollectionItemMapperComponent, component: CollectionItemMapperComponent,
data: { title: 'collection.edit.tabs.item-mapper.title', showBreadcrumbs: true } data: { title: 'collection.edit.tabs.item-mapper.title', hideReturnButton: true, showBreadcrumbs: true }
}, },
] ]
} }

View File

@@ -7,7 +7,6 @@ import {
} from '@ng-dynamic-forms/core'; } from '@ng-dynamic-forms/core';
import { Community } from '../../core/shared/community.model'; import { Community } from '../../core/shared/community.model';
import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component'; import { ComColFormComponent } from '../../shared/comcol-forms/comcol-form/comcol-form.component';
import { Location } from '@angular/common';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { NotificationsService } from '../../shared/notifications/notifications.service'; import { NotificationsService } from '../../shared/notifications/notifications.service';
import { CommunityDataService } from '../../core/data/community-data.service'; import { CommunityDataService } from '../../core/data/community-data.service';
@@ -68,14 +67,13 @@ export class CommunityFormComponent extends ComColFormComponent<Community> {
}), }),
]; ];
public constructor(protected location: Location, public constructor(protected formService: DynamicFormService,
protected formService: DynamicFormService,
protected translate: TranslateService, protected translate: TranslateService,
protected notificationsService: NotificationsService, protected notificationsService: NotificationsService,
protected authService: AuthService, protected authService: AuthService,
protected dsoService: CommunityDataService, protected dsoService: CommunityDataService,
protected requestService: RequestService, protected requestService: RequestService,
protected objectCache: ObjectCacheService) { protected objectCache: ObjectCacheService) {
super(location, formService, translate, notificationsService, authService, requestService, objectCache); super(formService, translate, notificationsService, authService, requestService, objectCache);
} }
} }

View File

@@ -7,5 +7,7 @@
</ng-container> </ng-container>
</div> </div>
</div> </div>
<ds-community-form (submitForm)="onSubmit($event)" (finish)="navigateToNewPage()"></ds-community-form> <ds-community-form (submitForm)="onSubmit($event)"
(back)="navigateToHome()"
(finish)="navigateToNewPage()"></ds-community-form>
</div> </div>

View File

@@ -2,18 +2,20 @@
<div class="row"> <div class="row">
<ng-container *ngVar="(dsoRD$ | async)?.payload as dso"> <ng-container *ngVar="(dsoRD$ | async)?.payload as dso">
<div class="col-12 pb-4"> <div class="col-12 pb-4">
<h2 id="header" class="border-bottom pb-2">{{ 'community.delete.head' | translate <h2 id="header" class="border-bottom pb-2">{{ 'community.delete.head' | translate}}</h2>
}}</h2>
<p class="pb-2">{{ 'community.delete.text' | translate:{ dso: dso.name } }}</p> <p class="pb-2">{{ 'community.delete.text' | translate:{ dso: dso.name } }}</p>
<button class="btn btn-primary mr-2" (click)="onConfirm(dso)"> <div class="form-group row">
{{'community.delete.confirm' | <div class="col text-right">
translate}} <button class="btn btn-outline-secondary" (click)="onCancel(dso)">
</button> <i class="fas fa-times"></i> {{'community.delete.cancel' | translate}}
<button class="btn btn-primary" (click)="onCancel(dso)">{{'community.delete.cancel' | translate}} </button>
</button> <button class="btn btn-danger mr-2" (click)="onConfirm(dso)">
<i class="fas fa-trash"></i> {{'community.delete.confirm' | translate}}
</button>
</div>
</div>
</div> </div>
</ng-container> </ng-container>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,5 @@
<ds-community-form (submitForm)="onSubmit($event)" <ds-community-form [dso]="(dsoRD$ | async)?.payload"
[dso]="(dsoRD$ | async)?.payload" (submitForm)="onSubmit($event)"
(back)="navigateToHomePage()"
(finish)="navigateToHomePage()"></ds-community-form> (finish)="navigateToHomePage()"></ds-community-form>
<a class="btn btn-danger"
[routerLink]="'/communities/' + (dsoRD$ | async)?.payload.uuid + '/delete'">{{'community.edit.delete'
| translate}}</a>

View File

@@ -11,6 +11,7 @@ import { GroupDataService } from '../../../core/eperson/group-data.service';
import { SharedModule } from '../../../shared/shared.module'; import { SharedModule } from '../../../shared/shared.module';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils'; import { createSuccessfulRemoteDataObject, createSuccessfulRemoteDataObject$ } from '../../../shared/remote-data.utils';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
describe('CommunityRolesComponent', () => { describe('CommunityRolesComponent', () => {
@@ -52,6 +53,7 @@ describe('CommunityRolesComponent', () => {
SharedModule, SharedModule,
RouterTestingModule.withRoutes([]), RouterTestingModule.withRoutes([]),
TranslateModule.forRoot(), TranslateModule.forRoot(),
NoopAnimationsModule
], ],
declarations: [ declarations: [
CommunityRolesComponent, CommunityRolesComponent,

View File

@@ -18,7 +18,14 @@ describe('EditCommunityPageComponent', () => {
dso: { payload: {} } dso: { payload: {} }
}), }),
routeConfig: { routeConfig: {
children: [] children: [
{
path: 'mockUrl',
data: {
hideReturnButton: false
}
}
]
}, },
snapshot: { snapshot: {
firstChild: { firstChild: {

View File

@@ -1,7 +1,6 @@
<div class="jumbotron jumbotron-fluid"> <div class="jumbotron jumbotron-fluid">
<div class="container"> <div class="container">
<div class="d-flex flex-wrap"> <div class="d-flex flex-wrap">
<img class="mr-4 dspace-logo" src="assets/images/dspace-logo.svg" alt="" />
<div> <div>
<h1 class="display-3">Welcome to the DSpace 7 Preview</h1> <h1 class="display-3">Welcome to the DSpace 7 Preview</h1>
<p class="lead">DSpace is the world leading open source repository platform that enables organisations to:</p> <p class="lead">DSpace is the world leading open source repository platform that enables organisations to:</p>

View File

@@ -8,7 +8,14 @@
word-break: break-word; word-break: break-word;
} }
.dspace-logo { .jumbotron {
height: 110px; background-color: var(--ds-home-news-background-color);
width: 110px; }
a {
color: var(--ds-home-news-link-color);
@include hover {
color: var(--ds-home-news-link-hover-color);
}
} }

View File

@@ -3,6 +3,6 @@
<ng-container *ngIf="(site$ | async) as site"> <ng-container *ngIf="(site$ | async) as site">
<ds-view-tracker [object]="site"></ds-view-tracker> <ds-view-tracker [object]="site"></ds-view-tracker>
</ng-container> </ng-container>
<ds-search-form [inPlaceSearch]="false"></ds-search-form> <ds-search-form [inPlaceSearch]="false" [searchPlaceholder]="'home.search-form.placeholder' | translate"></ds-search-form>
<ds-top-level-community-list></ds-top-level-community-list> <ds-top-level-community-list></ds-top-level-community-list>
</div> </div>

View File

@@ -23,7 +23,11 @@
<div class="mb-4"> <div class="mb-4">
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div> </div>
<a [routerLink]="getItemPage((itemRD$ | async)?.payload)" class="btn btn-outline-secondary">Cancel</a> <div class="button-row bottom">
<div class="text-right">
<a [routerLink]="getItemPage((itemRD$ | async)?.payload)" role="button" class="btn btn-outline-secondary mr-1"><i class="fas fa-arrow-left"></i> {{'item.edit.return' | translate}}</a>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -39,12 +39,11 @@ export class EditItemPageComponent implements OnInit {
pages: { page: string, enabled: Observable<boolean> }[]; pages: { page: string, enabled: Observable<boolean> }[];
constructor(private route: ActivatedRoute, private router: Router, private injector: Injector) { constructor(private route: ActivatedRoute, private router: Router, private injector: Injector) {
this.router.events.subscribe(() => { this.router.events.subscribe(() => this.initPageParamsByRoute());
this.currentPage = this.route.snapshot.firstChild.routeConfig.path;
});
} }
ngOnInit(): void { ngOnInit(): void {
this.initPageParamsByRoute();
this.pages = this.route.routeConfig.children this.pages = this.route.routeConfig.children
.filter((child: Route) => isNotEmpty(child.path)) .filter((child: Route) => isNotEmpty(child.path))
.map((child: Route) => { .map((child: Route) => {
@@ -70,4 +69,11 @@ export class EditItemPageComponent implements OnInit {
getItemPage(item: Item): string { getItemPage(item: Item): string {
return getItemPageRoute(item); return getItemPageRoute(item);
} }
/**
* Set page params depending on the route
*/
initPageParamsByRoute() {
this.currentPage = this.route.snapshot.firstChild.routeConfig.path;
}
} }

View File

@@ -5,22 +5,22 @@
class="fas fa-upload"></i> class="fas fa-upload"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.upload-button" | translate}}</span> <span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.upload-button" | translate}}</span>
</button> </button>
<button class="btn btn-danger mr-1" *ngIf="!(isReinstatable() | async)"
[disabled]="!(hasChanges() | async) || submitting"
(click)="discard()"><i
class="fas fa-times"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.discard-button" | translate}}</span>
</button>
<button class="btn btn-warning mr-1" *ngIf="isReinstatable() | async" <button class="btn btn-warning mr-1" *ngIf="isReinstatable() | async"
(click)="reinstate()"><i (click)="reinstate()"><i
class="fas fa-undo-alt"></i> class="fas fa-undo-alt"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.reinstate-button" | translate}}</span> <span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.reinstate-button" | translate}}</span>
</button> </button>
<button class="btn btn-primary" [disabled]="!(hasChanges() | async) || submitting" <button class="btn btn-primary mr-1" [disabled]="!(hasChanges() | async) || submitting"
(click)="submit()"><i (click)="submit()"><i
class="fas fa-save"></i> class="fas fa-save"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.save-button" | translate}}</span> <span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.save-button" | translate}}</span>
</button> </button>
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
[disabled]="!(hasChanges() | async) || submitting"
(click)="discard()"><i
class="fas fa-times"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.discard-button" | translate}}</span>
</button>
</div> </div>
<div *ngIf="item && bundles?.length > 0" class="container table-bordered mt-4"> <div *ngIf="item && bundles?.length > 0" class="container table-bordered mt-4">
@@ -48,12 +48,6 @@
<div class="button-row bottom"> <div class="button-row bottom">
<div class="mt-4 float-right"> <div class="mt-4 float-right">
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
[disabled]="!(hasChanges() | async) || submitting"
(click)="discard()"><i
class="fas fa-times"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.discard-button" | translate}}</span>
</button>
<button class="btn btn-warning" *ngIf="isReinstatable() | async" <button class="btn btn-warning" *ngIf="isReinstatable() | async"
(click)="reinstate()"><i (click)="reinstate()"><i
class="fas fa-undo-alt"></i> class="fas fa-undo-alt"></i>
@@ -64,6 +58,12 @@
class="fas fa-save"></i> class="fas fa-save"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.save-button" | translate}}</span> <span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.save-button" | translate}}</span>
</button> </button>
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
[disabled]="!(hasChanges() | async) || submitting"
(click)="discard()"><i
class="fas fa-times"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.bitstreams.discard-button" | translate}}</span>
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -29,6 +29,7 @@
[query]="(searchOptions$ | async)?.query" [query]="(searchOptions$ | async)?.query"
[currentUrl]="'./'" [currentUrl]="'./'"
[inPlaceSearch]="true" [inPlaceSearch]="true"
[searchPlaceholder]="'item.edit.item-mapper.search-form.placeholder' | translate"
(submitSearch)="performedSearch = true"> (submitSearch)="performedSearch = true">
</ds-search-form> </ds-search-form>
</div> </div>

View File

@@ -112,7 +112,7 @@ export class ItemDeleteComponent
super.ngOnInit(); super.ngOnInit();
this.url = this.router.url; this.url = this.router.url;
const label = this.item.firstMetadataValue('relationship.type'); const label = this.item.firstMetadataValue('dspace.entity.type');
if (label !== undefined) { if (label !== undefined) {
this.types$ = this.entityTypeService.getEntityTypeByLabel(label).pipe( this.types$ = this.entityTypeService.getEntityTypeByLabel(label).pipe(
getFirstSucceededRemoteData(), getFirstSucceededRemoteData(),

View File

@@ -5,12 +5,6 @@
class="fas fa-plus"></i> class="fas fa-plus"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.add-button" | translate}}</span> <span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.add-button" | translate}}</span>
</button> </button>
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
[disabled]="!(hasChanges() | async)"
(click)="discard()"><i
class="fas fa-times"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.discard-button" | translate}}</span>
</button>
<button class="btn btn-warning" *ngIf="isReinstatable() | async" <button class="btn btn-warning" *ngIf="isReinstatable() | async"
(click)="reinstate()"><i (click)="reinstate()"><i
class="fas fa-undo-alt"></i> class="fas fa-undo-alt"></i>
@@ -21,6 +15,12 @@
class="fas fa-save"></i> class="fas fa-save"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.save-button" | translate}}</span> <span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.save-button" | translate}}</span>
</button> </button>
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
[disabled]="!(hasChanges() | async)"
(click)="discard()"><i
class="fas fa-times"></i>
<span class="d-none d-sm-inline">&nbsp;{{"item.edit.metadata.discard-button" | translate}}</span>
</button>
</div> </div>
<table class="table table-responsive table-striped table-bordered" *ngIf="((updates$ | async)| dsObjectValues).length > 0"> <table class="table table-responsive table-striped table-bordered" *ngIf="((updates$ | async)| dsObjectValues).length > 0">
<tbody> <tbody>
@@ -47,20 +47,20 @@
<ds-alert [content]="'item.edit.metadata.empty'" [type]="AlertTypeEnum.Info"></ds-alert> <ds-alert [content]="'item.edit.metadata.empty'" [type]="AlertTypeEnum.Info"></ds-alert>
</div> </div>
<div class="button-row bottom"> <div class="button-row bottom">
<div class="float-right"> <div class="mt-2 float-right">
<button class="btn btn-danger mr-1" *ngIf="!(isReinstatable() | async)" <button class="btn btn-warning" *ngIf="isReinstatable() | async"
(click)="reinstate()"><i
class="fas fa-undo-alt"></i> {{"item.edit.metadata.reinstate-button" | translate}}
</button>
<button class="btn btn-primary mr-0" [disabled]="!(hasChanges() | async)"
(click)="submit()"><i
class="fas fa-save"></i> {{"item.edit.metadata.save-button" | translate}}
</button>
<button class="btn btn-danger" *ngIf="!(isReinstatable() | async)"
[disabled]="!(hasChanges() | async)" [disabled]="!(hasChanges() | async)"
(click)="discard()"><i (click)="discard()"><i
class="fas fa-times"></i> {{"item.edit.metadata.discard-button" | translate}} class="fas fa-times"></i> {{"item.edit.metadata.discard-button" | translate}}
</button> </button>
<button class="btn btn-warning mr-1" *ngIf="isReinstatable() | async"
(click)="reinstate()"><i
class="fas fa-undo-alt"></i> {{"item.edit.metadata.reinstate-button" | translate}}
</button>
<button class="btn btn-primary" [disabled]="!(hasChanges() | async)"
(click)="submit()"><i
class="fas fa-save"></i> {{"item.edit.metadata.save-button" | translate}}
</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -4,7 +4,7 @@
</span> </span>
</div> </div>
<div *ngIf="!operation.disabled" class="col-9 float-left action-button"> <div *ngIf="!operation.disabled" class="col-9 float-left action-button">
<a class="btn btn-outline-secondary" [routerLink]="operation.operationUrl"> <a class="btn btn-outline-primary" [routerLink]="operation.operationUrl">
{{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}} {{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}}
</a> </a>
</div> </div>
@@ -12,4 +12,4 @@
<span class="btn btn-danger"> <span class="btn btn-danger">
{{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}} {{'item.edit.tabs.status.buttons.' + operation.operationKey + '.button' | translate}}
</span> </span>
</div> </div>

View File

@@ -75,7 +75,7 @@ export class ItemRelationshipsComponent extends AbstractItemUpdateComponent {
*/ */
public initializeUpdates(): void { public initializeUpdates(): void {
const label = this.item.firstMetadataValue('relationship.type'); const label = this.item.firstMetadataValue('dspace.entity.type');
if (label !== undefined) { if (label !== undefined) {
this.entityType$ = this.entityTypeService.getEntityTypeByLabel(label).pipe( this.entityType$ = this.entityTypeService.getEntityTypeByLabel(label).pipe(

View File

@@ -10,11 +10,11 @@ export function getItemModuleRoute() {
/** /**
* Get the route to an item's page * Get the route to an item's page
* Depending on the item's relationship type, the route will either start with /items or /entities * Depending on the item's entity type, the route will either start with /items or /entities
* @param item The item to retrieve the route for * @param item The item to retrieve the route for
*/ */
export function getItemPageRoute(item: Item) { export function getItemPageRoute(item: Item) {
const type = item.firstMetadataValue('relationship.type'); const type = item.firstMetadataValue('dspace.entity.type');
return getEntityPageRoute(type, item.uuid); return getEntityPageRoute(type, item.uuid);
} }

View File

@@ -1,5 +1,5 @@
<h2 class="item-page-title-field"> <h2 class="item-page-title-field">
<div *ngIf="item.firstMetadataValue('relationship.type') as type"> <div *ngIf="item.firstMetadataValue('dspace.entity.type') as type">
{{ type.toLowerCase() + '.page.titleprefix' | translate }} {{ type.toLowerCase() + '.page.titleprefix' | translate }}
</div> </div>
<ds-metadata-values [mdValues]="item?.allMetadata(fields)"></ds-metadata-values> <ds-metadata-values [mdValues]="item?.allMetadata(fields)"></ds-metadata-values>

View File

@@ -79,8 +79,8 @@
</ds-item-page-uri-field> </ds-item-page-uri-field>
<ds-item-page-collections [item]="object"></ds-item-page-collections> <ds-item-page-collections [item]="object"></ds-item-page-collections>
<div> <div>
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']"> <a class="btn btn-outline-primary" role="button" [routerLink]="[itemPageRoute + '/full']">
{{"item.page.link.full" | translate}} <i class="fas fa-info-circle"></i> {{"item.page.link.full" | translate}}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -64,8 +64,8 @@
</ds-item-page-uri-field> </ds-item-page-uri-field>
<ds-item-page-collections [item]="object"></ds-item-page-collections> <ds-item-page-collections [item]="object"></ds-item-page-collections>
<div> <div>
<a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']"> <a class="btn btn-outline-primary" [routerLink]="[itemPageRoute + '/full']" role="button">
{{"item.page.link.full" | translate}} <i class="fas fa-info-circle"></i> {{"item.page.link.full" | translate}}
</a> </a>
</div> </div>
</div> </div>

View File

@@ -1,7 +1,7 @@
<div class="container w-100 h-100"> <div class="container w-100 h-100">
<div class="text-center mt-5 row justify-content-center"> <div class="text-center mt-5 row justify-content-center">
<div> <div>
<img class="mb-4 login-logo" src="assets/images/dspace-logo.png"> <img class="mb-4 login-logo" src="assets/images/dspace-logo.png" alt="{{'repository.image.logo' | translate}}">
<h1 class="h3 mb-0 font-weight-normal">{{"login.form.header" | translate}}</h1> <h1 class="h3 mb-0 font-weight-normal">{{"login.form.header" | translate}}</h1>
<ds-log-in <ds-log-in
[isStandalonePage]="true"></ds-log-in> [isStandalonePage]="true"></ds-log-in>

View File

@@ -8,12 +8,12 @@
</div> </div>
<div class="add"> <div class="add">
<button class="btn btn-lg btn-primary mt-1 ml-2" (click)="openDialog()" role="button" title="{{'mydspace.new-submission' | translate}}"> <button class="btn btn-lg btn-primary mt-1 ml-2" (click)="openDialog()" attr.aria-label="'mydspace.new-submission' | translate" title="{{'mydspace.new-submission' | translate}}">
<i class="fa fa-plus-circle" aria-hidden="true"></i> <i class="fa fa-plus" aria-hidden="true"></i>
</button> </button>
</div> </div>
<div class="add"> <div class="add">
<a class="btn btn-lg btn-primary mt-1 ml-2" [routerLink]="['/import-external']" role="button" title="{{'mydspace.new-submission-external' | translate}}"> <a class="btn btn-lg btn-outline-primary mt-1 ml-2" [routerLink]="['/import-external']" role="button" attr.aria-label="{{'mydspace.new-submission-external' | translate}}" title="{{'mydspace.new-submission-external' | translate}}">
<i class="fa fa-file-import" aria-hidden="true"></i> <i class="fa fa-file-import" aria-hidden="true"></i>
</a> </a>
</div> </div>

View File

@@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { MyDSpaceGuard } from './my-dspace.guard'; import { MyDSpaceGuard } from './my-dspace.guard';
import { ThemedMyDSpacePageComponent } from './themed-my-dspace-page.component'; import { ThemedMyDSpacePageComponent } from './themed-my-dspace-page.component';
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
@NgModule({ @NgModule({
imports: [ imports: [
@@ -9,7 +10,10 @@ import { ThemedMyDSpacePageComponent } from './themed-my-dspace-page.component';
{ {
path: '', path: '',
component: ThemedMyDSpacePageComponent, component: ThemedMyDSpacePageComponent,
data: { title: 'mydspace.title' }, resolve: {
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'mydspace.title', breadcrumbKey: 'mydspace' },
canActivate: [ canActivate: [
MyDSpaceGuard MyDSpaceGuard
] ]

View File

@@ -14,7 +14,8 @@
[scope]="(searchOptions$ | async)?.scope" [scope]="(searchOptions$ | async)?.scope"
[currentUrl]="getSearchLink()" [currentUrl]="getSearchLink()"
[scopes]="(scopeListRD$ | async)" [scopes]="(scopeListRD$ | async)"
[inPlaceSearch]="inPlaceSearch"> [inPlaceSearch]="inPlaceSearch"
[searchPlaceholder]="'mydspace.search-form.placeholder' | translate">
</ds-search-form> </ds-search-form>
<ds-search-labels [inPlaceSearch]="inPlaceSearch"></ds-search-labels> <ds-search-labels [inPlaceSearch]="inPlaceSearch"></ds-search-labels>
<div class="row"> <div class="row">

View File

@@ -45,7 +45,8 @@
[scope]="(searchOptions$ | async)?.scope" [scope]="(searchOptions$ | async)?.scope"
[currentUrl]="searchLink" [currentUrl]="searchLink"
[scopes]="(scopeListRD$ | async)" [scopes]="(scopeListRD$ | async)"
[inPlaceSearch]="inPlaceSearch"> [inPlaceSearch]="inPlaceSearch"
[searchPlaceholder]="'search.search-form.placeholder' | translate">
</ds-search-form> </ds-search-form>
<div class="row mb-3 mb-md-1"> <div class="row mb-3 mb-md-1">
<div class="labels col-sm-9 offset-sm-3"> <div class="labels col-sm-9 offset-sm-3">

View File

@@ -3,6 +3,7 @@ import { RouterModule } from '@angular/router';
import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
import { ThemedSubmissionSubmitComponent } from '../submission/submit/themed-submission-submit.component'; import { ThemedSubmissionSubmitComponent } from '../submission/submit/themed-submission-submit.component';
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
@NgModule({ @NgModule({
imports: [ imports: [
@@ -12,7 +13,10 @@ import { ThemedSubmissionSubmitComponent } from '../submission/submit/themed-sub
path: '', path: '',
pathMatch: 'full', pathMatch: 'full',
component: ThemedSubmissionSubmitComponent, component: ThemedSubmissionSubmitComponent,
data: { title: 'submission.submit.title' } resolve: {
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'submission.submit.title', breadcrumbKey: 'submission.submit' }
} }
]) ])
] ]

View File

@@ -3,10 +3,15 @@ import { RouterModule } from '@angular/router';
import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
import { WorkflowItemPageResolver } from './workflow-item-page.resolver'; import { WorkflowItemPageResolver } from './workflow-item-page.resolver';
import { WORKFLOW_ITEM_DELETE_PATH, WORKFLOW_ITEM_EDIT_PATH, WORKFLOW_ITEM_SEND_BACK_PATH } from './workflowitems-edit-page-routing-paths'; import {
WORKFLOW_ITEM_DELETE_PATH,
WORKFLOW_ITEM_EDIT_PATH,
WORKFLOW_ITEM_SEND_BACK_PATH
} from './workflowitems-edit-page-routing-paths';
import { ThemedSubmissionEditComponent } from '../submission/edit/themed-submission-edit.component'; import { ThemedSubmissionEditComponent } from '../submission/edit/themed-submission-edit.component';
import { ThemedWorkflowItemDeleteComponent } from './workflow-item-delete/themed-workflow-item-delete.component'; import { ThemedWorkflowItemDeleteComponent } from './workflow-item-delete/themed-workflow-item-delete.component';
import { ThemedWorkflowItemSendBackComponent } from './workflow-item-send-back/themed-workflow-item-send-back.component'; import { ThemedWorkflowItemSendBackComponent } from './workflow-item-send-back/themed-workflow-item-send-back.component';
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
@NgModule({ @NgModule({
imports: [ imports: [
@@ -19,19 +24,28 @@ import { ThemedWorkflowItemSendBackComponent } from './workflow-item-send-back/t
canActivate: [AuthenticatedGuard], canActivate: [AuthenticatedGuard],
path: WORKFLOW_ITEM_EDIT_PATH, path: WORKFLOW_ITEM_EDIT_PATH,
component: ThemedSubmissionEditComponent, component: ThemedSubmissionEditComponent,
data: { title: 'submission.edit.title' } resolve: {
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'workflow-item.edit.title', breadcrumbKey: 'workflow-item.edit' }
}, },
{ {
canActivate: [AuthenticatedGuard], canActivate: [AuthenticatedGuard],
path: WORKFLOW_ITEM_DELETE_PATH, path: WORKFLOW_ITEM_DELETE_PATH,
component: ThemedWorkflowItemDeleteComponent, component: ThemedWorkflowItemDeleteComponent,
data: { title: 'workflow-item.delete.title' } resolve: {
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'workflow-item.delete.title', breadcrumbKey: 'workflow-item.edit' }
}, },
{ {
canActivate: [AuthenticatedGuard], canActivate: [AuthenticatedGuard],
path: WORKFLOW_ITEM_SEND_BACK_PATH, path: WORKFLOW_ITEM_SEND_BACK_PATH,
component: ThemedWorkflowItemSendBackComponent, component: ThemedWorkflowItemSendBackComponent,
data: { title: 'workflow-item.send-back.title' } resolve: {
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'workflow-item.send-back.title', breadcrumbKey: 'workflow-item.edit' }
} }
] ]
}] }]

View File

@@ -3,6 +3,7 @@ import { RouterModule } from '@angular/router';
import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
import { ThemedSubmissionEditComponent } from '../submission/edit/themed-submission-edit.component'; import { ThemedSubmissionEditComponent } from '../submission/edit/themed-submission-edit.component';
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
@NgModule({ @NgModule({
imports: [ imports: [
@@ -12,7 +13,10 @@ import { ThemedSubmissionEditComponent } from '../submission/edit/themed-submiss
canActivate: [AuthenticatedGuard], canActivate: [AuthenticatedGuard],
path: ':id/edit', path: ':id/edit',
component: ThemedSubmissionEditComponent, component: ThemedSubmissionEditComponent,
data: { title: 'submission.edit.title' } resolve: {
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'submission.edit.title', breadcrumbKey: 'submission.edit' }
} }
]) ])
] ]

View File

@@ -4,22 +4,43 @@ import { EPeopleRegistryComponent } from './epeople-registry/epeople-registry.co
import { GroupFormComponent } from './group-registry/group-form/group-form.component'; import { GroupFormComponent } from './group-registry/group-form/group-form.component';
import { GroupsRegistryComponent } from './group-registry/groups-registry.component'; import { GroupsRegistryComponent } from './group-registry/groups-registry.component';
import { GROUP_EDIT_PATH } from './access-control-routing-paths'; import { GROUP_EDIT_PATH } from './access-control-routing-paths';
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
@NgModule({ @NgModule({
imports: [ imports: [
RouterModule.forChild([ RouterModule.forChild([
{ path: 'epeople', component: EPeopleRegistryComponent, data: { title: 'admin.access-control.epeople.title' } },
{ path: GROUP_EDIT_PATH, component: GroupsRegistryComponent, data: { title: 'admin.access-control.groups.title' } },
{ {
path: `${GROUP_EDIT_PATH}/:groupId`, path: 'epeople',
component: GroupFormComponent, component: EPeopleRegistryComponent,
data: {title: 'admin.access-control.groups.title.singleGroup'} resolve: {
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'admin.access-control.epeople.title', breadcrumbKey: 'admin.access-control.epeople' }
},
{
path: GROUP_EDIT_PATH,
component: GroupsRegistryComponent,
resolve: {
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'admin.access-control.groups.title', breadcrumbKey: 'admin.access-control.groups' }
}, },
{ {
path: `${GROUP_EDIT_PATH}/newGroup`, path: `${GROUP_EDIT_PATH}/newGroup`,
component: GroupFormComponent, component: GroupFormComponent,
data: {title: 'admin.access-control.groups.title.addGroup'} resolve: {
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'admin.access-control.groups.title.addGroup', breadcrumbKey: 'admin.access-control.groups.addGroup' }
}, },
{
path: `${GROUP_EDIT_PATH}/:groupId`,
component: GroupFormComponent,
resolve: {
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'admin.access-control.groups.title.singleGroup', breadcrumbKey: 'admin.access-control.groups.singleGroup' }
}
]) ])
] ]
}) })

View File

@@ -1,46 +1,53 @@
<div class="container"> <div class="container">
<div class="epeople-registry row"> <div class="epeople-registry row">
<div class="col-12"> <div class="col-12">
<div class="d-flex justify-content-between border-bottom mb-3">
<h2 id="header" class="pb-2">{{labelPrefix + 'head' | translate}}</h2>
<h2 id="header" class="border-bottom pb-2">{{labelPrefix + 'head' | translate}}</h2> <div *ngIf="!isEPersonFormShown">
<ds-eperson-form *ngIf="isEPersonFormShown" (submitForm)="reset()"
(cancelForm)="isEPersonFormShown = false"></ds-eperson-form>
<div *ngIf="!isEPersonFormShown">
<div class="button-row top d-flex pb-2">
<button class="mr-auto btn btn-success addEPerson-button" <button class="mr-auto btn btn-success addEPerson-button"
(click)="isEPersonFormShown = true"> (click)="isEPersonFormShown = true">
<i class="fas fa-plus"></i> <i class="fas fa-plus"></i>
<span class="d-none d-sm-inline">{{labelPrefix + 'button.add' | translate}}</span> <span class="d-none d-sm-inline">{{labelPrefix + 'button.add' | translate}}</span>
</button> </button>
</div> </div>
</div>
<ds-eperson-form *ngIf="isEPersonFormShown" (submitForm)="reset()"
(cancelForm)="isEPersonFormShown = false"></ds-eperson-form>
<div *ngIf="!isEPersonFormShown">
<h3 id="search" class="border-bottom pb-2">{{labelPrefix + 'search.head' | translate}} <h3 id="search" class="border-bottom pb-2">{{labelPrefix + 'search.head' | translate}}
<button (click)="clearFormAndResetResult();"
class="btn btn-primary float-right">{{labelPrefix + 'button.see-all' | translate}}</button>
</h3> </h3>
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="row"> <form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
<div class="col-12 col-sm-3"> <div>
<select name="scope" id="scope" formControlName="scope" class="form-control" aria-label="Search scope"> <select name="scope" id="scope" formControlName="scope" class="form-control" aria-label="Search scope">
<option value="metadata">{{labelPrefix + 'search.scope.metadata' | translate}}</option> <option value="metadata">{{labelPrefix + 'search.scope.metadata' | translate}}</option>
<option value="email">{{labelPrefix + 'search.scope.email' | translate}}</option> <option value="email">{{labelPrefix + 'search.scope.email' | translate}}</option>
</select> </select>
</div> </div>
<div class="col-sm-9 col-12"> <div class="flex-grow-1 mr-3 ml-3">
<div class="form-group input-group"> <div class="form-group input-group">
<input type="text" name="query" id="query" formControlName="query" <input type="text" name="query" id="query" formControlName="query"
class="form-control" aria-label="Search input"> class="form-control" attr.aria-label="{{labelPrefix + 'search.placeholder' | translate}}"
[placeholder]="(labelPrefix + 'search.placeholder' | translate)">
<span class="input-group-append"> <span class="input-group-append">
<button type="submit" <button type="submit" class="search-button btn btn-primary">
class="search-button btn btn-secondary">{{ labelPrefix + 'search.button' | translate }}</button> <i class="fas fa-search"></i> {{ labelPrefix + 'search.button' | translate }}
</span> </button>
</span>
</div> </div>
</div> </div>
<div>
<button (click)="clearFormAndResetResult();"
class="search-button btn btn-secondary">{{labelPrefix + 'button.see-all' | translate}}</button>
</div>
</form> </form>
<ds-loading *ngIf="searching$ | async"></ds-loading>
<ds-pagination <ds-pagination
*ngIf="(pageInfoState$ | async)?.totalElements > 0" *ngIf="(pageInfoState$ | async)?.totalElements > 0 && !(searching$ | async)"
[paginationOptions]="config" [paginationOptions]="config"
[pageInfoState]="pageInfoState$" [pageInfoState]="pageInfoState$"
[collectionSize]="(pageInfoState$ | async)?.totalElements" [collectionSize]="(pageInfoState$ | async)?.totalElements"

View File

@@ -51,6 +51,11 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
*/ */
pageInfoState$: BehaviorSubject<PageInfo> = new BehaviorSubject<PageInfo>(undefined); pageInfoState$: BehaviorSubject<PageInfo> = new BehaviorSubject<PageInfo>(undefined);
/**
* A boolean representing if a search is pending
*/
searching$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
/** /**
* Pagination config used to display the list of epeople * Pagination config used to display the list of epeople
*/ */
@@ -106,6 +111,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
* This method will initialise the page * This method will initialise the page
*/ */
initialisePage() { initialisePage() {
this.searching$.next(true);
this.isEPersonFormShown = false; this.isEPersonFormShown = false;
this.search({ scope: this.currentSearchScope, query: this.currentSearchQuery }); this.search({ scope: this.currentSearchScope, query: this.currentSearchQuery });
this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => { this.subs.push(this.epersonService.getActiveEPerson().subscribe((eperson: EPerson) => {
@@ -133,6 +139,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
return [epeople]; return [epeople];
} }
})).subscribe((value: PaginatedList<EpersonDtoModel>) => { })).subscribe((value: PaginatedList<EpersonDtoModel>) => {
this.searching$.next(false);
this.ePeopleDto$.next(value); this.ePeopleDto$.next(value);
this.pageInfoState$.next(value.pageInfo); this.pageInfoState$.next(value.pageInfo);
})); }));
@@ -154,6 +161,7 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
* @param data Contains scope and query param * @param data Contains scope and query param
*/ */
search(data: any) { search(data: any) {
this.searching$.next(true);
const query: string = data.query; const query: string = data.query;
const scope: string = data.scope; const scope: string = data.scope;
if (query != null && this.currentSearchQuery !== query) { if (query != null && this.currentSearchQuery !== query) {
@@ -228,6 +236,8 @@ export class EPeopleRegistryComponent implements OnInit, OnDestroy {
modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-eperson.info'; modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-eperson.info';
modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-eperson.cancel'; modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-eperson.cancel';
modalRef.componentInstance.confirmLabel = 'confirmation-modal.delete-eperson.confirm'; modalRef.componentInstance.confirmLabel = 'confirmation-modal.delete-eperson.confirm';
modalRef.componentInstance.brandColor = 'danger';
modalRef.componentInstance.confirmIcon = 'fas fa-trash';
modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => { modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => {
if (confirm) { if (confirm) {
if (hasValue(ePerson.id)) { if (hasValue(ePerson.id)) {

View File

@@ -12,19 +12,27 @@
[formModel]="formModel" [formModel]="formModel"
[formGroup]="formGroup" [formGroup]="formGroup"
[formLayout]="formLayout" [formLayout]="formLayout"
(cancel)="onCancel()" [displayCancel]="false"
(submitForm)="onSubmit()"> (submitForm)="onSubmit()">
<button class="btn btn-light" [disabled]="!(canReset$ | async)"> <div before class="btn-group">
<i class="fa fa-key"></i> {{'admin.access-control.epeople.actions.reset' | translate}} <button (click)="onCancel()"
</button> class="btn btn-outline-secondary"><i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}</button>
<button class="btn btn-light delete-button" [disabled]="!(canDelete$ | async)" (click)="delete()"> </div>
<i class="fa fa-trash"></i> {{'admin.access-control.epeople.actions.delete' | translate}} <div between class="btn-group">
</button> <button class="btn btn-primary" [disabled]="!(canReset$ | async)">
<button *ngIf="!isImpersonated" class="btn btn-light" [ngClass]="{'d-none' : !(canImpersonate$ | async)}" (click)="impersonate()"> <i class="fa fa-key"></i> {{'admin.access-control.epeople.actions.reset' | translate}}
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.impersonate' | translate}} </button>
</button> </div>
<button *ngIf="isImpersonated" class="btn btn-light" (click)="stopImpersonating()"> <div between class="btn-group ml-1">
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.stop-impersonating' | translate}} <button *ngIf="!isImpersonated" class="btn btn-primary" [ngClass]="{'d-none' : !(canImpersonate$ | async)}" (click)="impersonate()">
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.impersonate' | translate}}
</button>
<button *ngIf="isImpersonated" class="btn btn-primary" (click)="stopImpersonating()">
<i class="fa fa-user-secret"></i> {{'admin.access-control.epeople.actions.stop-impersonating' | translate}}
</button>
</div>
<button after class="btn btn-danger delete-button" [disabled]="!(canDelete$ | async)" (click)="delete()">
<i class="fas fa-trash"></i> {{'admin.access-control.epeople.actions.delete' | translate}}
</button> </button>
</ds-form> </ds-form>

View File

@@ -436,6 +436,8 @@ export class EPersonFormComponent implements OnInit, OnDestroy {
modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-eperson.info'; modalRef.componentInstance.infoLabel = 'confirmation-modal.delete-eperson.info';
modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-eperson.cancel'; modalRef.componentInstance.cancelLabel = 'confirmation-modal.delete-eperson.cancel';
modalRef.componentInstance.confirmLabel = 'confirmation-modal.delete-eperson.confirm'; modalRef.componentInstance.confirmLabel = 'confirmation-modal.delete-eperson.confirm';
modalRef.componentInstance.brandColor = 'danger';
modalRef.componentInstance.confirmIcon = 'fas fa-trash';
modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => { modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => {
if (confirm) { if (confirm) {
if (hasValue(eperson.id)) { if (hasValue(eperson.id)) {

View File

@@ -1,11 +1,6 @@
<div class="container"> <div class="container">
<div class="group-form row"> <div class="group-form row">
<div class="col-12"> <div class="col-12">
<ds-alert *ngIf="groupBeingEdited?.permanent" [type]="AlertTypeEnum.Warning"
[content]="messagePrefix + '.alert.permanent'"></ds-alert>
<ds-alert *ngIf="!(canEdit$ | async) && (groupDataService.getActiveGroup() | async)" [type]="AlertTypeEnum.Warning"
[content]="(messagePrefix + '.alert.workflowGroup' | translate:{ name: (getLinkedDSO(groupBeingEdited) | async)?.payload?.name, comcol: (getLinkedDSO(groupBeingEdited) | async)?.payload?.type, comcolEditRolesRoute: (getLinkedEditRolesRoute(groupBeingEdited) | async) })">
</ds-alert>
<div *ngIf="groupDataService.getActiveGroup() | async; then editheader; else createHeader"></div> <div *ngIf="groupDataService.getActiveGroup() | async; then editheader; else createHeader"></div>
@@ -17,29 +12,38 @@
<h2 class="border-bottom pb-2">{{messagePrefix + '.head.edit' | translate}}</h2> <h2 class="border-bottom pb-2">{{messagePrefix + '.head.edit' | translate}}</h2>
</ng-template> </ng-template>
<ds-alert *ngIf="groupBeingEdited?.permanent" [type]="AlertTypeEnum.Warning"
[content]="messagePrefix + '.alert.permanent'"></ds-alert>
<ds-alert *ngIf="!(canEdit$ | async) && (groupDataService.getActiveGroup() | async)" [type]="AlertTypeEnum.Warning"
[content]="(messagePrefix + '.alert.workflowGroup' | translate:{ name: (getLinkedDSO(groupBeingEdited) | async)?.payload?.name, comcol: (getLinkedDSO(groupBeingEdited) | async)?.payload?.type, comcolEditRolesRoute: (getLinkedEditRolesRoute(groupBeingEdited) | async) })">
</ds-alert>
<ds-form [formId]="formId" <ds-form [formId]="formId"
[formModel]="formModel" [formModel]="formModel"
[formGroup]="formGroup" [formGroup]="formGroup"
[formLayout]="formLayout" [formLayout]="formLayout"
(cancel)="onCancel()" [displayCancel]="false"
(submitForm)="onSubmit()"> (submitForm)="onSubmit()">
<div *ngIf="groupBeingEdited != null" class="row"> <div before class="btn-group">
<button class="btn btn-light delete-button" [disabled]="!(canEdit$ | async) || groupBeingEdited.permanent" <button (click)="onCancel()"
class="btn btn-outline-secondary"><i class="fas fa-arrow-left"></i> {{messagePrefix + '.return' | translate}}</button>
</div>
<div after *ngIf="groupBeingEdited != null" class="btn-group">
<button class="btn btn-danger delete-button" [disabled]="!(canEdit$ | async) || groupBeingEdited.permanent"
(click)="delete()"> (click)="delete()">
<i class="fa fa-trash"></i> {{ messagePrefix + '.actions.delete' | translate}} <i class="fa fa-trash"></i> {{ messagePrefix + '.actions.delete' | translate}}
</button> </button>
</div> </div>
</ds-form> </ds-form>
<ds-members-list *ngIf="groupBeingEdited != null" <div class="mb-5">
[messagePrefix]="messagePrefix + '.members-list'"></ds-members-list> <ds-members-list *ngIf="groupBeingEdited != null"
[messagePrefix]="messagePrefix + '.members-list'"></ds-members-list>
</div>
<ds-subgroups-list *ngIf="groupBeingEdited != null" <ds-subgroups-list *ngIf="groupBeingEdited != null"
[messagePrefix]="messagePrefix + '.subgroups-list'"></ds-subgroups-list> [messagePrefix]="messagePrefix + '.subgroups-list'"></ds-subgroups-list>
<div>
<button [routerLink]="[this.groupDataService.getGroupRegistryRouterLink()]"
class="btn btn-primary">{{messagePrefix + '.return' | translate}}</button>
</div>
</div> </div>
</div> </div>

View File

@@ -368,6 +368,8 @@ export class GroupFormComponent implements OnInit, OnDestroy {
modalRef.componentInstance.infoLabel = this.messagePrefix + '.delete-group.modal.info'; modalRef.componentInstance.infoLabel = this.messagePrefix + '.delete-group.modal.info';
modalRef.componentInstance.cancelLabel = this.messagePrefix + '.delete-group.modal.cancel'; modalRef.componentInstance.cancelLabel = this.messagePrefix + '.delete-group.modal.cancel';
modalRef.componentInstance.confirmLabel = this.messagePrefix + '.delete-group.modal.confirm'; modalRef.componentInstance.confirmLabel = this.messagePrefix + '.delete-group.modal.confirm';
modalRef.componentInstance.brandColor = 'danger';
modalRef.componentInstance.confirmIcon = 'fas fa-trash';
modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => { modalRef.componentInstance.response.pipe(take(1)).subscribe((confirm: boolean) => {
if (confirm) { if (confirm) {
if (hasValue(group.id)) { if (hasValue(group.id)) {

View File

@@ -2,26 +2,29 @@
<h3 class="border-bottom pb-2">{{messagePrefix + '.head' | translate}}</h3> <h3 class="border-bottom pb-2">{{messagePrefix + '.head' | translate}}</h3>
<h4 id="search" class="border-bottom pb-2">{{messagePrefix + '.search.head' | translate}} <h4 id="search" class="border-bottom pb-2">{{messagePrefix + '.search.head' | translate}}
<button (click)="clearFormAndResetResult();"
class="btn btn-primary float-right">{{messagePrefix + '.button.see-all' | translate}}</button>
</h4> </h4>
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="row"> <form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
<div class="col-12 col-sm-3"> <div>
<select name="scope" id="scope" formControlName="scope" class="form-control" aria-label="Search scope"> <select name="scope" id="scope" formControlName="scope" class="form-control" aria-label="Search scope">
<option value="metadata">{{messagePrefix + '.search.scope.metadata' | translate}}</option> <option value="metadata">{{messagePrefix + '.search.scope.metadata' | translate}}</option>
<option value="email">{{messagePrefix + '.search.scope.email' | translate}}</option> <option value="email">{{messagePrefix + '.search.scope.email' | translate}}</option>
</select> </select>
</div> </div>
<div class="col-sm-9 col-12"> <div class="flex-grow-1 mr-3 ml-3">
<div class="form-group input-group"> <div class="form-group input-group">
<input type="text" name="query" id="query" formControlName="query" <input type="text" name="query" id="query" formControlName="query"
class="form-control" aria-label="Search input"> class="form-control" aria-label="Search input">
<span class="input-group-append"> <span class="input-group-append">
<button type="submit" <button type="submit" class="search-button btn btn-primary">
class="search-button btn btn-secondary">{{ messagePrefix + '.search.button' | translate }}</button> <i class="fas fa-search"></i> {{ messagePrefix + '.search.button' | translate }}</button>
</span> </span>
</div> </div>
</div> </div>
<div>
<button (click)="clearFormAndResetResult();"
class="btn btn-secondary">{{messagePrefix + '.button.see-all' | translate}}</button>
</div>
</form> </form>
<ds-pagination *ngIf="(ePeopleSearchDtos | async)?.totalElements > 0" <ds-pagination *ngIf="(ePeopleSearchDtos | async)?.totalElements > 0"

View File

@@ -2,20 +2,26 @@
<h3 class="border-bottom pb-2">{{messagePrefix + '.head' | translate}}</h3> <h3 class="border-bottom pb-2">{{messagePrefix + '.head' | translate}}</h3>
<h4 id="search" class="border-bottom pb-2">{{messagePrefix + '.search.head' | translate}} <h4 id="search" class="border-bottom pb-2">{{messagePrefix + '.search.head' | translate}}
<button (click)="clearFormAndResetResult();"
class="btn btn-primary float-right">{{messagePrefix + '.button.see-all' | translate}}</button>
</h4> </h4>
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="row"> <form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
<div class="col-12"> <div class="flex-grow-1 mr-3">
<div class="form-group input-group"> <div class="form-group input-group mr-3">
<input type="text" name="query" id="query" formControlName="query" <input type="text" name="query" id="query" formControlName="query"
class="form-control" aria-label="Search input"> class="form-control" aria-label="Search input">
<span class="input-group-append"> <span class="input-group-append">
<button type="submit" <button type="submit" class="search-button btn btn-primary">
class="search-button btn btn-secondary">{{ messagePrefix + '.search.button' | translate }}</button> <i class="fas fa-search"></i> {{ messagePrefix + '.search.button' | translate }}
</button>
</span> </span>
</div> </div>
</div> </div>
<div>
<button (click)="clearFormAndResetResult();" class="btn btn-secondary float-right">
{{messagePrefix + '.button.see-all' | translate}}
</button>
</div>
</form> </form>
<ds-pagination *ngIf="(searchResults$ | async)?.payload?.totalElements > 0" <ds-pagination *ngIf="(searchResults$ | async)?.payload?.totalElements > 0"

View File

@@ -1,36 +1,41 @@
<div class="container"> <div class="container">
<div class="groups-registry row"> <div class="groups-registry row">
<div class="col-12"> <div class="col-12">
<div class="d-flex justify-content-between border-bottom mb-3">
<h2 id="header" class="border-bottom pb-2">{{messagePrefix + 'head' | translate}}</h2> <h2 id="header" class="pb-2">{{messagePrefix + 'head' | translate}}</h2>
<div>
<div class="button-row top d-flex pb-2"> <button class="mr-auto btn btn-success"
<button class="mr-auto btn btn-success" [routerLink]="['newGroup']">
[routerLink]="['newGroup']"> <i class="fas fa-plus"></i>
<i class="fas fa-plus"></i> <span class="d-none d-sm-inline">{{messagePrefix + 'button.add' | translate}}</span>
<span class="d-none d-sm-inline">{{messagePrefix + 'button.add' | translate}}</span> </button>
</button> </div>
</div> </div>
<h3 id="search" class="border-bottom pb-2">{{messagePrefix + 'search.head' | translate}} <h3 id="search" class="border-bottom pb-2">{{messagePrefix + 'search.head' | translate}}</h3>
<button (click)="clearFormAndResetResult();" <form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="d-flex justify-content-between">
class="btn btn-primary float-right">{{messagePrefix + 'button.see-all' | translate}}</button> <div class="flex-grow-1 mr-3">
</h3>
<form [formGroup]="searchForm" (ngSubmit)="search(searchForm.value)" class="row">
<div class="col-12">
<div class="form-group input-group"> <div class="form-group input-group">
<input type="text" name="query" id="query" formControlName="query" <input type="text" name="query" id="query" formControlName="query"
class="form-control" aria-label="Search input"> class="form-control" attr.aria-label="{{messagePrefix + 'search.placeholder' | translate}}"
[placeholder]="(messagePrefix + 'search.placeholder' | translate)" >
<span class="input-group-append"> <span class="input-group-append">
<button type="submit" <button type="submit" class="search-button btn btn-primary">
class="search-button btn btn-secondary">{{ messagePrefix + 'search.button' | translate }}</button> <i class="fas fa-search"></i> {{ messagePrefix + 'search.button' | translate }}
</span> </button>
</span>
</div> </div>
</div> </div>
<div>
<button (click)="clearFormAndResetResult();" class="btn btn-secondary">
{{messagePrefix + 'button.see-all' | translate}}
</button>
</div>
</form> </form>
<ds-loading *ngIf="searching$ | async"></ds-loading>
<ds-pagination <ds-pagination
*ngIf="(pageInfoState$ | async)?.totalElements > 0" *ngIf="(pageInfoState$ | async)?.totalElements > 0 && !(searching$ | async)"
[paginationOptions]="config" [paginationOptions]="config"
[pageInfoState]="pageInfoState$" [pageInfoState]="pageInfoState$"
[collectionSize]="(pageInfoState$ | async)?.totalElements" [collectionSize]="(pageInfoState$ | async)?.totalElements"
@@ -38,7 +43,7 @@
[hidePagerWhenSinglePage]="true" [hidePagerWhenSinglePage]="true"
(pageChange)="onPageChange($event)"> (pageChange)="onPageChange($event)">
<div class="table-responsive"> <div class="table-responsive">
<table id="groups" class="table table-striped table-hover table-bordered"> <table id="groups" class="table table-striped table-hover table-bordered">
<thead> <thead>
<tr> <tr>

View File

@@ -70,6 +70,11 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
// The search form // The search form
searchForm; searchForm;
/**
* A boolean representing if a search is pending
*/
searching$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
// Current search in groups registry // Current search in groups registry
currentSearchQuery: string; currentSearchQuery: string;
@@ -117,6 +122,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
* @param data Contains query param * @param data Contains query param
*/ */
search(data: any) { search(data: any) {
this.searching$.next(true);
const query: string = data.query; const query: string = data.query;
if (query != null && this.currentSearchQuery !== query) { if (query != null && this.currentSearchQuery !== query) {
this.router.navigateByUrl(this.groupService.getGroupRegistryRouterLink()); this.router.navigateByUrl(this.groupService.getGroupRegistryRouterLink());
@@ -163,6 +169,7 @@ export class GroupsRegistryComponent implements OnInit, OnDestroy {
})).subscribe((value: PaginatedList<GroupDtoModel>) => { })).subscribe((value: PaginatedList<GroupDtoModel>) => {
this.groupsDto$.next(value); this.groupsDto$.next(value);
this.pageInfoState$.next(value.pageInfo); this.pageInfoState$.next(value.pageInfo);
this.searching$.next(false);
}); });
this.subs.push(this.searchSub); this.subs.push(this.searchSub);
} }

View File

@@ -15,14 +15,11 @@ import { AdminSidebarSectionComponent } from './+admin/admin-sidebar/admin-sideb
import { AdminSidebarComponent } from './+admin/admin-sidebar/admin-sidebar.component'; import { AdminSidebarComponent } from './+admin/admin-sidebar/admin-sidebar.component';
import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component'; import { ExpandableAdminSidebarSectionComponent } from './+admin/admin-sidebar/expandable-admin-sidebar-section/expandable-admin-sidebar-section.component';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { appEffects } from './app.effects'; import { appEffects } from './app.effects';
import { appMetaReducers, debugMetaReducers } from './app.metareducers'; import { appMetaReducers, debugMetaReducers } from './app.metareducers';
import { appReducers, AppState, storeModuleConfig } from './app.reducer'; import { appReducers, AppState, storeModuleConfig } from './app.reducer';
import { CheckAuthenticationTokenAction } from './core/auth/auth.actions'; import { CheckAuthenticationTokenAction } from './core/auth/auth.actions';
import { CoreModule } from './core/core.module'; import { CoreModule } from './core/core.module';
import { ClientCookieService } from './core/services/client-cookie.service'; import { ClientCookieService } from './core/services/client-cookie.service';
import { FooterComponent } from './footer/footer.component'; import { FooterComponent } from './footer/footer.component';
@@ -30,8 +27,6 @@ import { HeaderNavbarWrapperComponent } from './header-nav-wrapper/header-navbar
import { HeaderComponent } from './header/header.component'; import { HeaderComponent } from './header/header.component';
import { NavbarModule } from './navbar/navbar.module'; import { NavbarModule } from './navbar/navbar.module';
import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component'; import { PageNotFoundComponent } from './pagenotfound/pagenotfound.component';
import { SearchNavbarComponent } from './search-navbar/search-navbar.component';
import { DSpaceRouterStateSerializer } from './shared/ngrx/dspace-router-state-serializer'; import { DSpaceRouterStateSerializer } from './shared/ngrx/dspace-router-state-serializer';
import { NotificationComponent } from './shared/notifications/notification/notification.component'; import { NotificationComponent } from './shared/notifications/notification/notification.component';
import { NotificationsBoardComponent } from './shared/notifications/notifications-board/notifications-board.component'; import { NotificationsBoardComponent } from './shared/notifications/notifications-board/notifications-board.component';
@@ -143,7 +138,6 @@ const DECLARATIONS = [
ThemedPageNotFoundComponent, ThemedPageNotFoundComponent,
NotificationComponent, NotificationComponent,
NotificationsBoardComponent, NotificationsBoardComponent,
SearchNavbarComponent,
BreadcrumbsComponent, BreadcrumbsComponent,
ThemedBreadcrumbsComponent, ThemedBreadcrumbsComponent,
ForbiddenComponent, ForbiddenComponent,

View File

@@ -1,6 +1,6 @@
<ng-container *ngVar="(breadcrumbs$ | async) as breadcrumbs"> <ng-container *ngVar="(breadcrumbs$ | async) as breadcrumbs">
<nav *ngIf="(showBreadcrumbs$ | async)" aria-label="breadcrumb"> <nav *ngIf="(showBreadcrumbs$ | async)" aria-label="breadcrumb" class="nav-breadcrumb">
<ol class="breadcrumb"> <ol class="container breadcrumb">
<ng-container <ng-container
*ngTemplateOutlet="breadcrumbs?.length > 0 ? breadcrumb : activeBreadcrumb; context: {text: 'home.breadcrumbs', url: '/'}"></ng-container> *ngTemplateOutlet="breadcrumbs?.length > 0 ? breadcrumb : activeBreadcrumb; context: {text: 'home.breadcrumbs', url: '/'}"></ng-container>
<ng-container *ngFor="let bc of breadcrumbs; let last = last;"> <ng-container *ngFor="let bc of breadcrumbs; let last = last;">

View File

@@ -0,0 +1,22 @@
.nav-breadcrumb {
background-color: var(--ds-breadcrumb-bg);
}
.breadcrumb {
border-radius: 0;
margin-top: calc(-1 * var(--ds-content-spacing));
padding-bottom: var(--ds-content-spacing / 3);
padding-top: var(--ds-content-spacing / 3);
background-color: var(--ds-breadcrumb-bg);
}
li.breadcrumb-item > a {
color: var(--ds-breadcrumb-link-color) !important;
}
li.breadcrumb-item.active {
color: var(--ds-breadcrumb-link-active-color) !important;
}
.breadcrumb-item+ .breadcrumb-item::before {
content: quote("") !important;
}

View File

@@ -4,6 +4,7 @@ import { CdkTreeModule } from '@angular/cdk/tree';
import { CommunityListService } from './community-list-service'; import { CommunityListService } from './community-list-service';
import { ThemedCommunityListPageComponent } from './themed-community-list-page.component'; import { ThemedCommunityListPageComponent } from './themed-community-list-page.component';
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
/** /**
* RouterModule to help navigate to the page with the community list tree * RouterModule to help navigate to the page with the community list tree
@@ -15,7 +16,10 @@ import { ThemedCommunityListPageComponent } from './themed-community-list-page.c
path: '', path: '',
component: ThemedCommunityListPageComponent, component: ThemedCommunityListPageComponent,
pathMatch: 'full', pathMatch: 'full',
data: { title: 'communityList.tabTitle' } resolve: {
breadcrumb: I18nBreadcrumbResolver
},
data: { title: 'communityList.tabTitle', breadcrumbKey: 'communityList' }
} }
]), ]),
CdkTreeModule, CdkTreeModule,

View File

@@ -9,8 +9,8 @@
</button> </button>
<div class="align-middle pt-2"> <div class="align-middle pt-2">
<a *ngIf="node!==loadingNode" [routerLink]="" (click)="getNextPage(node)" <a *ngIf="node!==loadingNode" [routerLink]="" (click)="getNextPage(node)"
class="btn btn-outline-secondary btn-sm"> class="btn btn-outline-primary btn-sm" role="button">
{{ 'communityList.showMore' | translate }} <i class="fas fa-angle-down"></i> {{ 'communityList.showMore' | translate }}
</a> </a>
<ds-loading *ngIf="node===loadingNode && dataSource.loading$ | async" class="ds-loading"></ds-loading> <ds-loading *ngIf="node===loadingNode && dataSource.loading$ | async" class="ds-loading"></ds-loading>
</div> </div>
@@ -25,6 +25,7 @@
class="example-tree-node expandable-node"> class="example-tree-node expandable-node">
<div class="btn-group"> <div class="btn-group">
<button type="button" class="btn btn-default" cdkTreeNodeToggle <button type="button" class="btn btn-default" cdkTreeNodeToggle
[title]="'toggle ' + node.name"
[attr.aria-label]="'toggle ' + node.name" [attr.aria-label]="'toggle ' + node.name"
(click)="toggleExpanded(node)" (click)="toggleExpanded(node)"
[ngClass]="(hasChild(null, node)| async) ? 'visible' : 'invisible'"> [ngClass]="(hasChild(null, node)| async) ? 'visible' : 'invisible'">

View File

@@ -120,7 +120,7 @@ describe('DsoRedirectDataService', () => {
it('should navigate to entities route with the corresponding entity type', () => { it('should navigate to entities route with the corresponding entity type', () => {
remoteData.payload.type = 'item'; remoteData.payload.type = 'item';
remoteData.payload.metadata = { remoteData.payload.metadata = {
'relationship.type': [ 'dspace.entity.type': [
{ {
language: 'en_US', language: 'en_US',
value: 'Publication' value: 'Publication'

View File

@@ -8,6 +8,8 @@ import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
import { LANG_COOKIE, LANG_ORIGIN, LocaleService } from './locale.service'; import { LANG_COOKIE, LANG_ORIGIN, LocaleService } from './locale.service';
import { AuthService } from '../auth/auth.service'; import { AuthService } from '../auth/auth.service';
import { NativeWindowRef } from '../services/window.service'; import { NativeWindowRef } from '../services/window.service';
import { RouteService } from '../services/route.service';
import { routeServiceStub } from '../../shared/testing/route-service.stub';
describe('LocaleService test suite', () => { describe('LocaleService test suite', () => {
let service: LocaleService; let service: LocaleService;
@@ -18,6 +20,7 @@ describe('LocaleService test suite', () => {
let spyOnGet; let spyOnGet;
let spyOnSet; let spyOnSet;
let authService; let authService;
let routeService;
authService = jasmine.createSpyObj('AuthService', { authService = jasmine.createSpyObj('AuthService', {
isAuthenticated: jasmine.createSpy('isAuthenticated'), isAuthenticated: jasmine.createSpy('isAuthenticated'),
@@ -38,7 +41,8 @@ describe('LocaleService test suite', () => {
], ],
providers: [ providers: [
{ provide: CookieService, useValue: new CookieServiceMock() }, { provide: CookieService, useValue: new CookieServiceMock() },
{ provide: AuthService, userValue: authService } { provide: AuthService, userValue: authService },
{ provide: RouteService, useValue: routeServiceStub },
] ]
}); });
})); }));
@@ -46,8 +50,9 @@ describe('LocaleService test suite', () => {
beforeEach(() => { beforeEach(() => {
cookieService = TestBed.inject(CookieService); cookieService = TestBed.inject(CookieService);
translateService = TestBed.inject(TranslateService); translateService = TestBed.inject(TranslateService);
routeService = TestBed.inject(RouteService);
window = new NativeWindowRef(); window = new NativeWindowRef();
service = new LocaleService(window, cookieService, translateService, authService); service = new LocaleService(window, cookieService, translateService, authService, routeService);
serviceAsAny = service; serviceAsAny = service;
spyOnGet = spyOn(cookieService, 'get'); spyOnGet = spyOn(cookieService, 'get');
spyOnSet = spyOn(cookieService, 'set'); spyOnSet = spyOn(cookieService, 'set');

View File

@@ -9,6 +9,7 @@ import { AuthService } from '../auth/auth.service';
import { combineLatest, Observable, of as observableOf } from 'rxjs'; import { combineLatest, Observable, of as observableOf } from 'rxjs';
import { map, mergeMap, take } from 'rxjs/operators'; import { map, mergeMap, take } from 'rxjs/operators';
import { NativeWindowRef, NativeWindowService } from '../services/window.service'; import { NativeWindowRef, NativeWindowService } from '../services/window.service';
import { RouteService } from '../services/route.service';
export const LANG_COOKIE = 'dsLanguage'; export const LANG_COOKIE = 'dsLanguage';
@@ -36,7 +37,8 @@ export class LocaleService {
@Inject(NativeWindowService) protected _window: NativeWindowRef, @Inject(NativeWindowService) protected _window: NativeWindowRef,
protected cookie: CookieService, protected cookie: CookieService,
protected translate: TranslateService, protected translate: TranslateService,
protected authService: AuthService) { protected authService: AuthService,
protected routeService: RouteService) {
} }
/** /**
@@ -183,9 +185,12 @@ export class LocaleService {
* Refresh route navigated * Refresh route navigated
*/ */
public refreshAfterChangeLanguage() { public refreshAfterChangeLanguage() {
// Hard redirect to the reload page with a unique number behind it this.routeService.getCurrentUrl().pipe(take(1)).subscribe((currentURL) => {
// so that all state is definitely lost // Hard redirect to the reload page with a unique number behind it
this._window.nativeWindow.location.href = `/reload/${new Date().getTime()}`; // so that all state is definitely lost
this._window.nativeWindow.location.href = `/reload/${new Date().getTime()}?redirect=` + encodeURIComponent(currentURL);
});
} }
} }

View File

@@ -8,8 +8,8 @@ import { RouterTestingModule } from '@angular/router/testing';
import { Store, StoreModule } from '@ngrx/store'; import { Store, StoreModule } from '@ngrx/store';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
import { EmptyError, Observable } from 'rxjs'; import { EmptyError, Observable, of } from 'rxjs';
import { RemoteData } from '../data/remote-data'; import { RemoteData } from '../data/remote-data';
import { Item } from '../shared/item.model'; import { Item } from '../shared/item.model';
@@ -94,6 +94,7 @@ describe('MetadataService', () => {
let itemDataService: ItemDataService; let itemDataService: ItemDataService;
let authService: AuthService; let authService: AuthService;
let rootService: RootDataService; let rootService: RootDataService;
let translateService: TranslateService;
let location: Location; let location: Location;
let router: Router; let router: Router;
@@ -195,6 +196,7 @@ describe('MetadataService', () => {
itemDataService = TestBed.inject(ItemDataService); itemDataService = TestBed.inject(ItemDataService);
metadataService = TestBed.inject(MetadataService); metadataService = TestBed.inject(MetadataService);
authService = TestBed.inject(AuthService); authService = TestBed.inject(AuthService);
translateService = TestBed.inject(TranslateService);
router = TestBed.inject(Router); router = TestBed.inject(Router);
location = TestBed.inject(Location); location = TestBed.inject(Location);
@@ -236,14 +238,15 @@ describe('MetadataService', () => {
it('other navigation should add title, description and Generator', fakeAsync(() => { it('other navigation should add title, description and Generator', fakeAsync(() => {
spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(ItemMock)); spyOn(itemDataService, 'findById').and.returnValue(mockRemoteData(ItemMock));
spyOn(translateService, 'get').and.returnValues(of('DSpace :: '), of('Dummy Title'), of('This is a dummy item component for testing!'));
router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']); router.navigate(['/items/0ec7ff22-f211-40ab-a69e-c819b0b1f357']);
tick(); tick();
expect(tagStore.size).toBeGreaterThan(0); expect(tagStore.size).toBeGreaterThan(0);
router.navigate(['/other']); router.navigate(['/other']);
tick(); tick();
expect(tagStore.size).toEqual(3); expect(tagStore.size).toEqual(3);
expect(title.getTitle()).toEqual('Dummy Title'); expect(title.getTitle()).toEqual('DSpace :: Dummy Title');
expect(tagStore.get('title')[0].content).toEqual('Dummy Title'); expect(tagStore.get('title')[0].content).toEqual('DSpace :: Dummy Title');
expect(tagStore.get('description')[0].content).toEqual('This is a dummy item component for testing!'); expect(tagStore.get('description')[0].content).toEqual('This is a dummy item component for testing!');
expect(tagStore.get('Generator')[0].content).toEqual('mock-dspace-version'); expect(tagStore.get('Generator')[0].content).toEqual('mock-dspace-version');
})); }));

View File

@@ -5,7 +5,7 @@ import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { catchError, distinctUntilKeyChanged, filter, first, map, take } from 'rxjs/operators'; import { catchError, distinctUntilKeyChanged, filter, first, map, take } from 'rxjs/operators';
import { hasValue, isNotEmpty } from '../../shared/empty.util'; import { hasValue, isNotEmpty } from '../../shared/empty.util';
@@ -83,9 +83,11 @@ export class MetadataService {
this.clearMetaTags(); this.clearMetaTags();
} }
if (routeInfo.data.value.title) { if (routeInfo.data.value.title) {
this.translate.get(routeInfo.data.value.title, routeInfo.data.value).pipe(take(1)).subscribe((translatedTitle: string) => { const titlePrefix = this.translate.get('repository.title.prefix');
this.addMetaTag('title', translatedTitle); const title = this.translate.get(routeInfo.data.value.title, routeInfo.data.value);
this.title.setTitle(translatedTitle); combineLatest([titlePrefix, title]).pipe(take(1)).subscribe(([translatedTitlePrefix, translatedTitle]: [string, string]) => {
this.addMetaTag('title', translatedTitlePrefix + translatedTitle);
this.title.setTitle(translatedTitlePrefix + translatedTitle);
}); });
} }
if (routeInfo.data.value.description) { if (routeInfo.data.value.description) {

View File

@@ -153,4 +153,32 @@ describe('RouteService', () => {
}); });
}); });
}); });
describe('getCurrentUrl', () => {
it('should return an observable with the current url', () => {
serviceAsAny.store = observableOf({
core: {
history: ['url', 'newurl']
}
});
service.getCurrentUrl().subscribe((history) => {
expect(history).toEqual('newurl');
});
});
});
describe('getCurrentUrl', () => {
it('should return an observable with the previous url', () => {
serviceAsAny.store = observableOf({
core: {
history: ['url', 'newurl']
}
});
service.getPreviousUrl().subscribe((history) => {
expect(history).toEqual('url');
});
});
});
}); });

View File

@@ -172,6 +172,18 @@ export class RouteService {
return this.store.pipe(select(historySelector)); return this.store.pipe(select(historySelector));
} }
/**
* Return the current url retrieved from history
*/
public getCurrentUrl(): Observable<string> {
return this.getHistory().pipe(
map((history: string[]) => history[history.length - 1] || '')
);
}
/**
* Return the current url retrieved from history
*/
public getPreviousUrl(): Observable<string> { public getPreviousUrl(): Observable<string> {
return this.getHistory().pipe( return this.getHistory().pipe(
map((history: string[]) => history[history.length - 2] || '') map((history: string[]) => history[history.length - 2] || '')

View File

@@ -104,7 +104,7 @@ export class Item extends DSpaceObject implements ChildHALResource {
* Method that returns as which type of object this object should be rendered * Method that returns as which type of object this object should be rendered
*/ */
getRenderTypes(): (string | GenericConstructor<ListableObject>)[] { getRenderTypes(): (string | GenericConstructor<ListableObject>)[] {
const entityType = this.firstMetadataValue('relationship.type'); const entityType = this.firstMetadataValue('dspace.entity.type');
if (isEmpty(entityType)) { if (isEmpty(entityType)) {
return super.getRenderTypes(); return super.getRenderTypes();
} }

View File

@@ -24,7 +24,7 @@ describe('ItemMetadataRepresentation', () => {
for (const metadataField of Object.keys(item.metadata)) { for (const metadataField of Object.keys(item.metadata)) {
describe(`when creating an ItemMetadataRepresentation`, () => { describe(`when creating an ItemMetadataRepresentation`, () => {
beforeEach(() => { beforeEach(() => {
item.metadata['relationship.type'] = [ item.metadata['dspace.entity.type'] = [
Object.assign(new MetadataValue(), { Object.assign(new MetadataValue(), {
value: itemType value: itemType
}) })
@@ -41,7 +41,7 @@ describe('ItemMetadataRepresentation', () => {
}); });
it('should return the correct item type', () => { it('should return the correct item type', () => {
expect(itemMetadataRepresentation.itemType).toEqual(item.firstMetadataValue('relationship.type')); expect(itemMetadataRepresentation.itemType).toEqual(item.firstMetadataValue('dspace.entity.type'));
}); });
}); });
} }

View File

@@ -21,7 +21,7 @@ export class ItemMetadataRepresentation extends Item implements MetadataRepresen
* The type of item this item can be represented as * The type of item this item can be represented as
*/ */
get itemType(): string { get itemType(): string {
return this.firstMetadataValue('relationship.type'); return this.firstMetadataValue('dspace.entity.type');
} }
/** /**

View File

@@ -1,21 +1,75 @@
<footer class="footer"> <footer class="top-footer text-lg-start">
<div class="container-fluid content-container-fluid"> <!-- Grid container -->
<img src="assets/images/dspace-logo.png"/> <div *ngIf="showTopFooter" class="container p-4">
<p> <!--Grid row-->
<a href="http://www.dspace.org/">{{ 'footer.link.dspace' | translate}}</a> <div class="row">
{{ 'footer.copyright' | translate:{year: dateObj | date:'y'} }}
<a href="http://www.duraspace.org/">{{ 'footer.link.duraspace' | translate}}</a> <!--Grid column-->
</p> <div class="col-lg-4 col-md-6 mb-4 mb-lg-0">
<ul class="list-unstyled small d-flex justify-content-center mb-0 text-secondary"> <h5 class="text-uppercase">Footer Content</h5>
<li>
<a href="#" (click)="showCookieSettings()">{{ 'footer.link.cookies' | translate}}</a> <ul class="list-unstyled mb-0">
</li> <li>
<li> <a routerLink="./" class="">Lorem ipsum</a>
<a routerLink="info/privacy">{{ 'footer.link.privacy-policy' | translate}}</a> </li>
</li> <li>
<li> <a routerLink="./" class="">Ut facilisis</a>
<a routerLink="info/end-user-agreement">{{ 'footer.link.end-user-agreement' | translate}}</a> </li>
</li> <li>
<a routerLink="./" class="">Aenean sit</a>
</li>
</ul> </ul>
</div>
<!--Grid column-->
<!--Grid column-->
<div class="col-lg-4 col-md-6 mb-4 mb-lg-0">
<h5 class="text-uppercase">Footer Content</h5>
<ul class="list-unstyled mb-0">
<li>
<a routerLink="./" class="">Suspendisse potenti</a>
</li>
</ul>
</div>
<!--Grid column-->
<!--Grid column-->
<div class="col-lg-4 col-md-12 mb-4 mb-md-0">
<h5 class="text-uppercase">Footer Content</h5>
<p>
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Iste atque ea quis
molestias. Fugiat pariatur maxime quis culpa corporis vitae repudiandae aliquam
voluptatem veniam, est atque cumque eum delectus sint!
</p>
</div>
<!--Grid column-->
</div> </div>
<!--Grid row-->
</div>
<!-- Grid container -->
<!-- Copyright -->
<div class="footer p-1 d-flex justify-content-center align-items-center text-white">
<div class="content-container">
<p class="m-0">
<a class="text-white" href="http://www.dspace.org/">{{ 'footer.link.dspace' | translate}}</a>
{{ 'footer.copyright' | translate:{year: dateObj | date:'y'} }}
<a class="text-white" href="http://www.duraspace.org/">{{ 'footer.link.duraspace' | translate}}</a>
</p>
<ul class="footer-info list-unstyled small d-flex justify-content-center mb-0">
<li>
<a class="text-white" href="#" (click)="showCookieSettings()">{{ 'footer.link.cookies' | translate}}</a>
</li>
<li>
<a class="text-white" routerLink="info/privacy">{{ 'footer.link.privacy-policy' | translate}}</a>
</li>
<li>
<a class="text-white" routerLink="info/end-user-agreement">{{ 'footer.link.end-user-agreement' | translate}}</a>
</li>
</ul>
</div>
</div>
<!-- Copyright -->
</footer> </footer>

View File

@@ -1,42 +1,42 @@
:host { :host {
--ds-footer-bg: var(--bs-gray-100);
--ds-footer-border: 1px solid var(--bs-gray-300);
--ds-footer-padding: calc(var(--bs-spacer) * 1.5);
--ds-footer-logo-height: 55px;
.footer { .top-footer {
background-color: var(--ds-footer-bg); background-color: var(--ds-top-footer-bg);
border-top: var(--ds-footer-border); border-top: var(--ds-footer-border);
text-align: center; text-align: center;
padding: var(--ds-footer-padding); padding: var(--ds-footer-padding);
padding-bottom: var(--bs-spacer); z-index: var(--ds-footer-z-index);
p { p {
margin: 0; margin: 0;
} }
img { div > img {
height: var(--ds-footer-logo-height); height: var(--ds-footer-logo-height);
} }
ul { .footer {
padding-top: calc(var(--bs-spacer) * 0.5); background-color: var(--ds-footer-bg);
li { ul {
display: inline-flex; li {
a { display: inline-flex;
padding: 0 calc(var(--bs-spacer) / 2); a {
color: inherit padding: 0 calc(var(--bs-spacer) / 2);
} color: inherit
&:not(:last-child) {
&:after {
content: '';
border-right: 1px var(--bs-secondary) solid;
} }
&:not(:last-child) {
&:after {
content: '';
border-right: 1px var(--bs-secondary) solid;
}
}
} }
} }
} }
} }
} }

View File

@@ -10,6 +10,11 @@ import { KlaroService } from '../shared/cookies/klaro.service';
export class FooterComponent { export class FooterComponent {
dateObj: number = Date.now(); dateObj: number = Date.now();
/**
* A boolean representing if to show or not the top footer container
*/
showTopFooter = false;
constructor(@Optional() private cookies: KlaroService) { constructor(@Optional() private cookies: KlaroService) {
} }

View File

@@ -1,4 +1,3 @@
<div [ngClass]="{'open': !(isNavBarCollapsed | async)}"> <div [ngClass]="{'open': !(isNavBarCollapsed | async)}">
<ds-themed-header></ds-themed-header> <ds-themed-header></ds-themed-header>
<ds-themed-navbar></ds-themed-navbar>
</div> </div>

View File

@@ -1,11 +1,12 @@
<header> <header class="header">
<div class="container"> <nav role="navigation" [attr.aria-label]="'nav.user.description' |translate" class="container navbar navbar-expand-md px-0">
<a class="navbar-brand my-2" routerLink="/home"> <div class="d-flex flex-grow-1">
<img src="assets/images/dspace-logo.svg"/> <a class="navbar-brand m-2" routerLink="/home">
</a> <img src="assets/images/dspace-logo.svg" alt="logo"/>
</a>
<nav class="navbar navbar-light navbar-expand-md float-right px-0"> </div>
<ds-search-navbar></ds-search-navbar> <div class="d-flex flex-grow-1 ml-auto justify-content-end align-items-center">
<ds-search-navbar class="navbar-search"></ds-search-navbar>
<ds-lang-switch></ds-lang-switch> <ds-lang-switch></ds-lang-switch>
<ds-auth-nav-menu></ds-auth-nav-menu> <ds-auth-nav-menu></ds-auth-nav-menu>
<ds-impersonate-navbar></ds-impersonate-navbar> <ds-impersonate-navbar></ds-impersonate-navbar>
@@ -16,6 +17,8 @@
<span class="navbar-toggler-icon fas fa-bars fa-fw" aria-hidden="true"></span> <span class="navbar-toggler-icon fas fa-bars fa-fw" aria-hidden="true"></span>
</button> </button>
</div> </div>
</nav> </div>
</div> </nav>
<ds-themed-navbar></ds-themed-navbar>
</header> </header>

View File

@@ -1,21 +1,19 @@
@media screen and (min-width: map-get($grid-breakpoints, md)) {
nav.navbar {
display: none;
}
.header {
background-color: var(--ds-header-bg);
}
}
.navbar-brand img { .navbar-brand img {
height: var(--ds-header-logo-height); @media screen and (max-width: map-get($grid-breakpoints, md)) {
@media screen and (max-width: map-get($grid-breakpoints, sm)) {
height: var(--ds-header-logo-height-xs); height: var(--ds-header-logo-height-xs);
} }
} }
.navbar-toggler .navbar-toggler-icon { .navbar-toggler .navbar-toggler-icon {
background-image: none !important; background-image: none !important;
line-height: 1.5; line-height: 1.5;
color: var(--bs-link-color);
} }
.navbar ::ng-deep {
a {
color: var(--ds-header-icon-color);
&:hover, &focus {
color: var(--ds-header-icon-color-hover);
}
}
}

View File

@@ -1,4 +1,4 @@
<li class="nav-item dropdown" <li class="nav-item dropdown h-100 d-flex flex-column justify-content-center"
(mouseenter)="activateSection($event)" (mouseenter)="activateSection($event)"
(mouseleave)="deactivateSection($event)"> (mouseleave)="deactivateSection($event)">
<a href="#" class="nav-link dropdown-toggle" routerLinkActive="active" <a href="#" class="nav-link dropdown-toggle" routerLinkActive="active"

View File

@@ -1,4 +1,4 @@
<li class="nav-item"> <li class="nav-item h-100 d-flex flex-column justify-content-center">
<ng-container <ng-container
*ngComponentOutlet="(sectionMap$ | async).get(section.id).component; injector: (sectionMap$ | async).get(section.id).injector;"></ng-container> *ngComponentOutlet="(sectionMap$ | async).get(section.id).component; injector: (sectionMap$ | async).get(section.id).injector;"></ng-container>
</li> </li>

View File

@@ -1,17 +1,24 @@
<nav [ngClass]="{'open': !(menuCollapsed | async)}" <nav [ngClass]="{'open': !(menuCollapsed | async)}"
[@slideMobileNav]="!(windowService.isXsOrSm() | async) ? 'default' : ((menuCollapsed | async) ? 'collapsed' : 'expanded')" [@slideMobileNav]="!(windowService.isXsOrSm() | async) ? 'default' : ((menuCollapsed | async) ? 'collapsed' : 'expanded')"
class="navbar navbar-light navbar-expand-md p-md-0 navbar-container"> <!-- TODO remove navbar-container class when https://github.com/twbs/bootstrap/issues/24726 is fixed --> class="navbar navbar-expand-md navbar-light p-0 navbar-container"
<div class="container"> role="navigation" role="navigation" [attr.aria-label]="'nav.main.description' |translate">
<div class="reset-padding-md w-100"> <div class="container h-100">
<div id="collapsingNav"> <a class="navbar-brand my-2" routerLink="/home">
<ul class="navbar-nav mr-auto shadow-none"> <img src="assets/images/dspace-logo.svg" alt="logo"/>
<ng-container *ngFor="let section of (sections | async)"> </a>
<ng-container
*ngComponentOutlet="(sectionMap$ | async).get(section.id).component; injector: (sectionMap$ | async).get(section.id).injector;"></ng-container> <div id="collapsingNav" class="w-100 h-100">
</ng-container> <ul class="navbar-nav me-auto mb-2 mb-lg-0 h-100">
</ul> <ng-container *ngFor="let section of (sections | async)">
</div> <ng-container
</div> *ngComponentOutlet="(sectionMap$ | async).get(section.id).component; injector: (sectionMap$ | async).get(section.id).injector;"></ng-container>
</ng-container>
</ul>
</div> </div>
<ds-search-navbar class="navbar-collapsed"></ds-search-navbar>
<ds-lang-switch class="navbar-collapsed"></ds-lang-switch>
<ds-auth-nav-menu class="navbar-collapsed"></ds-auth-nav-menu>
<ds-impersonate-navbar class="navbar-collapsed"></ds-impersonate-navbar>
</div>
</nav> </nav>

View File

@@ -1,6 +1,8 @@
nav.navbar { nav.navbar {
border-bottom: 1px var(--bs-gray-400) solid; border-top: 1px var(--ds-header-navbar-border-top-color) solid;
border-bottom: 1px var(--ds-header-navbar-border-bottom-color) solid;
align-items: baseline; align-items: baseline;
color: var(--ds-header-icon-color);
} }
/** Mobile menu styling **/ /** Mobile menu styling **/
@@ -29,7 +31,27 @@ nav.navbar {
@media screen and (max-width: map-get($grid-breakpoints, md)) { @media screen and (max-width: map-get($grid-breakpoints, md)) {
> .container { > .container {
padding: 0 var(--bs-spacer); padding: 0 var(--bs-spacer);
a.navbar-brand {
display: none;
}
.navbar-collapsed {
display: none;
}
} }
padding: 0; padding: 0;
} }
height: 80px;
}
a.navbar-brand img {
max-height: var(--ds-header-logo-height);
}
.navbar-nav {
::ng-deep a.nav-link {
color: var(--ds-navbar-link-color);
}
::ng-deep a.nav-link:hover {
color: var(--ds-navbar-link-color-hover);
}
} }

View File

@@ -41,6 +41,17 @@ export class NavbarComponent extends MenuComponent {
*/ */
createMenu() { createMenu() {
const menuList: any[] = [ const menuList: any[] = [
/* Communities & Collections tree */
{
id: `browse_global_communities_and_collections`,
active: false,
visible: true,
model: {
type: MenuItemType.LINK,
text: `menu.section.browse_global_communities_and_collections`,
link: `/community-list`
} as LinkMenuItemModel
},
/* News */ /* News */
{ {
id: 'browse_global', id: 'browse_global',
@@ -52,18 +63,6 @@ export class NavbarComponent extends MenuComponent {
} as TextMenuItemModel, } as TextMenuItemModel,
index: 0 index: 0
}, },
/* Communities & Collections tree */
{
id: `browse_global_communities_and_collections`,
parentID: 'browse_global',
active: false,
visible: true,
model: {
type: MenuItemType.LINK,
text: `menu.section.browse_global_communities_and_collections`,
link: `/community-list`
} as LinkMenuItemModel
},
]; ];
// Read the different Browse-By types from config and add them to the browse menu // Read the different Browse-By types from config and add them to the browse menu
const types = environment.browseBy.types; const types = environment.browseBy.types;

View File

@@ -9,6 +9,7 @@ import { NavbarSectionComponent } from './navbar-section/navbar-section.componen
import { ExpandableNavbarSectionComponent } from './expandable-navbar-section/expandable-navbar-section.component'; import { ExpandableNavbarSectionComponent } from './expandable-navbar-section/expandable-navbar-section.component';
import { NavbarComponent } from './navbar.component'; import { NavbarComponent } from './navbar.component';
import { MenuModule } from '../shared/menu/menu.module'; import { MenuModule } from '../shared/menu/menu.module';
import { SharedModule } from '../shared/shared.module';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { ThemedNavbarComponent } from './themed-navbar.component'; import { ThemedNavbarComponent } from './themed-navbar.component';
@@ -25,6 +26,7 @@ const ENTRY_COMPONENTS = [
@NgModule({ @NgModule({
imports: [ imports: [
CommonModule, CommonModule,
SharedModule,
MenuModule, MenuModule,
FormsModule, FormsModule,
EffectsModule.forFeature(effects), EffectsModule.forFeature(effects),

View File

@@ -2,5 +2,6 @@
[formId]="'profile-page-metadata-form-id'" [formId]="'profile-page-metadata-form-id'"
[formModel]="formModel" [formModel]="formModel"
[formGroup]="formGroup" [formGroup]="formGroup"
[displaySubmit]="false"> [displaySubmit]="false"
[displayCancel]="false">
</ds-form> </ds-form>

View File

@@ -3,7 +3,8 @@
[formId]="FORM_PREFIX" [formId]="FORM_PREFIX"
[formModel]="formModel" [formModel]="formModel"
[formGroup]="formGroup" [formGroup]="formGroup"
[displaySubmit]="false"> [displaySubmit]="false"
[displayCancel]="false">
</ds-form> </ds-form>
<div id="notLongEnough" class="container-fluid text-danger" *ngIf="formGroup.hasError('notLongEnough')">{{FORM_PREFIX + 'error.password-length' | translate}}</div> <div id="notLongEnough" class="container-fluid text-danger" *ngIf="formGroup.hasError('notLongEnough')">{{FORM_PREFIX + 'error.password-length' | translate}}</div>
<div id="notSame" class="container-fluid text-danger" *ngIf="formGroup.hasError('notSame')">{{FORM_PREFIX + 'error.matching-passwords' | translate}}</div> <div id="notSame" class="container-fluid text-danger" *ngIf="formGroup.hasError('notSame')">{{FORM_PREFIX + 'error.matching-passwords' | translate}}</div>

View File

@@ -17,7 +17,9 @@
></ds-profile-page-security-form> ></ds-profile-page-security-form>
</div> </div>
</div> </div>
<button class="btn btn-outline-primary" (click)="updateProfile()">{{'profile.form.submit' | translate}}</button> <div class="col-12 text-right pr-0">
<button class="btn btn-primary" (click)="updateProfile()"><i class="fas fa-edit"></i> {{'profile.form.submit' | translate}}</button>
</div>
<ng-container *ngVar="(groupsRD$ | async)?.payload?.page as groups"> <ng-container *ngVar="(groupsRD$ | async)?.payload?.page as groups">
<div *ngIf="groups"> <div *ngIf="groups">

View File

@@ -10,14 +10,14 @@
[options]="notificationOptions"> [options]="notificationOptions">
</ds-notifications-board> </ds-notifications-board>
<main class="main-content"> <main class="main-content">
<div class="container"> <ds-themed-breadcrumbs></ds-themed-breadcrumbs>
<ds-themed-breadcrumbs></ds-themed-breadcrumbs>
</div>
<div class="container" *ngIf="isLoading"> <div class="container d-flex justify-content-center align-items-center h-100" *ngIf="isLoading">
<ds-loading message="{{'loading.default' | translate}}"></ds-loading> <ds-loading [showMessage]="false"></ds-loading>
</div>
<div [class.d-none]="isLoading">
<router-outlet></router-outlet>
</div> </div>
<router-outlet></router-outlet>
</main> </main>
<ds-themed-footer></ds-themed-footer> <ds-themed-footer></ds-themed-footer>

View File

@@ -4,7 +4,7 @@
<input #searchInput [@toggleAnimation]="isExpanded" id="query" name="query" <input #searchInput [@toggleAnimation]="isExpanded" id="query" name="query"
formControlName="query" type="text" placeholder="{{searchExpanded ? ('nav.search' | translate) : ''}}" formControlName="query" type="text" placeholder="{{searchExpanded ? ('nav.search' | translate) : ''}}"
class="d-inline-block bg-transparent position-absolute form-control dropdown-menu-right p-1"> class="d-inline-block bg-transparent position-absolute form-control dropdown-menu-right p-1">
<a class="sticky-top submit-icon" (click)="searchExpanded ? onSubmit(searchForm.value) : expand()"> <a class="submit-icon" (click)="searchExpanded ? onSubmit(searchForm.value) : expand()">
<em class="fas fa-search fa-lg fa-fw"></em> <em class="fas fa-search fa-lg fa-fw"></em>
</a> </a>
</form> </form>

View File

@@ -12,14 +12,18 @@ input[type="text"] {
a.submit-icon { a.submit-icon {
cursor: pointer; cursor: pointer;
position: sticky;
top: 0;
} }
@media screen and (max-width: map-get($grid-breakpoints, md)) {
@media screen and (max-width: map-get($grid-breakpoints, sm)) {
#query:focus { #query:focus {
max-width: 250px !important; max-width: 250px !important;
width: 40vw !important; width: 40vw !important;
} }
a.submit-icon {
color: var(--bs-link-color);
}
} }

View File

@@ -12,22 +12,24 @@
</div> </div>
</li> </li>
<li *ngIf="!(isAuthenticated | async) && (isXsOrSm$ | async)" class="nav-item"> <li *ngIf="!(isAuthenticated | async) && (isXsOrSm$ | async)" class="nav-item">
<a id="loginLink" routerLink="/login" routerLinkActive="active" class="px-1">{{ 'nav.login' | translate }}<span <a id="loginLink" routerLink="/login" routerLinkActive="active" class="px-1">
class="sr-only">(current)</span></a> {{ 'nav.login' | translate }}<span class="sr-only">(current)</span>
</a>
</li> </li>
<li *ngIf="(isAuthenticated | async) && !(isXsOrSm$ | async) && (showAuth | async)" class="nav-item"> <li *ngIf="(isAuthenticated | async) && !(isXsOrSm$ | async) && (showAuth | async)" class="nav-item">
<div ngbDropdown display="dynamic" placement="bottom-right" class="d-inline-block" @fadeInOut> <div ngbDropdown display="dynamic" placement="bottom-right" class="d-inline-block" @fadeInOut>
<a href="#" id="dropdownUser" (click)="$event.preventDefault()" class="px-1" ngbDropdownToggle><i <a href="#" id="dropdownUser" role="button" [attr.aria-label]="'nav.logout' |translate" (click)="$event.preventDefault()" [title]="'nav.logout' | translate" class="px-1" ngbDropdownToggle>
class="fas fa-user-circle fa-lg fa-fw" [title]="'nav.logout' | translate"></i></a> <i class="fas fa-user-circle fa-lg fa-fw"></i></a>
<div id="logoutDropdownMenu" ngbDropdownMenu aria-labelledby="dropdownUser"> <div id="logoutDropdownMenu" ngbDropdownMenu aria-labelledby="dropdownUser">
<ds-user-menu></ds-user-menu> <ds-user-menu></ds-user-menu>
</div> </div>
</div> </div>
</li> </li>
<li *ngIf="(isAuthenticated | async) && (isXsOrSm$ | async)" class="nav-item"> <li *ngIf="(isAuthenticated | async) && (isXsOrSm$ | async)" class="nav-item">
<a id="logoutLink" routerLink="/logout" routerLinkActive="active" class="px-1"><i <a id="logoutLink" role="button" [attr.aria-label]="'nav.logout' |translate" [title]="'nav.logout' | translate" routerLink="/logout" routerLinkActive="active" class="px-1">
class="fas fa-user-circle fa-lg fa-fw" [title]="'nav.logout' | translate"></i><span <i class="fas fa-user-circle fa-lg fa-fw"></i>
class="sr-only">(current)</span></a> <span class="sr-only">(current)</span>
</a>
</li> </li>
</ul> </ul>

View File

@@ -11,3 +11,11 @@
.dropdown-item:hover, .dropdown-item:focus { .dropdown-item:hover, .dropdown-item:focus {
background-color: transparent !important; background-color: transparent !important;
} }
.dropdown-toggle {
color: var(--ds-header-icon-color) !important;
&:hover, &focus {
color: var(--ds-header-icon-color-hover);
}
}

View File

@@ -13,7 +13,7 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div *ngIf="!hideGear" ngbDropdown #paginationControls="ngbDropdown" placement="bottom-right" class="d-inline-block float-right"> <div *ngIf="!hideGear" ngbDropdown #paginationControls="ngbDropdown" placement="bottom-right" class="d-inline-block float-right">
<button class="btn btn-outline-primary" id="paginationControls" ngbDropdownToggle><i class="fas fa-cog" aria-hidden="true"></i></button> <button class="btn btn-secondary" id="paginationControls" [title]="'pagination.options.description' | translate" [attr.aria-label]="'pagination.options.description' | translate" ngbDropdownToggle><i class="fas fa-cog" aria-hidden="true"></i></button>
<div id="paginationControlsDropdownMenu" aria-labelledby="paginationControls" ngbDropdownMenu> <div id="paginationControlsDropdownMenu" aria-labelledby="paginationControls" ngbDropdownMenu>
<h6 class="dropdown-header">{{ 'pagination.results-per-page' | translate}}</h6> <h6 class="dropdown-header">{{ 'pagination.results-per-page' | translate}}</h6>
<button class="dropdown-item page-size-change" *ngFor="let item of paginationConfig?.pageSizeOptions" (click)="doPageSizeChange(item)"><i [ngClass]="{'invisible': item != paginationConfig?.pageSize}" class="fas fa-check" aria-hidden="true"></i> {{item}} </button> <button class="dropdown-item page-size-change" *ngFor="let item of paginationConfig?.pageSizeOptions" (click)="doPageSizeChange(item)"><i [ngClass]="{'invisible': item != paginationConfig?.pageSize}" class="fas fa-check" aria-hidden="true"></i> {{item}} </button>
@@ -29,8 +29,8 @@
</li> </li>
</ul> </ul>
<div> <div>
<button id="nav-prev" type="button" class="btn btn-outline-secondary float-left" (click)="goPrev()" [disabled]="objects?.payload?.currentPage <= 1"><<</button> <button id="nav-prev" type="button" class="btn btn-outline-primary float-left" (click)="goPrev()" [disabled]="objects?.payload?.currentPage <= 1"><i class="fas fa-angle-left"></i> {{'browse.previous.button' |translate}}</button>
<button id="nav-next" type="button" class="btn btn-outline-secondary float-right" (click)="goNext()" [disabled]="objects?.payload?.currentPage >= objects?.payload?.totalPages">>></button> <button id="nav-next" type="button" class="btn btn-outline-primary float-right" (click)="goNext()" [disabled]="objects?.payload?.currentPage >= objects?.payload?.totalPages"><i class="fas fa-angle-right"></i> {{'browse.next.button' |translate}}</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -15,7 +15,7 @@
(dragend)="onDragEnd(i)" (dragend)="onDragEnd(i)"
(mouseover)="showTooltip(t, i)" (mouseover)="showTooltip(t, i)"
(mouseout)="t.close()"> (mouseout)="t.close()">
<a class="flex-sm-fill text-sm-center nav-link active" <a class="flex-sm-fill text-sm-center nav-link active bg-info"
href="#" href="#"
[ngClass]="{'chip-selected disabled': (editable && c.editMode) || dragged == i}" [ngClass]="{'chip-selected disabled': (editable && c.editMode) || dragged == i}"
(click)="chipsSelected($event, i);"> (click)="chipsSelected($event, i);">
@@ -42,7 +42,8 @@
</a> </a>
</li> </li>
</ng-container> </ng-container>
<div [class.chips-sort-ignore]="(isDragging | async)" class="flex-grow-1">
<ng-content></ng-content> <ng-content ></ng-content>
</div>
</ul> </ul>
</div> </div>

View File

@@ -1,5 +1,5 @@
.chip-selected { .chip-selected {
background-color: var(--bs-info) !important; background-color: var(--bs-secondary) !important;
} }
.chip-label { .chip-label {

View File

@@ -8,6 +8,7 @@ import { ChipsItem } from './models/chips-item.model';
import { UploaderService } from '../uploader/uploader.service'; import { UploaderService } from '../uploader/uploader.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { Options } from 'sortablejs'; import { Options } from 'sortablejs';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
@Component({ @Component({
selector: 'ds-chips', selector: 'ds-chips',
@@ -25,6 +26,7 @@ export class ChipsComponent implements OnChanges {
@Output() remove: EventEmitter<number> = new EventEmitter<number>(); @Output() remove: EventEmitter<number> = new EventEmitter<number>();
@Output() change: EventEmitter<any> = new EventEmitter<any>(); @Output() change: EventEmitter<any> = new EventEmitter<any>();
isDragging: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
options: Options; options: Options;
dragged = -1; dragged = -1;
tipText: string[]; tipText: string[];
@@ -73,6 +75,7 @@ export class ChipsComponent implements OnChanges {
} }
onDragStart(index) { onDragStart(index) {
this.isDragging.next(true);
this.uploaderService.overrideDragOverPage(); this.uploaderService.overrideDragOverPage();
this.dragged = index; this.dragged = index;
} }
@@ -81,6 +84,7 @@ export class ChipsComponent implements OnChanges {
this.uploaderService.allowDragOverPage(); this.uploaderService.allowDragOverPage();
this.dragged = -1; this.dragged = -1;
this.chips.updateOrder(); this.chips.updateOrder();
this.isDragging.next(false);
} }
showTooltip(tooltip: NgbTooltip, index, field?) { showTooltip(tooltip: NgbTooltip, index, field?) {

View File

@@ -11,10 +11,14 @@
</div> </div>
<div class="col-4 d-inline-block"> <div class="col-4 d-inline-block">
<div *ngIf="logo" class="btn-group btn-group-sm float-right" role="group"> <div *ngIf="logo" class="btn-group btn-group-sm float-right" role="group">
<button *ngIf="!markLogoForDeletion" type="button" class="btn btn-danger" (click)="deleteLogo()"> <button *ngIf="!markLogoForDeletion" type="button" class="btn btn-danger"
title="{{type.value + '.edit.logo.delete.title' | translate}}"
(click)="deleteLogo()">
<i class="fas fa-trash" aria-hidden="true"></i> <i class="fas fa-trash" aria-hidden="true"></i>
</button> </button>
<button *ngIf="markLogoForDeletion" type="button" class="btn btn-warning" (click)="undoDeleteLogo()"> <button *ngIf="markLogoForDeletion" type="button" class="btn btn-warning"
title="{{type.value + '.edit.logo.delete-undo.title' | translate}}"
(click)="undoDeleteLogo()">
<i class="fas fa-undo" aria-hidden="true"></i> <i class="fas fa-undo" aria-hidden="true"></i>
</button> </button>
</div> </div>
@@ -35,4 +39,10 @@
</div> </div>
<ds-form *ngIf="formModel" <ds-form *ngIf="formModel"
[formId]="'comcol-form-id'" [formId]="'comcol-form-id'"
[formModel]="formModel" (submitForm)="onSubmit()" (cancel)="onCancel()"></ds-form> [formModel]="formModel"
[displayCancel]="false"
(submitForm)="onSubmit()">
<button before (click)="back.emit()" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left"></i> {{ type.value + '.edit.return' | translate }}
</button>
</ds-form>

Some files were not shown because too many files have changed in this diff Show More