├── .editorconfig ├── .gitignore ├── README.md ├── angular-cli.json ├── e2e ├── app.e2e-spec.ts ├── app.po.ts └── tsconfig.json ├── karma.conf.js ├── package.json ├── protractor.conf.js ├── src ├── app │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── index.ts │ └── tesla-battery │ │ ├── components │ │ ├── tesla-car │ │ │ ├── tesla-car.component.scss │ │ │ └── tesla-car.component.ts │ │ ├── tesla-climate │ │ │ ├── tesla-climate.component.scss │ │ │ └── tesla-climate.component.ts │ │ ├── tesla-counter │ │ │ ├── tesla-counter.component.scss │ │ │ └── tesla-counter.component.ts │ │ ├── tesla-stats │ │ │ ├── tesla-stats.component.scss │ │ │ └── tesla-stats.component.ts │ │ └── tesla-wheels │ │ │ ├── tesla-wheels.component.scss │ │ │ └── tesla-wheels.component.ts │ │ ├── containers │ │ └── tesla-battery │ │ │ ├── tesla-battery.component.scss │ │ │ └── tesla-battery.component.ts │ │ ├── models │ │ └── stat.interface.ts │ │ ├── tesla-battery.module.ts │ │ └── tesla-battery.service.ts ├── assets │ ├── .gitkeep │ ├── climate │ │ ├── ac-off.svg │ │ ├── ac-on.svg │ │ ├── heat-off.svg │ │ └── heat-on.svg │ ├── counter │ │ ├── down.svg │ │ └── up.svg │ ├── fonts │ │ ├── Roboto-Regular-webfont.eot │ │ ├── Roboto-Regular-webfont.svg │ │ ├── Roboto-Regular-webfont.ttf │ │ └── Roboto-Regular-webfont.woff │ ├── logo.svg │ ├── models │ │ ├── 60.svg │ │ ├── 60d.svg │ │ ├── 75.svg │ │ ├── 75d.svg │ │ ├── 90d.svg │ │ └── p100d.svg │ ├── tesla.jpg │ ├── wheel-19.png │ ├── wheel-21.png │ └── wheels │ │ ├── 19.svg │ │ └── 21.svg ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── polyfills.ts ├── styles.css ├── test.ts ├── tsconfig.json └── typings.d.ts └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = 0 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | 7 | # dependencies 8 | /node_modules 9 | /bower_components 10 | 11 | # IDEs and editors 12 | /.idea 13 | /.vscode 14 | .project 15 | .classpath 16 | *.launch 17 | .settings/ 18 | 19 | # misc 20 | /.sass-cache 21 | /connect.lock 22 | /coverage/* 23 | /libpeerconnection.log 24 | npm-debug.log 25 | testem.log 26 | /typings 27 | 28 | # e2e 29 | /e2e/*.js 30 | /e2e/*.map 31 | 32 | #System Files 33 | .DS_Store 34 | Thumbs.db 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tesla Battery Range Calculator in Angular 2 2 | 3 |
4 | 5 |
6 | 7 | > Check out the [live demo](https://toddmotto.com/angular-tesla-range-calculator/)! 8 | 9 | ### Features 10 | 11 | * Reactive Form APIs 12 | * FormGroups 13 | * FormControls 14 | * Custom FormControls 15 | * ControlValueAccessor 16 | * Ahead-of-Time compiled 17 | 18 | ### Running the demo locally 19 | * `npm install` 20 | * `npm run start` 21 | * Navigate to [http://localhost:4200/](http://localhost:4200/) 22 | -------------------------------------------------------------------------------- /angular-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "project": { 3 | "version": "1.0.0-beta.22-1", 4 | "name": "angular-tesla-range-calculator" 5 | }, 6 | "apps": [ 7 | { 8 | "root": "src", 9 | "outDir": "dist", 10 | "assets": [ 11 | "assets", 12 | "favicon.ico" 13 | ], 14 | "index": "index.html", 15 | "main": "main.ts", 16 | "test": "test.ts", 17 | "tsconfig": "tsconfig.json", 18 | "prefix": "app", 19 | "mobile": false, 20 | "styles": [ 21 | "styles.css" 22 | ], 23 | "scripts": [], 24 | "environments": { 25 | "source": "environments/environment.ts", 26 | "dev": "environments/environment.ts", 27 | "prod": "environments/environment.prod.ts" 28 | } 29 | } 30 | ], 31 | "addons": [], 32 | "packages": [], 33 | "e2e": { 34 | "protractor": { 35 | "config": "./protractor.conf.js" 36 | } 37 | }, 38 | "test": { 39 | "karma": { 40 | "config": "./karma.conf.js" 41 | } 42 | }, 43 | "defaults": { 44 | "styleExt": "css", 45 | "prefixInterfaces": false, 46 | "inline": { 47 | "style": false, 48 | "template": false 49 | }, 50 | "spec": { 51 | "class": false, 52 | "component": true, 53 | "directive": true, 54 | "module": false, 55 | "pipe": true, 56 | "service": true 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /e2e/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AngularTeslaPage } from './app.po'; 2 | 3 | describe('angular-tesla App', function() { 4 | let page: AngularTeslaPage; 5 | 6 | beforeEach(() => { 7 | page = new AngularTeslaPage(); 8 | }); 9 | 10 | it('should display message saying app works', () => { 11 | page.navigateTo(); 12 | expect(page.getParagraphText()).toEqual('app works!'); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /e2e/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, element, by } from 'protractor'; 2 | 3 | export class AngularTeslaPage { 4 | navigateTo() { 5 | return browser.get('/'); 6 | } 7 | 8 | getParagraphText() { 9 | return element(by.css('app-root h1')).getText(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "module": "commonjs", 8 | "moduleResolution": "node", 9 | "outDir": "../dist/out-tsc-e2e", 10 | "sourceMap": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "../node_modules/@types" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', 'angular-cli'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-remap-istanbul'), 12 | require('angular-cli/plugins/karma') 13 | ], 14 | files: [ 15 | { pattern: './src/test.ts', watched: false } 16 | ], 17 | preprocessors: { 18 | './src/test.ts': ['angular-cli'] 19 | }, 20 | remapIstanbulReporter: { 21 | reports: { 22 | html: 'coverage', 23 | lcovonly: './coverage/coverage.lcov' 24 | } 25 | }, 26 | angularCli: { 27 | config: './angular-cli.json', 28 | environment: 'dev' 29 | }, 30 | reporters: config.angularCli && config.angularCli.codeCoverage 31 | ? ['progress', 'karma-remap-istanbul'] 32 | : ['progress'], 33 | port: 9876, 34 | colors: true, 35 | logLevel: config.LOG_INFO, 36 | autoWatch: true, 37 | browsers: ['Chrome'], 38 | singleRun: false 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-tesla-range-calculator", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "angular-cli": {}, 6 | "scripts": { 7 | "start": "ng serve", 8 | "lint": "tslint \"src/**/*.ts\"", 9 | "test": "ng test", 10 | "pree2e": "webdriver-manager update", 11 | "e2e": "protractor" 12 | }, 13 | "private": true, 14 | "dependencies": { 15 | "@angular/common": "2.2.3", 16 | "@angular/compiler": "2.2.3", 17 | "@angular/core": "2.2.3", 18 | "@angular/forms": "2.2.3", 19 | "@angular/http": "2.2.3", 20 | "@angular/platform-browser": "2.2.3", 21 | "@angular/platform-browser-dynamic": "2.2.3", 22 | "@angular/router": "3.2.3", 23 | "core-js": "^2.4.1", 24 | "rxjs": "5.0.0-beta.12", 25 | "ts-helpers": "^1.1.1", 26 | "zone.js": "^0.6.23" 27 | }, 28 | "devDependencies": { 29 | "@angular/compiler-cli": "2.2.3", 30 | "@types/jasmine": "2.5.38", 31 | "@types/node": "^6.0.42", 32 | "angular-cli": "1.0.0-beta.22-1", 33 | "codelyzer": "~2.0.0-beta.1", 34 | "jasmine-core": "2.5.2", 35 | "jasmine-spec-reporter": "2.5.0", 36 | "karma": "1.2.0", 37 | "karma-chrome-launcher": "^2.0.0", 38 | "karma-cli": "^1.0.1", 39 | "karma-jasmine": "^1.0.2", 40 | "karma-remap-istanbul": "^0.2.1", 41 | "protractor": "4.0.9", 42 | "ts-node": "1.2.1", 43 | "tslint": "^4.0.2", 44 | "typescript": "~2.0.3", 45 | "webdriver-manager": "10.2.5" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /protractor.conf.js: -------------------------------------------------------------------------------- 1 | // Protractor configuration file, see link for more information 2 | // https://github.com/angular/protractor/blob/master/docs/referenceConf.js 3 | 4 | /*global jasmine */ 5 | var SpecReporter = require('jasmine-spec-reporter'); 6 | 7 | exports.config = { 8 | allScriptsTimeout: 11000, 9 | specs: [ 10 | './e2e/**/*.e2e-spec.ts' 11 | ], 12 | capabilities: { 13 | 'browserName': 'chrome' 14 | }, 15 | directConnect: true, 16 | baseUrl: 'http://localhost:4200/', 17 | framework: 'jasmine', 18 | jasmineNodeOpts: { 19 | showColors: true, 20 | defaultTimeoutInterval: 30000, 21 | print: function() {} 22 | }, 23 | useAllAngular2AppRoots: true, 24 | beforeLaunch: function() { 25 | require('ts-node').register({ 26 | project: 'e2e' 27 | }); 28 | }, 29 | onPrepare: function() { 30 | jasmine.getEnv().addReporter(new SpecReporter()); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |

2 | {{title}} 3 |

4 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | } 4 | .header { 5 | padding: 25px 0; 6 | text-align: center; 7 | background: #222; 8 | img { 9 | width: 100px; 10 | height: 13px; 11 | } 12 | } 13 | .wrapper { 14 | margin: 100px 0 150px; 15 | } -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-unused-variable */ 2 | 3 | import { TestBed, async } from '@angular/core/testing'; 4 | import { AppComponent } from './app.component'; 5 | 6 | describe('App: AngularTesla', () => { 7 | beforeEach(() => { 8 | TestBed.configureTestingModule({ 9 | declarations: [ 10 | AppComponent 11 | ], 12 | }); 13 | }); 14 | 15 | it('should create the app', async(() => { 16 | let fixture = TestBed.createComponent(AppComponent); 17 | let app = fixture.debugElement.componentInstance; 18 | expect(app).toBeTruthy(); 19 | })); 20 | 21 | it(`should have as title 'app works!'`, async(() => { 22 | let fixture = TestBed.createComponent(AppComponent); 23 | let app = fixture.debugElement.componentInstance; 24 | expect(app.title).toEqual('app works!'); 25 | })); 26 | 27 | it('should render title in a h1 tag', async(() => { 28 | let fixture = TestBed.createComponent(AppComponent); 29 | fixture.detectChanges(); 30 | let compiled = fixture.debugElement.nativeElement; 31 | expect(compiled.querySelector('h1').textContent).toContain('app works!'); 32 | })); 33 | }); 34 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | template: ` 6 |
7 | 8 |
9 |
10 | 11 |
12 | `, 13 | styleUrls: ['./app.component.scss'] 14 | }) 15 | export class AppComponent { 16 | logo: string = 'assets/logo.svg'; 17 | } -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * app.module.ts 3 | */ 4 | import { BrowserModule } from '@angular/platform-browser'; 5 | import { NgModule } from '@angular/core'; 6 | 7 | // our feature module 8 | import { TeslaBatteryModule } from './tesla-battery/tesla-battery.module'; 9 | 10 | // our app component 11 | import { AppComponent } from './app.component'; 12 | 13 | @NgModule({ 14 | declarations: [ 15 | AppComponent 16 | ], 17 | imports: [ 18 | BrowserModule, 19 | // include our TeslaBatteryModule 20 | TeslaBatteryModule 21 | ], 22 | providers: [], 23 | // bootstrap the AppComponent 24 | bootstrap: [AppComponent] 25 | }) 26 | export class AppModule {} -------------------------------------------------------------------------------- /src/app/index.ts: -------------------------------------------------------------------------------- 1 | export * from './app.component'; 2 | export * from './app.module'; 3 | -------------------------------------------------------------------------------- /src/app/tesla-battery/components/tesla-car/tesla-car.component.scss: -------------------------------------------------------------------------------- 1 | @mixin prefix($property, $value) { 2 | $prefixes: (webkit moz ms o); 3 | @each $prefix in $prefixes { 4 | #{'-' + $prefix + '-' + $property}: $value; 5 | } 6 | // Output standard non-prefixed declaration 7 | #{$property}: $value; 8 | } 9 | 10 | .tesla-car { 11 | width: 100%; 12 | min-height: 350px; 13 | background: #fff url(assets/tesla.jpg) no-repeat top center; 14 | background-size: contain; 15 | } 16 | .tesla-wheels { 17 | height: 247px; 18 | width: 555px; 19 | position: relative; 20 | margin: 0 auto; 21 | } 22 | .tesla-wheel { 23 | height: 80px; 24 | width: 80px; 25 | bottom: 0; 26 | position: absolute; 27 | background-repeat: no-repeat; 28 | background-position: 0 0; 29 | background-size: cover; 30 | &--front { 31 | left: 53px; 32 | } 33 | &--rear { 34 | right: 72px; 35 | } 36 | &--19 { 37 | &--45 { 38 | background-image: url(assets/wheel-19.png); 39 | @include prefix(animation, infinite-spinning 230ms steps(6) infinite); 40 | } 41 | &--50 { 42 | background-image: url(assets/wheel-19.png); 43 | @include prefix(animation, infinite-spinning 200ms steps(6) infinite); 44 | } 45 | &--55 { 46 | background-image: url(assets/wheel-19.png); 47 | @include prefix(animation, infinite-spinning 170ms steps(6) infinite); 48 | } 49 | &--60 { 50 | background-image: url(assets/wheel-19.png); 51 | @include prefix(animation, infinite-spinning 140ms steps(6) infinite); 52 | } 53 | &--65 { 54 | background-image: url(assets/wheel-19.png); 55 | @include prefix(animation, infinite-spinning 110ms steps(6) infinite); 56 | } 57 | &--70 { 58 | background-image: url(assets/wheel-19.png); 59 | @include prefix(animation, infinite-spinning 80ms steps(6) infinite); 60 | } 61 | } 62 | &--21 { 63 | &--45 { 64 | background-image: url(assets/wheel-21.png); 65 | @include prefix(animation, infinite-spinning 480ms steps(12) infinite); 66 | } 67 | &--50 { 68 | background-image: url(assets/wheel-21.png); 69 | @include prefix(animation, infinite-spinning 400ms steps(12) infinite); 70 | } 71 | &--55 { 72 | background-image: url(assets/wheel-21.png); 73 | @include prefix(animation, infinite-spinning 320ms steps(12) infinite); 74 | } 75 | &--60 { 76 | background-image: url(assets/wheel-21.png); 77 | @include prefix(animation, infinite-spinning 240ms steps(12) infinite); 78 | } 79 | &--65 { 80 | background-image: url(assets/wheel-21.png); 81 | @include prefix(animation, infinite-spinning 160ms steps(12) infinite); 82 | } 83 | &--70 { 84 | background-image: url(assets/wheel-21.png); 85 | @include prefix(animation, infinite-spinning 80ms steps(12) infinite); 86 | } 87 | } 88 | } 89 | 90 | @keyframes infinite-spinning { 91 | from { 92 | @include prefix(transform, rotate(0deg)); 93 | } 94 | to { 95 | @include prefix(transform, rotate(360deg)); 96 | } 97 | } 98 | 99 | @-webkit-keyframes infinite-spinning { 100 | from { 101 | @include prefix(transform, rotate(0deg)); 102 | } 103 | to { 104 | @include prefix(transform, rotate(360deg)); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/app/tesla-battery/components/tesla-car/tesla-car.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'tesla-car', 5 | changeDetection: ChangeDetectionStrategy.OnPush, 6 | template: ` 7 |
8 |
9 |
10 |
11 |
12 |
13 | `, 14 | styleUrls: ['./tesla-car.component.scss'] 15 | }) 16 | export class TeslaCarComponent { 17 | @Input() wheelsize: number; 18 | @Input() speed: number; 19 | constructor() {} 20 | } 21 | -------------------------------------------------------------------------------- /src/app/tesla-battery/components/tesla-climate/tesla-climate.component.scss: -------------------------------------------------------------------------------- 1 | .tesla-climate { 2 | float: left; 3 | &__item { 4 | cursor: pointer; 5 | display: block; 6 | width: 100px; 7 | height: 100px; 8 | border: 6px solid #f7f7f7; 9 | border-radius: 50%; 10 | box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.3); 11 | color: #666; 12 | background: #fff; 13 | &--active { 14 | color: #fff; 15 | background: #33a0ff; 16 | background: -moz-linear-gradient(top, #33a0ff 0%, #388bff 100%); 17 | background: -webkit-linear-gradient(top, #33a0ff 0%,#388bff 100%); 18 | background: linear-gradient(to bottom, #33a0ff 0%,#388bff 100%); 19 | &.tesla-heat { 20 | background: #d64800; 21 | background: -moz-linear-gradient(top, #d64800 0%, #d20200 100%); 22 | background: -webkit-linear-gradient(top, #d64800 0%,#d20200 100%); 23 | background: linear-gradient(to bottom, #d64800 0%,#d20200 100%); 24 | } 25 | } 26 | } 27 | &__icon { 28 | display: block; 29 | width: 22px; 30 | height: 22px; 31 | margin: 8px auto 0; 32 | background-repeat: no-repeat; 33 | background-position: center; 34 | background-image: url(assets/climate/ac-off.svg); 35 | .tesla-heat & { 36 | background-image: url(assets/climate/heat-off.svg); 37 | } 38 | .tesla-climate__item--active & { 39 | background-image: url(assets/climate/ac-on.svg); 40 | } 41 | .tesla-climate__item--active.tesla-heat & { 42 | background-image: url(assets/climate/heat-on.svg); 43 | } 44 | } 45 | p { 46 | margin: 14px 0 0; 47 | text-align: center; 48 | font-size: 10px; 49 | text-transform: uppercase; 50 | } 51 | input[type=checkbox] { 52 | border: 0; 53 | clip: rect(0 0 0 0); 54 | height: 1px; 55 | margin: -1px; 56 | overflow: hidden; 57 | padding: 0; 58 | position: absolute; 59 | width: 1px; 60 | } 61 | } -------------------------------------------------------------------------------- /src/app/tesla-battery/components/tesla-climate/tesla-climate.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ChangeDetectionStrategy, forwardRef } from '@angular/core'; 2 | import { FormControl, ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; 3 | 4 | const CHECKBOX_VALUE_ACCESSOR = { 5 | provide: NG_VALUE_ACCESSOR, 6 | useExisting: forwardRef(() => TeslaClimateComponent), 7 | multi: true 8 | }; 9 | 10 | @Component({ 11 | selector: 'tesla-climate', 12 | changeDetection: ChangeDetectionStrategy.OnPush, 13 | template: ` 14 |
15 | 30 |
31 | `, 32 | providers: [CHECKBOX_VALUE_ACCESSOR], 33 | styleUrls: ['./tesla-climate.component.scss'] 34 | }) 35 | export class TeslaClimateComponent implements ControlValueAccessor { 36 | 37 | @Input() limit: boolean; 38 | 39 | value: boolean; 40 | focused: boolean; 41 | 42 | private onTouch: Function; 43 | private onModelChange: Function; 44 | 45 | private onChange(value: boolean) { 46 | this.value = !value; 47 | this.onModelChange(this.value); 48 | } 49 | 50 | registerOnChange(fn: Function) { 51 | this.onModelChange = fn; 52 | } 53 | 54 | registerOnTouched(fn: Function) { 55 | this.onTouch = fn; 56 | } 57 | 58 | writeValue(value: boolean) { 59 | this.value = value; 60 | } 61 | 62 | private onBlur(value: boolean) { 63 | this.focused = false; 64 | } 65 | 66 | private onFocus(value: boolean) { 67 | this.focused = value; 68 | this.onTouch(); 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /src/app/tesla-battery/components/tesla-counter/tesla-counter.component.scss: -------------------------------------------------------------------------------- 1 | .tesla-counter { 2 | float: left; 3 | width: 230px; 4 | &__title { 5 | letter-spacing: 2px; 6 | font-size: 16px; 7 | } 8 | &__container { 9 | margin: 10px 0 0; 10 | padding-right: 40px; 11 | input[type=number] { 12 | border: 0; 13 | clip: rect(0 0 0 0); 14 | height: 1px; 15 | margin: -1px; 16 | overflow: hidden; 17 | padding: 0; 18 | position: absolute; 19 | width: 1px; 20 | } 21 | } 22 | &__number { 23 | font-family: 'RobotoNormal'; 24 | font-size: 25px; 25 | line-height: 25px; 26 | font-weight: 400; 27 | position: relative; 28 | span { 29 | position: absolute; 30 | top: 0; 31 | left: 35px; 32 | font-size: 15px; 33 | text-transform: uppercase; 34 | } 35 | } 36 | &__item { 37 | position: relative; 38 | width: 100%; 39 | height: 65px; 40 | border: 1px solid #ccc; 41 | display: inline-block; 42 | padding: 18px 0 0 30px; 43 | margin: 0 8px 0 0; 44 | background-color: #f7f7f7; 45 | background-position: 24.21053% 9px; 46 | background-repeat: no-repeat; 47 | background-size: 44px; 48 | &:focus { 49 | background-color: #f2f2f2; 50 | outline: none; 51 | } 52 | } 53 | &__controls { 54 | position: absolute; 55 | right: 10px; 56 | top: 7px; 57 | button { 58 | outline: 0; 59 | width: 30px; 60 | color: #008dff; 61 | cursor: pointer; 62 | display: block; 63 | padding: 11px 0; 64 | vertical-align: middle; 65 | border: 0; 66 | background-size: 60%; 67 | background-position: center; 68 | background-repeat: no-repeat; 69 | background-color: transparent; 70 | &[disabled] { 71 | opacity: 0.4; 72 | cursor: not-allowed; 73 | } 74 | &:first-child { 75 | border-bottom: 1px solid #fff; 76 | background-image: url(assets/counter/up.svg); 77 | } 78 | &:last-child { 79 | border-top: 1px solid #ccc; 80 | background-image: url(assets/counter/down.svg); 81 | } 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/app/tesla-battery/components/tesla-counter/tesla-counter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ChangeDetectionStrategy, forwardRef } from '@angular/core'; 2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; 3 | 4 | const NUMBER_CONTROL_ACCESSOR = { 5 | provide: NG_VALUE_ACCESSOR, 6 | useExisting: forwardRef(() => TeslaCounterComponent), 7 | multi: true 8 | }; 9 | 10 | @Component({ 11 | selector: 'tesla-counter', 12 | changeDetection: ChangeDetectionStrategy.OnPush, 13 | template: ` 14 |
15 |

{{ title }}

16 |
17 |
23 |

24 | {{ value }} 25 | {{ unit }} 26 |

27 |
28 | 29 | 30 |
31 |
32 |
33 |
34 | `, 35 | providers: [NUMBER_CONTROL_ACCESSOR], 36 | styleUrls: ['./tesla-counter.component.scss'] 37 | }) 38 | export class TeslaCounterComponent implements ControlValueAccessor { 39 | @Input() step: number = 1; 40 | @Input() min: number; 41 | @Input() max: number; 42 | 43 | @Input() title: string = ''; 44 | @Input() unit: string = ''; 45 | 46 | value: number; 47 | focused: boolean; 48 | 49 | private onTouch: Function; 50 | private onModelChange: Function; 51 | 52 | private onChange(value: number) { 53 | this.value = value; 54 | this.onModelChange(value); 55 | } 56 | 57 | increment() { 58 | if (this.value < this.max) { 59 | this.onChange(this.value + this.step); 60 | } 61 | this.onTouch(); 62 | } 63 | decrement() { 64 | if (this.value > this.min) { 65 | this.onChange(this.value - this.step); 66 | } 67 | this.onTouch(); 68 | } 69 | 70 | registerOnChange(fn: Function) { 71 | this.onModelChange = fn; 72 | } 73 | 74 | registerOnTouched(fn: Function) { 75 | this.onTouch = fn; 76 | } 77 | 78 | writeValue(value: number) { 79 | this.value = value; 80 | } 81 | 82 | private onBlur(event: FocusEvent) { 83 | this.focused = false; 84 | event.preventDefault(); 85 | event.stopPropagation(); 86 | } 87 | 88 | private onKeyUp(event: KeyboardEvent) { 89 | let handlers = { 90 | ArrowDown: () => this.decrement(), 91 | ArrowUp: () => this.increment() 92 | }; 93 | 94 | if (handlers[event.code]) { 95 | handlers[event.code](); 96 | event.preventDefault(); 97 | event.stopPropagation(); 98 | } 99 | } 100 | 101 | private onFocus(event: FocusEvent) { 102 | this.focused = true; 103 | event.preventDefault(); 104 | event.stopPropagation(); 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/app/tesla-battery/components/tesla-stats/tesla-stats.component.scss: -------------------------------------------------------------------------------- 1 | .tesla-stats { 2 | margin: -70px 0 30px; 3 | ul { 4 | text-align: center; 5 | li { 6 | display: inline-block; 7 | width: 130px; 8 | position: relative; 9 | p { 10 | font-size: 40px; 11 | font-weight: normal; 12 | font-family: 'RobotoNormal'; 13 | display: block; 14 | padding: 0 18px 0 0; 15 | position: relative; 16 | color: #008dff; 17 | text-align: right; 18 | &:after { 19 | font-size: 14px; 20 | font-weight: normal; 21 | font-family: 'RobotoNormal'; 22 | content: 'MI'; 23 | position: absolute; 24 | top: 8px; 25 | right: 0; 26 | } 27 | } 28 | } 29 | } 30 | &-icon { 31 | height: 20px; 32 | background-size: auto 13px; 33 | background-position: top right; 34 | background-repeat: no-repeat; 35 | &--60 { 36 | background-image: url(assets/models/60.svg); 37 | } 38 | &--60d { 39 | background-image: url(assets/models/60d.svg); 40 | } 41 | &--75 { 42 | background-image: url(assets/models/75.svg); 43 | } 44 | &--75d { 45 | background-image: url(assets/models/75d.svg); 46 | } 47 | &--90d { 48 | background-image: url(assets/models/90d.svg); 49 | } 50 | &--p100d { 51 | background-image: url(assets/models/p100d.svg); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/app/tesla-battery/components/tesla-stats/tesla-stats.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ChangeDetectionStrategy } from '@angular/core'; 2 | 3 | import { Stat } from '../../models/stat.interface'; 4 | 5 | @Component({ 6 | selector: 'tesla-stats', 7 | changeDetection: ChangeDetectionStrategy.OnPush, 8 | template: ` 9 |
10 | 16 |
17 | `, 18 | styleUrls: ['./tesla-stats.component.scss'] 19 | }) 20 | export class TeslaStatsComponent { 21 | @Input() stats: Stat[]; 22 | } -------------------------------------------------------------------------------- /src/app/tesla-battery/components/tesla-wheels/tesla-wheels.component.scss: -------------------------------------------------------------------------------- 1 | .tesla-wheels { 2 | float: left; 3 | width: 355px; 4 | &__title { 5 | letter-spacing: 2px; 6 | font-size: 16px; 7 | } 8 | &__container { 9 | margin: 10px 0 0; 10 | } 11 | &__item { 12 | cursor: pointer; 13 | width: 47%; 14 | height: 65px; 15 | border: 1px solid #ccc; 16 | display: inline-block; 17 | padding: 20px 0 0 90px; 18 | margin: 0 8px 0 0; 19 | background-color: #f7f7f7; 20 | background-position: 24.21053% 9px; 21 | background-repeat: no-repeat; 22 | background-size: 44px; 23 | &--19 { 24 | background-image: url(assets/wheels/19.svg); 25 | } 26 | &--21 { 27 | background-image: url(assets/wheels/21.svg); 28 | } 29 | &--focused { 30 | background-color: #f2f2f2; 31 | } 32 | &--active { 33 | border-color: #39f; 34 | box-shadow: inset 0px 0px 0px 1px #39f; 35 | } 36 | p { 37 | font-family: 'RobotoNormal'; 38 | font-size: 16px; 39 | font-weight: 400; 40 | color: #333; 41 | } 42 | input[type=radio] { 43 | border: 0; 44 | clip: rect(0 0 0 0); 45 | height: 1px; 46 | margin: -1px; 47 | overflow: hidden; 48 | padding: 0; 49 | position: absolute; 50 | width: 1px; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/app/tesla-battery/components/tesla-wheels/tesla-wheels.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, ChangeDetectionStrategy, forwardRef } from '@angular/core'; 2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; 3 | 4 | const RADIO_CONTROL_ACCESSOR = { 5 | provide: NG_VALUE_ACCESSOR, 6 | useExisting: forwardRef(() => TeslaWheelsComponent), 7 | multi: true 8 | }; 9 | 10 | @Component({ 11 | selector: 'tesla-wheels', 12 | changeDetection: ChangeDetectionStrategy.OnPush, 13 | template: ` 14 |
15 |

Wheels

16 |
17 | 34 |
35 |
36 | `, 37 | providers: [RADIO_CONTROL_ACCESSOR], 38 | styleUrls: ['./tesla-wheels.component.scss'] 39 | }) 40 | export class TeslaWheelsComponent implements ControlValueAccessor { 41 | constructor() {} 42 | private onModelChange: Function; 43 | private onTouch: Function; 44 | private value: string; 45 | private focused: string; 46 | private sizes: number[] = [19, 21]; 47 | 48 | registerOnChange(fn: Function) { 49 | this.onModelChange = fn; 50 | } 51 | 52 | registerOnTouched(fn: Function) { 53 | this.onTouch = fn; 54 | } 55 | 56 | writeValue(value: string) { 57 | this.value = value; 58 | } 59 | 60 | private onChange(value: string) { 61 | this.value = value; 62 | this.onModelChange(value); 63 | } 64 | 65 | private onBlur(value: string) { 66 | this.focused = ''; 67 | } 68 | 69 | private onFocus(value: string) { 70 | this.focused = value; 71 | this.onTouch(); 72 | } 73 | } -------------------------------------------------------------------------------- /src/app/tesla-battery/containers/tesla-battery/tesla-battery.component.scss: -------------------------------------------------------------------------------- 1 | .tesla-battery { 2 | width: 1050px; 3 | margin: 0 auto; 4 | h1 { 5 | font-family: 'RobotoNormal'; 6 | font-weight: 100; 7 | font-size: 38px; 8 | text-align: center; 9 | letter-spacing: 3px; 10 | } 11 | &__notice { 12 | margin: 20px 0; 13 | font-size: 15px; 14 | color: #666; 15 | line-height: 20px; 16 | } 17 | } 18 | .tesla-climate { 19 | float: left; 20 | width: 420px; 21 | padding: 0 40px; 22 | margin: 0 40px 0 0; 23 | border-left: 1px solid #ccc; 24 | border-right: 1px solid #ccc; 25 | } 26 | .tesla-controls { 27 | display: block; 28 | width: 100%; 29 | } -------------------------------------------------------------------------------- /src/app/tesla-battery/containers/tesla-battery/tesla-battery.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { FormBuilder, FormGroup } from '@angular/forms'; 3 | 4 | import { Stat } from '../../models/stat.interface'; 5 | import { BatteryService } from '../../tesla-battery.service'; 6 | 7 | @Component({ 8 | selector: 'tesla-battery', 9 | template: ` 10 |
11 |

{{ title }}

12 | 13 | 14 |
15 | 22 | 23 |
24 | 31 | 32 | 35 | 36 |
37 | 38 |
39 |
40 |

41 | The actual amount of range that you experience will vary based 42 | on your particular use conditions. See how particular use conditions 43 | may affect your range in our simulation model. 44 |

45 |

46 | Vehicle range may vary depending on the vehicle configuration, 47 | battery age and condition, driving style and operating, environmental 48 | and climate conditions. 49 |

50 |
51 |
52 | `, 53 | styleUrls: ['./tesla-battery.component.scss'] 54 | }) 55 | export class TeslaBatteryComponent implements OnInit { 56 | 57 | title: string = 'Range Per Charge'; 58 | models: any; 59 | stats: Stat[]; 60 | tesla: FormGroup; 61 | 62 | private results: Array = ['60', '60D', '75', '75D', '90D', 'P100D']; 63 | 64 | constructor(public fb: FormBuilder, private batteryService: BatteryService) {} 65 | 66 | ngOnInit() { 67 | this.models = this.batteryService.getModelData(); 68 | this.tesla = this.fb.group({ 69 | config: this.fb.group({ 70 | speed: 55, 71 | temperature: 20, 72 | climate: true, 73 | wheels: 19 74 | }) 75 | }); 76 | this.stats = this.calculateStats(this.results, this.tesla.controls['config'].value); 77 | this.tesla.controls['config'].valueChanges.subscribe(data => { 78 | this.stats = this.calculateStats(this.results, data); 79 | }); 80 | } 81 | 82 | private calculateStats(models, value): Stat[] { 83 | return models.map(model => { 84 | const { speed, temperature, climate, wheels } = value; 85 | const miles = this.models[model][wheels][climate ? 'on' : 'off'].speed[speed][temperature]; 86 | return { 87 | model, 88 | miles 89 | }; 90 | }); 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/app/tesla-battery/models/stat.interface.ts: -------------------------------------------------------------------------------- 1 | export interface Stat { 2 | model: string, 3 | miles: number 4 | } -------------------------------------------------------------------------------- /src/app/tesla-battery/tesla-battery.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | 5 | // containers 6 | import { TeslaBatteryComponent } from './containers/tesla-battery/tesla-battery.component'; 7 | 8 | // components 9 | import { TeslaCarComponent } from './components/tesla-car/tesla-car.component'; 10 | import { TeslaStatsComponent } from './components/tesla-stats/tesla-stats.component'; 11 | import { TeslaCounterComponent } from './components/tesla-counter/tesla-counter.component'; 12 | import { TeslaClimateComponent } from './components/tesla-climate/tesla-climate.component'; 13 | import { TeslaWheelsComponent } from './components/tesla-wheels/tesla-wheels.component'; 14 | 15 | // services 16 | import { BatteryService } from './tesla-battery.service'; 17 | 18 | @NgModule({ 19 | declarations: [ 20 | TeslaBatteryComponent, 21 | TeslaCarComponent, 22 | TeslaStatsComponent, 23 | TeslaCounterComponent, 24 | TeslaClimateComponent, 25 | TeslaWheelsComponent 26 | ], 27 | imports: [ 28 | CommonModule, 29 | ReactiveFormsModule 30 | ], 31 | providers: [ 32 | BatteryService 33 | ], 34 | exports: [ 35 | TeslaBatteryComponent 36 | ] 37 | }) 38 | export class TeslaBatteryModule {} -------------------------------------------------------------------------------- /src/app/tesla-battery/tesla-battery.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable() 4 | export class BatteryService { 5 | constructor() {} 6 | getModelData(): Object { 7 | return { 8 | '60': { 9 | 19: { 10 | on: { 11 | speed: { 12 | 45: { 13 | '-10': 224, 14 | '0': 255, 15 | '10': 287, 16 | '20': 289, 17 | '30': 287, 18 | '40': 258 19 | }, 20 | 50: { 21 | '-10': 211, 22 | '0': 238, 23 | '10': 264, 24 | '20': 267, 25 | '30': 267, 26 | '40': 244 27 | }, 28 | 55: { 29 | '-10': 198, 30 | '0': 221, 31 | '10': 242, 32 | '20': 246, 33 | '30': 245, 34 | '40': 228 35 | }, 36 | 60: { 37 | '-10': 184, 38 | '0': 204, 39 | '10': 222, 40 | '20': 225, 41 | '30': 226, 42 | '40': 212 43 | }, 44 | 65: { 45 | '-10': 170, 46 | '0': 187, 47 | '10': 202, 48 | '20': 206, 49 | '30': 208, 50 | '40': 195 51 | }, 52 | 70: { 53 | '-10': 156, 54 | '0': 172, 55 | '10': 185, 56 | '20': 189, 57 | '30': 190, 58 | '40': 179 59 | }, 60 | } 61 | }, 62 | off: { 63 | speed: { 64 | 45: { 65 | '-10': 297, 66 | '0': 312, 67 | '10': 318, 68 | '20': 325, 69 | '30': 329, 70 | '40': 333 71 | }, 72 | 50: { 73 | '-10': 269, 74 | '0': 283, 75 | '10': 288, 76 | '20': 294, 77 | '30': 298, 78 | '40': 304 79 | }, 80 | 55: { 81 | '-10': 245, 82 | '0': 256, 83 | '10': 261, 84 | '20': 267, 85 | '30': 269, 86 | '40': 277 87 | }, 88 | 60: { 89 | '-10': 221, 90 | '0': 231, 91 | '10': 236, 92 | '20': 242, 93 | '30': 243, 94 | '40': 252 95 | }, 96 | 65: { 97 | '-10': 200, 98 | '0': 209, 99 | '10': 214, 100 | '20': 219, 101 | '30': 222, 102 | '40': 230 103 | }, 104 | 70: { 105 | '-10': 181, 106 | '0': 189, 107 | '10': 194, 108 | '20': 199, 109 | '30': 202, 110 | '40': 209 111 | }, 112 | } 113 | } 114 | }, 115 | 21: { 116 | on: { 117 | speed: { 118 | 45: { 119 | '-10': 220, 120 | '0': 251, 121 | '10': 283, 122 | '20': 285, 123 | '30': 282, 124 | '40': 284 125 | }, 126 | 50: { 127 | '-10': 208, 128 | '0': 234, 129 | '10': 260, 130 | '20': 262, 131 | '30': 262, 132 | '40': 240 133 | }, 134 | 55: { 135 | '-10': 194, 136 | '0': 216, 137 | '10': 238, 138 | '20': 241, 139 | '30': 240, 140 | '40': 224 141 | }, 142 | 60: { 143 | '-10': 180, 144 | '0': 199, 145 | '10': 217, 146 | '20': 220, 147 | '30': 221, 148 | '40': 208 149 | }, 150 | 65: { 151 | '-10': 166, 152 | '0': 183, 153 | '10': 198, 154 | '20': 202, 155 | '30': 203, 156 | '40': 191 157 | }, 158 | 70: { 159 | '-10': 152, 160 | '0': 167, 161 | '10': 180, 162 | '20': 184, 163 | '30': 185, 164 | '40': 174 165 | }, 166 | } 167 | }, 168 | off: { 169 | speed: { 170 | 45: { 171 | '-10': 292, 172 | '0': 307, 173 | '10': 314, 174 | '20': 320, 175 | '30': 324, 176 | '40': 328 177 | }, 178 | 50: { 179 | '-10': 265, 180 | '0': 278, 181 | '10': 283, 182 | '20': 289, 183 | '30': 293, 184 | '40': 298 185 | }, 186 | 55: { 187 | '-10': 240, 188 | '0': 251, 189 | '10': 256, 190 | '20': 261, 191 | '30': 264, 192 | '40': 272 193 | }, 194 | 60: { 195 | '-10': 217, 196 | '0': 226, 197 | '10': 231, 198 | '20': 236, 199 | '30': 238, 200 | '40': 247 201 | }, 202 | 65: { 203 | '-10': 196, 204 | '0': 204, 205 | '10': 209, 206 | '20': 214, 207 | '30': 217, 208 | '40': 225 209 | }, 210 | 70: { 211 | '-10': 177, 212 | '0': 184, 213 | '10': 189, 214 | '20': 194, 215 | '30': 197, 216 | '40': 204 217 | }, 218 | } 219 | } 220 | } 221 | }, 222 | '60D': { 223 | 19: { 224 | on: { 225 | speed: { 226 | 45: { 227 | '-10': 227, 228 | '0': 258, 229 | '10': 291, 230 | '20': 293, 231 | '30': 292, 232 | '40': 264 233 | }, 234 | 50: { 235 | '-10': 215, 236 | '0': 242, 237 | '10': 269, 238 | '20': 271, 239 | '30': 272, 240 | '40': 250 241 | }, 242 | 55: { 243 | '-10': 201, 244 | '0': 224, 245 | '10': 247, 246 | '20': 250, 247 | '30': 251, 248 | '40': 235 249 | }, 250 | 60: { 251 | '-10': 187, 252 | '0': 207, 253 | '10': 226, 254 | '20': 229, 255 | '30': 232, 256 | '40': 217 257 | }, 258 | 65: { 259 | '-10': 172, 260 | '0': 191, 261 | '10': 207, 262 | '20': 211, 263 | '30': 213, 264 | '40': 201 265 | }, 266 | 70: { 267 | '-10': 158, 268 | '0': 175, 269 | '10': 189, 270 | '20': 193, 271 | '30': 195, 272 | '40': 185 273 | }, 274 | } 275 | }, 276 | off: { 277 | speed: { 278 | 45: { 279 | '-10': 301, 280 | '0': 317, 281 | '10': 323, 282 | '20': 330, 283 | '30': 336, 284 | '40': 339 285 | }, 286 | 50: { 287 | '-10': 275, 288 | '0': 288, 289 | '10': 294, 290 | '20': 301, 291 | '30': 306, 292 | '40': 311 293 | }, 294 | 55: { 295 | '-10': 249, 296 | '0': 260, 297 | '10': 266, 298 | '20': 273, 299 | '30': 278, 300 | '40': 283 301 | }, 302 | 60: { 303 | '-10': 225, 304 | '0': 236, 305 | '10': 241, 306 | '20': 248, 307 | '30': 251, 308 | '40': 258 309 | }, 310 | 65: { 311 | '-10': 204, 312 | '0': 214, 313 | '10': 219, 314 | '20': 225, 315 | '30': 229, 316 | '40': 236 317 | }, 318 | 70: { 319 | '-10': 184, 320 | '0': 193, 321 | '10': 198, 322 | '20': 205, 323 | '30': 207, 324 | '40': 215 325 | }, 326 | } 327 | } 328 | }, 329 | 21: { 330 | on: { 331 | speed: { 332 | 45: { 333 | '-10': 223, 334 | '0': 255, 335 | '10': 287, 336 | '20': 289, 337 | '30': 288, 338 | '40': 260 339 | }, 340 | 50: { 341 | '-10': 211, 342 | '0': 238, 343 | '10': 264, 344 | '20': 267, 345 | '30': 267, 346 | '40': 246 347 | }, 348 | 55: { 349 | '-10': 197, 350 | '0': 220, 351 | '10': 242, 352 | '20': 246, 353 | '30': 246, 354 | '40': 230 355 | }, 356 | 60: { 357 | '-10': 183, 358 | '0': 203, 359 | '10': 221, 360 | '20': 225, 361 | '30': 227, 362 | '40': 212 363 | }, 364 | 65: { 365 | '-10': 168, 366 | '0': 186, 367 | '10': 202, 368 | '20': 206, 369 | '30': 208, 370 | '40': 196 371 | }, 372 | 70: { 373 | '-10': 155, 374 | '0': 171, 375 | '10': 184, 376 | '20': 188, 377 | '30': 190, 378 | '40': 181 379 | }, 380 | } 381 | }, 382 | off: { 383 | speed: { 384 | 45: { 385 | '-10': 297, 386 | '0': 312, 387 | '10': 319, 388 | '20': 326, 389 | '30': 331, 390 | '40': 335 391 | }, 392 | 50: { 393 | '-10': 270, 394 | '0': 283, 395 | '10': 289, 396 | '20': 296, 397 | '30': 301, 398 | '40': 306 399 | }, 400 | 55: { 401 | '-10': 244, 402 | '0': 256, 403 | '10': 261, 404 | '20': 268, 405 | '30': 272, 406 | '40': 278 407 | }, 408 | 60: { 409 | '-10': 221, 410 | '0': 231, 411 | '10': 236, 412 | '20': 242, 413 | '30': 246, 414 | '40': 253 415 | }, 416 | 65: { 417 | '-10': 199, 418 | '0': 209, 419 | '10': 214, 420 | '20': 220, 421 | '30': 223, 422 | '40': 231 423 | }, 424 | 70: { 425 | '-10': 180, 426 | '0': 188, 427 | '10': 193, 428 | '20': 200, 429 | '30': 202, 430 | '40': 210 431 | }, 432 | } 433 | } 434 | } 435 | }, 436 | '75': { 437 | 19: { 438 | on: { 439 | speed: { 440 | 45: { 441 | '-10': 271, 442 | '0': 309, 443 | '10': 347, 444 | '20': 350, 445 | '30': 347, 446 | '40': 312 447 | }, 448 | 50: { 449 | '-10': 256, 450 | '0': 288, 451 | '10': 320, 452 | '20': 323, 453 | '30': 323, 454 | '40': 295 455 | }, 456 | 55: { 457 | '-10': 240, 458 | '0': 267, 459 | '10': 293, 460 | '20': 297, 461 | '30': 297, 462 | '40': 276 463 | }, 464 | 60: { 465 | '-10': 222, 466 | '0': 246, 467 | '10': 268, 468 | '20': 272, 469 | '30': 273, 470 | '40': 257 471 | }, 472 | 65: { 473 | '-10': 205, 474 | '0': 227, 475 | '10': 245, 476 | '20': 250, 477 | '30': 252, 478 | '40': 236 479 | }, 480 | 70: { 481 | '-10': 189, 482 | '0': 206, 483 | '10': 224, 484 | '20': 228, 485 | '30': 230, 486 | '40': 216 487 | }, 488 | } 489 | }, 490 | off: { 491 | speed: { 492 | 45: { 493 | '-10': 359, 494 | '0': 377, 495 | '10': 385, 496 | '20': 393, 497 | '30': 398, 498 | '40': 403 499 | }, 500 | 50: { 501 | '-10': 326, 502 | '0': 342, 503 | '10': 349, 504 | '20': 356, 505 | '30': 360, 506 | '40': 368 507 | }, 508 | 55: { 509 | '-10': 296, 510 | '0': 309, 511 | '10': 316, 512 | '20': 323, 513 | '30': 326, 514 | '40': 335 515 | }, 516 | 60: { 517 | '-10': 268, 518 | '0': 280, 519 | '10': 286, 520 | '20': 292, 521 | '30': 295, 522 | '40': 305 523 | }, 524 | 65: { 525 | '-10': 242, 526 | '0': 253, 527 | '10': 259, 528 | '20': 265, 529 | '30': 268, 530 | '40': 278 531 | }, 532 | 70: { 533 | '-10': 219, 534 | '0': 229, 535 | '10': 234, 536 | '20': 241, 537 | '30': 244, 538 | '40': 253 539 | }, 540 | } 541 | } 542 | }, 543 | 21: { 544 | on: { 545 | speed: { 546 | 45: { 547 | '-10': 267, 548 | '0': 304, 549 | '10': 342, 550 | '20': 344, 551 | '30': 342, 552 | '40': 308 553 | }, 554 | 50: { 555 | '-10': 251, 556 | '0': 283, 557 | '10': 314, 558 | '20': 317, 559 | '30': 317, 560 | '40': 290 561 | }, 562 | 55: { 563 | '-10': 235, 564 | '0': 262, 565 | '10': 287, 566 | '20': 291, 567 | '30': 291, 568 | '40': 271 569 | }, 570 | 60: { 571 | '-10': 218, 572 | '0': 241, 573 | '10': 262, 574 | '20': 266, 575 | '30': 267, 576 | '40': 251 577 | }, 578 | 65: { 579 | '-10': 201, 580 | '0': 222, 581 | '10': 239, 582 | '20': 244, 583 | '30': 246, 584 | '40': 231 585 | }, 586 | 70: { 587 | '-10': 184, 588 | '0': 203, 589 | '10': 218, 590 | '20': 223, 591 | '30': 224, 592 | '40': 211 593 | }, 594 | } 595 | }, 596 | off: { 597 | speed: { 598 | 45: { 599 | '-10': 353, 600 | '0': 372, 601 | '10': 279, 602 | '20': 387, 603 | '30': 392, 604 | '40': 397 605 | }, 606 | 50: { 607 | '-10': 320, 608 | '0': 336, 609 | '10': 343, 610 | '20': 350, 611 | '30': 354, 612 | '40': 361 613 | }, 614 | 55: { 615 | '-10': 290, 616 | '0': 303, 617 | '10': 309, 618 | '20': 316, 619 | '30': 319, 620 | '40': 329 621 | }, 622 | 60: { 623 | '-10': 262, 624 | '0': 274, 625 | '10': 279, 626 | '20': 286, 627 | '30': 288, 628 | '40': 299 629 | }, 630 | 65: { 631 | '-10': 237, 632 | '0': 247, 633 | '10': 253, 634 | '20': 259, 635 | '30': 262, 636 | '40': 272 637 | }, 638 | 70: { 639 | '-10': 214, 640 | '0': 223, 641 | '10': 229, 642 | '20': 235, 643 | '30': 238, 644 | '40': 247 645 | }, 646 | } 647 | } 648 | } 649 | }, 650 | '75D': { 651 | 19: { 652 | on: { 653 | speed: { 654 | 45: { 655 | '-10': 227, 656 | '0': 316, 657 | '10': 356, 658 | '20': 358, 659 | '30': 357, 660 | '40': 323 661 | }, 662 | 50: { 663 | '-10': 262, 664 | '0': 296, 665 | '10': 328, 666 | '20': 332, 667 | '30': 332, 668 | '40': 305 669 | }, 670 | 55: { 671 | '-10': 246, 672 | '0': 274, 673 | '10': 302, 674 | '20': 306, 675 | '30': 307, 676 | '40': 287 677 | }, 678 | 60: { 679 | '-10': 228, 680 | '0': 253, 681 | '10': 276, 682 | '20': 280, 683 | '30': 283, 684 | '40': 265 685 | }, 686 | 65: { 687 | '-10': 211, 688 | '0': 233, 689 | '10': 253, 690 | '20': 257, 691 | '30': 260, 692 | '40': 246 693 | }, 694 | 70: { 695 | '-10': 194, 696 | '0': 214, 697 | '10': 231, 698 | '20': 236, 699 | '30': 238, 700 | '40': 226 701 | }, 702 | } 703 | }, 704 | off: { 705 | speed: { 706 | 45: { 707 | '-10': 368, 708 | '0': 387, 709 | '10': 395, 710 | '20': 404, 711 | '30': 410, 712 | '40': 415 713 | }, 714 | 50: { 715 | '-10': 335, 716 | '0': 351, 717 | '10': 359, 718 | '20': 367, 719 | '30': 374, 720 | '40': 380 721 | }, 722 | 55: { 723 | '-10': 304, 724 | '0': 318, 725 | '10': 325, 726 | '20': 334, 727 | '30': 339, 728 | '40': 346 729 | }, 730 | 60: { 731 | '-10': 275, 732 | '0': 288, 733 | '10': 294, 734 | '20': 303, 735 | '30': 307, 736 | '40': 316 737 | }, 738 | 65: { 739 | '-10': 249, 740 | '0': 261, 741 | '10': 267, 742 | '20': 275, 743 | '30': 279, 744 | '40': 289 745 | }, 746 | 70: { 747 | '-10': 225, 748 | '0': 236, 749 | '10': 242, 750 | '20': 250, 751 | '30': 253, 752 | '40': 263 753 | }, 754 | } 755 | } 756 | }, 757 | 21: { 758 | on: { 759 | speed: { 760 | 45: { 761 | '-10': 273, 762 | '0': 311, 763 | '10': 351, 764 | '20': 354, 765 | '30': 352, 766 | '40': 318 767 | }, 768 | 50: { 769 | '-10': 258, 770 | '0': 291, 771 | '10': 323, 772 | '20': 326, 773 | '30': 327, 774 | '40': 300 775 | }, 776 | 55: { 777 | '-10': 241, 778 | '0': 269, 779 | '10': 296, 780 | '20': 300, 781 | '30': 301, 782 | '40': 281 783 | }, 784 | 60: { 785 | '-10': 223, 786 | '0': 248, 787 | '10': 270, 788 | '20': 275, 789 | '30': 277, 790 | '40': 259 791 | }, 792 | 65: { 793 | '-10': 206, 794 | '0': 228, 795 | '10': 247, 796 | '20': 252, 797 | '30': 254, 798 | '40': 240 799 | }, 800 | 70: { 801 | '-10': 189, 802 | '0': 209, 803 | '10': 225, 804 | '20': 230, 805 | '30': 232, 806 | '40': 221 807 | }, 808 | } 809 | }, 810 | off: { 811 | speed: { 812 | 45: { 813 | '-10': 363, 814 | '0': 382, 815 | '10': 390, 816 | '20': 398, 817 | '30': 405, 818 | '40': 409 819 | }, 820 | 50: { 821 | '-10': 330, 822 | '0': 346, 823 | '10': 353, 824 | '20': 361, 825 | '30': 368, 826 | '40': 373 827 | }, 828 | 55: { 829 | '-10': 299, 830 | '0': 312, 831 | '10': 319, 832 | '20': 327, 833 | '30': 333, 834 | '40': 340 835 | }, 836 | 60: { 837 | '-10': 270, 838 | '0': 282, 839 | '10': 288, 840 | '20': 296, 841 | '30': 301, 842 | '40': 309 843 | }, 844 | 65: { 845 | '-10': 244, 846 | '0': 255, 847 | '10': 261, 848 | '20': 269, 849 | '30': 273, 850 | '40': 282 851 | }, 852 | 70: { 853 | '-10': 219, 854 | '0': 230, 855 | '10': 236, 856 | '20': 244, 857 | '30': 247, 858 | '40': 257 859 | }, 860 | } 861 | } 862 | } 863 | }, 864 | '90D': { 865 | 19: { 866 | on: { 867 | speed: { 868 | 45: { 869 | '-10': 308, 870 | '0': 349, 871 | '10': 392, 872 | '20': 394, 873 | '30': 392, 874 | '40': 357 875 | }, 876 | 50: { 877 | '-10': 292, 878 | '0': 326, 879 | '10': 362, 880 | '20': 365, 881 | '30': 365, 882 | '40': 338 883 | }, 884 | 55: { 885 | '-10': 273, 886 | '0': 303, 887 | '10': 332, 888 | '20': 336, 889 | '30': 337, 890 | '40': 317 891 | }, 892 | 60: { 893 | '-10': 254, 894 | '0': 280, 895 | '10': 305, 896 | '20': 308, 897 | '30': 310, 898 | '40': 293 899 | }, 900 | 65: { 901 | '-10': 235, 902 | '0': 258, 903 | '10': 279, 904 | '20': 283, 905 | '30': 285, 906 | '40': 273 907 | }, 908 | 70: { 909 | '-10': 216, 910 | '0': 238, 911 | '10': 256, 912 | '20': 260, 913 | '30': 263, 914 | '40': 253 915 | }, 916 | } 917 | }, 918 | off: { 919 | speed: { 920 | 45: { 921 | '-10': 406, 922 | '0': 426, 923 | '10': 434, 924 | '20': 443, 925 | '30': 451, 926 | '40': 455 927 | }, 928 | 50: { 929 | '-10': 370, 930 | '0': 386, 931 | '10': 394, 932 | '20': 403, 933 | '30': 412, 934 | '40': 416 935 | }, 936 | 55: { 937 | '-10': 336, 938 | '0': 350, 939 | '10': 358, 940 | '20': 366, 941 | '30': 274, 942 | '40': 380 943 | }, 944 | 60: { 945 | '-10': 304, 946 | '0': 317, 947 | '10': 324, 948 | '20': 332, 949 | '30': 338, 950 | '40': 347 951 | }, 952 | 65: { 953 | '-10': 276, 954 | '0': 288, 955 | '10': 295, 956 | '20': 302, 957 | '30': 308, 958 | '40': 317 959 | }, 960 | 70: { 961 | '-10': 250, 962 | '0': 261, 963 | '10': 268, 964 | '20': 275, 965 | '30': 279, 966 | '40': 290 967 | }, 968 | } 969 | } 970 | }, 971 | 21: { 972 | on: { 973 | speed: { 974 | 45: { 975 | '-10': 304, 976 | '0': 345, 977 | '10': 386, 978 | '20': 388, 979 | '30': 387, 980 | '40': 352 981 | }, 982 | 50: { 983 | '-10': 287, 984 | '0': 321, 985 | '10': 356, 986 | '20': 359, 987 | '30': 360, 988 | '40': 332 989 | }, 990 | 55: { 991 | '-10': 268, 992 | '0': 297, 993 | '10': 326, 994 | '20': 330, 995 | '30': 330, 996 | '40': 311 997 | }, 998 | 60: { 999 | '-10': 249, 1000 | '0': 274, 1001 | '10': 299, 1002 | '20': 302, 1003 | '30': 303, 1004 | '40': 287 1005 | }, 1006 | 65: { 1007 | '-10': 230, 1008 | '0': 253, 1009 | '10': 273, 1010 | '20': 277, 1011 | '30': 279, 1012 | '40': 267 1013 | }, 1014 | 70: { 1015 | '-10': 211, 1016 | '0': 232, 1017 | '10': 250, 1018 | '20': 254, 1019 | '30': 257, 1020 | '40': 247 1021 | }, 1022 | } 1023 | }, 1024 | off: { 1025 | speed: { 1026 | 45: { 1027 | '-10': 401, 1028 | '0': 420, 1029 | '10': 428, 1030 | '20': 437, 1031 | '30': 446, 1032 | '40': 449 1033 | }, 1034 | 50: { 1035 | '-10': 364, 1036 | '0': 380, 1037 | '10': 388, 1038 | '20': 397, 1039 | '30': 405, 1040 | '40': 410 1041 | }, 1042 | 55: { 1043 | '-10': 330, 1044 | '0': 344, 1045 | '10': 351, 1046 | '20': 359, 1047 | '30': 367, 1048 | '40': 373 1049 | }, 1050 | 60: { 1051 | '-10': 298, 1052 | '0': 311, 1053 | '10': 318, 1054 | '20': 325, 1055 | '30': 331, 1056 | '40': 340 1057 | }, 1058 | 65: { 1059 | '-10': 270, 1060 | '0': 282, 1061 | '10': 288, 1062 | '20': 296, 1063 | '30': 301, 1064 | '40': 310 1065 | }, 1066 | 70: { 1067 | '-10': 244, 1068 | '0': 255, 1069 | '10': 262, 1070 | '20': 269, 1071 | '30': 273, 1072 | '40': 284 1073 | }, 1074 | } 1075 | } 1076 | } 1077 | }, 1078 | 'P100D': { 1079 | 19: { 1080 | on: { 1081 | speed: { 1082 | 45: { 1083 | '-10': 341, 1084 | '0': 390, 1085 | '10': 439, 1086 | '20': 442, 1087 | '30': 440, 1088 | '40': 401 1089 | }, 1090 | 50: { 1091 | '-10': 323, 1092 | '0': 365, 1093 | '10': 405, 1094 | '20': 409, 1095 | '30': 410, 1096 | '40': 380 1097 | }, 1098 | 55: { 1099 | '-10': 303, 1100 | '0': 339, 1101 | '10': 372, 1102 | '20': 376, 1103 | '30': 379, 1104 | '40': 353 1105 | }, 1106 | 60: { 1107 | '-10': 282, 1108 | '0': 313, 1109 | '10': 341, 1110 | '20': 345, 1111 | '30': 347, 1112 | '40': 329 1113 | }, 1114 | 65: { 1115 | '-10': 261, 1116 | '0': 289, 1117 | '10': 312, 1118 | '20': 317, 1119 | '30': 318, 1120 | '40': 306 1121 | }, 1122 | 70: { 1123 | '-10': 240, 1124 | '0': 265, 1125 | '10': 285, 1126 | '20': 290, 1127 | '30': 293, 1128 | '40': 283 1129 | }, 1130 | } 1131 | }, 1132 | off: { 1133 | speed: { 1134 | 45: { 1135 | '-10': 447, 1136 | '0': 474, 1137 | '10': 485, 1138 | '20': 496, 1139 | '30': 505, 1140 | '40': 509 1141 | }, 1142 | 50: { 1143 | '-10': 408, 1144 | '0': 431, 1145 | '10': 441, 1146 | '20': 451, 1147 | '30': 461, 1148 | '40': 466 1149 | }, 1150 | 55: { 1151 | '-10': 372, 1152 | '0': 391, 1153 | '10': 400, 1154 | '20': 409, 1155 | '30': 419, 1156 | '40': 425 1157 | }, 1158 | 60: { 1159 | '-10': 337, 1160 | '0': 354, 1161 | '10': 362, 1162 | '20': 371, 1163 | '30': 377, 1164 | '40': 388 1165 | }, 1166 | 65: { 1167 | '-10': 306, 1168 | '0': 321, 1169 | '10': 329, 1170 | '20': 337, 1171 | '30': 341, 1172 | '40': 354 1173 | }, 1174 | 70: { 1175 | '-10': 277, 1176 | '0': 291, 1177 | '10': 299, 1178 | '20': 307, 1179 | '30': 311, 1180 | '40': 323 1181 | }, 1182 | } 1183 | } 1184 | }, 1185 | 21: { 1186 | on: { 1187 | speed: { 1188 | 45: { 1189 | '-10': 322, 1190 | '0': 369, 1191 | '10': 414, 1192 | '20': 417, 1193 | '30': 416, 1194 | '40': 379 1195 | }, 1196 | 50: { 1197 | '-10': 306, 1198 | '0': 347, 1199 | '10': 384, 1200 | '20': 388, 1201 | '30': 389, 1202 | '40': 360 1203 | }, 1204 | 55: { 1205 | '-10': 228, 1206 | '0': 323, 1207 | '10': 354, 1208 | '20': 358, 1209 | '30': 360, 1210 | '40': 336 1211 | }, 1212 | 60: { 1213 | '-10': 269, 1214 | '0': 299, 1215 | '10': 325, 1216 | '20': 329, 1217 | '30': 331, 1218 | '40': 313 1219 | }, 1220 | 65: { 1221 | '-10': 250, 1222 | '0': 276, 1223 | '10': 299, 1224 | '20': 303, 1225 | '30': 305, 1226 | '40': 292 1227 | }, 1228 | 70: { 1229 | '-10': 230, 1230 | '0': 254, 1231 | '10': 273, 1232 | '20': 278, 1233 | '30': 281, 1234 | '40': 271 1235 | }, 1236 | } 1237 | }, 1238 | off: { 1239 | speed: { 1240 | 45: { 1241 | '-10': 422, 1242 | '0': 447, 1243 | '10': 458, 1244 | '20': 468, 1245 | '30': 477, 1246 | '40': 481 1247 | }, 1248 | 50: { 1249 | '-10': 387, 1250 | '0': 409, 1251 | '10': 418, 1252 | '20': 428, 1253 | '30': 437, 1254 | '40': 442 1255 | }, 1256 | 55: { 1257 | '-10': 353, 1258 | '0': 372, 1259 | '10': 380, 1260 | '20': 389, 1261 | '30': 398, 1262 | '40': 404 1263 | }, 1264 | 60: { 1265 | '-10': 322, 1266 | '0': 338, 1267 | '10': 345, 1268 | '20': 354, 1269 | '30': 359, 1270 | '40': 370 1271 | }, 1272 | 65: { 1273 | '-10': 293, 1274 | '0': 307, 1275 | '10': 315, 1276 | '20': 323, 1277 | '30': 326, 1278 | '40': 339 1279 | }, 1280 | 70: { 1281 | '-10': 265, 1282 | '0': 279, 1283 | '10': 286, 1284 | '20': 294, 1285 | '30': 298, 1286 | '40': 310 1287 | }, 1288 | } 1289 | } 1290 | } 1291 | } 1292 | }; 1293 | } 1294 | } -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimatecourses/angular-tesla-range-calculator/868e8107f349cecd3944ff691ef70fe05bb47fed/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/assets/climate/ac-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 12 | 15 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/assets/climate/ac-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 12 | 15 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/assets/climate/heat-off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 10 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/assets/climate/heat-on.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 10 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/assets/counter/down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/counter/up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/assets/fonts/Roboto-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimatecourses/angular-tesla-range-calculator/868e8107f349cecd3944ff691ef70fe05bb47fed/src/assets/fonts/Roboto-Regular-webfont.eot -------------------------------------------------------------------------------- /src/assets/fonts/Roboto-Regular-webfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | -------------------------------------------------------------------------------- /src/assets/fonts/Roboto-Regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimatecourses/angular-tesla-range-calculator/868e8107f349cecd3944ff691ef70fe05bb47fed/src/assets/fonts/Roboto-Regular-webfont.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Roboto-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimatecourses/angular-tesla-range-calculator/868e8107f349cecd3944ff691ef70fe05bb47fed/src/assets/fonts/Roboto-Regular-webfont.woff -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/assets/models/60.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/models/60d.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/assets/models/75.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/models/75d.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/models/90d.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/models/p100d.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/tesla.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimatecourses/angular-tesla-range-calculator/868e8107f349cecd3944ff691ef70fe05bb47fed/src/assets/tesla.jpg -------------------------------------------------------------------------------- /src/assets/wheel-19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimatecourses/angular-tesla-range-calculator/868e8107f349cecd3944ff691ef70fe05bb47fed/src/assets/wheel-19.png -------------------------------------------------------------------------------- /src/assets/wheel-21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimatecourses/angular-tesla-range-calculator/868e8107f349cecd3944ff691ef70fe05bb47fed/src/assets/wheel-21.png -------------------------------------------------------------------------------- /src/assets/wheels/19.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/wheels/21.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimatecourses/angular-tesla-range-calculator/868e8107f349cecd3944ff691ef70fe05bb47fed/src/favicon.ico -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular Tesla Range Calculator 6 | 7 | 8 | 9 | 10 | Loading... 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 2 | 3 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 4 | import { enableProdMode } from '@angular/core'; 5 | import { environment } from './environments/environment'; 6 | import { AppModule } from './app/'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | platformBrowserDynamic().bootstrapModule(AppModule); 13 | -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | // This file includes polyfills needed by Angular 2 and is loaded before 2 | // the app. You can add your own extra polyfills to this file. 3 | import 'core-js/es6/symbol'; 4 | import 'core-js/es6/object'; 5 | import 'core-js/es6/function'; 6 | import 'core-js/es6/parse-int'; 7 | import 'core-js/es6/parse-float'; 8 | import 'core-js/es6/number'; 9 | import 'core-js/es6/math'; 10 | import 'core-js/es6/string'; 11 | import 'core-js/es6/date'; 12 | import 'core-js/es6/array'; 13 | import 'core-js/es6/regexp'; 14 | import 'core-js/es6/map'; 15 | import 'core-js/es6/set'; 16 | import 'core-js/es6/reflect'; 17 | 18 | import 'core-js/es7/reflect'; 19 | import 'zone.js/dist/zone'; 20 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'RobotoNormal'; 3 | src: url('./assets/fonts/Roboto-Regular-webfont.eot'); 4 | src: url('./assets/fonts/Roboto-Regular-webfont.eot?#iefix') format('embedded-opentype'), 5 | url('./assets/fonts/Roboto-Regular-webfont.woff') format('woff'), 6 | url('./assets/fonts/Roboto-Regular-webfont.ttf') format('truetype'), 7 | url('./assets/fonts/Roboto-Regular-webfont.svg#RobotoRegular') format('svg'); 8 | font-weight: normal; 9 | font-style: normal; 10 | } 11 | 12 | *, *:before, *:after { 13 | box-sizing: border-box; 14 | margin: 0; 15 | padding: 0; 16 | font: 300 14px/1.4 'Helvetica Neue', Helvetica, Arial, sans-serif; 17 | -webkit-font-smoothing: antialiased; 18 | } 19 | 20 | .cf:before, 21 | .cf:after { 22 | content: ''; 23 | display: table; 24 | } 25 | .cf:after { 26 | clear: both; 27 | } 28 | .cf { 29 | *zoom: 1; 30 | } -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | import './polyfills.ts'; 2 | 3 | import 'zone.js/dist/long-stack-trace-zone'; 4 | import 'zone.js/dist/proxy.js'; 5 | import 'zone.js/dist/sync-test'; 6 | import 'zone.js/dist/jasmine-patch'; 7 | import 'zone.js/dist/async-test'; 8 | import 'zone.js/dist/fake-async-test'; 9 | 10 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. 11 | declare var __karma__: any; 12 | declare var require: any; 13 | 14 | // Prevent Karma from running prematurely. 15 | __karma__.loaded = function () {}; 16 | 17 | 18 | Promise.all([ 19 | System.import('@angular/core/testing'), 20 | System.import('@angular/platform-browser-dynamic/testing') 21 | ]) 22 | // First, initialize the Angular testing environment. 23 | .then(([testing, testingBrowser]) => { 24 | testing.getTestBed().initTestEnvironment( 25 | testingBrowser.BrowserDynamicTestingModule, 26 | testingBrowser.platformBrowserDynamicTesting() 27 | ); 28 | }) 29 | // Then we find all the tests. 30 | .then(() => require.context('./', true, /\.spec\.ts/)) 31 | // And load the modules. 32 | .then(context => context.keys().map(context)) 33 | // Finally, start Karma to run the tests. 34 | .then(__karma__.start, __karma__.error); 35 | -------------------------------------------------------------------------------- /src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declaration": false, 4 | "emitDecoratorMetadata": true, 5 | "experimentalDecorators": true, 6 | "lib": ["es6", "dom"], 7 | "mapRoot": "./", 8 | "module": "es6", 9 | "moduleResolution": "node", 10 | "outDir": "../dist/out-tsc", 11 | "sourceMap": true, 12 | "target": "es5", 13 | "typeRoots": [ 14 | "../node_modules/@types" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | // Typings reference file, you can add your own global typings here 2 | // https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html 3 | 4 | declare var System: any; 5 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/codelyzer" 4 | ], 5 | "rules": { 6 | "class-name": true, 7 | "comment-format": [ 8 | true, 9 | "check-space" 10 | ], 11 | "curly": true, 12 | "eofline": true, 13 | "forin": true, 14 | "indent": [ 15 | true, 16 | "spaces" 17 | ], 18 | "label-position": true, 19 | "label-undefined": true, 20 | "max-line-length": [ 21 | true, 22 | 140 23 | ], 24 | "member-access": false, 25 | "member-ordering": [ 26 | true, 27 | "static-before-instance", 28 | "variables-before-functions" 29 | ], 30 | "no-arg": true, 31 | "no-bitwise": true, 32 | "no-console": [ 33 | true, 34 | "debug", 35 | "info", 36 | "time", 37 | "timeEnd", 38 | "trace" 39 | ], 40 | "no-construct": true, 41 | "no-debugger": true, 42 | "no-duplicate-key": true, 43 | "no-duplicate-variable": true, 44 | "no-empty": false, 45 | "no-eval": true, 46 | "no-inferrable-types": true, 47 | "no-shadowed-variable": true, 48 | "no-string-literal": false, 49 | "no-switch-case-fall-through": true, 50 | "no-trailing-whitespace": true, 51 | "no-unused-expression": true, 52 | "no-unused-variable": true, 53 | "no-unreachable": true, 54 | "no-use-before-declare": true, 55 | "no-var-keyword": true, 56 | "object-literal-sort-keys": false, 57 | "one-line": [ 58 | true, 59 | "check-open-brace", 60 | "check-catch", 61 | "check-else", 62 | "check-whitespace" 63 | ], 64 | "quotemark": [ 65 | true, 66 | "single" 67 | ], 68 | "radix": true, 69 | "semicolon": [ 70 | "always" 71 | ], 72 | "triple-equals": [ 73 | true, 74 | "allow-null-check" 75 | ], 76 | "typedef-whitespace": [ 77 | true, 78 | { 79 | "call-signature": "nospace", 80 | "index-signature": "nospace", 81 | "parameter": "nospace", 82 | "property-declaration": "nospace", 83 | "variable-declaration": "nospace" 84 | } 85 | ], 86 | "variable-name": false, 87 | "whitespace": [ 88 | true, 89 | "check-branch", 90 | "check-decl", 91 | "check-operator", 92 | "check-separator", 93 | "check-type" 94 | ], 95 | 96 | "directive-selector-prefix": [true, "app"], 97 | "component-selector-prefix": [true, "app"], 98 | "directive-selector-name": [true, "camelCase"], 99 | "component-selector-name": [true, "kebab-case"], 100 | "directive-selector-type": [true, "attribute"], 101 | "component-selector-type": [true, "element"], 102 | "use-input-property-decorator": true, 103 | "use-output-property-decorator": true, 104 | "use-host-property-decorator": true, 105 | "no-input-rename": true, 106 | "no-output-rename": true, 107 | "use-life-cycle-interface": true, 108 | "use-pipe-transform-interface": true, 109 | "component-class-suffix": true, 110 | "directive-class-suffix": true, 111 | "templates-use-public": true, 112 | "invoke-injectable": true 113 | } 114 | } 115 | --------------------------------------------------------------------------------