├── src ├── assets │ ├── .gitkeep │ ├── horizon.png │ └── configuration.json ├── app │ ├── app.query.graphql │ ├── app.mutation.graphql │ ├── app.component.css │ ├── app-routing.module.ts │ ├── core │ │ ├── services │ │ │ ├── index.ts │ │ │ ├── authentication.service.ts │ │ │ ├── authentication.service.spec.ts │ │ │ ├── configuration.service.spec.ts │ │ │ ├── storage.service.ts │ │ │ ├── configuration.service.ts │ │ │ └── storage.service.spec.ts │ │ ├── initializers.ts │ │ ├── core.module.ts │ │ └── graphql │ │ │ └── graphql.module.ts │ ├── layout │ │ ├── layouts │ │ │ ├── box.component.ts │ │ │ ├── center.component.ts │ │ │ ├── grid.component.ts │ │ │ ├── imposter.component.ts │ │ │ ├── cluster.component.ts │ │ │ ├── stack.component.ts │ │ │ ├── sidebar.component.ts │ │ │ ├── switcher.component.ts │ │ │ ├── frame.component.ts │ │ │ ├── reel.component.ts │ │ │ └── cover.component.ts │ │ └── layout.module.ts │ ├── app.module.ts │ ├── app.component.ts │ ├── app.component.spec.ts │ └── app.component.html ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── favicon.ico ├── index.html ├── main.ts ├── styles.css └── polyfills.ts ├── setupJest.ts ├── .eslintignore ├── e2e ├── tsconfig.json ├── src │ ├── app.po.ts │ └── app.e2e-spec.ts └── protractor.conf.js ├── tsconfig.app.json ├── .editorconfig ├── codegen.json ├── tsconfig.spec.json ├── browserslist ├── tsconfig.json ├── .gitignore ├── README.md ├── package.json ├── .eslintrc.js └── angular.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setupJest.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; 2 | -------------------------------------------------------------------------------- /src/app/app.query.graphql: -------------------------------------------------------------------------------- 1 | query Time { 2 | time 3 | } -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # itself :( 2 | .eslintrc.js 3 | 4 | # generated files 5 | src/app/generated -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilvanCodes/angular-apollo-blueprint/master/src/favicon.ico -------------------------------------------------------------------------------- /src/assets/horizon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilvanCodes/angular-apollo-blueprint/master/src/assets/horizon.png -------------------------------------------------------------------------------- /src/assets/configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "storage": "localStorage", 3 | "gqlEndpoint": "http://localhost:8080/graphql" 4 | } -------------------------------------------------------------------------------- /src/app/app.mutation.graphql: -------------------------------------------------------------------------------- 1 | mutation Login($username: String!, $password: String!) { 2 | login(username: $username, password: $password) 3 | } 4 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | .small-stack { 2 | --margin: var(--s1); 3 | --padding: var(--s-2); 4 | } 5 | 6 | .big-stack { 7 | --margin: var(--s3); 8 | } 9 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es5", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Routes, RouterModule } from '@angular/router'; 3 | 4 | 5 | const routes: Routes = []; 6 | 7 | @NgModule({ 8 | imports: [RouterModule.forRoot(routes)], 9 | exports: [RouterModule] 10 | }) 11 | export class AppRoutingModule { } 12 | -------------------------------------------------------------------------------- /src/app/core/services/index.ts: -------------------------------------------------------------------------------- 1 | import { StorageService } from './storage.service'; 2 | import { ConfigurationService } from './configuration.service'; 3 | import { AuthenticationService } from './authentication.service'; 4 | 5 | 6 | export { 7 | StorageService, 8 | ConfigurationService, 9 | AuthenticationService 10 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://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 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /src/app/core/initializers.ts: -------------------------------------------------------------------------------- 1 | import { ConfigurationService } from './services'; 2 | import { Configuration } from './services/configuration.service'; 3 | 4 | 5 | export const initializeConfiguration = (configuration: ConfigurationService) => { 6 | return (): Promise => { 7 | return configuration.fetchConfiguration(); 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('app-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Blueprint 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /codegen.json: -------------------------------------------------------------------------------- 1 | { 2 | "schema": "http://localhost:4000", 3 | "documents": "src/app/**/*.graphql", 4 | "generates": { 5 | "src/app/generated/index.ts": { 6 | "plugins": [ 7 | "typescript", 8 | "typescript-operations", 9 | "typescript-apollo-angular" 10 | ] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "emitDecoratorMetadata": true, 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine", 8 | "node" 9 | ] 10 | }, 11 | "files": [ 12 | "src/test.ts", 13 | "src/polyfills.ts" 14 | ], 15 | "include": [ 16 | "src/**/*.spec.ts", 17 | "src/**/*.d.ts" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /browserslist: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. -------------------------------------------------------------------------------- /src/app/layout/layouts/box.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'claiv-box', 5 | template: '', 6 | styles: [ 7 | `claiv-box { 8 | --padding: var(--s0); 9 | --border: var(--s-5); 10 | padding: var(--padding); 11 | border: var(--border) solid; 12 | }`, 13 | `claiv-box * { 14 | color: inherit 15 | }` 16 | ], 17 | encapsulation: ViewEncapsulation.None 18 | }) 19 | export class BoxComponent {} 20 | -------------------------------------------------------------------------------- /src/app/layout/layouts/center.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'claiv-center', 5 | template: '', 6 | styles: [ 7 | `claiv-center { 8 | margin-left: auto; 9 | margin-right: auto; 10 | box-sizing: content-box; 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | }`, 15 | ], 16 | encapsulation: ViewEncapsulation.None 17 | }) 18 | export class CenterComponent {} 19 | -------------------------------------------------------------------------------- /src/app/layout/layouts/grid.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'claiv-grid', 5 | template: '', 6 | styles: [ 7 | `claiv-grid { 8 | --margin: var(--s0); 9 | --min-width: 10rem; 10 | display: grid; 11 | grid-gap: 1rem; 12 | }`, 13 | `claiv-grid { 14 | grid-template-columns: repeat(auto-fit, minmax(min(var(--min-width), 100%), 1fr)); 15 | }` 16 | ], 17 | encapsulation: ViewEncapsulation.None 18 | }) 19 | export class GridComponent {} 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "lib": [ 15 | "es2018", 16 | "dom", 17 | "esnext.asynciterable" 18 | ] 19 | }, 20 | "angularCompilerOptions": { 21 | "fullTemplateTypeCheck": true, 22 | "strictInjectionParameters": true 23 | } 24 | } -------------------------------------------------------------------------------- /src/app/layout/layouts/imposter.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'claiv-imposter', 5 | template: '', 6 | styles: [ 7 | `claiv-imposter { 8 | --positioning: absolute; 9 | --margin: var(--s0); 10 | position: var(--positioning); 11 | top: 50%; 12 | left: 50%; 13 | transform: translate(-50%, -50%); 14 | }`, 15 | `claiv-imposter > :first-child { 16 | overflow: auto; 17 | max-width: calc(100% - (var(--margin) * 2)); 18 | max-height: calc(100% - (var(--margin) * 2)); 19 | }` 20 | ], 21 | encapsulation: ViewEncapsulation.None 22 | }) 23 | export class ImposterComponent {} 24 | -------------------------------------------------------------------------------- /e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('blueprint app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/app/layout/layouts/cluster.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'claiv-cluster', 5 | template: '
', 6 | styles: [ 7 | `claiv-cluster { 8 | --margin: var(--s0); 9 | --justify: flex-start; 10 | overflow: hidden; 11 | }`, 12 | `claiv-cluster > * { 13 | display: flex; 14 | flex-wrap: wrap; 15 | margin: calc(var(--margin) / 2 * -1); 16 | justify-content: var(--justify); 17 | align-items: center; 18 | }`, 19 | `claiv-cluster > * > * { 20 | margin: calc(var(--margin) / 2); 21 | }` 22 | ], 23 | encapsulation: ViewEncapsulation.None 24 | }) 25 | export class ClusterComponent {} 26 | -------------------------------------------------------------------------------- /src/app/layout/layouts/stack.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'claiv-stack', 5 | template: "", 6 | styles: [ 7 | `claiv-stack { 8 | --margin: var(--s0); 9 | --split: 0; 10 | display: flex; 11 | flex-direction: column; 12 | justify-content: flex-start; 13 | }`, 14 | `claiv-stack > :first-child:nth-last-child(2) { 15 | margin-bottom: var(--split, auto); 16 | }`, 17 | `claiv-stack > * + * { 18 | margin-top: var(--margin); 19 | }`, 20 | `claiv-stack:only-child { 21 | height: 100%; 22 | }` 23 | ], 24 | encapsulation: ViewEncapsulation.None 25 | }) 26 | export class StackComponent {} 27 | -------------------------------------------------------------------------------- /src/app/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, APP_INITIALIZER } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { GraphQLModule } from './graphql/graphql.module'; 4 | import { initializeConfiguration } from './initializers'; 5 | import { ConfigurationService } from './services'; 6 | import { httpInterceptorProviders } from './http-interceptors'; 7 | 8 | 9 | @NgModule({ 10 | declarations: [], 11 | imports: [ 12 | CommonModule, 13 | GraphQLModule 14 | ], 15 | providers: [ 16 | { 17 | provide: APP_INITIALIZER, 18 | useFactory: initializeConfiguration, 19 | multi: true, 20 | deps: [ConfigurationService] 21 | }, 22 | httpInterceptorProviders 23 | ], 24 | }) 25 | export class CoreModule { } 26 | -------------------------------------------------------------------------------- /src/app/layout/layouts/sidebar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'claiv-sidebar', 5 | template: "", 6 | styles: [ 7 | `claiv-sidebar { 8 | --sidebar-width: initial; 9 | --content-width: 50%; 10 | }`, 11 | `claiv-sidebar { 12 | display: flex; 13 | flex-wrap: wrap; 14 | }`, 15 | `claiv-sidebar > :first-child { 16 | flex-basis: var(--sidebar-width); 17 | flex-grow: 1; 18 | }`, 19 | `claiv-sidebar > :last-child { 20 | flex-basis: 0; 21 | flex-grow: 999; 22 | min-width: var(--content-width); 23 | }` 24 | ], 25 | encapsulation: ViewEncapsulation.None 26 | }) 27 | export class SidebarComponent {} 28 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | graphql: 'http://localhost:4000' 8 | }; 9 | 10 | /* 11 | * For easier debugging in development mode, you can import the following file 12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 13 | * 14 | * This import should be commented out in production mode because it will have a negative impact 15 | * on performance if an error is thrown. 16 | */ 17 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI. 18 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | 4 | import { AppRoutingModule } from './app-routing.module'; 5 | import { AppComponent } from './app.component'; 6 | import { HttpClientModule } from '@angular/common/http'; 7 | import { CoreModule } from './core/core.module'; 8 | import { LayoutModule } from './layout/layout.module'; 9 | import { ReactiveFormsModule } from '@angular/forms'; 10 | 11 | @NgModule({ 12 | declarations: [ 13 | AppComponent, 14 | ], 15 | imports: [ 16 | BrowserModule, 17 | AppRoutingModule, 18 | HttpClientModule, 19 | CoreModule, 20 | LayoutModule, 21 | ReactiveFormsModule 22 | ], 23 | providers: [], 24 | bootstrap: [AppComponent] 25 | }) 26 | export class AppModule { } 27 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { TimeGQL } from './generated'; 3 | import { Observable } from 'rxjs'; 4 | import { map } from 'rxjs/operators'; 5 | import { ConfigurationService } from './core/services'; 6 | 7 | @Component({ 8 | selector: 'app-root', 9 | templateUrl: './app.component.html', 10 | styleUrls: ['./app.component.css'] 11 | }) 12 | export class AppComponent { 13 | public title = 'blueprint'; 14 | public time$: Observable; 15 | public storageType$: Observable; 16 | 17 | constructor(private time: TimeGQL, private configuration: ConfigurationService) { 18 | this.time$ = this.time.watch().valueChanges.pipe( 19 | map(({ data }) => data.time) 20 | ); 21 | 22 | this.storageType$ = this.configuration.getItem$('storage'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/layout/layouts/switcher.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'claiv-switcher', 5 | template: "
", 6 | styles: [ 7 | `claiv-switcher { 8 | --margin: var(--s0); 9 | --min-width: var(--measure); 10 | }`, 11 | `claiv-switcher > *{ 12 | display: flex; 13 | flex-wrap: wrap; 14 | margin: calc(var(--margin) / 2 * -1); 15 | }`, 16 | `claiv-switcher > * > * { 17 | flex-grow: 1; 18 | flex-basis: calc((var(--min-width) - (100% - var(--margin))) * 999); 19 | margin: calc(var(--margin) / 2); 20 | }`, 21 | `claiv-switcher > * > :nth-child(2):nth-last-child(2) { 22 | flex-grow: 2; 23 | }` 24 | ], 25 | encapsulation: ViewEncapsulation.None 26 | }) 27 | export class SwitcherComponent {} -------------------------------------------------------------------------------- /src/app/layout/layouts/frame.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'claiv-frame', 5 | template: '', 6 | styles: [ 7 | `claiv-frame { 8 | --n: 9; 9 | --d: 16; 10 | padding-bottom: calc(var(--n) / var(--d) * 100%); 11 | position: relative; 12 | }`, 13 | `claiv-frame > * { 14 | overflow: hidden; 15 | position: absolute; 16 | top: 0; 17 | right: 0; 18 | bottom: 0; 19 | left: 0; 20 | display: flex; 21 | justify-content: center; 22 | align-items: center; 23 | }`, 24 | `claiv-frame > img, claiv-frame > video { 25 | width: 100%; 26 | height: 100%; 27 | object-fit: cover; 28 | }` 29 | ], 30 | encapsulation: ViewEncapsulation.None 31 | }) 32 | export class FrameComponent {} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # generated output 4 | /src/app/generated 5 | 6 | # compiled output 7 | /dist 8 | /tmp 9 | /out-tsc 10 | # Only exists if Bazel was run 11 | /bazel-out 12 | 13 | # dependencies 14 | /node_modules 15 | 16 | # profiling files 17 | chrome-profiler-events*.json 18 | speed-measure-plugin*.json 19 | 20 | # IDEs and editors 21 | /.idea 22 | .project 23 | .classpath 24 | .c9/ 25 | *.launch 26 | .settings/ 27 | *.sublime-workspace 28 | 29 | # IDE - VSCode 30 | .vscode/* 31 | !.vscode/settings.json 32 | !.vscode/tasks.json 33 | !.vscode/launch.json 34 | !.vscode/extensions.json 35 | .history/* 36 | 37 | # misc 38 | /.sass-cache 39 | /connect.lock 40 | /coverage 41 | /libpeerconnection.log 42 | npm-debug.log 43 | yarn-error.log 44 | testem.log 45 | /typings 46 | 47 | # System Files 48 | .DS_Store 49 | Thumbs.db 50 | -------------------------------------------------------------------------------- /src/app/core/graphql/graphql.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { ApolloModule, APOLLO_OPTIONS } from 'apollo-angular'; 3 | import { HttpLinkModule, HttpLink, HttpLinkHandler } from 'apollo-angular-link-http'; 4 | import { InMemoryCache } from 'apollo-cache-inmemory'; 5 | import { ConfigurationService } from '../services'; 6 | 7 | 8 | export function createApollo(httpLink: HttpLink, config: ConfigurationService): { link: HttpLinkHandler; cache: InMemoryCache } { 9 | return { 10 | link: httpLink.create({ uri: config.getItem('gqlEndpoint') }), 11 | cache: new InMemoryCache(), 12 | }; 13 | } 14 | 15 | @NgModule({ 16 | exports: [ 17 | ApolloModule, 18 | HttpLinkModule 19 | ], 20 | providers: [ 21 | { 22 | provide: APOLLO_OPTIONS, 23 | useFactory: createApollo, 24 | deps: [HttpLink, ConfigurationService], 25 | }, 26 | ], 27 | }) 28 | export class GraphQLModule {} 29 | -------------------------------------------------------------------------------- /src/app/layout/layouts/reel.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'claiv-reel', 5 | template: '', 6 | styles: [ 7 | `claiv-reel { 8 | --height: auto; 9 | --margin: var(--s0); 10 | /* --border: var(--s-5); */ 11 | /* --padding: var(--s0); */ 12 | display: flex; 13 | overflow-x: scroll; 14 | overflow-y: hidden; 15 | height: var(--height); 16 | /* padding: var(--padding); */ 17 | /* border: var(--border) solid; */ 18 | }`, 19 | `claiv-reel > * { 20 | flex: 0 0 auto; 21 | }`, 22 | `claiv-reel > * + * { 23 | margin-left: var(--margin); 24 | }`, 25 | `claiv-reel > img { 26 | height: 100%; 27 | flex-basis: auto; 28 | width: auto; 29 | }` 30 | ], 31 | encapsulation: ViewEncapsulation.None 32 | }) 33 | export class ReelComponent {} 34 | -------------------------------------------------------------------------------- /e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /src/app/core/services/authentication.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { LoginGQL } from 'src/app/generated'; 3 | import { StorageService } from './storage.service'; 4 | import * as jwtDecode from 'jwt-decode'; 5 | import { Observable } from 'rxjs'; 6 | import { map } from 'rxjs/operators'; 7 | 8 | 9 | type Jwt = { 10 | username: string; 11 | exp: number; 12 | } 13 | 14 | @Injectable({ 15 | providedIn: 'root' 16 | }) 17 | export class AuthenticationService { 18 | 19 | constructor( 20 | private storage: StorageService 21 | ) { } 22 | 23 | // public doLogin(username: string, password: string): void { 24 | // this.login.mutate({ username, password }).subscribe(({ data: { login } }) => { 25 | // this.storage.setItem('jwt', login); 26 | // }); 27 | // } 28 | 29 | public get jwt$(): Observable { 30 | return this.storage.getItem$('jwt').pipe( 31 | map(jwt => jwt ? jwtDecode(jwt) : null) 32 | ); 33 | } 34 | 35 | public get isAuthenticated$(): Observable { 36 | return this.jwt$.pipe( 37 | map(jwt => jwt ? jwt.exp > Date.now() : false) 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/app/core/services/authentication.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { createServiceFactory, SpectatorService, mockProvider } from '@ngneat/spectator/jest'; 2 | import { AuthenticationService } from './authentication.service'; 3 | import { StorageService } from './storage.service'; 4 | import { LoginGQL } from 'src/app/generated'; 5 | import { BehaviorSubject } from 'rxjs'; 6 | 7 | 8 | describe('AuthenticationService', () => { 9 | let spectator: SpectatorService; 10 | 11 | const storage = { jwt: '' }; 12 | 13 | const createService = createServiceFactory({ 14 | service: AuthenticationService, 15 | providers: [ 16 | mockProvider(LoginGQL, { 17 | mutate: ({ username, password }) => new BehaviorSubject({ data: { login: username + password } }) 18 | }), 19 | mockProvider(StorageService, { 20 | setItem: (key, value) => { 21 | storage[key] = value; 22 | } 23 | }) 24 | ] 25 | }); 26 | 27 | beforeEach(() => spectator = createService()); 28 | 29 | it('should set received jwt', () => { 30 | spectator.service.doLogin('user', 'pass'); 31 | 32 | expect(storage.jwt).toEqual('userpass'); 33 | }); 34 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Setup 2 | 3 | - adapt config.json 4 | - adapt codgen.json 5 | 6 | # Blueprint 7 | 8 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.5. 9 | 10 | ## ToDo 11 | - implement every-layout 12 | - add storage service 13 | - add auth-(service/guard) 14 | 15 | ## Development server 16 | 17 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 18 | 19 | ## Code scaffolding 20 | 21 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 22 | 23 | ## Build 24 | 25 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build. 26 | 27 | ## Running unit tests 28 | 29 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 30 | 31 | ## Running end-to-end tests 32 | 33 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/). 34 | 35 | ## Further help 36 | 37 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 38 | -------------------------------------------------------------------------------- /src/app/core/services/configuration.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { createHttpFactory, HttpMethod, SpectatorHttp } from '@ngneat/spectator/jest'; 2 | import { ConfigurationService } from './configuration.service'; 3 | import { BehaviorSubject } from 'rxjs'; 4 | 5 | 6 | describe('ConfigurationService', () => { 7 | let spectator: SpectatorHttp; 8 | const createHttp = createHttpFactory(ConfigurationService); 9 | 10 | beforeEach(() => spectator = createHttp()); 11 | 12 | it('provides configuration', async () => { 13 | const configuration = spectator.service; 14 | 15 | const latestValue = new BehaviorSubject(null); 16 | configuration.getItem$('storage').subscribe(latestValue); 17 | 18 | // expect configuration to be missing 19 | expect(latestValue.getValue()).toEqual(null); 20 | 21 | configuration.fetchConfiguration(); 22 | const request = spectator.expectOne('/assets/configuration.json', HttpMethod.GET); 23 | request.flush({ storage: 'localStorage' }); 24 | 25 | // induce delay for observables to happen 26 | await new Promise(resolve => setTimeout(resolve, 0)); 27 | 28 | // expect fetched configuration values 29 | expect(configuration.getItem('storage')).toEqual('localStorage'); 30 | expect(latestValue.getValue()).toEqual('localStorage'); 31 | }) 32 | }); -------------------------------------------------------------------------------- /src/app/core/services/storage.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { BehaviorSubject, Observable } from 'rxjs'; 3 | import { ConfigurationService } from './configuration.service'; 4 | import { map } from 'rxjs/operators'; 5 | 6 | 7 | type StorageApi = { 8 | jwt: string | null; 9 | } 10 | 11 | type StorageKey = keyof StorageApi; 12 | type StorageValue = StorageApi[StorageKey]; 13 | 14 | @Injectable({ 15 | providedIn: 'root' 16 | }) 17 | export class StorageService { 18 | private readonly storage: Storage; 19 | private storage$: BehaviorSubject; 20 | 21 | constructor(private configuration: ConfigurationService) { 22 | const storageType = this.configuration.getItem('storage'); 23 | this.storage = window[storageType]; 24 | this.storage$ = new BehaviorSubject(this.storage); 25 | } 26 | 27 | public getItem$(key: StorageKey): Observable { 28 | return this.storage$.pipe( 29 | map(storage => JSON.parse(storage.getItem(key))) 30 | ); 31 | } 32 | 33 | public getItem(key: StorageKey): StorageValue { 34 | return JSON.parse(this.storage.getItem(key)); 35 | } 36 | 37 | public setItem(key: StorageKey, value: StorageValue): void { 38 | this.storage.setItem(key, JSON.stringify(value)); 39 | this.storage$.next(this.storage); 40 | } 41 | 42 | public removeItem(key: StorageKey): void { 43 | this.storage.removeItem(key); 44 | this.storage$.next(this.storage); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/app/layout/layouts/cover.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'claiv-cover', 5 | template: "", 6 | styles: [ 7 | `claiv-cover { 8 | --padding: var(--s0); 9 | --margin: var(--s0); 10 | --min-height: 100%; 11 | display: flex; 12 | flex-direction: column; 13 | min-height: var(--min-height); 14 | padding: var(--padding); 15 | }`, 16 | `claiv-cover > * { 17 | margin-top: var(--margin); 18 | margin-bottom: var(--margin); 19 | }`, 20 | /* if just on child, it is main */ 21 | `claiv-cover > :only-child { 22 | margin-top: auto; 23 | margin-bottom: auto; 24 | }`, 25 | /* if two childs, heading and main */ 26 | `claiv-cover > :last-child:nth-child(2) { 27 | margin-top: auto; 28 | margin-bottom: auto; 29 | }`, 30 | `claiv-cover > :first-child:nth-last-child(2) { 31 | margin-top: 0; 32 | }`, 33 | /* if three childs, heading, main, footer */ 34 | `claiv-cover > :nth-child(2):nth-last-child(2) { 35 | margin-top: auto; 36 | margin-bottom: auto; 37 | }`, 38 | `claiv-cover > :first-child:nth-last-child(3) { 39 | margin-top: 0; 40 | }`, 41 | `claiv-cover > :last-child::nth-child(3) { 42 | margin-bottom: 0; 43 | }`, 44 | ], 45 | encapsulation: ViewEncapsulation.None 46 | }) 47 | export class CoverComponent {} 48 | -------------------------------------------------------------------------------- /src/app/core/services/configuration.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs'; 4 | import { pluck, shareReplay } from 'rxjs/operators'; 5 | 6 | 7 | export type Configuration = { 8 | storage: 'localStorage' | 'sessionStorage'; 9 | gqlEndpoint: string; 10 | } 11 | 12 | type ConfigurationKey = keyof Configuration; 13 | type ConfigurationValue = Configuration[ConfigurationKey]; 14 | 15 | @Injectable({ 16 | providedIn: 'root' 17 | }) 18 | export class ConfigurationService { 19 | private static readonly CONFIGURATION_PATH = '/assets/configuration.json'; 20 | 21 | private readonly configuration$: Observable; 22 | private configuration: Configuration; 23 | 24 | constructor(private http: HttpClient) { 25 | this.configuration$ = this.http 26 | .get(ConfigurationService.CONFIGURATION_PATH) 27 | .pipe( 28 | shareReplay(1) 29 | ); 30 | } 31 | 32 | public fetchConfiguration(): Promise { 33 | return this.configuration$.toPromise().then( 34 | data => this.configuration = data 35 | ); 36 | } 37 | 38 | public getItem$(key: ConfigurationKey): Observable { 39 | return this.configuration$.pipe( 40 | pluck(key) 41 | ); 42 | } 43 | 44 | public getItem(key: ConfigurationKey): ConfigurationValue { 45 | return this.configuration[key]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | /* GLOBALS, DO NOT OVERRIDE IN YOUR STYLES */ 4 | :root { 5 | /* ratios for maintaining visual consistency */ 6 | --ratio: 1.7; 7 | --s-5: calc(var(--s-4) / var(--ratio)); 8 | --s-4: calc(var(--s-3) / var(--ratio)); 9 | --s-3: calc(var(--s-2) / var(--ratio)); 10 | --s-2: calc(var(--s-1) / var(--ratio)); 11 | --s-1: calc(var(--s0) / var(--ratio)); 12 | --s0: 1rem; 13 | --s1: calc(var(--s0) * var(--ratio)); 14 | --s2: calc(var(--s1) * var(--ratio)); 15 | --s3: calc(var(--s2) * var(--ratio)); 16 | --s4: calc(var(--s3) * var(--ratio)); 17 | --s5: calc(var(--s4) * var(--ratio)); 18 | 19 | /* zero value*/ 20 | --zero: 0; 21 | 22 | /* measure width */ 23 | --measure: 60ch; 24 | 25 | /* color scheme from colorsuplyyy */ 26 | --color-primary: #C54F1F; 27 | --color-secondary: #B8BB9A; 28 | --color-ternary: #E7B872; 29 | } 30 | 31 | * { 32 | /* in general calculate from border-box */ 33 | box-sizing: border-box; 34 | 35 | /* cap max-with for visual elements */ 36 | max-width: 100%; 37 | 38 | /* clear default padding and margin*/ 39 | margin: 0; 40 | padding: 0; 41 | } 42 | 43 | /* logical exceptions from measure rule */ 44 | 45 | h1, 46 | h2, 47 | h3, 48 | h4, 49 | h5, 50 | p { 51 | max-width: var(--measure); 52 | } 53 | /* 54 | html, 55 | body, 56 | div, 57 | header, 58 | nav, 59 | main, 60 | footer, 61 | img, 62 | video { 63 | max-width: none; 64 | } */ 65 | 66 | html, body, main { 67 | height: 100%; 68 | } 69 | -------------------------------------------------------------------------------- /src/app/layout/layout.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { StackComponent } from './layouts/stack.component'; 4 | import { BoxComponent } from './layouts/box.component'; 5 | import { CenterComponent } from './layouts/center.component'; 6 | import { ClusterComponent } from './layouts/cluster.component'; 7 | import { SidebarComponent } from './layouts/sidebar.component'; 8 | import { SwitcherComponent } from './layouts/switcher.component'; 9 | import { CoverComponent } from './layouts/cover.component'; 10 | import { GridComponent } from './layouts/grid.component'; 11 | import { FrameComponent } from './layouts/frame.component'; 12 | import { ReelComponent } from './layouts/reel.component'; 13 | import { ImposterComponent } from './layouts/imposter.component'; 14 | 15 | 16 | 17 | @NgModule({ 18 | declarations: [ 19 | StackComponent, 20 | BoxComponent, 21 | CenterComponent, 22 | ClusterComponent, 23 | SidebarComponent, 24 | SwitcherComponent, 25 | CoverComponent, 26 | GridComponent, 27 | FrameComponent, 28 | ReelComponent, 29 | ImposterComponent 30 | ], 31 | imports: [ 32 | CommonModule 33 | ], 34 | exports: [ 35 | StackComponent, 36 | BoxComponent, 37 | CenterComponent, 38 | ClusterComponent, 39 | SidebarComponent, 40 | SwitcherComponent, 41 | CoverComponent, 42 | GridComponent, 43 | FrameComponent, 44 | ReelComponent, 45 | ImposterComponent 46 | ] 47 | }) 48 | export class LayoutModule { } 49 | -------------------------------------------------------------------------------- /src/app/core/services/storage.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { createServiceFactory, SpectatorService, mockProvider } from '@ngneat/spectator/jest'; 2 | import { StorageService } from './storage.service'; 3 | import { ConfigurationService } from './configuration.service'; 4 | import { BehaviorSubject } from 'rxjs'; 5 | 6 | 7 | describe('StorageService', () => { 8 | let spectator: SpectatorService; 9 | const createService = createServiceFactory({ 10 | service: StorageService, 11 | providers: [ 12 | mockProvider(ConfigurationService, { 13 | getItem: _ => 'localStorage' 14 | }) 15 | ] 16 | }); 17 | 18 | beforeEach(() => spectator = createService()); 19 | 20 | afterEach(() => window.localStorage.clear()) 21 | 22 | it('should set key to stringified value', () => { 23 | spectator.service.setItem('jwt', 'someJwt'); 24 | 25 | expect(window.localStorage.getItem('jwt')).toEqual(JSON.stringify('someJwt')); 26 | }); 27 | 28 | it('should propagate updates', () => { 29 | const storage = spectator.service; 30 | 31 | const latestValue = new BehaviorSubject(null); 32 | storage.getItem$('jwt').subscribe(latestValue); 33 | 34 | // should be empty 35 | expect(latestValue.getValue()).toEqual(null); 36 | expect(storage.getItem('jwt')).toEqual(null); 37 | 38 | storage.setItem('jwt', 'someJwt'); 39 | 40 | // should have value 41 | expect(latestValue.getValue()).toEqual('someJwt'); 42 | expect(storage.getItem('jwt')).toEqual('someJwt'); 43 | 44 | storage.removeItem('jwt'); 45 | 46 | // should be empty again 47 | expect(latestValue.getValue()).toEqual(null); 48 | expect(storage.getItem('jwt')).toEqual(null); 49 | }) 50 | }); -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { Spectator, createHostFactory } from '@ngneat/spectator/jest'; 2 | import { AppComponent } from './app.component'; 3 | import { Router } from '@angular/router'; 4 | import { NO_ERRORS_SCHEMA } from '@angular/core'; 5 | import { TimeGQL, TimeQuery } from './generated'; 6 | import { mockProvider } from '@ngneat/spectator'; 7 | import { BehaviorSubject } from 'rxjs'; 8 | import { ApolloQueryResult, NetworkStatus } from 'apollo-client'; 9 | import { ConfigurationService } from './core/services'; 10 | 11 | 12 | describe('AppComponent', () => { 13 | let spectator: Spectator; 14 | const createComponent = createHostFactory({ 15 | component: AppComponent, 16 | mocks: [ 17 | Router, 18 | ], 19 | providers: [ 20 | mockProvider(TimeGQL, { 21 | watch: () => ({ valueChanges: new BehaviorSubject>({ 22 | data: { time: 0 }, 23 | loading: false, 24 | networkStatus: NetworkStatus.ready, 25 | stale: false 26 | }) }) 27 | }), 28 | mockProvider(ConfigurationService, { 29 | getItem$: () => new BehaviorSubject('') 30 | }) 31 | ], 32 | // in order to ignore non-stubbed router-outet 33 | schemas: [ NO_ERRORS_SCHEMA ] 34 | }); 35 | 36 | beforeEach(() => spectator = createComponent('')); 37 | 38 | it('should create the app', () => { 39 | const app = spectator.component; 40 | expect(app).toBeTruthy(); 41 | }); 42 | 43 | it(`should have as title 'blueprint'`, () => { 44 | const app = spectator.component; 45 | expect(app.title).toEqual('blueprint'); 46 | }); 47 | 48 | it('should render title', () => { 49 | spectator.detectChanges(); 50 | expect(spectator.query('h1').textContent).toContain('blueprint'); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blueprint", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "test": "jest", 9 | "lint": "eslint --ext .js,.ts src/app", 10 | "lint:fix": "eslint --ext .js,.ts --fix src/app", 11 | "e2e": "ng e2e", 12 | "generate": "graphql-codegen" 13 | }, 14 | "private": true, 15 | "jest": { 16 | "preset": "jest-preset-angular", 17 | "setupTestFrameworkScriptFile": "/setupJest.ts" 18 | }, 19 | "dependencies": { 20 | "@angular/animations": "~9.1.6", 21 | "@angular/common": "~9.1.6", 22 | "@angular/compiler": "~9.1.6", 23 | "@angular/core": "~9.1.6", 24 | "@angular/forms": "~9.1.6", 25 | "@angular/platform-browser": "~9.1.6", 26 | "@angular/platform-browser-dynamic": "~9.1.6", 27 | "@angular/router": "~9.1.6", 28 | "apollo-angular": "^1.9.1", 29 | "apollo-angular-link-http": "^1.10.0", 30 | "apollo-cache-inmemory": "^1.6.0", 31 | "apollo-client": "^2.6.0", 32 | "apollo-link": "^1.2.11", 33 | "graphql": "^14.6.0", 34 | "graphql-tag": "^2.10.0", 35 | "jwt-decode": "^2.2.0", 36 | "rxjs": "~6.5.4", 37 | "tslib": "^1.10.0", 38 | "zone.js": "~0.10.2" 39 | }, 40 | "devDependencies": { 41 | "@angular-devkit/build-angular": "~0.901.5", 42 | "@angular/cli": "~9.1.5", 43 | "@angular/compiler-cli": "~9.1.6", 44 | "@graphql-codegen/cli": "^1.13.5", 45 | "@graphql-codegen/typescript": "^1.13.5", 46 | "@graphql-codegen/typescript-apollo-angular": "^1.13.5", 47 | "@graphql-codegen/typescript-operations": "^1.13.5", 48 | "@ngneat/spectator": "^5.6.1", 49 | "@types/jest": "^25.2.1", 50 | "@types/node": "^12.11.1", 51 | "@typescript-eslint/eslint-plugin": "^2.31.0", 52 | "@typescript-eslint/parser": "^2.31.0", 53 | "eslint": "^7.0.0", 54 | "eslint-plugin-import": "^2.20.2", 55 | "jest": "^26.0.1", 56 | "jest-preset-angular": "^8.2.0", 57 | "ts-node": "~8.3.0", 58 | "typescript": "~3.8.3" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: "@typescript-eslint/parser", 3 | parserOptions: { 4 | project: "./tsconfig.json" 5 | }, 6 | plugins: ["@typescript-eslint"], 7 | extends: [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 12 | "plugin:import/errors", 13 | "plugin:import/warnings", 14 | "plugin:import/typescript" 15 | ], 16 | rules: { 17 | /*=== basic rules ===*/ 18 | "space-in-parens": [ 19 | "warn", 20 | "never", 21 | ], 22 | "space-before-blocks": [ 23 | "error", 24 | "always" 25 | ], 26 | "keyword-spacing": [ 27 | "error", 28 | { 29 | before: true, 30 | after: true 31 | } 32 | ], 33 | "function-call-argument-newline": [ 34 | "warn", 35 | "consistent" 36 | ], 37 | "object-curly-spacing": [ 38 | "error", 39 | "always" 40 | ], 41 | "no-multiple-empty-lines": [ 42 | "error", 43 | { 44 | max: 2, 45 | maxBOF: 0, 46 | maxEOF: 1 47 | } 48 | ], 49 | "quotes": [ 50 | "error", 51 | "single" 52 | ], 53 | 54 | /*=== typescript rules ===*/ 55 | "@typescript-eslint/no-empty-function": [ 56 | "error", 57 | { allow: ["constructors"] } 58 | ], 59 | "@typescript-eslint/no-unused-vars": [ 60 | "warn", 61 | { 62 | args: "all", 63 | argsIgnorePattern: "^_" 64 | } 65 | ], 66 | "@typescript-eslint/array-type": [ 67 | "error", 68 | { 69 | default: "generic" 70 | } 71 | ], 72 | "@typescript-eslint/explicit-member-accessibility": [ 73 | "error", 74 | { overrides: { constructors: 'no-public' } } 75 | ], 76 | 77 | /*=== import rules ===*/ 78 | "import/newline-after-import": [ 79 | "error", 80 | { count: 2 } 81 | ], 82 | }, 83 | settings: { 84 | "import/resolver": { 85 | node: { 86 | paths: ["."] 87 | } 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "blueprint": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "claiv", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/blueprint", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "tsconfig.app.json", 21 | "aot": true, 22 | "assets": [ 23 | "src/favicon.ico", 24 | "src/assets" 25 | ], 26 | "styles": [ 27 | "src/styles.css" 28 | ], 29 | "scripts": [] 30 | }, 31 | "configurations": { 32 | "production": { 33 | "fileReplacements": [ 34 | { 35 | "replace": "src/environments/environment.ts", 36 | "with": "src/environments/environment.prod.ts" 37 | } 38 | ], 39 | "optimization": true, 40 | "outputHashing": "all", 41 | "sourceMap": false, 42 | "extractCss": true, 43 | "namedChunks": false, 44 | "extractLicenses": true, 45 | "vendorChunk": false, 46 | "buildOptimizer": true, 47 | "budgets": [ 48 | { 49 | "type": "initial", 50 | "maximumWarning": "2mb", 51 | "maximumError": "5mb" 52 | }, 53 | { 54 | "type": "anyComponentStyle", 55 | "maximumWarning": "6kb", 56 | "maximumError": "10kb" 57 | } 58 | ] 59 | } 60 | } 61 | }, 62 | "serve": { 63 | "builder": "@angular-devkit/build-angular:dev-server", 64 | "options": { 65 | "browserTarget": "blueprint:build" 66 | }, 67 | "configurations": { 68 | "production": { 69 | "browserTarget": "blueprint:build:production" 70 | } 71 | } 72 | }, 73 | "extract-i18n": { 74 | "builder": "@angular-devkit/build-angular:extract-i18n", 75 | "options": { 76 | "browserTarget": "blueprint:build" 77 | } 78 | }, 79 | "e2e": { 80 | "builder": "@angular-devkit/build-angular:protractor", 81 | "options": { 82 | "protractorConfig": "e2e/protractor.conf.js", 83 | "devServerTarget": "blueprint:serve" 84 | }, 85 | "configurations": { 86 | "production": { 87 | "devServerTarget": "blueprint:serve:production" 88 | } 89 | } 90 | } 91 | } 92 | } 93 | }, 94 | "defaultProject": "blueprint", 95 | "cli": { 96 | "analytics": false 97 | } 98 | } -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |

test

5 |

sidebar

6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |

TITLE

15 | 16 | 17 | 18 | 19 | FOO 20 | 21 | 22 | BAR 23 | 24 | 25 | FOOFOO 26 | 27 | 28 | BARBAR 29 | 30 | 31 | FOOFOOFOO 32 | 33 | 34 | BARBARBAR 35 | 36 | 37 | 38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | STONKS 52 | 53 | 54 | 55 | 56 | 57 | FOO 58 | 59 | 60 | BAR 61 | 62 | 63 | FOO 64 | 65 | 66 | BAR 67 | 68 | 69 | FOO 70 | 71 | 72 | BAR 73 | 74 | 75 | FOO 76 | 77 | 78 | BAR 79 | 80 | 81 | FOO 82 | 83 | 84 | BAR 85 | 86 | 87 | FOO 88 | 89 | 90 | BAR 91 | 92 | 93 | FOO 94 | 95 | 96 | BAR 97 | 98 | 99 | FOO 100 | 101 | 102 | BAR 103 | 104 | 105 | FOO 106 | 107 | 108 | BAR 109 | 110 | 111 | FOO 112 | 113 | 114 | BAR 115 | 116 | 117 | FOO 118 | 119 | 120 | BAR 121 | 122 | 123 | FOO 124 | 125 | 126 | BAR 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 |
--------------------------------------------------------------------------------