From 91131b483a877b3ffb7b6acfdec93852c727d3a7 Mon Sep 17 00:00:00 2001 From: Tomas Trajan Date: Sun, 3 Jun 2018 13:47:14 +0800 Subject: [PATCH] feat(settings): add runtime animations toggles --- e2e/protractor.conf.js | 4 +- package-lock.json | 12 ++-- package.json | 4 +- src/app/app.component.ts | 47 ++++++++++--- .../animations/animations.service.spec.ts | 42 +++++++++++ src/app/core/animations/animations.service.ts | 25 +++++++ src/app/core/animations/router.transition.ts | 31 +++++--- src/app/core/core.module.ts | 3 +- src/app/core/index.ts | 1 + src/app/settings/settings.effects.ts | 26 +++---- src/app/settings/settings.reducer.ts | 51 +++++++++++++- .../settings/settings/settings.component.html | 70 +++++++++++++------ .../settings/settings/settings.component.scss | 15 ++++ .../settings/settings/settings.component.ts | 22 ++++-- src/app/shared/shared.module.ts | 5 +- 15 files changed, 285 insertions(+), 73 deletions(-) mode change 100644 => 100755 e2e/protractor.conf.js create mode 100755 src/app/core/animations/animations.service.spec.ts create mode 100755 src/app/core/animations/animations.service.ts mode change 100644 => 100755 src/app/core/index.ts mode change 100644 => 100755 src/app/settings/settings.effects.ts mode change 100644 => 100755 src/app/settings/settings.reducer.ts mode change 100644 => 100755 src/app/settings/settings/settings.component.html mode change 100644 => 100755 src/app/settings/settings/settings.component.scss mode change 100644 => 100755 src/app/settings/settings/settings.component.ts mode change 100644 => 100755 src/app/shared/shared.module.ts diff --git a/e2e/protractor.conf.js b/e2e/protractor.conf.js old mode 100644 new mode 100755 index 52740aef8..acfb3b50a --- a/e2e/protractor.conf.js +++ b/e2e/protractor.conf.js @@ -4,7 +4,7 @@ const { SpecReporter } = require('jasmine-spec-reporter'); exports.config = { - allScriptsTimeout: 11000, + allScriptsTimeout: 30000, specs: [ './src/**/*.e2e-spec.ts' ], @@ -16,7 +16,7 @@ exports.config = { framework: 'jasmine', jasmineNodeOpts: { showColors: true, - defaultTimeoutInterval: 30000, + defaultTimeoutInterval: 60000, print: function () { } }, diff --git a/package-lock.json b/package-lock.json index 2e6de4f2a..72fe7282d 100755 --- a/package-lock.json +++ b/package-lock.json @@ -476,9 +476,9 @@ } }, "@angular/cdk": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-6.0.0.tgz", - "integrity": "sha512-GVWUwmKWJPkK4gJTi0tgaLDs5QlRvkozIs6KnrsozkPUNDIsZyQCyEUB+llHiUB9AeDGcCDbpQyGIDLdya5khQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-6.2.0.tgz", + "integrity": "sha512-K5GMMxsIJOETwX9lE9rDz/Fg7EiacZnJP3/nN1cElZ9fmf1eKna/gxICMpKL3uy/VDik8DvH98J8DUKgj6CoNA==", "requires": { "tslib": "^1.7.1" } @@ -596,9 +596,9 @@ "dev": true }, "@angular/material": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-6.0.0.tgz", - "integrity": "sha512-g6P6yg2Gmn+LJKO+KxRnH5FL1D/dApwb/vloA9UTEzddhHoUB5JdRhlE78yQTwmtrP/cCJJih6ssZkWKRAqEbg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-6.2.0.tgz", + "integrity": "sha512-4vC2Ycrf1ORP6iGIy9qExVpbjqHJ/ObEQd72pVDQyK127O8cxhGhEiX8QvxdeAGREVxqwfhUGEK/vjH3o110fQ==", "requires": { "tslib": "^1.7.1" } diff --git a/package.json b/package.json index 0faaa2617..f104f8b06 100755 --- a/package.json +++ b/package.json @@ -23,13 +23,13 @@ "private": true, "dependencies": { "@angular/animations": "6.0.0", - "@angular/cdk": "^6.0.0", + "@angular/cdk": "^6.2.0", "@angular/common": "^6.0.0", "@angular/compiler": "^6.0.0", "@angular/core": "^6.0.0", "@angular/forms": "6.0.0", "@angular/http": "6.0.0", - "@angular/material": "^6.0.0", + "@angular/material": "^6.2.0", "@angular/platform-browser": "^6.0.0", "@angular/platform-browser-dynamic": "6.0.0", "@angular/router": "6.0.0", diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 818859507..adac19866 100755 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,3 +1,4 @@ +import browser from 'browser-detect'; import { Title } from '@angular/platform-browser'; import { OverlayContainer } from '@angular/cdk/overlay'; import { Component, HostBinding, OnDestroy, OnInit } from '@angular/core'; @@ -9,12 +10,18 @@ import { takeUntil, filter } from 'rxjs/operators'; import { ActionAuthLogin, ActionAuthLogout, + AnimationsService, selectorAuth, routerTransition } from '@app/core'; import { environment as env } from '@env/environment'; -import { NIGHT_MODE_THEME, selectorSettings, SettingsState } from './settings'; +import { + NIGHT_MODE_THEME, + selectorSettings, + SettingsState, + ActionSettingsChangeAnimationsPageDisabled +} from './settings'; @Component({ selector: 'anms-root', @@ -47,9 +54,19 @@ export class AppComponent implements OnInit, OnDestroy { public overlayContainer: OverlayContainer, private store: Store, private router: Router, - private titleService: Title + private titleService: Title, + private animationService: AnimationsService ) {} + private static trackPageView(event: NavigationEnd) { + (window).ga('set', 'page', event.urlAfterRedirects); + (window).ga('send', 'pageview'); + } + + private static isIEorEdge() { + return ['ie', 'edge'].includes(browser().name); + } + ngOnInit(): void { this.subscribeToSettings(); this.subscribeToIsAuthenticated(); @@ -77,18 +94,31 @@ export class AppComponent implements OnInit, OnDestroy { } private subscribeToSettings() { + if (AppComponent.isIEorEdge()) { + this.store.dispatch( + new ActionSettingsChangeAnimationsPageDisabled({ + pageAnimationsDisabled: true + }) + ); + } this.store .select(selectorSettings) .pipe(takeUntil(this.unsubscribe$)) - .subscribe(settings => this.setTheme(settings)); + .subscribe(settings => { + this.setTheme(settings); + this.animationService.updateRouteAnimationType( + settings.pageAnimations, + settings.elementsAnimations + ); + }); } private setTheme(settings: SettingsState) { const { theme, autoNightMode } = settings; const hours = new Date().getHours(); const effectiveTheme = (autoNightMode && (hours >= 20 || hours <= 6) - ? NIGHT_MODE_THEME - : theme + ? NIGHT_MODE_THEME + : theme ).toLowerCase(); this.componentCssClass = effectiveTheme; const classList = this.overlayContainer.getContainerElement().classList; @@ -116,7 +146,7 @@ export class AppComponent implements OnInit, OnDestroy { } if (event instanceof NavigationEnd) { - this.trackPageView(event); + AppComponent.trackPageView(event); } }); } @@ -131,9 +161,4 @@ export class AppComponent implements OnInit, OnDestroy { title ? `${title} - ${env.appName}` : env.appName ); } - - private trackPageView(event: NavigationEnd) { - (window).ga('set', 'page', event.urlAfterRedirects); - (window).ga('send', 'pageview'); - } } diff --git a/src/app/core/animations/animations.service.spec.ts b/src/app/core/animations/animations.service.spec.ts new file mode 100755 index 000000000..5938680fd --- /dev/null +++ b/src/app/core/animations/animations.service.spec.ts @@ -0,0 +1,42 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { AnimationsService } from './animations.service'; + +describe('AnimationsService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [AnimationsService] + }); + }); + + it( + 'should set route animation type to "NONE" by default', + inject([AnimationsService], (service: AnimationsService) => { + expect(AnimationsService.isRouteAnimationType('NONE')).toBe(true); + }) + ); + + it( + 'should set route animation type to "ALL"', + inject([AnimationsService], (service: AnimationsService) => { + service.updateRouteAnimationType(true, true); + expect(AnimationsService.isRouteAnimationType('ALL')).toBe(true); + }) + ); + + it( + 'should set route animation type to "PAGE"', + inject([AnimationsService], (service: AnimationsService) => { + service.updateRouteAnimationType(true, false); + expect(AnimationsService.isRouteAnimationType('PAGE')).toBe(true); + }) + ); + + it( + 'should set route animation type to "ELEMENTS"', + inject([AnimationsService], (service: AnimationsService) => { + service.updateRouteAnimationType(false, true); + expect(AnimationsService.isRouteAnimationType('ELEMENTS')).toBe(true); + }) + ); +}); diff --git a/src/app/core/animations/animations.service.ts b/src/app/core/animations/animations.service.ts new file mode 100755 index 000000000..1f9668a3e --- /dev/null +++ b/src/app/core/animations/animations.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@angular/core'; + +@Injectable() +export class AnimationsService { + constructor() { + } + + private static routeAnimationType: RouteAnimationType = 'NONE'; + + static isRouteAnimationType(type: RouteAnimationType) { + return AnimationsService.routeAnimationType === type; + } + + updateRouteAnimationType( + pageAnimations: boolean, + elementsAnimations: boolean + ) { + AnimationsService.routeAnimationType = + pageAnimations && elementsAnimations + ? 'ALL' + : pageAnimations ? 'PAGE' : elementsAnimations ? 'ELEMENTS' : 'NONE'; + } +} + +export type RouteAnimationType = 'ALL' | 'PAGE' | 'ELEMENTS' | 'NONE'; diff --git a/src/app/core/animations/router.transition.ts b/src/app/core/animations/router.transition.ts index 580e5142a..18bf6ccee 100755 --- a/src/app/core/animations/router.transition.ts +++ b/src/app/core/animations/router.transition.ts @@ -1,4 +1,3 @@ -import browser from 'browser-detect'; import { animate, query, @@ -8,10 +7,11 @@ import { stagger, sequence } from '@angular/animations'; +import { AnimationsService } from './animations.service'; export const ANIMATE_ON_ROUTE_ENTER = 'route-enter-staggered'; -const ROUTE_TRANSITION: any[] = [ +const STEPS_ALL: any[] = [ query(':enter > *', style({ opacity: 0, position: 'fixed' }), { optional: true }), @@ -59,18 +59,29 @@ const ROUTE_TRANSITION: any[] = [ { optional: true } ) ]; - -export const ROUTE_TRANSITION_IE = [ROUTE_TRANSITION[1], ROUTE_TRANSITION[3]]; +const STEPS_NONE = []; +const STEPS_PAGE = [STEPS_ALL[0], STEPS_ALL[2]]; +const STEPS_ELEMENTS = [STEPS_ALL[1], STEPS_ALL[3]]; export const routerTransition = trigger('routerTransition', [ - transition(isNotIEorEdge, ROUTE_TRANSITION), - transition(isIEorEdge, ROUTE_TRANSITION_IE) + transition(isRouteAnimationAll, STEPS_ALL), + transition(isRouteAnimationNone, STEPS_NONE), + transition(isRouteAnimationPage, STEPS_PAGE), + transition(isRouteAnimationElements, STEPS_ELEMENTS) ]); -export function isNotIEorEdge() { - return !isIEorEdge(); +export function isRouteAnimationAll() { + return AnimationsService.isRouteAnimationType('ALL'); +} + +export function isRouteAnimationNone() { + return AnimationsService.isRouteAnimationType('NONE'); +} + +export function isRouteAnimationPage() { + return AnimationsService.isRouteAnimationType('PAGE'); } -export function isIEorEdge() { - return ['ie', 'edge'].includes(browser().name); +export function isRouteAnimationElements() { + return AnimationsService.isRouteAnimationType('ELEMENTS'); } diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index 2c85f53b0..4710b9474 100755 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -13,6 +13,7 @@ import { LocalStorageService } from './local-storage/local-storage.service'; import { authReducer } from './auth/auth.reducer'; import { AuthEffects } from './auth/auth.effects'; import { AuthGuardService } from './auth/auth-guard.service'; +import { AnimationsService } from './animations/animations.service'; export const metaReducers: MetaReducer[] = [initStateFromLocalStorage]; @@ -36,7 +37,7 @@ if (!environment.production) { EffectsModule.forRoot([AuthEffects]) ], declarations: [], - providers: [LocalStorageService, AuthGuardService] + providers: [LocalStorageService, AuthGuardService, AnimationsService] }) export class CoreModule { constructor( diff --git a/src/app/core/index.ts b/src/app/core/index.ts old mode 100644 new mode 100755 index 57a5392c9..4938f0fc7 --- a/src/app/core/index.ts +++ b/src/app/core/index.ts @@ -1,5 +1,6 @@ export * from './local-storage/local-storage.service'; export * from './animations/router.transition'; +export * from './animations/animations.service'; export * from './auth/auth.reducer'; export * from './auth/auth-guard.service'; export * from './core.module'; diff --git a/src/app/settings/settings.effects.ts b/src/app/settings/settings.effects.ts old mode 100644 new mode 100755 index ac897816f..4fa07e8f6 --- a/src/app/settings/settings.effects.ts +++ b/src/app/settings/settings.effects.ts @@ -4,7 +4,7 @@ import { Actions, Effect } from '@ngrx/effects'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; -import { LocalStorageService } from '@app/core'; +import { LocalStorageService, AnimationsService } from '@app/core'; import { SETTINGS_KEY, @@ -16,20 +16,22 @@ import { export class SettingsEffects { constructor( private actions$: Actions, - private localStorageService: LocalStorageService + private localStorageService: LocalStorageService, + private animationsService: AnimationsService ) {} @Effect({ dispatch: false }) persistSettings(): Observable { - return this.actions$ - .ofType(SettingsActionTypes.PERSIST) - .pipe( - tap((action: ActionSettingsPersist) => - this.localStorageService.setItem( - SETTINGS_KEY, - action.payload.settings - ) - ) - ); + return this.actions$.ofType(SettingsActionTypes.PERSIST).pipe( + tap((action: ActionSettingsPersist) => { + const { settings } = action.payload; + const { pageAnimations, elementsAnimations } = settings; + this.localStorageService.setItem(SETTINGS_KEY, settings); + this.animationsService.updateRouteAnimationType( + pageAnimations, + elementsAnimations + ); + }) + ); } } diff --git a/src/app/settings/settings.reducer.ts b/src/app/settings/settings.reducer.ts old mode 100644 new mode 100755 index 987490f67..d40e94fd3 --- a/src/app/settings/settings.reducer.ts +++ b/src/app/settings/settings.reducer.ts @@ -5,6 +5,9 @@ export const SETTINGS_KEY = 'SETTINGS'; export enum SettingsActionTypes { CHANGE_THEME = '[Settings] Change Theme', CHANGE_AUTO_NIGHT_AUTO_MODE = '[Settings] Change Auto Night Mode', + CHANGE_ANIMATIONS_PAGE = '[Settings] Change Animations Page', + CHANGE_ANIMATIONS_PAGE_DISABLED = '[Settings] Change Animations Page Disabled', + CHANGE_ANIMATIONS_ELEMENTS = '[Settings] Change Animations Elements', PERSIST = '[Settings] Persist' } @@ -18,6 +21,27 @@ export class ActionSettingsChangeAutoNightMode implements Action { constructor(public payload: { autoNightMode: boolean }) {} } +export class ActionSettingsChangeAnimationsPage implements Action { + readonly type = SettingsActionTypes.CHANGE_ANIMATIONS_PAGE; + + constructor(public payload: { pageAnimations: boolean }) { + } +} + +export class ActionSettingsChangeAnimationsPageDisabled implements Action { + readonly type = SettingsActionTypes.CHANGE_ANIMATIONS_PAGE_DISABLED; + + constructor(public payload: { pageAnimationsDisabled: boolean }) { + } +} + +export class ActionSettingsChangeAnimationsElements implements Action { + readonly type = SettingsActionTypes.CHANGE_ANIMATIONS_ELEMENTS; + + constructor(public payload: { elementsAnimations: boolean }) { + } +} + export class ActionSettingsPersist implements Action { readonly type = SettingsActionTypes.PERSIST; constructor(public payload: { settings: SettingsState }) {} @@ -26,13 +50,19 @@ export class ActionSettingsPersist implements Action { export type SettingsActions = | ActionSettingsPersist | ActionSettingsChangeTheme + | ActionSettingsChangeAnimationsPage + | ActionSettingsChangeAnimationsPageDisabled + | ActionSettingsChangeAnimationsElements | ActionSettingsChangeAutoNightMode; export const NIGHT_MODE_THEME = 'BLACK-THEME'; export const initialState: SettingsState = { theme: 'DEFAULT-THEME', - autoNightMode: false + autoNightMode: false, + pageAnimations: true, + pageAnimationsDisabled: false, + elementsAnimations: true }; export const selectorSettings = state => @@ -49,6 +79,22 @@ export function settingsReducer( case SettingsActionTypes.CHANGE_AUTO_NIGHT_AUTO_MODE: return { ...state, autoNightMode: action.payload.autoNightMode }; + case SettingsActionTypes.CHANGE_ANIMATIONS_PAGE: + return { ...state, pageAnimations: action.payload.pageAnimations }; + + case SettingsActionTypes.CHANGE_ANIMATIONS_ELEMENTS: + return { + ...state, + elementsAnimations: action.payload.elementsAnimations + }; + + case SettingsActionTypes.CHANGE_ANIMATIONS_PAGE_DISABLED: + return { + ...state, + pageAnimations: false, + pageAnimationsDisabled: action.payload.pageAnimationsDisabled + }; + default: return state; } @@ -57,4 +103,7 @@ export function settingsReducer( export interface SettingsState { theme: string; autoNightMode: boolean; + pageAnimations: boolean; + pageAnimationsDisabled: boolean; + elementsAnimations: boolean; } diff --git a/src/app/settings/settings/settings.component.html b/src/app/settings/settings/settings.component.html old mode 100644 new mode 100755 index f3fdf2d80..8dbc7b02e --- a/src/app/settings/settings/settings.component.html +++ b/src/app/settings/settings/settings.component.html @@ -4,30 +4,54 @@
-
- color_lens - - - - {{t.label}} - - - +
+

Themes

+
+ color_lens + + + + {{t.label}} + + + +
+
+ lightbulb_outline + Auto night mode (from 21:00 to 7:00) + + +
-
- lightbulb_outline - - - Off - On - - +
+

Animations

+
+ panorama + Navigation whole page transition + + + + +
+
+ dashboard + Navigation page elements slide up + + +
diff --git a/src/app/settings/settings/settings.component.scss b/src/app/settings/settings/settings.component.scss old mode 100644 new mode 100755 index d0f7bb300..a4a0b90b9 --- a/src/app/settings/settings/settings.component.scss +++ b/src/app/settings/settings/settings.component.scss @@ -7,9 +7,24 @@ h1 { text-transform: uppercase; } +h2 { + margin: 0 0 10px 0; + text-transform: uppercase; +} + +.group { + margin: 0 0 40px 0; +} + .icon-form-field { + position: relative; display: flex; + height: 65.5px; align-items: center; + + mat-placeholder { + flex: 2 1 auto; + } } mat-icon { diff --git a/src/app/settings/settings/settings.component.ts b/src/app/settings/settings/settings.component.ts old mode 100644 new mode 100755 index 6da5622df..1e79eea6a --- a/src/app/settings/settings/settings.component.ts +++ b/src/app/settings/settings/settings.component.ts @@ -7,6 +7,8 @@ import { selectorSettings, ActionSettingsChangeTheme, ActionSettingsChangeAutoNightMode, + ActionSettingsChangeAnimationsPage, + ActionSettingsChangeAnimationsElements, SettingsState, ActionSettingsPersist } from '../settings.reducer'; @@ -46,11 +48,23 @@ export class SettingsComponent implements OnInit, OnDestroy { this.store.dispatch(new ActionSettingsPersist({ settings: this.settings })); } - onAutoNightModeSelect({ value: autoNightMode }) { + onAutoNightModeToggle({ checked: autoNightMode }) { this.store.dispatch( - new ActionSettingsChangeAutoNightMode({ - autoNightMode: autoNightMode === 'true' - }) + new ActionSettingsChangeAutoNightMode({ autoNightMode }) + ); + this.store.dispatch(new ActionSettingsPersist({ settings: this.settings })); + } + + onPageAnimationsToggle({ checked: pageAnimations }) { + this.store.dispatch( + new ActionSettingsChangeAnimationsPage({ pageAnimations }) + ); + this.store.dispatch(new ActionSettingsPersist({ settings: this.settings })); + } + + onElementsAnimationsToggle({ checked: elementsAnimations }) { + this.store.dispatch( + new ActionSettingsChangeAnimationsElements({ elementsAnimations }) ); this.store.dispatch(new ActionSettingsPersist({ settings: this.settings })); } diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts old mode 100644 new mode 100755 index ab63e8100..569fd42df --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -17,6 +17,7 @@ import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; import { BigInputComponent } from './big-input/big-input.component'; import { BigInputActionComponent } from './big-input/big-input-action.component'; @@ -40,7 +41,8 @@ import { BigInputActionComponent } from './big-input/big-input-action.component' MatMenuModule, MatIconModule, MatTooltipModule, - MatSnackBarModule + MatSnackBarModule, + MatSlideToggleModule ], declarations: [BigInputComponent, BigInputActionComponent], exports: [ @@ -62,6 +64,7 @@ import { BigInputActionComponent } from './big-input/big-input-action.component' MatIconModule, MatTooltipModule, MatSnackBarModule, + MatSlideToggleModule, BigInputComponent, BigInputActionComponent