From 6926e17cfb45c2fbaf65d91d048e4607b8700013 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-=C3=89ric?= Date: Mon, 20 Feb 2017 11:51:56 -0500 Subject: [PATCH] =?UTF-8?q?feat(Request=20service=20and=20spinner):=20Requ?= =?UTF-8?q?est=20service=20that=20tracks=20the=20on=E2=80=A6=20(#119)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(Request service and spinner): Request service that tracks the ongoing requests and a spinner component that displays itself when more than 0 requests are ongoing --- src/app/app.component.html | 1 + src/app/app.component.spec.ts | 8 +++-- src/app/app.component.styl | 10 ++++++ src/app/core/core.module.ts | 19 +++++++---- src/app/core/request.service.spec.ts | 16 +++++++++ src/app/core/request.service.ts | 25 ++++++++++++++ src/app/core/search.service.spec.ts | 4 ++- src/app/core/search.service.ts | 8 +++-- src/app/core/spinner/spinner.component.html | 4 +++ .../core/spinner/spinner.component.spec.ts | 32 ++++++++++++++++++ src/app/core/spinner/spinner.component.styl | 33 +++++++++++++++++++ src/app/core/spinner/spinner.component.ts | 22 +++++++++++++ src/app/core/tool.service.ts | 9 +++-- src/app/map/map/map.component.styl | 22 +++++++++++++ src/app/map/zoom/zoom.component.styl | 11 ------- .../navigator/navigator.component.spec.ts | 4 ++- .../pages/navigator/navigator.component.styl | 12 ++++++- .../search-bar/search-bar.component.spec.ts | 4 ++- src/css/styles.styl | 15 ++------- src/css/theme.styl | 14 +++----- 20 files changed, 222 insertions(+), 51 deletions(-) create mode 100644 src/app/core/request.service.spec.ts create mode 100644 src/app/core/request.service.ts create mode 100644 src/app/core/spinner/spinner.component.html create mode 100644 src/app/core/spinner/spinner.component.spec.ts create mode 100644 src/app/core/spinner/spinner.component.styl create mode 100644 src/app/core/spinner/spinner.component.ts diff --git a/src/app/app.component.html b/src/app/app.component.html index b3cb23596..83b1b9a63 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,3 +1,4 @@
+
diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts index e2dbefdef..d857cf28f 100644 --- a/src/app/app.component.spec.ts +++ b/src/app/app.component.spec.ts @@ -4,8 +4,10 @@ import { LanguageService } from './core/language/language.service'; import { TestModule } from './test.module'; import { AppComponent } from './app.component'; +import { SpinnerComponent } from './core/spinner/spinner.component'; import { NavigatorModule, NavigatorRoutingModule } from './pages'; import { MediaService } from './core/media.service'; +import { RequestService } from './core/request.service'; import { provideAppStore } from './core/core.module'; import {} from 'jasmine'; @@ -20,12 +22,14 @@ describe('AppComponent', () => { NavigatorRoutingModule ], declarations: [ - AppComponent + AppComponent, + SpinnerComponent ], providers: [ LanguageService, provideAppStore(), - MediaService + MediaService, + RequestService ] }); TestBed.compileComponents(); diff --git a/src/app/app.component.styl b/src/app/app.component.styl index af45b752c..ffef49d62 100644 --- a/src/app/app.component.styl +++ b/src/app/app.component.styl @@ -1,3 +1,5 @@ +@require '../css/theme.styl'; + :host, main { width: 100%; height: 100%; @@ -8,3 +10,11 @@ main { padding: 0; transition: 0.5s; } + +/*--- Spinner ---*/ +igo-spinner { + position: absolute; + top: $igo-margin; + right: $igo-margin; + z-index: 5; +} diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index e39ee6888..b46277b9e 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -1,6 +1,7 @@ import { NgModule, Optional, SkipSelf, ModuleWithProviders } from '@angular/core'; import { Http, Jsonp } from '@angular/http'; import { CommonModule } from '@angular/common'; +import { MaterialModule } from '@angular/material'; import { MissingTranslationHandler } from 'ng2-translate'; @@ -15,8 +16,8 @@ import { browserMedia, mapView, mapLayers, selectedTool, focusedResult } from '../reducers'; import { MediaService } from './media.service'; +import { RequestService } from './request.service'; import { MapService } from './map.service'; - import { ToolService } from './tool.service'; import { SearchService } from './search.service'; import { SearchSourceService } from './search-source.service'; @@ -24,6 +25,8 @@ import { SearchSource } from '../search/sources/search-source'; import { SearchSourceNominatim } from '../search/sources/search-source-nominatim'; import { SearchSourceMSP } from '../search/sources/search-source-msp'; +import { SpinnerComponent } from './spinner/spinner.component'; + export function searchSourceServiceFactory(sources: SearchSource[]) { return new SearchSourceService(sources); } @@ -77,15 +80,18 @@ export function provideAppStore() { }); } - @NgModule({ imports: [ - CommonModule + CommonModule, + MaterialModule ], - exports: [], - declarations: [] + exports: [ + SpinnerComponent + ], + declarations: [ + SpinnerComponent + ] }) - export class CoreModule { static forRoot(): ModuleWithProviders { return { @@ -94,6 +100,7 @@ export class CoreModule { LanguageService, { provide: MissingTranslationHandler, useClass: IgoMissingTranslationHandler }, MediaService, + RequestService, provideAppStore(), provideSearchSourceService(), MapService, diff --git a/src/app/core/request.service.spec.ts b/src/app/core/request.service.spec.ts new file mode 100644 index 000000000..4e3de278d --- /dev/null +++ b/src/app/core/request.service.spec.ts @@ -0,0 +1,16 @@ +/* tslint:disable:no-unused-variable */ + +import { TestBed, async, inject } from '@angular/core/testing'; +import { RequestService } from './request.service'; + +describe('RequestService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [RequestService] + }); + }); + + it('should ...', inject([RequestService], (service: RequestService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/core/request.service.ts b/src/app/core/request.service.ts new file mode 100644 index 000000000..a5fad1bc0 --- /dev/null +++ b/src/app/core/request.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { Subject } from 'rxjs/Subject'; + +@Injectable() +export class RequestService { + + private count = 0; + requests = new Subject(); + + constructor() { } + + register(request: Observable) { + this.count += 1; + this.requests.next(this.count); + + return request.finally(this.unregister.call(this)); + } + + private unregister() { + this.count -= 1; + this.requests.next(this.count); + } + +} diff --git a/src/app/core/search.service.spec.ts b/src/app/core/search.service.spec.ts index 38b1b6a83..abc57e94c 100644 --- a/src/app/core/search.service.spec.ts +++ b/src/app/core/search.service.spec.ts @@ -1,6 +1,7 @@ import { TestBed, inject } from '@angular/core/testing'; import { SearchService } from './search.service'; +import { RequestService } from './request.service'; import { HttpModule, JsonpModule } from '@angular/http'; import { @@ -20,7 +21,8 @@ describe('SearchService', () => { provideAppStore(), provideSearchSourceService(), provideSearchSource(), - SearchService + SearchService, + RequestService ] }); }); diff --git a/src/app/core/search.service.ts b/src/app/core/search.service.ts index d7455f79e..efc847130 100644 --- a/src/app/core/search.service.ts +++ b/src/app/core/search.service.ts @@ -5,6 +5,7 @@ import { Store } from '@ngrx/store'; import { Observable } from 'rxjs/Observable'; import { Subscription } from 'rxjs/Subscription'; +import { RequestService } from './request.service'; import { SearchSourceService } from './search-source.service'; import { SearchResult } from '../search/shared/search-result.interface'; import { SearchSource } from '../search/sources/search-source'; @@ -17,7 +18,8 @@ export class SearchService { subscriptions: Subscription[] = []; constructor(private store: Store, - private searchSourceService: SearchSourceService) { + private searchSourceService: SearchSourceService, + private requestService: RequestService) { } search(term?: string) { @@ -29,7 +31,9 @@ export class SearchService { } searchSource(source: SearchSource, term?: string) { - return source.search(term) + const request = source.search(term); + + return this.requestService.register(request) .catch(this.handleError) .subscribe((results: SearchResult[]) => this.handleSearchResults(results, source)); diff --git a/src/app/core/spinner/spinner.component.html b/src/app/core/spinner/spinner.component.html new file mode 100644 index 000000000..cfa579f46 --- /dev/null +++ b/src/app/core/spinner/spinner.component.html @@ -0,0 +1,4 @@ +
+
+ +
diff --git a/src/app/core/spinner/spinner.component.spec.ts b/src/app/core/spinner/spinner.component.spec.ts new file mode 100644 index 000000000..fcca86af6 --- /dev/null +++ b/src/app/core/spinner/spinner.component.spec.ts @@ -0,0 +1,32 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TestModule } from '../../test.module'; + +import { RequestService } from '../request.service'; +import { SpinnerComponent } from './spinner.component'; + +describe('SpinnerComponent', () => { + let component: SpinnerComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + TestModule + ], + declarations: [ SpinnerComponent ], + providers: [ RequestService ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SpinnerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/core/spinner/spinner.component.styl b/src/app/core/spinner/spinner.component.styl new file mode 100644 index 000000000..53f3e2337 --- /dev/null +++ b/src/app/core/spinner/spinner.component.styl @@ -0,0 +1,33 @@ +@require '../../../css/theme.styl'; + +$igo-spinner-size = 40px; +$igo-spinner-border-width = 4px; +$igo-spinner-top-left = 2px; + +.igo-spinner { + display: none; +} + +.igo-spinner-shown { + display: block; +} + +md-progress-circle { + height: $igo-spinner-size; + width: $igo-spinner-size; + border-radius: 50%; +} + +:host >>> md-progress-circle path { + stroke: $igo-primary-color; +} + +.igo-spinner-background { + height: $igo-spinner-size - $igo-spinner-border-width; + width: $igo-spinner-size - $igo-spinner-border-width; + border-radius: 50%; + border: $igo-spinner-border-width solid $igo-tertiary-color; + position: absolute; + top: $igo-spinner-top-left; + left: $igo-spinner-top-left; +} diff --git a/src/app/core/spinner/spinner.component.ts b/src/app/core/spinner/spinner.component.ts new file mode 100644 index 000000000..2ef6bad9e --- /dev/null +++ b/src/app/core/spinner/spinner.component.ts @@ -0,0 +1,22 @@ +import { Component, OnInit } from '@angular/core'; + +import { RequestService } from '../request.service'; + +@Component({ + selector: 'igo-spinner', + templateUrl: './spinner.component.html', + styleUrls: ['./spinner.component.styl'], +}) +export class SpinnerComponent implements OnInit { + + shown: boolean = false; + + constructor(private requestService: RequestService) { } + + ngOnInit() { + this.requestService.requests.subscribe((count: number) => { + this.shown = count > 0; + }); + } + +} diff --git a/src/app/core/tool.service.ts b/src/app/core/tool.service.ts index 43f84a371..ca732355d 100644 --- a/src/app/core/tool.service.ts +++ b/src/app/core/tool.service.ts @@ -22,6 +22,10 @@ export class ToolService { ToolService.toolClasses.push(cls); } + constructor() { + ToolService.register(SearchToolComponent); + } + getTool(name: string) { return ToolService.tools.find(t => t.name === name); } @@ -29,9 +33,4 @@ export class ToolService { getToolClass(name: string) { return ToolService.toolClasses.find(t => t.name_ === name); } - - constructor() { - ToolService.register(SearchToolComponent); - } - } diff --git a/src/app/map/map/map.component.styl b/src/app/map/map/map.component.styl index f0ca319a4..c7524604d 100644 --- a/src/app/map/map/map.component.styl +++ b/src/app/map/map/map.component.styl @@ -5,3 +5,25 @@ width: 100%; height: 100%; } + +:host >>> .igo-zoom-container { + position: absolute; + bottom: $igo-margin; + right: $igo-margin; + width: $igo-icon-button-width; + + +media(mobile) { + display: none; + } +} + +:host >>> .ol-attribution { + left: $igo-margin; + right: inherit; + text-align: left; + padding: 0; +} + +:host >>> .ol-attribution ul { + padding: 0; +} diff --git a/src/app/map/zoom/zoom.component.styl b/src/app/map/zoom/zoom.component.styl index 8d70c3b93..917e3cc9f 100644 --- a/src/app/map/zoom/zoom.component.styl +++ b/src/app/map/zoom/zoom.component.styl @@ -1,17 +1,6 @@ @require '../../../css/theme.styl'; @require '../../../css/media.styl'; -.igo-zoom-container { - position: absolute; - top: $igo-margin; - right: $igo-margin; - width: $igo-icon-button-width; - - +media(mobile) { - display: none; - } -} - .igo-zoom-container button:first-child { margin-bottom: 2px; } diff --git a/src/app/pages/navigator/navigator.component.spec.ts b/src/app/pages/navigator/navigator.component.spec.ts index 4268e2835..d034c955b 100644 --- a/src/app/pages/navigator/navigator.component.spec.ts +++ b/src/app/pages/navigator/navigator.component.spec.ts @@ -8,6 +8,7 @@ import { import { MapService } from '../../core/map.service'; import { LayerService } from '../../map/shared/layer.service'; import { SearchService } from '../../core/search.service'; +import { RequestService } from '../../core/request.service'; import { TestModule } from '../../test.module'; import { SharedModule } from '../../shared/shared.module'; @@ -40,7 +41,8 @@ describe('NavigatorComponent', () => { MapService, LayerService, SearchService, - ToolService + ToolService, + RequestService ] }) .compileComponents(); diff --git a/src/app/pages/navigator/navigator.component.styl b/src/app/pages/navigator/navigator.component.styl index 86624db55..9fd219e63 100644 --- a/src/app/pages/navigator/navigator.component.styl +++ b/src/app/pages/navigator/navigator.component.styl @@ -5,12 +5,22 @@ $igo-sidenav-width = 400px; $igo-sidenav-margin-top = 50px; $igo-mobile-min-space-left = $igo-icon-button-width; + /*--- Sidenav ---*/ md-sidenav-container { width: 100%; height: 100%; } +// This is needed because whe using the +// sidenav "side" mode, the z-index is 1 +// and the sidenav appears below our backdrop. +// The "side" mode is required to prevent +// the sidenav from focusing a random button on open. +:host >>> md-sidenav.md-sidenav-side { + z-index: 3 !important; +} + :host >>> igo-sidenav md-sidenav { width: $igo-sidenav-width; @@ -60,6 +70,6 @@ md-sidenav-container { } #igo-lower-panel { - border-top(); + bordered-top(); border-box(); } \ No newline at end of file diff --git a/src/app/search/search-bar/search-bar.component.spec.ts b/src/app/search/search-bar/search-bar.component.spec.ts index d2c82678a..c0f5a4a29 100644 --- a/src/app/search/search-bar/search-bar.component.spec.ts +++ b/src/app/search/search-bar/search-bar.component.spec.ts @@ -7,6 +7,7 @@ import { } from '../../core/core.module'; import { SearchService } from '../../core/search.service'; +import { RequestService } from '../../core/request.service'; import { TestModule } from '../../test.module'; import { SharedModule } from '../../shared/shared.module'; @@ -27,7 +28,8 @@ describe('SearchBarComponent', () => { provideAppStore(), provideSearchSourceService(), provideSearchSource(), - SearchService + SearchService, + RequestService ] }) .compileComponents(); diff --git a/src/css/styles.styl b/src/css/styles.styl index 0fb999fd7..3bc4718d8 100644 --- a/src/css/styles.styl +++ b/src/css/styles.styl @@ -17,20 +17,11 @@ html, body { box-sizing: border-box; } -/* -@import '~@angular/material/core/theming/prebuilt/deeppurple-amber.css'; -*/ -@require './theme.styl'; +//@import '~@angular/material/core/theming/prebuilt/deeppurple-amber.css'; -// This is needed because whe using the -// sidenav "side" mode, the z-index is 1 -// and the sidenav appears below our backdrop. -// The "side" mode is required to prevent -// the sidenav from focusing a random button on open. -md-sidenav.md-sidenav-side { - z-index: 3 !important; -} + +@require './theme.styl'; input { height: $igo-icon-button-height; diff --git a/src/css/theme.styl b/src/css/theme.styl index e545125c8..e30981eb7 100644 --- a/src/css/theme.styl +++ b/src/css/theme.styl @@ -48,31 +48,27 @@ border-box() -moz-box-sizing: border-box; -webkit-box-sizing: border-box; -border() +bordered() border-width $igo-border-width border-style $igo-border-style border-color $igo-border-color - -webkit-box-shadow: inset 0px 0px 0px 10px #f00; - -moz-box-shadow: inset 0px 0px 0px 10px #f00; - box-shadow: inset 0px 0px 0px 10px #f00; - -border-top() +bordered-top() border-top-width $igo-border-width border-top-style $igo-border-style border-top-color $igo-border-color -border-bottom() +bordered-bottom() border-bottom-width $igo-border-width border-bottom-style $igo-border-style border-bottom-color $igo-border-color -border-left() +bordered-left() border-left-width $igo-border-width border-left-style $igo-border-style border-left-color $igo-border-color -border-right() +bordered-right() border-right-width $igo-border-width border-right-style $igo-border-style border-right-color $igo-border-color