EUGENIO SOUZA CARVALHO 5 years ago
commit
dad0d4bfaf

+ 11 - 0
browserslist

@@ -0,0 +1,11 @@
+# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
+# For additional information regarding the format and rule options, please see:
+# https://github.com/browserslist/browserslist#queries
+#
+# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
+
+> 0.5%
+last 2 versions
+Firefox ESR
+not dead
+not IE 9-11

+ 31 - 0
karma.conf.js

@@ -0,0 +1,31 @@
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+  config.set({
+    basePath: '',
+    frameworks: ['jasmine', '@angular-devkit/build-angular'],
+    plugins: [
+      require('karma-jasmine'),
+      require('karma-chrome-launcher'),
+      require('karma-jasmine-html-reporter'),
+      require('karma-coverage-istanbul-reporter'),
+      require('@angular-devkit/build-angular/plugins/karma')
+    ],
+    client: {
+      clearContext: false // leave Jasmine Spec Runner output visible in browser
+    },
+    coverageIstanbulReporter: {
+      dir: require('path').join(__dirname, '../../coverage'),
+      reports: ['html', 'lcovonly'],
+      fixWebpackSourcePaths: true
+    },
+    reporters: ['progress', 'kjhtml'],
+    port: 9876,
+    colors: true,
+    logLevel: config.LOG_INFO,
+    autoWatch: true,
+    browsers: ['Chrome'],
+    singleRun: false
+  });
+};

+ 3 - 0
src/app/app.component.html

@@ -0,0 +1,3 @@
+
+
+<dito-timeline></dito-timeline>

+ 0 - 0
src/app/app.component.scss


+ 27 - 0
src/app/app.component.spec.ts

@@ -0,0 +1,27 @@
+import { TestBed, async } from '@angular/core/testing';
+import { AppComponent } from './app.component';
+describe('AppComponent', () => {
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [
+        AppComponent
+      ],
+    }).compileComponents();
+  }));
+  it('should create the app', async(() => {
+    const fixture = TestBed.createComponent(AppComponent);
+    const app = fixture.debugElement.componentInstance;
+    expect(app).toBeTruthy();
+  }));
+  it(`should have as title 'ditofront'`, async(() => {
+    const fixture = TestBed.createComponent(AppComponent);
+    const app = fixture.debugElement.componentInstance;
+    expect(app.title).toEqual('ditofront');
+  }));
+  it('should render title in a h1 tag', async(() => {
+    const fixture = TestBed.createComponent(AppComponent);
+    fixture.detectChanges();
+    const compiled = fixture.debugElement.nativeElement;
+    expect(compiled.querySelector('h1').textContent).toContain('Welcome to ditofront!');
+  }));
+});

+ 10 - 0
src/app/app.component.ts

@@ -0,0 +1,10 @@
+import { Component } from '@angular/core';
+
+@Component({
+  selector: 'app-root',
+  templateUrl: './app.component.html',
+  styleUrls: ['./app.component.scss']
+})
+export class AppComponent {
+  title = 'ditofront';
+}

+ 24 - 0
src/app/app.module.ts

@@ -0,0 +1,24 @@
+import { BrowserModule } from '@angular/platform-browser';
+import { NgModule } from '@angular/core';
+
+import { AppComponent } from './app.component';
+import { TimelineComponent } from './timeline/timeline.component';
+import { HttpClientModule } from '@angular/common/http';
+import { LOCALE_ID } from '@angular/core';
+import { registerLocaleData } from '@angular/common';
+import localePt from '@angular/common/locales/pt';
+registerLocaleData(localePt);
+
+@NgModule({
+  declarations: [
+    AppComponent,
+    TimelineComponent
+  ],
+  imports: [
+    BrowserModule,
+    HttpClientModule
+  ],
+  providers: [{ provide: LOCALE_ID, useValue: 'pt-BR' }],
+  bootstrap: [AppComponent]
+})
+export class AppModule { }

+ 23 - 0
src/app/timeline/timeline.component.html

@@ -0,0 +1,23 @@
+<ul class="timeline">
+  <li class="event" *ngFor="let transaction of transactions">
+    <!-- <img class="" src="assets/icons/check.svg" /> -->
+    <div class="event-circle icon icon-check"></div>
+
+    <ul class="event-header">
+      <li *ngFor="let item of transaction.header">
+        <i class="icon {{item.icon}}"></i>
+        <div>{{item.value}}</div>
+      </li>
+    </ul>
+    <ul class="event-itens">
+      <li>
+        <div>Produto</div>
+        <div>Preço</div>
+      </li>
+      <li *ngFor="let item of transaction.itens">
+        <div>{{item.name}}</div>
+        <div>{{item.value}}</div>
+      </li>
+    </ul>
+  </li>
+</ul>

+ 147 - 0
src/app/timeline/timeline.component.scss

@@ -0,0 +1,147 @@
+:host {
+    background: #eff1fd;
+    display: flex;
+    padding: 32px 19px 0px 32px;
+
+    ul {
+        // border: 1px solid #000;
+        display: flex;
+        list-style: none;
+        margin: 0px;
+        padding: 0px;
+    }
+
+    * {
+        box-sizing: border-box;
+    }
+}
+
+.timeline {
+    font-family: 'Roboto', sans-serif !important;
+    flex-direction: column;
+    border-left: #d6d9e0 2px solid;
+    user-select: none;
+
+    .event {
+        color: #000000a8;
+        margin: 34px 29px 0px;
+        position: relative;
+        width: 100%;
+        display: flex;
+        flex-direction: column;
+        background: #f8f8f8;
+        border-radius: 5px;
+        box-shadow: 0 0 5px #0000004d;
+        width: 100%;
+        font-size: 14px;
+
+        &:last-child {
+            margin-bottom: 34px;
+        }
+
+        .event-decorators {
+            position: absolute;
+            border: 1px solid blue;
+        }
+
+        .event-header {
+            background: #fff;
+            flex-direction: row;
+            height: 44px;
+            width: 100%;
+            align-items: center;
+            justify-content: space-between;
+            padding: 0px 24px;
+            box-sizing: border-box;
+            border-top-left-radius: 5px;
+            border-top-right-radius: 5px;
+
+            li {
+                display: flex;
+                align-items: center;
+                justify-content: center;
+
+                i {
+                    margin-right: 5px;
+                    transform: translateY(2px);
+                    color: #00000073;
+                }
+            }
+        }
+
+        .event-itens {
+            display: flex;
+            flex-direction: column;
+            width: 100%;
+            padding: 4px 22px 8px;
+
+            li {
+                height: 32px;
+                display: flex;
+                align-items: center;
+                justify-content: space-between;
+                box-sizing: border-box;
+
+                &:first-child {
+                    color: #5c5c5c;
+                    font-weight: 600;
+                }
+
+                &:not(:last-child) {
+                    border-bottom: 1px solid #0000001a;
+                }
+            }
+        }
+
+        .event-item {}
+
+        .event-circle {
+            position: absolute;
+            height: 25px;
+            color: #55a469;
+            width: 25px;
+            border: 2px solid #d6d9e0;
+            border-radius: 50%;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            background: #f8f8f8;
+            box-sizing: border-box;
+            font-size: 28px;
+            top: 10px;
+            left: -44px;
+        }
+
+        &::before {
+            box-sizing: border-box;
+            width: 0;
+            height: 0;
+            margin-top: 10px;
+            content: '';
+            transform: rotate(45deg);
+            transform-origin: 12px 18px;
+            border: 8px solid transparent;
+            // border-bottom-color: #f8f8f8;
+            // border-left-color: #f8f8f8;
+            border-bottom-color: #fff;
+            border-left-color: #fff;
+            box-shadow: -2px 3px 4px -3px rgba(0, 0, 0, 0.4);
+            top: 2px;
+            left: -14px;
+            position: absolute;
+            border-radius: 1px;
+        }
+
+        // content: "";
+        // position: absolute;
+        // transform: rotate(45deg);
+        // height: 13px;
+        // width: 13px;
+        // display: flex;
+        // left: -6px;
+        // background: #f8f8f8;
+        // box-shadow: -2px 2px 5px 0px #cdced9;
+        // z-index: 0;
+        // top: 15px;
+    }
+}

+ 25 - 0
src/app/timeline/timeline.component.spec.ts

@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TimelineComponent } from './timeline.component';
+
+describe('TimelineComponent', () => {
+  let component: TimelineComponent;
+  let fixture: ComponentFixture<TimelineComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ TimelineComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(TimelineComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 21 - 0
src/app/timeline/timeline.component.ts

@@ -0,0 +1,21 @@
+import { Component } from '@angular/core';
+import { ApiEventsService, Transaction } from './timeline.service';
+
+@Component({
+  selector: 'dito-timeline',
+  templateUrl: './timeline.component.html',
+  styleUrls: ['./timeline.component.scss']
+})
+export class TimelineComponent {
+
+  public transactions: Transaction[];
+
+  constructor(
+    protected $tls: ApiEventsService
+  ) {
+    this.$tls.transactions().subscribe(transactions => {
+      this.transactions = transactions;
+    })
+  }
+
+}

+ 15 - 0
src/app/timeline/timeline.service.spec.ts

@@ -0,0 +1,15 @@
+import { TestBed, inject } from '@angular/core/testing';
+
+import { ApiEventsService } from './timeline.service';
+
+describe('TimelineService', () => {
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      providers: [ApiEventsService]
+    });
+  });
+
+  it('should be created', inject([ApiEventsService], (service: ApiEventsService) => {
+    expect(service).toBeTruthy();
+  }));
+});

+ 135 - 0
src/app/timeline/timeline.service.ts

@@ -0,0 +1,135 @@
+import { Injectable } from '@angular/core';
+import { Observable } from 'rxjs';
+import { HttpClient } from '@angular/common/http';
+import * as moment from 'moment';
+import { CurrencyPipe } from '@angular/common';
+
+export class Transaction {
+  itens = [];
+  custom_data = {}
+  header: { value: string, icon: string }[] = [];
+  time: moment.Moment;
+};
+
+@Injectable({
+  providedIn: 'root'
+})
+export class ApiEventsService {
+
+  // public apiURL = 'https://storage.googleapis.com/dito-questions/events.json';
+  public apiURL = 'https://dito-questions.storage.googleapis.com/events.json';
+  public _events: any;
+
+  constructor(
+    protected $http: HttpClient
+  ) {
+    this._events = [
+      {
+        "event": "comprou-produto", "timestamp": "2016-09-22T13:57:33.2311892-03:00", "custom_data": [{ "key": "product_price", "value": 150 }, { "key": "transaction_id", "value": "3029384" }, { "key": "product_name", "value": "Calça Rosa" }]
+      },
+      {
+        "event": "comprou-produto", "timestamp": "2016-10-02T11:37:35.2300892-03:00", "custom_data": [{ "key": "transaction_id", "value": "3409340" }, { "key": "product_name", "value": "Tenis Preto" }, { "key": "product_price", "value": 120 }]
+      },
+      {
+        "event": "comprou", "timestamp": "2016-10-02T11:37:31.2300892-03:00", "revenue": 120, "custom_data": [{ "key": "transaction_id", "value": "3409340" }, { "key": "store_name", "value": "BH Shopping" }]
+      },
+      {
+        "event": "comprou-produto", "timestamp": "2016-09-22T13:57:32.2311892-03:00", "custom_data": [{ "key": "product_name", "value": "Camisa Azul" }, { "key": "transaction_id", "value": "3029384" }, { "key": "product_price", "value": 100 }]
+      },
+      {
+        "event": "comprou", "timestamp": "2016-09-22T13:57:31.2311892-03:00", "revenue": 250, "custom_data": [{ "key": "store_name", "value": "Patio Savassi" }, { "key": "transaction_id", "value": "3029384" }]
+      }
+    ]
+    console.log(this._events)
+
+  }
+
+  public transactions() {
+
+    return new Observable<any>(ob => {
+      // this.$http.get(this.apiURL).subscribe(results => {
+      // const transations = this.parseEventResults(results);
+      setTimeout(() => {
+        ob.next(this.parseEventResults(this._events))
+      })
+      //   console.log(results)
+      // })
+    })
+  }
+  protected parseEventResults(events) {
+    let currencyPipe = new CurrencyPipe('pt-BR')
+      , transactions = []
+      , transactionsMap = new Map<string, Transaction>();
+
+    events.map(event => {
+      let id;
+      const found = event.custom_data.find(data => {
+        if (data.key === 'transaction_id') {
+          id = data.value;
+          return true;
+        }
+      })
+
+      if (!found) {
+        return;
+      }
+
+      if (!transactionsMap.has(id)) {
+        transactionsMap.set(id, new Transaction());
+      }
+
+      let trans = transactionsMap.get(id);
+
+      switch (event.event) {
+        case "comprou-produto":
+          let add = { name: "", value: "" }
+          // Atribui os valores do nome do produto e o preço para o objeto da listagem
+          event.custom_data.find(item => {
+            if (item.key === "product_name") {
+              add.name = item.value;
+            } else if (item.key === "product_price") {
+              // Formata o valor com a moeda brasileira
+              add.value = currencyPipe.transform(item.value, 'BRL', true, '1.2-2');
+            }
+          })
+          trans.itens.push(add)
+          break;
+        case "comprou":
+          // Converte o timestamp para um moment object
+          trans.time = moment(event.timestamp);
+          let place = "";
+
+          trans.custom_data = event;
+          // Busca o nome do estabelecimento
+          event.custom_data.find(item => {
+            if (item.key === "store_name") {
+              place = item.value;
+              return true;
+            }
+          })
+          // Cria  um array contendo os elementos do header de cada evento
+          // Cada elemento é mapeado para um item do array
+          trans.header = [
+            { icon: "icon-calendar", value: trans.time.format("DD/MM/YYYY") },
+            { icon: "icon-clock", value: trans.time.format("HH:mm") },
+            { icon: "icon-place", value: place },
+            { icon: "icon-money", value: currencyPipe.transform(event.revenue, 'BRL', true, '1.2-2') },
+          ];
+
+          break;
+      }
+    })
+    // Converte o map 
+    transactionsMap.forEach(t => transactions.push(t))
+
+    // Ordena as transações pela data
+    transactions.sort((t1, t2) => {
+      // if (t1) return 1;
+      // if ( ) return -1;
+      return (t1.time - t2.time);
+    });
+    console.log('transactions', transactions)
+    return transactions;
+  }
+
+}

+ 0 - 0
src/assets/.gitkeep


BIN
src/assets/fonts/untitled-font-11.eot


File diff suppressed because it is too large
+ 15 - 0
src/assets/fonts/untitled-font-11.svg


BIN
src/assets/fonts/untitled-font-11.ttf


BIN
src/assets/fonts/untitled-font-11.woff


File diff suppressed because it is too large
+ 80 - 0
src/assets/icons-reference.html


+ 55 - 0
src/assets/icons.css

@@ -0,0 +1,55 @@
+@charset "UTF-8";
+
+@font-face {
+  font-family: "untitled-font-11";
+  src:url("fonts/untitled-font-11.eot");
+  src:url("fonts/untitled-font-11.eot?#iefix") format("embedded-opentype"),
+    url("fonts/untitled-font-11.woff") format("woff"),
+    url("fonts/untitled-font-11.ttf") format("truetype"),
+    url("fonts/untitled-font-11.svg#untitled-font-11") format("svg");
+  font-weight: normal;
+  font-style: normal;
+
+}
+
+[data-icon]:before {
+  font-family: "untitled-font-11" !important;
+  content: attr(data-icon);
+  font-style: normal !important;
+  font-weight: normal !important;
+  font-variant: normal !important;
+  text-transform: none !important;
+  speak: none;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+[class^="icon-"]:before,
+[class*=" icon-"]:before {
+  font-family: "untitled-font-11" !important;
+  font-style: normal !important;
+  font-weight: normal !important;
+  font-variant: normal !important;
+  text-transform: none !important;
+  speak: none;
+  line-height: 1;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.icon-calendar:before {
+  content: "\61";
+}
+.icon-clock:before {
+  content: "\63";
+}
+.icon-place:before {
+  content: "\65";
+}
+.icon-check:before {
+  content: "\62";
+}
+.icon-money:before {
+  content: "\64";
+}

File diff suppressed because it is too large
+ 19 - 0
src/assets/icons/calendar.svg


File diff suppressed because it is too large
+ 16 - 0
src/assets/icons/check .svg


File diff suppressed because it is too large
+ 18 - 0
src/assets/icons/clock.svg


File diff suppressed because it is too large
+ 18 - 0
src/assets/icons/money.svg


File diff suppressed because it is too large
+ 18 - 0
src/assets/icons/place.svg


BIN
src/assets/layout.png


+ 3 - 0
src/environments/environment.prod.ts

@@ -0,0 +1,3 @@
+export const environment = {
+  production: true
+};

+ 15 - 0
src/environments/environment.ts

@@ -0,0 +1,15 @@
+// This file can be replaced during build by using the `fileReplacements` array.
+// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.
+// The list of file replacements can be found in `angular.json`.
+
+export const environment = {
+  production: false
+};
+
+/*
+ * In development mode, for easier debugging, you can ignore zone related error
+ * stack frames such as `zone.run`/`zoneDelegate.invokeTask` by importing the
+ * below file. Don't forget to comment it out in production mode
+ * because it will have a performance impact when errors are thrown
+ */
+// import 'zone.js/dist/zone-error';  // Included with Angular CLI.

BIN
src/favicon.ico


+ 19 - 0
src/index.html

@@ -0,0 +1,19 @@
+<!doctype html>
+<html lang="en">
+
+<head>
+  <meta charset="utf-8">
+  <title>Ditofront</title>
+  <base href="/">
+
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <link rel="stylesheet" href="assets/icons.css">
+  <link rel="icon" type="image/x-icon" href="favicon.ico">
+</head>
+
+<body>
+  <app-root></app-root>
+</body>
+<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
+
+</html>

+ 12 - 0
src/main.ts

@@ -0,0 +1,12 @@
+import { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { AppModule } from './app/app.module';
+import { environment } from './environments/environment';
+
+if (environment.production) {
+  enableProdMode();
+}
+
+platformBrowserDynamic().bootstrapModule(AppModule)
+  .catch(err => console.log(err));

+ 80 - 0
src/polyfills.ts

@@ -0,0 +1,80 @@
+/**
+ * This file includes polyfills needed by Angular and is loaded before the app.
+ * You can add your own extra polyfills to this file.
+ *
+ * This file is divided into 2 sections:
+ *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
+ *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
+ *      file.
+ *
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
+ *
+ * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
+ */
+
+/***************************************************************************************************
+ * BROWSER POLYFILLS
+ */
+
+/** IE9, IE10 and IE11 requires all of the following polyfills. **/
+// import 'core-js/es6/symbol';
+// import 'core-js/es6/object';
+// import 'core-js/es6/function';
+// import 'core-js/es6/parse-int';
+// import 'core-js/es6/parse-float';
+// import 'core-js/es6/number';
+// import 'core-js/es6/math';
+// import 'core-js/es6/string';
+// import 'core-js/es6/date';
+// import 'core-js/es6/array';
+// import 'core-js/es6/regexp';
+// import 'core-js/es6/map';
+// import 'core-js/es6/weak-map';
+// import 'core-js/es6/set';
+
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+// import 'classlist.js';  // Run `npm install --save classlist.js`.
+
+/** IE10 and IE11 requires the following for the Reflect API. */
+// import 'core-js/es6/reflect';
+
+
+/** Evergreen browsers require these. **/
+// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
+import 'core-js/es7/reflect';
+
+
+/**
+ * Web Animations `@angular/platform-browser/animations`
+ * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
+ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
+ **/
+// import 'web-animations-js';  // Run `npm install --save web-animations-js`.
+
+/**
+ * By default, zone.js will patch all possible macroTask and DomEvents
+ * user can disable parts of macroTask/DomEvents patch by setting following flags
+ */
+
+ // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
+ // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
+ // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
+
+ /*
+ * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
+ * with the following flag, it will bypass `zone.js` patch for IE/Edge
+ */
+// (window as any).__Zone_enable_cross_context_check = true;
+
+/***************************************************************************************************
+ * Zone JS is required by default for Angular itself.
+ */
+import 'zone.js/dist/zone';  // Included with Angular CLI.
+
+
+
+/***************************************************************************************************
+ * APPLICATION IMPORTS
+ */

+ 1 - 0
src/styles.scss

@@ -0,0 +1 @@
+/* You can add global styles to this file, and also import other style files */

+ 20 - 0
src/test.ts

@@ -0,0 +1,20 @@
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/dist/zone-testing';
+import { getTestBed } from '@angular/core/testing';
+import {
+  BrowserDynamicTestingModule,
+  platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+declare const require: any;
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+  BrowserDynamicTestingModule,
+  platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);

+ 11 - 0
tsconfig.app.json

@@ -0,0 +1,11 @@
+{
+  "extends": "../../tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../../out-tsc/app",
+    "types": []
+  },
+  "exclude": [
+    "test.ts",
+    "**/*.spec.ts"
+  ]
+}

+ 18 - 0
tsconfig.spec.json

@@ -0,0 +1,18 @@
+{
+  "extends": "../../tsconfig.json",
+  "compilerOptions": {
+    "outDir": "../../out-tsc/spec",
+    "types": [
+      "jasmine",
+      "node"
+    ]
+  },
+  "files": [
+    "src/test.ts",
+    "src/polyfills.ts"
+  ],
+  "include": [
+    "**/*.spec.ts",
+    "**/*.d.ts"
+  ]
+}

+ 17 - 0
tslint.json

@@ -0,0 +1,17 @@
+{
+    "extends": "../../tslint.json",
+    "rules": {
+        "directive-selector": [
+            true,
+            "attribute",
+            "app",
+            "camelCase"
+        ],
+        "component-selector": [
+            true,
+            "element",
+            "app",
+            "kebab-case"
+        ]
+    }
+}