mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
57053: progress menu bar
This commit is contained in:
@@ -10,52 +10,81 @@ import { type } from '../../shared/ngrx/type';
|
||||
* literal types and runs a simple check to guarantee all
|
||||
* action types in the application are unique.
|
||||
*/
|
||||
export const AdminSidebarSectionActionTypes = {
|
||||
COLLAPSE: type('dspace/admin-sidebar-section/COLLAPSE'),
|
||||
EXPAND: type('dspace/admin-sidebar-sectio/EXPAND'),
|
||||
TOGGLE: type('dspace/admin-sidebar-sectio/TOGGLE'),
|
||||
export const AdminSidebarActionTypes = {
|
||||
SECTION_COLLAPSE: type('dspace/admin-sidebar/SECTION_COLLAPSE'),
|
||||
SECTION_EXPAND: type('dspace/admin-sidebar/SECTION_EXPAND'),
|
||||
SECTION_TOGGLE: type('dspace/admin-sidebar/SECTION_TOGGLE'),
|
||||
COLLAPSE: type('dspace/admin-sidebar/COLLAPSE'),
|
||||
EXPAND: type('dspace/admin-sidebar/EXPAND'),
|
||||
TOGGLE: type('dspace/admin-sidebar/TOGGLE'),
|
||||
};
|
||||
|
||||
export class AdminSidebarSectionAction implements Action {
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
export class AdminSidebarAction implements Action {
|
||||
|
||||
/**
|
||||
* Type of action that will be performed
|
||||
*/
|
||||
type;
|
||||
}
|
||||
|
||||
export class AdminSidebarSectionAction extends AdminSidebarAction {
|
||||
/**
|
||||
* Name of the section the action is performed on, used to identify the section
|
||||
*/
|
||||
sectionName: string;
|
||||
|
||||
/**
|
||||
* Type of action that will be performed
|
||||
*/
|
||||
type;
|
||||
|
||||
/**
|
||||
* Initialize with the section's name
|
||||
* @param {string} name of the section
|
||||
*/
|
||||
constructor(name: string) {
|
||||
super();
|
||||
this.sectionName = name;
|
||||
}
|
||||
}
|
||||
|
||||
/* tslint:disable:max-classes-per-file */
|
||||
/**
|
||||
* Used to collapse the sidebar
|
||||
*/
|
||||
export class AdminSidebarCollapseAction extends AdminSidebarAction {
|
||||
type = AdminSidebarActionTypes.COLLAPSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to expand the sidebar
|
||||
*/
|
||||
export class AdminSidebarExpandAction extends AdminSidebarAction {
|
||||
type = AdminSidebarActionTypes.EXPAND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to collapse the sidebar when it's expanded and expand it when it's collapsed
|
||||
*/
|
||||
export class AdminSidebarToggleAction extends AdminSidebarAction {
|
||||
type = AdminSidebarActionTypes.TOGGLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to collapse a section
|
||||
*/
|
||||
export class AdminSidebarSectionCollapseAction extends AdminSidebarSectionAction {
|
||||
type = AdminSidebarSectionActionTypes.COLLAPSE;
|
||||
type = AdminSidebarActionTypes.SECTION_COLLAPSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to expand a section
|
||||
*/
|
||||
export class AdminSidebarSectionExpandAction extends AdminSidebarSectionAction {
|
||||
type = AdminSidebarSectionActionTypes.EXPAND;
|
||||
type = AdminSidebarActionTypes.SECTION_EXPAND;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to collapse a section when it's expanded and expand it when it's collapsed
|
||||
*/
|
||||
export class AdminSidebarSectionToggleAction extends AdminSidebarSectionAction {
|
||||
type = AdminSidebarSectionActionTypes.TOGGLE;
|
||||
type = AdminSidebarActionTypes.SECTION_TOGGLE;
|
||||
}
|
||||
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
@@ -1,17 +1,21 @@
|
||||
<nav class="navbar navbar-dark bg-dark p-0">
|
||||
<nav class="navbar navbar-dark bg-dark p-0" [ngClass]="{'active': (sidebarCollapsed | async)}"
|
||||
[@slideSidebar]="{
|
||||
value: ((sidebarCollapsed | async) ? 'collapsed' : 'expanded'),
|
||||
params: {sidebarWidth: (sidebarWidth | async)}
|
||||
}">
|
||||
<div class="sidebar-top-level-items">
|
||||
<ul class="navbar-nav">
|
||||
<li class="admin-menu-header">
|
||||
<a class="shortcuts-tree navbar-brand" href="#">
|
||||
<img class="admin-logo mr-2" src="assets/images/dspace-logo-mini.svg">
|
||||
<h4 class="nav-item-name">Admin</h4>
|
||||
<h4 class="section-header-text">Admin</h4>
|
||||
</a>
|
||||
</li>
|
||||
<li [ngClass]="{'active': (active('new') | async)}">
|
||||
<a class="nav-item nav-link shortcuts-tree" href="#"
|
||||
(click)="toggle($event, 'new')">
|
||||
(click)="toggleSection($event, 'new')">
|
||||
<i class="fas fa-plus-circle fa-fw"></i>
|
||||
New
|
||||
<span class="section-header-text">New</span>
|
||||
<i class="fas fa-chevron-right fa-pull-right fa-xxs"
|
||||
[ngClass]="{'fa-rotate-90': (active('new') | async)}"></i>
|
||||
</a>
|
||||
@@ -24,9 +28,9 @@
|
||||
</li>
|
||||
<li [ngClass]="{'active': (active('edit') | async)}">
|
||||
<a class="nav-item nav-link shortcuts-tree" href="#"
|
||||
(click)="toggle($event, 'edit')">
|
||||
(click)="toggleSection($event, 'edit')">
|
||||
<i class="fas fa-pencil-alt fa-fw"></i>
|
||||
Edit
|
||||
<span class="section-header-text">Edit</span>
|
||||
<i class="fas fa-chevron-right fa-pull-right fa-xxs"
|
||||
[ngClass]="{'fa-rotate-90': (active('edit') | async)}"></i>
|
||||
</a>
|
||||
@@ -38,9 +42,9 @@
|
||||
</li>
|
||||
<li [ngClass]="{'active': (active('import') | async)}">
|
||||
<a class="nav-item nav-link shortcuts-tree" href="#"
|
||||
(click)="toggle($event, 'import')">
|
||||
(click)="toggleSection($event, 'import')">
|
||||
<i class="fas fa-arrow-circle-up fa-fw"></i>
|
||||
Import
|
||||
<span class="section-header-text">Import</span>
|
||||
<i class="fas fa-chevron-right fa-pull-right fa-xxs"
|
||||
[ngClass]="{'fa-rotate-90': (active('import') | async)}"></i>
|
||||
</a>
|
||||
@@ -51,9 +55,9 @@
|
||||
</li>
|
||||
<li [ngClass]="{'active': (active('export') | async)}">
|
||||
<a class="nav-item nav-link shortcuts-tree" href="#"
|
||||
(click)="toggle($event, 'export')">
|
||||
(click)="toggleSection($event, 'export')">
|
||||
<i class="fas fa-arrow-circle-down fa-fw"></i>
|
||||
Export
|
||||
<span class="section-header-text">Export</span>
|
||||
<i class="fas fa-chevron-right fa-pull-right fa-xxs"
|
||||
[ngClass]="{'fa-rotate-90': (active('export') | async)}"></i>
|
||||
</a>
|
||||
@@ -66,9 +70,9 @@
|
||||
</li>
|
||||
<li [ngClass]="{'active': (active('access_control') | async)}">
|
||||
<a class="nav-item nav-link shortcuts-tree" href="#"
|
||||
(click)="toggle($event, 'access_control')">
|
||||
(click)="toggleSection($event, 'access_control')">
|
||||
<i class="fas fa-key fa-fw"></i>
|
||||
Access Control
|
||||
<span class="section-header-text">Access Control</span>
|
||||
<i class="fas fa-chevron-right fa-pull-right fa-xxs"
|
||||
[ngClass]="{'fa-rotate-90': (active('access_control') | async)}"></i>
|
||||
</a>
|
||||
@@ -81,9 +85,9 @@
|
||||
</li>
|
||||
<li [ngClass]="{'active': (active('find') | async)}">
|
||||
<a class="nav-item nav-link shortcuts-tree" href="#"
|
||||
(click)="toggle($event, 'find')">
|
||||
(click)="toggleSection($event, 'find')">
|
||||
<i class="fas fa-search fa-fw"></i>
|
||||
Find
|
||||
<span class="section-header-text">Find</span>
|
||||
<i class="fas fa-chevron-right fa-pull-right fa-xxs"
|
||||
[ngClass]="{'fa-rotate-90': (active('find') | async)}"></i>
|
||||
</a>
|
||||
@@ -95,9 +99,9 @@
|
||||
</li>
|
||||
<li [ngClass]="{'active': (active('registries') | async)}">
|
||||
<a class="nav-item nav-link shortcuts-tree" href="#"
|
||||
(click)="toggle($event, 'registries')">
|
||||
(click)="toggleSection($event, 'registries')">
|
||||
<i class="fas fa-list fa-fw"></i>
|
||||
Registries
|
||||
<span class="section-header-text">Registries</span>
|
||||
<i class="fas fa-chevron-right fa-pull-right fa-xxs"
|
||||
[ngClass]="{'fa-rotate-90': (active('registries') | async)}"></i>
|
||||
</a>
|
||||
@@ -109,27 +113,28 @@
|
||||
<li>
|
||||
<a class="nav-item nav-link shortcuts-tree" href="#">
|
||||
<i class="fas fa-filter fa-fw"></i>
|
||||
Curation Tasks
|
||||
<span class="section-header-text">Curation Tasks</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-item nav-link shortcuts-tree" href="#">
|
||||
<i class="fas fa-chart-bar fa-fw"></i>
|
||||
Statistics
|
||||
<span class="section-header-text">Statistics</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-item nav-link shortcuts-tree" href="#">
|
||||
<i class="fas fa-cogs fa-fw"></i>
|
||||
Control Panel
|
||||
<span class="section-header-text">Control Panel</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="navbar-nav">
|
||||
<a class="nav-item nav-link shortcuts-tree" href="#">
|
||||
<a class="nav-item nav-link shortcuts-tree" href="#"
|
||||
(click)="toggle($event)">
|
||||
<i class="fas fa-fw fa-angle-double-right"></i>
|
||||
Collapse
|
||||
<span class="section-header-text">Collapse</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
@@ -1,21 +1,36 @@
|
||||
@import '../../../styles/variables.scss';
|
||||
|
||||
:host {
|
||||
position: fixed;
|
||||
nav {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
flex: 1 1 auto;
|
||||
nav {
|
||||
height: 100%;
|
||||
flex-direction: column;
|
||||
>div.sidebar-top-level-items {
|
||||
> div {
|
||||
width: 100%;
|
||||
&.sidebar-top-level-items {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.section-header-text {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.navbar-nav {
|
||||
min-width: $admin-sidebar-width;
|
||||
> * {
|
||||
> li, > a {
|
||||
padding: $spacer/2 $spacer;
|
||||
&.active {
|
||||
background-color: $admin-sidebar-dark;
|
||||
}
|
||||
.fa-fw {
|
||||
text-align: left;
|
||||
width: 1.75em;
|
||||
}
|
||||
}
|
||||
.sidebar-sub-level-items {
|
||||
list-style: disc;
|
||||
@@ -28,14 +43,16 @@
|
||||
.admin-menu-header {
|
||||
background-color: $admin-sidebar-dark;
|
||||
img {
|
||||
|
||||
height: 1em;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
h4 {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
.section-header-text, .fa-chevron-right {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,34 +1,41 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store';
|
||||
import { AdminSidebarSectionsState, AdminSidebarSectionState } from './admin-sidebar.reducer';
|
||||
import { MemoizedSelector, select, Store } from '@ngrx/store';
|
||||
import { AdminSidebarSectionState, AdminSidebarState, } from './admin-sidebar.reducer';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { AdminSidebarSectionToggleAction } from './admin-sidebar.actions';
|
||||
import { AdminSidebarSectionToggleAction, AdminSidebarToggleAction } from './admin-sidebar.actions';
|
||||
import { AppState, keySelector } from '../../app.reducer';
|
||||
import { slideSidebar } from '../../shared/animations/slide';
|
||||
import { CSSVariableService } from '../../shared/sass-helper/sass-helper.service';
|
||||
|
||||
const sidebarSectionStateSelector = (state: AdminSidebarSectionsState) => state.adminSidebarSection;
|
||||
const sidebarSectionStateSelector = (state: AppState) => state.adminSidebar.sections;
|
||||
const sidebarStateSelector = (state) => state.adminSidebar;
|
||||
|
||||
const sectionByNameSelector = (name: string): MemoizedSelector<AdminSidebarSectionsState, AdminSidebarSectionState> => {
|
||||
return keySelector<AdminSidebarSectionState>(name);
|
||||
const sectionByNameSelector = (name: string): MemoizedSelector<AppState, AdminSidebarSectionState> => {
|
||||
return keySelector<AdminSidebarSectionState>(name, sidebarSectionStateSelector);
|
||||
};
|
||||
|
||||
export function keySelector<T>(key: string): MemoizedSelector<AdminSidebarSectionsState, T> {
|
||||
return createSelector(sidebarSectionStateSelector, (state: AdminSidebarSectionState) => {
|
||||
if (hasValue(state)) {
|
||||
return state[key];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
@Component({
|
||||
selector: 'ds-admin-sidebar',
|
||||
templateUrl: './admin-sidebar.component.html',
|
||||
styleUrls: ['./admin-sidebar.component.scss'],
|
||||
animations: [slideSidebar]
|
||||
})
|
||||
export class AdminSidebarComponent {
|
||||
constructor(private store: Store<AdminSidebarSectionsState>) {
|
||||
export class AdminSidebarComponent implements OnInit {
|
||||
sidebarCollapsed: Observable<boolean>;
|
||||
sidebarWidth: Observable<string>;
|
||||
|
||||
constructor(private store: Store<AdminSidebarState>,
|
||||
private variableService: CSSVariableService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.sidebarWidth = this.variableService.getVariable('adminSidebarWidth')
|
||||
this.sidebarCollapsed = this.store.pipe(
|
||||
select(sidebarStateSelector),
|
||||
map((state: AdminSidebarState) => state.collapsed)
|
||||
);
|
||||
}
|
||||
|
||||
public active(name: string): Observable<boolean> {
|
||||
@@ -38,8 +45,13 @@ export class AdminSidebarComponent {
|
||||
);
|
||||
}
|
||||
|
||||
toggle(event: Event, name: string) {
|
||||
toggleSection(event: Event, name: string) {
|
||||
event.preventDefault();
|
||||
this.store.dispatch(new AdminSidebarSectionToggleAction(name));
|
||||
}
|
||||
|
||||
toggle(event: Event) {
|
||||
event.preventDefault();
|
||||
this.store.dispatch(new AdminSidebarToggleAction());
|
||||
}
|
||||
}
|
||||
|
@@ -1,56 +1,100 @@
|
||||
import { AdminSidebarSectionAction, AdminSidebarSectionActionTypes } from './admin-sidebar.actions';
|
||||
import {
|
||||
AdminSidebarAction,
|
||||
AdminSidebarSectionAction,
|
||||
AdminSidebarActionTypes
|
||||
} from './admin-sidebar.actions';
|
||||
import { hasValue } from '../../shared/empty.util';
|
||||
|
||||
/**
|
||||
* Interface that represents the state for a single section
|
||||
* Interface that represents the state for a single sidebar section
|
||||
*/
|
||||
export interface AdminSidebarSectionState {
|
||||
sectionCollapsed: boolean,
|
||||
sectionCollapsed: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface that represents the state for all available sections
|
||||
*/
|
||||
export interface AdminSidebarSectionsState {
|
||||
[name: string]: AdminSidebarSectionState
|
||||
[name: string]: AdminSidebarSectionState;
|
||||
}
|
||||
|
||||
const initialState: AdminSidebarSectionsState = Object.create(null);
|
||||
const initiallyCollapsed = true;
|
||||
/**
|
||||
* Performs a search section action on the current state
|
||||
* @param {AdminSidebarSectionsState} state The state before the action is performed
|
||||
* @param {AdminSidebarSectionAction} action The action that should be performed
|
||||
* @returns {AdminSidebarSectionsState} The state after the action is performed
|
||||
* Interface that represents the state of the admin sidebar and its sections
|
||||
*/
|
||||
export function sidebarSectionReducer(state = initialState, action: AdminSidebarSectionAction): AdminSidebarSectionsState {
|
||||
export interface AdminSidebarState {
|
||||
sections: AdminSidebarSectionsState,
|
||||
collapsed: boolean;
|
||||
}
|
||||
|
||||
const initialState: AdminSidebarState = Object.create(null);
|
||||
const initiallySectionCollapsed = true;
|
||||
const initiallyCollapsed = false;
|
||||
|
||||
/**
|
||||
* Performs a sidebar action on the current state
|
||||
* @param {AdminSidebarState} state The state before the action is performed
|
||||
* @param {AdminSidebarAction} action The action that should be performed
|
||||
* @returns {AdminSidebarState} The state after the action is performed
|
||||
*/
|
||||
export function adminSidebarReducer(state = initialState, action: AdminSidebarAction): AdminSidebarState {
|
||||
|
||||
if (action instanceof AdminSidebarSectionAction) {
|
||||
return reduceSectionAction(state, action);
|
||||
} else {
|
||||
switch (action.type) {
|
||||
case AdminSidebarSectionActionTypes.COLLAPSE: {
|
||||
case AdminSidebarActionTypes.COLLAPSE: {
|
||||
return Object.assign({}, state, {
|
||||
collapsed: true
|
||||
});
|
||||
}
|
||||
|
||||
case AdminSidebarActionTypes.EXPAND: {
|
||||
return Object.assign({}, state, {
|
||||
collapsed: false
|
||||
});
|
||||
|
||||
}
|
||||
case AdminSidebarActionTypes.TOGGLE: {
|
||||
const currentState = state.collapsed;
|
||||
const collapsed = hasValue(currentState) ? currentState : initiallyCollapsed;
|
||||
return Object.assign({}, state, {
|
||||
collapsed: !collapsed
|
||||
});
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reduceSectionAction(state: AdminSidebarState, action: AdminSidebarSectionAction): AdminSidebarState {
|
||||
switch (action.type) {
|
||||
case AdminSidebarActionTypes.SECTION_COLLAPSE: {
|
||||
const sections = Object.assign({}, state.sections, {
|
||||
[action.sectionName]: {
|
||||
sectionCollapsed: true,
|
||||
}
|
||||
});
|
||||
return Object.assign({}, state, { sections });
|
||||
}
|
||||
|
||||
case AdminSidebarSectionActionTypes.EXPAND: {
|
||||
return Object.assign({}, state, {
|
||||
case AdminSidebarActionTypes.SECTION_EXPAND: {
|
||||
const sections = Object.assign({}, state.sections, {
|
||||
[action.sectionName]: {
|
||||
sectionCollapsed: false,
|
||||
}
|
||||
});
|
||||
return Object.assign({}, state, { sections });
|
||||
|
||||
}
|
||||
case AdminSidebarSectionActionTypes.TOGGLE: {
|
||||
const currentState = state[action.sectionName];
|
||||
const collapsed = hasValue(currentState) ? currentState.sectionCollapsed : initiallyCollapsed;
|
||||
return Object.assign({}, state, {
|
||||
case AdminSidebarActionTypes.SECTION_TOGGLE: {
|
||||
const currentState = state.sections;
|
||||
const collapsed = (hasValue(currentState) && currentState[action.sectionName]) ? currentState[action.sectionName].sectionCollapsed : initiallySectionCollapsed;
|
||||
const sections = Object.assign({}, state.sections, {
|
||||
[action.sectionName]: {
|
||||
sectionCollapsed: !collapsed,
|
||||
}
|
||||
});
|
||||
|
||||
return Object.assign({}, state, { sections });
|
||||
}
|
||||
|
||||
default: {
|
||||
|
@@ -8,6 +8,9 @@
|
||||
|
||||
.dspace-logo-container {
|
||||
margin: 10px 20px 0px 20px;
|
||||
.display-3 {
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.dspace-logo-container img {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
@import '../styles/variables.scss';
|
||||
@import '../styles/font-awesome-imports.scss';
|
||||
@import '../styles/helpers/font_awesome_imports.scss';
|
||||
@import '../../node_modules/bootstrap/scss/bootstrap.scss';
|
||||
@import '../../node_modules/nouislider/distribute/nouislider.min.css';
|
||||
|
||||
|
@@ -23,6 +23,8 @@ import { NativeWindowRef, NativeWindowService } from './shared/services/window.s
|
||||
import { isAuthenticated } from './core/auth/selectors';
|
||||
import { AuthService } from './core/auth/auth.service';
|
||||
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
|
||||
import variables from '../styles/_exposed_variables.scss';
|
||||
import { CSSVariableService } from './shared/sass-helper/sass-helper.service';
|
||||
|
||||
@Component({
|
||||
selector: 'ds-app',
|
||||
@@ -42,7 +44,8 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
private metadata: MetadataService,
|
||||
private angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics,
|
||||
private authService: AuthService,
|
||||
private router: Router
|
||||
private router: Router,
|
||||
private cssService: CSSVariableService
|
||||
) {
|
||||
// this language will be used as a fallback when a translation isn't found in the current language
|
||||
translate.setDefaultLang('en');
|
||||
@@ -54,6 +57,8 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
if (config.debug) {
|
||||
console.info(config);
|
||||
}
|
||||
this.storeCSSVariables();
|
||||
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
@@ -67,7 +72,13 @@ export class AppComponent implements OnInit, AfterViewInit {
|
||||
first(),
|
||||
filter((authenticated) => !authenticated)
|
||||
).subscribe((authenticated) => this.authService.checkAuthenticationToken());
|
||||
}
|
||||
|
||||
private storeCSSVariables() {
|
||||
const vars = variables.locals;
|
||||
Object.keys(vars).forEach((name: string) => {
|
||||
this.cssService.addCSSVariable(name, vars[name]);
|
||||
})
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { ActionReducerMap } from '@ngrx/store';
|
||||
import { ActionReducerMap, createSelector, MemoizedSelector } from '@ngrx/store';
|
||||
import * as fromRouter from '@ngrx/router-store';
|
||||
import { hostWindowReducer, HostWindowState } from './shared/host-window.reducer';
|
||||
import { formReducer, FormState } from './shared/form/form.reducer';
|
||||
@@ -17,9 +17,11 @@ import {
|
||||
import { truncatableReducer, TruncatablesState } from './shared/truncatable/truncatable.reducer';
|
||||
import { navbarReducer, NavbarState } from './navbar/navbar.reducer';
|
||||
import {
|
||||
AdminSidebarSectionsState,
|
||||
sidebarSectionReducer
|
||||
adminSidebarReducer,
|
||||
AdminSidebarState
|
||||
} from './+admin/admin-sidebar/admin-sidebar.reducer';
|
||||
import { hasValue } from './shared/empty.util';
|
||||
import { cssVariablesReducer, CSSVariablesState } from './shared/sass-helper/sass-helper.reducer';
|
||||
|
||||
export interface AppState {
|
||||
router: fromRouter.RouterReducerState;
|
||||
@@ -30,7 +32,8 @@ export interface AppState {
|
||||
searchSidebar: SearchSidebarState;
|
||||
searchFilter: SearchFiltersState;
|
||||
truncatable: TruncatablesState;
|
||||
adminSidebarSection: AdminSidebarSectionsState;
|
||||
adminSidebar: AdminSidebarState;
|
||||
cssVariables: CSSVariablesState;
|
||||
}
|
||||
|
||||
export const appReducers: ActionReducerMap<AppState> = {
|
||||
@@ -42,7 +45,18 @@ export const appReducers: ActionReducerMap<AppState> = {
|
||||
searchSidebar: sidebarReducer,
|
||||
searchFilter: filterReducer,
|
||||
truncatable: truncatableReducer,
|
||||
adminSidebarSection: sidebarSectionReducer
|
||||
adminSidebar: adminSidebarReducer,
|
||||
cssVariables: cssVariablesReducer,
|
||||
};
|
||||
|
||||
export const routerStateSelector = (state: AppState) => state.router;
|
||||
|
||||
export function keySelector<T>(key: string, selector): MemoizedSelector<AppState, T> {
|
||||
return createSelector(selector, (state) => {
|
||||
if (hasValue(state)) {
|
||||
return state[key];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@@ -64,6 +64,7 @@ import { NotificationsService } from '../shared/notifications/notifications.serv
|
||||
import { UploaderService } from '../shared/uploader/uploader.service';
|
||||
import { BrowseItemsResponseParsingService } from './data/browse-items-response-parsing-service';
|
||||
import { DSpaceObjectDataService } from './data/dspace-object-data.service';
|
||||
import { CSSVariableService } from '../shared/sass-helper/sass-helper.service';
|
||||
|
||||
const IMPORTS = [
|
||||
CommonModule,
|
||||
@@ -128,6 +129,7 @@ const PROVIDERS = [
|
||||
UploaderService,
|
||||
UUIDService,
|
||||
DSpaceObjectDataService,
|
||||
CSSVariableService,
|
||||
// register AuthInterceptor as HttpInterceptor
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
|
@@ -17,7 +17,7 @@ export class NavbarComponent {
|
||||
|
||||
constructor(
|
||||
private store: Store<AppState>,
|
||||
protected windowService: HostWindowService
|
||||
public windowService: HostWindowService
|
||||
) {
|
||||
}
|
||||
|
||||
|
@@ -2,18 +2,18 @@ import { animate, state, transition, trigger, style } from '@angular/animations'
|
||||
|
||||
export const focusShadow = trigger('focusShadow', [
|
||||
|
||||
state('focus', style({ 'box-shadow': 'rgba(119, 119, 119, 0.6) 0px 0px 6px' })),
|
||||
state('focus', style({ boxShadow: 'rgba(119, 119, 119, 0.6) 0px 0px 6px' })),
|
||||
|
||||
state('blur', style({ 'box-shadow': 'none' })),
|
||||
state('blur', style({ boxShadow: 'none' })),
|
||||
|
||||
transition('focus <=> blur', animate(250))
|
||||
transition('focus <=> blur', [animate('250ms')])
|
||||
]);
|
||||
|
||||
export const focusBackground = trigger('focusBackground', [
|
||||
|
||||
state('focus', style({ 'background-color': 'rgba(119, 119, 119, 0.1)' })),
|
||||
state('focus', style({ backgroundColor: 'rgba(119, 119, 119, 0.1)' })),
|
||||
|
||||
state('blur', style({ 'background-color': 'transparent' })),
|
||||
state('blur', style({ backgroundColor: 'transparent' })),
|
||||
|
||||
transition('focus <=> blur', animate(250))
|
||||
transition('focus <=> blur', [animate('250ms')])
|
||||
]);
|
||||
|
@@ -6,7 +6,7 @@ export const slide = trigger('slide', [
|
||||
|
||||
state('collapsed', style({ height: 0 })),
|
||||
|
||||
transition('expanded <=> collapsed', animate(250))
|
||||
transition('expanded <=> collapsed', animate('250ms'))
|
||||
]);
|
||||
|
||||
export const slideMobileNav = trigger('slideMobileNav', [
|
||||
@@ -15,5 +15,17 @@ export const slideMobileNav = trigger('slideMobileNav', [
|
||||
|
||||
state('collapsed', style({ height: 0 })),
|
||||
|
||||
transition('expanded <=> collapsed', animate(300))
|
||||
transition('expanded <=> collapsed', animate('300ms'))
|
||||
]);
|
||||
|
||||
export const slideSidebar = trigger('slideSidebar', [
|
||||
|
||||
state('expanded',
|
||||
style({ width: '{{ sidebarWidth }}' }),
|
||||
{ params: { sidebarWidth: '*' } }
|
||||
),
|
||||
|
||||
state('collapsed', style({ width: '*' })),
|
||||
|
||||
transition('expanded <=> collapsed', animate('300ms ease-in-out')),
|
||||
]);
|
||||
|
@@ -3,7 +3,13 @@ import { cold, hot } from 'jasmine-marbles';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { AppState } from '../app.reducer';
|
||||
|
||||
import { GridBreakpoint, HostWindowService, WidthCategory } from './host-window.service';
|
||||
import { HostWindowService, WidthCategory } from './host-window.service';
|
||||
enum GridBreakpoint {
|
||||
SM_MIN = 576,
|
||||
MD_MIN = 768,
|
||||
LG_MIN = 992,
|
||||
XL_MIN = 1200
|
||||
}
|
||||
|
||||
describe('HostWindowService', () => {
|
||||
let service: HostWindowService;
|
||||
|
@@ -1,20 +1,13 @@
|
||||
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
|
||||
|
||||
import { filter, distinctUntilChanged, map } from 'rxjs/operators';
|
||||
import { filter, distinctUntilChanged, map, first } from 'rxjs/operators';
|
||||
import { HostWindowState } from './host-window.reducer';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { createSelector, select, Store } from '@ngrx/store';
|
||||
|
||||
import { hasValue } from './empty.util';
|
||||
import { AppState } from '../app.reducer';
|
||||
|
||||
// TODO: ideally we should get these from sass somehow
|
||||
export enum GridBreakpoint {
|
||||
SM_MIN = 576,
|
||||
MD_MIN = 768,
|
||||
LG_MIN = 992,
|
||||
XL_MIN = 1200
|
||||
}
|
||||
import { CSSVariableService } from './sass-helper/sass-helper.service';
|
||||
|
||||
export enum WidthCategory {
|
||||
XS,
|
||||
@@ -29,10 +22,19 @@ const widthSelector = createSelector(hostWindowStateSelector, (hostWindow: HostW
|
||||
|
||||
@Injectable()
|
||||
export class HostWindowService {
|
||||
private breakPoints: { XS_MIN, SM_MIN, MD_MIN, LG_MIN, XL_MIN } = {} as any;
|
||||
|
||||
constructor(
|
||||
private store: Store<AppState>
|
||||
private store: Store<AppState>,
|
||||
private variableService: CSSVariableService
|
||||
) {
|
||||
/* See _exposed_variables.scss */
|
||||
variableService.getAllVariables().pipe(first()).subscribe((variables) => {
|
||||
this.breakPoints.XL_MIN = parseInt(variables.xlMin, 10);
|
||||
this.breakPoints.LG_MIN = parseInt(variables.lgMin, 10);
|
||||
this.breakPoints.MD_MIN = parseInt(variables.mdMin, 10);
|
||||
this.breakPoints.SM_MIN = parseInt(variables.smMin, 10);
|
||||
});
|
||||
}
|
||||
|
||||
private getWidthObs(): Observable<number> {
|
||||
@@ -45,13 +47,13 @@ export class HostWindowService {
|
||||
get widthCategory(): Observable<WidthCategory> {
|
||||
return this.getWidthObs().pipe(
|
||||
map((width: number) => {
|
||||
if (width < GridBreakpoint.SM_MIN) {
|
||||
if (width < this.breakPoints.SM_MIN) {
|
||||
return WidthCategory.XS
|
||||
} else if (width >= GridBreakpoint.SM_MIN && width < GridBreakpoint.MD_MIN) {
|
||||
} else if (width >= this.breakPoints.SM_MIN && width < this.breakPoints.MD_MIN) {
|
||||
return WidthCategory.SM
|
||||
} else if (width >= GridBreakpoint.MD_MIN && width < GridBreakpoint.LG_MIN) {
|
||||
} else if (width >= this.breakPoints.MD_MIN && width < this.breakPoints.LG_MIN) {
|
||||
return WidthCategory.MD
|
||||
} else if (width >= GridBreakpoint.LG_MIN && width < GridBreakpoint.XL_MIN) {
|
||||
} else if (width >= this.breakPoints.LG_MIN && width < this.breakPoints.XL_MIN) {
|
||||
return WidthCategory.LG
|
||||
} else {
|
||||
return WidthCategory.XL
|
||||
|
29
src/app/shared/sass-helper/sass-helper.actions.ts
Normal file
29
src/app/shared/sass-helper/sass-helper.actions.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Action } from '@ngrx/store';
|
||||
import { type } from '../../shared/ngrx/type';
|
||||
|
||||
/**
|
||||
* For each action type in an action group, make a simple
|
||||
* enum object for all of this group's action types.
|
||||
*
|
||||
* The 'type' utility function coerces strings into string
|
||||
* literal types and runs a simple check to guarantee all
|
||||
* action types in the application are unique.
|
||||
*/
|
||||
export const CSSVariableActionTypes = {
|
||||
ADD: type('dspace/css-variables/ADD'),
|
||||
};
|
||||
|
||||
export class AddCSSVariableAction implements Action {
|
||||
type = CSSVariableActionTypes.ADD;
|
||||
payload: {
|
||||
name: string,
|
||||
value: string
|
||||
};
|
||||
|
||||
constructor(name: string, value: string) {
|
||||
this.payload = {name, value};
|
||||
}
|
||||
}
|
||||
/* tslint:enable:max-classes-per-file */
|
||||
|
||||
export type CSSVariableAction = AddCSSVariableAction
|
20
src/app/shared/sass-helper/sass-helper.reducer.ts
Normal file
20
src/app/shared/sass-helper/sass-helper.reducer.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { CSSVariableAction, CSSVariableActionTypes } from './sass-helper.actions';
|
||||
|
||||
export interface CSSVariablesState {
|
||||
[name: string]: string;
|
||||
}
|
||||
|
||||
const initialState: CSSVariablesState = Object.create({});
|
||||
|
||||
export function cssVariablesReducer(state = initialState, action: CSSVariableAction): CSSVariablesState {
|
||||
switch (action.type) {
|
||||
case CSSVariableActionTypes.ADD: {
|
||||
const variable = action.payload;
|
||||
const t = Object.assign({}, state, { [variable.name]: variable.value });
|
||||
return t;
|
||||
}
|
||||
default: {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
}
|
33
src/app/shared/sass-helper/sass-helper.service.ts
Normal file
33
src/app/shared/sass-helper/sass-helper.service.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Inject, Injectable } from '@angular/core';
|
||||
import { AppState, keySelector } from '../../app.reducer';
|
||||
import { MemoizedSelector, select, Store } from '@ngrx/store';
|
||||
import { GLOBAL_CONFIG } from '../../../config';
|
||||
import { GlobalConfig } from '../../../config/global-config.interface';
|
||||
import { AddCSSVariableAction } from './sass-helper.actions';
|
||||
|
||||
@Injectable()
|
||||
export class CSSVariableService {
|
||||
constructor(
|
||||
protected store: Store<AppState>,
|
||||
@Inject(GLOBAL_CONFIG) protected EnvConfig: GlobalConfig) {
|
||||
}
|
||||
|
||||
addCSSVariable(name: string, value: string) {
|
||||
this.store.dispatch(new AddCSSVariableAction(name, value));
|
||||
}
|
||||
|
||||
getVariable(name: string) {
|
||||
return this.store.pipe(select(themeVariableByNameSelector(name)));
|
||||
}
|
||||
|
||||
getAllVariables() {
|
||||
return this.store.pipe(select(themeVariablesSelector));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const themeVariablesSelector = (state: AppState) => state.cssVariables;
|
||||
|
||||
const themeVariableByNameSelector = (name: string): MemoizedSelector<AppState, string> => {
|
||||
return keySelector<string>(name, themeVariablesSelector);
|
||||
};
|
5
src/app/typings.d.ts
vendored
5
src/app/typings.d.ts
vendored
@@ -82,3 +82,8 @@ declare module '*.json' {
|
||||
}
|
||||
|
||||
declare module 'reflect-metadata';
|
||||
|
||||
declare module '*.scss' {
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
|
9
src/styles/_exposed_variables.scss
Normal file
9
src/styles/_exposed_variables.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
@import '_variables.scss';
|
||||
|
||||
:export {
|
||||
adminSidebarWidth: $admin-sidebar-width;
|
||||
xlMin: map-get($grid-breakpoints, xl);
|
||||
mdMin: map-get($grid-breakpoints, md);
|
||||
lgMin: map-get($grid-breakpoints, lg);
|
||||
smMin: map-get($grid-breakpoints, sm);
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
const path = require('path');
|
||||
const {
|
||||
root,
|
||||
join
|
||||
@@ -37,7 +37,8 @@ module.exports = {
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
sourceMap: true
|
||||
sourceMap: true,
|
||||
modules: true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -50,7 +51,9 @@ module.exports = {
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
exclude: [/node_modules/,
|
||||
path.resolve(__dirname, '..', 'src/styles/_exposed_variables.scss')
|
||||
],
|
||||
use: [{
|
||||
loader: 'to-string-loader',
|
||||
options: {
|
||||
@@ -61,12 +64,6 @@ module.exports = {
|
||||
options: {
|
||||
sourceMap: true
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'postcss-loader',
|
||||
options: {
|
||||
sourceMap: true
|
||||
}
|
||||
},
|
||||
{
|
||||
loader: 'resolve-url-loader',
|
||||
@@ -82,6 +79,15 @@ module.exports = {
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /_exposed_variables.scss$/,
|
||||
exclude: /node_modules/,
|
||||
use: [{
|
||||
loader: "css-loader" // translates CSS into CommonJS
|
||||
}, {
|
||||
loader: "sass-loader" // compiles Sass to CSS
|
||||
}]
|
||||
},
|
||||
{
|
||||
test: /\.html$/,
|
||||
loader: 'raw-loader'
|
||||
|
Reference in New Issue
Block a user