intermediate commit
This commit is contained in:
@@ -29,6 +29,10 @@
|
|||||||
"@angular/cli": "1.6.3",
|
"@angular/cli": "1.6.3",
|
||||||
"@angular/compiler-cli": "^5.0.0",
|
"@angular/compiler-cli": "^5.0.0",
|
||||||
"@angular/language-service": "^5.0.0",
|
"@angular/language-service": "^5.0.0",
|
||||||
|
"@types/gapi": "^0.0.35",
|
||||||
|
"@types/gapi.auth2": "^0.0.46",
|
||||||
|
"@types/gapi.client": "^1.0.0",
|
||||||
|
"@types/gapi.client.sheets": "^4.0.0",
|
||||||
"@types/jasmine": "~2.5.53",
|
"@types/jasmine": "~2.5.53",
|
||||||
"@types/jasminewd2": "~2.0.2",
|
"@types/jasminewd2": "~2.0.2",
|
||||||
"@types/node": "~6.0.60",
|
"@types/node": "~6.0.60",
|
||||||
|
|||||||
@@ -1,7 +1,16 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { Routes, RouterModule } from '@angular/router';
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
|
|
||||||
const routes: Routes = [];
|
import { HomeComponent } from './home/home.component';
|
||||||
|
|
||||||
|
import { AuthGuard } from './auth.guard';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: HomeComponent
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [RouterModule.forRoot(routes)],
|
imports: [RouterModule.forRoot(routes)],
|
||||||
|
|||||||
@@ -1,21 +1,4 @@
|
|||||||
<!--The content below is only a placeholder and can be replaced.-->
|
<app-header></app-header>
|
||||||
<div style="text-align:center">
|
<div class="container">
|
||||||
<h1>
|
<router-outlet></router-outlet>
|
||||||
Welcome to {{ title }}!
|
|
||||||
</h1>
|
|
||||||
<img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
|
|
||||||
</div>
|
</div>
|
||||||
<h2>Here are some links to help you start: </h2>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h2><a target="_blank" rel="noopener" href="https://github.com/angular/angular-cli/wiki">CLI Documentation</a></h2>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<router-outlet></router-outlet>
|
|
||||||
|
|||||||
@@ -4,17 +4,22 @@ import { NgModule } from '@angular/core';
|
|||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
|
import { HeaderComponent } from './header/header.component';
|
||||||
|
import { HomeComponent } from './home/home.component';
|
||||||
|
import { GApiService } from './gapi.service';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent
|
AppComponent,
|
||||||
|
HeaderComponent,
|
||||||
|
HomeComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
AppRoutingModule
|
AppRoutingModule
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [GApiService],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
export class AppModule { }
|
export class AppModule { }
|
||||||
|
|||||||
15
src/app/auth.guard.spec.ts
Normal file
15
src/app/auth.guard.spec.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { TestBed, async, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AuthGuard } from './auth.guard';
|
||||||
|
|
||||||
|
describe('AuthGuard', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [AuthGuard]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ...', inject([AuthGuard], (guard: AuthGuard) => {
|
||||||
|
expect(guard).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
||||||
19
src/app/auth.guard.ts
Normal file
19
src/app/auth.guard.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { GApiService } from './gapi.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthGuard implements CanActivate {
|
||||||
|
constructor (private gapiService: GApiService, private router: Router) { }
|
||||||
|
|
||||||
|
canActivate (
|
||||||
|
next: ActivatedRouteSnapshot,
|
||||||
|
state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
|
||||||
|
const authenticated = this.gapiService.isAuthenticated();
|
||||||
|
if (!authenticated) {
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
}
|
||||||
|
return authenticated;
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/app/gapi.service.spec.ts
Normal file
15
src/app/gapi.service.spec.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { TestBed, inject } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { GApiService } from './gapi.service';
|
||||||
|
|
||||||
|
describe('GapiService', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
providers: [GApiService]
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be created', inject([GApiService], (service: GApiService) => {
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
}));
|
||||||
|
});
|
||||||
84
src/app/gapi.service.ts
Normal file
84
src/app/gapi.service.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import { bindCallback } from 'rxjs/Observable/bindCallback';
|
||||||
|
import { fromPromise } from 'rxjs/Observable/fromPromise';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import 'rxjs/add/operator/map';
|
||||||
|
|
||||||
|
const API_KEY = 'AIzaSyA9BX0C-ku3NBkTSeHY59DiJi02U-DbCe0';
|
||||||
|
const CLIENT_ID = '505888163689-q01h4puni0e74t36m04mcksr0g7pti5v.apps.googleusercontent.com';
|
||||||
|
|
||||||
|
const DISCOVERY_DOCS: string[] = ['https://sheets.googleapis.com/$discovery/rest?version=v4'];
|
||||||
|
const SCOPES = 'https://www.googleapis.com/auth/spreadsheets.readonly';
|
||||||
|
|
||||||
|
function gapiRequestToObservable<T>(request: gapi.client.Request<T>): Observable<T> {
|
||||||
|
return fromPromise<gapi.client.Response<T>>(request).map(res => res.result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a positive integer and returns the corresponding column name.
|
||||||
|
* @param {number} num The positive integer to convert to a column name.
|
||||||
|
* @return {string} The column name.
|
||||||
|
*/
|
||||||
|
function numberToColumnName(num: number): string {
|
||||||
|
let ret = '';
|
||||||
|
for (; num > 0; num = Math.floor(num % 26)) {
|
||||||
|
num--; // because nobody starts counting columns at 0 and A needs to be 1
|
||||||
|
ret = String.fromCharCode(Math.floor(num % 26) + 65) + ret;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GApiService {
|
||||||
|
private observableGetAuthInstance: () => Observable<boolean>;
|
||||||
|
private authInstance: gapi.auth2.GoogleAuth;
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
gapi.load('client:auth2', () => {
|
||||||
|
gapi.client.init({
|
||||||
|
apiKey: API_KEY,
|
||||||
|
clientId: CLIENT_ID,
|
||||||
|
discoveryDocs: DISCOVERY_DOCS,
|
||||||
|
scope: SCOPES
|
||||||
|
}).then(() => {
|
||||||
|
this.authInstance = gapi.auth2.getAuthInstance();
|
||||||
|
// Listen for sign-in state changes.
|
||||||
|
this.observableGetAuthInstance = bindCallback<boolean>(this.authInstance.isSignedIn.listen);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get authStatusChange(): Observable<boolean> { return this.observableGetAuthInstance(); }
|
||||||
|
|
||||||
|
isAuthenticated (): boolean {
|
||||||
|
return this.authInstance.isSignedIn.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
signIn (): void {
|
||||||
|
gapi.auth2.getAuthInstance().signIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
signOut (): void {
|
||||||
|
gapi.auth2.getAuthInstance().signOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
getSpreadsheet (id: string): Observable<gapi.client.sheets.Spreadsheet> {
|
||||||
|
return gapiRequestToObservable<gapi.client.sheets.Spreadsheet>(gapi.client.spreadsheets.get({
|
||||||
|
spreadsheetId: id
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
getDataOnSheet (spreadsheetId: string, sheet: gapi.client.sheets.Sheet) {
|
||||||
|
gapi.client.spreadsheets.values.get({
|
||||||
|
spreadsheetId,
|
||||||
|
range: sheet.properties.title + '!A1:1',
|
||||||
|
majorDimension: 'ROWS'
|
||||||
|
}).then(firstRowResponse => {
|
||||||
|
const firstRow = firstRowResponse.result.values[0];
|
||||||
|
gapi.client.spreadsheets.values.get({
|
||||||
|
spreadsheetId,
|
||||||
|
range: sheet.properties.title + '!A1:' + numberToColumnName(firstRow.length)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/app/header/header.component.css
Normal file
0
src/app/header/header.component.css
Normal file
11
src/app/header/header.component.html
Normal file
11
src/app/header/header.component.html
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<nav class="navbar navbar-inverse navbar-top">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<a routerLink="/" class="navbar-brand">QContacts</a>
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
<li><a routerLink="/">Home</a></li>
|
||||||
|
</ul>
|
||||||
|
<div class="navbar-right" *ngIf="loggedIn"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
25
src/app/header/header.component.spec.ts
Normal file
25
src/app/header/header.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { HeaderComponent } from './header.component';
|
||||||
|
|
||||||
|
describe('HeaderComponent', () => {
|
||||||
|
let component: HeaderComponent;
|
||||||
|
let fixture: ComponentFixture<HeaderComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ HeaderComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(HeaderComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
26
src/app/header/header.component.ts
Normal file
26
src/app/header/header.component.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
|
import { Observable } from 'rxjs/Observable';
|
||||||
|
import { GApiService } from '../gapi.service';
|
||||||
|
import { Subscription } from 'rxjs/Subscription';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-header',
|
||||||
|
templateUrl: './header.component.html',
|
||||||
|
styleUrls: ['./header.component.css']
|
||||||
|
})
|
||||||
|
export class HeaderComponent implements OnInit, OnDestroy {
|
||||||
|
private sub: Subscription;
|
||||||
|
constructor(private gapiService: GApiService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
loggedIn = false;
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loggedIn = this.gapiService.isAuthenticated();
|
||||||
|
this.sub = this.gapiService.authStatusChange.subscribe(newValue => this.loggedIn = newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.sub.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/app/home/home.component.css
Normal file
0
src/app/home/home.component.css
Normal file
3
src/app/home/home.component.html
Normal file
3
src/app/home/home.component.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<p>
|
||||||
|
home works!
|
||||||
|
</p>
|
||||||
25
src/app/home/home.component.spec.ts
Normal file
25
src/app/home/home.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { HomeComponent } from './home.component';
|
||||||
|
|
||||||
|
describe('HomeComponent', () => {
|
||||||
|
let component: HomeComponent;
|
||||||
|
let fixture: ComponentFixture<HomeComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ HomeComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(HomeComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
15
src/app/home/home.component.ts
Normal file
15
src/app/home/home.component.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-home',
|
||||||
|
templateUrl: './home.component.html',
|
||||||
|
styleUrls: ['./home.component.css']
|
||||||
|
})
|
||||||
|
export class HomeComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor() { }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,8 +5,18 @@
|
|||||||
<title>QContacts</title>
|
<title>QContacts</title>
|
||||||
<base href="/">
|
<base href="/">
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
|
|
||||||
|
<!-- bootstrap stuff begin -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha256-916EbMg70RQy9LHiGkXzG8hSg9EdNy97GazNG/aiY1w=" crossorigin="anonymous" />
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" integrity="sha256-NuCn4IvuZXdBaFKJOAcsU2Q3ZpwbdFisd5dux4jkQ5w=" crossorigin="anonymous" />
|
||||||
|
<!-- bootstrap stuff end -->
|
||||||
|
|
||||||
|
<!-- google stuff begin -->
|
||||||
|
<script src="https://apis.google.com/js/api.js"></script>
|
||||||
|
<!-- <meta name="google-signin-client_id" content="505888163689-q01h4puni0e74t36m04mcksr0g7pti5v.apps.googleusercontent.com"> -->
|
||||||
|
<!-- google stuff end -->
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root></app-root>
|
<app-root></app-root>
|
||||||
|
|||||||
@@ -24,7 +24,8 @@
|
|||||||
"import-spacing": true,
|
"import-spacing": true,
|
||||||
"indent": [
|
"indent": [
|
||||||
true,
|
true,
|
||||||
"spaces"
|
"spaces",
|
||||||
|
2
|
||||||
],
|
],
|
||||||
"interface-over-type-literal": true,
|
"interface-over-type-literal": true,
|
||||||
"label-position": true,
|
"label-position": true,
|
||||||
|
|||||||
20
yarn.lock
20
yarn.lock
@@ -185,6 +185,26 @@
|
|||||||
version "0.0.11"
|
version "0.0.11"
|
||||||
resolved "https://registry.yarnpkg.com/@schematics/schematics/-/schematics-0.0.11.tgz#c8f70f270ed38f29b2873248126fd59abd635862"
|
resolved "https://registry.yarnpkg.com/@schematics/schematics/-/schematics-0.0.11.tgz#c8f70f270ed38f29b2873248126fd59abd635862"
|
||||||
|
|
||||||
|
"@types/gapi.auth2@^0.0.46":
|
||||||
|
version "0.0.46"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/gapi.auth2/-/gapi.auth2-0.0.46.tgz#5c929d3e66d74b84293c65aaa0ce12e0f8d4fb36"
|
||||||
|
dependencies:
|
||||||
|
"@types/gapi" "*"
|
||||||
|
|
||||||
|
"@types/gapi.client.sheets@^4.0.0":
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/gapi.client.sheets/-/gapi.client.sheets-4.0.0.tgz#bfc4f4f24a0292da3b4d7b561d6d67ded7b11ebb"
|
||||||
|
dependencies:
|
||||||
|
"@types/gapi.client" "*"
|
||||||
|
|
||||||
|
"@types/gapi.client@*", "@types/gapi.client@^1.0.0":
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/gapi.client/-/gapi.client-1.0.0.tgz#aa42501465fcf6440421050da70ec724d4c4c4df"
|
||||||
|
|
||||||
|
"@types/gapi@*", "@types/gapi@^0.0.35":
|
||||||
|
version "0.0.35"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/gapi/-/gapi-0.0.35.tgz#8e0d672d66d8c6ce8493f5e8e550344f7ff500dc"
|
||||||
|
|
||||||
"@types/jasmine@*":
|
"@types/jasmine@*":
|
||||||
version "2.8.3"
|
version "2.8.3"
|
||||||
resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.8.3.tgz#f910edc67d69393d562d10f8f3d205ea3f3306bf"
|
resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.8.3.tgz#f910edc67d69393d562d10f8f3d205ea3f3306bf"
|
||||||
|
|||||||
Reference in New Issue
Block a user