Fix UX
This commit is contained in:
parent
674f07f5f1
commit
19aad2b2af
23 changed files with 231 additions and 10012 deletions
9896
package-lock.json
generated
9896
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -26,6 +26,7 @@
|
||||||
"core-js": "^2.5.4",
|
"core-js": "^2.5.4",
|
||||||
"rxjs": "~6.3.3",
|
"rxjs": "~6.3.3",
|
||||||
"tslib": "^1.10.0",
|
"tslib": "^1.10.0",
|
||||||
|
"uuid": "^3.3.3",
|
||||||
"zone.js": "~0.8.26"
|
"zone.js": "~0.8.26"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import { ToggleComponent } from './components/shared/toggle/toggle.component';
|
||||||
import { TasksComponent } from './components/pages/page/tower/tasks/tasks.component';
|
import { TasksComponent } from './components/pages/page/tower/tasks/tasks.component';
|
||||||
import { ColorPipe } from './pipes/color.pipe';
|
import { ColorPipe } from './pipes/color.pipe';
|
||||||
import { BlocksComponent } from './components/modal/modals/blocks/blocks.component';
|
import { BlocksComponent } from './components/modal/modals/blocks/blocks.component';
|
||||||
|
import { FormatDatePipe } from './pipes/format-date.pipe';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
|
@ -39,7 +40,8 @@ import { BlocksComponent } from './components/modal/modals/blocks/blocks.compone
|
||||||
ToggleComponent,
|
ToggleComponent,
|
||||||
TasksComponent,
|
TasksComponent,
|
||||||
ColorPipe,
|
ColorPipe,
|
||||||
BlocksComponent
|
BlocksComponent,
|
||||||
|
FormatDatePipe
|
||||||
],
|
],
|
||||||
imports: [BrowserModule, AppRoutingModule, FormsModule, BrowserAnimationsModule, DragDropModule],
|
imports: [BrowserModule, AppRoutingModule, FormsModule, BrowserAnimationsModule, DragDropModule],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
class="{{ modalService.active ? 'active' : '' }}"
|
class="{{ modalService.active ? 'active' : '' }}"
|
||||||
[ngSwitch]="modalService.active?.type"
|
[ngSwitch]="modalService.active?.type"
|
||||||
>
|
>
|
||||||
<app-blocks (click)="$event.stopPropagation()" *ngSwitchCase="ModalType.blocks"></app-blocks>
|
<app-blocks (save)="save = $event" (click)="$event.stopPropagation()" *ngSwitchCase="ModalType.blocks"></app-blocks>
|
||||||
<app-remove-tower (click)="$event.stopPropagation()" *ngSwitchCase="ModalType.removeTower"></app-remove-tower>
|
<app-remove-tower (click)="$event.stopPropagation()" *ngSwitchCase="ModalType.removeTower"></app-remove-tower>
|
||||||
<app-settings (click)="$event.stopPropagation()" *ngSwitchCase="ModalType.settings"></app-settings>
|
<app-settings (click)="$event.stopPropagation()" *ngSwitchCase="ModalType.settings"></app-settings>
|
||||||
<app-get-started (click)="$event.stopPropagation()" *ngSwitchCase="ModalType.getStarted"></app-get-started>
|
<app-get-started (click)="$event.stopPropagation()" *ngSwitchCase="ModalType.getStarted"></app-get-started>
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,16 @@ import { CancelService } from '../../services/cancel.service';
|
||||||
export class ModalComponent {
|
export class ModalComponent {
|
||||||
ModalType = ModalType;
|
ModalType = ModalType;
|
||||||
|
|
||||||
|
save: () => void = null;
|
||||||
|
|
||||||
constructor(public modalService: ModalService, private cancelService: CancelService) {
|
constructor(public modalService: ModalService, private cancelService: CancelService) {
|
||||||
this.cancelService.subscribe(this, () => this.modalService.cancel());
|
this.cancelService.subscribe(this, () => {
|
||||||
|
if (this.save) {
|
||||||
|
this.save();
|
||||||
|
this.save = null;
|
||||||
|
} else {
|
||||||
|
this.modalService.cancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
<section #container>
|
<section #container *ngIf="tower">
|
||||||
<div class="card placeholder"></div>
|
<div class="card placeholder"></div>
|
||||||
<div
|
<div
|
||||||
*ngFor="let i of range({ max: blocks.length })"
|
*ngFor="let i of range({ max: blocks.length })"
|
||||||
|
|
@ -12,13 +12,12 @@
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="exit" (click)="modalService.cancel()"></div>
|
<div class="exit" (click)="modalService.cancel()"></div>
|
||||||
<div class="block" [ngStyle]="{ 'background-color': tower.getColorOfTag(editedValues[i].tag) | color }"></div>
|
<div class="block" [ngStyle]="{ 'background-color': tower.getColorOfTag(editedValues[i].tag) | color }"></div>
|
||||||
<h1>View item</h1>
|
<h1 [innerText]="editedValues[i]?.created | formatDate"></h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="select-add-container">
|
<div class="select-add-container">
|
||||||
<app-select-add
|
<app-select-add
|
||||||
class="select"
|
class="select"
|
||||||
[disabled]="!editMode"
|
|
||||||
[options]="tower.tags"
|
[options]="tower.tags"
|
||||||
[default]="editedValues[i].tag"
|
[default]="editedValues[i].tag"
|
||||||
[alwaysDropShadow]="true"
|
[alwaysDropShadow]="true"
|
||||||
|
|
@ -28,30 +27,16 @@
|
||||||
></app-select-add>
|
></app-select-add>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<textarea
|
<textarea placeholder="Write a description here…" [(ngModel)]="editedValues[i].description"></textarea>
|
||||||
[disabled]="!editMode"
|
|
||||||
placeholder="{{ editMode ? 'Write a description here…' : '' }}"
|
|
||||||
[(ngModel)]="editedValues[i].description"
|
|
||||||
></textarea>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<app-toggle
|
<app-toggle
|
||||||
[beforeText]="'This task hasn\'t been finished yet'"
|
[beforeText]="'This task hasn\'t been finished yet'"
|
||||||
[afterText]="'Goal already accomplished'"
|
[afterText]="'Goal already accomplished'"
|
||||||
[default]="blocks[i].isDone"
|
[default]="blocks[i].isDone"
|
||||||
[disabled]="!editMode"
|
|
||||||
(value)="editedValues[i].isDone = $event"
|
(value)="editedValues[i].isDone = $event"
|
||||||
></app-toggle>
|
></app-toggle>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="bottom">
|
|
||||||
<button class="{{ editMode && i + 1 === activeChild ? '' : 'hidden' }}" (click)="submitChange()">
|
|
||||||
Save and exit
|
|
||||||
</button>
|
|
||||||
<div class="edit {{ editMode && i + 1 === activeChild ? 'active' : '' }}" (click)="editMode = !editMode">
|
|
||||||
<img src="assets/pen.svg" alt="edit" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
@ -65,7 +50,7 @@
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<div class="exit" (click)="modalService.cancel()"></div>
|
<div class="exit" (click)="modalService.cancel()"></div>
|
||||||
<div class="block" [ngStyle]="{ 'background-color': tower.getColorOfTag(top(editedValues).tag) | color }"></div>
|
<div class="block" [ngStyle]="{ 'background-color': tower.getColorOfTag(top(editedValues).tag) | color }"></div>
|
||||||
<h1>Create an item</h1>
|
<h1>Create now</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="select-add-container">
|
<div class="select-add-container">
|
||||||
|
|
@ -75,7 +60,8 @@
|
||||||
[default]="tower.tags.length ? tower.tags[0] : null"
|
[default]="tower.tags.length ? tower.tags[0] : null"
|
||||||
[alwaysDropShadow]="true"
|
[alwaysDropShadow]="true"
|
||||||
[onlyShadowBorder]="true"
|
[onlyShadowBorder]="true"
|
||||||
[placeholder]="'Tag this item…'"
|
[placeholder]="'Set a category…'"
|
||||||
|
[newValuePlaceholder]="'Add a category…'"
|
||||||
(value)="top(editedValues).tag = $event"
|
(value)="top(editedValues).tag = $event"
|
||||||
></app-select-add>
|
></app-select-add>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,8 @@
|
||||||
|
|
||||||
&.placeholder {
|
&.placeholder {
|
||||||
opacity: 0 !important;
|
opacity: 0 !important;
|
||||||
width: 1000px;
|
width: 60vw;
|
||||||
max-width: 1000px;
|
max-width: 60vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include inner-spacing(var(--large-padding));
|
@include inner-spacing(var(--large-padding));
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,14 @@
|
||||||
import { ChangeDetectorRef, Component, ElementRef, HostListener, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
import {
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
EventEmitter,
|
||||||
|
HostListener,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
Output,
|
||||||
|
ViewChild
|
||||||
|
} from '@angular/core';
|
||||||
import { ModalService } from '../../../../services/modal.service';
|
import { ModalService } from '../../../../services/modal.service';
|
||||||
import { Tower } from '../../../../model/tower';
|
import { Tower } from '../../../../model/tower';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
|
|
@ -16,20 +26,30 @@ import { top } from 'src/app/utils/top';
|
||||||
export class BlocksComponent implements OnInit, OnDestroy {
|
export class BlocksComponent implements OnInit, OnDestroy {
|
||||||
readonly range = range;
|
readonly range = range;
|
||||||
readonly top = top;
|
readonly top = top;
|
||||||
|
|
||||||
tower: Tower;
|
tower: Tower;
|
||||||
|
|
||||||
editedValues: Array<Partial<IBlock>>;
|
editedValues: Array<Partial<IBlock>>;
|
||||||
|
|
||||||
endOfScrollToken = 0;
|
endOfScrollToken = 0;
|
||||||
editMode = false;
|
|
||||||
activeChild: number;
|
activeChild: number;
|
||||||
scrollMayEnd = true;
|
scrollMayEnd = true;
|
||||||
|
|
||||||
onlyDone: boolean;
|
onlyDone: boolean;
|
||||||
|
|
||||||
@ViewChild('container') container: ElementRef;
|
@ViewChild('container') container: ElementRef;
|
||||||
private subscription;
|
|
||||||
|
private intervalID: number;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public modalService: ModalService,
|
||||||
|
private cancelService: CancelService,
|
||||||
|
private changeDetector: ChangeDetectorRef,
|
||||||
|
private component: ElementRef
|
||||||
|
) {
|
||||||
|
window.addEventListener('resize', this.onScroll.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Output() save: EventEmitter<() => void> = new EventEmitter();
|
||||||
|
|
||||||
|
get blocks(): Array<Block> {
|
||||||
|
return this.tower.blocks.filter(b => b.isDone === this.onlyDone);
|
||||||
|
}
|
||||||
|
|
||||||
@HostListener('click') cancel() {
|
@HostListener('click') cancel() {
|
||||||
this.cancelService.cancelAll();
|
this.cancelService.cancelAll();
|
||||||
|
|
@ -45,28 +65,13 @@ export class BlocksComponent implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('scroll') onScroll() {
|
@HostListener('scroll') onScroll() {
|
||||||
console.log('scrolling');
|
|
||||||
|
|
||||||
this.animateScroll();
|
|
||||||
const newToken = ++this.endOfScrollToken;
|
const newToken = ++this.endOfScrollToken;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (newToken === this.endOfScrollToken && this.scrollMayEnd) {
|
if (newToken === this.endOfScrollToken && this.scrollMayEnd) {
|
||||||
this.adjustPosition();
|
this.adjustPosition();
|
||||||
}
|
}
|
||||||
}, 120);
|
}, 150);
|
||||||
}
|
this.animateScroll();
|
||||||
|
|
||||||
constructor(
|
|
||||||
public modalService: ModalService,
|
|
||||||
private cancelService: CancelService,
|
|
||||||
private changeDetector: ChangeDetectorRef,
|
|
||||||
private component: ElementRef
|
|
||||||
) {
|
|
||||||
window.addEventListener('resize', this.onScroll.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
get blocks(): Array<Block> {
|
|
||||||
return this.tower.blocks.filter(b => b.isDone === this.onlyDone);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
@ -76,18 +81,31 @@ export class BlocksComponent implements OnInit, OnDestroy {
|
||||||
startBlock
|
startBlock
|
||||||
}: { tower$: Observable<Tower>; onlyDone: boolean; startBlock: Block } = this.modalService.active.input;
|
}: { tower$: Observable<Tower>; onlyDone: boolean; startBlock: Block } = this.modalService.active.input;
|
||||||
|
|
||||||
|
this.save.emit(() => this.submitChange());
|
||||||
|
|
||||||
|
this.intervalID = setInterval(() => this.changeDetector.detectChanges(), 1000);
|
||||||
|
|
||||||
this.onlyDone = onlyDone;
|
this.onlyDone = onlyDone;
|
||||||
this.subscription = tower$.subscribe(value => {
|
const subscription = tower$.subscribe(value => {
|
||||||
|
if (value) {
|
||||||
this.tower = value;
|
this.tower = value;
|
||||||
this.editedValues = this.blocks.map(({ isDone, description, tag }) => ({ isDone, description, tag }));
|
this.editedValues = this.blocks.map(({ isDone, description, tag, created }) => ({
|
||||||
|
isDone,
|
||||||
|
description,
|
||||||
|
tag,
|
||||||
|
created
|
||||||
|
}));
|
||||||
this.editedValues.push({
|
this.editedValues.push({
|
||||||
|
tag: this.tower.tags.length ? this.tower.tags[0] : null,
|
||||||
isDone: this.onlyDone,
|
isDone: this.onlyDone,
|
||||||
description: ''
|
description: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() =>
|
setTimeout(() => {
|
||||||
this.scrollToChild(startBlock ? this.blocks.indexOf(startBlock) + 1 : this.blocks.length + 1, true)
|
this.scrollToChild(startBlock ? this.blocks.indexOf(startBlock) + 1 : this.blocks.length + 1, true);
|
||||||
);
|
subscription.unsubscribe();
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,7 +131,6 @@ export class BlocksComponent implements OnInit, OnDestroy {
|
||||||
animate(cardStyle, maskStyle, t: number) {
|
animate(cardStyle, maskStyle, t: number) {
|
||||||
t = Math.min(2, Math.max(0, t));
|
t = Math.min(2, Math.max(0, t));
|
||||||
cardStyle.opacity = (1.33 * (1 - t / 2)).toString();
|
cardStyle.opacity = (1.33 * (1 - t / 2)).toString();
|
||||||
console.log(1 - t / 2);
|
|
||||||
t = Math.min(1, Math.max(0, t));
|
t = Math.min(1, Math.max(0, t));
|
||||||
maskStyle.opacity = Math.pow(t, 0.5).toString();
|
maskStyle.opacity = Math.pow(t, 0.5).toString();
|
||||||
maskStyle.display = t <= 0.05 ? 'none' : 'block';
|
maskStyle.display = t <= 0.05 ? 'none' : 'block';
|
||||||
|
|
@ -124,30 +141,25 @@ export class BlocksComponent implements OnInit, OnDestroy {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('adjusting position');
|
|
||||||
|
|
||||||
const c = this.component.nativeElement;
|
const c = this.component.nativeElement;
|
||||||
|
|
||||||
const middle =
|
const middle =
|
||||||
[...this.container.nativeElement.children]
|
[...this.container.nativeElement.children]
|
||||||
.slice(1, -1)
|
.slice(1, -1)
|
||||||
.map(element => Math.abs(element.offsetLeft - c.scrollLeft + element.clientWidth / 2 - window.innerWidth / 2))
|
.map(element => Math.abs(element.offsetLeft - c.scrollLeft + element.clientWidth / 2 - window.innerWidth / 2))
|
||||||
.map((value, index) =>
|
.map((value, index) => (Math.abs(index + 1 - this.activeChild) === 1 ? Math.abs(value - 100) : value))
|
||||||
Math.abs(index + 1 - this.activeChild) === 1 ? Math.abs(value - window.innerWidth / 4) : value
|
|
||||||
)
|
|
||||||
.reduce(
|
.reduce(
|
||||||
(middleIndex, current, currentIndex, list) => (list[middleIndex] < current ? middleIndex : currentIndex),
|
(middleIndex, current, currentIndex, list) => (list[middleIndex] < current ? middleIndex : currentIndex),
|
||||||
0
|
0
|
||||||
) + 1;
|
) + 1;
|
||||||
|
|
||||||
this.scrollToChild(middle);
|
this.scrollToChild(middle);
|
||||||
this.changeDetector.markForCheck();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToChild(index: number, instantly?: boolean) {
|
scrollToChild(index: number, instantly?: boolean) {
|
||||||
this.activeChild = index;
|
this.activeChild = index;
|
||||||
console.log('scrolling to', index);
|
|
||||||
const element = this.container.nativeElement.children[index];
|
const element = this.container.nativeElement.children[index];
|
||||||
|
|
||||||
this.component.nativeElement.scrollTo({
|
this.component.nativeElement.scrollTo({
|
||||||
left: element.offsetLeft - (window.innerWidth / 2 - element.clientWidth / 2),
|
left: element.offsetLeft - (window.innerWidth / 2 - element.clientWidth / 2),
|
||||||
behavior: instantly ? 'auto' : 'smooth'
|
behavior: instantly ? 'auto' : 'smooth'
|
||||||
|
|
@ -157,17 +169,15 @@ export class BlocksComponent implements OnInit, OnDestroy {
|
||||||
submitAdd() {
|
submitAdd() {
|
||||||
top(this.editedValues).created = new Date();
|
top(this.editedValues).created = new Date();
|
||||||
this.tower.addBlock(top(this.editedValues) as IBlock);
|
this.tower.addBlock(top(this.editedValues) as IBlock);
|
||||||
this.modalService.submit();
|
this.cancelService.cancelAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
submitChange() {
|
submitChange() {
|
||||||
this.blocks[this.activeChild - 1].changeKeys(this.editedValues[this.activeChild - 1]);
|
this.blocks.forEach((b, i) => b.changeKeys(this.editedValues[i]));
|
||||||
this.modalService.submit();
|
this.modalService.submit();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
if (this.subscription) {
|
clearInterval(this.intervalID);
|
||||||
this.subscription.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,7 @@ export class PageComponent implements OnInit {
|
||||||
if (value) {
|
if (value) {
|
||||||
this.towers = value.towers.map((t, index) => {
|
this.towers = value.towers.map((t, index) => {
|
||||||
if (index < this.towers.length) {
|
if (index < this.towers.length) {
|
||||||
if (this.towers[index].getValue() !== t) {
|
|
||||||
this.towers[index].next(t);
|
this.towers[index].next(t);
|
||||||
}
|
|
||||||
return this.towers[index];
|
return this.towers[index];
|
||||||
}
|
}
|
||||||
return new BehaviorSubject(t);
|
return new BehaviorSubject(t);
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ export class BlockComponent {
|
||||||
@Input() block: ColoredBlock;
|
@Input() block: ColoredBlock;
|
||||||
@Input() tower$: Observable<Tower>;
|
@Input() tower$: Observable<Tower>;
|
||||||
|
|
||||||
constructor(private modalService: ModalService, private changeDetection: ChangeDetectorRef) {}
|
constructor(private modalService: ModalService) {}
|
||||||
|
|
||||||
async handleClick() {
|
async handleClick() {
|
||||||
try {
|
try {
|
||||||
|
|
@ -23,8 +23,6 @@ export class BlockComponent {
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
// pass
|
// pass
|
||||||
} finally {
|
|
||||||
this.changeDetection.markForCheck();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { ModalService } from '../../../../services/modal.service';
|
||||||
import { Observable } from 'rxjs/internal/Observable';
|
import { Observable } from 'rxjs/internal/Observable';
|
||||||
import { Range } from '../../../../interfaces/range';
|
import { Range } from '../../../../interfaces/range';
|
||||||
import { top } from '../../../../utils/top';
|
import { top } from '../../../../utils/top';
|
||||||
|
import { CancelService } from '../../../../services/cancel.service';
|
||||||
|
|
||||||
type StyledBlock = ColoredBlock & { style: { [p: string]: string }; shouldDraw: boolean; cssClass: string };
|
type StyledBlock = ColoredBlock & { style: { [p: string]: string }; shouldDraw: boolean; cssClass: string };
|
||||||
|
|
||||||
|
|
@ -35,10 +36,13 @@ export class TowerComponent implements OnInit {
|
||||||
return this.styledBlocks.filter(b => b.shouldDraw);
|
return this.styledBlocks.filter(b => b.shouldDraw);
|
||||||
}
|
}
|
||||||
|
|
||||||
public constructor(private modalService: ModalService, private changeDetection: ChangeDetectorRef) {}
|
public constructor(private modalService: ModalService, private changeDetection: ChangeDetectorRef) {
|
||||||
|
console.log('oo');
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.tower$.subscribe(value => {
|
this.tower$.subscribe(value => {
|
||||||
|
console.log(this.tower, value);
|
||||||
if (value) {
|
if (value) {
|
||||||
this.styledBlocks = value.coloredBlocks
|
this.styledBlocks = value.coloredBlocks
|
||||||
.filter(b => b.isDone)
|
.filter(b => b.isDone)
|
||||||
|
|
@ -65,13 +69,17 @@ export class TowerComponent implements OnInit {
|
||||||
const lastBlock = top(this.styledBlocks);
|
const lastBlock = top(this.styledBlocks);
|
||||||
if (lastBlock) {
|
if (lastBlock) {
|
||||||
lastBlock.style = { transform: 'translateY(500%)', opacity: '0' };
|
lastBlock.style = { transform: 'translateY(500%)', opacity: '0' };
|
||||||
setTimeout(() => this.makeBlockDescend(lastBlock), 0);
|
setTimeout(() => {
|
||||||
|
this.makeBlockDescend(lastBlock);
|
||||||
|
this.changeDetection.markForCheck();
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.tasks = value.coloredBlocks.filter(block => !block.isDone);
|
this.tasks = value.coloredBlocks.filter(block => !block.isDone);
|
||||||
this.tower = value;
|
this.tower = value;
|
||||||
|
this.changeDetection.markForCheck();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -112,8 +120,6 @@ export class TowerComponent implements OnInit {
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
// pass
|
// pass
|
||||||
} finally {
|
|
||||||
this.changeDetection.markForCheck();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
<div
|
<div class="select-add {{ onlyShadowBorder ? 'shadow-border' : '' }}" (click)="$event.stopPropagation()">
|
||||||
class="select-add {{ onlyShadowBorder ? 'shadow-border' : '' }} {{ disabled ? 'disabled' : '' }}"
|
<div #top class="top" (click)="!editMode && toggle()">
|
||||||
(click)="$event.stopPropagation()"
|
|
||||||
>
|
|
||||||
<div #top class="top" (click)="!editMode && !disabled && toggle()">
|
|
||||||
<p [innerHTML]="selected ? selected : placeholder" *ngIf="!editMode || !selected; else editableSelected"></p>
|
<p [innerHTML]="selected ? selected : placeholder" *ngIf="!editMode || !selected; else editableSelected"></p>
|
||||||
<ng-template #editableSelected>
|
<ng-template #editableSelected>
|
||||||
<input type="text" [value]="selected" (change)="changeOption(selected, $event)" />
|
<input type="text" [value]="selected" (change)="changeOption(selected, $event)" />
|
||||||
|
|
@ -27,7 +24,7 @@
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
*ngIf="options.length <= maxItemCount"
|
*ngIf="options.length <= maxItemCount"
|
||||||
placeholder="Add a value…"
|
[placeholder]="newValuePlaceholder"
|
||||||
[(ngModel)]="newOption"
|
[(ngModel)]="newOption"
|
||||||
(keyup)="handleKeys($event)"
|
(keyup)="handleKeys($event)"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,6 @@ $inner-padding: var(--medium-padding);
|
||||||
width: 100%;
|
width: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&.disabled {
|
|
||||||
.top {
|
|
||||||
cursor: not-allowed !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.top,
|
.top,
|
||||||
.bottom {
|
.bottom {
|
||||||
padding: $inner-padding;
|
padding: $inner-padding;
|
||||||
|
|
|
||||||
|
|
@ -8,18 +8,15 @@ import { CancelService } from '../../../services/cancel.service';
|
||||||
})
|
})
|
||||||
export class SelectAddComponent {
|
export class SelectAddComponent {
|
||||||
@Input() placeholder = 'Add a new value…';
|
@Input() placeholder = 'Add a new value…';
|
||||||
|
@Input() newValuePlaceholder = 'Add a value…';
|
||||||
@Input() maxItemCount = 7;
|
@Input() maxItemCount = 7;
|
||||||
@Input() options: string[];
|
@Input() options: string[];
|
||||||
@Input() alwaysDropShadow = false;
|
@Input() alwaysDropShadow = false;
|
||||||
@Input() onlyShadowBorder = false;
|
@Input() onlyShadowBorder = false;
|
||||||
@Input() editable = false;
|
@Input() editable = false;
|
||||||
@Input() disabled = false;
|
|
||||||
|
|
||||||
@Input() set default(value: string) {
|
@Input() set default(value: string) {
|
||||||
this.selected = value;
|
this.selected = value;
|
||||||
if (value) {
|
|
||||||
this.value.emit(value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
backgroundHeight: string;
|
backgroundHeight: string;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<span [className]="!on ? 'active' : ''" (click)="on = false" [innerText]="beforeText"></span>
|
<span [className]="!on ? 'active' : ''" (click)="on = false" [innerText]="beforeText"></span>
|
||||||
|
|
||||||
<label class="{{ disabled ? 'disabled' : '' }}">
|
<label>
|
||||||
<input type="checkbox" [disabled]="disabled" [(ngModel)]="on" [className]="on ? 'on' : ''" />
|
<input type="checkbox" [(ngModel)]="on" [className]="on ? 'on' : ''" />
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<span [className]="on ? 'active' : ''" (click)="on = true" [innerText]="afterText"></span>
|
<span [className]="on ? 'active' : ''" (click)="on = true" [innerText]="afterText"></span>
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.disabled) {
|
|
||||||
input[type='checkbox'] {
|
input[type='checkbox'] {
|
||||||
@media (min-width: $mobile-width) {
|
@media (min-width: $mobile-width) {
|
||||||
&:hover:after {
|
&:hover:after {
|
||||||
|
|
@ -74,4 +73,3 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,6 @@ export class ToggleComponent {
|
||||||
@Input() beforeText: string;
|
@Input() beforeText: string;
|
||||||
@Input() afterText: string;
|
@Input() afterText: string;
|
||||||
|
|
||||||
@Input() disabled = false;
|
|
||||||
|
|
||||||
@Output() value: EventEmitter<boolean> = new EventEmitter();
|
@Output() value: EventEmitter<boolean> = new EventEmitter();
|
||||||
|
|
||||||
@Input() set default(value: boolean) {
|
@Input() set default(value: boolean) {
|
||||||
|
|
|
||||||
49
src/app/pipes/format-date.pipe.ts
Normal file
49
src/app/pipes/format-date.pipe.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'formatDate',
|
||||||
|
pure: false
|
||||||
|
})
|
||||||
|
export class FormatDatePipe implements PipeTransform {
|
||||||
|
transform(value: Date): string {
|
||||||
|
const now = new Date();
|
||||||
|
|
||||||
|
const years = Math.floor(now.getFullYear() - value.getFullYear());
|
||||||
|
const months = Math.floor(now.getMonth() - value.getMonth());
|
||||||
|
const days = Math.floor(now.getDay() - value.getDay());
|
||||||
|
const minutes = Math.floor(now.getMinutes() - value.getMinutes());
|
||||||
|
const seconds = Math.floor(now.getSeconds() - value.getSeconds());
|
||||||
|
|
||||||
|
if (years === 1) {
|
||||||
|
return 'a year ago';
|
||||||
|
} else if (years > 1) {
|
||||||
|
return `${years} years ago`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (months === 1) {
|
||||||
|
return 'a month ago';
|
||||||
|
} else if (months > 1) {
|
||||||
|
return `${months} months ago`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (days === 1) {
|
||||||
|
return 'a day ago';
|
||||||
|
} else if (days > 1) {
|
||||||
|
return `${days} days ago`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minutes === 1) {
|
||||||
|
return 'a minute ago';
|
||||||
|
} else if (minutes > 1) {
|
||||||
|
return `${minutes} minutes ago`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds === 1) {
|
||||||
|
return 'just now';
|
||||||
|
} else if (seconds > 1) {
|
||||||
|
return `${seconds} seconds ago`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'just now';
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/app/services/map-store.service.ts
Normal file
35
src/app/services/map-store.service.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
const LOCAL_STORAGE_KEY = 'life-towers.data.v.3';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class MapStoreService<T> {
|
||||||
|
readonly storage: {
|
||||||
|
[id: number]: T;
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const localStorageData = localStorage.getItem(LOCAL_STORAGE_KEY);
|
||||||
|
if (localStorageData) {
|
||||||
|
this.storage = JSON.parse(localStorageData) as {
|
||||||
|
[id: number]: T;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get(id: number) {
|
||||||
|
if (this.storage.hasOwnProperty(id)) {
|
||||||
|
return this.storage[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
add(id: number, value: T) {
|
||||||
|
if (this.storage.hasOwnProperty(id)) {
|
||||||
|
throw new Error('Key already set.');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.storage[id] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ export abstract class Node extends Unique implements NodeState {
|
||||||
|
|
||||||
protected constructor(children: Array<InnerNode> = []) {
|
protected constructor(children: Array<InnerNode> = []) {
|
||||||
super();
|
super();
|
||||||
children.map(c => (c.parent = this));
|
children.forEach(c => (c.parent = this));
|
||||||
}
|
}
|
||||||
protected abstract changeKeys<T extends NodeState>(props: Partial<T>): this;
|
protected abstract changeKeys<T extends NodeState>(props: Partial<T>): this;
|
||||||
|
|
||||||
|
|
|
||||||
29
src/app/store/store.ts
Normal file
29
src/app/store/store.ts
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
class Store {
|
||||||
|
private readonly onSet: (id: string, element: any) => void;
|
||||||
|
private readonly elements: {
|
||||||
|
[id: string]: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
elements: {
|
||||||
|
[id: string]: any;
|
||||||
|
},
|
||||||
|
onSet: (id: string, element: any) => void
|
||||||
|
) {
|
||||||
|
this.elements = elements;
|
||||||
|
this.onSet = onSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
add(element: any): number {
|
||||||
|
const id = uuidv4();
|
||||||
|
this.elements[id] = element;
|
||||||
|
this.onSet(id, element);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(id: string): any {
|
||||||
|
return this.elements[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
export class Unique {
|
export class Unique {
|
||||||
protected static nextId = 0;
|
private static nextId = 0;
|
||||||
|
private static store;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.setUniqueness();
|
this.setUniqueness();
|
||||||
|
|
@ -10,11 +11,13 @@ export class Unique {
|
||||||
}
|
}
|
||||||
|
|
||||||
private _id: number;
|
private _id: number;
|
||||||
|
|
||||||
get id(): number {
|
get id(): number {
|
||||||
return this._id;
|
return this._id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _copies = 0;
|
private _copies = 0;
|
||||||
|
|
||||||
get copies(): number {
|
get copies(): number {
|
||||||
return this._copies;
|
return this._copies;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6433,6 +6433,11 @@ uuid@^3.0.1, uuid@^3.3.2:
|
||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||||
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
|
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
|
||||||
|
|
||||||
|
uuid@^3.3.3:
|
||||||
|
version "3.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866"
|
||||||
|
integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==
|
||||||
|
|
||||||
validate-npm-package-license@^3.0.1:
|
validate-npm-package-license@^3.0.1:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
|
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue