Reformat code

This commit is contained in:
schmelczerandras 2019-08-24 15:25:43 +02:00
parent 6e27539eca
commit 420cd788c4
94 changed files with 10592 additions and 2608 deletions

8
.prettierrc Normal file
View file

@ -0,0 +1,8 @@
{
"printWidth": 120,
"singleQuote": true,
"useTabs": false,
"tabWidth": 2,
"semi": true,
"bracketSpacing": true
}

View file

@ -22,13 +22,8 @@
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"scripts": [],
"es5BrowserSupport": true
},
@ -40,7 +35,7 @@
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"optimization": false,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
@ -76,61 +71,11 @@
"browserTarget": "frontend:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": [
"src/styles.scss"
],
"scripts": [],
"assets": [
"src/favicon.ico",
"src/assets"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"frontend-e2e": {
"root": "e2e/",
"projectType": "application",
"prefix": "",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "frontend:serve"
},
"configurations": {
"production": {
"devServerTarget": "frontend:serve:production"
}
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
"tsConfig": ["src/tsconfig.app.json", "src/tsconfig.spec.json"],
"exclude": ["**/node_modules/**"]
}
}
}

View file

@ -1,28 +0,0 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.e2e.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

View file

@ -1,23 +0,0 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('Welcome to frontend!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});

View file

@ -1,11 +0,0 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get(browser.baseUrl) as Promise<any>;
}
getTitleText() {
return element(by.css('app-root h1')).getText() as Promise<string>;
}
}

View file

@ -1,13 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

3914
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -5,17 +5,18 @@
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
"build:prod": "ng build --prod --base-href /life-qa/",
"lint": "ng lint"
},
"private": true,
"dependencies": {
"@angular/animations": "~7.2.0",
"@angular/animations": "^7.2.15",
"@angular/cdk": "^7.3.7",
"@angular/common": "~7.2.0",
"@angular/compiler": "~7.2.0",
"@angular/core": "~7.2.0",
"@angular/forms": "~7.2.0",
"@angular/material": "^7.3.7",
"@angular/platform-browser": "~7.2.0",
"@angular/platform-browser-dynamic": "~7.2.0",
"@angular/router": "~7.2.0",
@ -29,18 +30,14 @@
"@angular/cli": "~7.3.8",
"@angular/compiler-cli": "~7.2.0",
"@angular/language-service": "~7.2.0",
"@types/node": "~8.9.4",
"@types/jasmine": "~2.8.8",
"@types/jasminewd2": "~2.0.3",
"@types/node": "~8.9.4",
"codelyzer": "~4.5.0",
"jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.0.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~1.1.2",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.4.0",
"husky": "^3.0.4",
"npm-run-all": "^4.1.5",
"prettier": "^1.18.2",
"pretty-quick": "^1.11.1",
"ts-node": "~7.0.0",
"tslint": "~5.11.0",
"typescript": "~3.2.2"

View file

@ -1,7 +1,13 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { PagesComponent } from './components/pages/pages.component';
const routes: Routes = [];
const routes: Routes = [
{
path: '',
component: PagesComponent
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],

View file

@ -1,21 +1,2 @@
<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
<h1>
Welcome to {{ title }}!
</h1>
<img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<h2>Here are some links to help you start: </h2>
<ul>
<li>
<h2><a target="_blank" rel="noopener" href="https://angular.io/tutorial">Tour of Heroes</a></h2>
</li>
<li>
<h2><a target="_blank" rel="noopener" href="https://angular.io/cli">CLI Documentation</a></h2>
</li>
<li>
<h2><a target="_blank" rel="noopener" href="https://blog.angular.io/">Angular blog</a></h2>
</li>
</ul>
<app-modal></app-modal>
<router-outlet></router-outlet>

View file

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

View file

@ -1,4 +1,5 @@
import { Component } from '@angular/core';
import { ModalService } from './services/modal.service';
@Component({
selector: 'app-root',

View file

@ -3,15 +3,47 @@ import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { PageComponent } from './components/pages/page/page.component';
import { TowerComponent } from './components/pages/page/tower/tower.component';
import { DoubleSliderComponent } from './components/shared/double-slider/double-slider.component';
import { PagesComponent } from './components/pages/pages.component';
import { SelectAddComponent } from './components/shared/select-add/select-add.component';
import { ModalComponent } from './components/modal/modal.component';
import { FormsModule } from '@angular/forms';
import { BlockComponent } from './components/pages/page/tower/block/block.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { EditBlockComponent } from './components/modal/modals/edit-block/edit-block.component';
import { SettingsComponent } from './components/modal/modals/settings/settings.component';
import { RemoveTowerComponent } from './components/modal/modals/remove-tower/remove-tower.component';
import { RemovePageComponent } from './components/modal/modals/remove-page/remove-page.component';
import { GetStartedComponent } from './components/modal/modals/get-started/get-started.component';
import { CreateBlockComponent } from './components/modal/modals/create-block/create-block.component';
import { RemoveBlockComponent } from './components/modal/modals/remove-block/remove-block.component';
import { ToggleComponent } from './components/shared/toggle/toggle.component';
import { TasksComponent } from './components/pages/page/tower/tasks/tasks.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
AppRoutingModule
AppComponent,
PageComponent,
TowerComponent,
DoubleSliderComponent,
PagesComponent,
SelectAddComponent,
ModalComponent,
BlockComponent,
EditBlockComponent,
SettingsComponent,
RemoveTowerComponent,
RemovePageComponent,
GetStartedComponent,
CreateBlockComponent,
RemoveBlockComponent,
ToggleComponent,
TasksComponent
],
imports: [BrowserModule, AppRoutingModule, FormsModule, BrowserAnimationsModule, DragDropModule],
providers: [],
bootstrap: [AppComponent]
})

View file

@ -0,0 +1,9 @@
<main class="{{ modalService.active ? 'active' : '' }}" [ngSwitch]="modalService.active?.type">
<app-create-block *ngSwitchCase="ModalType.createBlock"></app-create-block>
<app-edit-block *ngSwitchCase="ModalType.editBlock"></app-edit-block>
<app-remove-block *ngSwitchCase="ModalType.removeBlock"></app-remove-block>
<app-remove-tower *ngSwitchCase="ModalType.removeTower"></app-remove-tower>
<app-settings *ngSwitchCase="ModalType.settings"></app-settings>
<app-get-started *ngSwitchCase="ModalType.getStarted"></app-get-started>
<app-remove-page *ngSwitchCase="ModalType.removePage"></app-remove-page>
</main>

View file

@ -0,0 +1,29 @@
@import '../../../styles';
main {
position: absolute;
top: 0;
left: 0;
z-index: 10000;
width: 100%;
height: 100%;
@include center-child();
padding: var(--large-padding);
box-sizing: border-box;
background: $background-gradient;
transition: opacity 300ms;
&:not(.active) {
opacity: 0;
pointer-events: none;
}
button {
margin-top: var(--medium-padding);
}
}

View file

@ -0,0 +1,20 @@
import { Component } from '@angular/core';
import { ModalService, ModalType } from '../../services/modal.service';
@Component({
selector: 'app-modal',
templateUrl: './modal.component.html',
styleUrls: ['./modal.component.scss']
})
export class ModalComponent {
// Needed for accessing the enum from html.
ModalType = ModalType;
constructor(public modalService: ModalService) {
window.addEventListener('keydown', (event: KeyboardEvent) => {
if (event.key === 'Escape') {
this.modalService.cancel();
}
});
}
}

View file

@ -0,0 +1,33 @@
<div class="header">
<div class="exit" (click)="modalService.cancel()"></div>
<h1>Create an item</h1>
</div>
<div class="select-add-container">
<!-- wrapper for easier styling -->
<app-select-add
class="select"
[options]="modalService.active.input"
[default]="modalService.active.input[0]"
[alwaysDropShadow]="true"
[onlyShadowBorder]="true"
[placeholder]="'Tag this item…'"
(value)="selected = $event"
></app-select-add>
</div>
<!-- wrapper for easier styling -->
<textarea placeholder="Write a description here…" [(ngModel)]="description"></textarea>
<div>
<!-- wrapper for easier styling -->
<app-toggle
[beforeText]="'This task hasn\'t been finished yet'"
[afterText]="'Goal already accomplished'"
[default]="false"
(value)="isDone = $event"
></app-toggle>
</div>
<!-- wrapper for easier styling -->
<button (click)="submit()">Create</button>

View file

@ -0,0 +1,29 @@
@import '../../../../../styles';
:host {
@include card();
box-shadow: $shadow;
width: 66vw;
max-width: 400px;
@media (max-width: $mobile-width) {
width: 300px;
}
box-sizing: border-box;
padding: var(--large-padding);
position: relative;
@include inner-spacing(var(--large-padding));
.header {
@include center-child();
.exit {
position: absolute;
left: var(--large-padding);
@include exit();
}
}
}

View file

@ -0,0 +1,23 @@
import { Component } from '@angular/core';
import { ModalService } from '../../../../services/modal.service';
@Component({
selector: 'app-create-block',
templateUrl: './create-block.component.html',
styleUrls: ['./create-block.component.scss']
})
export class CreateBlockComponent {
selected: string;
description: string = null;
isDone: boolean;
constructor(public modalService: ModalService) {}
submit() {
this.modalService.submit({
selected: this.selected,
description: this.description,
isDone: this.isDone
});
}
}

View file

@ -0,0 +1,33 @@
<div class="header">
<div class="exit" (click)="modalService.cancel()"></div>
<h1>View item</h1>
</div>
<div class="select-add-container">
<!-- wrapper for easier styling -->
<app-select-add
class="select"
[options]="modalService.active.input.options"
[default]="modalService.active.input.options[0]"
[alwaysDropShadow]="true"
[onlyShadowBorder]="true"
[placeholder]="'Tag this item…'"
(value)="selected = $event"
></app-select-add>
</div>
<!-- wrapper for easier styling -->
<textarea placeholder="Write a description here…" [(ngModel)]="modalService.active.input.description"></textarea>
<div>
<!-- wrapper for easier styling -->
<app-toggle
[beforeText]="'This task hasn\'t been finished yet'"
[afterText]="'Goal already accomplished'"
[default]="modalService.active.input.isDone"
(value)="modalService.active.input.isDone = $event"
></app-toggle>
</div>
<!-- wrapper for easier styling -->
<button (click)="submit()">Modify</button>

View file

@ -0,0 +1,29 @@
@import '../../../../../styles';
:host {
@include card();
box-shadow: $shadow;
width: 66vw;
max-width: 400px;
@media (max-width: $mobile-width) {
width: 300px;
}
box-sizing: border-box;
padding: var(--large-padding);
position: relative;
@include inner-spacing(var(--large-padding));
.header {
@include center-child();
.exit {
position: absolute;
left: var(--large-padding);
@include exit();
}
}
}

View file

@ -0,0 +1,21 @@
import { Component } from '@angular/core';
import { ModalService } from '../../../../services/modal.service';
@Component({
selector: 'app-edit-block',
templateUrl: './edit-block.component.html',
styleUrls: ['./edit-block.component.scss']
})
export class EditBlockComponent {
selected: string;
constructor(public modalService: ModalService) {}
submit() {
this.modalService.submit({
selected: this.selected,
description: this.modalService.active.input.description,
isDone: this.modalService.active.input.isDone
});
}
}

View file

@ -0,0 +1,3 @@
<p>
get-started works!
</p>

View file

@ -0,0 +1,12 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-get-started',
templateUrl: './get-started.component.html',
styleUrls: ['./get-started.component.scss']
})
export class GetStartedComponent implements OnInit {
constructor() {}
ngOnInit() {}
}

View file

@ -0,0 +1,3 @@
<p>
remove-block works!
</p>

View file

@ -0,0 +1,12 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-remove-block',
templateUrl: './remove-block.component.html',
styleUrls: ['./remove-block.component.scss']
})
export class RemoveBlockComponent implements OnInit {
constructor() {}
ngOnInit() {}
}

View file

@ -0,0 +1,13 @@
<section>
<div class="header">
<div class="exit" (click)="modalService.cancel()"></div>
<h1>Are you sure?</h1>
</div>
<p>
You are trying to remove <strong>{{ this.modalService.active.input }}</strong
>.
</p>
<button (click)="modalService.submit()">Remove</button>
</section>

View file

@ -0,0 +1,30 @@
@import '../../../../../styles';
section {
@include card();
width: 66vw;
max-width: 500px;
@media (max-width: $mobile-width) {
width: 300px;
}
box-sizing: border-box;
padding: var(--large-padding);
position: relative;
box-shadow: $shadow;
@include inner-spacing(var(--large-padding));
.header {
@include center-child();
.exit {
position: absolute;
left: var(--large-padding);
@include exit();
}
}
}

View file

@ -0,0 +1,11 @@
import { Component } from '@angular/core';
import { ModalService } from '../../../../services/modal.service';
@Component({
selector: 'app-remove-page',
templateUrl: './remove-page.component.html',
styleUrls: ['./remove-page.component.scss']
})
export class RemovePageComponent {
constructor(public modalService: ModalService) {}
}

View file

@ -0,0 +1,16 @@
<section>
<div class="header">
<div class="exit" (click)="modalService.cancel()"></div>
<h1>Are you sure?</h1>
</div>
<p>
You are trying to remove
<span [ngStyle]="{ color: modalService.active.input.baseColor.toString() }">{{
modalService.active.input.name ? modalService.active.input.name : 'an unnamed tower'
}}</span
>.
</p>
<button (click)="modalService.submit()">Remove</button>
</section>

View file

@ -0,0 +1,30 @@
@import '../../../../../styles';
section {
@include card();
width: 66vw;
max-width: 500px;
@media (max-width: $mobile-width) {
width: 300px;
}
box-sizing: border-box;
padding: var(--large-padding);
position: relative;
box-shadow: $shadow;
@include inner-spacing(var(--large-padding));
.header {
@include center-child();
.exit {
position: absolute;
left: var(--large-padding);
@include exit();
}
}
}

View file

@ -0,0 +1,11 @@
import { Component } from '@angular/core';
import { ModalService } from '../../../../services/modal.service';
@Component({
selector: 'app-remove-tower',
templateUrl: './remove-tower.component.html',
styleUrls: ['./remove-tower.component.scss']
})
export class RemoveTowerComponent {
constructor(public modalService: ModalService) {}
}

View file

@ -0,0 +1,19 @@
<div class="header">
<div class="exit" (click)="modalService.cancel()"></div>
<h1>Settings</h1>
</div>
<div>
<!-- wrapper for easier styling -->
<app-toggle
[beforeText]="'Hide create tower button'"
[afterText]="'Show create tower button'"
[default]="!dataService.active.userData?.hideCreateTowerButton"
(value)="dataService.active.userData.hideCreateTowerButton = !$event"
></app-toggle>
</div>
<!-- wrapper for easier styling -->
<p *ngIf="dataService.active?.towers.length == 5">There can be a maximum of <strong>5</strong> towers on each page.</p>
<button (click)="deletePage()">Delete current page</button>

View file

@ -0,0 +1,34 @@
@import '../../../../../styles';
:host {
@include card();
width: 66vw;
max-width: 500px;
@media (max-width: $mobile-width) {
width: 300px;
}
box-sizing: border-box;
padding: var(--large-padding);
position: relative;
box-shadow: $shadow;
@include inner-spacing(var(--large-padding));
.header {
@include center-child();
.exit {
position: absolute;
left: var(--large-padding);
@include exit();
}
}
p {
font-size: var(--medium-font-size);
}
}

View file

@ -0,0 +1,22 @@
import { Component } from '@angular/core';
import { ModalService } from '../../../../services/modal.service';
import { DataService } from '../../../../services/data.service';
@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss']
})
export class SettingsComponent {
constructor(public modalService: ModalService, public dataService: DataService) {}
async deletePage() {
try {
await this.modalService.showRemovePage(this.dataService.active.name);
this.dataService.remove();
this.modalService.submit();
} catch {
// pass
}
}
}

View file

@ -0,0 +1,35 @@
<button
*ngIf="page && page.towers.length < 5 && !dataService.active?.userData?.hideCreateTowerButton"
(click)="createTower()"
>
Create tower
</button>
<section class="towers" cdkDropList cdkDropListOrientation="horizontal" (cdkDropListDropped)="dropDrag($event)">
<app-tower
*ngFor="let tower of page?.towers"
[tower]="tower"
[dateRange]="dateRange"
cdkDrag
(cdkDragStarted)="startDrag(page.towers.indexOf(tower))"
></app-tower>
</section>
<img
*ngIf="isDragging"
src="assets/trash.svg"
alt="trashcan"
(pointerenter)="trashEnter()"
(pointerleave)="trashExit()"
(pointerup)="removeTower()"
/>
<div class="double-slider-container">
<app-double-slider
*ngIf="!isDragging && dates.length >= MIN_BLOCK_COUNT_BEFORE_SHOWING_SLIDER"
[values]="dates"
[labels]="dateLabels"
(lowerBound)="startDate = $event"
(upperBound)="endDate = $event"
></app-double-slider>
</div>

View file

@ -0,0 +1,53 @@
@import '../../../../styles';
:host {
display: flex;
flex-direction: column;
height: 100%;
@include inner-spacing(var(--large-padding));
button {
margin-top: 0;
}
.towers {
display: flex;
justify-content: center;
width: 100%;
flex: 1 0 auto;
transition: box-shadow $short-animation-time;
&.cdk-drop-list-dragging {
*:not(.cdk-drag-placeholder) {
transition: transform $long-animation-time cubic-bezier(0, 0, 0.2, 1);
}
}
@include inner-spacing(var(--medium-padding), $horizontal: true);
}
.double-slider-container {
@media (max-height: $min-height) {
display: none;
}
}
img {
@include square(47.33333px);
padding: 47.3333px 0;
margin: auto;
position: relative;
z-index: 1500;
transition: transform $long-animation-time;
&:hover {
transform: scale(1.2);
}
}
}

View file

@ -0,0 +1,98 @@
import { Component, Input } from '@angular/core';
import { Page } from '../../../model/page';
import { ModalService } from '../../../services/modal.service';
import { DataService } from '../../../services/data.service';
@Component({
selector: 'app-page',
templateUrl: './page.component.html',
styleUrls: ['./page.component.scss']
})
export class PageComponent {
private _page: Page;
@Input() set page(value: Page) {
if (!value) {
return;
}
this._page = value;
value.subscribe(() => this.updateDates());
this.updateDates();
}
get page(): Page {
return this._page;
}
readonly MIN_BLOCK_COUNT_BEFORE_SHOWING_SLIDER = 3;
isDragging = false;
draggedTowerIndex: number;
nearTrashcan = false;
dates: Date[] = [];
startDate: Date;
endDate: Date;
get dateLabels(): string[] {
return this.dates.map(d => d.toLocaleDateString());
}
get dateRange(): { from: Date; to: Date } {
return this.dates.length >= this.MIN_BLOCK_COUNT_BEFORE_SHOWING_SLIDER
? {
from: this.startDate,
to: this.endDate
}
: {
from: new Date(0, 0),
to: new Date(10000, 0)
};
}
constructor(private modalService: ModalService, public dataService: DataService) {}
createTower() {
this.page.addTower();
}
dropDrag(event: any) {
this.page.moveTower(event);
this.isDragging = false;
}
startDrag(id: number) {
this.draggedTowerIndex = id;
this.isDragging = true;
}
trashEnter() {
this.nearTrashcan = true;
window.document.querySelector('.cdk-drag-preview').className += ' trash-highlight';
}
trashExit() {
this.nearTrashcan = false;
const elem = window.document.querySelector('.cdk-drag-preview');
elem.className = elem.className
.split(' ')
.slice(0, -1)
.join(' ');
}
async removeTower() {
try {
const tower = this.page.towers[this.draggedTowerIndex];
await this.modalService.showRemoveTower(tower);
this.page.removeTower(tower);
} catch {
// pass
}
}
private updateDates() {
this.dates = this.page.towers
.reduce((all, t) => [...t.blocks.map(b => b.created), ...all], [])
.sort((d1, d2) => d1.getTime() - d2.getTime());
}
}

View file

@ -0,0 +1 @@
<div [ngStyle]="{ 'background-color': block.color }" (click)="handleClick()"></div>

View file

@ -0,0 +1,15 @@
@import '../../../../../../styles';
:host {
position: relative;
width: calc(100% / 6);
padding-bottom: calc(100% / 6);
div {
position: absolute;
width: 100%;
height: 100%;
@include gravitate();
}
}

View file

@ -0,0 +1,32 @@
import { Component, Input } from '@angular/core';
import { Block } from '../../../../../model/block';
import { ModalService } from '../../../../../services/modal.service';
import { Tower } from '../../../../../model/tower';
@Component({
selector: 'app-block',
templateUrl: './block.component.html',
styleUrls: ['./block.component.scss']
})
export class BlockComponent {
@Input() block: Block;
@Input() tower: Tower;
constructor(private modalService: ModalService) {}
async handleClick() {
try {
const { selected, description, isDone } = await this.modalService.showEditBlock({
options: this.tower.tags,
default: this.block.tag,
description: this.block.description,
isDone: this.block.isDone
});
this.block.tag = selected;
this.block.description = description;
this.block.isDone = isDone;
} catch {
// pass
}
}
}

View file

@ -0,0 +1,16 @@
<div class="container">
<p class="header" (click)="isOpen = !isOpen">
{{ tasks.length == 0 ? 'no' : '' }}
<strong>
{{ tasks.length == 0 ? '' : tasks.length }}
</strong>
{{ tasks.length == 1 ? 'task' : 'tasks' }}
</p>
<div class="all-task" #allTask [ngStyle]="{ height: (isOpen ? allTask?.scrollHeight : 0) + 'px' }">
<p
*ngFor="let task of tasks"
(click)="handleClick(task)"
[innerText]="task.description ? task.description : 'unknown'"
></p>
</div>
</div>

View file

@ -0,0 +1,53 @@
@import '../../../../../../styles';
:host {
width: 100%;
box-sizing: border-box;
.container {
@include card();
box-shadow: $shadow-border;
padding: var(--small-padding);
margin: var(--small-padding);
max-height: 30vh;
overflow-y: auto;
.header {
cursor: pointer;
}
p {
width: 100%;
font-size: var(--medium-font-size);
}
.all-task {
@include inner-spacing(var(--small-padding));
:first-child {
margin-top: var(--small-padding);
}
width: 100%;
box-sizing: border-box;
overflow-y: hidden;
height: 0;
transition: height $long-animation-time;
p {
max-width: 60px;
white-space: nowrap;
text-overflow: ellipsis;
overflow-x: hidden;
text-align: left;
&:hover {
font-weight: bold;
}
}
}
}
}

View file

@ -0,0 +1,41 @@
import { Component, ElementRef, Input, OnInit, ViewChild } from '@angular/core';
import { Block } from '../../../../../model/block';
import { Tower } from '../../../../../model/tower';
import { ModalService } from '../../../../../services/modal.service';
@Component({
selector: 'app-tasks',
templateUrl: './tasks.component.html',
styleUrls: ['./tasks.component.scss']
})
export class TasksComponent implements OnInit {
@Input() tasks: Block[];
@Input() tower: Tower;
@Input() isOpen = false;
@ViewChild('allTask') allTask: ElementRef;
constructor(private modalService: ModalService) {}
ngOnInit() {}
async handleClick(block: Block) {
try {
const { selected, description, isDone } = await this.modalService.showEditBlock({
options: this.tower.tags,
default: block.tag,
description: block.description,
isDone: block.isDone
});
block.tag = selected;
block.description = description;
if (!block.isDone && isDone) {
block.created = new Date();
}
block.isDone = isDone;
} catch {
// pass
}
}
}

View file

@ -0,0 +1,22 @@
<div class="tower">
<div class="container">
<div class="tasks-container">
<app-tasks [tasks]="tasks" [tower]="tower"></app-tasks>
</div>
<img src="assets/plus-sign.svg" alt="add item" (click)="addBlock()" />
<div class="block-container {{ isFalling ? 'falling' : '' }}">
<app-block *ngFor="let block of drawableBlocks" [block]="block" [tower]="tower"></app-block>
</div>
</div>
<label for="tower-name" class="hidden">Card name</label>
<input
id="tower-name"
type="text"
placeholder="…"
[(ngModel)]="tower.name"
[ngStyle]="{ color: tower.baseColor.toString() }"
/>
</div>

View file

@ -0,0 +1,140 @@
@import '../../../../../styles';
:host {
cursor: pointer;
&.cdk-drag-animating {
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
}
&.cdk-drag-placeholder {
opacity: 0;
}
&.cdk-drag-preview,
&:hover {
div {
.container {
box-shadow: $shadow;
}
}
}
&.trash-highlight {
.container {
transform: scale(0.75);
position: relative;
:before {
opacity: 0.5 !important;
}
}
input {
display: none;
}
}
.tower {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
height: 100%;
@include inner-spacing(var(--small-padding));
.container {
display: flex;
flex-direction: column;
flex: 1 1 auto;
position: relative;
@include card();
transition: transform $short-animation-time, box-shadow $long-animation-time;
@include inner-spacing(var(--medium-padding));
width: 100%;
:before {
content: '';
pointer-events: none;
position: absolute;
z-index: 3;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: red;
opacity: 0;
border-radius: var(--border-radius);
transition: opacity $short-animation-time;
}
.tasks-container {
}
img {
position: relative;
z-index: 2;
height: 56px;
@media (max-width: $mobile-width) {
height: 42px;
}
opacity: 0.33;
transition: opacity $long-animation-time;
cursor: pointer;
&:hover {
opacity: 1;
}
}
.block-container {
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
align-content: flex-start;
align-items: flex-end;
transform: scaleY(-1);
flex: 1;
&.falling > *:last-child {
animation: falling 1.5s cubic-bezier(0.5, 0, 1, 0) forwards;
@keyframes falling {
0% {
opacity: 0;
transform: translateY(500%);
}
50% {
opacity: 1;
}
100% {
transform: translateY(0);
}
}
}
}
}
input[type='text'] {
font-size: var(--medium-font-size);
text-align: center;
@media (min-width: $mobile-width) {
width: 50%;
}
}
}
}

View file

@ -0,0 +1,49 @@
import { Component, ElementRef, Input, ViewChild } from '@angular/core';
import { Tower } from '../../../../model/tower';
import { ModalService } from '../../../../services/modal.service';
import { Block } from '../../../../model/block';
@Component({
selector: 'app-tower',
templateUrl: './tower.component.html',
styleUrls: ['./tower.component.scss']
})
export class TowerComponent {
@Input() set dateRange(value: { from: Date; to: Date }) {
if (this.dateRange !== undefined && this.dateRange.from === value.from && this.dateRange.to === value.to) {
return;
}
this._dateRange = value;
}
get dateRange(): { from: Date; to: Date } {
return this._dateRange;
}
public constructor(private modalService: ModalService) {}
get drawableBlocks(): Block[] {
return this.tower.blocks.filter(
block => this.dateRange.from <= block.created && block.created <= this.dateRange.to && block.isDone
);
}
get tasks(): Block[] {
return this.tower.blocks.filter(block => !block.isDone);
}
@Input() tower: Tower;
_dateRange: { from: Date; to: Date };
isFalling = true;
public async addBlock() {
try {
const { selected: tag, description, isDone } = await this.modalService.showCreateBlock(this.tower.tags);
this.tower.addBlock({ tag, description, isDone });
} catch (e) {
// pass
}
}
}

View file

@ -0,0 +1,18 @@
<div class="select-add-container">
<!-- wrapper for easier styling -->
<app-select-add
[options]="dataService.pageNames"
[default]="dataService.active?.name"
(value)="selectPage($event)"
[placeholder]="'Add a new page…'"
></app-select-add>
</div>
<!-- wrapper for easier styling -->
<div class="page-container">
<!-- wrapper for easier styling -->
<app-page *ngIf="dataService.active !== null" [page]="dataService.active"></app-page>
</div>
<!-- wrapper for easier styling -->
<button (click)="openSettings()">Settings</button>

View file

@ -0,0 +1,21 @@
@import '../../../styles';
:host {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
@include inner-spacing(var(--large-padding));
.select-add-container {
width: 250px;
margin-left: auto;
margin-right: auto;
}
.page-container {
flex: 1 0 auto;
}
}

View file

@ -0,0 +1,40 @@
import { Component, ElementRef, ViewChild } from '@angular/core';
import { Page } from '../../model/page';
import { DataService } from '../../services/data.service';
import { ModalService } from '../../services/modal.service';
@Component({
selector: 'app-pages',
templateUrl: './pages.component.html',
styleUrls: ['./pages.component.scss']
})
export class PagesComponent {
@ViewChild('top') top: ElementRef;
@ViewChild('page') page: ElementRef;
@ViewChild('bottom') bottom: ElementRef;
constructor(public dataService: DataService, private modalService: ModalService) {}
async selectPage(selected: string) {
if (!this.dataService.pageNames.includes(selected)) {
const page = new Page({
name: selected,
towers: [],
userData: {}
});
this.dataService.push(page);
page.addTower();
}
await this.dataService.changeActiveByName(selected);
}
async openSettings() {
try {
await this.modalService.showSettings();
} catch {
// pass
}
}
}

View file

@ -0,0 +1,13 @@
<div class="container">
<label for="date-selector-1">date selector 1</label>
<label for="date-selector-2">date selector 2</label>
<input id="date-selector-1" type="range" min="0" [max]="MAX - 1" [(ngModel)]="oneValue" />
<input id="date-selector-2" type="range" min="0" [max]="MAX - 1" [(ngModel)]="otherValue" />
<div class="value-container">
<span
*ngFor="let i of drawnLabelsIndices"
[innerHTML]="drawnLabels[i]"
[ngStyle]="{ transform: getOffset(i) }"
></span>
</div>
</div>

View file

@ -0,0 +1,75 @@
@import '../../../../styles';
$height: 70px;
$width: 300px;
$slider-size: 40px;
.container {
width: $width;
height: $height;
position: relative;
margin: $slider-size / 2 auto 0 auto;
label {
display: none;
}
input[type='range'] {
width: 100%;
position: absolute;
left: 0;
-webkit-appearance: none;
outline: none;
&::-webkit-slider-thumb {
-webkit-appearance: none;
height: $slider-size;
width: $slider-size;
border-radius: 1000px;
background-color: $light-color;
transform-origin: center center;
transform: translateY(-$slider-size / 2 + $line-height / 2);
transition: box-shadow $long-animation-time, transform $long-animation-time;
&:hover {
box-shadow: $shadow;
transform: translateY(-$slider-size / 2 + $line-height / 2) scale(1.1);
}
cursor: pointer;
position: relative;
z-index: 2;
}
&::-webkit-slider-runnable-track {
-webkit-appearance: none;
width: 100%;
height: $line-height;
background-color: $text-color;
border-radius: 1000px;
}
&::-moz-focus-outer {
border: 0;
}
}
.value-container {
@include small-text();
display: flex;
justify-content: space-evenly;
span {
display: block;
margin-top: 10px;
}
}
}

View file

@ -0,0 +1,98 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { range } from '../../../utils/range';
@Component({
selector: 'app-double-slider',
templateUrl: './double-slider.component.html',
styleUrls: ['./double-slider.component.scss']
})
export class DoubleSliderComponent {
@Input() labels: string[];
@Input() set values(values: any[]) {
if (values.length === 0) {
return;
}
this._values = values;
this.calculateLabels();
this.emitValue();
}
get values(): any[] {
return this._values;
}
get oneValue(): number {
return this._oneValue;
}
set oneValue(value: number) {
this._oneValue = value;
this.emitValue();
}
get otherValue(): number {
return this._otherValue;
}
set otherValue(value: number) {
this._otherValue = value;
this.emitValue();
}
private _values: any[];
@Output() lowerBound: EventEmitter<any> = new EventEmitter();
@Output() upperBound: EventEmitter<any> = new EventEmitter();
drawnLabels: string[];
readonly MAX = 100;
private _oneValue = 0;
private _otherValue: number = this.MAX - 1;
drawnLabelsIndices: Iterable<number>;
private calculateLabels() {
const labelCount = 6;
const jumpLength = Math.round(this.labels.length / labelCount);
this.drawnLabels = this.labels.filter((_, index) => index % jumpLength === 0);
this.drawnLabelsIndices = range({ max: this.drawnLabels.length });
}
private indexFromValue(value: number): number {
return Math.floor((value / this.MAX) * this.values.length);
}
getOffset(index: number): string {
const labelIndex = index / this.drawnLabels.length;
const slider1Index = this.oneValue / this.MAX - 0.1;
const slider2Index = this.otherValue / this.MAX - 0.1;
const dist = (a, b) => Math.abs(a - b);
const labelSliderDistance = Math.min(dist(labelIndex, slider1Index), dist(labelIndex, slider2Index));
const ACTIVE_ZONE = 0.2;
const BASE_TRANSFORM = 'translateX(-50%) rotate(-45deg) translateY(100%)';
if (labelSliderDistance > ACTIVE_ZONE) {
return BASE_TRANSFORM;
}
return `translateY(${Math.pow((ACTIVE_ZONE - labelSliderDistance) / ACTIVE_ZONE, 1) * 30}px) ${BASE_TRANSFORM}`;
}
private emitValue() {
if (this.oneValue < this.otherValue) {
this.lowerBound.emit(this.values[this.indexFromValue(this.oneValue)]);
this.upperBound.emit(this.values[this.indexFromValue(this.otherValue)]);
} else {
this.lowerBound.emit(this.values[this.indexFromValue(this.otherValue)]);
this.upperBound.emit(this.values[this.indexFromValue(this.oneValue)]);
}
}
}

View file

@ -0,0 +1,27 @@
<div class="select-add {{ onlyShadowBorder ? 'shadow-border' : '' }}">
<div #top class="top" (click)="toggle()">
<p [innerHTML]="selected ? selected : placeholder"></p>
<img src="assets/arrow.svg" [className]="isOpen ? 'upside-down' : ''" alt="arrow" />
</div>
<div class="bottom-container">
<div #bottom class="bottom {{ isOpen ? 'open' : '' }}">
<p *ngFor="let option of otherOptions" [innerHTML]="option" (click)="select(option)"></p>
<input
type="text"
*ngIf="options.length <= maxItemCount"
placeholder="Add a value…"
[(ngModel)]="newOption"
(keyup)="handleKeys($event)"
/>
<button *ngIf="options.length <= maxItemCount" (click)="addNewOption()">Add</button>
</div>
</div>
<div
class="background {{ isOpen || alwaysDropShadow ? 'active' : '' }}"
[ngStyle]="{ height: backgroundHeight }"
></div>
</div>

View file

@ -0,0 +1,117 @@
@import '../../../../styles';
$inner-padding: var(--medium-padding);
.select-add {
width: 100%;
position: relative;
.top,
.bottom {
padding: $inner-padding;
z-index: 4;
}
.top {
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
cursor: pointer;
p {
display: inline-block;
@include sub-title-text();
}
img {
@include square(16px);
transition: transform $long-animation-time;
&.upside-down {
transform: rotate(-180deg);
}
}
}
.bottom-container {
width: 100%;
height: 40vh;
position: absolute;
overflow-y: hidden;
pointer-events: none;
.bottom {
position: absolute;
width: 100%;
pointer-events: all;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: flex-start;
border-radius: 0 0 var(--border-radius) var(--border-radius);
padding-top: 0;
@include inner-spacing($inner-padding);
transform: translateY(-100%);
visibility: hidden;
&.open {
visibility: visible;
transform: none;
}
transition: transform $long-animation-time;
p {
@include sub-title-text();
display: inline-block;
text-align: left;
cursor: pointer;
}
}
}
.background {
position: absolute;
top: 0;
height: 100%;
width: 100%;
@include card();
z-index: 3;
transition: box-shadow $long-animation-time, height $long-animation-time;
&.active {
box-shadow: $shadow;
}
}
&:hover {
.background {
box-shadow: $shadow;
}
}
&.shadow-border {
.background.active {
box-shadow: $shadow-border;
}
}
&.shadow-border:hover {
.background {
box-shadow: $shadow-border;
}
}
}

View file

@ -0,0 +1,66 @@
import { Component, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';
@Component({
selector: 'app-select-add',
templateUrl: './select-add.component.html',
styleUrls: ['./select-add.component.scss']
})
export class SelectAddComponent {
@Input() placeholder = 'Add a new value…';
@Input() maxItemCount = 7;
@Input() options: string[];
@Input() alwaysDropShadow = false;
@Input() onlyShadowBorder = false;
@Input() set default(value: string) {
this.selected = value;
if (value) {
this.value.emit(value);
}
}
@Output() value: EventEmitter<string> = new EventEmitter();
@ViewChild('top') top: ElementRef;
@ViewChild('bottom') bottom: ElementRef;
selected: string;
newOption: string;
isOpen = false;
get otherOptions(): string[] {
return this.options.filter(a => a !== this.selected);
}
handleKeys(event: KeyboardEvent) {
if (event.key === 'Enter') {
this.addNewOption();
}
}
get backgroundHeight(): string {
if (this.isOpen && this.top && this.bottom) {
const topHeight = this.top.nativeElement.clientHeight;
const bottomHeight = this.bottom.nativeElement.clientHeight;
return `${topHeight + bottomHeight}px`;
}
return `100%`;
}
addNewOption() {
if (this.newOption) {
this.select(this.newOption);
this.newOption = '';
}
}
select(option: string) {
this.selected = option;
this.value.emit(this.selected);
this.toggle();
}
toggle() {
this.isOpen = !this.isOpen;
}
}

View file

@ -0,0 +1,5 @@
<span [className]="!on ? 'active' : ''" (click)="on = false" [innerText]="beforeText"></span>
<input type="checkbox" [(ngModel)]="on" [className]="on ? 'on' : ''" />
<span [className]="on ? 'active' : ''" (click)="on = true" [innerText]="afterText"></span>

View file

@ -0,0 +1,66 @@
@import '../../../../styles';
:host {
$size: 30px;
@include center-child();
@include inner-spacing(var(--medium-padding), $horizontal: true);
span {
@include medium-text();
max-width: 3 * $size;
cursor: pointer;
&.active {
font-weight: bold;
}
&:first-of-type {
text-align: right;
}
&:last-of-type {
text-align: left;
}
}
input[type='checkbox'] {
-webkit-appearance: none;
width: 2 * $size;
height: $size;
border-radius: 1000px;
box-shadow: $shadow-border;
position: relative;
cursor: pointer;
&:after {
content: '';
position: absolute;
display: block;
left: 0;
@include square($size);
border-radius: 1000px;
background-color: $text-color;
transition: box-shadow $long-animation-time, left $long-animation-time, transform $long-animation-time;
}
&:hover:after {
box-shadow: $shadow;
transform: translateX(2px);
}
&.on:hover:after {
transform: translateX(-2px);
}
&.on:after {
left: $size;
}
}
}

View file

@ -0,0 +1,27 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-toggle',
templateUrl: './toggle.component.html',
styleUrls: ['./toggle.component.scss']
})
export class ToggleComponent {
@Input() beforeText: string;
@Input() afterText: string;
@Output() value: EventEmitter<boolean> = new EventEmitter();
@Input() set default(value: boolean) {
this.on = value;
}
private _on = false;
set on(value: boolean) {
this._on = value;
this.value.emit(value);
}
get on(): boolean {
return this._on;
}
}

View file

@ -0,0 +1,6 @@
export interface IBlock {
created: Date;
tag: string;
isDone: boolean;
description: string;
}

View file

@ -0,0 +1,5 @@
export interface IColor {
h: number;
s: number;
l: number;
}

View file

@ -0,0 +1,14 @@
import { ITower } from './tower';
export interface IPage {
name: string;
towers: ITower[];
userData: {
hideCreateTowerButton: boolean;
defaultDateRange: {
from: Date;
to: Date;
};
};
}

View file

@ -0,0 +1,8 @@
import { IBlock } from './block';
import { IColor } from './color';
export interface ITower {
name: string;
blocks: IBlock[];
baseColor: IColor;
}

View file

@ -0,0 +1,4 @@
export interface Vector {
x: number;
y: number;
}

51
src/app/model/base.ts Normal file
View file

@ -0,0 +1,51 @@
export class Base {
private static propertyList: any = {};
protected subscribers: (() => void)[] = [];
constructor(properties: any) {
const type = this.constructor.name;
if (!Base.propertyList.hasOwnProperty(type)) {
Base.propertyList[type] = [];
}
for (const property in properties) {
if (properties.hasOwnProperty(property)) {
const propertyName = `__${property}`;
this[propertyName] = properties[property];
Object.defineProperty(this, property, {
get: () => this[propertyName],
set: value => {
if (value == this[propertyName]) {
return;
}
this[propertyName] = value;
this.update();
}
});
if (!Base.propertyList[type].includes(property)) {
Base.propertyList[type].push(property);
}
}
}
}
toJSON(): object {
return Base.propertyList[this.constructor.name].reduce(
(object, property) => ({
[property]: this[property],
...object
}),
{}
);
}
subscribe(func: () => void) {
this.subscribers.push(func);
}
protected update() {
this.subscribers.map(f => f());
}
}

23
src/app/model/block.ts Normal file
View file

@ -0,0 +1,23 @@
import { Base } from './base';
import { IBlock } from '../interfaces/persistance/block';
import { Color } from './color';
export class Block extends Base implements IBlock {
constructor(props: IBlock) {
super(props);
if (this.created.constructor.name !== 'Date') {
// Prevent update message
// @ts-ignore
this.__created = new Date(this.created);
}
}
color: Color;
// Only here to prevent ts warnings.
created: Date;
isDone: boolean;
description: string;
tag: string;
}

28
src/app/model/color.ts Normal file
View file

@ -0,0 +1,28 @@
import { IColor } from '../interfaces/persistance/color';
import { Base } from './base';
export class Color extends Base implements IColor {
constructor(props: IColor) {
super(props);
}
// Only here to prevent ts warnings.
h: number;
s: number;
l: number;
public lighten(by: number) {
const newL = this.l + by;
if (this.l > 100) {
this.l = 100;
} else if (this.l < 0) {
this.l = 0;
}
return new Color({ h: this.h, s: this.s, l: newL });
}
public toString(): string {
return `hsl(${this.h}, ${this.s}%, ${this.l}%)`;
}
}

62
src/app/model/page.ts Normal file
View file

@ -0,0 +1,62 @@
import { Base } from './base';
import { IPage } from '../interfaces/persistance/page';
import { Tower } from './tower';
import { ITower } from '../interfaces/persistance/tower';
export class Page extends Base implements IPage {
constructor(props) {
super(props);
// @ts-ignore to prevent update message
this.__towers = this.towers.map(t => this.createTower(t));
}
// Only here to prevent ts warnings.
name: string;
towers: Tower[];
userData: {
hideCreateTowerButton: boolean;
defaultDateRange: {
from: Date;
to: Date;
};
};
moveTower({ previousIndex, currentIndex }: { previousIndex: number; currentIndex: number }) {
if (previousIndex === currentIndex) {
return;
}
const tower = this.towers[previousIndex];
this.towers.splice(previousIndex, 1);
this.towers.splice(currentIndex, 0, tower);
this.update();
}
addTower(name = '') {
let hue;
do {
hue = Math.random() * 360;
} while (30 <= hue && hue <= 200);
console.log(hue);
this.towers.push(
this.createTower({
name,
blocks: [],
baseColor: { h: hue, s: 100, l: 50 }
})
);
this.update();
}
private createTower(props: ITower): Tower {
const tower = new Tower(props);
tower.subscribe(() => this.update());
return tower;
}
removeTower(tower: Tower) {
this.towers = this.towers.filter(t => t !== tower);
}
}

54
src/app/model/tower.ts Normal file
View file

@ -0,0 +1,54 @@
import { ITower } from '../interfaces/persistance/tower';
import { Color } from './color';
import { Block } from './block';
import { Base } from './base';
import { IBlock } from '../interfaces/persistance/block';
import { hashCode } from '../utils/hash';
export class Tower extends Base implements ITower {
constructor(props: ITower) {
super(props);
// @ts-ignore to prevent update message
this.__baseColor = new Color(this.baseColor);
this.blocks = this.blocks.map(b => this.createBlock(b));
this.blocks.sort((a, b) => a.created.getTime() - b.created.getTime());
}
tags: string[];
// Only here to prevent ts warnings.
name: string;
blocks: Block[];
baseColor: Color;
addBlock(props: { tag: string; description: string; isDone: boolean }) {
this.blocks.push(
this.createBlock({
created: new Date(),
...props
})
);
this.update();
}
private createBlock(props: IBlock): Block {
const block = new Block(props);
block.subscribe(() => this.update());
return block;
}
protected update() {
this.tags = [];
for (const block of this.blocks) {
if (!this.tags.includes(block.tag)) {
this.tags.push(block.tag);
}
block.color = this.baseColor.lighten(hashCode(block.tag) * 50);
}
super.update();
}
}

View file

@ -0,0 +1,93 @@
import { Injectable } from '@angular/core';
import { StoreService } from './store.service';
import { Page } from '../model/page';
const USER_DATA_KEY = 'life-towers.user-data.v.1';
@Injectable({
providedIn: 'root'
})
export class DataService {
get active(): Page {
return this._active;
}
get pageNames(): string[] {
return this.data.map(p => p.name);
}
private subscribers: (() => void)[] = [];
private _active: Page = null;
private data: Page[];
private hasLoaded = new Promise(resolve => (this.afterLoadFinished = resolve));
private afterLoadFinished: () => void;
constructor(private storeService: StoreService) {
this.init();
}
push(value: Page) {
value.subscribe(() => this.save());
this.data.push(value);
this._active = value;
this.save();
}
remove() {
this.data = this.data.filter(p => p !== this.active);
this._active = this.data.length > 0 ? this.data[0] : null;
this.save();
}
subscribe(func: () => void) {
this.subscribers.push(func);
}
async changeActiveByName(name: string): Promise<void> {
await this.hasLoaded;
this._active = this.data.filter(p => p.name === name)[0];
this.saveActiveIndex(this.data.indexOf(this.active));
this.update();
}
async changeActiveByIndex(index: number): Promise<void> {
await this.hasLoaded;
this._active = this.data[index];
this.saveActiveIndex(index);
this.update();
}
private async init() {
this.data = await this.storeService.load();
this.data.map(p => p.subscribe(() => this.save()));
this._active = this.data.length > 0 ? this.data[0] : null;
this.loadActiveIndex();
this.afterLoadFinished();
}
private save() {
this.storeService.save(this.data);
this.update();
}
private update() {
this.subscribers.map(f => f());
}
private loadActiveIndex() {
const userData = JSON.parse(window.localStorage.getItem(USER_DATA_KEY));
if (userData === null) {
return;
}
this._active = this.data[userData.index];
}
private saveActiveIndex(index: number) {
window.localStorage.setItem(
USER_DATA_KEY,
JSON.stringify({
index
})
);
}
}

View file

@ -0,0 +1,83 @@
import { Injectable } from '@angular/core';
import { Tower } from '../model/tower';
import { top } from '../utils/top';
export enum ModalType {
createBlock,
editBlock,
removeBlock,
settings,
removeTower,
removePage,
getStarted
}
interface Modal {
type: ModalType;
input: any;
resolve: (output: any) => void;
reject: () => void;
}
@Injectable({
providedIn: 'root'
})
export class ModalService {
private modalStack: Modal[] = [];
showCreateBlock(options: string[]): Promise<{ selected: string; description: string; isDone: boolean }> {
return this.createPromiseAndPushToStack(options, ModalType.createBlock);
}
showEditBlock(data: {
default: string;
options: string[];
description: string;
isDone: boolean;
}): Promise<{ selected: string; description: string; isDone: boolean }> {
return this.createPromiseAndPushToStack(data, ModalType.editBlock);
}
showSettings(): Promise<void> {
return this.createPromiseAndPushToStack(null, ModalType.settings);
}
showRemoveTower(tower: Tower): Promise<void> {
return this.createPromiseAndPushToStack(tower, ModalType.removeTower);
}
showRemovePage(name: string): Promise<void> {
return this.createPromiseAndPushToStack(name, ModalType.removePage);
}
get active(): Modal {
return top(this.modalStack);
}
submit(output?: any) {
const { resolve } = this.modalStack.pop();
resolve(output);
}
cancel() {
const { reject } = this.modalStack.pop();
reject();
}
private createPromiseAndPushToStack(input: any, type: ModalType): Promise<any> {
const modal = {
input,
type,
resolve: () => {},
reject: () => {}
};
const modalPromise = new Promise((resolve, reject) => {
modal.resolve = resolve;
modal.reject = reject;
});
this.modalStack.push(modal);
return modalPromise;
}
}

View file

@ -0,0 +1,102 @@
import { Injectable } from '@angular/core';
import { Page } from '../model/page';
const LOCAL_STORAGE_KEY = 'life-towers.data.v.2.1';
@Injectable({
providedIn: 'root'
})
export class StoreService {
private storedData: Page[];
private mockData: string = JSON.stringify([
{
name: 'Work & life',
userData: {},
towers: [
{
name: 'work',
baseColor: { h: 0, s: 100, l: 50 },
blocks: [
{
created: new Date(2015, 2, 13),
tag: 'a',
description: 'done it',
isDone: true
},
{
created: new Date(2016, 2, 15),
tag: 'go to school',
description: 'done it'
},
{
created: new Date(2017, 2, 15),
tag: 'go to work',
isDone: true
},
{
created: new Date(2018, 2, 13),
tag: 'go to work',
description: 'done it',
isDone: true
},
{
created: new Date(2019, 3, 13),
tag: 'go to work'
},
{
created: new Date(2020, 2, 15),
tag: 'go to school',
description: 'done it',
isDone: true
},
{
created: new Date(2021, 2, 15),
tag: 'go to school'
}
]
},
{
baseColor: { h: 180, s: 100, l: 50 },
name: 'life',
blocks: [
{
created: new Date(2019, 3, 13),
tag: 'go home',
description: 'done it'
},
{
created: new Date(2019, 4, 13),
tag: 'go home'
},
{
created: new Date(2019, 4, 15),
tag: 'go to work',
description: 'done it'
},
{
created: new Date(2019, 4, 15, 14),
tag: 'go to work'
}
]
}
]
}
]);
constructor() {
const localStorageData = localStorage.getItem(LOCAL_STORAGE_KEY);
const data = JSON.parse(localStorageData ? localStorageData : this.mockData);
this.storedData = data.map(p => new Page(p));
}
async load(): Promise<Page[]> {
return this.storedData;
}
async save(data: Page[]) {
this.storedData = data;
console.log('save', this.storedData);
window.localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(this.storedData));
}
}

14
src/app/utils/hash.ts Normal file
View file

@ -0,0 +1,14 @@
export const hashCode = (text: string) => {
let hash = 0;
if (text.length == 0) {
return hash;
}
for (let i = 0; i < text.length; i++) {
const char = text.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash;
}
hash /= Math.pow(2, 32) - 1;
return hash;
};

7
src/app/utils/range.ts Normal file
View file

@ -0,0 +1,7 @@
export const range = ({ min = 0, max = Infinity, step = 1 }: { min?: number; max?: number; step?: number }) => {
return {
*[Symbol.iterator]() {
for (let i = min; i < max; yield i, i += step);
}
};
};

3
src/app/utils/top.ts Normal file
View file

@ -0,0 +1,3 @@
export const top = <T>(iterable: ArrayLike<T>): T => {
return iterable.length > 0 ? iterable[iterable.length - 1] : null;
};

4
src/assets/arrow.svg Normal file
View file

@ -0,0 +1,4 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 292.362 292.362" style="enable-background:new 0 0 292.362 292.362;" xml:space="preserve" class=""><g><g>
<path d="M286.935,69.377c-3.614-3.617-7.898-5.424-12.848-5.424H18.274c-4.952,0-9.233,1.807-12.85,5.424 C1.807,72.998,0,77.279,0,82.228c0,4.948,1.807,9.229,5.424,12.847l127.907,127.907c3.621,3.617,7.902,5.428,12.85,5.428 s9.233-1.811,12.847-5.428L286.935,95.074c3.613-3.617,5.427-7.898,5.427-12.847C292.362,77.279,290.548,72.998,286.935,69.377z" data-original="#000000" class="active-path" data-old_color="#000000" fill="#5D576B"/>
</g></g> </svg>

After

Width:  |  Height:  |  Size: 746 B

12
src/assets/plus-sign.svg Normal file
View file

@ -0,0 +1,12 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 31.059 31.059" style="enable-background:new 0 0 31.059 31.059;" xml:space="preserve" width="512px" height="512px" class=""><g><g>
<g>
<path d="M15.529,31.059C6.966,31.059,0,24.092,0,15.529C0,6.966,6.966,0,15.529,0 c8.563,0,15.529,6.966,15.529,15.529C31.059,24.092,24.092,31.059,15.529,31.059z M15.529,1.774 c-7.585,0-13.755,6.171-13.755,13.755s6.17,13.754,13.755,13.754c7.584,0,13.754-6.17,13.754-13.754S23.113,1.774,15.529,1.774z" data-original="#010002" class="active-path" data-old_color="#010002" fill="#5D576B"/>
</g>
<g>
<path d="M21.652,16.416H9.406c-0.49,0-0.888-0.396-0.888-0.887c0-0.49,0.397-0.888,0.888-0.888h12.246 c0.49,0,0.887,0.398,0.887,0.888C22.539,16.02,22.143,16.416,21.652,16.416z" data-original="#010002" class="active-path" data-old_color="#010002" fill="#5D576B"/>
</g>
<g>
<path d="M15.529,22.539c-0.49,0-0.888-0.397-0.888-0.887V9.406c0-0.49,0.398-0.888,0.888-0.888 c0.49,0,0.887,0.398,0.887,0.888v12.246C16.416,22.143,16.02,22.539,15.529,22.539z" data-original="#010002" class="active-path" data-old_color="#010002" fill="#5D576B"/>
</g>
</g></g> </svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

13
src/assets/trash.svg Normal file
View file

@ -0,0 +1,13 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="512px" height="512px" viewBox="0 0 729.837 729.838" style="enable-background:new 0 0 729.837 729.838;" xml:space="preserve"><g><g>
<g>
<g>
<path d="M589.193,222.04c0-6.296,5.106-11.404,11.402-11.404S612,215.767,612,222.04v437.476c0,19.314-7.936,36.896-20.67,49.653 c-12.733,12.734-30.339,20.669-49.653,20.669H188.162c-19.315,0-36.943-7.935-49.654-20.669 c-12.734-12.734-20.669-30.313-20.669-49.653V222.04c0-6.296,5.108-11.404,11.403-11.404c6.296,0,11.404,5.131,11.404,11.404 v437.476c0,13.02,5.37,24.922,13.97,33.521c8.6,8.601,20.503,13.993,33.522,13.993h353.517c13.019,0,24.896-5.394,33.498-13.993 c8.624-8.624,13.992-20.503,13.992-33.498V222.04H589.193z" data-original="#000000" class="active-path" data-old_color="#000000" fill="#5D576B"/>
<path d="M279.866,630.056c0,6.296-5.108,11.403-11.404,11.403s-11.404-5.107-11.404-11.403v-405.07 c0-6.296,5.108-11.404,11.404-11.404s11.404,5.108,11.404,11.404V630.056z" data-original="#000000" class="active-path" data-old_color="#000000" fill="#5D576B"/>
<path d="M376.323,630.056c0,6.296-5.107,11.403-11.403,11.403s-11.404-5.107-11.404-11.403v-405.07 c0-6.296,5.108-11.404,11.404-11.404s11.403,5.108,11.403,11.404V630.056z" data-original="#000000" class="active-path" data-old_color="#000000" fill="#5D576B"/>
<path d="M472.803,630.056c0,6.296-5.106,11.403-11.402,11.403c-6.297,0-11.404-5.107-11.404-11.403v-405.07 c0-6.296,5.107-11.404,11.404-11.404c6.296,0,11.402,5.108,11.402,11.404V630.056L472.803,630.056z" data-original="#000000" class="active-path" data-old_color="#000000" fill="#5D576B"/>
<path d="M273.214,70.323c0,6.296-5.108,11.404-11.404,11.404c-6.295,0-11.403-5.108-11.403-11.404 c0-19.363,7.911-36.943,20.646-49.677C283.787,7.911,301.368,0,320.73,0h88.379c19.339,0,36.92,7.935,49.652,20.669 c12.734,12.734,20.67,30.362,20.67,49.654c0,6.296-5.107,11.404-11.403,11.404s-11.403-5.108-11.403-11.404 c0-13.019-5.369-24.922-13.97-33.522c-8.602-8.601-20.503-13.994-33.522-13.994h-88.378c-13.043,0-24.922,5.369-33.546,13.97 C278.583,45.401,273.214,57.28,273.214,70.323z" data-original="#000000" class="active-path" data-old_color="#000000" fill="#5D576B"/>
<path d="M99.782,103.108h530.273c11.189,0,21.405,4.585,28.818,11.998l0.047,0.048c7.413,7.412,11.998,17.628,11.998,28.818 v29.46c0,6.295-5.108,11.403-11.404,11.403h-0.309H70.323c-6.296,0-11.404-5.108-11.404-11.403v-0.285v-29.175 c0-11.166,4.585-21.406,11.998-28.818l0.048-0.048C78.377,107.694,88.616,103.108,99.782,103.108L99.782,103.108z M630.056,125.916H99.782c-4.965,0-9.503,2.02-12.734,5.274L87,131.238c-3.255,3.23-5.274,7.745-5.274,12.734v18.056h566.361 v-18.056c0-4.965-2.02-9.503-5.273-12.734l-0.049-0.048C639.536,127.936,635.021,125.916,630.056,125.916z" data-original="#000000" class="active-path" data-old_color="#000000" fill="#5D576B"/>
</g>
</g>
</g></g> </svg>

After

Width:  |  Height:  |  Size: 3 KiB

4
src/assets/x-sign.svg Normal file
View file

@ -0,0 +1,4 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 212.982 212.982" style="enable-background:new 0 0 212.982 212.982;" xml:space="preserve" width="512px" height="512px" class=""><g><g id="Close">
<path d="M131.804,106.491l75.936-75.936c6.99-6.99,6.99-18.323,0-25.312 c-6.99-6.99-18.322-6.99-25.312,0l-75.937,75.937L30.554,5.242c-6.99-6.99-18.322-6.99-25.312,0c-6.989,6.99-6.989,18.323,0,25.312 l75.937,75.936L5.242,182.427c-6.989,6.99-6.989,18.323,0,25.312c6.99,6.99,18.322,6.99,25.312,0l75.937-75.937l75.937,75.937 c6.989,6.99,18.322,6.99,25.312,0c6.99-6.99,6.99-18.322,0-25.312L131.804,106.491z" data-original="#000000" class="active-path" data-old_color="#000000" fill="#5D576B"/>
</g></g> </svg>

After

Width:  |  Height:  |  Size: 816 B

View file

@ -1,12 +1,21 @@
<!doctype html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta charset="utf-8" />
<title>Frontend</title>
<base href="/">
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link
href="https://fonts.googleapis.com/css?family=Open+Sans+Condensed:300|Raleway&display=swap&subset=latin-ext"
rel="stylesheet"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#5d576b" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
</head>
<body>
<app-root></app-root>

View file

@ -0,0 +1,22 @@
@import 'common-variables';
$long-animation-time: 200ms;
$short-animation-time: 100ms;
@mixin gravitate {
cursor: pointer;
transition: box-shadow $long-animation-time, transform $long-animation-time;
&:hover {
box-shadow: $shadow;
transform: scale(1.1);
}
}
@mixin jump {
cursor: pointer;
transition: transform $long-animation-time;
&:hover {
transform: translateY(-2px);
}
}

View file

@ -0,0 +1,14 @@
$accent-color: #a2666f;
$text-color: #5d576b;
$light-color: #ffffff;
$background-gradient: linear-gradient(90deg, #fff9e077 0, #ffd6d677 100%);
$shadow: 0 0 1.5px 1.5px rgba(0, 0, 0, 0.1), 0 0 3px 2px rgba(0, 0, 0, 0.05);
$shadow-border: 0 0 0 0.75px rgba(0, 0, 0, 0.1);
$normal-font: 'Open Sans Condensed', sans-serif;
$title-font: 'Raleway', serif;
$mobile-width: 520px;
$min-height: 400px;

54
src/library/forms.scss Normal file
View file

@ -0,0 +1,54 @@
@import 'text';
@import 'animations';
textarea {
@include normal-text();
display: block;
width: 100%;
height: 150px;
@media (max-width: $mobile-width) {
height: 100px;
}
resize: none;
box-sizing: border-box;
border: none;
}
input[type='text'] {
@include sub-title-text();
width: 100%;
background: transparent;
display: block;
border: 0;
&::placeholder {
color: inherit;
opacity: 0.6;
}
&:focus {
box-shadow: 0 1px $text-color;
}
}
button {
-webkit-appearance: none;
margin: 8px auto 0 auto;
user-select: none;
background: transparent;
border: 0;
text-decoration: underline;
@include medium-text();
@include jump();
}
label {
display: none;
}

62
src/library/main.scss Normal file
View file

@ -0,0 +1,62 @@
@import 'common-variables';
@import 'animations';
@import 'text';
@import 'spacing';
@import 'forms';
@import 'utils';
:root {
--border-radius: 5px;
--large-padding: 30px;
--medium-padding: 15px;
--small-padding: 10px;
@media (max-width: $mobile-width) {
--border-radius: 3px;
--large-padding: 20px;
--medium-padding: 15px;
--small-padding: 7.5px;
}
}
@mixin card {
border-radius: var(--border-radius);
background-color: $light-color;
}
@mixin center-child {
display: flex;
justify-content: center;
align-items: center;
}
@mixin exit {
@include square(16px);
background: url('/assets/x-sign.svg') no-repeat center center;
background-size: 50% 50%;
box-sizing: content-box;
padding: 8px;
@include jump();
}
img {
user-select: none;
}
*::-webkit-scrollbar {
width: 4px;
}
*::-webkit-scrollbar-track {
box-shadow: $shadow-border;
border-radius: var(--border-radius);
}
*::-webkit-scrollbar-thumb {
background-color: $text-color;
border-radius: var(--border-radius);
cursor: pointer;
}

9
src/library/spacing.scss Normal file
View file

@ -0,0 +1,9 @@
@mixin inner-spacing($spacing, $horizontal: false) {
& > *:not(:last-child) {
@if $horizontal {
margin-right: $spacing;
} @else {
margin-bottom: $spacing;
}
}
}

57
src/library/text.scss Normal file
View file

@ -0,0 +1,57 @@
@import 'common-variables';
:root {
--larger-font-size: 22px;
--large-font-size: 18px;
--medium-font-size: 16px;
--small-font-size: 11px;
@media (max-width: $mobile-width) {
--larger-font-size: 20px;
--large-font-size: 16px;
--medium-font-size: 14px;
--small-font-size: 10px;
}
}
@mixin title-text {
font-family: $title-font;
color: $text-color;
font-size: var(--larger-font-size);
user-select: none;
}
@mixin sub-title-text {
font-family: $title-font;
color: $text-color;
font-size: var(--medium-font-size);
user-select: none;
}
@mixin normal-text {
font-family: $normal-font;
color: $text-color;
font-size: var(--larger-font-size);
}
@mixin medium-text {
font-family: $normal-font;
color: $text-color;
font-size: var(--medium-font-size);
}
@mixin small-text {
font-family: $normal-font;
color: $text-color;
font-size: var(--small-font-size);
}
h1,
h2,
h3 {
@include title-text();
}
p {
@include normal-text();
}

4
src/library/utils.scss Normal file
View file

@ -0,0 +1,4 @@
@mixin square($size) {
width: $size;
height: $size;
}

View file

@ -8,5 +8,6 @@ if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch(err => console.error(err));

View file

@ -1 +1,35 @@
/* You can add global styles to this file, and also import other style files */
@import 'library/main';
$line-height: 2px;
* {
margin: 0;
padding: 0;
&:active,
&:focus {
outline: 0;
}
&::selection {
background: $text-color;
color: $light-color;
}
&::placeholder {
user-select: none;
}
}
html,
body {
height: 100%;
background: $background-gradient;
}
body {
text-align: center;
padding: var(--large-padding);
box-sizing: border-box;
position: relative;
}

View file

@ -2,10 +2,8 @@
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"types": []
"types": [],
"downlevelIteration": true
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
"exclude": ["test.ts", "**/*.spec.ts"]
}

View file

@ -1,66 +1,49 @@
{
"extends": "tslint:recommended",
"rulesDirectory": [
"codelyzer"
],
"rulesDirectory": ["node_modules/codelyzer"],
"rules": {
"array-type": false,
"arrow-parens": false,
"deprecation": {
"severity": "warn"
},
"import-blacklist": [
true,
"rxjs/Rx"
],
"interface-name": false,
"max-classes-per-file": false,
"max-line-length": [
true,
140
],
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"forin": true,
"import-blacklist": [true, "rxjs", "rxjs/Rx"],
"interface-over-type-literal": true,
"label-position": true,
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
"order": ["static-field", "instance-field", "static-method", "instance-method"]
}
],
"no-consecutive-blank-lines": false,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-arg": true,
"no-bitwise": true,
"no-console": [true, "debug", "info", "time", "timeEnd", "trace"],
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [true, "ignore-params"],
"no-misused-new": true,
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-requires": false,
"object-literal-key-quotes": [
true,
"as-needed"
],
"no-var-keyword": true,
"object-literal-sort-keys": false,
"ordered-imports": false,
"quotemark": [
true,
"single"
],
"trailing-comma": false,
"prefer-const": true,
"radix": true,
"triple-equals": [true, "allow-null-check"],
"typeof-compare": true,
"unified-signatures": true,
"variable-name": false,
"directive-selector": [true, "attribute", "app", "camelCase"],
"component-selector": [true, "element", "app", "kebab-case"],
"no-output-on-prefix": true,
"use-input-property-decorator": true,
"use-output-property-decorator": true,

6461
yarn.lock Normal file

File diff suppressed because it is too large Load diff