diff --git a/config/environment.default.js b/config/environment.default.js index 804d80b0f2..1d1025b43f 100644 --- a/config/environment.default.js +++ b/config/environment.default.js @@ -155,5 +155,13 @@ module.exports = { edit: { undoTimeout: 10000 // 10 seconds } - } + }, + themes: [ + { + name: 'Preview Release', + cssClass: 'preview-release' + } + ] + + }; diff --git a/src/app/+home-page/home-page.component.default.scss b/src/app/+home-page/home-page.component.default.scss index db6fcb4886..a7ab662130 100644 --- a/src/app/+home-page/home-page.component.default.scss +++ b/src/app/+home-page/home-page.component.default.scss @@ -1,3 +1,3 @@ :host { //color: red; -} \ No newline at end of file +} diff --git a/src/app/+home-page/themes/home-page.component.preview-release.scss b/src/app/+home-page/themes/home-page.component.preview-release.scss new file mode 100644 index 0000000000..4cca79d1b3 --- /dev/null +++ b/src/app/+home-page/themes/home-page.component.preview-release.scss @@ -0,0 +1,3 @@ +:host-context(.preview-release) { + color: green; +} \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index c1de58819e..65f59f49a8 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -32,7 +32,9 @@ import { Observable } from 'rxjs/internal/Observable'; import { slideSidebarPadding } from './shared/animations/slide'; import { combineLatest as combineLatestObservable, of } from 'rxjs'; import { HostWindowService } from './shared/host-window.service'; +import { ThemeService } from './core/theme/theme.service'; import { Theme } from '../config/theme.inferface'; +import { isNotEmpty } from './shared/empty.util'; @Component({ selector: 'ds-app', @@ -63,6 +65,7 @@ export class AppComponent implements OnInit, AfterViewInit { private cssService: CSSVariableService, private menuService: MenuService, private windowService: HostWindowService, + private themeService: ThemeService ) { // Load all the languages that are defined as active from the config file @@ -89,6 +92,11 @@ export class AppComponent implements OnInit, AfterViewInit { } ngOnInit() { + const availableThemes: Theme[] = this.config.themes; + if (isNotEmpty(availableThemes)) { + this.themeService.setCurrentTheme(availableThemes[0]); + } + this.theme = this.themeService.getCurrentTheme(); const env: string = this.config.production ? 'Production' : 'Development'; const color: string = this.config.production ? 'red' : 'green'; diff --git a/src/app/core/core.effects.ts b/src/app/core/core.effects.ts index 6f4eed018b..94669cac31 100644 --- a/src/app/core/core.effects.ts +++ b/src/app/core/core.effects.ts @@ -5,6 +5,7 @@ import { AuthEffects } from './auth/auth.effects'; import { JsonPatchOperationsEffects } from './json-patch/json-patch-operations.effects'; import { ServerSyncBufferEffects } from './cache/server-sync-buffer.effects'; import { ObjectUpdatesEffects } from './data/object-updates/object-updates.effects'; +import { ThemeEffects } from './theme/theme.effects'; export const coreEffects = [ RequestEffects, @@ -14,4 +15,5 @@ export const coreEffects = [ JsonPatchOperationsEffects, ServerSyncBufferEffects, ObjectUpdatesEffects, + ThemeEffects ]; diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 6550435aa3..6f8614b0ae 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -87,6 +87,7 @@ import { MyDSpaceResponseParsingService } from './data/mydspace-response-parsing import { ClaimedTaskDataService } from './tasks/claimed-task-data.service'; import { PoolTaskDataService } from './tasks/pool-task-data.service'; import { TaskResponseParsingService } from './tasks/task-response-parsing.service'; +import { ThemeService } from './theme/theme.service'; const IMPORTS = [ CommonModule, @@ -175,6 +176,7 @@ const PROVIDERS = [ TaskResponseParsingService, ClaimedTaskDataService, PoolTaskDataService, + ThemeService, // register AuthInterceptor as HttpInterceptor { provide: HTTP_INTERCEPTORS, diff --git a/src/app/core/core.reducers.ts b/src/app/core/core.reducers.ts index c93b4bf44b..3ab3564844 100644 --- a/src/app/core/core.reducers.ts +++ b/src/app/core/core.reducers.ts @@ -13,6 +13,7 @@ import { objectUpdatesReducer, ObjectUpdatesState } from './data/object-updates/object-updates.reducer'; +import { themeReducer, ThemeState } from './theme/theme.reducer'; export interface CoreState { 'cache/object': ObjectCacheState, @@ -21,7 +22,8 @@ export interface CoreState { 'data/request': RequestState, 'index': MetaIndexState, 'auth': AuthState, - 'json/patch': JsonPatchOperationsState + 'json/patch': JsonPatchOperationsState, + 'theme': ThemeState } export const coreReducers: ActionReducerMap = { @@ -31,5 +33,6 @@ export const coreReducers: ActionReducerMap = { 'data/request': requestReducer, 'index': indexReducer, 'auth': authReducer, - 'json/patch': jsonPatchOperationsReducer + 'json/patch': jsonPatchOperationsReducer, + 'theme': themeReducer }; diff --git a/src/app/core/theme/theme.actions.ts b/src/app/core/theme/theme.actions.ts new file mode 100644 index 0000000000..550a53c91d --- /dev/null +++ b/src/app/core/theme/theme.actions.ts @@ -0,0 +1,37 @@ +/** + * The list of ObjectUpdatesAction type definitions + */ +import { type } from '../../shared/ngrx/type'; +import { Action } from '@ngrx/store'; +import { Theme } from '../../../config/theme.inferface'; + +export const ThemeActionTypes = { + SET: type('dspace/core/theme/SET'), +}; + +/* tslint:disable:max-classes-per-file */ + +/** + * An ngrx action to set a the repository's current theme + */ +export class SetThemeAction implements Action { + type = ThemeActionTypes.SET; + payload: { + theme: Theme + }; + + /** + * Create a new SetThemeAction + * + * @param theme + * the theme configuration to change the current theme to + */ + constructor( + theme: Theme + ) { + this.payload = { theme }; + } +} + +export type ThemeAction + = SetThemeAction; diff --git a/src/app/core/theme/theme.effects.ts b/src/app/core/theme/theme.effects.ts new file mode 100644 index 0000000000..f03b356b78 --- /dev/null +++ b/src/app/core/theme/theme.effects.ts @@ -0,0 +1,25 @@ +import { Inject, Injectable } from '@angular/core'; +import { Action, Store } from '@ngrx/store'; +import { Actions, Effect } from '@ngrx/effects'; +import { CoreState } from '../core.reducers'; +import { SetThemeAction } from './theme.actions'; +import { GLOBAL_CONFIG, GlobalConfig } from '../../../config'; +import { Theme } from '../../../config/theme.inferface'; +import { isNotEmpty } from '../../shared/empty.util'; +import { asyncScheduler, defer, Observable, of as observableOf } from 'rxjs'; + +@Injectable() +export class ThemeEffects { + + // @Effect() setInitialTheme: Observable = defer(() => { + // console.log('set theme'); + // const availableThemes: Theme[] = this.config.themes; + // if (isNotEmpty(availableThemes)) { + // return observableOf(new SetThemeAction(availableThemes[0]), asyncScheduler); + // } + // }); + // + // constructor(private actions: Actions, private store: Store, @Inject(GLOBAL_CONFIG) public config: GlobalConfig) { + // console.log("theme effects"); + // } +} diff --git a/src/app/core/theme/theme.reducer.ts b/src/app/core/theme/theme.reducer.ts new file mode 100644 index 0000000000..fb3e0eb7b8 --- /dev/null +++ b/src/app/core/theme/theme.reducer.ts @@ -0,0 +1,19 @@ +import { Theme } from '../../../config/theme.inferface'; +import { ThemeAction, ThemeActionTypes } from './theme.actions'; + +export interface ThemeState { + theme: Theme +} + +// Object.create(null) ensures the object has no default js properties (e.g. `__proto__`) +const initialState = Object.create(null); + +export function themeReducer(state = initialState, action: ThemeAction): ThemeState { + switch (action.type) { + case ThemeActionTypes.SET: { + const newState = Object.assign({}, state, { theme: action.payload.theme }); + console.log(newState); + return newState; + } + } +} diff --git a/src/app/core/theme/theme.service.ts b/src/app/core/theme/theme.service.ts new file mode 100644 index 0000000000..998fe6b856 --- /dev/null +++ b/src/app/core/theme/theme.service.ts @@ -0,0 +1,33 @@ +import { createSelector, MemoizedSelector, select, Store } from '@ngrx/store'; +import { coreSelector, CoreState } from '../core.reducers'; +import { Inject, Injectable } from '@angular/core'; +import { Theme } from '../../../config/theme.inferface'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { ThemeState } from './theme.reducer'; +import { SetThemeAction } from './theme.actions'; + +function themeStateSelector(): MemoizedSelector { + return createSelector(coreSelector, (state: CoreState) => state['theme']); +} + +/** + * Service that dispatches to and reads from the Theme state in the store + */ +@Injectable() +export class ThemeService { + constructor(private store: Store) { + + } + + public getCurrentTheme(): Observable { + return this.store.pipe( + select(themeStateSelector()), + map((state: ThemeState) => state.theme) + ); + } + + public setCurrentTheme(theme: Theme): void { + return this.store.dispatch(new SetThemeAction(theme)); + } +} \ No newline at end of file