├── docs ├── CNAME ├── favicon.ico ├── index.html └── dist │ ├── vendor-manifest.json │ └── 89889688147bd7575d6327160d64e760.svg ├── wwwroot ├── favicon.ico └── index.html ├── README.md ├── ClientApp ├── app │ ├── components │ │ ├── app │ │ │ ├── app.component.css │ │ │ ├── app.component.ts │ │ │ └── app.component.html │ │ ├── navmenu │ │ │ ├── navmenu.component.ts │ │ │ ├── navmenu.component.css │ │ │ └── navmenu.component.html │ │ └── home │ │ │ ├── home.component.html │ │ │ ├── home.component.ts │ │ │ └── home.component.spec.ts │ ├── app.module.server.ts │ ├── app.module.browser.ts │ └── app.module.shared.ts ├── test │ ├── karma.conf.js │ └── boot-tests.ts ├── boot.browser.ts └── boot.server.ts ├── tsconfig.json ├── package.json ├── webpack.config.js ├── webpack.config.vendor.js └── .gitignore /docs/CNAME: -------------------------------------------------------------------------------- 1 | decimalnow.com -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaphHaddad/DecimalNow/HEAD/docs/favicon.ico -------------------------------------------------------------------------------- /wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RaphHaddad/DecimalNow/HEAD/wwwroot/favicon.ico -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Decimal now 2 | Uses decimal time 3 | 4 | deployed [here](http://decimalnow.com) 5 | 6 | the meat of the code is in `home.component.ts` and `home.component.spec.ts` -------------------------------------------------------------------------------- /ClientApp/app/components/app/app.component.css: -------------------------------------------------------------------------------- 1 | @media (max-width: 767px) { 2 | /* On small screens, the nav menu spans the full width of the screen. Leave a space for it. */ 3 | .body-content { 4 | padding-top: 50px; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ClientApp/app/components/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | } 10 | -------------------------------------------------------------------------------- /ClientApp/app/components/navmenu/navmenu.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'nav-menu', 5 | templateUrl: './navmenu.component.html', 6 | styleUrls: ['./navmenu.component.css'] 7 | }) 8 | export class NavMenuComponent { 9 | } 10 | -------------------------------------------------------------------------------- /ClientApp/app/app.module.server.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { ServerModule } from '@angular/platform-server'; 3 | import { AppModuleShared } from './app.module.shared'; 4 | import { AppComponent } from './components/app/app.component'; 5 | 6 | @NgModule({ 7 | bootstrap: [ AppComponent ], 8 | imports: [ 9 | ServerModule, 10 | AppModuleShared 11 | ] 12 | }) 13 | export class AppModule { 14 | } 15 | -------------------------------------------------------------------------------- /ClientApp/app/components/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 | 14 |
-------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2015", 4 | "moduleResolution": "node", 5 | "target": "es5", 6 | "sourceMap": true, 7 | "experimentalDecorators": true, 8 | "emitDecoratorMetadata": true, 9 | "skipDefaultLibCheck": true, 10 | "skipLibCheck": true, // Workaround for https://github.com/angular/angular/issues/17863. Remove this if you upgrade to a fixed version of Angular. 11 | "strict": true, 12 | "lib": [ "es6", "dom" ], 13 | "types": [ "webpack-env" ] 14 | }, 15 | "exclude": [ "bin", "node_modules" ], 16 | "atom": { "rewriteTsconfig": false } 17 | } 18 | -------------------------------------------------------------------------------- /wwwroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /ClientApp/app/app.module.browser.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { AppModuleShared } from './app.module.shared'; 4 | import { AppComponent } from './components/app/app.component'; 5 | 6 | @NgModule({ 7 | bootstrap: [ AppComponent ], 8 | imports: [ 9 | BrowserModule, 10 | AppModuleShared 11 | ], 12 | providers: [ 13 | { provide: 'BASE_URL', useFactory: getBaseUrl } 14 | ] 15 | }) 16 | export class AppModule { 17 | } 18 | 19 | export function getBaseUrl() { 20 | return document.getElementsByTagName('base')[0].href; 21 | } 22 | -------------------------------------------------------------------------------- /ClientApp/app/app.module.shared.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { FormsModule } from '@angular/forms'; 4 | import { HttpModule } from '@angular/http'; 5 | import { RouterModule } from '@angular/router'; 6 | 7 | import { AppComponent } from './components/app/app.component'; 8 | import { NavMenuComponent } from './components/navmenu/navmenu.component'; 9 | import { HomeComponent } from './components/home/home.component'; 10 | 11 | @NgModule({ 12 | declarations: [ 13 | AppComponent, 14 | NavMenuComponent, 15 | HomeComponent 16 | ], 17 | imports: [ 18 | CommonModule, 19 | HttpModule, 20 | FormsModule, 21 | RouterModule.forRoot([ 22 | { path: '', redirectTo: 'home', pathMatch: 'full' }, 23 | { path: 'home', component: HomeComponent }, 24 | { path: '**', redirectTo: 'home' } 25 | ]) 26 | ] 27 | }) 28 | export class AppModuleShared { 29 | } 30 | -------------------------------------------------------------------------------- /ClientApp/test/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'], 8 | files: [ 9 | '../../wwwroot/dist/vendor.js', 10 | './boot-tests.ts' 11 | ], 12 | preprocessors: { 13 | './boot-tests.ts': ['webpack'] 14 | }, 15 | reporters: ['progress'], 16 | port: 9876, 17 | colors: true, 18 | logLevel: config.LOG_INFO, 19 | autoWatch: true, 20 | browsers: ['Chrome'], 21 | mime: { 'application/javascript': ['ts','tsx'] }, 22 | singleRun: false, 23 | webpack: require('../../webpack.config.js')().filter(config => config.target !== 'node'), // Test against client bundle, because tests run in a browser 24 | webpackMiddleware: { stats: 'errors-only' } 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /ClientApp/boot.browser.ts: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill' 2 | import 'reflect-metadata'; 3 | import 'zone.js'; 4 | import 'bootstrap'; 5 | import { enableProdMode } from '@angular/core'; 6 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 7 | import { AppModule } from './app/app.module.browser'; 8 | 9 | if (module.hot) { 10 | module.hot.accept(); 11 | module.hot.dispose(() => { 12 | // Before restarting the app, we create a new root element and dispose the old one 13 | const oldRootElem = document.querySelector('app'); 14 | const newRootElem = document.createElement('app'); 15 | oldRootElem!.parentNode!.insertBefore(newRootElem, oldRootElem); 16 | modulePromise.then(appModule => appModule.destroy()); 17 | }); 18 | } else { 19 | enableProdMode(); 20 | } 21 | 22 | // Note: @ng-tools/webpack looks for the following expression when performing production 23 | // builds. Don't change how this line looks, otherwise you may break tree-shaking. 24 | const modulePromise = platformBrowserDynamic().bootstrapModule(AppModule); 25 | -------------------------------------------------------------------------------- /ClientApp/test/boot-tests.ts: -------------------------------------------------------------------------------- 1 | // Load required polyfills and testing libraries 2 | import 'reflect-metadata'; 3 | import 'zone.js'; 4 | import 'zone.js/dist/long-stack-trace-zone'; 5 | import 'zone.js/dist/proxy.js'; 6 | import 'zone.js/dist/sync-test'; 7 | import 'zone.js/dist/jasmine-patch'; 8 | import 'zone.js/dist/async-test'; 9 | import 'zone.js/dist/fake-async-test'; 10 | import * as testing from '@angular/core/testing'; 11 | import * as testingBrowser from '@angular/platform-browser-dynamic/testing'; 12 | 13 | // There's no typing for the `__karma__` variable. Just declare it as any 14 | declare var __karma__: any; 15 | declare var require: any; 16 | 17 | // Prevent Karma from running prematurely 18 | __karma__.loaded = function () {}; 19 | 20 | // First, initialize the Angular testing environment 21 | testing.getTestBed().initTestEnvironment( 22 | testingBrowser.BrowserDynamicTestingModule, 23 | testingBrowser.platformBrowserDynamicTesting() 24 | ); 25 | 26 | // Then we find all the tests 27 | const context = require.context('../', true, /\.spec\.ts$/); 28 | 29 | // And load the modules 30 | context.keys().map(context); 31 | 32 | // Finally, start Karma to run the tests 33 | __karma__.start(); 34 | -------------------------------------------------------------------------------- /docs/dist/vendor-manifest.json: -------------------------------------------------------------------------------- 1 | {"name":"vendor_0d8620b209dffc725fd8","content":{"./node_modules/jquery/dist/jquery.js":{"id":0,"meta":{}},"./node_modules/process/browser.js":{"id":1,"meta":{}},"./node_modules/webpack/buildin/global.js":{"id":2,"meta":{}},"./node_modules/bootstrap/dist/js/npm.js":{"id":3,"meta":{}},"./node_modules/es6-promise/dist/es6-promise.js":{"id":4,"meta":{}},"./node_modules/es6-shim/es6-shim.js":{"id":5,"meta":{}},"./node_modules/event-source-polyfill/eventsource.js":{"id":6,"meta":{}},"./node_modules/bootstrap/dist/css/bootstrap.css":{"id":7,"meta":{}},"./node_modules/bootstrap/js/affix.js":{"id":8,"meta":{}},"./node_modules/bootstrap/js/alert.js":{"id":9,"meta":{}},"./node_modules/bootstrap/js/button.js":{"id":10,"meta":{}},"./node_modules/bootstrap/js/carousel.js":{"id":11,"meta":{}},"./node_modules/bootstrap/js/collapse.js":{"id":12,"meta":{}},"./node_modules/bootstrap/js/dropdown.js":{"id":13,"meta":{}},"./node_modules/bootstrap/js/modal.js":{"id":14,"meta":{}},"./node_modules/bootstrap/js/popover.js":{"id":15,"meta":{}},"./node_modules/bootstrap/js/scrollspy.js":{"id":16,"meta":{}},"./node_modules/bootstrap/js/tab.js":{"id":17,"meta":{}},"./node_modules/bootstrap/js/tooltip.js":{"id":18,"meta":{}},"./node_modules/bootstrap/js/transition.js":{"id":19,"meta":{}}}} -------------------------------------------------------------------------------- /ClientApp/app/components/home/home.component.html: -------------------------------------------------------------------------------- 1 |

Decimal time NOW

2 |

For those of us who want decimal time

3 |

10 hours in a day, each hour has 100 minutes, each minute has 100 seconds

4 |

Decimal Time Now:

5 |

{{ decimalNow }}

6 |
7 |
8 |

{{ error }}
9 |

10 |
11 |
12 | 13 | 20 | 21 | 22 | 29 | 30 |
31 |
32 | -------------------------------------------------------------------------------- /ClientApp/app/components/navmenu/navmenu.component.css: -------------------------------------------------------------------------------- 1 | li .glyphicon { 2 | margin-right: 10px; 3 | } 4 | 5 | /* Highlighting rules for nav menu items */ 6 | li.link-active a, 7 | li.link-active a:hover, 8 | li.link-active a:focus { 9 | background-color: #4189C7; 10 | color: white; 11 | } 12 | 13 | /* Keep the nav menu independent of scrolling and on top of other items */ 14 | .main-nav { 15 | position: fixed; 16 | top: 0; 17 | left: 0; 18 | right: 0; 19 | z-index: 1; 20 | } 21 | 22 | @media (min-width: 768px) { 23 | /* On small screens, convert the nav menu to a vertical sidebar */ 24 | .main-nav { 25 | height: 100%; 26 | width: calc(25% - 20px); 27 | } 28 | .navbar { 29 | border-radius: 0px; 30 | border-width: 0px; 31 | height: 100%; 32 | } 33 | .navbar-header { 34 | float: none; 35 | } 36 | .navbar-collapse { 37 | border-top: 1px solid #444; 38 | padding: 0px; 39 | } 40 | .navbar ul { 41 | float: none; 42 | } 43 | .navbar li { 44 | float: none; 45 | font-size: 15px; 46 | margin: 6px; 47 | } 48 | .navbar li a { 49 | padding: 10px 16px; 50 | border-radius: 4px; 51 | } 52 | .navbar a { 53 | /* If a menu item's text is too long, truncate it */ 54 | width: 100%; 55 | white-space: nowrap; 56 | overflow: hidden; 57 | text-overflow: ellipsis; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ClientApp/app/components/navmenu/navmenu.component.html: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /ClientApp/boot.server.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import 'zone.js'; 3 | import 'rxjs/add/operator/first'; 4 | import { APP_BASE_HREF } from '@angular/common'; 5 | import { enableProdMode, ApplicationRef, NgZone, ValueProvider } from '@angular/core'; 6 | import { platformDynamicServer, PlatformState, INITIAL_CONFIG } from '@angular/platform-server'; 7 | import { createServerRenderer, RenderResult } from 'aspnet-prerendering'; 8 | import { AppModule } from './app/app.module.server'; 9 | 10 | enableProdMode(); 11 | 12 | export default createServerRenderer(params => { 13 | const providers = [ 14 | { provide: INITIAL_CONFIG, useValue: { document: '', url: params.url } }, 15 | { provide: APP_BASE_HREF, useValue: params.baseUrl }, 16 | { provide: 'BASE_URL', useValue: params.origin + params.baseUrl }, 17 | ]; 18 | 19 | return platformDynamicServer(providers).bootstrapModule(AppModule).then(moduleRef => { 20 | const appRef: ApplicationRef = moduleRef.injector.get(ApplicationRef); 21 | const state = moduleRef.injector.get(PlatformState); 22 | const zone = moduleRef.injector.get(NgZone); 23 | 24 | return new Promise((resolve, reject) => { 25 | zone.onError.subscribe((errorInfo: any) => reject(errorInfo)); 26 | appRef.isStable.first(isStable => isStable).subscribe(() => { 27 | // Because 'onStable' fires before 'onError', we have to delay slightly before 28 | // completing the request in case there's an error to report 29 | setImmediate(() => { 30 | resolve({ 31 | html: state.renderToString() 32 | }); 33 | moduleRef.destroy(); 34 | }); 35 | }); 36 | }); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Raph_DecimalNow", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "test": "karma start ClientApp/test/karma.conf.js", 7 | "start": "node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js && node node_modules/webpack/bin/webpack.js && node ./node_modules/http-server/bin/http-server ./wwwroot", 8 | "publish": "npm install && node node_modules/webpack/bin/webpack.js --config webpack.config.vendor.js --env.prod && node node_modules/webpack/bin/webpack.js --env.prod" 9 | }, 10 | "dependencies": { 11 | "@angular/animations": "4.2.5", 12 | "@angular/common": "4.2.5", 13 | "@angular/compiler": "4.2.5", 14 | "@angular/compiler-cli": "4.2.5", 15 | "@angular/core": "4.2.5", 16 | "@angular/forms": "4.2.5", 17 | "@angular/http": "4.2.5", 18 | "@angular/platform-browser": "4.2.5", 19 | "@angular/platform-browser-dynamic": "4.2.5", 20 | "@angular/platform-server": "4.2.5", 21 | "@angular/router": "4.2.5", 22 | "@ngtools/webpack": "1.5.0", 23 | "@types/webpack-env": "1.13.0", 24 | "angular2-template-loader": "0.6.2", 25 | "aspnet-prerendering": "^3.0.1", 26 | "aspnet-webpack": "^2.0.1", 27 | "awesome-typescript-loader": "3.2.1", 28 | "babel-polyfill": "^6.26.0", 29 | "bootstrap": "3.3.7", 30 | "css": "^2.2.3", 31 | "css-loader": "0.28.4", 32 | "es6-shim": "0.35.3", 33 | "event-source-polyfill": "0.0.9", 34 | "expose-loader": "0.7.3", 35 | "extract-text-webpack-plugin": "2.1.2", 36 | "file-loader": "0.11.2", 37 | "html-loader": "0.4.5", 38 | "http-server": "^0.11.1", 39 | "isomorphic-fetch": "2.2.1", 40 | "jquery": "3.2.1", 41 | "json-loader": "0.5.4", 42 | "preboot": "4.5.2", 43 | "raw-loader": "0.5.1", 44 | "reflect-metadata": "0.1.10", 45 | "rxjs": "5.4.2", 46 | "style-loader": "0.18.2", 47 | "to-string-loader": "1.1.5", 48 | "typescript": "2.4.1", 49 | "url-loader": "^1.0.1", 50 | "webpack": "2.5.1", 51 | "webpack-hot-middleware": "2.18.2", 52 | "webpack-merge": "4.1.0", 53 | "zone.js": "0.8.12" 54 | }, 55 | "devDependencies": { 56 | "@types/chai": "4.0.1", 57 | "@types/jasmine": "2.5.53", 58 | "chai": "4.0.2", 59 | "jasmine-core": "2.6.4", 60 | "karma": "^2.0.5", 61 | "karma-chai": "0.1.0", 62 | "karma-chrome-launcher": "2.2.0", 63 | "karma-cli": "1.0.1", 64 | "karma-jasmine": "1.1.0", 65 | "karma-webpack": "^3.0.0" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const merge = require('webpack-merge'); 4 | const AotPlugin = require('@ngtools/webpack').AotPlugin; 5 | const CheckerPlugin = require('awesome-typescript-loader').CheckerPlugin; 6 | 7 | module.exports = (env) => { 8 | // Configuration in common to both client-side and server-side bundles 9 | const isDevBuild = !(env && env.prod); 10 | const sharedConfig = { 11 | stats: { modules: false }, 12 | context: __dirname, 13 | resolve: { extensions: [ '.js', '.ts' ] }, 14 | output: { 15 | filename: '[name].js', 16 | publicPath: 'dist/' // Webpack dev middleware, if enabled, handles requests for this URL prefix 17 | }, 18 | module: { 19 | rules: [ 20 | { test: /\.ts$/, include: /ClientApp/, use: isDevBuild ? ['awesome-typescript-loader?silent=true', 'angular2-template-loader'] : '@ngtools/webpack' }, 21 | { test: /\.html$/, use: 'html-loader?minimize=false' }, 22 | { test: /\.css$/, use: [ 'to-string-loader', isDevBuild ? 'css-loader' : 'css-loader?minimize' ] }, 23 | { test: /\.(png|jpg|jpeg|gif|svg)$/, use: 'url-loader?limit=25000' } 24 | ] 25 | }, 26 | plugins: [new CheckerPlugin()] 27 | }; 28 | 29 | // Configuration for client-side bundle suitable for running in browsers 30 | const clientBundleOutputDir = './wwwroot/dist'; 31 | const clientBundleConfig = merge(sharedConfig, { 32 | entry: { 'main-client': './ClientApp/boot.browser.ts' }, 33 | output: { path: path.join(__dirname, clientBundleOutputDir) }, 34 | plugins: [ 35 | new webpack.DllReferencePlugin({ 36 | context: __dirname, 37 | manifest: require('./wwwroot/dist/vendor-manifest.json') 38 | }) 39 | ].concat(isDevBuild ? [ 40 | // Plugins that apply in development builds only 41 | new webpack.SourceMapDevToolPlugin({ 42 | filename: '[file].map', // Remove this line if you prefer inline source maps 43 | moduleFilenameTemplate: path.relative(clientBundleOutputDir, '[resourcePath]') // Point sourcemap entries to the original file locations on disk 44 | }) 45 | ] : [ 46 | // Plugins that apply in production builds only 47 | new webpack.optimize.UglifyJsPlugin(), 48 | new AotPlugin({ 49 | tsConfigPath: './tsconfig.json', 50 | entryModule: path.join(__dirname, 'ClientApp/app/app.module.browser#AppModule'), 51 | exclude: ['./**/*.server.ts'] 52 | }) 53 | ]) 54 | }); 55 | 56 | // Configuration for server-side (prerendering) bundle suitable for running in Node 57 | const serverBundleConfig = merge(sharedConfig, { 58 | resolve: { mainFields: ['main'] }, 59 | entry: { 'main-server': './ClientApp/boot.server.ts' }, 60 | plugins: [ 61 | new webpack.DllReferencePlugin({ 62 | context: __dirname, 63 | manifest: require('./ClientApp/dist/vendor-manifest.json'), 64 | sourceType: 'commonjs2', 65 | name: './vendor' 66 | }) 67 | ].concat(isDevBuild ? [] : [ 68 | // Plugins that apply in production builds only 69 | new AotPlugin({ 70 | tsConfigPath: './tsconfig.json', 71 | entryModule: path.join(__dirname, 'ClientApp/app/app.module.server#AppModule'), 72 | exclude: ['./**/*.browser.ts'] 73 | }) 74 | ]), 75 | output: { 76 | libraryTarget: 'commonjs', 77 | path: path.join(__dirname, './ClientApp/dist') 78 | }, 79 | target: 'node', 80 | devtool: 'inline-source-map' 81 | }); 82 | 83 | return [clientBundleConfig, serverBundleConfig]; 84 | }; 85 | -------------------------------------------------------------------------------- /webpack.config.vendor.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | const merge = require('webpack-merge'); 5 | const treeShakableModules = [ 6 | '@angular/animations', 7 | '@angular/common', 8 | '@angular/compiler', 9 | '@angular/core', 10 | '@angular/forms', 11 | '@angular/http', 12 | '@angular/platform-browser', 13 | '@angular/platform-browser-dynamic', 14 | '@angular/router', 15 | 'zone.js', 16 | ]; 17 | const nonTreeShakableModules = [ 18 | 'bootstrap', 19 | 'bootstrap/dist/css/bootstrap.css', 20 | 'es6-promise', 21 | 'es6-shim', 22 | 'event-source-polyfill', 23 | 'jquery', 24 | ]; 25 | const allModules = treeShakableModules.concat(nonTreeShakableModules); 26 | 27 | module.exports = (env) => { 28 | const extractCSS = new ExtractTextPlugin('vendor.css'); 29 | const isDevBuild = !(env && env.prod); 30 | const sharedConfig = { 31 | stats: { modules: false }, 32 | resolve: { extensions: [ '.js' ] }, 33 | module: { 34 | rules: [ 35 | { test: /\.(png|woff|woff2|eot|ttf|svg)(\?|$)/, use: 'url-loader?limit=100000' } 36 | ] 37 | }, 38 | output: { 39 | publicPath: 'dist/', 40 | filename: '[name].js', 41 | library: '[name]_[hash]' 42 | }, 43 | plugins: [ 44 | new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }), // Maps these identifiers to the jQuery package (because Bootstrap expects it to be a global variable) 45 | new webpack.ContextReplacementPlugin(/\@angular\b.*\b(bundles|linker)/, path.join(__dirname, './ClientApp')), // Workaround for https://github.com/angular/angular/issues/11580 46 | new webpack.ContextReplacementPlugin(/angular(\\|\/)core(\\|\/)@angular/, path.join(__dirname, './ClientApp')), // Workaround for https://github.com/angular/angular/issues/14898 47 | new webpack.IgnorePlugin(/^vertx$/) // Workaround for https://github.com/stefanpenner/es6-promise/issues/100 48 | ] 49 | }; 50 | 51 | const clientBundleConfig = merge(sharedConfig, { 52 | entry: { 53 | // To keep development builds fast, include all vendor dependencies in the vendor bundle. 54 | // But for production builds, leave the tree-shakable ones out so the AOT compiler can produce a smaller bundle. 55 | vendor: isDevBuild ? allModules : nonTreeShakableModules 56 | }, 57 | output: { path: path.join(__dirname, 'wwwroot', 'dist') }, 58 | module: { 59 | rules: [ 60 | { test: /\.css(\?|$)/, use: extractCSS.extract({ use: isDevBuild ? 'css-loader' : 'css-loader?minimize' }) } 61 | ] 62 | }, 63 | plugins: [ 64 | extractCSS, 65 | new webpack.DllPlugin({ 66 | path: path.join(__dirname, 'wwwroot', 'dist', '[name]-manifest.json'), 67 | name: '[name]_[hash]' 68 | }) 69 | ].concat(isDevBuild ? [] : [ 70 | new webpack.optimize.UglifyJsPlugin() 71 | ]) 72 | }); 73 | 74 | const serverBundleConfig = merge(sharedConfig, { 75 | target: 'node', 76 | resolve: { mainFields: ['main'] }, 77 | entry: { vendor: allModules.concat(['aspnet-prerendering']) }, 78 | output: { 79 | path: path.join(__dirname, 'ClientApp', 'dist'), 80 | libraryTarget: 'commonjs2', 81 | }, 82 | module: { 83 | rules: [ { test: /\.css(\?|$)/, use: ['to-string-loader', isDevBuild ? 'css-loader' : 'css-loader?minimize' ] } ] 84 | }, 85 | plugins: [ 86 | new webpack.DllPlugin({ 87 | path: path.join(__dirname, 'ClientApp', 'dist', '[name]-manifest.json'), 88 | name: '[name]_[hash]' 89 | }) 90 | ] 91 | }); 92 | 93 | return [clientBundleConfig, serverBundleConfig]; 94 | } 95 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Properties/launchSettings.json 2 | 3 | ## Ignore Visual Studio temporary files, build results, and 4 | ## files generated by popular Visual Studio add-ons. 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | build/ 23 | bld/ 24 | bin/ 25 | Bin/ 26 | obj/ 27 | Obj/ 28 | 29 | # Visual Studio 2015 cache/options directory 30 | .vs/ 31 | /wwwroot/dist/ 32 | /ClientApp/dist/ 33 | 34 | # MSTest test Results 35 | [Tt]est[Rr]esult*/ 36 | [Bb]uild[Ll]og.* 37 | 38 | # NUNIT 39 | *.VisualState.xml 40 | TestResult.xml 41 | 42 | # Build Results of an ATL Project 43 | [Dd]ebugPS/ 44 | [Rr]eleasePS/ 45 | dlldata.c 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | 84 | # Visual Studio profiler 85 | *.psess 86 | *.vsp 87 | *.vspx 88 | *.sap 89 | 90 | # TFS 2012 Local Workspace 91 | $tf/ 92 | 93 | # Guidance Automation Toolkit 94 | *.gpState 95 | 96 | # ReSharper is a .NET coding add-in 97 | _ReSharper*/ 98 | *.[Rr]e[Ss]harper 99 | *.DotSettings.user 100 | 101 | # JustCode is a .NET coding add-in 102 | .JustCode 103 | 104 | # TeamCity is a build add-in 105 | _TeamCity* 106 | 107 | # DotCover is a Code Coverage Tool 108 | *.dotCover 109 | 110 | # NCrunch 111 | _NCrunch_* 112 | .*crunch*.local.xml 113 | nCrunchTemp_* 114 | 115 | # MightyMoose 116 | *.mm.* 117 | AutoTest.Net/ 118 | 119 | # Web workbench (sass) 120 | .sass-cache/ 121 | 122 | # Installshield output folder 123 | [Ee]xpress/ 124 | 125 | # DocProject is a documentation generator add-in 126 | DocProject/buildhelp/ 127 | DocProject/Help/*.HxT 128 | DocProject/Help/*.HxC 129 | DocProject/Help/*.hhc 130 | DocProject/Help/*.hhk 131 | DocProject/Help/*.hhp 132 | DocProject/Help/Html2 133 | DocProject/Help/html 134 | 135 | # Click-Once directory 136 | publish/ 137 | 138 | # Publish Web Output 139 | *.[Pp]ublish.xml 140 | *.azurePubxml 141 | # TODO: Comment the next line if you want to checkin your web deploy settings 142 | # but database connection strings (with potential passwords) will be unencrypted 143 | *.pubxml 144 | *.publishproj 145 | 146 | # NuGet Packages 147 | *.nupkg 148 | # The packages folder can be ignored because of Package Restore 149 | **/packages/* 150 | # except build/, which is used as an MSBuild target. 151 | !**/packages/build/ 152 | # Uncomment if necessary however generally it will be regenerated when needed 153 | #!**/packages/repositories.config 154 | 155 | # Microsoft Azure Build Output 156 | csx/ 157 | *.build.csdef 158 | 159 | # Microsoft Azure Emulator 160 | ecf/ 161 | rcf/ 162 | 163 | # Microsoft Azure ApplicationInsights config file 164 | ApplicationInsights.config 165 | 166 | # Windows Store app package directory 167 | AppPackages/ 168 | BundleArtifacts/ 169 | 170 | # Visual Studio cache files 171 | # files ending in .cache can be ignored 172 | *.[Cc]ache 173 | # but keep track of directories ending in .cache 174 | !*.[Cc]ache/ 175 | 176 | # Others 177 | ClientBin/ 178 | ~$* 179 | *~ 180 | *.dbmdl 181 | *.dbproj.schemaview 182 | *.pfx 183 | *.publishsettings 184 | orleans.codegen.cs 185 | 186 | /node_modules 187 | 188 | /yarn.lock 189 | 190 | # RIA/Silverlight projects 191 | Generated_Code/ 192 | 193 | # Backup & report files from converting an old project file 194 | # to a newer Visual Studio version. Backup files are not needed, 195 | # because we have git ;-) 196 | _UpgradeReport_Files/ 197 | Backup*/ 198 | UpgradeLog*.XML 199 | UpgradeLog*.htm 200 | 201 | # SQL Server files 202 | *.mdf 203 | *.ldf 204 | 205 | # Business Intelligence projects 206 | *.rdl.data 207 | *.bim.layout 208 | *.bim_*.settings 209 | 210 | # Microsoft Fakes 211 | FakesAssemblies/ 212 | 213 | # GhostDoc plugin setting file 214 | *.GhostDoc.xml 215 | 216 | # Node.js Tools for Visual Studio 217 | .ntvs_analysis.dat 218 | 219 | # Visual Studio 6 build log 220 | *.plg 221 | 222 | # Visual Studio 6 workspace options file 223 | *.opt 224 | 225 | # Visual Studio LightSwitch build output 226 | **/*.HTMLClient/GeneratedArtifacts 227 | **/*.DesktopClient/GeneratedArtifacts 228 | **/*.DesktopClient/ModelManifest.xml 229 | **/*.Server/GeneratedArtifacts 230 | **/*.Server/ModelManifest.xml 231 | _Pvt_Extensions 232 | 233 | # Paket dependency manager 234 | .paket/paket.exe 235 | 236 | # FAKE - F# Make 237 | .fake/ 238 | -------------------------------------------------------------------------------- /ClientApp/app/components/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Inject } from '@angular/core'; 2 | import { PLATFORM_ID } from '@angular/core'; 3 | import { isPlatformBrowser } from '@angular/common'; 4 | 5 | @Component({ 6 | selector: 'home', 7 | templateUrl: './home.component.html' 8 | }) 9 | 10 | export class HomeComponent implements OnInit { 11 | 12 | public decimalNow: string; 13 | public inputOutputDecimalTime: string; 14 | public inputOutputTime: string; 15 | public errors: string[]; 16 | private inputChanged: string; 17 | private inputChangedDecimal = "decimalTime"; 18 | private inputChangedTime = "time"; 19 | 20 | constructor( @Inject(PLATFORM_ID) private platformId: Object) { } 21 | 22 | ngOnInit(): void { 23 | if (isPlatformBrowser(this.platformId)) { 24 | setInterval(() => { 25 | this.setDecimalTimeNow(); 26 | }, 0); 27 | } 28 | } 29 | 30 | public convert() { 31 | this.errors = []; 32 | if (this.inputChanged === this.inputChangedTime) { 33 | this.convertInputToDecimalTime(); 34 | } else if (this.inputChanged === this.inputChangedDecimal) { 35 | this.convertInputToTime(); 36 | } else { 37 | this.errors.push("you must change one of the inputs"); 38 | } 39 | } 40 | 41 | public decimalTimeInputChanged() { 42 | this.inputChanged = this.inputChangedDecimal; 43 | } 44 | 45 | public timeInputChanged() { 46 | this.inputChanged = this.inputChangedTime; 47 | } 48 | 49 | private setDecimalTime(hours: number, minutes: number, seconds: number) { 50 | this.decimalNow = this.formatDecimalTime(this.getDecimalTime(hours, minutes, seconds)); 51 | } 52 | 53 | private convertInputToDecimalTime() { 54 | if (this.inputOutputTime) { 55 | var timeArr = this.inputOutputTime.split(':'); 56 | var hours = timeArr[0]; 57 | var minutes = timeArr[1]; 58 | var seconds = timeArr[2]; 59 | if (!seconds) { 60 | seconds = "0"; 61 | } 62 | this.inputOutputDecimalTime = this.formatDecimalTime(this.getDecimalTime(parseInt(hours), parseInt(minutes), parseInt(seconds))); 63 | } else { 64 | this.errors.push("Please input valid time"); 65 | } 66 | } 67 | 68 | private formatDecimalTime(decimalTime: number) { 69 | if (decimalTime === 0) { 70 | return "00.00"; 71 | } 72 | if (!decimalTime) { 73 | return ""; 74 | } 75 | var decimalTimeAsString = decimalTime.toString(); 76 | if (decimalTimeAsString.length === 3) { 77 | return decimalTimeAsString.slice(0, 1) + '.' + 78 | decimalTimeAsString.slice(2, 3) + '0'; 79 | } 80 | if (decimalTimeAsString.length === 1) { 81 | return decimalTimeAsString + '.00'; 82 | } 83 | var secondsStr = decimalTimeAsString.slice(4, 6); 84 | if (secondsStr.length < 2) { 85 | secondsStr += '0'; 86 | } 87 | return decimalTimeAsString.slice(0, 1) + '.' + 88 | decimalTimeAsString.slice(2, 4) + '.' + 89 | secondsStr; 90 | } 91 | 92 | private getDecimalTime(hours: number, minutes: number, seconds: number) { 93 | var dhours = hours * 5 / 12; 94 | var dminutes = minutes * 1000 / 144 / 1000; 95 | var dseconds = seconds * 1000 / 864 / 10000; 96 | return dhours + dminutes + dseconds; 97 | } 98 | 99 | private getTime(dhours: number, dminutes: number, dseconds: number) { 100 | var hours = (dhours * 8640); 101 | var minutes = dminutes * (864/10); 102 | var seconds = (dseconds * 60) / 100; 103 | return new Date(2000, 1, 1, 0, 0, seconds + hours + minutes).toLocaleTimeString('en-GB'); 104 | } 105 | 106 | private setDecimalTimeNow() { 107 | var now = new Date(); 108 | this.setDecimalTime(now.getHours(), now.getMinutes(), now.getSeconds()); 109 | } 110 | 111 | private convertInputToTime() { 112 | try { 113 | var splitedDecimalTime = this.inputOutputDecimalTime.split('.'); 114 | var dhours = parseInt(splitedDecimalTime[0]); 115 | var dminutesStr = splitedDecimalTime[1]; 116 | var dminutes = parseInt(dminutesStr); 117 | var dsecondsStr = splitedDecimalTime[2]; 118 | var dseconds = parseInt(dsecondsStr); 119 | if (dminutesStr.length !== 2) { 120 | this.errors.push("Decimal minutes should be two digits. Example: '00', '05' or '20'"); 121 | } else if (dminutes < 0 || dminutes >= 100) { 122 | this.errors.push("Decimal minutes should be between 0 and 99 inclussive"); 123 | } 124 | 125 | if (dsecondsStr && (dsecondsStr.length !== 2)) { 126 | this.errors.push("Decimal seconds should be two digits. Example: '00', '05' or '20'"); 127 | } else if (dsecondsStr && (dseconds < 0 || dseconds >= 100)) { 128 | this.errors.push("Decimal seconds should be between 0 and 99 inclussive"); 129 | } 130 | else if (!dsecondsStr) { 131 | dseconds = 0; 132 | } 133 | if (dhours < 0 || dhours > 10) { 134 | this.errors.push("Decimal hours should be between 0 and 10 inclussive"); 135 | } 136 | if (this.errors.length <= 0) { 137 | this.inputOutputTime = this.getTime(dhours, dminutes, dseconds); 138 | } 139 | } catch (error) { 140 | this.errors.push( 141 | "You must enter decimal time in a proper format. See example above (decimal seconds are optional)"); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /ClientApp/app/components/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { assert } from 'chai'; 3 | import { HomeComponent } from './home.component'; 4 | import { TestBed, async, ComponentFixture } from '@angular/core/testing'; 5 | import { FormsModule } from '@angular/forms'; 6 | let fixture: ComponentFixture; 7 | 8 | // For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 9 | 10 | describe('Home component', () => { 11 | beforeEach(() => { 12 | TestBed.configureTestingModule({ imports: [FormsModule], declarations: [HomeComponent] }); 13 | fixture = TestBed.createComponent(HomeComponent); 14 | fixture.detectChanges(); 15 | }); 16 | 17 | it('24 hours should return 1 decimal day (10 decimal hours)', async(() => { 18 | fixture.componentInstance.inputOutputTime = new Date(1980, 6, 6, 24, 0, 0).toLocaleTimeString(); 19 | fixture.componentInstance.timeInputChanged(); 20 | 21 | fixture.componentInstance.convert(); 22 | 23 | expect(fixture.componentInstance.inputOutputDecimalTime).toEqual("00.00"); 24 | })); 25 | 26 | it('12 hours should return 0.5 decimal day (5 decimal hours)', async(() => { 27 | fixture.componentInstance.inputOutputTime = new Date(1980, 6, 6, 12, 0, 0).toLocaleTimeString(); 28 | fixture.componentInstance.timeInputChanged(); 29 | 30 | fixture.componentInstance.convert(); 31 | 32 | expect(fixture.componentInstance.inputOutputDecimalTime).toEqual("5.00"); 33 | })); 34 | 35 | it('should convert inputed time AM', async(() => { 36 | fixture.componentInstance.inputOutputTime = '9:30'; 37 | fixture.componentInstance.timeInputChanged(); 38 | fixture.componentInstance.convert(); 39 | 40 | expect(fixture.componentInstance.inputOutputDecimalTime).toEqual("3.95.83"); 41 | })); 42 | 43 | it('should convert inputed time PM', async(() => { 44 | fixture.componentInstance.inputOutputTime = '17:30'; 45 | fixture.componentInstance.timeInputChanged(); 46 | fixture.componentInstance.convert(); 47 | 48 | expect(fixture.componentInstance.inputOutputDecimalTime).toEqual("7.29.16"); 49 | })); 50 | 51 | it('should convert inputed time with nice format', async(() => { 52 | fixture.componentInstance.inputOutputTime = '03:02'; 53 | fixture.componentInstance.timeInputChanged(); 54 | fixture.componentInstance.convert(); 55 | 56 | expect(fixture.componentInstance.inputOutputDecimalTime).toEqual("1.26.38"); 57 | })); 58 | 59 | it('should convert decimal time to time', async(() => { 60 | fixture.componentInstance.inputOutputDecimalTime = "10.00.00"; 61 | fixture.componentInstance.decimalTimeInputChanged(); 62 | 63 | fixture.componentInstance.convert(); 64 | 65 | var expected = new Date(2000, 1, 1, 24, 0, 0); 66 | expect(fixture.componentInstance.inputOutputTime).toEqual(expected.toLocaleTimeString()); 67 | })); 68 | 69 | it('should convert decimal time to time for half day', async(() => { 70 | fixture.componentInstance.inputOutputDecimalTime = "5.00.00"; 71 | fixture.componentInstance.decimalTimeInputChanged(); 72 | 73 | fixture.componentInstance.convert(); 74 | 75 | var expected = new Date(2000, 1, 1, 12, 0, 0); 76 | expect(fixture.componentInstance.inputOutputTime).toEqual(expected.toLocaleTimeString()); 77 | })); 78 | 79 | it('should convert decimal time to time for fifth of a day', async(() => { 80 | fixture.componentInstance.inputOutputDecimalTime = "2.00.00"; 81 | fixture.componentInstance.decimalTimeInputChanged(); 82 | 83 | fixture.componentInstance.convert(); 84 | 85 | var expected = new Date(2000, 0, 0, 4, 48, 0); 86 | expect(fixture.componentInstance.inputOutputTime).toEqual(expected.toLocaleTimeString()); 87 | })); 88 | 89 | it('should convert decimal time to time for fourth of a minute', async(() => { 90 | fixture.componentInstance.inputOutputDecimalTime = "0.00.25"; 91 | fixture.componentInstance.decimalTimeInputChanged(); 92 | 93 | fixture.componentInstance.convert(); 94 | 95 | var expected = new Date(2000, 1, 1, 0, 0, 15); 96 | expect(fixture.componentInstance.inputOutputTime).toEqual(expected.toLocaleTimeString()); 97 | })); 98 | 99 | it('should convert decimal time to time for 30 seconds', async(() => { 100 | fixture.componentInstance.inputOutputDecimalTime = "0.00.50"; 101 | fixture.componentInstance.decimalTimeInputChanged(); 102 | 103 | fixture.componentInstance.convert(); 104 | 105 | var expected = new Date(2000, 1, 1, 0, 0, 30); 106 | expect(fixture.componentInstance.inputOutputTime).toEqual(expected.toLocaleTimeString()); 107 | })); 108 | 109 | it('should convert decimal time to time for quarter day', async(() => { 110 | fixture.componentInstance.inputOutputDecimalTime = "2.50.00"; 111 | fixture.componentInstance.decimalTimeInputChanged(); 112 | 113 | fixture.componentInstance.convert(); 114 | 115 | var expected = new Date(2000, 1, 1, 6, 0, 0); 116 | expect(fixture.componentInstance.inputOutputTime).toEqual(expected.toLocaleTimeString()); 117 | })); 118 | 119 | it('should convert time to decimal time for quarter day', async(() => { 120 | fixture.componentInstance.inputOutputTime = new Date(2000, 1, 1, 6, 0, 0).toLocaleTimeString(); 121 | fixture.componentInstance.timeInputChanged(); 122 | 123 | fixture.componentInstance.convert(); 124 | 125 | expect(fixture.componentInstance.inputOutputDecimalTime).toEqual("2.50"); 126 | })); 127 | 128 | it('should convert decimal time to time for 4:30PM', async(() => { 129 | fixture.componentInstance.inputOutputDecimalTime = "6.88.00"; 130 | fixture.componentInstance.decimalTimeInputChanged(); 131 | 132 | fixture.componentInstance.convert(); 133 | 134 | var expected = new Date(2000, 1, 1, 16, 30, 43); 135 | expect(fixture.componentInstance.inputOutputTime).toEqual(expected.toLocaleTimeString()); 136 | })); 137 | 138 | it('should convert decimal time to time for 3:14PM', async(() => { 139 | fixture.componentInstance.inputOutputDecimalTime = "6.35.00"; 140 | fixture.componentInstance.decimalTimeInputChanged(); 141 | 142 | fixture.componentInstance.convert(); 143 | 144 | var expected = new Date(2000, 1, 1, 15, 14, 24); 145 | expect(fixture.componentInstance.inputOutputTime).toEqual(expected.toLocaleTimeString()); 146 | })); 147 | 148 | it('should convert decimal time to time for 7:09PM', async(() => { 149 | fixture.componentInstance.inputOutputDecimalTime = "7.98.00"; 150 | fixture.componentInstance.decimalTimeInputChanged(); 151 | 152 | fixture.componentInstance.convert(); 153 | 154 | var expected = new Date(2000, 1, 1, 19, 9, 7); 155 | expect(fixture.componentInstance.inputOutputTime).toEqual(expected.toLocaleTimeString()); 156 | })); 157 | 158 | it('should validate decimal time format when only minute', async(() => { 159 | fixture.componentInstance.inputOutputDecimalTime = "6.35"; 160 | fixture.componentInstance.decimalTimeInputChanged(); 161 | 162 | fixture.componentInstance.convert(); 163 | 164 | var hasError = fixture.componentInstance.errors.length > 0; 165 | expect(hasError).toBe(false); 166 | })); 167 | 168 | it('should error decimal time format when hour greater than 10', async(() => { 169 | fixture.componentInstance.inputOutputDecimalTime = "11.35"; 170 | fixture.componentInstance.decimalTimeInputChanged(); 171 | 172 | fixture.componentInstance.convert(); 173 | 174 | var hasError = fixture.componentInstance.errors.length > 0; 175 | expect(hasError).toBe(true); 176 | })); 177 | 178 | it('should error decimal time format when hour less than 0', async(() => { 179 | fixture.componentInstance.inputOutputDecimalTime = "-1.35"; 180 | fixture.componentInstance.decimalTimeInputChanged(); 181 | 182 | fixture.componentInstance.convert(); 183 | 184 | var hasError = fixture.componentInstance.errors.length > 0; 185 | expect(hasError).toBe(true); 186 | })); 187 | 188 | it('should error decimal time format when minute is more than 99', async(() => { 189 | fixture.componentInstance.inputOutputDecimalTime = "1.100"; 190 | fixture.componentInstance.decimalTimeInputChanged(); 191 | 192 | fixture.componentInstance.convert(); 193 | 194 | var hasError = fixture.componentInstance.errors.length > 0; 195 | expect(hasError).toBe(true); 196 | })); 197 | 198 | it('should error decimal time format when minute is less than 0', async(() => { 199 | fixture.componentInstance.inputOutputDecimalTime = "1.-1"; 200 | fixture.componentInstance.decimalTimeInputChanged(); 201 | 202 | fixture.componentInstance.convert(); 203 | 204 | var hasError = fixture.componentInstance.errors.length > 0; 205 | expect(hasError).toBe(true); 206 | })); 207 | 208 | it('should error decimal time format when minute is not two digits', async(() => { 209 | fixture.componentInstance.inputOutputDecimalTime = "1.5"; 210 | fixture.componentInstance.decimalTimeInputChanged(); 211 | 212 | fixture.componentInstance.convert(); 213 | 214 | var hasExplanation = fixture.componentInstance.errors.filter(x => x.includes("two digits")).length > 0; 215 | expect(hasExplanation).toBe(true); 216 | })); 217 | 218 | 219 | it('should error decimal time format when second is more than 99', async(() => { 220 | fixture.componentInstance.inputOutputDecimalTime = "1.10.100"; 221 | fixture.componentInstance.decimalTimeInputChanged(); 222 | 223 | fixture.componentInstance.convert(); 224 | 225 | var hasError = fixture.componentInstance.errors.length > 0; 226 | expect(hasError).toBe(true); 227 | })); 228 | 229 | it('should error decimal time format when second is less than 0', async(() => { 230 | fixture.componentInstance.inputOutputDecimalTime = "1.10.-1"; 231 | fixture.componentInstance.decimalTimeInputChanged(); 232 | 233 | fixture.componentInstance.convert(); 234 | 235 | var hasError = fixture.componentInstance.errors.length > 0; 236 | expect(hasError).toBe(true); 237 | })); 238 | 239 | it('should validate decimal time format when second is 00', async(() => { 240 | fixture.componentInstance.inputOutputDecimalTime = "1.10.00"; 241 | fixture.componentInstance.decimalTimeInputChanged(); 242 | 243 | fixture.componentInstance.convert(); 244 | 245 | var hasError = fixture.componentInstance.errors.length > 0; 246 | expect(hasError).toBe(false); 247 | })); 248 | 249 | it('should error decimal time format when second is not two digits', async(() => { 250 | fixture.componentInstance.inputOutputDecimalTime = "1.50.4"; 251 | fixture.componentInstance.decimalTimeInputChanged(); 252 | 253 | fixture.componentInstance.convert(); 254 | 255 | var hasExplanation = fixture.componentInstance.errors.filter(x => x.includes("two digits")).length > 0; 256 | expect(hasExplanation).toBe(true); 257 | })); 258 | 259 | it('should calculate valid date if only decimal hours and decimal minutes are given', async(() => { 260 | fixture.componentInstance.inputOutputDecimalTime = "7.98"; 261 | fixture.componentInstance.decimalTimeInputChanged(); 262 | 263 | fixture.componentInstance.convert(); 264 | 265 | var expected = new Date(2000, 1, 1, 19, 9, 7); 266 | expect(fixture.componentInstance.inputOutputTime).toEqual(expected.toLocaleTimeString()); 267 | })); 268 | 269 | it('should append zero to decimal seconds', async(() => { 270 | fixture.componentInstance.inputOutputTime = new Date(2000, 1, 1, 19, 30, 0).toLocaleTimeString(); 271 | fixture.componentInstance.timeInputChanged(); 272 | 273 | fixture.componentInstance.convert(); 274 | 275 | expect(fixture.componentInstance.inputOutputDecimalTime).toEqual("8.12.50"); 276 | })); 277 | 278 | it('should convert 2:24:00 AM to 1.00', async(() => { 279 | fixture.componentInstance.inputOutputTime = new Date(2000, 1, 1, 2, 24, 0).toLocaleTimeString(); 280 | fixture.componentInstance.timeInputChanged(); 281 | 282 | fixture.componentInstance.convert(); 283 | 284 | expect(fixture.componentInstance.inputOutputDecimalTime).toEqual("1.00"); 285 | })); 286 | 287 | it('should error on first attempt to convert', async(() => { 288 | fixture.componentInstance.convert(); 289 | 290 | var hasExplanation = fixture.componentInstance.errors.filter(x => x.includes("must change one of the inputs")).length > 0; 291 | expect(hasExplanation).toBe(true); 292 | })); 293 | }); 294 | -------------------------------------------------------------------------------- /docs/dist/89889688147bd7575d6327160d64e760.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 | --------------------------------------------------------------------------------