Make app work again
This commit is contained in:
parent
a9ad628488
commit
938f3def1f
30 changed files with 236 additions and 155 deletions
|
|
@ -22,6 +22,7 @@ import { CreateBlockComponent } from './components/modal/modals/create-block/cre
|
||||||
import { RemoveBlockComponent } from './components/modal/modals/remove-block/remove-block.component';
|
import { RemoveBlockComponent } from './components/modal/modals/remove-block/remove-block.component';
|
||||||
import { ToggleComponent } from './components/shared/toggle/toggle.component';
|
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';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
|
@ -41,7 +42,8 @@ import { TasksComponent } from './components/pages/page/tower/tasks/tasks.compon
|
||||||
CreateBlockComponent,
|
CreateBlockComponent,
|
||||||
RemoveBlockComponent,
|
RemoveBlockComponent,
|
||||||
ToggleComponent,
|
ToggleComponent,
|
||||||
TasksComponent
|
TasksComponent,
|
||||||
|
ColorPipe
|
||||||
],
|
],
|
||||||
imports: [BrowserModule, AppRoutingModule, FormsModule, BrowserAnimationsModule, DragDropModule],
|
imports: [BrowserModule, AppRoutingModule, FormsModule, BrowserAnimationsModule, DragDropModule],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
<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]="!modalService.active.input.isTask"
|
[default]="isDone"
|
||||||
(value)="isDone = $event"
|
(value)="isDone = $event"
|
||||||
></app-toggle>
|
></app-toggle>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import { ModalService } from '../../../../services/modal.service';
|
||||||
export class CreateBlockComponent {
|
export class CreateBlockComponent {
|
||||||
selected: string;
|
selected: string;
|
||||||
description: string = null;
|
description: string = null;
|
||||||
isDone: boolean;
|
isDone: boolean = !this.modalService.active.input.isTask;
|
||||||
|
|
||||||
constructor(public modalService: ModalService) {}
|
constructor(public modalService: ModalService) {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<app-select-add
|
<app-select-add
|
||||||
class="select"
|
class="select"
|
||||||
[options]="modalService.active.input.options"
|
[options]="modalService.active.input.options"
|
||||||
[default]="modalService.active.input.options[0]"
|
[default]="modalService.active.input.default"
|
||||||
[alwaysDropShadow]="true"
|
[alwaysDropShadow]="true"
|
||||||
[onlyShadowBorder]="true"
|
[onlyShadowBorder]="true"
|
||||||
[placeholder]="'Tag this item…'"
|
[placeholder]="'Tag this item…'"
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
You are trying to remove
|
You are trying to remove
|
||||||
<span [ngStyle]="{ color: modalService.active.input.baseColor.toString() }">{{
|
<span [ngStyle]="{ color: modalService.active.input.baseColor | color }">{{
|
||||||
modalService.active.input.name ? modalService.active.input.name : 'an unnamed tower'
|
modalService.active.input.name ? modalService.active.input.name : 'an unnamed tower'
|
||||||
}}</span
|
}}</span
|
||||||
>.
|
>.
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
<div [ngStyle]="{ 'background-color': block.color }" (click)="handleClick()"></div>
|
<div [ngStyle]="{ 'background-color': block.color | color }" (click)="handleClick()"></div>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { Block } from '../../../../../model/block';
|
import { Block } from '../../../../../model/block';
|
||||||
import { ModalService } from '../../../../../services/modal.service';
|
import { ModalService } from '../../../../../services/modal.service';
|
||||||
import { Tower } from '../../../../../model/tower';
|
import { ColoredBlock, Tower } from '../../../../../model/tower';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-block',
|
selector: 'app-block',
|
||||||
|
|
@ -9,7 +9,7 @@ import { Tower } from '../../../../../model/tower';
|
||||||
styleUrls: ['./block.component.scss']
|
styleUrls: ['./block.component.scss']
|
||||||
})
|
})
|
||||||
export class BlockComponent {
|
export class BlockComponent {
|
||||||
@Input() block: Block;
|
@Input() block: ColoredBlock;
|
||||||
@Input() tower: Tower;
|
@Input() tower: Tower;
|
||||||
|
|
||||||
constructor(private modalService: ModalService) {}
|
constructor(private modalService: ModalService) {}
|
||||||
|
|
@ -22,9 +22,12 @@ export class BlockComponent {
|
||||||
description: this.block.description,
|
description: this.block.description,
|
||||||
isDone: this.block.isDone
|
isDone: this.block.isDone
|
||||||
});
|
});
|
||||||
this.block.tag = selected;
|
console.log(description);
|
||||||
this.block.description = description;
|
this.block.changeProperties({
|
||||||
this.block.isDone = isDone;
|
tag: selected,
|
||||||
|
description,
|
||||||
|
isDone
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
// pass
|
// pass
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@
|
||||||
{{ tasks.length == 0 ? '​' : tasks.length == 1 ? 'task' : 'tasks' }}
|
{{ tasks.length == 0 ? '​' : tasks.length == 1 ? 'task' : 'tasks' }}
|
||||||
</p>
|
</p>
|
||||||
<div class="all-task" #allTask [ngStyle]="{ height: (isOpen ? allTask?.scrollHeight : 0) + 'px' }">
|
<div class="all-task" #allTask [ngStyle]="{ height: (isOpen ? allTask?.scrollHeight : 0) + 'px' }">
|
||||||
<div class="task-container" *ngFor="let task of tasks" [ngStyle]="{ color: toHslString(task.color) }">
|
<div class="task-container" *ngFor="let task of tasks" [ngStyle]="{ color: task.color | color }">
|
||||||
<div [ngStyle]="{ 'background-color': toHslString(task.color) }"></div>
|
<div [ngStyle]="{ 'background-color': task.color | color }"></div>
|
||||||
<p (click)="handleClick(task)" [innerText]="task.description ? task.description : 'unknown'"></p>
|
<p (click)="handleClick(task)" [innerText]="task.description ? task.description : 'unknown'"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import { Block } from '../../../../../model/block';
|
||||||
import { Tower } from '../../../../../model/tower';
|
import { Tower } from '../../../../../model/tower';
|
||||||
import { ModalService } from '../../../../../services/modal.service';
|
import { ModalService } from '../../../../../services/modal.service';
|
||||||
import { CancelService } from '../../../../../services/cancel.service';
|
import { CancelService } from '../../../../../services/cancel.service';
|
||||||
import { toHslString } from '../../../../../utils/color';
|
import { IColor } from '../../../../../interfaces/color';
|
||||||
import { IColor } from '../../../../../interfaces/persistance/color';
|
import { IBlock } from '../../../../../interfaces/persistance/block';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tasks',
|
selector: 'app-tasks',
|
||||||
|
|
@ -12,8 +12,6 @@ import { IColor } from '../../../../../interfaces/persistance/color';
|
||||||
styleUrls: ['./tasks.component.scss']
|
styleUrls: ['./tasks.component.scss']
|
||||||
})
|
})
|
||||||
export class TasksComponent implements OnInit {
|
export class TasksComponent implements OnInit {
|
||||||
readonly toHslString = toHslString;
|
|
||||||
|
|
||||||
@Input() tasks: Array<Block & { color: IColor }>;
|
@Input() tasks: Array<Block & { color: IColor }>;
|
||||||
@Input() tower: Tower;
|
@Input() tower: Tower;
|
||||||
|
|
||||||
|
|
@ -48,12 +46,16 @@ export class TasksComponent implements OnInit {
|
||||||
isDone: block.isDone
|
isDone: block.isDone
|
||||||
});
|
});
|
||||||
|
|
||||||
block.tag = selected;
|
const change: Partial<IBlock> = {
|
||||||
block.description = description;
|
tag: selected,
|
||||||
|
description,
|
||||||
|
isDone
|
||||||
|
};
|
||||||
if (!block.isDone && isDone) {
|
if (!block.isDone && isDone) {
|
||||||
block.created = new Date();
|
change.created = new Date();
|
||||||
}
|
}
|
||||||
block.isDone = isDone;
|
|
||||||
|
block.changeProperties(change);
|
||||||
} catch {
|
} catch {
|
||||||
// pass
|
// pass
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
id="tower-name"
|
id="tower-name"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="name…"
|
placeholder="name…"
|
||||||
[(ngModel)]="tower.name"
|
[(ngModel)]="towerName"
|
||||||
[ngStyle]="{ color: toHslString(tower?.baseColor) }"
|
[ngStyle]="{ color: tower?.baseColor | color }"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
import { Component, Input } from '@angular/core';
|
import { Component, Input } from '@angular/core';
|
||||||
import { Tower } from '../../../../model/tower';
|
import { ColoredBlock, Tower } from '../../../../model/tower';
|
||||||
import { ModalService } from '../../../../services/modal.service';
|
import { ModalService } from '../../../../services/modal.service';
|
||||||
import { Block } from '../../../../model/block';
|
|
||||||
import { IColor } from '../../../../interfaces/persistance/color';
|
|
||||||
import { toHslString } from '../../../../utils/color';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tower',
|
selector: 'app-tower',
|
||||||
|
|
@ -11,7 +8,13 @@ import { toHslString } from '../../../../utils/color';
|
||||||
styleUrls: ['./tower.component.scss']
|
styleUrls: ['./tower.component.scss']
|
||||||
})
|
})
|
||||||
export class TowerComponent {
|
export class TowerComponent {
|
||||||
readonly toHslString = toHslString;
|
get towerName(): string {
|
||||||
|
return this.tower.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
set towerName(value: string) {
|
||||||
|
this.tower.changeName(value);
|
||||||
|
}
|
||||||
|
|
||||||
@Input() set dateRange(value: { from: Date; to: Date }) {
|
@Input() set dateRange(value: { from: Date; to: Date }) {
|
||||||
if (this.dateRange !== undefined && this.dateRange.from === value.from && this.dateRange.to === value.to) {
|
if (this.dateRange !== undefined && this.dateRange.from === value.from && this.dateRange.to === value.to) {
|
||||||
|
|
@ -26,13 +29,13 @@ export class TowerComponent {
|
||||||
|
|
||||||
public constructor(private modalService: ModalService) {}
|
public constructor(private modalService: ModalService) {}
|
||||||
|
|
||||||
get drawableBlocks(): Array<Block & { color: IColor }> {
|
get drawableBlocks(): Array<ColoredBlock> {
|
||||||
return this.tower.coloredBlocks.filter(
|
return this.tower.coloredBlocks.filter(
|
||||||
block => this.dateRange.from <= block.created && block.created <= this.dateRange.to && block.isDone
|
block => this.dateRange.from <= block.created && block.created <= this.dateRange.to && block.isDone
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get tasks(): Array<Block & { color: IColor }> {
|
get tasks(): Array<ColoredBlock> {
|
||||||
return this.tower.coloredBlocks.filter(
|
return this.tower.coloredBlocks.filter(
|
||||||
block => this.dateRange.from <= block.created && block.created <= this.dateRange.to && !block.isDone
|
block => this.dateRange.from <= block.created && block.created <= this.dateRange.to && !block.isDone
|
||||||
);
|
);
|
||||||
|
|
|
||||||
5
src/app/interfaces/color.ts
Normal file
5
src/app/interfaces/color.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export interface IColor {
|
||||||
|
h: number;
|
||||||
|
s: number;
|
||||||
|
l: number;
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
import { Typed } from './typed';
|
export interface IBlock {
|
||||||
|
|
||||||
export interface IBlock extends Typed {
|
|
||||||
type: 'Block';
|
|
||||||
created: Date;
|
created: Date;
|
||||||
tag: string;
|
tag: string;
|
||||||
isDone: boolean;
|
isDone: boolean;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
import { Typed } from './typed';
|
|
||||||
|
|
||||||
export interface IColor extends Typed {
|
|
||||||
type: 'Color';
|
|
||||||
h: number;
|
|
||||||
s: number;
|
|
||||||
l: number;
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import { ITower } from './tower';
|
import { ITower } from './tower';
|
||||||
import { Typed } from './typed';
|
|
||||||
|
|
||||||
export interface IPage extends Typed {
|
export interface IPage {
|
||||||
type: 'Page';
|
|
||||||
name: string;
|
name: string;
|
||||||
towers: ITower[];
|
towers: ITower[];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
import { IBlock } from './block';
|
import { IBlock } from './block';
|
||||||
import { IColor } from './color';
|
import { IColor } from '../color';
|
||||||
import { Typed } from './typed';
|
|
||||||
|
|
||||||
export interface ITower extends Typed {
|
export interface ITower {
|
||||||
type: 'Tower';
|
|
||||||
name: string;
|
name: string;
|
||||||
blocks: IBlock[];
|
blocks: IBlock[];
|
||||||
baseColor: IColor;
|
baseColor: IColor;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
export interface Typed {
|
|
||||||
type: string;
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
export interface Vector {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +1,30 @@
|
||||||
import { Serializable } from './serializable';
|
import { Serializable } from './serializable';
|
||||||
import { IBlock } from '../interfaces/persistance/block';
|
import { IBlock } from '../interfaces/persistance/block';
|
||||||
import { Node } from '../storage/node';
|
import { Node } from '../store/node';
|
||||||
|
|
||||||
export class Block extends Serializable implements IBlock {
|
export class Block extends Serializable implements IBlock {
|
||||||
constructor(parent: Node, props: IBlock) {
|
constructor(parent: Node, props: IBlock) {
|
||||||
super(parent, props);
|
super(parent, props);
|
||||||
|
this.onAfterClone();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onAfterClone(): void {
|
||||||
if (this.created.constructor.name !== 'Date') {
|
if (this.created.constructor.name !== 'Date') {
|
||||||
this.created = new Date(this.created);
|
this.created = new Date(this.created);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove.
|
||||||
|
if (this.isDone === null || this.isDone === undefined) {
|
||||||
|
this.isDone = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
changeProperties(values: Partial<IBlock>) {
|
||||||
|
this.changeKeys(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only here to prevent ts warnings.
|
|
||||||
type: 'Block';
|
|
||||||
created: Date;
|
created: Date;
|
||||||
isDone: boolean;
|
isDone: boolean;
|
||||||
description: string;
|
readonly description: string;
|
||||||
tag: string;
|
readonly tag: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,19 @@
|
||||||
import { Serializable } from './serializable';
|
import { Serializable } from './serializable';
|
||||||
import { IPage } from '../interfaces/persistance/page';
|
import { IPage } from '../interfaces/persistance/page';
|
||||||
import { Tower } from './tower';
|
import { Tower } from './tower';
|
||||||
import { Node } from '../storage/node';
|
import { Node } from '../store/node';
|
||||||
|
|
||||||
export class Page extends Serializable implements IPage {
|
export class Page extends Serializable implements IPage {
|
||||||
constructor(parent: Node, props: IPage) {
|
constructor(parent: Node, props: IPage) {
|
||||||
super(parent, props);
|
super(parent, props);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only here to prevent ts warnings.
|
readonly name: string;
|
||||||
name: string;
|
|
||||||
get towers(): Array<Tower> {
|
get towers(): Array<Tower> {
|
||||||
return this.children as Array<Tower>;
|
return this.children as Array<Tower>;
|
||||||
}
|
}
|
||||||
type: 'Page';
|
|
||||||
|
|
||||||
userData: {
|
readonly userData: {
|
||||||
hideCreateTowerButton: boolean;
|
hideCreateTowerButton: boolean;
|
||||||
defaultDateRange: {
|
defaultDateRange: {
|
||||||
from: Date;
|
from: Date;
|
||||||
|
|
@ -38,10 +36,14 @@ export class Page extends Serializable implements IPage {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.map(page => {
|
const towers = [...this.towers];
|
||||||
const tower = page.towers[previousIndex];
|
const tower = towers[previousIndex];
|
||||||
page.towers.splice(previousIndex, 1);
|
towers.splice(previousIndex, 1);
|
||||||
page.towers.splice(currentIndex, 0, tower);
|
towers.splice(currentIndex, 0, tower);
|
||||||
|
|
||||||
|
this.changeValue({
|
||||||
|
oldValue: this.towers,
|
||||||
|
newValue: towers
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,10 +54,9 @@ export class Page extends Serializable implements IPage {
|
||||||
} while (30 <= hue && hue <= 200);
|
} while (30 <= hue && hue <= 200);
|
||||||
|
|
||||||
new Tower(this, {
|
new Tower(this, {
|
||||||
type: 'Tower',
|
|
||||||
name,
|
name,
|
||||||
blocks: [],
|
blocks: [],
|
||||||
baseColor: { h: hue, s: 100, l: 50, type: 'Color' }
|
baseColor: { h: hue, s: 100, l: 50 }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import { Cloneable } from '../storage/cloneable';
|
import { Cloneable } from '../store/cloneable';
|
||||||
import { Node } from '../storage/node';
|
import { Node } from '../store/node';
|
||||||
|
|
||||||
export class Serializable extends Cloneable {
|
export class Serializable extends Cloneable {
|
||||||
type: string;
|
|
||||||
private static propertyList: any = {};
|
private static propertyList: any = {};
|
||||||
static childrenMap: {
|
static childrenMap: {
|
||||||
[type: string]: {
|
[type: string]: {
|
||||||
|
|
@ -11,7 +10,11 @@ export class Serializable extends Cloneable {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(parent: Node, properties: any) {
|
protected onAfterClone(): void {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
|
||||||
|
protected constructor(parent: Node, properties: any) {
|
||||||
super(parent);
|
super(parent);
|
||||||
|
|
||||||
const type = this.constructor.name;
|
const type = this.constructor.name;
|
||||||
|
|
|
||||||
|
|
@ -3,44 +3,39 @@ import { lighten } from '../utils/color';
|
||||||
import { Block } from './block';
|
import { Block } from './block';
|
||||||
import { Serializable } from './serializable';
|
import { Serializable } from './serializable';
|
||||||
import { hash } from '../utils/hash';
|
import { hash } from '../utils/hash';
|
||||||
import { Node } from '../storage/node';
|
import { Node } from '../store/node';
|
||||||
import { IColor } from '../interfaces/persistance/color';
|
import { IColor } from '../interfaces/color';
|
||||||
|
|
||||||
|
export type ColoredBlock = Block & { color: IColor };
|
||||||
|
|
||||||
export class Tower extends Serializable implements ITower {
|
export class Tower extends Serializable implements ITower {
|
||||||
constructor(parent: Node, props: ITower) {
|
|
||||||
super(parent, props);
|
|
||||||
|
|
||||||
this.blocks.sort((a, b) => a.created.getTime() - b.created.getTime());
|
|
||||||
this.calculateTagList();
|
|
||||||
}
|
|
||||||
|
|
||||||
tags: string[];
|
tags: string[];
|
||||||
|
|
||||||
// Only here to prevent ts warnings.
|
|
||||||
name: string;
|
name: string;
|
||||||
type: 'Tower';
|
|
||||||
get blocks(): Array<Block> {
|
get blocks(): Array<Block> {
|
||||||
return this.children as Array<Block>;
|
return this.children as Array<Block>;
|
||||||
}
|
}
|
||||||
baseColor: IColor;
|
|
||||||
|
|
||||||
get coloredBlocks(): Array<Block & { color: IColor }> {
|
coloredBlocks: Array<ColoredBlock>;
|
||||||
return this.children.map(b => {
|
|
||||||
const coloredBlock = b as Block & { color: IColor };
|
readonly baseColor: IColor;
|
||||||
|
|
||||||
|
constructor(parent: Node, props: ITower) {
|
||||||
|
super(parent, props);
|
||||||
|
this.onAfterClone();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onAfterClone(): void {
|
||||||
|
this.blocks.sort((a, b) => {
|
||||||
|
return a.created.getTime() - b.created.getTime();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.coloredBlocks = this.blocks.map(b => {
|
||||||
|
const coloredBlock = b as ColoredBlock;
|
||||||
coloredBlock.color = lighten((hash(coloredBlock.tag) - 0.5) * 50, this.baseColor);
|
coloredBlock.color = lighten((hash(coloredBlock.tag) - 0.5) * 50, this.baseColor);
|
||||||
return coloredBlock;
|
return coloredBlock;
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
addBlock(props: { tag: string; description: string; isDone: boolean }) {
|
|
||||||
new Block(this, {
|
|
||||||
created: new Date(),
|
|
||||||
...props,
|
|
||||||
type: 'Block'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private calculateTagList() {
|
|
||||||
this.tags = [];
|
this.tags = [];
|
||||||
for (const block of this.blocks) {
|
for (const block of this.blocks) {
|
||||||
if (!this.tags.includes(block.tag)) {
|
if (!this.tags.includes(block.tag)) {
|
||||||
|
|
@ -48,4 +43,17 @@ export class Tower extends Serializable implements ITower {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addBlock(props: { tag: string; description: string; isDone: boolean }) {
|
||||||
|
new Block(this, {
|
||||||
|
created: new Date(),
|
||||||
|
...props
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
changeName(newName: string) {
|
||||||
|
// For optimization purposes.
|
||||||
|
this.name = newName;
|
||||||
|
this.mutatedUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
src/app/pipes/color.pipe.ts
Normal file
12
src/app/pipes/color.pipe.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
import { IColor } from '../interfaces/color';
|
||||||
|
import { toHslString } from '../utils/color';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'color'
|
||||||
|
})
|
||||||
|
export class ColorPipe implements PipeTransform {
|
||||||
|
transform(color: IColor, args?: any): string {
|
||||||
|
return toHslString(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { StoreService } from './store.service';
|
import { StoreService } from './store.service';
|
||||||
import { Page } from '../model/page';
|
import { Page } from '../model/page';
|
||||||
import { Root } from '../storage/root';
|
import { Root } from '../store/root';
|
||||||
import { Serializable } from '../model/serializable';
|
import { Serializable } from '../model/serializable';
|
||||||
import { Tower } from '../model/tower';
|
import { Tower } from '../model/tower';
|
||||||
import { Block } from '../model/block';
|
import { Block } from '../model/block';
|
||||||
|
|
@ -47,15 +47,22 @@ export class DataService extends Root<Page> {
|
||||||
this.children$.subscribe(value => {
|
this.children$.subscribe(value => {
|
||||||
this.log();
|
this.log();
|
||||||
this._safeChildren.next(value);
|
this._safeChildren.next(value);
|
||||||
this.storeService.scheduleSave(this.pages);
|
this.save(0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutatedUpdate() {
|
||||||
|
this.save(2500);
|
||||||
|
}
|
||||||
|
|
||||||
|
save(timeout: number) {
|
||||||
|
this.storeService.scheduleSave(this.pages, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
addPage(name: string) {
|
addPage(name: string) {
|
||||||
new Page(this, {
|
new Page(this, {
|
||||||
name,
|
name,
|
||||||
userData: {},
|
userData: {},
|
||||||
type: 'Page',
|
|
||||||
towers: []
|
towers: []
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
||||||
import { Page } from '../model/page';
|
import { Page } from '../model/page';
|
||||||
import { IPage } from '../interfaces/persistance/page';
|
import { IPage } from '../interfaces/persistance/page';
|
||||||
|
|
||||||
const LOCAL_STORAGE_KEY = 'life-towers.data.v.3';
|
const LOCAL_STORAGE_KEY = 'life-towers.data.v.2';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
|
@ -15,84 +15,77 @@ export class StoreService<T> {
|
||||||
{
|
{
|
||||||
name: 'Work & life',
|
name: 'Work & life',
|
||||||
userData: {},
|
userData: {},
|
||||||
type: 'Page',
|
|
||||||
towers: [
|
towers: [
|
||||||
{
|
{
|
||||||
name: 'work',
|
name: 'work',
|
||||||
baseColor: { h: 0, s: 100, l: 50, type: 'Color' },
|
baseColor: { h: 0, s: 100, l: 50 },
|
||||||
type: 'Tower',
|
|
||||||
blocks: [
|
blocks: [
|
||||||
{
|
{
|
||||||
created: new Date(2015, 2, 13),
|
created: new Date(2015, 2, 13),
|
||||||
tag: 'a',
|
tag: 'a',
|
||||||
description: 'done it',
|
description: 'done it',
|
||||||
isDone: true,
|
isDone: true
|
||||||
type: 'Block'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
created: new Date(2016, 2, 15),
|
created: new Date(2016, 2, 15),
|
||||||
tag: 'go to school',
|
tag: 'go to school',
|
||||||
description: 'done it',
|
description: 'done it',
|
||||||
type: 'Block'
|
isDone: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
created: new Date(2017, 2, 15),
|
created: new Date(2017, 2, 15),
|
||||||
tag: 'go to work',
|
tag: 'go to work',
|
||||||
isDone: true,
|
isDone: true
|
||||||
type: 'Block'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
created: new Date(2018, 2, 13),
|
created: new Date(2018, 2, 13),
|
||||||
tag: 'go to work',
|
tag: 'go to work',
|
||||||
description: 'done it',
|
description: 'done it',
|
||||||
isDone: true,
|
isDone: true
|
||||||
type: 'Block'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
created: new Date(2019, 3, 13),
|
created: new Date(2019, 3, 13),
|
||||||
tag: 'go to work',
|
tag: 'go to work',
|
||||||
type: 'Block'
|
isDone: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
created: new Date(2020, 2, 15),
|
created: new Date(2020, 2, 15),
|
||||||
tag: 'go to school',
|
tag: 'go to school',
|
||||||
description: 'done it',
|
description: 'done it',
|
||||||
isDone: true,
|
isDone: true
|
||||||
type: 'Block'
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
created: new Date(2021, 2, 15),
|
created: new Date(2021, 2, 15),
|
||||||
tag: 'go to school',
|
tag: 'go to school',
|
||||||
type: 'Block'
|
isDone: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
baseColor: { h: 180, s: 100, l: 50, type: 'Color' },
|
baseColor: { h: 180, s: 100, l: 50 },
|
||||||
name: 'life',
|
name: 'life',
|
||||||
type: 'Tower',
|
|
||||||
blocks: [
|
blocks: [
|
||||||
{
|
{
|
||||||
created: new Date(2019, 3, 13),
|
created: new Date(2019, 3, 13),
|
||||||
tag: 'go home',
|
tag: 'go home',
|
||||||
description: 'done it',
|
description: 'done it',
|
||||||
type: 'Block'
|
isDone: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
created: new Date(2019, 4, 13),
|
created: new Date(2019, 4, 13),
|
||||||
tag: 'go home',
|
tag: 'go home',
|
||||||
type: 'Block'
|
isDone: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
created: new Date(2019, 4, 15),
|
created: new Date(2019, 4, 15),
|
||||||
tag: 'go to work',
|
tag: 'go to work',
|
||||||
description: 'done it',
|
description: 'done it',
|
||||||
type: 'Block'
|
isDone: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
created: new Date(2019, 4, 15, 14),
|
created: new Date(2019, 4, 15, 14),
|
||||||
tag: 'go to work',
|
tag: 'go to work',
|
||||||
type: 'Block'
|
isDone: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -105,14 +98,14 @@ export class StoreService<T> {
|
||||||
this.storedData = JSON.parse(localStorageData ? localStorageData : this.mockData) as T;
|
this.storedData = JSON.parse(localStorageData ? localStorageData : this.mockData) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleSave(data: T) {
|
scheduleSave(data: T, timeout: number) {
|
||||||
this.dataToSave = data;
|
this.dataToSave = data;
|
||||||
if (!this.saveScheduled) {
|
if (!this.saveScheduled) {
|
||||||
this.saveScheduled = true;
|
this.saveScheduled = true;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.saveScheduled = false;
|
this.saveScheduled = false;
|
||||||
this.save(this.dataToSave).catch();
|
this.save(this.dataToSave).catch();
|
||||||
}, 0);
|
}, timeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import { InnerNode } from './inner-node';
|
import { InnerNode } from './inner-node';
|
||||||
import { Node } from './node';
|
import { Node } from './node';
|
||||||
|
|
||||||
export class Cloneable extends InnerNode {
|
export abstract class Cloneable extends InnerNode {
|
||||||
constructor(parent: Node) {
|
protected constructor(parent: Node) {
|
||||||
super(parent);
|
super(parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract onAfterClone(): void;
|
||||||
|
|
||||||
protected cloneWithMap(map: (node: this) => void): this {
|
protected cloneWithMap(map: (node: this) => void): this {
|
||||||
const insides = Object.getOwnPropertyDescriptors(this);
|
const insides = Object.getOwnPropertyDescriptors(this);
|
||||||
|
|
||||||
|
|
@ -20,8 +22,8 @@ export class Cloneable extends InnerNode {
|
||||||
return value.bind(proxy);
|
return value.bind(proxy);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
} else if (this.hasOwnProperty(prop)) {
|
} else if (target.prototype.hasOwnProperty(prop)) {
|
||||||
const value = this[prop];
|
const value = target.prototype[prop];
|
||||||
if (typeof value === 'function') {
|
if (typeof value === 'function') {
|
||||||
return value.bind(proxy);
|
return value.bind(proxy);
|
||||||
}
|
}
|
||||||
|
|
@ -34,28 +36,44 @@ export class Cloneable extends InnerNode {
|
||||||
});
|
});
|
||||||
map(<any>insidesProxy);
|
map(<any>insidesProxy);
|
||||||
|
|
||||||
(<any>insidesProxy.__target__).id.value = Node.id++;
|
return this.cloneFromInsides(<any>insidesProxy.__target__);
|
||||||
(<any>insidesProxy.__target__).copyCount.value++;
|
|
||||||
Node.sumCopyCount++;
|
|
||||||
|
|
||||||
return Object.create(Object.getPrototypeOf(this), <any>insidesProxy.__target__);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected cloneWithAdd({ value, propertyName }: { value: any; propertyName: string }): this {
|
protected cloneWithAdd({ propertyName, value }: { value: any; propertyName: string }): this {
|
||||||
|
if (this[propertyName] === value) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
const insides = Object.getOwnPropertyDescriptors(this);
|
const insides = Object.getOwnPropertyDescriptors(this);
|
||||||
insides[propertyName].value = value;
|
insides[propertyName].value = value;
|
||||||
insides.id.value = Node.id++;
|
return this.cloneFromInsides(insides);
|
||||||
insides.copyCount.value++;
|
}
|
||||||
Node.sumCopyCount++;
|
|
||||||
|
|
||||||
return Object.create(Object.getPrototypeOf(this), insides);
|
protected cloneWithChangedKeys(props: { [propertyName: string]: any }): this {
|
||||||
|
const insides = Object.getOwnPropertyDescriptors(this);
|
||||||
|
|
||||||
|
for (let key in props) {
|
||||||
|
if (props.hasOwnProperty(key)) {
|
||||||
|
if (insides.hasOwnProperty(key)) {
|
||||||
|
insides[key].value = props[key];
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
insides[key] = {
|
||||||
|
value: props[key]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.cloneFromInsides(insides);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected cloneWithModify({ oldValue, newValue }: { oldValue: any; newValue: any }): this {
|
protected cloneWithModify({ oldValue, newValue }: { oldValue: any; newValue: any }): this {
|
||||||
|
if (oldValue === newValue) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
const insides = Object.getOwnPropertyDescriptors(this);
|
const insides = Object.getOwnPropertyDescriptors(this);
|
||||||
insides.id.value = Node.id++;
|
|
||||||
insides.copyCount.value++;
|
|
||||||
Node.sumCopyCount++;
|
|
||||||
|
|
||||||
let wasMatch = false;
|
let wasMatch = false;
|
||||||
for (let name in insides) {
|
for (let name in insides) {
|
||||||
|
|
@ -69,6 +87,16 @@ export class Cloneable extends InnerNode {
|
||||||
throw new TypeError(`Object has no property with value: ${oldValue.toString()}`);
|
throw new TypeError(`Object has no property with value: ${oldValue.toString()}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.create(Object.getPrototypeOf(this), insides);
|
return this.cloneFromInsides(insides);
|
||||||
|
}
|
||||||
|
|
||||||
|
private cloneFromInsides(insides): this {
|
||||||
|
insides.id.value = Node.id++;
|
||||||
|
insides.copyCount.value++;
|
||||||
|
Node.sumCopyCount++;
|
||||||
|
|
||||||
|
const clone = Object.create(Object.getPrototypeOf(this), insides);
|
||||||
|
clone.onAfterClone();
|
||||||
|
return clone;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { Node } from './node';
|
import { Node } from './node';
|
||||||
|
import { observableToBeFn } from 'rxjs/internal/testing/TestScheduler';
|
||||||
|
|
||||||
export abstract class InnerNode extends Node {
|
export abstract class InnerNode extends Node {
|
||||||
parent: Node;
|
parent: Node;
|
||||||
|
|
@ -17,21 +18,31 @@ export abstract class InnerNode extends Node {
|
||||||
protected constructor(parent: Node) {
|
protected constructor(parent: Node) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
parent.addChild({
|
new Promise(r => r()).then(() =>
|
||||||
value: this
|
parent.addChild({
|
||||||
});
|
value: this
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
mutatedUpdate() {
|
||||||
|
this.parent.mutatedUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
map(map: (a: this) => void) {
|
map(map: (a: this) => void) {
|
||||||
return this.update((self: this) => this.cloneWithMap.call(self, map));
|
return this.update((self: this) => this.cloneWithMap.call(self, map));
|
||||||
}
|
}
|
||||||
|
|
||||||
changeKey(update: { propertyName: string; value: any }): this {
|
changeKey({ propertyName, value }: { propertyName: string; value: any }): this {
|
||||||
return this.update((self: this) => this.cloneWithAdd.call(self, update));
|
return this.update((self: this) => this.cloneWithAdd.call(self, { propertyName, value }));
|
||||||
}
|
}
|
||||||
|
|
||||||
changeValue(update: { oldValue: any; newValue: any }): this {
|
changeKeys(props: { [propertyName: string]: any }): this {
|
||||||
return this.update((self: this) => this.cloneWithModify.call(self, update));
|
return this.update((self: this) => this.cloneWithChangedKeys.call(self, props));
|
||||||
|
}
|
||||||
|
|
||||||
|
changeValue({ oldValue, newValue }: { oldValue: any; newValue: any }): this {
|
||||||
|
return this.update((self: this) => this.cloneWithModify.call(self, { oldValue, newValue }));
|
||||||
}
|
}
|
||||||
|
|
||||||
addChild(update: { value: InnerNode }) {
|
addChild(update: { value: InnerNode }) {
|
||||||
|
|
@ -44,6 +55,7 @@ export abstract class InnerNode extends Node {
|
||||||
|
|
||||||
protected abstract cloneWithMap(map: (a: this) => void): this;
|
protected abstract cloneWithMap(map: (a: this) => void): this;
|
||||||
protected abstract cloneWithAdd(update: { value: any; propertyName: string }): this;
|
protected abstract cloneWithAdd(update: { value: any; propertyName: string }): this;
|
||||||
|
protected abstract cloneWithChangedKeys(props: { [propertyName: string]: any }): this;
|
||||||
protected abstract cloneWithModify(update: { oldValue: any; newValue: any }): this;
|
protected abstract cloneWithModify(update: { oldValue: any; newValue: any }): this;
|
||||||
|
|
||||||
private update(cloneMethod: (self: this) => this): this {
|
private update(cloneMethod: (self: this) => this): this {
|
||||||
|
|
@ -52,6 +64,10 @@ export abstract class InnerNode extends Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
const clone = cloneMethod(this);
|
const clone = cloneMethod(this);
|
||||||
|
if (clone === this) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
for (let child of clone.children) {
|
for (let child of clone.children) {
|
||||||
child.parent = clone;
|
child.parent = clone;
|
||||||
}
|
}
|
||||||
|
|
@ -14,6 +14,8 @@ export abstract class Node {
|
||||||
Node.sumCopyCount++;
|
Node.sumCopyCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract mutatedUpdate(): void;
|
||||||
|
|
||||||
addChild(update: { value: InnerNode }) {
|
addChild(update: { value: InnerNode }) {
|
||||||
this.changeValue({
|
this.changeValue({
|
||||||
oldValue: this.children,
|
oldValue: this.children,
|
||||||
|
|
@ -22,6 +24,10 @@ export abstract class Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
changeChild({ oldValue, newValue }: { oldValue: InnerNode; newValue: InnerNode }) {
|
changeChild({ oldValue, newValue }: { oldValue: InnerNode; newValue: InnerNode }) {
|
||||||
|
if (oldValue === newValue) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.changeValue({
|
this.changeValue({
|
||||||
oldValue: this.children,
|
oldValue: this.children,
|
||||||
newValue: this.children.map(c => (c === oldValue ? newValue : c))
|
newValue: this.children.map(c => (c === oldValue ? newValue : c))
|
||||||
|
|
@ -15,6 +15,10 @@ export class Root<T extends InnerNode> extends Node {
|
||||||
this._children.next(value);
|
this._children.next(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mutatedUpdate() {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
|
|
||||||
changeValue({ oldValue, newValue }: { oldValue: any; newValue: any }) {
|
changeValue({ oldValue, newValue }: { oldValue: any; newValue: any }) {
|
||||||
if (this.children !== oldValue) {
|
if (this.children !== oldValue) {
|
||||||
throw new TypeError('Only children can be changed.');
|
throw new TypeError('Only children can be changed.');
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { IColor } from '../interfaces/persistance/color';
|
import { IColor } from '../interfaces/color';
|
||||||
|
|
||||||
export const lighten = (by: number, { h, s, l }: IColor): IColor => {
|
export const lighten = (by: number, { h, s, l }: IColor): IColor => {
|
||||||
let newL = l + by;
|
let newL = l + by;
|
||||||
|
|
@ -8,7 +8,7 @@ export const lighten = (by: number, { h, s, l }: IColor): IColor => {
|
||||||
newL = 0;
|
newL = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { h, s, l: newL, type: 'Color' };
|
return { h, s, l: newL };
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toHslString = ({ h, s, l }: IColor): string => {
|
export const toHslString = ({ h, s, l }: IColor): string => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue