mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 01:54:15 +00:00
Add support for dynamic themes
This commit is contained in:
@@ -339,7 +339,6 @@ dspace-angular
|
||||
├── tslint.json * TSLint (https://palantir.github.io/tslint/) configuration
|
||||
├── typedoc.json * TYPEDOC configuration
|
||||
├── webpack * Webpack (https://webpack.github.io/) config directory
|
||||
│ ├── helpers.js *
|
||||
│ ├── webpack.aot.js * Webpack (https://webpack.github.io/) config for AoT build
|
||||
│ ├── webpack.client.js * Webpack (https://webpack.github.io/) config for client build
|
||||
│ ├── webpack.common.js *
|
||||
|
18
angular.json
18
angular.json
@@ -17,6 +17,7 @@
|
||||
"build": {
|
||||
"builder": "@angular-builders/custom-webpack:browser",
|
||||
"options": {
|
||||
"extractCss": true,
|
||||
"preserveSymlinks": true,
|
||||
"customWebpackConfig": {
|
||||
"path": "./webpack/webpack.browser.ts",
|
||||
@@ -46,7 +47,16 @@
|
||||
"src/robots.txt"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
{
|
||||
"input": "src/styles/base-theme.scss",
|
||||
"inject": false,
|
||||
"bundleName": "base-theme"
|
||||
},
|
||||
{
|
||||
"input": "src/themes/custom/styles/theme.scss",
|
||||
"inject": false,
|
||||
"bundleName": "custom-theme"
|
||||
}
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
@@ -116,7 +126,11 @@
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
{
|
||||
"input": "src/styles/base-theme.scss",
|
||||
"inject": false,
|
||||
"bundleName": "base-theme"
|
||||
}
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
|
@@ -20,7 +20,9 @@
|
||||
"serve": "ts-node --project ./tsconfig.ts-node.json scripts/serve.ts",
|
||||
"start:dev": "npm-run-all --parallel config:dev:watch serve",
|
||||
"start:prod": "yarn run build:prod && yarn run serve:ssr",
|
||||
"analyze": "webpack-bundle-analyzer dist/browser/stats.json",
|
||||
"build": "ng build",
|
||||
"build:stats": "ng build --stats-json",
|
||||
"build:prod": "yarn run build:ssr",
|
||||
"build:ssr": "yarn run build:client-and-server-bundles && yarn run compile:server",
|
||||
"build:client-and-server-bundles": "ng build --prod && ng run dspace-angular:server:production --bundleDependencies true",
|
||||
@@ -179,7 +181,7 @@
|
||||
"tslint": "^6.1.3",
|
||||
"typescript": "~4.0.5",
|
||||
"webpack": "^4.44.2",
|
||||
"webpack-bundle-analyzer": "^3.3.2",
|
||||
"webpack-bundle-analyzer": "^4.4.0",
|
||||
"webpack-cli": "^4.2.0",
|
||||
"webpack-node-externals": "1.7.2"
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('postcss-import')(),
|
||||
require('postcss-cssnext')(),
|
||||
require('postcss-preset-env')(),
|
||||
require('postcss-apply')(),
|
||||
require('postcss-responsive-type')()
|
||||
]
|
||||
|
@@ -1,22 +0,0 @@
|
||||
const syncBuildDir = require('copyfiles');
|
||||
const path = require('path');
|
||||
const {
|
||||
projectRoot,
|
||||
theme,
|
||||
themePath,
|
||||
} = require('../webpack/helpers');
|
||||
|
||||
const projectDepth = projectRoot('./').split(path.sep).length;
|
||||
|
||||
let callback;
|
||||
|
||||
if (theme !== null && theme !== undefined) {
|
||||
callback = () => {
|
||||
syncBuildDir([path.join(themePath, '**/*'), 'build'], { up: projectDepth + 2 }, () => {})
|
||||
}
|
||||
}
|
||||
else {
|
||||
callback = () => {};
|
||||
}
|
||||
|
||||
syncBuildDir([projectRoot('src/**/*'), 'build'], { up: projectDepth + 1 }, callback);
|
@@ -16,6 +16,8 @@ import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { ItemSearchResult } from '../../../../../shared/object-collection/shared/item-search-result.model';
|
||||
import { ItemAdminSearchResultGridElementComponent } from './item-admin-search-result-grid-element.component';
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
|
||||
import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock';
|
||||
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
|
||||
|
||||
describe('ItemAdminSearchResultGridElementComponent', () => {
|
||||
let component: ItemAdminSearchResultGridElementComponent;
|
||||
@@ -29,6 +31,8 @@ describe('ItemAdminSearchResultGridElementComponent', () => {
|
||||
}
|
||||
};
|
||||
|
||||
const mockThemeService = getMockThemeService();
|
||||
|
||||
function init() {
|
||||
id = '780b2588-bda5-4112-a1cd-0b15000a5339';
|
||||
searchResult = new ItemSearchResult();
|
||||
@@ -50,6 +54,7 @@ describe('ItemAdminSearchResultGridElementComponent', () => {
|
||||
providers: [
|
||||
{ provide: TruncatableService, useValue: mockTruncatableService },
|
||||
{ provide: BitstreamDataService, useValue: mockBitstreamDataService },
|
||||
{ provide: ThemeService, useValue: mockThemeService },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
|
@@ -12,6 +12,7 @@ import { TruncatableService } from '../../../../../shared/truncatable/truncatabl
|
||||
import { BitstreamDataService } from '../../../../../core/data/bitstream-data.service';
|
||||
import { GenericConstructor } from '../../../../../core/shared/generic-constructor';
|
||||
import { ListableObjectDirective } from '../../../../../shared/object-collection/shared/listable-object/listable-object.directive';
|
||||
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
|
||||
|
||||
@listableObjectComponent(ItemSearchResult, ViewMode.GridElement, Context.AdminSearch)
|
||||
@Component({
|
||||
@@ -29,6 +30,7 @@ export class ItemAdminSearchResultGridElementComponent extends SearchResultGridE
|
||||
|
||||
constructor(protected truncatableService: TruncatableService,
|
||||
protected bitstreamDataService: BitstreamDataService,
|
||||
private themeService: ThemeService,
|
||||
private componentFactoryResolver: ComponentFactoryResolver
|
||||
) {
|
||||
super(truncatableService, bitstreamDataService);
|
||||
@@ -63,6 +65,6 @@ export class ItemAdminSearchResultGridElementComponent extends SearchResultGridE
|
||||
* @returns {GenericConstructor<Component>}
|
||||
*/
|
||||
private getComponent(): GenericConstructor<Component> {
|
||||
return getListableObjectComponent(this.object.getRenderTypes(), ViewMode.GridElement, undefined);
|
||||
return getListableObjectComponent(this.object.getRenderTypes(), ViewMode.GridElement, undefined, this.themeService.getThemeName());
|
||||
}
|
||||
}
|
||||
|
@@ -1,12 +1,12 @@
|
||||
$icon-z-index: 10;
|
||||
|
||||
:host {
|
||||
--ds-icon-z-index: 10;
|
||||
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
flex: 1 1 auto;
|
||||
nav {
|
||||
background-color: $admin-sidebar-bg;
|
||||
background-color: var(--ds-admin-sidebar-bg);
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
> div {
|
||||
@@ -19,12 +19,12 @@ $icon-z-index: 10;
|
||||
}
|
||||
|
||||
&.inactive ::ng-deep .sidebar-collapsible {
|
||||
margin-left: -#{$sidebar-items-width};
|
||||
margin-left: calc(-1 * var(--ds-sidebar-items-width));
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
.admin-menu-header {
|
||||
background-color: $admin-sidebar-header-bg;
|
||||
background-color: var(--ds-admin-sidebar-header-bg);
|
||||
.logo-wrapper {
|
||||
img {
|
||||
height: 20px;
|
||||
@@ -43,29 +43,29 @@ $icon-z-index: 10;
|
||||
.sidebar-section {
|
||||
display: flex;
|
||||
align-content: stretch;
|
||||
background-color: $admin-sidebar-bg;
|
||||
background-color: var(--ds-admin-sidebar-bg);
|
||||
.nav-item {
|
||||
padding-top: $spacer;
|
||||
padding-bottom: $spacer;
|
||||
padding-top: var(--bs-spacer);
|
||||
padding-bottom: var(--bs-spacer);
|
||||
}
|
||||
.shortcut-icon {
|
||||
padding-left: $icon-padding;
|
||||
padding-right: $icon-padding;
|
||||
padding-left: var(--ds-icon-padding);
|
||||
padding-right: var(--ds-icon-padding);
|
||||
}
|
||||
.shortcut-icon, .icon-wrapper {
|
||||
background-color: inherit;
|
||||
z-index: $icon-z-index;
|
||||
z-index: var(--ds-icon-z-index);
|
||||
}
|
||||
.sidebar-collapsible {
|
||||
width: $sidebar-items-width;
|
||||
width: var(--ds-sidebar-items-width);
|
||||
position: relative;
|
||||
a {
|
||||
padding-right: $spacer;
|
||||
padding-right: var(--bs-spacer);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
&.active > .sidebar-collapsible > .nav-link {
|
||||
color: $navbar-dark-active-color;
|
||||
color: var(--bs-navbar-dark-active-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,13 +1,13 @@
|
||||
:host ::ng-deep {
|
||||
.fa-chevron-right {
|
||||
padding-left: $spacer/2;
|
||||
padding-left: calc(var(--bs-spacer) / 2);
|
||||
font-size: 0.5rem;
|
||||
line-height: 3;
|
||||
}
|
||||
|
||||
.sidebar-sub-level-items {
|
||||
list-style: disc;
|
||||
color: $navbar-dark-color;
|
||||
color: var(--bs-navbar-dark-color);
|
||||
overflow: hidden;
|
||||
|
||||
}
|
||||
|
@@ -19,6 +19,8 @@ import { BitstreamDataService } from '../../../../../core/data/bitstream-data.se
|
||||
import { createSuccessfulRemoteDataObject$ } from '../../../../../shared/remote-data.utils';
|
||||
import { getMockLinkService } from '../../../../../shared/mocks/link-service.mock';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { getMockThemeService } from '../../../../../shared/mocks/theme-service.mock';
|
||||
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
|
||||
|
||||
describe('WorkflowItemAdminWorkflowGridElementComponent', () => {
|
||||
let component: WorkflowItemSearchResultAdminWorkflowGridElementComponent;
|
||||
@@ -28,6 +30,7 @@ describe('WorkflowItemAdminWorkflowGridElementComponent', () => {
|
||||
let itemRD$;
|
||||
let linkService;
|
||||
let object;
|
||||
let themeService;
|
||||
|
||||
function init() {
|
||||
itemRD$ = createSuccessfulRemoteDataObject$(new Item());
|
||||
@@ -37,6 +40,7 @@ describe('WorkflowItemAdminWorkflowGridElementComponent', () => {
|
||||
wfi.item = itemRD$;
|
||||
object.indexableObject = wfi;
|
||||
linkService = getMockLinkService();
|
||||
themeService = getMockThemeService();
|
||||
}
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
@@ -51,6 +55,7 @@ describe('WorkflowItemAdminWorkflowGridElementComponent', () => {
|
||||
],
|
||||
providers: [
|
||||
{ provide: LinkService, useValue: linkService },
|
||||
{ provide: ThemeService, useValue: themeService },
|
||||
{
|
||||
provide: TruncatableService, useValue: {
|
||||
isCollapsed: () => observableOf(true),
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import { Component, ComponentFactoryResolver, ElementRef, ViewChild } from '@angular/core';
|
||||
import { Item } from '../../../../../core/shared/item.model';
|
||||
import { ViewMode } from '../../../../../core/shared/view-mode.model';
|
||||
import { getListableObjectComponent, listableObjectComponent } from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||
import {
|
||||
getListableObjectComponent,
|
||||
listableObjectComponent
|
||||
} from '../../../../../shared/object-collection/shared/listable-object/listable-object.decorator';
|
||||
import { Context } from '../../../../../core/shared/context.model';
|
||||
import { SearchResultGridElementComponent } from '../../../../../shared/object-grid/search-result-grid-element/search-result-grid-element.component';
|
||||
import { TruncatableService } from '../../../../../shared/truncatable/truncatable.service';
|
||||
@@ -13,9 +16,13 @@ import { Observable } from 'rxjs';
|
||||
import { LinkService } from '../../../../../core/cache/builders/link.service';
|
||||
import { followLink } from '../../../../../shared/utils/follow-link-config.model';
|
||||
import { RemoteData } from '../../../../../core/data/remote-data';
|
||||
import { getAllSucceededRemoteData, getRemoteDataPayload } from '../../../../../core/shared/operators';
|
||||
import {
|
||||
getAllSucceededRemoteData,
|
||||
getRemoteDataPayload
|
||||
} from '../../../../../core/shared/operators';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { WorkflowItemSearchResult } from '../../../../../shared/object-collection/shared/workflow-item-search-result.model';
|
||||
import { ThemeService } from '../../../../../shared/theme-support/theme.service';
|
||||
|
||||
@listableObjectComponent(WorkflowItemSearchResult, ViewMode.GridElement, Context.AdminWorkflowSearch)
|
||||
@Component({
|
||||
@@ -51,6 +58,7 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S
|
||||
private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private linkService: LinkService,
|
||||
protected truncatableService: TruncatableService,
|
||||
private themeService: ThemeService,
|
||||
protected bitstreamDataService: BitstreamDataService
|
||||
) {
|
||||
super(truncatableService, bitstreamDataService);
|
||||
@@ -92,7 +100,7 @@ export class WorkflowItemSearchResultAdminWorkflowGridElementComponent extends S
|
||||
* @returns {GenericConstructor<Component>}
|
||||
*/
|
||||
private getComponent(item: Item): GenericConstructor<Component> {
|
||||
return getListableObjectComponent(item.getRenderTypes(), ViewMode.GridElement, undefined);
|
||||
return getListableObjectComponent(item.getRenderTypes(), ViewMode.GridElement, undefined, this.themeService.getThemeName());
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@
|
||||
::ng-deep {
|
||||
.switch {
|
||||
position: absolute;
|
||||
top: $spacer*2.5;
|
||||
top: calc(var(--bs-spacer) * 2.5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -6,17 +6,21 @@ describe('CollectionPageResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: CollectionPageResolver;
|
||||
let collectionService: any;
|
||||
let store: any;
|
||||
const uuid = '1234-65487-12354-1235';
|
||||
|
||||
beforeEach(() => {
|
||||
collectionService = {
|
||||
findById: (id: string) => createSuccessfulRemoteDataObject$({ id })
|
||||
};
|
||||
resolver = new CollectionPageResolver(collectionService);
|
||||
store = jasmine.createSpyObj('store', {
|
||||
dispatch: {},
|
||||
});
|
||||
resolver = new CollectionPageResolver(collectionService, store);
|
||||
});
|
||||
|
||||
it('should resolve a collection with the correct id', (done) => {
|
||||
resolver.resolve({ params: { id: uuid } } as any, undefined)
|
||||
resolver.resolve({ params: { id: uuid } } as any, { url: 'current-url' } as any)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(resolved) => {
|
||||
|
@@ -4,15 +4,31 @@ import { Collection } from '../core/shared/collection.model';
|
||||
import { Observable } from 'rxjs';
|
||||
import { CollectionDataService } from '../core/data/collection-data.service';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { followLink } from '../shared/utils/follow-link-config.model';
|
||||
import { followLink, FollowLinkConfig } from '../shared/utils/follow-link-config.model';
|
||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { ResolvedAction } from '../core/resolving/resolver.actions';
|
||||
|
||||
/**
|
||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
export const COLLECTION_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Collection>[] = [
|
||||
followLink('parentCommunity', undefined, true, true, true,
|
||||
followLink('parentCommunity')
|
||||
),
|
||||
followLink('logo')
|
||||
];
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific collection before the route is activated
|
||||
*/
|
||||
@Injectable()
|
||||
export class CollectionPageResolver implements Resolve<RemoteData<Collection>> {
|
||||
constructor(private collectionService: CollectionDataService) {
|
||||
constructor(
|
||||
private collectionService: CollectionDataService,
|
||||
private store: Store<any>
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,8 +39,19 @@ export class CollectionPageResolver implements Resolve<RemoteData<Collection>> {
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Collection>> {
|
||||
return this.collectionService.findById(route.params.id, true, false, followLink('logo')).pipe(
|
||||
const collectionRD$ = this.collectionService.findById(
|
||||
route.params.id,
|
||||
true,
|
||||
false,
|
||||
...COLLECTION_PAGE_LINKS_TO_FOLLOW
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData()
|
||||
);
|
||||
|
||||
collectionRD$.subscribe((collectionRD: RemoteData<Collection>) => {
|
||||
this.store.dispatch(new ResolvedAction(state.url, collectionRD.payload));
|
||||
});
|
||||
|
||||
return collectionRD$;
|
||||
}
|
||||
}
|
||||
|
@@ -6,17 +6,21 @@ describe('CommunityPageResolver', () => {
|
||||
describe('resolve', () => {
|
||||
let resolver: CommunityPageResolver;
|
||||
let communityService: any;
|
||||
let store: any;
|
||||
const uuid = '1234-65487-12354-1235';
|
||||
|
||||
beforeEach(() => {
|
||||
communityService = {
|
||||
findById: (id: string) => createSuccessfulRemoteDataObject$({ id })
|
||||
};
|
||||
resolver = new CommunityPageResolver(communityService);
|
||||
store = jasmine.createSpyObj('store', {
|
||||
dispatch: {},
|
||||
});
|
||||
resolver = new CommunityPageResolver(communityService, store);
|
||||
});
|
||||
|
||||
it('should resolve a community with the correct id', (done) => {
|
||||
resolver.resolve({ params: { id: uuid } } as any, undefined)
|
||||
resolver.resolve({ params: { id: uuid } } as any, { url: 'current-url' } as any)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
(resolved) => {
|
||||
|
@@ -4,15 +4,31 @@ import { Observable } from 'rxjs';
|
||||
import { RemoteData } from '../core/data/remote-data';
|
||||
import { Community } from '../core/shared/community.model';
|
||||
import { CommunityDataService } from '../core/data/community-data.service';
|
||||
import { followLink } from '../shared/utils/follow-link-config.model';
|
||||
import { followLink, FollowLinkConfig } from '../shared/utils/follow-link-config.model';
|
||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||
import { ResolvedAction } from '../core/resolving/resolver.actions';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
/**
|
||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
export const COMMUNITY_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Community>[] = [
|
||||
followLink('logo'),
|
||||
followLink('subcommunities'),
|
||||
followLink('collections'),
|
||||
followLink('parentCommunity')
|
||||
];
|
||||
|
||||
/**
|
||||
* This class represents a resolver that requests a specific community before the route is activated
|
||||
*/
|
||||
@Injectable()
|
||||
export class CommunityPageResolver implements Resolve<RemoteData<Community>> {
|
||||
constructor(private communityService: CommunityDataService) {
|
||||
constructor(
|
||||
private communityService: CommunityDataService,
|
||||
private store: Store<any>
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -23,15 +39,19 @@ export class CommunityPageResolver implements Resolve<RemoteData<Community>> {
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Community>> {
|
||||
return this.communityService.findById(
|
||||
const communityRD$ = this.communityService.findById(
|
||||
route.params.id,
|
||||
true,
|
||||
false,
|
||||
followLink('logo'),
|
||||
followLink('subcommunities'),
|
||||
followLink('collections')
|
||||
...COMMUNITY_PAGE_LINKS_TO_FOLLOW
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
|
||||
communityRD$.subscribe((communityRD: RemoteData<Community>) => {
|
||||
this.store.dispatch(new ResolvedAction(state.url, communityRD.payload));
|
||||
});
|
||||
|
||||
return communityRD$;
|
||||
}
|
||||
}
|
||||
|
@@ -18,11 +18,14 @@ import { PageInfo } from '../../core/shared/page-info.model';
|
||||
import { HostWindowService } from '../../shared/host-window.service';
|
||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
|
||||
import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service';
|
||||
import { getMockThemeService } from '../../shared/mocks/theme-service.mock';
|
||||
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||
|
||||
describe('CommunityPageSubCollectionList Component', () => {
|
||||
let comp: CommunityPageSubCollectionListComponent;
|
||||
let fixture: ComponentFixture<CommunityPageSubCollectionListComponent>;
|
||||
let collectionDataServiceStub: any;
|
||||
let themeService;
|
||||
let subCollList = [];
|
||||
|
||||
const collections = [Object.assign(new Community(), {
|
||||
@@ -110,6 +113,8 @@ describe('CommunityPageSubCollectionList Component', () => {
|
||||
}
|
||||
};
|
||||
|
||||
themeService = getMockThemeService();
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
@@ -124,6 +129,7 @@ describe('CommunityPageSubCollectionList Component', () => {
|
||||
{ provide: CollectionDataService, useValue: collectionDataServiceStub },
|
||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
|
||||
{ provide: SelectableListService, useValue: {} },
|
||||
{ provide: ThemeService, useValue: themeService },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
|
@@ -18,11 +18,14 @@ import { HostWindowService } from '../../shared/host-window.service';
|
||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service';
|
||||
import { getMockThemeService } from '../../shared/mocks/theme-service.mock';
|
||||
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||
|
||||
describe('CommunityPageSubCommunityListComponent Component', () => {
|
||||
let comp: CommunityPageSubCommunityListComponent;
|
||||
let fixture: ComponentFixture<CommunityPageSubCommunityListComponent>;
|
||||
let communityDataServiceStub: any;
|
||||
let themeService;
|
||||
let subCommList = [];
|
||||
|
||||
const subcommunities = [Object.assign(new Community(), {
|
||||
@@ -111,6 +114,8 @@ describe('CommunityPageSubCommunityListComponent Component', () => {
|
||||
}
|
||||
};
|
||||
|
||||
themeService = getMockThemeService();
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
@@ -125,6 +130,7 @@ describe('CommunityPageSubCommunityListComponent Component', () => {
|
||||
{ provide: CommunityDataService, useValue: communityDataServiceStub },
|
||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
|
||||
{ provide: SelectableListService, useValue: {} },
|
||||
{ provide: ThemeService, useValue: themeService },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
|
@@ -1,7 +1,7 @@
|
||||
:host {
|
||||
display: block;
|
||||
margin-top: -$content-spacing;
|
||||
margin-bottom: -$content-spacing;
|
||||
margin-top: calc(-1 * var(--ds-content-spacing));
|
||||
margin-bottom: calc(-1 * var(--ds-content-spacing));
|
||||
}
|
||||
|
||||
.display-3 {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-home-news',
|
||||
@@ -10,5 +10,4 @@ import { Component } from '@angular/core';
|
||||
* Component to render the news section on the home page
|
||||
*/
|
||||
export class HomeNewsComponent {
|
||||
|
||||
}
|
||||
|
27
src/app/+home-page/home-news/themed-home-news.component.ts
Normal file
27
src/app/+home-page/home-news/themed-home-news.component.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { ThemedComponent } from '../../shared/theme-support/themed.component';
|
||||
import { HomeNewsComponent } from './home-news.component';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-themed-home-news',
|
||||
styleUrls: [],
|
||||
templateUrl: '../../shared/theme-support/themed.component.html',
|
||||
})
|
||||
|
||||
/**
|
||||
* Component to render the news section on the home page
|
||||
*/
|
||||
export class ThemedHomeNewsComponent extends ThemedComponent<HomeNewsComponent> {
|
||||
protected getComponentName(): string {
|
||||
return 'HomeNewsComponent';
|
||||
}
|
||||
|
||||
protected importThemedComponent(themeName: string): Promise<any> {
|
||||
return import(`../../../themes/${themeName}/app/+home-page/home-news/home-news.component`);
|
||||
}
|
||||
|
||||
protected importUnthemedComponent(): Promise<any> {
|
||||
return import(`./home-news.component`);
|
||||
}
|
||||
|
||||
}
|
@@ -1,17 +1,17 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { HomePageComponent } from './home-page.component';
|
||||
import { HomePageResolver } from './home-page.resolver';
|
||||
import { MenuItemType } from '../shared/menu/initial-menus-state';
|
||||
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
|
||||
import { ThemedHomePageComponent } from './themed-home-page.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild([
|
||||
{
|
||||
path: '',
|
||||
component: HomePageComponent,
|
||||
component: ThemedHomePageComponent,
|
||||
pathMatch: 'full',
|
||||
data: {
|
||||
title: 'home.title',
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<ds-home-news></ds-home-news>
|
||||
<ds-themed-home-news></ds-themed-home-news>
|
||||
<div class="container">
|
||||
<ng-container *ngIf="(site$ | async) as site">
|
||||
<ds-view-tracker [object]="site"></ds-view-tracker>
|
||||
|
@@ -10,7 +10,7 @@ import { Site } from '../core/shared/site.model';
|
||||
templateUrl: './home-page.component.html'
|
||||
})
|
||||
export class HomePageComponent implements OnInit {
|
||||
|
||||
testInput = 'Bingo!';
|
||||
site$: Observable<Site>;
|
||||
|
||||
constructor(
|
||||
@@ -23,4 +23,8 @@ export class HomePageComponent implements OnInit {
|
||||
map((data) => data.site as Site),
|
||||
);
|
||||
}
|
||||
|
||||
onOutput(event: string) {
|
||||
console.log('testOutput:', event);
|
||||
}
|
||||
}
|
||||
|
@@ -7,6 +7,16 @@ import { HomePageRoutingModule } from './home-page-routing.module';
|
||||
import { HomePageComponent } from './home-page.component';
|
||||
import { TopLevelCommunityListComponent } from './top-level-community-list/top-level-community-list.component';
|
||||
import { StatisticsModule } from '../statistics/statistics.module';
|
||||
import { ThemedHomeNewsComponent } from './home-news/themed-home-news.component';
|
||||
import { ThemedHomePageComponent } from './themed-home-page.component';
|
||||
|
||||
const DECLARATIONS = [
|
||||
HomePageComponent,
|
||||
ThemedHomePageComponent,
|
||||
TopLevelCommunityListComponent,
|
||||
ThemedHomeNewsComponent,
|
||||
HomeNewsComponent,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -16,10 +26,11 @@ import { StatisticsModule } from '../statistics/statistics.module';
|
||||
StatisticsModule.forRoot()
|
||||
],
|
||||
declarations: [
|
||||
HomePageComponent,
|
||||
TopLevelCommunityListComponent,
|
||||
HomeNewsComponent,
|
||||
]
|
||||
...DECLARATIONS,
|
||||
],
|
||||
exports: [
|
||||
...DECLARATIONS,
|
||||
],
|
||||
})
|
||||
export class HomePageModule {
|
||||
|
||||
|
26
src/app/+home-page/themed-home-page.component.ts
Normal file
26
src/app/+home-page/themed-home-page.component.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { ThemedComponent } from '../shared/theme-support/themed.component';
|
||||
import { HomePageComponent } from './home-page.component';
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-themed-home-page',
|
||||
styleUrls: [],
|
||||
templateUrl: '../shared/theme-support/themed.component.html',
|
||||
})
|
||||
export class ThemedHomePageComponent extends ThemedComponent<HomePageComponent> {
|
||||
protected inAndOutputNames: (keyof HomePageComponent & keyof this)[];
|
||||
|
||||
|
||||
protected getComponentName(): string {
|
||||
return 'HomePageComponent';
|
||||
}
|
||||
|
||||
protected importThemedComponent(themeName: string): Promise<any> {
|
||||
return import(`../../themes/${themeName}/app/+home-page/home-page.component`);
|
||||
}
|
||||
|
||||
protected importUnthemedComponent(): Promise<any> {
|
||||
return import(`./home-page.component`);
|
||||
}
|
||||
|
||||
}
|
@@ -18,11 +18,14 @@ import { HostWindowService } from '../../shared/host-window.service';
|
||||
import { HostWindowServiceStub } from '../../shared/testing/host-window-service.stub';
|
||||
import { CommunityDataService } from '../../core/data/community-data.service';
|
||||
import { SelectableListService } from '../../shared/object-list/selectable-list/selectable-list.service';
|
||||
import { getMockThemeService } from '../../shared/mocks/theme-service.mock';
|
||||
import { ThemeService } from '../../shared/theme-support/theme.service';
|
||||
|
||||
describe('TopLevelCommunityList Component', () => {
|
||||
let comp: TopLevelCommunityListComponent;
|
||||
let fixture: ComponentFixture<TopLevelCommunityListComponent>;
|
||||
let communityDataServiceStub: any;
|
||||
let themeService;
|
||||
|
||||
const topCommList = [Object.assign(new Community(), {
|
||||
id: '123456789-1',
|
||||
@@ -101,6 +104,8 @@ describe('TopLevelCommunityList Component', () => {
|
||||
}
|
||||
};
|
||||
|
||||
themeService = getMockThemeService();
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
@@ -115,6 +120,7 @@ describe('TopLevelCommunityList Component', () => {
|
||||
{ provide: CommunityDataService, useValue: communityDataServiceStub },
|
||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(0) },
|
||||
{ provide: SelectableListService, useValue: {} },
|
||||
{ provide: ThemeService, useValue: themeService },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
}).compileComponents();
|
||||
|
@@ -1,3 +1,3 @@
|
||||
.btn {
|
||||
min-width: $edit-item-button-min-width;
|
||||
min-width: var(--ds-edit-item-button-min-width);
|
||||
}
|
||||
|
@@ -1,19 +1,19 @@
|
||||
.header-row {
|
||||
color: $table-dark-color;
|
||||
background-color: $table-dark-bg;
|
||||
border-color: $table-dark-border-color;
|
||||
color: var(--bs-table-dark-color);
|
||||
background-color: var(--bs-table-dark-bg);
|
||||
border-color: var(--bs-table-dark-border-color);
|
||||
}
|
||||
|
||||
.bundle-row {
|
||||
color: $table-head-color;
|
||||
background-color: $table-head-bg;
|
||||
border-color: $table-border-color;
|
||||
color: var(--bs-table-head-color);
|
||||
background-color: var(--bs-table-head-bg);
|
||||
border-color: var(--bs-table-border-color);
|
||||
}
|
||||
|
||||
.row-element {
|
||||
padding: 12px;
|
||||
padding: 0.75em;
|
||||
border-bottom: $table-border-width solid $table-border-color;
|
||||
border-bottom: var(--bs-table-border-width) solid var(--bs-table-border-color);
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
|
@@ -1,13 +1,13 @@
|
||||
.btn[disabled] {
|
||||
color: $gray-600;
|
||||
border-color: $gray-600;
|
||||
color: var(--bs-gray-600);
|
||||
border-color: var(--bs-gray-600);
|
||||
z-index: 0; // prevent border colors jumping on hover
|
||||
}
|
||||
|
||||
.metadata-field {
|
||||
width: $edit-item-metadata-field-width;
|
||||
width: var(--ds-edit-item-metadata-field-width);
|
||||
}
|
||||
|
||||
.language-field {
|
||||
width: $edit-item-language-field-width;
|
||||
width: var(--ds-edit-item-language-field-width);
|
||||
}
|
@@ -1,19 +1,19 @@
|
||||
.button-row {
|
||||
.btn {
|
||||
margin-right: 0.5 * $spacer;
|
||||
margin-right: calc(0.5 * var(--bs-spacer));
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
@media screen and (min-width: map-get($grid-breakpoints, sm)) {
|
||||
min-width: $edit-item-button-min-width;
|
||||
@media screen and (min-width: var(--ds-grid-breakpoints-sm)) {
|
||||
min-width: var(--ds-edit-item-button-min-width);
|
||||
}
|
||||
}
|
||||
|
||||
&.top .btn {
|
||||
margin-top: $spacer/2;
|
||||
margin-bottom: $spacer/2;
|
||||
margin-top: calc(var(--bs-spacer) / 2);
|
||||
margin-bottom: calc(var(--bs-spacer) / 2);
|
||||
}
|
||||
|
||||
|
||||
|
@@ -1,10 +1,10 @@
|
||||
.relationship-row:not(.alert) {
|
||||
padding: $alert-padding-y 0;
|
||||
padding: var(--bs-alert-padding-y) 0;
|
||||
}
|
||||
|
||||
.relationship-row.alert {
|
||||
margin-left: -$alert-padding-x;
|
||||
margin-right: -$alert-padding-x;
|
||||
margin-left: calc(-1 * var(--bs-alert-padding-x));
|
||||
margin-right: calc(-1 * var(--bs-alert-padding-x));
|
||||
margin-top: -1px;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
.btn[disabled] {
|
||||
color: $gray-600;
|
||||
border-color: $gray-600;
|
||||
color: var(--bs-gray-600);
|
||||
border-color: var(--bs-gray-600);
|
||||
z-index: 0; // prevent border colors jumping on hover
|
||||
}
|
||||
|
||||
|
@@ -1,19 +1,19 @@
|
||||
.button-row {
|
||||
.btn {
|
||||
margin-right: 0.5 * $spacer;
|
||||
margin-right: calc(0.5 * var(--bs-spacer));
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
@media screen and (min-width: map-get($grid-breakpoints, sm)) {
|
||||
min-width: $edit-item-button-min-width;
|
||||
@media screen and (min-width: var(--ds-grid-breakpoints-sm)) {
|
||||
min-width: var(--ds-edit-item-button-min-width);
|
||||
}
|
||||
}
|
||||
|
||||
&.top .btn {
|
||||
margin-top: $spacer/2;
|
||||
margin-bottom: $spacer/2;
|
||||
margin-top: calc(var(--bs-spacer) / 2);
|
||||
margin-bottom: calc(var(--bs-spacer) / 2);
|
||||
}
|
||||
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
@media screen and (min-width: map-get($grid-breakpoints, md)) {
|
||||
@media screen and (min-width: var(--ds-grid-breakpoints-md)) {
|
||||
dt {
|
||||
text-align: right;
|
||||
}
|
||||
|
@@ -32,17 +32,7 @@ const ENTRY_COMPONENTS = [
|
||||
UntypedItemComponent
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule.withEntryComponents(),
|
||||
ItemPageRoutingModule,
|
||||
EditItemPageModule,
|
||||
StatisticsModule.forRoot(),
|
||||
JournalEntitiesModule.withEntryComponents(),
|
||||
ResearchEntitiesModule.withEntryComponents()
|
||||
],
|
||||
declarations: [
|
||||
const DECLARATIONS = [
|
||||
ItemPageComponent,
|
||||
FullItemPageComponent,
|
||||
MetadataUriValuesComponent,
|
||||
@@ -60,6 +50,23 @@ const ENTRY_COMPONENTS = [
|
||||
ItemComponent,
|
||||
UploadBitstreamComponent,
|
||||
AbstractIncrementalListComponent,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
SharedModule.withEntryComponents(),
|
||||
ItemPageRoutingModule,
|
||||
EditItemPageModule,
|
||||
StatisticsModule.forRoot(),
|
||||
JournalEntitiesModule.withEntryComponents(),
|
||||
ResearchEntitiesModule.withEntryComponents()
|
||||
],
|
||||
declarations: [
|
||||
...DECLARATIONS
|
||||
],
|
||||
exports: [
|
||||
...DECLARATIONS
|
||||
]
|
||||
})
|
||||
export class ItemPageModule {
|
||||
|
@@ -7,9 +7,18 @@ import { Item } from '../core/shared/item.model';
|
||||
import { followLink, FollowLinkConfig } from '../shared/utils/follow-link-config.model';
|
||||
import { FindListOptions } from '../core/data/request.models';
|
||||
import { getFirstCompletedRemoteData } from '../core/shared/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { ResolvedAction } from '../core/resolving/resolver.actions';
|
||||
|
||||
/**
|
||||
* The self links defined in this list are expected to be requested somewhere in the near future
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
export const ITEM_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Item>[] = [
|
||||
followLink('owningCollection'),
|
||||
followLink('owningCollection', undefined, true, true, true,
|
||||
followLink('parentCommunity', undefined, true, true, true,
|
||||
followLink('parentCommunity'))
|
||||
),
|
||||
followLink('bundles', new FindListOptions(), true, true, true, followLink('bitstreams')),
|
||||
followLink('relationships'),
|
||||
followLink('version', undefined, true, true, true, followLink('versionhistory')),
|
||||
@@ -20,7 +29,10 @@ export const ITEM_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Item>[] = [
|
||||
*/
|
||||
@Injectable()
|
||||
export class ItemPageResolver implements Resolve<RemoteData<Item>> {
|
||||
constructor(private itemService: ItemDataService) {
|
||||
constructor(
|
||||
private itemService: ItemDataService,
|
||||
private store: Store<any>
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -31,12 +43,18 @@ export class ItemPageResolver implements Resolve<RemoteData<Item>> {
|
||||
* or an error if something went wrong
|
||||
*/
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Item>> {
|
||||
return this.itemService.findById(route.params.id,
|
||||
const itemRD$ = this.itemService.findById(route.params.id,
|
||||
true,
|
||||
false,
|
||||
...ITEM_PAGE_LINKS_TO_FOLLOW
|
||||
).pipe(
|
||||
getFirstCompletedRemoteData(),
|
||||
);
|
||||
|
||||
itemRD$.subscribe((itemRD: RemoteData<Item>) => {
|
||||
this.store.dispatch(new ResolvedAction(state.url, itemRD.payload));
|
||||
});
|
||||
|
||||
return itemRD$;
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
.login-logo {
|
||||
height: $login-logo-height;
|
||||
width: $login-logo-width;
|
||||
height: var(--ds-login-logo-height);
|
||||
width: var(--ds-login-logo-width);
|
||||
}
|
||||
|
@@ -6,5 +6,5 @@
|
||||
}
|
||||
|
||||
::ng-deep .search-controls {
|
||||
margin-bottom: $spacer;
|
||||
margin-bottom: var(--bs-spacer);
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import { Item } from './core/shared/item.model';
|
||||
import { getCommunityPageRoute } from './+community-page/community-page-routing-paths';
|
||||
import { getCollectionPageRoute } from './+collection-page/collection-page-routing-paths';
|
||||
import { getItemPageRoute } from './+item-page/item-page-routing-paths';
|
||||
import { hasValue } from './shared/empty.util';
|
||||
|
||||
export const BITSTREAM_MODULE_PATH = 'bitstreams';
|
||||
|
||||
@@ -45,6 +46,7 @@ export function getWorkflowItemModuleRoute() {
|
||||
}
|
||||
|
||||
export function getDSORoute(dso: DSpaceObject): string {
|
||||
if (hasValue(dso)) {
|
||||
switch ((dso as any).type) {
|
||||
case Community.type.value:
|
||||
return getCommunityPageRoute(dso.uuid);
|
||||
@@ -54,6 +56,7 @@ export function getDSORoute(dso: DSpaceObject): string {
|
||||
return getItemPageRoute(dso.uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const FORBIDDEN_PATH = '403';
|
||||
|
||||
|
@@ -1,30 +1 @@
|
||||
<div class="outer-wrapper" *ngIf="isNotAuthBlocking$ | async; else authLoader">
|
||||
<ds-admin-sidebar></ds-admin-sidebar>
|
||||
<div class="inner-wrapper" [@slideSidebarPadding]="{
|
||||
value: (!(sidebarVisible | async) ? 'hidden' : (slideSidebarOver | async) ? 'shown' : 'expanded'),
|
||||
params: {collapsedSidebarWidth: (collapsedSidebarWidth | async), totalSidebarWidth: (totalSidebarWidth | async)}
|
||||
}">
|
||||
<ds-header-navbar-wrapper></ds-header-navbar-wrapper>
|
||||
|
||||
<ds-notifications-board
|
||||
[options]="notificationOptions">
|
||||
</ds-notifications-board>
|
||||
<main class="main-content">
|
||||
<div class="container">
|
||||
<ds-breadcrumbs></ds-breadcrumbs>
|
||||
</div>
|
||||
|
||||
<div class="container" *ngIf="isLoading$ | async">
|
||||
<ds-loading message="{{'loading.default' | translate}}"></ds-loading>
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
|
||||
<ds-footer></ds-footer>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #authLoader>
|
||||
<div class="text-center ds-full-screen-loader d-flex align-items-center flex-column justify-content-center">
|
||||
<ds-loading [showMessage]="false"></ds-loading>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ds-themed-root [isNotAuthBlocking]="isNotAuthBlocking$ | async" [isLoading]="isLoading$ | async"></ds-themed-root>
|
||||
|
@@ -1,53 +0,0 @@
|
||||
@import '../styles/helpers/font_awesome_imports.scss';
|
||||
@import '../../node_modules/bootstrap/scss/bootstrap.scss';
|
||||
@import '../../node_modules/nouislider/distribute/nouislider.min';
|
||||
|
||||
html {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
// Sticky Footer
|
||||
.outer-wrapper {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.inner-wrapper {
|
||||
flex: 1 1 auto;
|
||||
flex-flow: column nowrap;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
z-index: $main-z-index;
|
||||
flex: 1 1 100%;
|
||||
margin-top: $content-spacing;
|
||||
margin-bottom: $content-spacing;
|
||||
}
|
||||
|
||||
.alert.hide {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ds-header-navbar-wrapper {
|
||||
z-index: $nav-z-index;
|
||||
}
|
||||
|
||||
ds-admin-sidebar {
|
||||
position: fixed;
|
||||
z-index: $sidebar-z-index;
|
||||
}
|
||||
|
||||
.ds-full-screen-loader {
|
||||
height: 100vh;
|
||||
}
|
||||
|
@@ -5,12 +5,12 @@ import {
|
||||
Component,
|
||||
HostListener,
|
||||
Inject,
|
||||
OnInit, Optional,
|
||||
ViewEncapsulation
|
||||
OnInit,
|
||||
Optional,
|
||||
} from '@angular/core';
|
||||
import { NavigationCancel, NavigationEnd, NavigationStart, Router } from '@angular/router';
|
||||
|
||||
import { BehaviorSubject, combineLatest as combineLatestObservable, Observable, of } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, of } from 'rxjs';
|
||||
import { select, Store } from '@ngrx/store';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
||||
@@ -23,25 +23,25 @@ import { isAuthenticationBlocking } from './core/auth/selectors';
|
||||
import { AuthService } from './core/auth/auth.service';
|
||||
import { CSSVariableService } from './shared/sass-helper/sass-helper.service';
|
||||
import { MenuService } from './shared/menu/menu.service';
|
||||
import { MenuID } from './shared/menu/initial-menus-state';
|
||||
import { slideSidebarPadding } from './shared/animations/slide';
|
||||
import { HostWindowService } from './shared/host-window.service';
|
||||
import { Theme } from '../config/theme.inferface';
|
||||
import { ThemeConfig } from '../config/theme.model';
|
||||
import { Angulartics2DSpace } from './statistics/angulartics/dspace-provider';
|
||||
import { environment } from '../environments/environment';
|
||||
import { models } from './core/core.module';
|
||||
import { LocaleService } from './core/locale/locale.service';
|
||||
import { hasValue } from './shared/empty.util';
|
||||
import { hasValue, isNotEmpty } from './shared/empty.util';
|
||||
import { KlaroService } from './shared/cookies/klaro.service';
|
||||
import { GoogleAnalyticsService } from './statistics/google-analytics.service';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { ThemeService } from './shared/theme-support/theme.service';
|
||||
import { BASE_THEME_NAME } from './shared/theme-support/theme.constants';
|
||||
import { DEFAULT_THEME_CONFIG } from './shared/theme-support/theme.effects';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-app',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
animations: [slideSidebarPadding]
|
||||
})
|
||||
export class AppComponent implements OnInit, AfterViewInit {
|
||||
isLoading$: BehaviorSubject<boolean> = new BehaviorSubject(true);
|
||||
@@ -49,7 +49,7 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
slideSidebarOver: Observable<boolean>;
|
||||
collapsedSidebarWidth: Observable<string>;
|
||||
totalSidebarWidth: Observable<string>;
|
||||
theme: Observable<Theme> = of({} as any);
|
||||
theme: Observable<ThemeConfig> = of({} as any);
|
||||
notificationOptions = environment.notifications;
|
||||
models;
|
||||
|
||||
@@ -60,6 +60,8 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
|
||||
constructor(
|
||||
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
||||
@Inject(DOCUMENT) private document: any,
|
||||
private themeService: ThemeService,
|
||||
private translate: TranslateService,
|
||||
private store: Store<HostWindowState>,
|
||||
private metadata: MetadataService,
|
||||
@@ -77,6 +79,17 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
|
||||
/* Use models object so all decorators are actually called */
|
||||
this.models = models;
|
||||
|
||||
this.themeService.getThemeName$().subscribe((themeName: string) => {
|
||||
if (hasValue(themeName)) {
|
||||
this.setThemeCss(themeName);
|
||||
} else if (hasValue(DEFAULT_THEME_CONFIG)) {
|
||||
this.setThemeCss(DEFAULT_THEME_CONFIG.name);
|
||||
} else {
|
||||
this.setThemeCss(BASE_THEME_NAME);
|
||||
}
|
||||
});
|
||||
|
||||
// Load all the languages that are defined as active from the config file
|
||||
translate.addLangs(environment.languages.filter((LangConfig) => LangConfig.active === true).map((a) => a.code));
|
||||
|
||||
@@ -116,17 +129,6 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
const color: string = environment.production ? 'red' : 'green';
|
||||
console.info(`Environment: %c${env}`, `color: ${color}; font-weight: bold;`);
|
||||
this.dispatchWindowSize(this._window.nativeWindow.innerWidth, this._window.nativeWindow.innerHeight);
|
||||
|
||||
this.sidebarVisible = this.menuService.isMenuVisible(MenuID.ADMIN);
|
||||
|
||||
this.collapsedSidebarWidth = this.cssService.getVariable('collapsedSidebarWidth');
|
||||
this.totalSidebarWidth = this.cssService.getVariable('totalSidebarWidth');
|
||||
|
||||
const sidebarCollapsed = this.menuService.isMenuCollapsed(MenuID.ADMIN);
|
||||
this.slideSidebarOver = combineLatestObservable(sidebarCollapsed, this.windowService.isXsOrSm())
|
||||
.pipe(
|
||||
map(([collapsed, mobile]) => collapsed || mobile)
|
||||
);
|
||||
}
|
||||
|
||||
private storeCSSVariables() {
|
||||
@@ -177,4 +179,34 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
this.cookiesService.initialize();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the theme css file in <head>
|
||||
*
|
||||
* @param themeName The name of the new theme
|
||||
* @private
|
||||
*/
|
||||
private setThemeCss(themeName: string): void {
|
||||
const head = this.document.getElementsByTagName('head')[0];
|
||||
// Array.from to ensure we end up with an array, not an HTMLCollection, which would be
|
||||
// automatically updated if we add nodes later
|
||||
const currentThemeLinks = Array.from(this.document.getElementsByClassName('theme-css'));
|
||||
const link = this.document.createElement('link');
|
||||
link.setAttribute('rel', 'stylesheet');
|
||||
link.setAttribute('type', 'text/css');
|
||||
link.setAttribute('class', 'theme-css');
|
||||
link.setAttribute('href', `/${encodeURIComponent(themeName)}-theme.css`);
|
||||
// wait for the new css to download before removing the old one to prevent a
|
||||
// flash of unstyled content
|
||||
link.onload = () => {
|
||||
if (isNotEmpty(currentThemeLinks)) {
|
||||
currentThemeLinks.forEach((currentThemeLink: any) => {
|
||||
if (hasValue(currentThemeLink)) {
|
||||
currentThemeLink.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
head.appendChild(link);
|
||||
}
|
||||
}
|
||||
|
@@ -3,11 +3,13 @@ import { NotificationsEffects } from './shared/notifications/notifications.effec
|
||||
import { NavbarEffects } from './navbar/navbar.effects';
|
||||
import { SidebarEffects } from './shared/sidebar/sidebar-effects.service';
|
||||
import { RelationshipEffects } from './shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/relationship.effects';
|
||||
import { ThemeEffects } from './shared/theme-support/theme.effects';
|
||||
|
||||
export const appEffects = [
|
||||
StoreEffects,
|
||||
NavbarEffects,
|
||||
NotificationsEffects,
|
||||
SidebarEffects,
|
||||
ThemeEffects,
|
||||
RelationshipEffects
|
||||
];
|
||||
|
@@ -43,6 +43,9 @@ import { ForbiddenComponent } from './forbidden/forbidden.component';
|
||||
import { AuthInterceptor } from './core/auth/auth.interceptor';
|
||||
import { LocaleInterceptor } from './core/locale/locale.interceptor';
|
||||
import { XsrfInterceptor } from './core/xsrf/xsrf.interceptor';
|
||||
import { RootComponent } from './root/root.component';
|
||||
import { ThemedRootComponent } from './root/themed-root.component';
|
||||
import { ThemedEntryComponentModule } from '../themes/themed-entry-component.module';
|
||||
|
||||
export function getBase() {
|
||||
return environment.ui.nameSpace;
|
||||
@@ -65,6 +68,7 @@ const IMPORTS = [
|
||||
EffectsModule.forRoot(appEffects),
|
||||
StoreModule.forRoot(appReducers, storeModuleConfig),
|
||||
StoreRouterConnectingModule.forRoot(),
|
||||
ThemedEntryComponentModule.withEntryComponents(),
|
||||
];
|
||||
|
||||
IMPORTS.push(
|
||||
@@ -120,6 +124,8 @@ const PROVIDERS = [
|
||||
|
||||
const DECLARATIONS = [
|
||||
AppComponent,
|
||||
RootComponent,
|
||||
ThemedRootComponent,
|
||||
HeaderComponent,
|
||||
HeaderNavbarWrapperComponent,
|
||||
AdminSidebarComponent,
|
||||
@@ -135,7 +141,6 @@ const DECLARATIONS = [
|
||||
];
|
||||
|
||||
const EXPORTS = [
|
||||
AppComponent
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
@@ -150,7 +155,8 @@ const EXPORTS = [
|
||||
...DECLARATIONS,
|
||||
],
|
||||
exports: [
|
||||
...EXPORTS
|
||||
...EXPORTS,
|
||||
...DECLARATIONS,
|
||||
]
|
||||
})
|
||||
export class AppModule {
|
||||
|
@@ -12,7 +12,10 @@ import {
|
||||
metadataRegistryReducer,
|
||||
MetadataRegistryState
|
||||
} from './+admin/admin-registries/metadata-registry/metadata-registry.reducers';
|
||||
import { CommunityListReducer, CommunityListState } from './community-list-page/community-list.reducer';
|
||||
import {
|
||||
CommunityListReducer,
|
||||
CommunityListState
|
||||
} from './community-list-page/community-list.reducer';
|
||||
import { hasValue } from './shared/empty.util';
|
||||
import {
|
||||
NameVariantListsState,
|
||||
@@ -20,19 +23,32 @@ import {
|
||||
} from './shared/form/builder/ds-dynamic-form-ui/relation-lookup-modal/name-variant.reducer';
|
||||
import { formReducer, FormState } from './shared/form/form.reducer';
|
||||
import { menusReducer, MenusState } from './shared/menu/menu.reducer';
|
||||
import { notificationsReducer, NotificationsState } from './shared/notifications/notifications.reducers';
|
||||
import {
|
||||
notificationsReducer,
|
||||
NotificationsState
|
||||
} from './shared/notifications/notifications.reducers';
|
||||
import {
|
||||
selectableListReducer,
|
||||
SelectableListsState
|
||||
} from './shared/object-list/selectable-list/selectable-list.reducer';
|
||||
import { ObjectSelectionListState, objectSelectionReducer } from './shared/object-select/object-select.reducer';
|
||||
import {
|
||||
ObjectSelectionListState,
|
||||
objectSelectionReducer
|
||||
} from './shared/object-select/object-select.reducer';
|
||||
import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer';
|
||||
|
||||
import { hostWindowReducer, HostWindowState } from './shared/search/host-window.reducer';
|
||||
import { filterReducer, SearchFiltersState } from './shared/search/search-filters/search-filter/search-filter.reducer';
|
||||
import { sidebarFilterReducer, SidebarFiltersState } from './shared/sidebar/filter/sidebar-filter.reducer';
|
||||
import {
|
||||
filterReducer,
|
||||
SearchFiltersState
|
||||
} from './shared/search/search-filters/search-filter/search-filter.reducer';
|
||||
import {
|
||||
sidebarFilterReducer,
|
||||
SidebarFiltersState
|
||||
} from './shared/sidebar/filter/sidebar-filter.reducer';
|
||||
import { sidebarReducer, SidebarState } from './shared/sidebar/sidebar.reducer';
|
||||
import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer';
|
||||
import { ThemeState, themeReducer } from './shared/theme-support/theme.reducer';
|
||||
|
||||
export interface AppState {
|
||||
router: fromRouter.RouterReducerState;
|
||||
@@ -45,6 +61,7 @@ export interface AppState {
|
||||
searchFilter: SearchFiltersState;
|
||||
truncatable: TruncatablesState;
|
||||
cssVariables: CSSVariablesState;
|
||||
theme: ThemeState;
|
||||
menus: MenusState;
|
||||
objectSelection: ObjectSelectionListState;
|
||||
selectableLists: SelectableListsState;
|
||||
@@ -65,6 +82,7 @@ export const appReducers: ActionReducerMap<AppState> = {
|
||||
searchFilter: filterReducer,
|
||||
truncatable: truncatableReducer,
|
||||
cssVariables: cssVariablesReducer,
|
||||
theme: themeReducer,
|
||||
menus: menusReducer,
|
||||
objectSelection: objectSelectionReducer,
|
||||
selectableLists: selectableListReducer,
|
||||
|
@@ -3,7 +3,8 @@ import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
|
||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
||||
import { Collection } from '../shared/collection.model';
|
||||
import { CollectionDataService } from '../data/collection-data.service';
|
||||
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { COLLECTION_PAGE_LINKS_TO_FOLLOW } from '../../+collection-page/collection-page.resolver';
|
||||
|
||||
/**
|
||||
* The class that resolves the BreadcrumbConfig object for a Collection
|
||||
@@ -22,10 +23,6 @@ export class CollectionBreadcrumbResolver extends DSOBreadcrumbResolver<Collecti
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
get followLinks(): FollowLinkConfig<Collection>[] {
|
||||
return [
|
||||
followLink('parentCommunity', undefined, true, true, true,
|
||||
followLink('parentCommunity')
|
||||
)
|
||||
];
|
||||
return COLLECTION_PAGE_LINKS_TO_FOLLOW;
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,8 @@ import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
|
||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
||||
import { CommunityDataService } from '../data/community-data.service';
|
||||
import { Community } from '../shared/community.model';
|
||||
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { COMMUNITY_PAGE_LINKS_TO_FOLLOW } from '../../+community-page/community-page.resolver';
|
||||
|
||||
/**
|
||||
* The class that resolves the BreadcrumbConfig object for a Community
|
||||
@@ -22,8 +23,6 @@ export class CommunityBreadcrumbResolver extends DSOBreadcrumbResolver<Community
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
get followLinks(): FollowLinkConfig<Community>[] {
|
||||
return [
|
||||
followLink('parentCommunity')
|
||||
];
|
||||
return COMMUNITY_PAGE_LINKS_TO_FOLLOW;
|
||||
}
|
||||
}
|
||||
|
@@ -28,7 +28,7 @@ export class DSOBreadcrumbsService implements BreadcrumbsService<ChildHALResourc
|
||||
|
||||
/**
|
||||
* Method to recursively calculate the breadcrumbs
|
||||
* This method returns the name and url of the key and all its parent DSO's recursively, top down
|
||||
* This method returns the name and url of the key and all its parent DSOs recursively, top down
|
||||
* @param key The key (a DSpaceObject) used to resolve the breadcrumb
|
||||
* @param url The url to use as a link for this breadcrumb
|
||||
*/
|
||||
|
@@ -20,7 +20,7 @@ export class DSONameService {
|
||||
*
|
||||
* With only two exceptions those solutions seem overkill for now.
|
||||
*/
|
||||
private factories = {
|
||||
private readonly factories = {
|
||||
Person: (dso: DSpaceObject): string => {
|
||||
return `${dso.firstMetadataValue('person.familyName')}, ${dso.firstMetadataValue('person.givenName')}`;
|
||||
},
|
||||
|
@@ -3,7 +3,8 @@ import { DSOBreadcrumbsService } from './dso-breadcrumbs.service';
|
||||
import { ItemDataService } from '../data/item-data.service';
|
||||
import { Item } from '../shared/item.model';
|
||||
import { DSOBreadcrumbResolver } from './dso-breadcrumb.resolver';
|
||||
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
|
||||
import { ITEM_PAGE_LINKS_TO_FOLLOW } from '../../+item-page/item-page.resolver';
|
||||
|
||||
/**
|
||||
* The class that resolves the BreadcrumbConfig object for an Item
|
||||
@@ -22,13 +23,6 @@ export class ItemBreadcrumbResolver extends DSOBreadcrumbResolver<Item> {
|
||||
* Requesting them as embeds will limit the number of requests
|
||||
*/
|
||||
get followLinks(): FollowLinkConfig<Item>[] {
|
||||
return [
|
||||
followLink('owningCollection', undefined, true, true, true,
|
||||
followLink('parentCommunity', undefined, true, true, true,
|
||||
followLink('parentCommunity'))
|
||||
),
|
||||
followLink('bundles'),
|
||||
followLink('relationships')
|
||||
];
|
||||
return ITEM_PAGE_LINKS_TO_FOLLOW;
|
||||
}
|
||||
}
|
||||
|
32
src/app/core/cache/builders/link.service.ts
vendored
32
src/app/core/cache/builders/link.service.ts
vendored
@@ -3,7 +3,15 @@ import { hasNoValue, hasValue, isNotEmpty } from '../../../shared/empty.util';
|
||||
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
|
||||
import { GenericConstructor } from '../../shared/generic-constructor';
|
||||
import { HALResource } from '../../shared/hal-resource.model';
|
||||
import { getDataServiceFor, getLinkDefinition, getLinkDefinitions, LinkDefinition } from './build-decorators';
|
||||
import {
|
||||
getDataServiceFor,
|
||||
getLinkDefinition,
|
||||
getLinkDefinitions,
|
||||
LinkDefinition
|
||||
} from './build-decorators';
|
||||
import { RemoteData } from '../../data/remote-data';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { EMPTY } from 'rxjs';
|
||||
|
||||
/**
|
||||
* A Service to handle the resolving and removing
|
||||
@@ -33,12 +41,14 @@ export class LinkService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given {@link FollowLinkConfig} for the given model
|
||||
* Resolve the given {@link FollowLinkConfig} for the given model and return the result. This does
|
||||
* not attach the link result to the property on the model. Useful when you're working with a
|
||||
* readonly object
|
||||
*
|
||||
* @param model the {@link HALResource} to resolve the link for
|
||||
* @param linkToFollow the {@link FollowLinkConfig} to resolve
|
||||
*/
|
||||
public resolveLink<T extends HALResource>(model, linkToFollow: FollowLinkConfig<T>): T {
|
||||
public resolveLinkWithoutAttaching<T extends HALResource, U extends HALResource>(model, linkToFollow: FollowLinkConfig<T>): Observable<RemoteData<U>> {
|
||||
const matchingLinkDef = getLinkDefinition(model.constructor, linkToFollow.name);
|
||||
|
||||
if (hasNoValue(matchingLinkDef)) {
|
||||
@@ -61,9 +71,9 @@ export class LinkService {
|
||||
|
||||
try {
|
||||
if (matchingLinkDef.isList) {
|
||||
model[linkToFollow.name] = service.findAllByHref(href, linkToFollow.findListOptions, linkToFollow.useCachedVersionIfAvailable, linkToFollow.reRequestOnStale, ...linkToFollow.linksToFollow);
|
||||
return service.findAllByHref(href, linkToFollow.findListOptions, linkToFollow.useCachedVersionIfAvailable, linkToFollow.reRequestOnStale, ...linkToFollow.linksToFollow);
|
||||
} else {
|
||||
model[linkToFollow.name] = service.findByHref(href, linkToFollow.useCachedVersionIfAvailable, linkToFollow.reRequestOnStale, ...linkToFollow.linksToFollow);
|
||||
return service.findByHref(href, linkToFollow.useCachedVersionIfAvailable, linkToFollow.reRequestOnStale, ...linkToFollow.linksToFollow);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Something went wrong when using @dataService(${matchingLinkDef.resourceType.value}) ${hasValue(service) ? '' : '(undefined) '}to resolve link ${linkToFollow.name} at ${href}`);
|
||||
@@ -71,6 +81,18 @@ export class LinkService {
|
||||
}
|
||||
}
|
||||
}
|
||||
return EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the given {@link FollowLinkConfig} for the given model and return the model with the
|
||||
* link property attached.
|
||||
*
|
||||
* @param model the {@link HALResource} to resolve the link for
|
||||
* @param linkToFollow the {@link FollowLinkConfig} to resolve
|
||||
*/
|
||||
public resolveLink<T extends HALResource>(model, linkToFollow: FollowLinkConfig<T>): T {
|
||||
model[linkToFollow.name] = this.resolveLinkWithoutAttaching(model, linkToFollow);
|
||||
return model;
|
||||
}
|
||||
|
||||
|
@@ -13,9 +13,14 @@ import { RestRequestMethod } from '../data/rest-request-method';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
import { ApplyPatchObjectCacheAction } from './object-cache.actions';
|
||||
import { ObjectCacheService } from './object-cache.service';
|
||||
import { CommitSSBAction, EmptySSBAction, ServerSyncBufferActionTypes } from './server-sync-buffer.actions';
|
||||
import {
|
||||
CommitSSBAction,
|
||||
EmptySSBAction,
|
||||
ServerSyncBufferActionTypes
|
||||
} from './server-sync-buffer.actions';
|
||||
import { ServerSyncBufferEffects } from './server-sync-buffer.effects';
|
||||
import { storeModuleConfig } from '../../app.reducer';
|
||||
import { NoOpAction } from '../../shared/ngrx/no-op.action';
|
||||
|
||||
describe('ServerSyncBufferEffects', () => {
|
||||
let ssbEffects: ServerSyncBufferEffects;
|
||||
@@ -143,7 +148,7 @@ describe('ServerSyncBufferEffects', () => {
|
||||
payload: { method: RestRequestMethod.PATCH }
|
||||
}
|
||||
});
|
||||
const expected = cold('b', { b: { type: 'NO_ACTION' } });
|
||||
const expected = cold('b', { b: new NoOpAction() });
|
||||
|
||||
expect(ssbEffects.commitServerSyncBuffer).toBeObservable(expected);
|
||||
});
|
||||
|
@@ -21,6 +21,7 @@ import { RestRequestMethod } from '../data/rest-request-method';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { ObjectCacheEntry } from './object-cache.reducer';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { NoOpAction } from '../../shared/ngrx/no-op.action';
|
||||
|
||||
@Injectable()
|
||||
export class ServerSyncBufferEffects {
|
||||
@@ -80,7 +81,7 @@ export class ServerSyncBufferEffects {
|
||||
switchMap((array) => [...array, new EmptySSBAction(action.payload)])
|
||||
);
|
||||
} else {
|
||||
return observableOf({ type: 'NO_ACTION' });
|
||||
return observableOf(new NoOpAction());
|
||||
}
|
||||
})
|
||||
);
|
||||
|
@@ -11,10 +11,14 @@ import {
|
||||
RemoveFieldUpdateAction,
|
||||
RemoveObjectUpdatesAction
|
||||
} from './object-updates.actions';
|
||||
import { INotification, Notification } from '../../../shared/notifications/models/notification.model';
|
||||
import {
|
||||
INotification,
|
||||
Notification
|
||||
} from '../../../shared/notifications/models/notification.model';
|
||||
import { NotificationType } from '../../../shared/notifications/models/notification-type';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { hasValue } from '../../../shared/empty.util';
|
||||
import { NoOpAction } from '../../../shared/ngrx/no-op.action';
|
||||
|
||||
describe('ObjectUpdatesEffects', () => {
|
||||
let updatesEffects: ObjectUpdatesEffects;
|
||||
@@ -97,7 +101,7 @@ describe('ObjectUpdatesEffects', () => {
|
||||
actions = hot('a', { a: new DiscardObjectUpdatesAction(testURL, infoNotification) });
|
||||
actions = hot('b', { b: new ReinstateObjectUpdatesAction(testURL) });
|
||||
updatesEffects.removeAfterDiscardOrReinstateOnUndo$.subscribe((t) => {
|
||||
expect(t).toEqual({ type: 'NO_ACTION' });
|
||||
expect(t).toEqual(new NoOpAction());
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@@ -3,7 +3,8 @@ import { Actions, Effect, ofType } from '@ngrx/effects';
|
||||
import {
|
||||
DiscardObjectUpdatesAction,
|
||||
ObjectUpdatesAction,
|
||||
ObjectUpdatesActionTypes, RemoveAllObjectUpdatesAction,
|
||||
ObjectUpdatesActionTypes,
|
||||
RemoveAllObjectUpdatesAction,
|
||||
RemoveObjectUpdatesAction
|
||||
} from './object-updates.actions';
|
||||
import { delay, filter, map, switchMap, take, tap } from 'rxjs/operators';
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
RemoveNotificationAction
|
||||
} from '../../../shared/notifications/notifications.actions';
|
||||
import { Action } from '@ngrx/store';
|
||||
import { NoOpAction } from '../../../shared/ngrx/no-op.action';
|
||||
|
||||
/**
|
||||
* NGRX effects for ObjectUpdatesActions
|
||||
@@ -111,7 +113,7 @@ export class ObjectUpdatesEffects {
|
||||
map((updateAction: ObjectUpdatesAction) => {
|
||||
if (updateAction.type === ObjectUpdatesActionTypes.REINSTATE) {
|
||||
// If someone reinstated, do nothing, just let the reinstating happen
|
||||
return { type: 'NO_ACTION' };
|
||||
return new NoOpAction();
|
||||
}
|
||||
// If someone performed another action, assume the user does not want to reinstate and remove all changes
|
||||
return removeAction;
|
||||
|
@@ -8,6 +8,7 @@ import { Item } from '../shared/item.model';
|
||||
import { AddToIndexAction } from './index.actions';
|
||||
import { IndexName } from './index.reducer';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { NoOpAction } from '../../shared/ngrx/no-op.action';
|
||||
|
||||
describe('ObjectUpdatesEffects', () => {
|
||||
let indexEffects: UUIDIndexEffects;
|
||||
@@ -79,14 +80,14 @@ describe('ObjectUpdatesEffects', () => {
|
||||
it('should emit NO_ACTION when a AddToObjectCacheAction without an alternativeLink is dispatched', () => {
|
||||
action = new AddToObjectCacheAction(objectToCache, timeCompleted, msToLive, requestUUID, undefined);
|
||||
actions = hot('--a-', { a: action });
|
||||
const expected = cold('--b-', { b: { type: 'NO_ACTION' } });
|
||||
const expected = cold('--b-', { b: new NoOpAction() });
|
||||
expect(indexEffects.addAlternativeObjectLink$).toBeObservable(expected);
|
||||
});
|
||||
|
||||
it('should emit NO_ACTION when a AddToObjectCacheAction with an alternativeLink that\'s the same as the objectToCache\'s selfLink is dispatched', () => {
|
||||
action = new AddToObjectCacheAction(objectToCache, timeCompleted, msToLive, requestUUID, objectToCache._links.self.href);
|
||||
actions = hot('--a-', { a: action });
|
||||
const expected = cold('--b-', { b: { type: 'NO_ACTION' } });
|
||||
const expected = cold('--b-', { b: new NoOpAction() });
|
||||
expect(indexEffects.addAlternativeObjectLink$).toBeObservable(expected);
|
||||
});
|
||||
});
|
||||
|
@@ -19,6 +19,7 @@ import { RestRequestMethod } from '../data/rest-request-method';
|
||||
import { getUrlWithoutEmbedParams, uuidFromHrefSelector } from './index.selectors';
|
||||
import { Store, select } from '@ngrx/store';
|
||||
import { CoreState } from '../core.reducers';
|
||||
import { NoOpAction } from '../../shared/ngrx/no-op.action';
|
||||
|
||||
@Injectable()
|
||||
export class UUIDIndexEffects {
|
||||
@@ -53,7 +54,7 @@ export class UUIDIndexEffects {
|
||||
selfLink
|
||||
);
|
||||
} else {
|
||||
return { type: 'NO_ACTION' };
|
||||
return new NoOpAction();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
27
src/app/core/resolving/resolver.actions.ts
Normal file
27
src/app/core/resolving/resolver.actions.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { type } from '../../shared/ngrx/type';
|
||||
import { Action } from '@ngrx/store';
|
||||
import { DSpaceObject } from '../shared/dspace-object.model';
|
||||
|
||||
export const ResolverActionTypes = {
|
||||
RESOLVED: type('dspace/resolver/RESOLVED')
|
||||
};
|
||||
|
||||
/**
|
||||
* An action that indicates a route object has been resolved.
|
||||
*
|
||||
* It isn't used in a reducer for now. Its purpose is to be able to be notified that an object
|
||||
* has been resolved in an effect.
|
||||
*/
|
||||
export class ResolvedAction implements Action {
|
||||
type = ResolverActionTypes.RESOLVED;
|
||||
payload: {
|
||||
url: string,
|
||||
dso: DSpaceObject
|
||||
};
|
||||
|
||||
constructor(url: string, dso: DSpaceObject) {
|
||||
this.payload = { url, dso };
|
||||
}
|
||||
}
|
||||
|
||||
export type ResolverAction = ResolvedAction;
|
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
export enum Context {
|
||||
Undefined = 'undefined',
|
||||
Any = 'undefined',
|
||||
ItemPage = 'itemPage',
|
||||
Search = 'search',
|
||||
Workflow = 'workflow',
|
||||
|
@@ -1,5 +1,3 @@
|
||||
$submission-relationship-thumbnail-width: 80px;
|
||||
|
||||
.person-thumbnail {
|
||||
width: $submission-relationship-thumbnail-width;
|
||||
width: var(--ds-submission-relationship-thumbnail-width);
|
||||
}
|
||||
|
@@ -1,5 +1,3 @@
|
||||
$submission-relationship-thumbnail-width: 80px;
|
||||
|
||||
.person-thumbnail {
|
||||
width: $submission-relationship-thumbnail-width;
|
||||
width: var(--ds-submission-relationship-thumbnail-width);
|
||||
}
|
||||
|
@@ -1,40 +1,42 @@
|
||||
$footer-bg: $gray-100;
|
||||
$footer-border: 1px solid darken($footer-bg, 10%);
|
||||
$footer-padding: $spacer * 1.5;
|
||||
$footer-logo-height: 55px;
|
||||
: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 {
|
||||
background-color: $footer-bg;
|
||||
border-top: $footer-border;
|
||||
background-color: var(--ds-footer-bg);
|
||||
border-top: var(--ds-footer-border);
|
||||
text-align: center;
|
||||
padding: $footer-padding;
|
||||
padding-bottom: $spacer;
|
||||
padding: var(--ds-footer-padding);
|
||||
padding-bottom: var(--bs-spacer);
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
height: $footer-logo-height;
|
||||
height: var(--ds-footer-logo-height);
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-top: $spacer * 0.5;
|
||||
padding-top: calc(var(--bs-spacer) * 0.5);
|
||||
|
||||
li {
|
||||
display: inline-flex;
|
||||
a {
|
||||
padding: 0 $spacer/2;
|
||||
padding: 0 calc(var(--bs-spacer) / 2);
|
||||
color: inherit
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
&:after {
|
||||
content: '';
|
||||
border-right: 1px map-get($theme-colors, secondary) solid;
|
||||
border-right: 1px var(--bs-secondary) solid;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
@media screen and (max-width: map-get($grid-breakpoints, md)) {
|
||||
@media screen and (max-width: var(--ds-grid-breakpoints-md)) {
|
||||
:host.open {
|
||||
background-color: $white;
|
||||
background-color: var(--bs-white);
|
||||
top: 0;
|
||||
position: sticky;
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
.navbar-brand img {
|
||||
height: $header-logo-height;
|
||||
@media screen and (max-width: map-get($grid-breakpoints, sm)) {
|
||||
height: $header-logo-height-xs;
|
||||
height: var(--ds-header-logo-height);
|
||||
@media screen and (max-width: var(--ds-grid-breakpoints-sm)) {
|
||||
height: var(--ds-header-logo-height-xs);
|
||||
}
|
||||
}
|
||||
.navbar-toggler .navbar-toggler-icon {
|
||||
@@ -11,10 +11,10 @@
|
||||
|
||||
.navbar ::ng-deep {
|
||||
a {
|
||||
color: $header-icon-color;
|
||||
color: var(--ds-header-icon-color);
|
||||
|
||||
&:hover, &focus {
|
||||
color: darken($header-icon-color, 15%);
|
||||
color: var(--ds-header-icon-color-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,18 +4,18 @@
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
::ng-deep a.nav-link {
|
||||
padding-right: $spacer;
|
||||
padding-left: $spacer;
|
||||
padding-right: var(--bs-spacer);
|
||||
padding-left: var(--bs-spacer);
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
/** Mobile menu styling **/
|
||||
@media screen and (max-width: map-get($grid-breakpoints, md)) {
|
||||
@media screen and (max-width: var(--ds-grid-breakpoints-md)) {
|
||||
.dropdown-toggle {
|
||||
&:after {
|
||||
float: right;
|
||||
margin-top: $spacer/2;
|
||||
margin-top: calc(var(--bs-spacer) / 2);
|
||||
}
|
||||
}
|
||||
.dropdown-menu {
|
||||
|
@@ -1,13 +1,13 @@
|
||||
nav.navbar {
|
||||
border-bottom: 1px $gray-400 solid;
|
||||
border-bottom: 1px var(--bs-gray-400) solid;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
/** Mobile menu styling **/
|
||||
@media screen and (max-width: map-get($grid-breakpoints, md)) {
|
||||
@media screen and (max-width: var(--ds-grid-breakpoints-md)) {
|
||||
.navbar {
|
||||
width: 100%;
|
||||
background-color: $white;
|
||||
background-color: var(--bs-white);
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
@@ -17,18 +17,18 @@ nav.navbar {
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: map-get($grid-breakpoints, md)) {
|
||||
@media screen and (min-width: var(--ds-grid-breakpoints-md)) {
|
||||
.reset-padding-md {
|
||||
margin-left: -$spacer/2;
|
||||
margin-right: -$spacer/2;
|
||||
margin-left: -calc(var(--bs-spacer) / 2);
|
||||
margin-right: -calc(var(--bs-spacer) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO remove when https://github.com/twbs/bootstrap/issues/24726 is fixed */
|
||||
.navbar-expand-md.navbar-container {
|
||||
@media screen and (max-width: map-get($grid-breakpoints, md)) {
|
||||
@media screen and (max-width: var(--ds-grid-breakpoints-md)) {
|
||||
> .container {
|
||||
padding: 0 $spacer;
|
||||
padding: 0 var(--bs-spacer);
|
||||
}
|
||||
padding: 0;
|
||||
}
|
||||
|
@@ -85,5 +85,4 @@ export class NavbarComponent extends MenuComponent {
|
||||
})));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ import {
|
||||
import { MenuID } from '../shared/menu/initial-menus-state';
|
||||
import { MenuService } from '../shared/menu/menu.service';
|
||||
import { MenuState } from '../shared/menu/menu.reducer';
|
||||
import { NoOpAction } from '../shared/ngrx/no-op.action';
|
||||
|
||||
@Injectable()
|
||||
export class NavbarEffects {
|
||||
@@ -51,7 +52,7 @@ export class NavbarEffects {
|
||||
return new CollapseMenuAction(MenuID.PUBLIC);
|
||||
}
|
||||
}
|
||||
return { type: 'NO_ACTION' };
|
||||
return new NoOpAction();
|
||||
}));
|
||||
})
|
||||
);
|
||||
|
@@ -9,6 +9,7 @@ import { NavbarSectionComponent } from './navbar-section/navbar-section.componen
|
||||
import { ExpandableNavbarSectionComponent } from './expandable-navbar-section/expandable-navbar-section.component';
|
||||
import { NavbarComponent } from './navbar.component';
|
||||
import { MenuModule } from '../shared/menu/menu.module';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
const effects = [
|
||||
NavbarEffects
|
||||
@@ -24,6 +25,7 @@ const ENTRY_COMPONENTS = [
|
||||
imports: [
|
||||
CommonModule,
|
||||
MenuModule,
|
||||
FormsModule,
|
||||
EffectsModule.forFeature(effects),
|
||||
CoreModule.forRoot()
|
||||
],
|
||||
|
30
src/app/root/root.component.html
Normal file
30
src/app/root/root.component.html
Normal file
@@ -0,0 +1,30 @@
|
||||
<div class="outer-wrapper" *ngIf="isNotAuthBlocking; else authLoader">
|
||||
<ds-admin-sidebar></ds-admin-sidebar>
|
||||
<div class="inner-wrapper" [@slideSidebarPadding]="{
|
||||
value: (!(sidebarVisible | async) ? 'hidden' : (slideSidebarOver | async) ? 'shown' : 'expanded'),
|
||||
params: {collapsedSidebarWidth: (collapsedSidebarWidth | async), totalSidebarWidth: (totalSidebarWidth | async)}
|
||||
}">
|
||||
<ds-header-navbar-wrapper></ds-header-navbar-wrapper>
|
||||
|
||||
<ds-notifications-board
|
||||
[options]="notificationOptions">
|
||||
</ds-notifications-board>
|
||||
<main class="main-content">
|
||||
<div class="container">
|
||||
<ds-breadcrumbs></ds-breadcrumbs>
|
||||
</div>
|
||||
|
||||
<div class="container" *ngIf="isLoading">
|
||||
<ds-loading message="{{'loading.default' | translate}}"></ds-loading>
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
|
||||
<ds-footer></ds-footer>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #authLoader>
|
||||
<div class="text-center ds-full-screen-loader d-flex align-items-center flex-column justify-content-center">
|
||||
<ds-loading [showMessage]="false"></ds-loading>
|
||||
</div>
|
||||
</ng-template>
|
77
src/app/root/root.component.spec.ts
Normal file
77
src/app/root/root.component.spec.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { RootComponent } from './root.component';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { authReducer } from '../core/auth/auth.reducer';
|
||||
import { storeModuleConfig } from '../app.reducer';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { TranslateLoaderMock } from '../shared/mocks/translate-loader.mock';
|
||||
import { NativeWindowRef, NativeWindowService } from '../core/services/window.service';
|
||||
import { MetadataService } from '../core/metadata/metadata.service';
|
||||
import { MetadataServiceMock } from '../shared/mocks/metadata-service.mock';
|
||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
||||
import { AngularticsProviderMock } from '../shared/mocks/angulartics-provider.service.mock';
|
||||
import { Angulartics2DSpace } from '../statistics/angulartics/dspace-provider';
|
||||
import { AuthService } from '../core/auth/auth.service';
|
||||
import { AuthServiceMock } from '../shared/mocks/auth.service.mock';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { RouterMock } from '../shared/mocks/router.mock';
|
||||
import { MockActivatedRoute } from '../shared/mocks/active-router.mock';
|
||||
import { MenuService } from '../shared/menu/menu.service';
|
||||
import { CSSVariableService } from '../shared/sass-helper/sass-helper.service';
|
||||
import { CSSVariableServiceStub } from '../shared/testing/css-variable-service.stub';
|
||||
import { HostWindowService } from '../shared/host-window.service';
|
||||
import { HostWindowServiceStub } from '../shared/testing/host-window-service.stub';
|
||||
import { LocaleService } from '../core/locale/locale.service';
|
||||
import { provideMockStore } from '@ngrx/store/testing';
|
||||
import { RouteService } from '../core/services/route.service';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { MenuServiceStub } from '../shared/testing/menu-service.stub';
|
||||
|
||||
describe('RootComponent', () => {
|
||||
let component: RootComponent;
|
||||
let fixture: ComponentFixture<RootComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
StoreModule.forRoot(authReducer, storeModuleConfig),
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderMock
|
||||
}
|
||||
}),
|
||||
],
|
||||
declarations: [RootComponent], // declare the test component
|
||||
providers: [
|
||||
{ provide: NativeWindowService, useValue: new NativeWindowRef() },
|
||||
{ provide: MetadataService, useValue: new MetadataServiceMock() },
|
||||
{ provide: Angulartics2GoogleAnalytics, useValue: new AngularticsProviderMock() },
|
||||
{ provide: Angulartics2DSpace, useValue: new AngularticsProviderMock() },
|
||||
{ provide: AuthService, useValue: new AuthServiceMock() },
|
||||
{ provide: Router, useValue: new RouterMock() },
|
||||
{ provide: ActivatedRoute, useValue: new MockActivatedRoute() },
|
||||
{ provide: MenuService, useValue: new MenuServiceStub() },
|
||||
{ provide: CSSVariableService, useClass: CSSVariableServiceStub },
|
||||
{ provide: HostWindowService, useValue: new HostWindowServiceStub(800) },
|
||||
{ provide: LocaleService, useValue: {} },
|
||||
provideMockStore({ core: { auth: { loading: false } } } as any),
|
||||
RootComponent,
|
||||
RouteService
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(RootComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
79
src/app/root/root.component.ts
Normal file
79
src/app/root/root.component.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Component, Inject, OnInit, Optional, Input } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { combineLatest as combineLatestObservable, Observable, of } from 'rxjs';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
||||
|
||||
import { MetadataService } from '../core/metadata/metadata.service';
|
||||
import { HostWindowState } from '../shared/search/host-window.reducer';
|
||||
import { NativeWindowRef, NativeWindowService } from '../core/services/window.service';
|
||||
import { AuthService } from '../core/auth/auth.service';
|
||||
import { CSSVariableService } from '../shared/sass-helper/sass-helper.service';
|
||||
import { MenuService } from '../shared/menu/menu.service';
|
||||
import { MenuID } from '../shared/menu/initial-menus-state';
|
||||
import { HostWindowService } from '../shared/host-window.service';
|
||||
import { ThemeConfig } from '../../config/theme.model';
|
||||
import { Angulartics2DSpace } from '../statistics/angulartics/dspace-provider';
|
||||
import { environment } from '../../environments/environment';
|
||||
import { LocaleService } from '../core/locale/locale.service';
|
||||
import { KlaroService } from '../shared/cookies/klaro.service';
|
||||
import { slideSidebarPadding } from '../shared/animations/slide';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-root',
|
||||
templateUrl: './root.component.html',
|
||||
styleUrls: ['./root.component.scss'],
|
||||
animations: [slideSidebarPadding],
|
||||
})
|
||||
export class RootComponent implements OnInit {
|
||||
sidebarVisible: Observable<boolean>;
|
||||
slideSidebarOver: Observable<boolean>;
|
||||
collapsedSidebarWidth: Observable<string>;
|
||||
totalSidebarWidth: Observable<string>;
|
||||
theme: Observable<ThemeConfig> = of({} as any);
|
||||
notificationOptions = environment.notifications;
|
||||
models;
|
||||
|
||||
/**
|
||||
* Whether or not the authentication is currently blocking the UI
|
||||
*/
|
||||
@Input() isNotAuthBlocking: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the the application is loading;
|
||||
*/
|
||||
@Input() isLoading: boolean;
|
||||
|
||||
constructor(
|
||||
@Inject(NativeWindowService) private _window: NativeWindowRef,
|
||||
private translate: TranslateService,
|
||||
private store: Store<HostWindowState>,
|
||||
private metadata: MetadataService,
|
||||
private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics,
|
||||
private angulartics2DSpace: Angulartics2DSpace,
|
||||
private authService: AuthService,
|
||||
private router: Router,
|
||||
private cssService: CSSVariableService,
|
||||
private menuService: MenuService,
|
||||
private windowService: HostWindowService,
|
||||
private localeService: LocaleService,
|
||||
@Optional() private cookiesService: KlaroService
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.sidebarVisible = this.menuService.isMenuVisible(MenuID.ADMIN);
|
||||
|
||||
this.collapsedSidebarWidth = this.cssService.getVariable('collapsedSidebarWidth');
|
||||
this.totalSidebarWidth = this.cssService.getVariable('totalSidebarWidth');
|
||||
|
||||
const sidebarCollapsed = this.menuService.isMenuCollapsed(MenuID.ADMIN);
|
||||
this.slideSidebarOver = combineLatestObservable(sidebarCollapsed, this.windowService.isXsOrSm())
|
||||
.pipe(
|
||||
map(([collapsed, mobile]) => collapsed || mobile)
|
||||
);
|
||||
}
|
||||
}
|
35
src/app/root/themed-root.component.ts
Normal file
35
src/app/root/themed-root.component.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { ThemedComponent } from '../shared/theme-support/themed.component';
|
||||
import { RootComponent } from './root.component';
|
||||
import { Component, Input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-themed-root',
|
||||
styleUrls: [],
|
||||
templateUrl: '../shared/theme-support/themed.component.html',
|
||||
})
|
||||
export class ThemedRootComponent extends ThemedComponent<RootComponent> {
|
||||
/**
|
||||
* Whether or not the authentication is currently blocking the UI
|
||||
*/
|
||||
@Input() isNotAuthBlocking: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not the the application is loading;
|
||||
*/
|
||||
@Input() isLoading: boolean;
|
||||
|
||||
protected inAndOutputNames: (keyof RootComponent & keyof this)[] = ['isLoading', 'isNotAuthBlocking'];
|
||||
|
||||
protected getComponentName(): string {
|
||||
return 'RootComponent';
|
||||
}
|
||||
|
||||
protected importThemedComponent(themeName: string): Promise<any> {
|
||||
return import(`../../themes/${themeName}/app/root/root.component`);
|
||||
}
|
||||
|
||||
protected importUnthemedComponent(): Promise<any> {
|
||||
return import(`./root.component`);
|
||||
}
|
||||
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
input[type="text"] {
|
||||
margin-top: -0.5 * $font-size-base;
|
||||
margin-top: calc(-0.5 * var(--bs-font-size-base));
|
||||
|
||||
&:focus {
|
||||
background-color: rgba(255, 255, 255, 0.5) !important;
|
||||
@@ -16,7 +16,7 @@ a.submit-icon {
|
||||
|
||||
|
||||
|
||||
@media screen and (max-width: map-get($grid-breakpoints, sm)) {
|
||||
@media screen and (max-width: var(--ds-grid-breakpoints-sm)) {
|
||||
#query:focus {
|
||||
max-width: 250px !important;
|
||||
width: 40vw !important;
|
||||
|
@@ -1,5 +1,5 @@
|
||||
.chip-selected {
|
||||
background-color: map-get($theme-colors, info) !important;
|
||||
background-color: var(--bs-info) !important;
|
||||
}
|
||||
|
||||
.chip-label {
|
||||
|
@@ -1,15 +1,15 @@
|
||||
.scrollable-menu {
|
||||
height: auto;
|
||||
max-height: $dropdown-menu-max-height;
|
||||
max-height: var(--ds-dropdown-menu-max-height);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.collection-item {
|
||||
border-bottom: $dropdown-border-width solid $dropdown-border-color;
|
||||
border-bottom: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);
|
||||
}
|
||||
|
||||
#collectionControlsDropdownMenu {
|
||||
outline: 0;
|
||||
left: 0 !important;
|
||||
box-shadow: $btn-focus-box-shadow;
|
||||
box-shadow: var(--bs-btn-focus-box-shadow);
|
||||
}
|
||||
|
@@ -1,3 +1,3 @@
|
||||
.btn-dark {
|
||||
background-color: $admin-sidebar-bg;
|
||||
background-color: var(--ds-admin-sidebar-bg);
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
.scrollable-menu {
|
||||
height: auto;
|
||||
max-height: $dso-selector-list-max-height;
|
||||
max-height: var(--ds-dso-selector-list-max-height);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
.ds-base-drop-zone {
|
||||
border: 2px dashed $gray-600;
|
||||
border: 2px dashed var(--bs-gray-600);
|
||||
}
|
||||
|
||||
.ds-document-drop-zone {
|
||||
@@ -9,21 +9,21 @@
|
||||
}
|
||||
|
||||
.ds-document-drop-zone-active {
|
||||
z-index: $drop-zone-area-z-index !important;
|
||||
z-index: var(--ds-drop-zone-area-z-index) !important;
|
||||
}
|
||||
|
||||
.ds-document-drop-zone-inner {
|
||||
background-color: rgba($white, 0.7);
|
||||
z-index: $drop-zone-area-inner-z-index;
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
z-index: var(--ds-drop-zone-area-inner-z-index);
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.ds-document-drop-zone-inner-content {
|
||||
border: 4px dashed map-get($theme-colors, primary);
|
||||
z-index: $drop-zone-area-inner-z-index;
|
||||
border: 4px dashed var(--bs-primary);
|
||||
z-index: var(--ds-drop-zone-area-inner-z-index);
|
||||
}
|
||||
|
||||
.ds-document-drop-zone-inner-content p {
|
||||
font-size: ($font-size-lg * 2.5);
|
||||
font-size: calc(var(--bs-font-size-lg) * 2.5);
|
||||
}
|
||||
|
@@ -1,3 +1,3 @@
|
||||
span.text-contents{
|
||||
padding: $btn-padding-y 0;
|
||||
padding: var(--bs-btn-padding-y) 0;
|
||||
}
|
||||
|
@@ -1,3 +1,3 @@
|
||||
span.text-contents{
|
||||
padding: $btn-padding-y 0;
|
||||
padding: var(--bs-btn-padding-y) 0;
|
||||
}
|
||||
|
@@ -5,16 +5,16 @@
|
||||
}
|
||||
|
||||
.cdk-drag {
|
||||
margin-left: -(2 * $spacer);
|
||||
margin-right: -(0.5 * $spacer);
|
||||
padding-right: (0.5 * $spacer);
|
||||
margin-left: calc(-2 * var(--bs-spacer));
|
||||
margin-right: calc(-0.5 * var(--bs-spacer));
|
||||
padding-right: calc(0.5 * var(--bs-spacer));
|
||||
.drag-icon {
|
||||
visibility: hidden;
|
||||
width: (2 * $spacer);
|
||||
color: $gray-600;
|
||||
margin: $btn-padding-y 0;
|
||||
line-height: $btn-line-height;
|
||||
text-indent: 0.5 * $spacer
|
||||
width: calc(2 * var(--bs-spacer));
|
||||
color: var(--bs-gray-600);
|
||||
margin: var(--bs-btn-padding-y) 0;
|
||||
line-height: var(--bs-btn-line-height);
|
||||
text-indent: calc(0.5 * var(--bs-spacer))
|
||||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
@@ -37,7 +37,7 @@
|
||||
|
||||
.cdk-drag-preview {
|
||||
background-color: white;
|
||||
border-radius: $border-radius-sm;
|
||||
border-radius: var(--bs-border-radius-sm);
|
||||
margin-left: 0;
|
||||
box-shadow: 0 5px 5px 0px rgba(0, 0, 0, 0.2),
|
||||
0 8px 10px 1px rgba(0, 0, 0, 0.14),
|
||||
|
@@ -5,7 +5,7 @@
|
||||
:host ::ng-deep .dropdown-menu {
|
||||
left: 0 !important;
|
||||
width: 100% !important;
|
||||
max-height: $dropdown-menu-max-height;
|
||||
max-height: var(--ds-dropdown-menu-max-height);
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
@@ -14,8 +14,8 @@
|
||||
:host ::ng-deep .dropdown-item:active,
|
||||
:host ::ng-deep .dropdown-item:focus,
|
||||
:host ::ng-deep .dropdown-item:hover {
|
||||
color: $dropdown-link-hover-color !important;
|
||||
background-color: $dropdown-link-hover-bg !important;
|
||||
color: var(--bs-dropdown-link-hover-color) !important;
|
||||
background-color: var(--bs-dropdown-link-hover-bg) !important;
|
||||
}
|
||||
|
||||
div {
|
||||
@@ -23,5 +23,5 @@ div {
|
||||
}
|
||||
|
||||
.lookup-item {
|
||||
border-bottom: $dropdown-border-width solid $dropdown-border-color;
|
||||
border-bottom: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);
|
||||
}
|
||||
|
@@ -1,20 +1,20 @@
|
||||
:host ::ng-deep .dropdown-menu {
|
||||
width: 100% !important;
|
||||
max-height: $dropdown-menu-max-height;
|
||||
max-height: var(--ds-dropdown-menu-max-height);
|
||||
overflow-y: auto !important;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
:host ::ng-deep .dropdown-item {
|
||||
border-bottom: $dropdown-border-width solid $dropdown-border-color;
|
||||
border-bottom: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);
|
||||
}
|
||||
|
||||
:host ::ng-deep .dropdown-item.active,
|
||||
:host ::ng-deep .dropdown-item:active,
|
||||
:host ::ng-deep .dropdown-item:focus,
|
||||
:host ::ng-deep .dropdown-item:hover {
|
||||
color: $dropdown-link-hover-color !important;
|
||||
background-color: $dropdown-link-hover-bg !important;
|
||||
color: var(--bs-dropdown-link-hover-color) !important;
|
||||
background-color: var(--bs-dropdown-link-hover-bg) !important;
|
||||
}
|
||||
|
||||
.treeview .modal-body {
|
||||
|
@@ -2,25 +2,25 @@
|
||||
|
||||
.scrollable-menu {
|
||||
height: auto;
|
||||
max-height: $dropdown-menu-max-height;
|
||||
max-height: var(--ds-dropdown-menu-max-height);
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.collection-item {
|
||||
border-bottom: $dropdown-border-width solid $dropdown-border-color;
|
||||
border-bottom: var(--bs-dropdown-border-width) solid var(--bs-dropdown-border-color);
|
||||
}
|
||||
|
||||
.scrollable-dropdown-loading {
|
||||
background-color: map-get($theme-colors, primary);
|
||||
background-color: var(--bs-primary);
|
||||
color: white;
|
||||
height: $spacer * 2 !important;
|
||||
line-height: $spacer * 2;
|
||||
height: calc(var(--bs-spacer) * 2) !important;
|
||||
line-height: calc(var(--bs-spacer) * 2);
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.scrollable-dropdown-menu {
|
||||
left: 0 !important;
|
||||
margin-bottom: $spacer;
|
||||
margin-bottom: var(--bs-spacer);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
@@ -10,19 +10,19 @@
|
||||
|
||||
:host ::ng-deep .dropdown-menu {
|
||||
width: 100% !important;
|
||||
max-height: $dropdown-menu-max-height;
|
||||
max-height: var(--ds-dropdown-menu-max-height);
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
left: 0 !important;
|
||||
margin-top: $spacer !important;
|
||||
margin-top: var(--bs-spacer) !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .dropdown-item.active,
|
||||
:host ::ng-deep .dropdown-item:active,
|
||||
:host ::ng-deep .dropdown-item:focus,
|
||||
:host ::ng-deep .dropdown-item:hover {
|
||||
color: $dropdown-link-hover-color !important;
|
||||
background-color: $dropdown-link-hover-bg !important;
|
||||
color: var(--bs-dropdown-link-hover-color) !important;
|
||||
background-color: var(--bs-dropdown-link-hover-bg) !important;
|
||||
}
|
||||
|
||||
.tag-input {
|
||||
|
@@ -1,3 +1,3 @@
|
||||
.position-absolute {
|
||||
right: $spacer;
|
||||
right: var(--bs-spacer);
|
||||
}
|
@@ -5,7 +5,7 @@
|
||||
}
|
||||
|
||||
.ds-form-input-btn {
|
||||
border: $input-btn-border-width solid $input-border-color;
|
||||
border: var(--bs-input-btn-border-width) solid var(--bs-input-border-color);
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-left: 0;
|
||||
@@ -36,9 +36,9 @@
|
||||
|
||||
/* add padding */
|
||||
.left-addon input {
|
||||
padding-left: $spacer * 2.25;
|
||||
padding-left: calc(var(--bs-spacer) * 2.25);
|
||||
}
|
||||
|
||||
.right-addon input {
|
||||
padding-right: $spacer * 2.25;
|
||||
padding-right: calc(var(--bs-spacer) * 2.25);
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
.dropdown-item {
|
||||
white-space: normal;
|
||||
word-break: break-word;
|
||||
padding: $input-padding-y $input-padding-x;
|
||||
padding: var(--bs-input-padding-y) var(--bs-input-padding-x);
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
@@ -1,18 +1,18 @@
|
||||
:host ::ng-deep .card {
|
||||
margin-bottom: $submission-sections-margin-bottom;
|
||||
margin-bottom: var(--ds-submission-sections-margin-bottom);
|
||||
overflow: unset;
|
||||
}
|
||||
|
||||
.section-focus {
|
||||
border-radius: $border-radius;
|
||||
box-shadow: $btn-focus-box-shadow;
|
||||
border-radius: var(--bs-border-radius);
|
||||
box-shadow: var(--bs-btn-focus-box-shadow);
|
||||
}
|
||||
|
||||
// TODO to remove the following when upgrading @ng-bootstrap
|
||||
:host ::ng-deep .card:first-of-type {
|
||||
border-bottom: $card-border-width solid $card-border-color !important;
|
||||
border-bottom-left-radius: $card-border-radius !important;
|
||||
border-bottom-right-radius: $card-border-radius !important;
|
||||
border-bottom: var(--bs-card-border-width) solid var(--bs-card-border-color) !important;
|
||||
border-bottom-left-radius: var(--bs-card-border-radius) !important;
|
||||
border-bottom-right-radius: var(--bs-card-border-radius) !important;
|
||||
}
|
||||
|
||||
:host ::ng-deep .card-header button {
|
||||
|
@@ -6,6 +6,7 @@ import { GenericConstructor } from '../../core/shared/generic-constructor';
|
||||
import { MetadataRepresentationListElementComponent } from '../object-list/metadata-representation-list-element/metadata-representation-list-element.component';
|
||||
import { MetadataRepresentationDirective } from './metadata-representation.directive';
|
||||
import { hasValue } from '../empty.util';
|
||||
import { ThemeService } from '../theme-support/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-metadata-representation-loader',
|
||||
@@ -42,7 +43,10 @@ export class MetadataRepresentationLoaderComponent implements OnInit {
|
||||
*/
|
||||
@ViewChild(MetadataRepresentationDirective, {static: true}) mdRepDirective: MetadataRepresentationDirective;
|
||||
|
||||
constructor(private componentFactoryResolver: ComponentFactoryResolver) {
|
||||
constructor(
|
||||
private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private themeService: ThemeService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,6 +68,6 @@ export class MetadataRepresentationLoaderComponent implements OnInit {
|
||||
* @returns {string}
|
||||
*/
|
||||
private getComponent(): GenericConstructor<MetadataRepresentationListElementComponent> {
|
||||
return getMetadataRepresentationComponent(this.mdRepresentation.itemType, this.mdRepresentation.representationType, this.context);
|
||||
return getMetadataRepresentationComponent(this.mdRepresentation.itemType, this.mdRepresentation.representationType, this.context, this.themeService.getThemeName());
|
||||
}
|
||||
}
|
||||
|
@@ -6,15 +6,17 @@ export const map = new Map();
|
||||
|
||||
export const DEFAULT_ENTITY_TYPE = 'Publication';
|
||||
export const DEFAULT_REPRESENTATION_TYPE = MetadataRepresentationType.PlainText;
|
||||
export const DEFAULT_CONTEXT = Context.Undefined;
|
||||
export const DEFAULT_CONTEXT = Context.Any;
|
||||
export const DEFAULT_THEME = '*';
|
||||
|
||||
/**
|
||||
* Decorator function to store metadata representation mapping
|
||||
* @param entityType The entity type the component represents
|
||||
* @param mdRepresentationType The metadata representation type the component represents
|
||||
* @param context The optional context the component represents
|
||||
* @param theme The optional theme for the component
|
||||
*/
|
||||
export function metadataRepresentationComponent(entityType: string, mdRepresentationType: MetadataRepresentationType, context: Context = DEFAULT_CONTEXT) {
|
||||
export function metadataRepresentationComponent(entityType: string, mdRepresentationType: MetadataRepresentationType, context: Context = DEFAULT_CONTEXT, theme = DEFAULT_THEME) {
|
||||
return function decorator(component: any) {
|
||||
if (hasNoValue(map.get(entityType))) {
|
||||
map.set(entityType, new Map());
|
||||
@@ -23,10 +25,14 @@ export function metadataRepresentationComponent(entityType: string, mdRepresenta
|
||||
map.get(entityType).set(mdRepresentationType, new Map());
|
||||
}
|
||||
|
||||
if (hasValue(map.get(entityType).get(mdRepresentationType).get(context))) {
|
||||
if (hasNoValue(map.get(entityType).get(mdRepresentationType).get(context))) {
|
||||
map.get(entityType).get(mdRepresentationType).set(context, new Map());
|
||||
}
|
||||
|
||||
if (hasValue(map.get(entityType).get(mdRepresentationType).get(context).get(theme))) {
|
||||
throw new Error(`There can't be more than one component to render Entity of type "${entityType}" in MetadataRepresentation "${mdRepresentationType}" with context "${context}"`);
|
||||
}
|
||||
map.get(entityType).get(mdRepresentationType).set(context, component);
|
||||
map.get(entityType).get(mdRepresentationType).get(context).set(theme, component);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,22 +41,32 @@ export function metadataRepresentationComponent(entityType: string, mdRepresenta
|
||||
* @param entityType The entity type to match
|
||||
* @param mdRepresentationType The metadata representation to match
|
||||
* @param context The context to match
|
||||
* @param theme the theme to match
|
||||
*/
|
||||
export function getMetadataRepresentationComponent(entityType: string, mdRepresentationType: MetadataRepresentationType, context: Context = DEFAULT_CONTEXT) {
|
||||
export function getMetadataRepresentationComponent(entityType: string, mdRepresentationType: MetadataRepresentationType, context: Context = DEFAULT_CONTEXT, theme = DEFAULT_THEME) {
|
||||
const mapForEntity = map.get(entityType);
|
||||
if (hasValue(mapForEntity)) {
|
||||
const entityAndMDRepMap = mapForEntity.get(mdRepresentationType);
|
||||
if (hasValue(entityAndMDRepMap)) {
|
||||
if (hasValue(entityAndMDRepMap.get(context))) {
|
||||
return entityAndMDRepMap.get(context);
|
||||
const contextMap = entityAndMDRepMap.get(context);
|
||||
if (hasValue(contextMap)) {
|
||||
if (hasValue(contextMap.get(theme))) {
|
||||
return contextMap.get(theme);
|
||||
}
|
||||
if (hasValue(entityAndMDRepMap.get(DEFAULT_CONTEXT))) {
|
||||
return entityAndMDRepMap.get(DEFAULT_CONTEXT);
|
||||
if (hasValue(contextMap.get(DEFAULT_THEME))) {
|
||||
return contextMap.get(DEFAULT_THEME);
|
||||
}
|
||||
}
|
||||
if (hasValue(mapForEntity.get(DEFAULT_REPRESENTATION_TYPE))) {
|
||||
return mapForEntity.get(DEFAULT_REPRESENTATION_TYPE).get(DEFAULT_CONTEXT);
|
||||
if (hasValue(entityAndMDRepMap.get(DEFAULT_CONTEXT)) &&
|
||||
hasValue(entityAndMDRepMap.get(DEFAULT_CONTEXT).get(DEFAULT_THEME))) {
|
||||
return entityAndMDRepMap.get(DEFAULT_CONTEXT).get(DEFAULT_THEME);
|
||||
}
|
||||
}
|
||||
return map.get(DEFAULT_ENTITY_TYPE).get(DEFAULT_REPRESENTATION_TYPE).get(DEFAULT_CONTEXT);
|
||||
if (hasValue(mapForEntity.get(DEFAULT_REPRESENTATION_TYPE)) &&
|
||||
hasValue(mapForEntity.get(DEFAULT_REPRESENTATION_TYPE).get(DEFAULT_CONTEXT)) &&
|
||||
hasValue(mapForEntity.get(DEFAULT_REPRESENTATION_TYPE).get(DEFAULT_CONTEXT).get(DEFAULT_THEME))) {
|
||||
return mapForEntity.get(DEFAULT_REPRESENTATION_TYPE).get(DEFAULT_CONTEXT).get(DEFAULT_THEME);
|
||||
}
|
||||
}
|
||||
return map.get(DEFAULT_ENTITY_TYPE).get(DEFAULT_REPRESENTATION_TYPE).get(DEFAULT_CONTEXT).get(DEFAULT_THEME);
|
||||
}
|
||||
|
7
src/app/shared/mocks/theme-service.mock.ts
Normal file
7
src/app/shared/mocks/theme-service.mock.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { ThemeService } from '../theme-support/theme.service';
|
||||
|
||||
export function getMockThemeService(): ThemeService {
|
||||
return jasmine.createSpyObj('themeService', {
|
||||
getThemeName: 'base'
|
||||
});
|
||||
}
|
14
src/app/shared/ngrx/no-op.action.ts
Normal file
14
src/app/shared/ngrx/no-op.action.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
import { type } from './type';
|
||||
|
||||
export const NO_OP_ACTION_TYPE = type('dspace/ngrx/NO_OP_ACTION');
|
||||
|
||||
/**
|
||||
* An action to use when nothing needs to happen, but you're forced to dispatch an action anyway.
|
||||
* e.g. an effect that needs to do something if a certain check succeeds, and nothing otherwise.
|
||||
*
|
||||
* It should not be used in any reducer or listened for in any effect.
|
||||
*/
|
||||
export class NoOpAction implements Action {
|
||||
public readonly type = NO_OP_ACTION_TYPE;
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
.alert {
|
||||
display: inline-block;
|
||||
min-width: $modal-sm;
|
||||
min-width: var(--bs-modal-sm);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -19,14 +19,14 @@
|
||||
}
|
||||
|
||||
.alert-success .notification-progress-loader span {
|
||||
background: darken(adjust-hue(map-get($theme-colors, success), -10), 10%);
|
||||
background: var(--ds-notification-bg-success);
|
||||
}
|
||||
.alert-danger .notification-progress-loader span {
|
||||
background: darken(adjust-hue(map-get($theme-colors, danger), -10), 10%);
|
||||
background: var(--ds-notification-bg-danger);
|
||||
}
|
||||
.alert-info .notification-progress-loader span {
|
||||
background: darken(adjust-hue(map-get($theme-colors, info), -10), 10%);
|
||||
background: var(--ds-notification-bg-info);
|
||||
}
|
||||
.alert-warning .notification-progress-loader span {
|
||||
background: darken(adjust-hue(map-get($theme-colors, warning), -10), 10%);
|
||||
background: var(--ds-notification-bg-warning);
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
.notifications-wrapper {
|
||||
z-index: $zindex-popover;
|
||||
z-index: var(--bs-zindex-popover);
|
||||
text-align: right;
|
||||
@include word-wrap;
|
||||
.notification {
|
||||
@@ -37,7 +37,7 @@
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
@media screen and (max-width: map-get($grid-breakpoints, sm)) {
|
||||
@media screen and (max-width: var(--ds-grid-breakpoints-sm)) {
|
||||
.notifications-wrapper {
|
||||
width: auto;
|
||||
left: 0;
|
||||
|
@@ -1,4 +1,11 @@
|
||||
import { Component, ComponentFactoryResolver, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
ComponentFactoryResolver,
|
||||
ElementRef,
|
||||
Input,
|
||||
OnInit,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { ListableObject } from '../listable-object.model';
|
||||
import { ViewMode } from '../../../../core/shared/view-mode.model';
|
||||
import { Context } from '../../../../core/shared/context.model';
|
||||
@@ -7,6 +14,7 @@ import { GenericConstructor } from '../../../../core/shared/generic-constructor'
|
||||
import { ListableObjectDirective } from './listable-object.directive';
|
||||
import { CollectionElementLinkType } from '../../collection-element-link.type';
|
||||
import { hasValue } from '../../../empty.util';
|
||||
import { ThemeService } from '../../../theme-support/theme.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-listable-object-component-loader',
|
||||
@@ -83,7 +91,10 @@ export class ListableObjectComponentLoaderComponent implements OnInit {
|
||||
*/
|
||||
withdrawnBadge = false;
|
||||
|
||||
constructor(private componentFactoryResolver: ComponentFactoryResolver) {
|
||||
constructor(
|
||||
private componentFactoryResolver: ComponentFactoryResolver,
|
||||
private themeService: ThemeService
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,6 +143,6 @@ export class ListableObjectComponentLoaderComponent implements OnInit {
|
||||
* @returns {GenericConstructor<Component>}
|
||||
*/
|
||||
private getComponent(): GenericConstructor<Component> {
|
||||
return getListableObjectComponent(this.object.getRenderTypes(), this.viewMode, this.context);
|
||||
return getListableObjectComponent(this.object.getRenderTypes(), this.viewMode, this.context, this.themeService.getThemeName());
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user