├── .gitignore
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── images
├── app-demo.png
└── app-registration.png
├── index.html
├── jsconfig.json
├── main-config.js
├── main.js
├── menu.js
├── package.json
├── src
├── app
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── main.ts
│ └── view-main.html
├── auth
│ └── auth-guard.service.ts
├── config
│ └── azure-config.ts
├── contacts
│ ├── contacts.component.ts
│ └── view-contacts.html
├── files
│ ├── files.component.ts
│ └── view-files.html
├── groups
│ ├── groups.component.ts
│ └── view-groups.html
├── home
│ ├── home.component.ts
│ └── view-home.html
├── login
│ ├── login.component.ts
│ └── view-login.html
├── mails
│ ├── mails.component.ts
│ └── view-mails.html
├── messages
│ └── messages.ts
├── models
│ └── runtime-info.model.ts
├── notes
│ ├── notes.component.ts
│ └── view-notes.html
├── office
│ └── office-urls.ts
├── package.json
├── profile
│ └── profile.component.ts
├── routes
│ └── app.routes.ts
├── services
│ ├── electron.service.ts
│ └── graph.service.ts
├── tasks
│ ├── tasks.component.ts
│ └── view-tasks.html
├── toast
│ └── toast.component.ts
├── trending
│ ├── trending.component.ts
│ └── view-trending.html
└── users
│ ├── users.component.ts
│ └── view-users.html
├── tsconfig.json
├── tslint.json
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
27 | node_modules
28 | build
29 | typings
30 | releases
31 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | // Place your settings in this file to overwrite default and user settings.
2 | {
3 | "typescript.tsdk": "node_modules/typescript/lib"
4 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Cecil du Toit
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # angular2-O365-desktop-app
2 | Desktop application using Electron, Angular 2, Material Design Lite and Office 365 (Microsoft Graph API).
3 |
4 | 
5 |
6 | ## Getting started
7 |
8 | Install dependencies
9 |
10 | `
11 | npm install
12 | `
13 |
14 | To build and watch for changes
15 |
16 | `
17 | npm run watch
18 | `
19 |
20 | Run application
21 |
22 | `
23 | npm start
24 | `
25 |
26 | ## Debug Electron using [Devtron](http://electron.atom.io/devtron/)
27 |
28 | `
29 |
30 | // Run the following from the Console tab of your app's DevTools
31 |
32 | require('devtron').install()
33 |
34 | // You should now see a Devtron tab added to the DevTools
35 | `
36 |
37 | ## Azure AD application Registration
38 |
39 | Register this application in the Azure Active Directory service for your Office 365 tenant.
40 | Easy to use online tool: [Office 365 App Registration Tool](https://dev.office.com/app-registration)
41 |
42 | * App Registration Type: Native Application
43 | * Redirect URI: http://localhost/callback
44 | * Select all read items for Users, Groups, Mail, Calendar, Contacts and Files.
45 | * Register the app and remember to copy your client ID provided to you.
46 | * Update the azure-config.ts file with your values.
47 |
48 | 
49 |
50 | Once your app is registered you need to download the app manifest file from [Azure Portal](https://manage.windowsazure.com/)
51 | Set "oauth2AllowImplicitFlow": true in application manifest file and upload the manifest file again.
52 |
53 | ### Azure AD Application Permission requirements
54 | You need to make sure the application permission is set correctly to have access to the Microsoft Graph resource.
55 | This can be done in the [Azure Portal](https://manage.windowsazure.com/).
56 |
57 | Required Permissions
58 |
59 | * Read all notebooks that the user can access
60 | * Access user's data anytime
61 | * Read items in all site collections
62 | * Read files that the user selects
63 | * Read user files and files shared with user
64 | * Read user contacts
65 | * Read user calendars
66 | * Read user mail
67 | * Read directory data
68 | * Read all groups
69 | * Read all users' full profiles
70 | * Read all users' basic profiles
71 | * Sign in and read user profile
72 | * Read user notebooks
73 | * Read users' relevant people list
74 |
75 | Go to [Microsoft Graph API](http://graph.microsoft.io/docs/overview/overview) if you want to know more.
76 |
77 | ## Application Build & Deployment
78 |
79 | Follow Electron documentation for build for Windows, Linux and OSX.
80 |
81 | To package files for deployment:
82 |
83 |
84 | * npm install -g asar
85 | * asar pack angular2-O365-desktop-app app.asar
86 |
87 | TODO Packaging: https://www.xplatform.rocks/2016/02/14/angular2-and-electron-the-definitive-guide/
88 |
89 |
90 | ** More details to follow **
91 |
92 | ## Resources
93 |
94 | * [Angular 2](https://angular.io/)
95 | * [Material Design Lite](http://www.getmdl.io/)
96 | * [Electron](http://electron.atom.io/)
97 | * [Office 365](https://products.office.com/en-gb/business/compare-office-365-for-business-plans)
98 | * [Office 365 Development](https://dev.office.com/)
99 |
100 | If you have any issues or suggestion please submit on [GitHub Issues](https://github.com/Cecildt/angular2-O365-desktop-app/issues).
--------------------------------------------------------------------------------
/images/app-demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cecildt/angular2-O365-desktop-app/543815bd35a268ede6c8a9758748e51b530d836f/images/app-demo.png
--------------------------------------------------------------------------------
/images/app-registration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cecildt/angular2-O365-desktop-app/543815bd35a268ede6c8a9758748e51b530d836f/images/app-registration.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Office 365 Desktop Experience
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Loading...
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | // See https://go.microsoft.com/fwlink/?LinkId=759670
3 | // for the documentation about the jsconfig.json format
4 | "compilerOptions": {
5 | "target": "es6",
6 | "module": "commonjs",
7 | "allowSyntheticDefaultImports": true
8 | },
9 | "exclude": [
10 | "node_modules",
11 | "bower_components",
12 | "jspm_packages",
13 | "tmp",
14 | "temp"
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/main-config.js:
--------------------------------------------------------------------------------
1 | var mainConfig = {
2 | authorityHostUrl: 'https://login.windows.net',
3 | tenant: 'mod340807.onmicrosoft.com',
4 | clientId: 'bbffe1fd-bf52-41b2-b898-4903cb73f9db',
5 | clientSecret: '',
6 | extraQueryParameter: 'nux=1',
7 | disableRenewal: true,
8 | resource: "https://graph.microsoft.com",
9 | redirectUri: "/",
10 | postLogoutRedirectUri: "",
11 | endpoints: {
12 | graphApiUri: 'https://graph.microsoft.com'
13 | }
14 | }
15 |
16 | module.export = mainConfig;
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const electron = require('electron');
4 | const app = electron.app;
5 |
6 | const AdalMainConfig = require("./main-config");
7 |
8 | require('electron-debug')({ showDevTools: true });
9 |
10 | // browser-window creates a native window
11 | const BrowserWindow = electron.BrowserWindow;
12 | let mainWindow = null;
13 | let hash = "";
14 |
15 | // Allows for live-reload while developing the app
16 | require('electron-reload')(__dirname + '/build');
17 |
18 | function createWindow() {
19 |
20 | // Initialize the window to our specified dimensions
21 | mainWindow = new BrowserWindow({
22 | width: 1200,
23 | height: 900,
24 | autoHideMenuBar: true,
25 | webPreferences: {
26 | nodeIntegration: true
27 | }
28 | });
29 |
30 | mainWindow.maximize();
31 | // Tell Electron where to load the entry point from
32 | mainWindow.loadURL('file://' + __dirname + '/index.html');
33 |
34 | // Clear out the main window when the app is closed
35 | mainWindow.on('closed', function () {
36 | mainWindow = null;
37 | });
38 | }
39 |
40 | app.on('ready', createWindow);
41 |
42 | app.on('window-all-closed', function () {
43 | if (process.platform != 'darwin') {
44 | app.quit();
45 | }
46 | });
47 |
48 | app.on('activate', function () {
49 | // On OS X it's common to re-create a window in the app when the
50 | // dock icon is clicked and there are no other windows open.
51 | if (mainWindow === null) {
52 | createWindow();
53 | }
54 | });
55 |
56 |
--------------------------------------------------------------------------------
/menu.js:
--------------------------------------------------------------------------------
1 | const remote = require('electron').remote;
2 | const Menu = remote.Menu;
3 |
4 | var template = [
5 | {
6 | label: 'Edit',
7 | submenu: [
8 | {
9 | label: 'Undo',
10 | accelerator: 'CmdOrCtrl+Z',
11 | role: 'undo'
12 | },
13 | {
14 | label: 'Redo',
15 | accelerator: 'Shift+CmdOrCtrl+Z',
16 | role: 'redo'
17 | },
18 | {
19 | type: 'separator'
20 | },
21 | {
22 | label: 'Cut',
23 | accelerator: 'CmdOrCtrl+X',
24 | role: 'cut'
25 | },
26 | {
27 | label: 'Copy',
28 | accelerator: 'CmdOrCtrl+C',
29 | role: 'copy'
30 | },
31 | {
32 | label: 'Paste',
33 | accelerator: 'CmdOrCtrl+V',
34 | role: 'paste'
35 | },
36 | {
37 | label: 'Select All',
38 | accelerator: 'CmdOrCtrl+A',
39 | role: 'selectall'
40 | },
41 | {
42 | label: 'Custom Action',
43 | role: 'customAction'
44 | }
45 | ]
46 | },
47 | {
48 | label: 'View',
49 | submenu: [
50 | {
51 | label: 'Reload',
52 | accelerator: 'CmdOrCtrl+R',
53 | click: function(item, focusedWindow) {
54 | if (focusedWindow)
55 | focusedWindow.reload();
56 | }
57 | },
58 | {
59 | label: 'Toggle Full Screen',
60 | accelerator: (function() {
61 | if (process.platform == 'darwin')
62 | return 'Ctrl+Command+F';
63 | else
64 | return 'F11';
65 | })(),
66 | click: function(item, focusedWindow) {
67 | if (focusedWindow)
68 | focusedWindow.setFullScreen(!focusedWindow.isFullScreen());
69 | }
70 | },
71 | {
72 | label: 'Toggle Developer Tools',
73 | accelerator: (function() {
74 | if (process.platform == 'darwin')
75 | return 'Alt+Command+I';
76 | else
77 | return 'Ctrl+Shift+I';
78 | })(),
79 | click: function(item, focusedWindow) {
80 | if (focusedWindow)
81 | focusedWindow.webContents.toggleDevTools();
82 | }
83 | },
84 | ]
85 | },
86 | {
87 | label: 'Window',
88 | role: 'window',
89 | submenu: [
90 | {
91 | label: 'Minimize',
92 | accelerator: 'CmdOrCtrl+M',
93 | role: 'minimize'
94 | },
95 | {
96 | label: 'Close',
97 | accelerator: 'CmdOrCtrl+W',
98 | role: 'close'
99 | },
100 | ]
101 | },
102 | {
103 | label: 'Help',
104 | role: 'help',
105 | submenu: [
106 | {
107 | label: 'Electron',
108 | click: function() { require('electron').shell.openExternal('http://electron.atom.io') }
109 | },
110 | {
111 | label: 'Office 365',
112 | click: function() { require('electron').shell.openExternal('https://office365.com') }
113 | },
114 | {
115 | label: 'Office 365 Development',
116 | click: function() { require('electron').shell.openExternal('https://dev.office.com') }
117 | },
118 | {
119 | label: 'Office Graph',
120 | click: function() { require('electron').shell.openExternal('https://graph.microsoft.com') }
121 | }
122 | ]
123 | },
124 | ];
125 |
126 | if (process.platform == 'darwin') {
127 | var name = require('electron').remote.app.getName();
128 | template.unshift({
129 | label: name,
130 | submenu: [
131 | {
132 | label: 'About ' + name,
133 | role: 'about'
134 | },
135 | {
136 | type: 'separator'
137 | },
138 | {
139 | label: 'Services',
140 | role: 'services',
141 | submenu: []
142 | },
143 | {
144 | type: 'separator'
145 | },
146 | {
147 | label: 'Hide ' + name,
148 | accelerator: 'Command+H',
149 | role: 'hide'
150 | },
151 | {
152 | label: 'Hide Others',
153 | accelerator: 'Command+Alt+H',
154 | role: 'hideothers'
155 | },
156 | {
157 | label: 'Show All',
158 | role: 'unhide'
159 | },
160 | {
161 | type: 'separator'
162 | },
163 | {
164 | label: 'Quit',
165 | accelerator: 'Command+Q',
166 | click: function() { app.quit(); }
167 | },
168 | ]
169 | });
170 | // Window menu.
171 | template[3].submenu.push(
172 | {
173 | type: 'separator'
174 | },
175 | {
176 | label: 'Bring All to Front',
177 | role: 'front'
178 | }
179 | );
180 | }
181 |
182 | var menu = Menu.buildFromTemplate(template);
183 | Menu.setApplicationMenu(menu);
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular2-o365-desktop-app",
3 | "version": "1.0.0",
4 | "description": "Angular 2 desktop application using Electron and Office 365.",
5 | "repository": {
6 | "type": "git",
7 | "url": "https://github.com/Cecildt/angular2-O365-desktop-app.git"
8 | },
9 | "eslintConfig": {
10 | "env": {
11 | "browser": true,
12 | "node": true
13 | }
14 | },
15 | "main": "main.js",
16 | "scripts": {
17 | "test": "echo \"Error: no test specified\" && exit 1",
18 | "start": "electron main.js",
19 | "build": "webpack --progress --profile --colors --display-error-details --display-cached",
20 | "watch": "webpack-dashboard -- webpack --watch --progress --profile --colors --display-error-details --display-cached",
21 | "distribute": "electron-packager . O365-Angular --platform=darwin --arch=x64 --out=releases/ --overwrite --icon=src/assets/images/icon --asar",
22 | "electron:package": "npm run electron:pack-mac && npm run electron:pack-win && npm run electron:pack-linux",
23 | "electron:pack-mac": "electron-packager www --asar --overwrite --platform=darwin --arch=x64 --out=built",
24 | "electron:pack-win": "electron-packager www --asar --overwrite --platform=win32 --arch=ia32 --out=built",
25 | "electron:pack-linux": "electron-packager www --asar --overwrite --platform=linux --arch=x64 --out=built"
26 | },
27 | "author": "Cecil du Toit",
28 | "license": "MIT",
29 | "dependencies": {
30 | "@angular/common": "2.4.0",
31 | "@angular/compiler": "2.4.0",
32 | "@angular/core": "2.4.0",
33 | "@angular/http": "2.4.0",
34 | "@angular/platform-browser": "2.4.0",
35 | "@angular/platform-browser-dynamic": "2.4.0",
36 | "@angular/router": "3.4.0",
37 | "core-js": "^2.4.1",
38 | "material-design-lite": "^1.3.0",
39 | "modular-adal-angular": "^1.0.4",
40 | "oauth": "^0.9.14",
41 | "reflect-metadata": "^0.1.9",
42 | "rxjs": "5.0.1",
43 | "systemjs": "^0.19.40",
44 | "zone.js": "^0.7.4"
45 | },
46 | "devDependencies": {
47 | "@types/core-js": "^0.9.35",
48 | "@types/electron": "^1.4.30",
49 | "@types/lodash": "^4.14.45",
50 | "@types/node": "^6.0.56",
51 | "@types/rx": "^4.1.0",
52 | "@types/rx-angular": "0.0.28",
53 | "@types/webpack": "^2.1.0",
54 | "@types/zone.js": "0.0.27",
55 | "codelyzer": "^2.0.0-beta.4",
56 | "css-loader": "^0.26.1",
57 | "devtron": "^1.4.0",
58 | "electron-debug": "^1.1.0",
59 | "electron-packager": "^8.4.0",
60 | "electron-prebuilt": "^1.4.13",
61 | "electron-reload": "^1.1.0",
62 | "expose-loader": "^0.7.1",
63 | "file-loader": "^0.9.0",
64 | "json-loader": "^0.5.4",
65 | "raw-loader": "^0.5.1",
66 | "ts-loader": "^1.3.3",
67 | "tslint": "^4.2.0",
68 | "typescript": "^2.1.4",
69 | "url-loader": "^0.5.7",
70 | "webpack": "^1.14.0",
71 | "webpack-dashboard": "^0.2.1",
72 | "webpack-dev-server": "^1.16.2"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, AfterViewInit, OnInit } from "@angular/core";
2 |
3 | import { ElectronService } from "../services/electron.service";
4 | import { RuntimeInfoModel } from "../models/runtime-info.model";
5 |
6 | declare var componentHandler: any;
7 |
8 | @Component({
9 | providers: [ElectronService],
10 | selector: "graph-app",
11 | templateUrl: "src/app/view-main.html",
12 | })
13 | export class AppComponent implements AfterViewInit, OnInit {
14 | private nodeVersion: string = "";
15 | private chromeVersion: string = "";
16 | private electronVersion: string = "";
17 |
18 | constructor(private electronService: ElectronService) {
19 | }
20 |
21 | public ngOnInit() {
22 | let info: RuntimeInfoModel = this.electronService.getInfo();
23 |
24 | this.nodeVersion = info.nodeVersion;
25 | this.chromeVersion = info.chromeVersion;
26 | this.electronVersion = info.electronVersion;
27 | }
28 |
29 | public ngAfterViewInit() {
30 | componentHandler.upgradeAllRegistered();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from "@angular/core";
2 | import { BrowserModule } from "@angular/platform-browser";
3 | import { HttpModule } from "@angular/http";
4 |
5 | import { APP_ROUTING } from "../routes/app.routes";
6 | import { AppComponent } from "../app/app.component";
7 | import { GraphService } from "../services/graph.service";
8 | import { ProfileComponent } from "../profile/profile.component";
9 | import { ElectronService } from "../services/electron.service";
10 | import { ToastComponent } from "../toast/toast.component";
11 | import { HomeComponent } from "../home/home.component";
12 | import { LoginComponent } from "../login/login.component";
13 | import { FilesComponent } from "../files/files.component";
14 | import { ContactsComponent } from "../contacts/contacts.component";
15 | import { GroupsComponent } from "../groups/groups.component";
16 | import { MailsComponent } from "../mails/mails.component";
17 | import { NotesComponent } from "../notes/notes.component";
18 | import { TasksComponent } from "../tasks/tasks.component";
19 | import { TrendingComponent } from "../trending/trending.component";
20 | import { UsersComponent } from "../users/users.component";
21 | import { AuthGuardService } from "../auth/auth-guard.service";
22 |
23 | @NgModule({
24 | bootstrap: [ AppComponent ],
25 | declarations: [
26 | AppComponent, ToastComponent, ProfileComponent, HomeComponent, LoginComponent, FilesComponent,
27 | ContactsComponent, GroupsComponent, MailsComponent, NotesComponent, TasksComponent, TrendingComponent,
28 | UsersComponent ],
29 | imports: [ BrowserModule, HttpModule, APP_ROUTING ],
30 | providers: [ GraphService, AuthGuardService, ElectronService, ProfileComponent, ToastComponent ],
31 | })
32 | export class AppModule { }
33 |
--------------------------------------------------------------------------------
/src/app/main.ts:
--------------------------------------------------------------------------------
1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2 |
3 | import { AppModule } from './app.module';
4 |
5 | const platform = platformBrowserDynamic();
6 | platform.bootstrapModule(AppModule);
--------------------------------------------------------------------------------
/src/app/view-main.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/auth/auth-guard.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 | import {
3 | CanActivate,
4 | Router,
5 | ActivatedRouteSnapshot,
6 | RouterStateSnapshot,
7 | } from "@angular/router";
8 |
9 | import { GraphService } from "../services/graph.service";
10 | import { ToastComponent } from "../toast/toast.component";
11 | import { USER_MESSAGES } from "../messages/messages";
12 |
13 | @Injectable()
14 | export class AuthGuardService implements CanActivate {
15 | constructor(private graph: GraphService, private toast: ToastComponent, private router: Router) {}
16 |
17 | public canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise {
18 | return this.graph.isUserAuthenticated().then(() => {
19 | return true;
20 | }).catch((err) => {
21 | this.toast.show(USER_MESSAGES.not_authenticated + " " + err);
22 | this.router.navigate(["login"]);
23 | return false;
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/config/azure-config.ts:
--------------------------------------------------------------------------------
1 | export const AZURE_CONFIG = {
2 | authorityHostUrl: "https://login.windows.net",
3 | clientId: "8582721b-acf4-4f1e-b1a3-6be84a8b55f7",
4 | disableRenewal: true,
5 | endpoints: {
6 | graphApiUri: "https://graph.microsoft.com",
7 | },
8 | extraQueryParameter: "nux=1",
9 | postLogoutRedirectUri: "",
10 | redirectUri: "http://localhost/callback",
11 | resource: "https://graph.microsoft.com",
12 | tenant: "devlaundry.onmicrosoft.com",
13 | };
14 |
--------------------------------------------------------------------------------
/src/contacts/contacts.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectorRef } from "@angular/core";
2 |
3 | import { GraphService } from "../services/graph.service";
4 | import { ToastComponent } from "../toast/toast.component";
5 | import { USER_MESSAGES } from "../messages/messages";
6 | import { OFFICE_URLS } from "../office/office-urls";
7 |
8 | @Component({
9 | selector: "my-contacts",
10 | templateUrl: "src/contacts/view-contacts.html",
11 | })
12 | export class ContactsComponent implements OnInit {
13 | private contacts = [];
14 |
15 | constructor(private graph: GraphService,
16 | private toast: ToastComponent,
17 | private changeRef: ChangeDetectorRef) {
18 | }
19 |
20 | public ngOnInit() {
21 | this.toast.show(USER_MESSAGES.get_contacts);
22 | this.getContacts();
23 | }
24 |
25 | private getContacts() {
26 | this.graph.getRequestPromise(OFFICE_URLS.me_contacts_url)
27 | .then((data: any) => {
28 | if (data) {
29 | this.contacts = data.value;
30 | this.changeRef.detectChanges();
31 | } else {
32 | this.toast.show(USER_MESSAGES.fail_graph_api);
33 | }
34 | })
35 | .catch(() => {
36 | this.toast.show(USER_MESSAGES.fail_graph_api);
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/contacts/view-contacts.html:
--------------------------------------------------------------------------------
1 | My Contacts
2 |
3 |
4 |
5 |
6 | Display Name
7 | Email
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{contact.displayName}}
15 |
16 |
17 | {{contact.emailAddresses[0].address}}
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/files/files.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectorRef } from "@angular/core";
2 |
3 | import { GraphService } from "../services/graph.service";
4 | import { ToastComponent } from "../toast/toast.component";
5 | import { USER_MESSAGES } from "../messages/messages";
6 | import { OFFICE_URLS } from "../office/office-urls";
7 |
8 | @Component({
9 | selector: "my-files",
10 | templateUrl: "src/files/view-files.html",
11 | })
12 |
13 | export class FilesComponent implements OnInit {
14 | private files;
15 |
16 | constructor(private graph: GraphService,
17 | private toast: ToastComponent,
18 | private changeRef: ChangeDetectorRef) {
19 | }
20 |
21 | public ngOnInit() {
22 | this.toast.show(USER_MESSAGES.get_files);
23 | this.getFiles();
24 | }
25 |
26 | public getFiles() {
27 | this.graph.getRequestPromise(OFFICE_URLS.me_drive_url)
28 | .then((data: any) => {
29 | if (data) {
30 | this.files = data.value;
31 | this.changeRef.detectChanges();
32 | } else {
33 | this.toast.show(USER_MESSAGES.fail_graph_api);
34 | }
35 | }).catch(() => {
36 | this.toast.show(USER_MESSAGES.fail_graph_api);
37 | });
38 | }
39 |
40 | public refreshInfo() {
41 | this.getFiles();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/files/view-files.html:
--------------------------------------------------------------------------------
1 | My Files
2 |
3 |
4 | cached
5 |
6 |
7 |
8 |
9 |
10 | Type
11 | Name
12 | Size
13 |
14 |
15 |
16 |
17 | {{i + 1}}
18 |
19 | File
20 | Folder
21 |
22 |
23 | {{file.name}}
24 |
25 | {{file.size}}
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/groups/groups.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectorRef } from "@angular/core";
2 |
3 | import { GraphService } from "../services/graph.service";
4 | import { ToastComponent } from "../toast/toast.component";
5 | import { USER_MESSAGES } from "../messages/messages";
6 | import { OFFICE_URLS } from "../office/office-urls";
7 |
8 | @Component({
9 | selector: "my-groups",
10 | templateUrl: "src/groups/view-groups.html",
11 | })
12 | export class GroupsComponent implements OnInit {
13 | private groups = [];
14 |
15 | constructor(public graph: GraphService,
16 | public toast: ToastComponent,
17 | private changeRef: ChangeDetectorRef) {
18 | }
19 |
20 | public ngOnInit() {
21 | this.toast.show(USER_MESSAGES.get_groups);
22 | this.getGroups();
23 | }
24 |
25 | private getGroups() {
26 | this.graph.getRequestPromise(OFFICE_URLS.me_groups_url)
27 | .then((data: any) => {
28 | if (data) {
29 | this.groups = data.value;
30 | this.changeRef.detectChanges();
31 | } else {
32 | this.toast.show(USER_MESSAGES.fail_graph_api);
33 | }
34 | })
35 | .catch(() => {
36 | this.toast.show(USER_MESSAGES.fail_graph_api);
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/groups/view-groups.html:
--------------------------------------------------------------------------------
1 | My Groups
2 |
3 |
4 |
5 |
6 | Name
7 | Mail Address
8 | Visibility
9 |
10 |
11 |
12 |
13 |
14 |
15 | {{group.displayName}}
16 |
17 |
18 | {{group.mail}}
19 |
20 | {{group.visibility}}
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from "@angular/core";
2 |
3 | import { ProfileComponent } from "../profile/profile.component";
4 | import { ElectronService } from "../services/electron.service";
5 |
6 | @Component({
7 | selector: "my-home",
8 | templateUrl: "src/home/view-home.html",
9 | })
10 | export class HomeComponent {
11 |
12 | constructor(private electron: ElectronService,
13 | private profile: ProfileComponent) {
14 | }
15 |
16 | public signOut() {
17 | this.electron.logOut();
18 | }
19 |
20 | public refreshInfo() {
21 | this.profile.refreshInfo();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/home/view-home.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | cached
4 |
5 |
6 |
7 |
8 | Information is only read from your Office 365 tenant. This application does not modify any information. Also does not provide any functionality to modify information.
9 |
10 |
--------------------------------------------------------------------------------
/src/login/login.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from "@angular/core";
2 |
3 | import { ElectronService } from "../services/electron.service";
4 |
5 | @Component({
6 | selector: "my-login",
7 | templateUrl: "src/login/view-login.html",
8 | })
9 | export class LoginComponent {
10 | constructor(private electronService: ElectronService) {
11 | }
12 |
13 | public login() {
14 | // Clear local cache
15 | window.localStorage.removeItem("id_token");
16 | window.localStorage.removeItem("access_token");
17 | window.localStorage.removeItem("user");
18 |
19 | this.electronService.logIn();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/login/view-login.html:
--------------------------------------------------------------------------------
1 | Sign-in with Office 365
--------------------------------------------------------------------------------
/src/mails/mails.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectorRef } from "@angular/core";
2 |
3 | import { GraphService } from "../services/graph.service";
4 | import { ToastComponent } from "../toast/toast.component";
5 | import { USER_MESSAGES } from "../messages/messages";
6 | import { OFFICE_URLS } from "../office/office-urls";
7 |
8 | @Component({
9 | selector: "my-mails",
10 | templateUrl: "src/mails/view-mails.html",
11 | })
12 | export class MailsComponent implements OnInit {
13 | private messages = [];
14 |
15 | constructor(private graph: GraphService,
16 | private toast: ToastComponent,
17 | private changeRef: ChangeDetectorRef) {
18 | }
19 |
20 | public ngOnInit() {
21 | this.toast.show(USER_MESSAGES.get_mails);
22 | this.getMails();
23 | }
24 |
25 | private getMails() {
26 | this.graph.getRequestPromise(OFFICE_URLS.me_messages_url)
27 | .then((data: any) => {
28 | if (data) {
29 | this.messages = data.value;
30 | this.changeRef.detectChanges();
31 | } else {
32 | this.toast.show(USER_MESSAGES.fail_graph_api);
33 | }
34 | })
35 | .catch(() => {
36 | this.toast.show(USER_MESSAGES.fail_graph_api);
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/mails/view-mails.html:
--------------------------------------------------------------------------------
1 | My Mails
2 |
3 |
4 |
5 |
6 | Subject
7 | Send Date
8 | Has Attachments
9 | Body Preview
10 |
11 |
12 |
13 |
14 |
15 |
16 | {{message.subject}}
17 |
18 |
19 | {{message.sendDateTime}}
20 |
21 |
22 | {{message.hasAttachments}}
23 |
24 |
25 | {{message.bodyPreview}}
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/messages/messages.ts:
--------------------------------------------------------------------------------
1 | export const USER_MESSAGES = {
2 | fail_graph_api: "Failed to the Microsoft Graph API.",
3 | fail_user_photo: "Failed to get your photo!",
4 | fail_username: "Failed to get your name!",
5 | get_contacts: "Getting your contacts...",
6 | get_files: "Getting your files...",
7 | get_groups: "Getting your groups...",
8 | get_mails: "Getting your mails...",
9 | get_notebooks: "Getting your notebooks...",
10 | get_tasks: "Getting your tasks...",
11 | get_trending: "Getting your trending content...",
12 | get_users: "Getting your organization users...",
13 | no_access_token: "No access token available. Please login.",
14 | not_authenticated: "You are not authenticated!",
15 | redirect_login: "Redirecting to Login. You must first login to Office 365.",
16 | };
17 |
--------------------------------------------------------------------------------
/src/models/runtime-info.model.ts:
--------------------------------------------------------------------------------
1 | export class RuntimeInfoModel{
2 | constructor(public nodeVersion: string, public chromeVersion: string, public electronVersion: string){
3 | }
4 | }
--------------------------------------------------------------------------------
/src/notes/notes.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectorRef } from "@angular/core";
2 |
3 | import { GraphService } from "../services/graph.service";
4 | import { ToastComponent } from "../toast/toast.component";
5 | import { USER_MESSAGES } from "../messages/messages";
6 | import { OFFICE_URLS } from "../office/office-urls";
7 |
8 | @Component({
9 | selector: "my-notes",
10 | templateUrl: "src/notes/view-notes.html",
11 | })
12 | export class NotesComponent implements OnInit {
13 | private books = [];
14 |
15 | constructor(private graph: GraphService,
16 | private toast: ToastComponent,
17 | private changeRef: ChangeDetectorRef) {
18 | }
19 |
20 | public ngOnInit() {
21 | this.toast.show(USER_MESSAGES.get_notebooks);
22 | this.getNotebooks();
23 | }
24 |
25 | private getNotebooks() {
26 | this.graph.getRequestPromise(OFFICE_URLS.me_notebooks_url)
27 | .then((data: any) => {
28 | if (data) {
29 | this.books = data.value;
30 | this.changeRef.detectChanges();
31 | } else {
32 | this.toast.show(USER_MESSAGES.fail_graph_api);
33 | }
34 | })
35 | .catch(() => {
36 | this.toast.show(USER_MESSAGES.fail_graph_api);
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/notes/view-notes.html:
--------------------------------------------------------------------------------
1 | My Notes
2 |
3 |
4 |
5 | Name
6 | Last Modified By
7 | Last Modified Date
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{book.name}}
15 |
16 |
17 | {{book.lastModifiedBy}}
18 |
19 |
20 | {{book.lastModifiedTime}}
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/office/office-urls.ts:
--------------------------------------------------------------------------------
1 | export const OFFICE_URLS = {
2 | all_users_url: "/v1.0/users",
3 | me_contacts_url: "/v1.0/me/contacts",
4 | me_drive_url: "/v1.0/me/drive/root/children",
5 | me_groups_url: "/v1.0/me/memberOf/$/microsoft.graph.group",
6 | me_messages_url: "/v1.0/me/messages",
7 | me_notebooks_url: "/beta/me/notes/notebooks",
8 | me_photo_url: "/v1.0/me/photo/$value",
9 | me_profile_url: "/v1.0/me/",
10 | me_tasks_url: "/beta/me/tasks",
11 | me_trending_around_url: "/beta/me/trendingAround",
12 | };
13 |
--------------------------------------------------------------------------------
/src/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular2-o365-desktop-app",
3 | "version": "0.0.1",
4 | "main": "main.js"
5 | }
--------------------------------------------------------------------------------
/src/profile/profile.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Injectable, ApplicationRef } from "@angular/core";
2 |
3 | import { GraphService } from "../services/graph.service";
4 | import { ToastComponent } from "../toast/toast.component";
5 | import { USER_MESSAGES } from "../messages/messages";
6 | import { OFFICE_URLS } from "../office/office-urls";
7 |
8 | @Component({
9 | selector: "my-profile",
10 | template: "Welcome {{displayName}} ",
11 | })
12 | @Injectable()
13 | export class ProfileComponent implements OnInit {
14 | private displayName: string = "User";
15 | private photo: string = "";
16 |
17 | constructor(private graph: GraphService,
18 | private toast: ToastComponent,
19 | private changeRef: ApplicationRef) {
20 | }
21 |
22 | public ngOnInit() {
23 | this.refreshInfo();
24 | }
25 |
26 | public refreshInfo() {
27 |
28 | this.graph.isUserAuthenticated()
29 | .then(() => {
30 | this.getUserName();
31 | this.getUserPhoto();
32 | })
33 | .catch((reason) => {
34 | this.toast.show(USER_MESSAGES.not_authenticated);
35 | });
36 | }
37 |
38 | private getUserName() {
39 | this.graph.getRequestPromise(OFFICE_URLS.me_profile_url).then((data: any) => {
40 | if (data) {
41 | this.displayName = data.displayName;
42 | this.changeRef.tick();
43 | } else {
44 | this.toast.show(USER_MESSAGES.fail_username);
45 | }
46 | }).catch(() => {
47 | this.toast.show(USER_MESSAGES.fail_graph_api);
48 | });
49 | }
50 |
51 | private getUserPhoto() {
52 | this.graph.getPhotoRequestPromise(OFFICE_URLS.me_photo_url).then((data: any) => {
53 | if (data) {
54 | this.photo = data;
55 | this.changeRef.tick();
56 | } else {
57 | this.toast.show(USER_MESSAGES.fail_user_photo);
58 | }
59 | }).catch(() => {
60 | this.toast.show(USER_MESSAGES.fail_graph_api);
61 | });
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/routes/app.routes.ts:
--------------------------------------------------------------------------------
1 | import { ModuleWithProviders } from "@angular/core";
2 | import { Routes, RouterModule } from "@angular/router";
3 |
4 | import { HomeComponent } from "../home/home.component";
5 | import { LoginComponent } from "../login/login.component";
6 | import { FilesComponent } from "../files/files.component";
7 | import { ContactsComponent } from "../contacts/contacts.component";
8 | import { GroupsComponent } from "../groups/groups.component";
9 | import { MailsComponent } from "../mails/mails.component";
10 | import { NotesComponent } from "../notes/notes.component";
11 | import { TasksComponent } from "../tasks/tasks.component";
12 | import { TrendingComponent } from "../trending/trending.component";
13 | import { UsersComponent } from "../users/users.component";
14 | import { AuthGuardService } from "../auth/auth-guard.service";
15 |
16 | const appRoutes: Routes = [
17 | { component: LoginComponent, path: "login" },
18 | { canActivate: [AuthGuardService], component: HomeComponent, path: "" },
19 | { canActivate: [AuthGuardService], component: HomeComponent, path: "home" },
20 | { canActivate: [AuthGuardService], component: FilesComponent, path: "files" },
21 | { canActivate: [AuthGuardService], component: GroupsComponent, path: "groups" },
22 | { canActivate: [AuthGuardService], component: ContactsComponent, path: "contacts" },
23 | { canActivate: [AuthGuardService], component: MailsComponent, path: "mails" },
24 | { canActivate: [AuthGuardService], component: NotesComponent, path: "notes" },
25 | { canActivate: [AuthGuardService], component: TasksComponent, path: "tasks" },
26 | { canActivate: [AuthGuardService], component: TrendingComponent, path: "trending" },
27 | { canActivate: [AuthGuardService], component: UsersComponent, path: "users" },
28 | { component: LoginComponent, path: "**"},
29 | ];
30 |
31 | export const APP_ROUTING: ModuleWithProviders = RouterModule.forRoot(appRoutes);
32 |
--------------------------------------------------------------------------------
/src/services/electron.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 | import { Http } from "@angular/http";
3 | import { Router } from "@angular/router";
4 | import "rxjs/add/operator/toPromise";
5 | import { remote } from "electron";
6 |
7 | import { RuntimeInfoModel } from "../models/runtime-info.model";
8 | import { AZURE_CONFIG } from "../config/azure-config";
9 |
10 | @Injectable()
11 | export class ElectronService {
12 |
13 | private originalURL;
14 |
15 | constructor(private http: Http, private router: Router) {
16 | }
17 |
18 | public getInfo(): RuntimeInfoModel {
19 | return new RuntimeInfoModel(
20 | remote.process.versions.node,
21 | remote.process.versions.chrome,
22 | remote.process.versions.electron);
23 | }
24 |
25 | public logIn(state = "/") {
26 | this.originalURL = location.href;
27 | let authUrl = "https://login.microsoftonline.com/" + AZURE_CONFIG.tenant +
28 | "/oauth2/authorize?response_type=id_token&client_id=" + AZURE_CONFIG.clientId +
29 | "&redirect_uri=" + encodeURIComponent(AZURE_CONFIG.redirectUri) +
30 | "&state=" + state + "&nonce=SomeNonce";
31 | let BrowserWindow = remote.BrowserWindow;
32 |
33 | let authWindow = new BrowserWindow({
34 | autoHideMenuBar: true,
35 | frame: true,
36 | height: 600,
37 | show: false,
38 | webPreferences: {
39 | nodeIntegration: false,
40 | },
41 | width: 800,
42 | });
43 |
44 | authWindow.webContents.on("did-get-redirect-request", (event: any, oldUrl: string, newUrl: string) => {
45 | authWindow.destroy();
46 | let params: any = this.parseQueryString(newUrl);
47 | let idToken = params.id_token;
48 | if (idToken != null) {
49 | window.localStorage.setItem("id_token", idToken);
50 | window.localStorage.removeItem("access_token");
51 |
52 | this.getNewAccessToken();
53 | } else {
54 | window.localStorage.removeItem("id_token");
55 | window.localStorage.removeItem("access_token");
56 | }
57 | });
58 |
59 | authWindow.on("closed", () => {
60 | authWindow = null;
61 | });
62 |
63 | authWindow.loadURL(authUrl);
64 | authWindow.show();
65 | }
66 |
67 | public getNewAccessToken(state = "/") {
68 | let originalURL = location.href;
69 | let accessTokenUrl = "https://login.microsoftonline.com/" + AZURE_CONFIG.tenant +
70 | "/oauth2/authorize?response_type=token&client_id=" + AZURE_CONFIG.clientId +
71 | "&resource=" + AZURE_CONFIG.endpoints.graphApiUri +
72 | "&redirect_uri=" + encodeURIComponent(AZURE_CONFIG.redirectUri) +
73 | "&prompt=none&state=" + state + "&nonce=SomeNonce";
74 | let BrowserWindow = remote.BrowserWindow;
75 |
76 | let accessWindow = new BrowserWindow({
77 | autoHideMenuBar: true,
78 | frame: false,
79 | height: 600,
80 | show: false,
81 | webPreferences: {
82 | nodeIntegration: false,
83 | },
84 | width: 800,
85 | });
86 |
87 | accessWindow.on("closed", () => {
88 | accessWindow = null;
89 | });
90 |
91 | accessWindow.webContents.on("did-get-redirect-request", (event: any, oldUrl: string, newUrl: string) => {
92 | accessWindow.destroy();
93 | let params: any = this.parseQueryString(newUrl);
94 | let accessToken = params.access_token;
95 | if (accessToken != null) {
96 | window.localStorage.setItem("access_token", accessToken);
97 | // remote.getCurrentWindow().loadURL(originalURL + "index.html");
98 | this.router.navigate(["home"]);
99 | } else {
100 | window.localStorage.removeItem("access_token");
101 | }
102 | });
103 |
104 | accessWindow.loadURL(accessTokenUrl);
105 | }
106 |
107 | public logOut(state = "/") {
108 | window.localStorage.removeItem("id_token");
109 | window.localStorage.removeItem("access_token");
110 | this.router.navigate(["login"]);
111 | }
112 |
113 | public refreshAccessToken(state = "/") {
114 | this.logIn(state); // force login, assume that renewToken.html didn't work which is why dev is calling this.
115 | }
116 |
117 | public getAccessToken() {
118 | return window.localStorage.getItem("access_token");
119 | }
120 |
121 | private parseQueryString(url: string) {
122 | let params = {};
123 | let queryString = "";
124 |
125 | if (url.search("#") !== -1) {
126 | queryString = url.substring(url.search("#") + 1);
127 | } else {
128 | queryString = url.substring(url.indexOf("?") + 1);
129 | }
130 |
131 | let a = queryString.split("&");
132 | for (let i = 0; i < a.length; i++) {
133 | let b = a[i].split("=");
134 | params[decodeURIComponent(b[0])] = decodeURIComponent(b[1] || "");
135 | }
136 |
137 | return params;
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/src/services/graph.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from "@angular/core";
2 | import { Http, Headers } from "@angular/http";
3 | import "rxjs";
4 |
5 | import { AZURE_CONFIG } from "../config/azure-config";
6 | import { USER_MESSAGES } from "../messages/messages";
7 | import { OFFICE_URLS } from "../office/office-urls";
8 |
9 | @Injectable()
10 | export class GraphService {
11 |
12 | private http: Http;
13 | private accessToken: string | null;
14 |
15 | constructor(http: Http) {
16 | this.http = http;
17 | }
18 |
19 | public isUserAuthenticated = (): Promise => {
20 | let p = new Promise((resolve: Function, reject: Function) => {
21 | let token: string | null = localStorage.getItem("access_token");
22 | this.accessToken = token;
23 |
24 | if (this.accessToken === null) {
25 | reject(USER_MESSAGES.no_access_token);
26 | }
27 |
28 | this.getRequestPromise(OFFICE_URLS.me_profile_url)
29 | .then((data: any) => {
30 | if (data) {
31 | resolve();
32 | } else {
33 | reject(USER_MESSAGES.no_access_token);
34 | }
35 | })
36 | .catch((err) => {
37 | reject(USER_MESSAGES.fail_graph_api + " " + err);
38 | });
39 | });
40 |
41 | return p;
42 | }
43 |
44 | public getRequestPromise = (reqUrl: string): Promise => {
45 | let p = new Promise((resolve: Function, reject: Function) => {
46 | let tokenPromise = this.tokenPromise(AZURE_CONFIG.endpoints.graphApiUri);
47 |
48 | tokenPromise.then((token: string) => {
49 | let headers = new Headers();
50 | headers.append("Authorization", "Bearer " + token);
51 |
52 | this.http.get(AZURE_CONFIG.endpoints.graphApiUri + reqUrl, { headers: headers })
53 | .map((res: any) => res.json())
54 | .subscribe(
55 | (res: any) => resolve(res),
56 | (error: any) => {
57 | console.error(error);
58 | reject(error);
59 | });
60 | });
61 | });
62 |
63 | return p;
64 | };
65 |
66 | public getPhotoRequestPromise = (reqUrl: string): Promise => {
67 | let p = new Promise((resolve: Function, reject: Function) => {
68 | let tokenPromise = this.tokenPromise(AZURE_CONFIG.endpoints.graphApiUri);
69 | tokenPromise.then((token: string) => {
70 | let request = new XMLHttpRequest();
71 | request.open("GET", AZURE_CONFIG.endpoints.graphApiUri + reqUrl);
72 | request.setRequestHeader("Authorization", "Bearer " + token);
73 | request.responseType = "blob";
74 | request.onload = function () {
75 | if (request.readyState === 4 && request.status === 200) {
76 | let reader = new FileReader();
77 | reader.onload = () => {
78 | resolve(reader.result);
79 | };
80 |
81 | reader.readAsDataURL(request.response);
82 | } else {
83 | reject(USER_MESSAGES.fail_graph_api);
84 | }
85 | };
86 |
87 | request.send(null);
88 | });
89 | });
90 |
91 | return p;
92 | };
93 |
94 | private tokenPromise = (endpoint: string): Promise => {
95 | let p = new Promise((resolve: Function, reject: Function) => {
96 | let token = window.localStorage.getItem("access_token");
97 | if (token && token !== "undefined") {
98 | resolve(token);
99 | } else {
100 | reject(USER_MESSAGES.no_access_token);
101 | }
102 | });
103 |
104 | return p;
105 | };
106 | }
107 |
--------------------------------------------------------------------------------
/src/tasks/tasks.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectorRef } from "@angular/core";
2 |
3 | import { GraphService } from "../services/graph.service";
4 | import { ToastComponent } from "../toast/toast.component";
5 | import { USER_MESSAGES } from "../messages/messages";
6 | import { OFFICE_URLS } from "../office/office-urls";
7 |
8 | @Component({
9 | selector: "my-tasks",
10 | templateUrl: "src/tasks/view-tasks.html",
11 | })
12 | export class TasksComponent implements OnInit {
13 | private tasks = [];
14 |
15 | constructor(private graph: GraphService,
16 | private toast: ToastComponent,
17 | private changeRef: ChangeDetectorRef) {
18 | }
19 |
20 | public ngOnInit() {
21 | this.toast.show(USER_MESSAGES.get_tasks);
22 | this.getTasks();
23 | }
24 |
25 | private getTasks() {
26 | this.graph.getRequestPromise(OFFICE_URLS.me_tasks_url)
27 | .then((data: any) => {
28 | if (data) {
29 | this.tasks = data.value;
30 | this.changeRef.detectChanges();
31 | } else {
32 | this.toast.show(USER_MESSAGES.fail_graph_api);
33 | }
34 | })
35 | .catch(() => {
36 | this.toast.show(USER_MESSAGES.fail_graph_api);
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/tasks/view-tasks.html:
--------------------------------------------------------------------------------
1 | My Tasks
2 |
3 |
4 |
5 |
6 | Title
7 | Due Date
8 | Created By
9 |
10 |
11 |
12 |
13 |
14 |
15 | {{task.title}}
16 |
17 |
18 | {{task.dueDateTime}}
19 |
20 |
21 | {{task.createdBy}}
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/toast/toast.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Injectable } from "@angular/core";
2 |
3 | @Component({
4 | selector: "my-toast",
5 | template: ``,
9 | })
10 | @Injectable()
11 | export class ToastComponent {
12 |
13 | public show(message: string) {
14 | let notify: any = document.querySelector(".mdl-js-snackbar");
15 | notify.style.backgroundColor = "#FF5722";
16 | notify.MaterialSnackbar.showSnackbar({
17 | message: message,
18 | timeout: 4000,
19 | });
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/trending/trending.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectorRef } from "@angular/core";
2 |
3 | import { GraphService } from "../services/graph.service";
4 | import { ToastComponent } from "../toast/toast.component";
5 | import { USER_MESSAGES } from "../messages/messages";
6 | import { OFFICE_URLS } from "../office/office-urls";
7 |
8 | @Component({
9 | selector: "my-trending",
10 | templateUrl: "src/trending/view-trending.html",
11 | })
12 | export class TrendingComponent implements OnInit {
13 | private trends = [];
14 |
15 | constructor(private graph: GraphService,
16 | private toast: ToastComponent,
17 | private changeRef: ChangeDetectorRef) {
18 | }
19 |
20 | public ngOnInit() {
21 | this.toast.show(USER_MESSAGES.get_trending);
22 | this.getTrending();
23 | }
24 |
25 | private getTrending() {
26 | this.graph.getRequestPromise(OFFICE_URLS.me_trending_around_url)
27 | .then((data: any) => {
28 | if (data) {
29 | this.trends = data.value;
30 | this.changeRef.detectChanges();
31 | } else {
32 | this.toast.show(USER_MESSAGES.fail_graph_api);
33 | }
34 | })
35 | .catch(() => {
36 | this.toast.show(USER_MESSAGES.fail_graph_api);
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/trending/view-trending.html:
--------------------------------------------------------------------------------
1 | My Trending Files
2 |
3 |
4 |
5 |
6 | Name
7 | URL
8 | Last Modified
9 |
10 |
11 |
12 |
13 |
14 |
15 | {{trend.name}}
16 |
17 |
18 | Link
19 |
20 |
21 | {{trend.DateTimeLastModified}}
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/users/users.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ChangeDetectorRef } from "@angular/core";
2 |
3 | import { GraphService } from "../services/graph.service";
4 | import { ToastComponent } from "../toast/toast.component";
5 | import { USER_MESSAGES } from "../messages/messages";
6 | import { OFFICE_URLS } from "../office/office-urls";
7 |
8 | @Component({
9 | selector: "my-users",
10 | templateUrl: "src/users/view-users.html",
11 | })
12 | export class UsersComponent implements OnInit {
13 | private users = [];
14 |
15 | constructor(private graph: GraphService,
16 | private toast: ToastComponent,
17 | private changeRef: ChangeDetectorRef) {
18 | }
19 |
20 | public ngOnInit() {
21 | this.toast.show(USER_MESSAGES.get_users);
22 | this.getUsers();
23 | }
24 |
25 | private getUsers() {
26 | this.graph.getRequestPromise(OFFICE_URLS.all_users_url)
27 | .then((data: any) => {
28 | if (data) {
29 | this.users = data.value;
30 | this.changeRef.detectChanges();
31 | } else {
32 | this.toast.show(USER_MESSAGES.fail_graph_api)
33 | }
34 | })
35 | .catch(() => {
36 | this.toast.show(USER_MESSAGES.fail_graph_api)
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/users/view-users.html:
--------------------------------------------------------------------------------
1 | People
2 |
3 |
4 |
5 |
6 |
7 | Display Name
8 | Email
9 |
10 |
11 |
12 |
13 |
14 |
15 | {{user.displayName}}
16 |
17 |
18 | {{user.mail}}
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "commonjs",
5 | "sourceMap": true,
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "removeComments": false,
9 | "noImplicitAny": false,
10 | "strictNullChecks": false
11 | },
12 | "exclude": [
13 | "node_modules",
14 | "releases"
15 | ],
16 | "filesGlob": [
17 | "./src/**/*.ts",
18 | "!./node_modules/**/*.ts"
19 | ],
20 | "awesomeTypescriptLoaderOptions": {
21 | "resolveGlobs": true,
22 | "forkChecker": true
23 | },
24 | "compileOnSave": false,
25 | "buildOnSave": false,
26 | "atom": { "rewriteTsconfig": false }
27 | }
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | /*
3 | * Possible values:
4 | * - the name of a built-in config
5 | * - the name of an NPM module which has a "main" file that exports a config object
6 | * - a relative path to a JSON file
7 | */
8 | "extends": "tslint:recommended",
9 | "rules": {
10 | /*
11 | * Any rules specified here will override those from the base config we are extending.
12 | */
13 | "curly": true
14 | },
15 | "jsRules": {
16 | /*
17 | * Any rules specified here will override those from the base config we are extending.
18 | */
19 | "curly": true
20 | },
21 | "rulesDirectory": [
22 | /*
23 | * A list of relative or absolute paths to directories that contain custom rules.
24 | * See the Custom Rules documentation below for more details.
25 | */
26 | ]
27 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | var CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;
4 | var DashboardPlugin = require('webpack-dashboard/plugin');
5 |
6 | module.exports = {
7 | devtool: 'source-map',
8 | debug: true,
9 |
10 | entry: {
11 | '@angular': [
12 | 'rxjs',
13 | 'reflect-metadata',
14 | 'zone.js'
15 | ],
16 | 'common': [],
17 | 'app': './src/app/main.ts'
18 | },
19 |
20 | output: {
21 | path: __dirname + '/build/',
22 | publicPath: 'build/',
23 | filename: '[name].js',
24 | sourceMapFilename: '[name].js.map',
25 | chunkFilename: '[id].chunk.js'
26 | },
27 |
28 | resolve: {
29 | extensions: ['','.ts','.js','.json', '.css', '.html']
30 | },
31 |
32 | module: {
33 | loaders: [
34 | {
35 | test: /\.ts$/,
36 | loader: 'ts',
37 | exclude: [ /node_modules/, /releases/ ]
38 | },
39 | {
40 | test: /\.json$/,
41 | loader: 'json'
42 | },
43 | {
44 | test: /\.(css|html)$/,
45 | loader: 'raw'
46 | },
47 | {
48 | test: /\.(png|jpg)$/,
49 | loader: 'url?limit=10000'
50 | }
51 | ]
52 | },
53 | plugins: [
54 | new CommonsChunkPlugin({ names: ['@angular', 'common'], minChunks: Infinity }),
55 | new webpack.ExternalsPlugin('commonjs', ['electron']),
56 | new DashboardPlugin()
57 | ],
58 | externals: [
59 | {
60 | electron : "commonjs electron"
61 | }
62 | ],
63 | target:'electron-renderer'
64 | };
65 |
--------------------------------------------------------------------------------