57053: progress menu bar

This commit is contained in:
lotte
2018-11-16 17:00:06 +01:00
parent 0c4784346b
commit cd5c7b72c2
22 changed files with 386 additions and 127 deletions

View File

@@ -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 */

View 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>

View File

@@ -1,21 +1,36 @@
@import '../../../styles/variables.scss';
:host {
position: fixed;
position: sticky;
left: 0;
top: 0;
height: 100vh;
flex: 1 1 auto;
nav {
height: 100vh;
height: 100%;
flex-direction: column;
>div.sidebar-top-level-items {
flex: 1;
overflow: auto;
> 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,13 +43,15 @@
.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;
}
}
}

View File

@@ -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());
}
}

View File

@@ -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 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 AdminSidebarSectionActionTypes.COLLAPSE: {
return Object.assign({}, state, {
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: {

View File

@@ -8,6 +8,9 @@
.dspace-logo-container {
margin: 10px 20px 0px 20px;
.display-3 {
word-break: break-word;
}
}
.dspace-logo-container img {

View File

@@ -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';

View File

@@ -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() {

View File

@@ -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;
}
});
}

View File

@@ -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,

View File

@@ -17,7 +17,7 @@ export class NavbarComponent {
constructor(
private store: Store<AppState>,
protected windowService: HostWindowService
public windowService: HostWindowService
) {
}

View File

@@ -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')])
]);

View File

@@ -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')),
]);

View File

@@ -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;

View File

@@ -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

View 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

View 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;
}
}
}

View 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);
};

View File

@@ -82,3 +82,8 @@ declare module '*.json' {
}
declare module 'reflect-metadata';
declare module '*.scss' {
const content: any;
export default content;
}

View 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);
}

View File

@@ -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: {
@@ -62,12 +65,6 @@ module.exports = {
sourceMap: true
}
},
{
loader: 'postcss-loader',
options: {
sourceMap: true
}
},
{
loader: 'resolve-url-loader',
options: {
@@ -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'