├── .editorconfig
├── .gitignore
├── README.md
├── firebase.json
├── gulpfile.js
├── package.json
├── src
├── app-shell.js
├── app
│ └── app.ts
├── components
│ ├── About.ts
│ ├── Home.ts
│ ├── header.ts
│ ├── sidenav.ts
│ └── toast.ts
├── css
│ ├── core.scss
│ └── core
│ │ ├── _card.scss
│ │ ├── _colors.scss
│ │ ├── _core.scss
│ │ ├── _dialog.scss
│ │ ├── _header.scss
│ │ ├── _loader.scss
│ │ ├── _main.scss
│ │ ├── _side-nav.scss
│ │ ├── _toast.scss
│ │ └── _z-index.scss
├── images
│ ├── apple-touch-icon.png
│ ├── chrome-splashscreen-icon-384x384.png
│ ├── chrome-touch-icon-192x192.png
│ ├── ic_add_24px.svg
│ ├── ic_info_outline_24px.svg
│ ├── ic_menu_24px.svg
│ ├── icon-128x128.png
│ ├── ms-touch-icon-144x144-precomposed.png
│ └── side-nav-bg@2x.jpg
├── index.html
├── main.ts
├── ng2-service-worker.ts
├── services
│ ├── Auth.ts
│ ├── Backend.ts
│ └── Nav.ts
└── util
│ └── styleloader.js
├── system.config.js
├── tasks
├── build.ts
├── compile.ts
├── copy.ts
└── styles.ts
└── tsconfig.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | charset = utf-8
7 | indent_style = space
8 | indent_size = 2
9 | end_of_line = lf
10 | insert_final_newline = true
11 | trim_trailing_whitespace = true
12 |
13 | [*.md]
14 | insert_final_newline = false
15 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /dist/
2 | /node_modules/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ng2-application-shell
2 |
3 | Port of the Chrome Team's [Application Shell](https://github.com/GoogleChrome/application-shell) architecture to Angular2.
4 |
5 | Work in Progress!
6 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "firebase": "ng2-forum-demo",
3 | "public": "dist",
4 | "ignore": [
5 | "firebase.json",
6 | "**/.*",
7 | "**/node_modules/**"
8 | ],
9 | "rewrites": [{
10 | "source": "**",
11 | "destination": "/index.html"
12 | }]
13 | }
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var tsregister = require('ts-node/register');
3 | var tsconfig = require('./tsconfig.json');
4 |
5 | var config = {
6 | index: 'src/index.html',
7 | css:['src/**/*.scss'],
8 | staticFiles: [
9 | 'src/**/*',
10 | '!src/**/*.ts',
11 | 'node_modules/systemjs/dist/system.js',
12 | 'node_modules/angular2/bundles/angular2-polyfills.js'
13 | ],
14 | dist: './dist',
15 | system: {
16 | configFile: 'system.config.js'
17 | }
18 | };
19 |
20 | require('./tasks/build').build(gulp, config);
21 | require('./tasks/copy').copy(gulp, config);
22 | require('./tasks/styles').compile(gulp, config);
23 | require('./tasks/compile').compile(gulp, config);
24 |
25 |
26 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ng2-application-shell",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "angular2": "^2.0.0-alpha.52",
13 | "es6-promise": "^3.0.2",
14 | "es6-shim": "^0.33.13",
15 | "firebase": "^2.3.2",
16 | "reflect-metadata": "^0.1.2",
17 | "rxjs": "^5.0.0-alpha.14",
18 | "zone.js": "^0.5.8"
19 | },
20 | "devDependencies": {
21 | "gulp": "^3.9.0",
22 | "gulp-inline": "^0.1.0",
23 | "gulp-minify-css": "^1.2.2",
24 | "gulp-sass": "^2.1.1",
25 | "gulp-util": "^3.0.7",
26 | "systemjs": "^0.19.6",
27 | "systemjs-builder": "^0.14.11",
28 | "ts-node": "^0.5.4",
29 | "typescript": "^1.7.3",
30 | "typescript-node": "^0.1.3"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/app-shell.js:
--------------------------------------------------------------------------------
1 | //TODO: service worker stuffs
2 |
3 | System.config({
4 | map: {
5 | app: 'src/main.ts'
6 | }
7 | });
8 |
9 | System.import('app').catch(function(err){
10 | console.log(err);
11 | });
--------------------------------------------------------------------------------
/src/app/app.ts:
--------------------------------------------------------------------------------
1 | //ng2 deps
2 | import {Component} from 'angular2/core';
3 | import {Router, Route, RouteConfig, AsyncRoute, ROUTER_DIRECTIVES} from 'angular2/router';
4 |
5 | //app services
6 | import {AuthService} from '../services/Auth';
7 | import {Nav} from '../services/Nav';
8 | import {Backend, BackendConfig} from '../services/Backend';
9 |
10 | //generic components
11 | import {AppHeader} from '../components/header';
12 | import {SideNav} from '../components/sidenav';
13 | import {Toast} from '../components/toast';
14 |
15 | //routable components
16 | import {Home} from '../components/Home';
17 | import {About} from '../components/About';
18 |
19 | @Component({
20 | selector: 'app',
21 | providers: [Nav],
22 | directives: [AppHeader, SideNav, Toast, ROUTER_DIRECTIVES],
23 | template: `
24 |
25 |
26 |
27 |
28 |
29 | `,
30 | })
31 | @RouteConfig([
32 | new Route({ name: 'Home', component: Home, path: '/home', useAsDefault: true }),
33 | new Route({ name: 'About', component: About, path: '/about' })
34 | ])
35 | export class App {
36 | constructor(public backend: Backend, public nav: Nav) { }
37 |
38 | authenticate() {
39 | this.backend.authenticate()
40 | }
41 | showToast(message) { }
42 | }
--------------------------------------------------------------------------------
/src/components/About.ts:
--------------------------------------------------------------------------------
1 | import {Component} from 'angular2/core';
2 | import {Nav} from '../services/Nav';
3 |
4 | @Component({
5 | selector: 'about',
6 | template: `
7 |
8 |
About
9 |
10 |
11 | `
12 | })
13 | export class About {
14 | constructor(){
15 |
16 | }
17 | }
--------------------------------------------------------------------------------
/src/components/Home.ts:
--------------------------------------------------------------------------------
1 | import {Component} from 'angular2/core';
2 | import {Nav} from '../services/Nav';
3 |
4 | @Component({
5 | selector: 'home',
6 | template: `
7 |
8 |
Hello World
9 |
10 |
11 |
12 |
13 |
Hello World
14 |
15 |
16 |
17 |
18 |
Hello World
19 |
20 |
21 |
22 |
23 |
Hello World
24 |
25 |
26 |
27 |
28 |
Hello World
29 |
30 |
31 |
32 |
33 |
Hello World
34 |
35 |
36 |
37 |
38 |
Hello World
39 |
40 |
41 |
42 |
43 |
Hello World
44 |
45 |
46 |
47 |
48 |
Hello World
49 |
50 |
51 |
52 |
53 |
Hello World
54 |
55 |
56 |
57 |
58 |
Hello World
59 |
60 |
61 |
62 |
63 |
Hello World
64 |
65 |
66 |
67 |
68 | `
69 | })
70 | export class Home {
71 | constructor(private nav: Nav){
72 |
73 | }
74 | openSideNav(){
75 | this.nav.open()
76 | }
77 | }
--------------------------------------------------------------------------------
/src/components/header.ts:
--------------------------------------------------------------------------------
1 | import {Component} from 'angular2/core';
2 | import {Nav} from '../services/Nav';
3 |
4 | @Component({
5 | selector: 'app-header',
6 | template: `
7 |
8 |
9 |
10 | `
11 | })
12 | export class AppHeader {
13 | constructor(private nav: Nav){
14 |
15 | }
16 | openSideNav(){
17 | this.nav.open()
18 | }
19 | }
--------------------------------------------------------------------------------
/src/components/sidenav.ts:
--------------------------------------------------------------------------------
1 | import {Component, Directive, Output, Input, EventEmitter, Renderer, ElementRef} from 'angular2/core';
2 | import {App} from '../app/app';
3 | import {Nav} from '../services/Nav';
4 | import {ReplaySubject} from 'rxjs/subject/ReplaySubject';
5 | import {ROUTER_DIRECTIVES} from 'angular2/router';
6 |
7 | @Directive({
8 | selector: '[side-nav-content]',
9 | host: {
10 | '(transitionend)': 'cleanup()'
11 | }
12 | })
13 | class SideNavContent {
14 | @Input() open: boolean;
15 |
16 | constructor(private el: ElementRef, private renderer: Renderer){
17 |
18 | }
19 | ngOnChanges(changes:any){
20 | if(changes.open.currentValue && changes.open.previousValue !== undefined){
21 | this.setOpen()
22 | }
23 | else{
24 | this.setClosed()
25 | }
26 | }
27 | setOpen(){
28 | this.renderer.setElementClass(this.el, 'side-nav__content--animatable', true);
29 | this.renderer.setElementStyle(this.el, 'transform', 'translateX(0px)');
30 | }
31 | setClosed(){
32 | this.renderer.setElementClass(this.el, 'side-nav__content--animatable', true);
33 | this.renderer.setElementStyle(this.el, 'transform', 'translateX(-102%)');
34 | }
35 | cleanup(){
36 | this.renderer.setElementClass(this.el, 'side-nav__content--animatable', false);
37 | }
38 |
39 | }
40 |
41 | @Component({
42 | selector: 'side-nav',
43 | directives: [SideNavContent, ROUTER_DIRECTIVES],
44 | template: `
45 |
46 |
47 |
50 |
51 |
52 |
Index
53 |
About
54 |
URL 2
55 |
56 |
57 |
Version @VERSION@
58 |
59 |
60 | `
61 | })
62 | export class SideNav {
63 | @Output() toggle: EventEmitter = new EventEmitter();
64 | isVisible: ReplaySubject;
65 | constructor(public nav: Nav){
66 | this.isVisible = nav.isOpen;
67 | }
68 | }
--------------------------------------------------------------------------------
/src/components/toast.ts:
--------------------------------------------------------------------------------
1 | import {Component} from 'angular2/core';
2 |
3 | @Component({
4 | selector: 'toast-view',
5 | template: `
6 |
7 | `
8 | })
9 | export class Toast {
10 | visible = false;
11 | show(message){
12 | this.message = message;
13 | this.visible = true;
14 | setTimeout(() => {
15 | this.visible = false;
16 | },3000);
17 | }
18 | }
--------------------------------------------------------------------------------
/src/css/core.scss:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Copyright 2015 Google Inc. All rights reserved.
4 | *
5 | * Licensed under the Apache License, Version 2.0 (the "License");
6 | * you may not use this file except in compliance with the License.
7 | * You may obtain a copy of the License at
8 | *
9 | * http://www.apache.org/licenses/LICENSE-2.0
10 | *
11 | * Unless required by applicable law or agreed to in writing, software
12 | * distributed under the License is distributed on an "AS IS" BASIS,
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | * See the License for the specific language governing permissions and
15 | * limitations under the License.
16 | */
17 |
18 | @import 'core/_colors';
19 | @import 'core/_card';
20 | @import 'core/_core';
21 | @import 'core/_dialog';
22 | @import 'core/_side-nav';
23 | @import 'core/_main';
24 | @import 'core/_header';
25 | @import 'core/_loader';
26 | @import 'core/_z-index';
27 | @import 'core/_toast';
28 |
--------------------------------------------------------------------------------
/src/css/core/_card.scss:
--------------------------------------------------------------------------------
1 | .card {
2 | padding: 16px;
3 | position: relative;
4 | box-sizing: border-box;
5 | background: #fff;
6 | border-radius: 2px;
7 | margin: 16px;
8 | box-shadow: 0 2px 2px 0 rgba(0,0,0,.14),
9 | 0 3px 1px -2px rgba(0,0,0,.2),
10 | 0 1px 5px 0 rgba(0,0,0,.12);
11 | }
12 |
--------------------------------------------------------------------------------
/src/css/core/_colors.scss:
--------------------------------------------------------------------------------
1 | $primary: #F44336;
2 | $primaryLight: #FFCDD2;
3 | $primaryDark: #D32F2F;
4 | $secondary: #448AFF;
5 | $secondaryDark: #303F9F;
6 | $toast: #404040;
7 | $background: #FAFAFA;
--------------------------------------------------------------------------------
/src/css/core/_core.scss:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | html, body {
6 | padding: 0;
7 | margin: 0;
8 | height: 100%;
9 | width: 100%;
10 | font-family: 'Helvetica', 'Verdana', sans-serif;
11 | font-weight: 400;
12 | // Not implemented yet but is a nice solution for async loading fonts
13 | font-display: optional;
14 | color: #444;
15 | -webkit-font-smoothing: antialiased;
16 | -moz-osx-font-smoothing: grayscale;
17 | }
18 |
19 | html {
20 | overflow: hidden;
21 | }
22 |
23 | body {
24 | display: flex;
25 | flex-direction: column;
26 | flex-wrap: nowrap;
27 | justify-content: flex-start;
28 | align-items: stretch;
29 | align-content: stretch;
30 | background: #ececec;
31 | }
32 |
33 | body:after {
34 | content: '';
35 | position: fixed;
36 | top: 0;
37 | left: 0;
38 | width: 100%;
39 | height: 100%;
40 | pointer-events: none;
41 | background: $background;
42 | opacity: 0;
43 | will-change: opacity;
44 | transition: opacity 0.333s cubic-bezier(0,0,0.21,1) 0.4s;
45 | }
46 |
47 | h1, h2, h3, h4, h5, h6 {
48 | font-family: 'Roboto', 'Helvetica', 'Verdana', sans-serif;
49 | }
50 |
51 | .app-deeplink:after {
52 | opacity: 1;
53 | pointer-events: auto;
54 | }
55 |
56 | a {
57 | color: $secondary;
58 | }
59 |
60 | .is-hidden {
61 | display: none;
62 | }
63 |
64 | button::-moz-focus-inner {
65 | border: 0;
66 | }
67 |
68 | @media(min-width: 600px) {
69 | .view-underpanel {
70 | top: 0;
71 | right: 0;
72 | position: fixed;
73 | width: 400px;
74 | height: 100%;
75 | overflow: hidden;
76 | pointer-events: none;
77 | }
78 |
79 | .view-underpanel__block {
80 | position: absolute;
81 | top: 0;
82 | right: 0;
83 | width: 360px;
84 | height: 100%;
85 | background: $background;
86 | box-shadow: 0 0 14px rgba(0,0,0,.24),
87 | 0 14px 28px rgba(0,0,0,.48);
88 | transform: translateX(105%);
89 | transition: transform 0.233s cubic-bezier(0,0,0.21,1) 0.04s,
90 | opacity 0.213s cubic-bezier(0,0,0.21,1) 0.04s;
91 | will-change: transform;
92 | opacity: 0;
93 | }
94 |
95 | .view-underpanel__block:after {
96 | content: '';
97 | height: 144px;
98 | width: 100%;
99 | display: block;
100 | background: $primaryDark;
101 | position: absolute;
102 | top: 0;
103 | left: 0;
104 | }
105 |
106 | .view-underpanel--visible .view-underpanel__block,
107 | .view-underpanel--locked .view-underpanel__block {
108 | opacity: 1;
109 | transform: translateX(0);
110 | }
111 | }
112 |
113 | @media(min-width: 960px) {
114 | .view-underpanel {
115 | margin-top: 56px;
116 | left: 46%;
117 | right: auto;
118 | position: fixed;
119 | width: 520px;
120 | height: 100%;
121 | overflow: hidden;
122 | pointer-events: none;
123 | }
124 |
125 | .view-underpanel__block {
126 | opacity: 0.0001;
127 | width: 504px;
128 | left: 8px;
129 | transform: translateY(50px);
130 | transition: transform 0.233s cubic-bezier(0,0,0.21,1) 0.04s,
131 | opacity 0.213s cubic-bezier(0,0,0.21,1) 0.04s;
132 |
133 | box-shadow: 0 0 6px rgba(0,0,0,.16),
134 | 0 6px 12px rgba(0,0,0,.32);
135 | }
136 |
137 | .view-underpanel__block:after {
138 | height: 288px;
139 | }
140 |
141 | .view-underpanel--visible .view-underpanel__block,
142 | .view-underpanel--locked .view-underpanel__block {
143 | opacity: 1;
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/src/css/core/_dialog.scss:
--------------------------------------------------------------------------------
1 | .dialog-view {
2 | background: rgba(0,0,0,0.57);
3 | position: fixed;
4 | left: 0;
5 | top: 0;
6 | width: 100%;
7 | height: 100%;
8 | opacity: 0;
9 | pointer-events: none;
10 | will-change: opacity;
11 | transition: opacity 0.333s cubic-bezier(0,0,0.21,1);
12 | }
13 |
14 | .dialog-view--visible {
15 | opacity: 1;
16 | pointer-events: auto;
17 | }
18 |
19 | .dialog-view__panel {
20 | background: #FFF;
21 | border-radius: 2px;
22 | box-shadow: 0 0 14px rgba(0,0,0,.24),
23 | 0 14px 28px rgba(0,0,0,.48);
24 | min-width: 280px;
25 | position: absolute;
26 | left: 50%;
27 | top: 50%;
28 | transform: translate(-50%, -50%) translateY(30px);
29 | transition: transform 0.333s cubic-bezier(0,0,0.21,1) 0.05s;
30 | }
31 |
32 | .dialog-view--visible .dialog-view__panel {
33 | transform: translate(-50%, -50%);
34 | }
35 |
36 | .dialog-view__panel-header {
37 | padding: 24px;
38 | }
39 |
40 | .dialog-view__panel-footer {
41 | padding: 8px;
42 | text-align: right;
43 | }
44 |
45 | .dialog-view__panel-button {
46 | height: 36px;
47 | line-height: 1;
48 | text-transform: uppercase;
49 | color: $secondary;
50 | font-size: 15px;
51 | font-weight: 500;
52 | background: none;
53 | border: none;
54 | padding: 0 8px;
55 | }
56 |
57 | .dialog-view__panel-title {
58 | line-height: 32px;
59 | font-size: 24px;
60 | color: #000;
61 | opacity: 0.87;
62 | font-weight: 500;
63 | margin: 0;
64 | }
65 |
66 | .dialog-view__panel-message {
67 | font-size: 16px;
68 | line-height: 24px;
69 | margin: 20px 0 0 0;
70 | opacity: 0.54;
71 | }
72 |
--------------------------------------------------------------------------------
/src/css/core/_header.scss:
--------------------------------------------------------------------------------
1 | .header {
2 | width: 100%;
3 | height: 56px;
4 | color: #FFF;
5 | background: $primary;
6 | position: fixed;
7 | font-size: 20px;
8 | box-shadow: 0 4px 5px 0 rgba(0, 0, 0, 0.14),
9 | 0 2px 9px 1px rgba(0, 0, 0, 0.12),
10 | 0 4px 2px -2px rgba(0, 0, 0, 0.2);
11 | padding: 16px 16px 0 16px;
12 | will-change: transform;
13 |
14 | display: flex;
15 | flex-direction: row;
16 | flex-wrap: nowrap;
17 | justify-content: flex-start;
18 | align-items: stretch;
19 | align-content: center;
20 | transition: transform 0.233s cubic-bezier(0,0,0.21,1) 0.1s;
21 | }
22 |
23 | .header--collapsed {
24 | transition: transform 0.233s cubic-bezier(0,0,0.21,1) 0.13s;
25 | transform: translateY(-56px);
26 | }
27 |
28 | .header__menu {
29 | // background: url(/images/ic_menu_24px.svg) center center no-repeat;
30 | width: 24px;
31 | height: 24px;
32 | margin-right: 16px;
33 | text-indent: -30000px;
34 | overflow: hidden;
35 | opacity: 0.54;
36 | transition: opacity 0.333s cubic-bezier(0,0,0.21,1);
37 | border: none;
38 | outline: none;
39 | }
40 |
41 | .header__menu:focus,
42 | .header__menu:hover {
43 | opacity: 1;
44 | border: 1px solid white;
45 | }
46 |
47 | .header__title {
48 | font-weight: 400;
49 | font-size: 20px;
50 | margin: 0;
51 | flex: 1;
52 | }
--------------------------------------------------------------------------------
/src/css/core/_loader.scss:
--------------------------------------------------------------------------------
1 | .loader {
2 | left: 50%;
3 | top: 50%;
4 | position: fixed;
5 | transform: translate(-50%, -50%);
6 |
7 | #spinner {
8 | box-sizing: border-box;
9 | stroke: #673AB7;
10 | stroke-width: 3px;
11 | transform-origin: 50%;
12 |
13 | animation: line 1.6s cubic-bezier(0.4, 0.0, 0.2, 1) infinite,
14 | rotate 1.6s linear infinite;
15 | }
16 |
17 | @keyframes rotate {
18 |
19 | from {
20 | transform: rotate(0)
21 | }
22 |
23 | to {
24 | transform: rotate(450deg);
25 | }
26 | }
27 |
28 | @keyframes line {
29 | 0% {
30 | stroke-dasharray: 2, 85.964;
31 | transform: rotate(0);
32 | }
33 |
34 | 50% {
35 | stroke-dasharray: 65.973, 21.9911;
36 | stroke-dashoffset: 0;
37 | }
38 |
39 | 100% {
40 | stroke-dasharray: 2, 85.964;
41 | stroke-dashoffset: -65.973;
42 | transform: rotate(90deg);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/css/core/_main.scss:
--------------------------------------------------------------------------------
1 | .main {
2 | padding-top: 72px;
3 | flex: 1;
4 | overflow-x: hidden;
5 | overflow-y: auto;
6 | -webkit-overflow-scrolling: touch;
7 | }
8 |
9 | app {
10 | overflow-y: auto;
11 | }
12 |
13 | .superfail .main {
14 | background: url(/images/superfail.svg) center center no-repeat;
15 | }
16 |
17 | .empty-set-cta {
18 | color: $primary;
19 | font-size: 20px;
20 | position: fixed;
21 | left: 0;
22 | top: 0;
23 | width: 100%;
24 | height: 100%;
25 | background: $background;
26 | opacity: 0;
27 | will-change: opacity;
28 | pointer-events: none;
29 | transition: opacity 0.333s cubic-bezier(0,0,0.21,1);
30 |
31 | display: flex;
32 | flex-direction: column;
33 | flex-wrap: nowrap;
34 | justify-content: center;
35 | align-content: center;
36 | align-items: center;
37 | }
38 |
39 | .empty-set-cta--visible {
40 | opacity: 1;
41 | }
42 |
--------------------------------------------------------------------------------
/src/css/core/_side-nav.scss:
--------------------------------------------------------------------------------
1 | .side-nav {
2 | width: 100%;
3 | height: 100%;
4 | position: fixed;
5 | pointer-events: none;
6 | top: 0;
7 | left: 0;
8 | overflow: hidden;
9 | }
10 |
11 | .side-nav:before {
12 | content: '';
13 | width: 100%;
14 | height: 100%;
15 | background: #000;
16 | opacity: 0;
17 | display: block;
18 | position: absolute;
19 | will-change: opacity;
20 | transition: opacity 0.233s cubic-bezier(0,0,0.21,1);
21 | }
22 |
23 | .side-nav--visible {
24 | pointer-events: auto;
25 | }
26 |
27 | .side-nav--visible:before {
28 | opacity: 0.7;
29 | }
30 |
31 | .side-nav__content {
32 | background: #FAFAFA;
33 | width: 80%;
34 | max-width: 304px;
35 | height: 100%;
36 | overflow: hidden;
37 | position: relative;
38 |
39 | box-shadow: 0 0 4px rgba(0, 0, 0, .14),
40 | 0 4px 8px rgba(0, 0, 0, .28);
41 |
42 | will-change: transform;
43 | transform: translateX(-102%);
44 |
45 | &.side-nav--visible {
46 | transform: translateX(0px);
47 | }
48 | }
49 |
50 |
51 | .side-nav__content--animatable {
52 | transition: transform 0.233s cubic-bezier(0,0,0.21,1);
53 | }
54 |
55 | .side-nav__header {
56 | background: $primary url(/images/side-nav-bg@2x.jpg);
57 | background-size: cover;
58 | width: 100%;
59 | height: 171px;
60 | position: relative;
61 | }
62 |
63 | .side-nav__title {
64 | font-size: 16px;
65 | line-height: 1;
66 | color: #FFF;
67 | position: absolute;
68 | bottom: 8px;
69 | left: 16px;
70 | height: 16px;
71 | font-weight: 500;
72 | }
73 |
74 | .side-nav__body {
75 | padding-top: 8px;
76 | }
77 |
78 | .side-nav__version {
79 | position: absolute;
80 | bottom: 16px;
81 | left: 16px;
82 | font-size: 14px;
83 | opacity: 0.54;
84 | }
85 |
86 | .side-nav__delete-memos,
87 | .side-nav__delete-all,
88 | .side-nav__blog-post,
89 | .side-nav__file-bug-report {
90 | font-family: 'Roboto';
91 | font-size: 14px;
92 | outline: none;
93 | height: 48px;
94 | padding-left: 72px;
95 | width: 100%;
96 | text-align: left;
97 | display: block;
98 | border: none;
99 | background: url(/images/ic_delete_24px.svg) 16px 12px no-repeat;
100 | color: rgba(0,0,0,0.87);
101 | cursor: pointer;
102 | }
103 |
104 | .side-nav__delete-all {
105 | background-image: url(/images/ic_restore_24px.svg);
106 | }
107 |
108 | .side-nav__blog-post {
109 | background-image: url(/images/ic_info_outline_24px.svg);
110 | line-height: 48px;
111 | text-decoration: none;
112 | }
113 |
114 | .side-nav__blog-post:focus {
115 | background-color: #eee;
116 | outline: 0;
117 | }
118 |
119 | .side-nav__file-bug-report {
120 | background-image: url(/images/ic_feedback_24px.svg);
121 | line-height: 48px;
122 | text-decoration: none;
123 | }
124 |
125 | .side-nav__blog-post.active {
126 | font-weight: bold;
127 | background-color: rgba(0,0,0,.05);
128 | }
129 |
--------------------------------------------------------------------------------
/src/css/core/_toast.scss:
--------------------------------------------------------------------------------
1 | .toast-view {
2 | background-color: $toast;
3 | border-radius: 3px;
4 | box-shadow: 0 0 2px rgba(0,0,0,.12),
5 | 0 2px 4px rgba(0,0,0,.24);
6 | color: #fff;
7 | line-height: 20px;
8 | margin-top: 8px;
9 | padding: 16px;
10 | transition: opacity 200ms,
11 | transform 300ms cubic-bezier(0.165,0.840,0.440,1.000);
12 | white-space: nowrap;
13 | opacity: 0;
14 | transform: translateY(20px);
15 | will-change: transform;
16 | position: fixed;
17 | left: 16px;
18 | bottom: 16px;
19 | }
20 |
21 | .toast-view--visible {
22 | opacity: 1;
23 | transform: translateY(0);
24 | }
25 |
--------------------------------------------------------------------------------
/src/css/core/_z-index.scss:
--------------------------------------------------------------------------------
1 | .header {
2 | z-index: 2;
3 | }
4 |
5 | .main {
6 | z-index: 0;
7 | }
8 |
9 | .new-appshelling-btn {
10 | z-index: 3;
11 | }
12 |
13 | .view-underpanel {
14 | z-index: 4;
15 | }
16 |
17 | .details-view {
18 | z-index: 6;
19 | }
20 |
21 | .edit-view__circular-reveal-container {
22 | z-index: 10;
23 | }
24 |
25 | .appshell-view {
26 | z-index: 5;
27 | }
28 |
29 | .details-view__box-reveal {
30 | z-index: 5;
31 | }
32 |
33 | .details-view__panel {
34 | z-index: 6;
35 | }
36 |
37 | .details-view__playback {
38 | z-index: 1;
39 | }
40 |
41 | .edit-view__panel {
42 | z-index: 7;
43 | }
44 |
45 | body:after {
46 | z-index: 100;
47 | }
48 |
49 | .side-nav {
50 | z-index: 200;
51 | }
52 |
53 | .dialog-view {
54 | z-index: 250;
55 | }
56 |
57 | .toast-view {
58 | z-index: 300;
59 | }
60 |
61 | .loader {
62 | z-index: 400;
63 | }
64 |
--------------------------------------------------------------------------------
/src/images/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robwormald/ng2-application-shell/f1e051c5bccb5a58a1f10f07d2cc67a0eff028d3/src/images/apple-touch-icon.png
--------------------------------------------------------------------------------
/src/images/chrome-splashscreen-icon-384x384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robwormald/ng2-application-shell/f1e051c5bccb5a58a1f10f07d2cc67a0eff028d3/src/images/chrome-splashscreen-icon-384x384.png
--------------------------------------------------------------------------------
/src/images/chrome-touch-icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robwormald/ng2-application-shell/f1e051c5bccb5a58a1f10f07d2cc67a0eff028d3/src/images/chrome-touch-icon-192x192.png
--------------------------------------------------------------------------------
/src/images/ic_add_24px.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/images/ic_info_outline_24px.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/images/ic_menu_24px.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/images/icon-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robwormald/ng2-application-shell/f1e051c5bccb5a58a1f10f07d2cc67a0eff028d3/src/images/icon-128x128.png
--------------------------------------------------------------------------------
/src/images/ms-touch-icon-144x144-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robwormald/ng2-application-shell/f1e051c5bccb5a58a1f10f07d2cc67a0eff028d3/src/images/ms-touch-icon-144x144-precomposed.png
--------------------------------------------------------------------------------
/src/images/side-nav-bg@2x.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robwormald/ng2-application-shell/f1e051c5bccb5a58a1f10f07d2cc67a0eff028d3/src/images/side-nav-bg@2x.jpg
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NG2 Application Shell
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
25 |
26 |
27 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import {bootstrap} from 'angular2/platform/browser';
2 | import {platform, provide} from 'angular2/core';
3 | import {ROUTER_PROVIDERS} from 'angular2/router';
4 |
5 | import {AuthService} from './services/Auth';
6 | import {Backend, BackendConfig} from './services/Backend';
7 | import {Nav} from './services/Nav';
8 |
9 | const FIREBASE_URL = 'https://ng2-forum-demo.firebaseio.com';
10 |
11 | import {App} from './app/app';
12 |
13 | bootstrap(App,[
14 | ROUTER_PROVIDERS,
15 | AuthService,
16 | Backend,
17 | provide(BackendConfig, {useValue: {url: FIREBASE_URL }})
18 | ]);
19 |
20 |
--------------------------------------------------------------------------------
/src/ng2-service-worker.ts:
--------------------------------------------------------------------------------
1 | import 'reflect-metadata';
2 | import {Injector, Injectable, provide} from 'angular2/src/core/di';
3 |
4 | class SWLogger {
5 | log(...args: any[]) {
6 | console.log.apply(console, ['[service-worker]'].concat(args));
7 | }
8 | }
9 |
10 |
11 | const SW_EVENTS = {
12 | INSTALL: 'install',
13 | FETCH: 'fetch',
14 | ACTIVATE: 'activate'
15 | }
16 |
17 | class SWContext {
18 | addEventListener(event, handler) { }
19 | }
20 |
21 |
22 | @Injectable()
23 | class NgServiceWorker {
24 | constructor(serviceWorkerContext: SWContext, private _logger: SWLogger) {
25 | serviceWorkerContext.addEventListener(SW_EVENTS.ACTIVATE, (ev) => this.onActivate(ev));
26 | serviceWorkerContext.addEventListener(SW_EVENTS.INSTALL, (ev) => this.onInstall(ev));
27 | serviceWorkerContext.addEventListener(SW_EVENTS.FETCH, (ev) => this.onFetch(ev));
28 | }
29 |
30 | bootstrap() {
31 | this._logger.log('bootstrap')
32 | }
33 |
34 | onInstall(installEvent) {
35 | installEvent.waitUntil(this._onInstall());
36 | }
37 |
38 | private _onInstall() {
39 | return Promise.resolve('ready');
40 | }
41 |
42 | onActivate(activateEvent) {
43 | this._logger.log('activate', activateEvent);
44 | }
45 | onFetch(fetchEvent) {
46 | this._logger.log('fetch', fetchEvent.request.url);
47 | }
48 | }
49 |
50 |
51 |
52 | Injector
53 | .resolveAndCreate([
54 | SWLogger,
55 | provide(SWContext, { useValue: self }),
56 | NgServiceWorker, ])
57 | .get(NgServiceWorker)
58 | .bootstrap();
--------------------------------------------------------------------------------
/src/services/Auth.ts:
--------------------------------------------------------------------------------
1 | import {Injectable, Inject} from 'angular2/core';
2 | import {Backend} from './Backend';
3 |
4 | @Injectable()
5 | export class AuthService {
6 | constructor(private backend: Backend){
7 |
8 | }
9 | authenticate(){
10 | this.backend.authenticate()
11 | }
12 | }
--------------------------------------------------------------------------------
/src/services/Backend.ts:
--------------------------------------------------------------------------------
1 | import Firebase from 'firebase';
2 | import {Injectable} from 'angular2/core';
3 | import {Observable} from 'rxjs/Observable';
4 | import {ReplaySubject} from 'rxjs/subject/ReplaySubject';
5 |
6 | export class BackendConfig {
7 | url: string;
8 | }
9 |
10 |
11 | @Injectable()
12 | export class Backend {
13 | authState: ReplaySubject = new ReplaySubject(1);
14 | ref: Firebase;
15 | constructor(config: BackendConfig){
16 | this.ref = new Firebase(config.url);
17 | }
18 | authenticate(){
19 | let authRequest = new Observable(obs => {
20 |
21 | this.ref.authWithOAuthPopup('github', (err, res) => {
22 | if(err){
23 | obs.error(err);
24 | }
25 | else {
26 | obs.next(res);
27 | }
28 | })
29 |
30 | });
31 |
32 | authRequest.subscribe(this.authState);
33 |
34 | }
35 | }
--------------------------------------------------------------------------------
/src/services/Nav.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from 'angular2/core';
2 | import {ReplaySubject} from 'rxjs/subject/ReplaySubject';
3 |
4 | @Injectable()
5 | export class Nav {
6 | isOpen: ReplaySubject = new ReplaySubject(1);
7 |
8 | open(){
9 | this.isOpen.next(true);
10 | }
11 | close(){
12 | this.isOpen.next(false);
13 | }
14 | toggle(){
15 | this.isOpen.map(open => !open).take(1).subscribe(this.isOpen);
16 | }
17 | }
--------------------------------------------------------------------------------
/src/util/styleloader.js:
--------------------------------------------------------------------------------
1 | var remoteStyles = ['https://fonts.googleapis.com/css?family=Roboto:400,300,700,500,400italic']
2 |
3 | window.requestAnimationFrame(function() {
4 | var elementToInsertLinkBefore =
5 | document.getElementsByTagName('script')[0];
6 | for (var i = 0; i < remoteStyles.length; i++) {
7 | var linkElement = document.createElement('link');
8 | linkElement.rel = 'stylesheet';
9 | linkElement.media = 'all';
10 | linkElement.href = remoteStyles[i];
11 |
12 | elementToInsertLinkBefore.parentNode.insertBefore(linkElement,
13 | elementToInsertLinkBefore);
14 | }
15 | });
--------------------------------------------------------------------------------
/system.config.js:
--------------------------------------------------------------------------------
1 | System.config({
2 | transpiler: 'typescript',
3 | typescriptOptions: {
4 | emitDecoratorMetadata: true
5 | },
6 | map: {
7 | typescript: 'node_modules/typescript/lib/typescript.js',
8 | app: './src',
9 | angular2: 'node_modules/angular2',
10 | rxjs: 'node_modules/rxjs',
11 | 'reflect-metadata': 'node_modules/reflect-metadata/temp/Reflect.js',
12 | 'zone.js': 'node_modules/zone.js/dist/zone.js',
13 | firebase: 'node_modules/firebase/lib/firebase-web.js'
14 | },
15 | packages: {
16 | app: {
17 | defaultExtension: 'ts',
18 | main: 'main.ts'
19 | },
20 | angular2: {
21 | defaultExtension: 'js',
22 | },
23 | rxjs: {
24 | defaultExtension: 'js'
25 | },
26 | 'reflect-metadata': {
27 | format: 'global'
28 | }
29 | }
30 | });
--------------------------------------------------------------------------------
/tasks/build.ts:
--------------------------------------------------------------------------------
1 | declare var require;
2 |
3 | const sass = require('gulp-sass');
4 | const inline = require('gulp-inline');
5 | const minifyCSS = require('gulp-minify-css');
6 |
7 | export const build = (gulp, config) => {
8 |
9 | gulp.task('default', ['compile:sass','compile:app','copy:dev'], () => {
10 |
11 | gulp.src(config.index)
12 | .pipe(inline({
13 | base: 'dist',
14 | css: minifyCSS,
15 | disabledTypes: ['img', 'js'], // Only inline css files
16 | }))
17 | .pipe(gulp.dest('dist/'));
18 |
19 | });
20 |
21 |
22 | }
--------------------------------------------------------------------------------
/tasks/compile.ts:
--------------------------------------------------------------------------------
1 | declare var require;
2 |
3 | var Builder = require('systemjs-builder');
4 | var util = require('gulp-util');
5 |
6 | export const compile = (gulp, config) => {
7 |
8 | gulp.task('compile:app', ['compile:vendor'], () => {
9 |
10 | let builder = new Builder();
11 |
12 | return builder.loadConfig(config.system.configFile)
13 | .then(() => {
14 | return builder.bundle('app - dist/vendor.js', 'dist/app.js', {minify: util.env.production})
15 | })
16 | });
17 |
18 | gulp.task('compile:vendor', [], () => {
19 |
20 | let builder = new Builder();
21 |
22 | return builder.loadConfig(config.system.configFile)
23 | .then(() => {
24 | return builder.bundle('app - [app/**/*]', 'dist/vendor.js', {minify: util.env.production})
25 | })
26 | });
27 |
28 | gulp.task('compile:serviceworker', [], () => {
29 |
30 | let builder = new Builder();
31 |
32 | return builder.loadConfig(config.system.configFile)
33 | .then(() => {
34 | return builder.buildStatic('app/ng2-service-worker', 'dist/worker.js', {minify: util.env.production})
35 | })
36 | });
37 | }
--------------------------------------------------------------------------------
/tasks/copy.ts:
--------------------------------------------------------------------------------
1 | const copy = (gulp, config) => {
2 |
3 | gulp.task('copy:dev', () => {
4 | return gulp.src(config.staticFiles)
5 | .pipe(gulp.dest(config.dist));
6 | });
7 |
8 | }
9 |
10 | export {copy};
--------------------------------------------------------------------------------
/tasks/styles.ts:
--------------------------------------------------------------------------------
1 | declare var require;
2 |
3 | const sass = require('gulp-sass');
4 |
5 | export const compile = (gulp, config) => {
6 | gulp.task('compile:sass', () => {
7 | return gulp.src(config.css)
8 | .pipe(sass().on('error', sass.logError))
9 | .pipe(gulp.dest(config.dist))
10 | })
11 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es5",
5 | "noImplicitAny": false,
6 | "outDir": "build",
7 | "rootDir": ".",
8 | "sourceMap": false,
9 | "emitDecoratorMetadata": true,
10 | "experimentalDecorators": true,
11 | "moduleResolution": "node"
12 | },
13 | "exclude": [
14 | "node_modules"
15 | ]
16 | }
--------------------------------------------------------------------------------