+
+
+
+
+
+
diff --git a/src/app/components/shared/select-add/select-add.component.scss b/src/app/components/shared/select-add/select-add.component.scss
old mode 100644
new mode 100755
index 941da58..9920d6f
--- a/src/app/components/shared/select-add/select-add.component.scss
+++ b/src/app/components/shared/select-add/select-add.component.scss
@@ -19,7 +19,8 @@ $inner-padding: var(--medium-padding);
position: relative;
cursor: pointer;
- p {
+ p,
+ input[type='text'] {
display: inline-block;
@include sub-title-text();
}
@@ -36,7 +37,7 @@ $inner-padding: var(--medium-padding);
.bottom-container {
width: 100%;
- height: 40vh;
+ height: 300px;
position: absolute;
overflow-y: hidden;
@@ -77,6 +78,75 @@ $inner-padding: var(--medium-padding);
cursor: pointer;
}
+
+ .buttons {
+ height: 32px;
+ @media (max-width: $mobile-width) {
+ height: 24px;
+ }
+ position: relative;
+ width: 100%;
+
+ button {
+ margin: 0;
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translateY(-50%) translateX(-50%);
+ }
+
+ .edit {
+ position: absolute;
+ right: 0;
+ top: 50%;
+ transform: translateY(-50%);
+ margin: 0;
+ opacity: 0.25;
+ cursor: pointer;
+
+ img {
+ @include square(16px);
+ }
+
+ transition: opacity $short-animation-time;
+
+ &:before {
+ content: '';
+ display: block;
+ position: absolute;
+ bottom: calc(-1 * #{$line-height});
+ left: 0;
+ height: $line-height;
+ background-color: $text-color;
+ width: 0;
+ transition: width $long-animation-time;
+ }
+
+ @media (min-width: $mobile-width) {
+ &:hover {
+ opacity: 0.5;
+ }
+ &:hover {
+ &:before {
+ width: 100% !important;
+ }
+ }
+ }
+
+ &.active {
+ &:before {
+ width: 100% !important;
+ }
+ }
+
+ &.active {
+ opacity: 1;
+ }
+ }
+ }
+ }
+
+ .edit {
}
}
diff --git a/src/app/components/shared/select-add/select-add.component.ts b/src/app/components/shared/select-add/select-add.component.ts
old mode 100644
new mode 100755
index d52f4d3..a6751dc
--- a/src/app/components/shared/select-add/select-add.component.ts
+++ b/src/app/components/shared/select-add/select-add.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';
+import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, ChangeDetectorRef } from '@angular/core';
import { CancelService } from '../../../services/cancel.service';
@Component({
@@ -8,19 +8,31 @@ import { CancelService } from '../../../services/cancel.service';
})
export class SelectAddComponent {
@Input() placeholder = 'Add a new value…';
+ @Input() newValuePlaceholder = 'Add a value…';
@Input() maxItemCount = 7;
@Input() options: string[];
@Input() alwaysDropShadow = false;
@Input() onlyShadowBorder = false;
+ @Input() editable = false;
@Input() set default(value: string) {
this.selected = value;
- if (value) {
- this.value.emit(value);
- }
+ }
+
+ backgroundHeight: string;
+
+ private _editMode = false;
+ set editMode(value: boolean) {
+ this._editMode = value;
+ this.backgroundHeight = this.getBackgroundHeight();
+ }
+
+ get editMode(): boolean {
+ return this._editMode;
}
@Output() value: EventEmitter
= new EventEmitter();
+ @Output() optionChange: EventEmitter<{ from: string; to: string }> = new EventEmitter();
@ViewChild('top') top: ElementRef;
@ViewChild('bottom') bottom: ElementRef;
@@ -29,9 +41,19 @@ export class SelectAddComponent {
newOption: string;
isOpen = false;
- constructor(private cancelService: CancelService) {
+ constructor(private cancelService: CancelService, private changeDetection: ChangeDetectorRef) {
this.cancelService.subscribe(this, () => {
this.isOpen = false;
+ this.editMode = false;
+ this.changeDetection.markForCheck();
+ });
+ }
+
+ changeOption(from: string, event) {
+ // console.log(event);
+ this.optionChange.emit({
+ from,
+ to: event.target.value
});
}
@@ -45,15 +67,6 @@ export class SelectAddComponent {
}
}
- 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);
@@ -69,5 +82,26 @@ export class SelectAddComponent {
toggle() {
this.isOpen = !this.isOpen;
+ if (!this.isOpen) {
+ this.editMode = false;
+ }
+ this.backgroundHeight = this.getBackgroundHeight();
+ }
+
+ onArrowClick(event) {
+ if (this.editMode) {
+ this.toggle();
+ event.stopPropagation();
+ }
+ }
+
+ private getBackgroundHeight(): string {
+ if (this.isOpen && this.top && this.bottom) {
+ const topHeight = this.top.nativeElement.clientHeight;
+ const bottomHeight = this.bottom.nativeElement.clientHeight;
+ // console.log(topHeight, bottomHeight);
+ return `${topHeight + bottomHeight}px`;
+ }
+ return `100%`;
}
}
diff --git a/src/app/components/shared/toggle/toggle.component.html b/src/app/components/shared/toggle/toggle.component.html
old mode 100644
new mode 100755
index 8026d79..6873dcd
--- a/src/app/components/shared/toggle/toggle.component.html
+++ b/src/app/components/shared/toggle/toggle.component.html
@@ -1,5 +1,7 @@
-
+
diff --git a/src/app/components/shared/toggle/toggle.component.scss b/src/app/components/shared/toggle/toggle.component.scss
old mode 100644
new mode 100755
index 9ef51ea..2f2c77f
--- a/src/app/components/shared/toggle/toggle.component.scss
+++ b/src/app/components/shared/toggle/toggle.component.scss
@@ -24,46 +24,52 @@
}
}
- input[type='checkbox'] {
- -webkit-appearance: none;
- -moz-appearance: none;
+ label {
+ display: block;
- width: 2 * $size;
- height: $size;
+ input[type='checkbox'] {
+ -webkit-appearance: none;
+ -moz-appearance: none;
- border-radius: 1000px;
- box-shadow: $shadow-border;
-
- position: relative;
- cursor: pointer;
-
- &:after {
- content: '';
- position: absolute;
- display: block;
- left: 0;
-
- @include square($size);
+ width: 2 * $size;
+ height: $size;
border-radius: 1000px;
- background-color: $text-color;
+ box-shadow: $shadow-border;
- transition: box-shadow $long-animation-time, left $long-animation-time, transform $long-animation-time;
- }
+ position: relative;
+ cursor: pointer;
- @media (min-width: $mobile-width) {
- &:hover:after {
- box-shadow: $shadow;
- transform: translateX(2px);
+ &: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;
}
- &.on:hover:after {
- transform: translateX(-2px);
+ &.on:after {
+ left: $size;
}
}
- &.on:after {
- left: $size;
+ input[type='checkbox'] {
+ @media (min-width: $mobile-width) {
+ &:hover:after {
+ box-shadow: $shadow;
+ transform: translateX(2px);
+ }
+
+ &.on:hover:after {
+ transform: translateX(-2px);
+ }
+ }
}
}
}
diff --git a/src/app/components/shared/toggle/toggle.component.ts b/src/app/components/shared/toggle/toggle.component.ts
old mode 100644
new mode 100755
diff --git a/src/app/interfaces/color.ts b/src/app/interfaces/color.ts
old mode 100644
new mode 100755
diff --git a/src/app/interfaces/persistance/block.ts b/src/app/interfaces/persistance/block.ts
old mode 100644
new mode 100755
index 5021ea3..8cf78d1
--- a/src/app/interfaces/persistance/block.ts
+++ b/src/app/interfaces/persistance/block.ts
@@ -1,4 +1,6 @@
-export interface IBlock {
+import { IUnique } from './unique';
+
+export interface IBlock extends IUnique {
created: Date;
tag: string;
isDone: boolean;
diff --git a/src/app/interfaces/persistance/data.ts b/src/app/interfaces/persistance/data.ts
new file mode 100755
index 0000000..df92560
--- /dev/null
+++ b/src/app/interfaces/persistance/data.ts
@@ -0,0 +1,6 @@
+import { IPage } from './page';
+import { IUnique } from './unique';
+
+export interface IData extends IUnique {
+ pages: Array;
+}
diff --git a/src/app/interfaces/persistance/page.ts b/src/app/interfaces/persistance/page.ts
old mode 100644
new mode 100755
index 75d9bf7..a096fae
--- a/src/app/interfaces/persistance/page.ts
+++ b/src/app/interfaces/persistance/page.ts
@@ -1,12 +1,13 @@
import { ITower } from './tower';
import { Range } from '../range';
+import { IUnique } from './unique';
-export interface IPage {
+export interface IPage extends IUnique {
name: string;
towers: ITower[];
userData: {
- hideCreateTowerButton?: boolean;
- defaultDateRange?: Range;
+ hideCreateTowerButton: boolean;
+ defaultDateRange: Range;
};
}
diff --git a/src/app/interfaces/persistance/tower.ts b/src/app/interfaces/persistance/tower.ts
old mode 100644
new mode 100755
index 4776161..05d0a51
--- a/src/app/interfaces/persistance/tower.ts
+++ b/src/app/interfaces/persistance/tower.ts
@@ -1,7 +1,8 @@
import { IBlock } from './block';
import { IColor } from '../color';
+import { IUnique } from './unique';
-export interface ITower {
+export interface ITower extends IUnique {
name: string;
blocks: IBlock[];
baseColor: IColor;
diff --git a/src/app/interfaces/persistance/unique.ts b/src/app/interfaces/persistance/unique.ts
new file mode 100755
index 0000000..e41e7c2
--- /dev/null
+++ b/src/app/interfaces/persistance/unique.ts
@@ -0,0 +1,3 @@
+export interface IUnique {
+ id?: string;
+}
diff --git a/src/app/interfaces/range.ts b/src/app/interfaces/range.ts
old mode 100644
new mode 100755
diff --git a/src/app/interfaces/serializable.ts b/src/app/interfaces/serializable.ts
new file mode 100755
index 0000000..3a23338
--- /dev/null
+++ b/src/app/interfaces/serializable.ts
@@ -0,0 +1,3 @@
+export interface ISerializable {
+ serialize(referenceSerializer: (ref: object) => any): object;
+}
diff --git a/src/app/model/block.ts b/src/app/model/block.ts
old mode 100644
new mode 100755
index 2d49058..e54cc32
--- a/src/app/model/block.ts
+++ b/src/app/model/block.ts
@@ -1,30 +1,36 @@
-import { Serializable } from './serializable';
import { IBlock } from '../interfaces/persistance/block';
-import { Node } from '../store/node';
+import { InnerNode, InnerNodeState } from '../store/inner-node';
-export class Block extends Serializable implements IBlock {
- constructor(parent: Node, props: IBlock) {
- super(parent, props, 'Block');
- this.onAfterClone();
- }
+export interface BlockState extends IBlock, InnerNodeState {}
- protected onAfterClone(): void {
- if (this.created.constructor.name !== 'Date') {
- this.created = new Date(this.created);
- }
-
- // TODO: remove.
- if (this.isDone === null || this.isDone === undefined) {
- this.isDone = false;
- }
- }
-
- changeProperties(values: Partial) {
- this.changeKeys(values);
- }
-
- created: Date;
- isDone: boolean;
- readonly description: string;
+export class Block extends InnerNode implements IBlock, BlockState {
readonly tag: string;
+ readonly description: string;
+ readonly isDone: boolean;
+ readonly created: Date;
+
+ constructor(props: IBlock, referenceDeserializer: (from: any) => any = e => e) {
+ super([], props.id);
+ if (props.created.constructor.name !== 'Date') {
+ props.created = new Date(props.created);
+ }
+ this.tag = props.tag;
+ this.description = props.description;
+ this.isDone = props.isDone;
+ this.created = props.created;
+ }
+
+ changeKeys(props: Partial): this {
+ return super.changeKeys(props);
+ }
+
+ serialize(referenceSerializer: (ref: object) => any): IBlock {
+ return {
+ ...super.serialize(referenceSerializer),
+ tag: this.tag,
+ description: this.description,
+ isDone: this.isDone,
+ created: this.created
+ };
+ }
}
diff --git a/src/app/model/data.ts b/src/app/model/data.ts
new file mode 100755
index 0000000..89b60dc
--- /dev/null
+++ b/src/app/model/data.ts
@@ -0,0 +1,44 @@
+import { InnerNode, InnerNodeState } from '../store/inner-node';
+import { IData } from '../interfaces/persistance/data';
+import { Page } from './page';
+
+export interface DataState extends IData, InnerNodeState {}
+
+export class Data extends InnerNode implements IData, DataState {
+ constructor(props: IData, referenceDeserializer: (from: any) => any = e => e) {
+ super(props.pages.map(p => new Page(referenceDeserializer(p), referenceDeserializer)), props.id);
+ }
+
+ get pages(): Array {
+ return this.children as Array;
+ }
+
+ addPage(name: string) {
+ const page = new Page({
+ name,
+ userData: {
+ hideCreateTowerButton: false,
+ defaultDateRange: {
+ from: null,
+ to: null
+ }
+ },
+ towers: []
+ });
+ this.addChildren([page]);
+ page.addTower();
+ }
+
+ removePage(page: Page) {
+ this.changeKeys({
+ children: this.children.filter(c => c !== page)
+ });
+ }
+
+ serialize(referenceSerializer: (ref: object) => any): IData {
+ return {
+ ...super.serialize(referenceSerializer),
+ pages: this.pages.map(referenceSerializer)
+ };
+ }
+}
diff --git a/src/app/model/page.ts b/src/app/model/page.ts
old mode 100644
new mode 100755
index ba12c64..76ea7d3
--- a/src/app/model/page.ts
+++ b/src/app/model/page.ts
@@ -1,30 +1,41 @@
-import { Serializable } from './serializable';
import { IPage } from '../interfaces/persistance/page';
+import { Range } from '../interfaces/range';
import { Tower } from './tower';
-import { Node } from '../store/node';
+import { InnerNode, InnerNodeState } from '../store/inner-node';
-export class Page extends Serializable implements IPage {
- constructor(parent: Node, props: IPage) {
- super(parent, props, 'Page');
+export interface PageState extends InnerNodeState, IPage {
+ towers: Array;
+}
+
+export class Page extends InnerNode implements IPage, PageState {
+ readonly name: string;
+
+ readonly userData: {
+ hideCreateTowerButton: boolean;
+ defaultDateRange: Range;
+ };
+
+ constructor(props: IPage, referenceDeserializer: (from: any) => any = e => e) {
+ super(props.towers.map(t => new Tower(referenceDeserializer(t), referenceDeserializer)), props.id);
+ this.name = props.name;
+ this.userData = props.userData;
}
- readonly name: string;
get towers(): Array {
return this.children as Array;
}
- readonly userData: {
- hideCreateTowerButton: boolean;
- defaultDateRange: {
- from: Date;
- to: Date;
- };
- };
+ changeProps(props: Partial): this {
+ if (props.hasOwnProperty('towers')) {
+ props.children = props.towers;
+ delete props.towers;
+ }
+ return this.changeKeys(props);
+ }
setHideCreateTowerButton(value: boolean) {
- this.changeKey({
- propertyName: 'userData',
- value: {
+ this.changeProps({
+ userData: {
...this.userData,
hideCreateTowerButton: value
}
@@ -41,9 +52,14 @@ export class Page extends Serializable implements IPage {
towers.splice(previousIndex, 1);
towers.splice(currentIndex, 0, tower);
- this.changeValue({
- oldValue: this.towers,
- newValue: towers
+ this.changeProps({
+ towers
+ });
+ }
+
+ changeName(to: string) {
+ this.changeProps({
+ name: to
});
}
@@ -53,17 +69,27 @@ export class Page extends Serializable implements IPage {
hue = Math.random() * 360;
} while (30 <= hue && hue <= 200);
- new Tower(this, {
- name,
- blocks: [],
- baseColor: { h: hue, s: 100, l: 50 }
- });
+ this.addChildren([
+ new Tower({
+ name,
+ blocks: [],
+ baseColor: { h: hue, s: 100, l: 50 }
+ })
+ ]);
}
removeTower(tower: Tower) {
- this.changeValue({
- oldValue: this.towers,
- newValue: this.towers.filter(t => t !== tower)
+ this.changeProps({
+ towers: this.towers.filter(t => t !== tower)
});
}
+
+ serialize(referenceSerializer: (ref: object) => any): IPage {
+ return {
+ ...super.serialize(referenceSerializer),
+ name: this.name,
+ userData: this.userData,
+ towers: this.towers.map(referenceSerializer)
+ };
+ }
}
diff --git a/src/app/model/serializable.ts b/src/app/model/serializable.ts
deleted file mode 100644
index 8eaae74..0000000
--- a/src/app/model/serializable.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-import { Cloneable } from '../store/cloneable';
-import { Node } from '../store/node';
-
-export class Serializable extends Cloneable {
- protected type: string;
-
- private static propertyList: any = {};
- static childrenMap: {
- [type: string]: {
- childrenConstructor: typeof Serializable;
- childrenListName: string;
- childrenType: string;
- };
- };
-
- protected onAfterClone(): void {
- // pass
- }
-
- protected constructor(parent: Node, properties: any, type: string) {
- super(parent);
-
- const compiledType = this.constructor.name;
- if (!Serializable.propertyList.hasOwnProperty(compiledType)) {
- Serializable.propertyList[compiledType] = [];
- }
- for (const property in properties) {
- if (properties.hasOwnProperty(property)) {
- const propertyValue = properties[property];
- // This should be ran after the original constructor has finished.
- console.log(type);
- if (property === Serializable.childrenMap[type].childrenListName) {
- new Promise(r => r()).then(() => {
- for (let child of propertyValue) {
- new Serializable.childrenMap[type].childrenConstructor(
- this,
- child,
- Serializable.childrenMap[type].childrenType
- );
- }
- });
- } else {
- this[property] = properties[property];
- }
-
- if (!Serializable.propertyList[compiledType].includes(property)) {
- Serializable.propertyList[compiledType].push(property);
- }
- }
- }
- }
-
- toJSON(): object {
- return Serializable.propertyList[this.constructor.name].reduce(
- (object, property) => ({
- [property]: this[property],
- ...object
- }),
- {}
- );
- }
-}
diff --git a/src/app/model/tower.ts b/src/app/model/tower.ts
old mode 100644
new mode 100755
index 7161212..0f85056
--- a/src/app/model/tower.ts
+++ b/src/app/model/tower.ts
@@ -1,40 +1,75 @@
import { ITower } from '../interfaces/persistance/tower';
import { lighten } from '../utils/color';
import { Block } from './block';
-import { Serializable } from './serializable';
import { hash } from '../utils/hash';
-import { Node } from '../store/node';
import { IColor } from '../interfaces/color';
+import { InnerNode, InnerNodeState } from '../store/inner-node';
export type ColoredBlock = Block & { color: IColor };
-export class Tower extends Serializable implements ITower {
- protected type = 'Tower';
+export interface TowerState extends ITower, InnerNodeState {
+ blocks: Array;
+}
+export class Tower extends InnerNode implements ITower, TowerState {
+ readonly name: string;
+ readonly baseColor: IColor;
tags: string[];
- name: string;
+ coloredBlocks: Array;
+
+ constructor(props: ITower, referenceDeserializer: (from: any) => any = e => e) {
+ super(props.blocks.map(b => new Block(referenceDeserializer(b), referenceDeserializer)), props.id);
+ this.name = props.name;
+ this.baseColor = props.baseColor;
+ this.onAfterClone();
+ }
get blocks(): Array {
return this.children as Array;
}
- coloredBlocks: Array;
-
- readonly baseColor: IColor;
-
- constructor(parent: Node, props: ITower) {
- super(parent, props, 'Tower');
- this.onAfterClone();
+ changeKeys(props: Partial): this {
+ if (props.hasOwnProperty('blocks')) {
+ props.children = props.blocks;
+ delete props.blocks;
+ }
+ return super.changeKeys(props);
}
- protected onAfterClone(): void {
+ addBlock(props: { tag: string; description: string; isDone: boolean }) {
+ this.addChildren([
+ new Block({
+ created: new Date(),
+ ...props
+ })
+ ]);
+ }
+
+ changeName(name: string) {
+ this.changeKeys({ name });
+ }
+
+ getColorOfTag(tag: string): IColor {
+ return lighten((hash(tag) - 0.5) * 50, this.baseColor);
+ }
+
+ serialize(referenceSerializer: (ref: object) => any): ITower {
+ return {
+ ...super.serialize(referenceSerializer),
+ name: this.name,
+ baseColor: this.baseColor,
+ blocks: this.blocks.map(referenceSerializer)
+ };
+ }
+
+ protected onAfterClone() {
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 = this.getColorOfTag(b.tag);
return coloredBlock;
});
@@ -45,17 +80,4 @@ 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();
- }
}
diff --git a/src/app/pipes/color.pipe.ts b/src/app/pipes/color.pipe.ts
old mode 100644
new mode 100755
diff --git a/src/app/pipes/format-date.pipe.ts b/src/app/pipes/format-date.pipe.ts
new file mode 100755
index 0000000..96314c4
--- /dev/null
+++ b/src/app/pipes/format-date.pipe.ts
@@ -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';
+ }
+}
diff --git a/src/app/services/api.service.ts b/src/app/services/api.service.ts
new file mode 100755
index 0000000..fd4430f
--- /dev/null
+++ b/src/app/services/api.service.ts
@@ -0,0 +1,53 @@
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpHeaders } from '@angular/common/http';
+import { Unique } from '../store/unique';
+
+const API_URI = 'https://store.schmelczer.dev/api/store/';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class ApiService {
+ constructor(private http: HttpClient) {}
+
+ private static getAuthorizationHeader(id: string): HttpHeaders {
+ return new HttpHeaders().set('Authorization', `life-towers-v3 ${id}`);
+ }
+
+ async track(id: string): Promise {
+ await this.http.post(`${API_URI}me`, {}, { headers: ApiService.getAuthorizationHeader(id) }).toPromise();
+ }
+
+ async register(id: string): Promise {
+ await this.http.post(API_URI, { token: id }).toPromise();
+ }
+
+ async getObject(userId: string, objectId: string): Promise {
+ return await this.http
+ .get(`${API_URI}me/${objectId}`, { headers: ApiService.getAuthorizationHeader(userId) })
+ .toPromise();
+ }
+
+ async postObject(userId: string, objectId: string, serializedObject: string): Promise {
+ await this.http
+ .post(
+ `${API_URI}me/${objectId}`,
+ { data: serializedObject },
+ { headers: ApiService.getAuthorizationHeader(userId) }
+ )
+ .toPromise();
+ }
+
+ async getRootId(userId: string): Promise {
+ return await this.http
+ // @ts-ignore
+ .get(`${API_URI}me/root`, { headers: ApiService.getAuthorizationHeader(userId), responseType: 'text' })
+ .toPromise();
+ }
+
+ async setRootId(userId: string, rootId: string): Promise {
+ await this.http
+ .put(`${API_URI}me/root`, { root_id: rootId }, { headers: ApiService.getAuthorizationHeader(userId) })
+ .toPromise();
+ }
+}
diff --git a/src/app/services/cancel.service.ts b/src/app/services/cancel.service.ts
old mode 100644
new mode 100755
index b159577..e3537ea
--- a/src/app/services/cancel.service.ts
+++ b/src/app/services/cancel.service.ts
@@ -11,8 +11,6 @@ interface Subscriber {
export class CancelService {
private subscribers: Subscriber[] = [];
- constructor() {}
-
subscribe(object: object, callback: () => void) {
this.subscribers.push({
object,
diff --git a/src/app/services/data.service.ts b/src/app/services/data.service.ts
old mode 100644
new mode 100755
index 8c01ba4..7e1dcef
--- a/src/app/services/data.service.ts
+++ b/src/app/services/data.service.ts
@@ -1,86 +1,35 @@
import { Injectable } from '@angular/core';
-import { StoreService } from './store.service';
-import { Page } from '../model/page';
import { Root } from '../store/root';
-import { Serializable } from '../model/serializable';
-import { Tower } from '../model/tower';
-import { Block } from '../model/block';
-import { IPage } from '../interfaces/persistance/page';
-import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
-import { Observable } from 'rxjs/internal/Observable';
+import { MapStoreService } from './map-store.service';
+import { Data } from '../model/data';
@Injectable({
providedIn: 'root'
})
-export class DataService extends Root {
- get pages(): Array {
- return this.children;
- }
-
- private readonly _safeChildren: BehaviorSubject> = new BehaviorSubject(null);
- readonly safeChildren$: Observable> = this._safeChildren.asObservable();
-
- constructor(private storeService: StoreService>) {
+export class DataService extends Root {
+ private shouldSave = true;
+ constructor(private store: MapStoreService) {
super();
- this.init().catch();
- }
- private async init() {
- const pages = await this.storeService.load();
- Serializable.childrenMap = {
- Page: {
- childrenListName: 'towers',
- childrenConstructor: Tower,
- childrenType: 'Tower'
- },
- Tower: {
- childrenListName: 'blocks',
- childrenConstructor: Block,
- childrenType: 'Block'
- },
- Block: {
- childrenListName: null,
- childrenConstructor: null,
- childrenType: null
+ this.store.data.subscribe(d => {
+ if (d) {
+ this.shouldSave = false;
+ this.changeKeys({ children: [d] });
}
- };
-
- for (let page of pages) {
- new Page(this, page);
- }
- setTimeout(() => {
- this.children$.subscribe(value => {
- this.log();
- });
- }, 0);
-
- this.children$.subscribe(value => {
- this._safeChildren.next(value);
- this.save(0);
});
- }
- mutatedUpdate() {
- this.save(2500);
- }
-
- save(timeout: number) {
- this.storeService.scheduleSave(this.pages, timeout);
- }
-
- addPage(name: string) {
- const page = new Page(this, {
- name,
- userData: {},
- towers: []
+ this.children$.subscribe(_ => {
+ this.log();
});
- page.addTower();
- }
- removePage(page: Page) {
- this.changeValue({
- oldValue: this.children,
- newValue: this.children.filter(c => c !== page)
+ this.children$.subscribe(data => {
+ if (data && data.length && data[0]) {
+ if (!this.shouldSave) {
+ this.shouldSave = true;
+ return;
+ }
+ this.store.save(data[0]);
+ }
});
}
}
diff --git a/src/app/services/map-store.service.ts b/src/app/services/map-store.service.ts
new file mode 100755
index 0000000..322806c
--- /dev/null
+++ b/src/app/services/map-store.service.ts
@@ -0,0 +1,191 @@
+import { Injectable } from '@angular/core';
+import * as uuid from 'uuid';
+import { Data } from '../model/data';
+import { ITower } from '../interfaces/persistance/tower';
+import { IPage } from '../interfaces/persistance/page';
+import { IData } from '../interfaces/persistance/data';
+import { IUnique } from '../interfaces/persistance/unique';
+import { ApiService } from './api.service';
+import { Unique } from '../store/unique';
+import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
+import { Observable } from 'rxjs/internal/Observable';
+
+const LOCAL_STORAGE_KEY = 'life-towers.data.v.3';
+
+interface LifeTowersData {
+ token: string;
+ root: string;
+ objects: {
+ [id: string]: IUnique;
+ };
+}
+
+@Injectable({
+ providedIn: 'root'
+})
+export class MapStoreService {
+ private state: LifeTowersData;
+ private canSaveTrigger: () => void;
+ private canSave = new Promise(r => (this.canSaveTrigger = r));
+ private dataToSave: Data;
+
+ private saveEverything = false;
+
+ private readonly _data: BehaviorSubject = new BehaviorSubject(null);
+ readonly data: Observable = this._data.asObservable();
+
+ constructor(private api: ApiService) {
+ const storedData: string = localStorage.getItem(LOCAL_STORAGE_KEY);
+
+ if (storedData) {
+ this.state = JSON.parse(storedData);
+ this.initWithLoad().catch();
+ } else {
+ this.initWithRegister().catch();
+ }
+ this.api.track(this.state.token).catch();
+ }
+
+ private static getSeed(): LifeTowersData {
+ const towerId = uuid.v4();
+ const tower: ITower = {
+ id: towerId,
+ name: null,
+ blocks: [],
+ baseColor: { h: 0, s: 100, l: 50 }
+ };
+
+ const pageId = uuid.v4();
+ const page: IPage = {
+ id: pageId,
+ name: 'My first page',
+ towers: [towerId],
+ userData: {
+ hideCreateTowerButton: false,
+ defaultDateRange: {
+ from: null,
+ to: null
+ }
+ }
+ };
+
+ const dataId = uuid.v4();
+ const data: IData = {
+ id: dataId,
+ pages: [pageId]
+ };
+
+ return {
+ token: uuid.v4(),
+ root: dataId,
+ objects: {
+ [dataId]: data,
+ [pageId]: page,
+ [towerId]: tower
+ }
+ };
+ }
+
+ save(root: Data) {
+ this.dataToSave = root;
+
+ setTimeout(() => {
+ if (this.dataToSave === root) {
+ this.realSave(root).catch();
+ }
+ }, 750);
+ }
+
+ private get root(): Data {
+ return new Data(this.state.objects[this.state.root] as IData, id => this.state.objects[id]);
+ }
+
+ get userToken(): string {
+ return this.state.token;
+ }
+
+ set userToken(value: string) {
+ this.state.token = value;
+ this.initWithLoad().catch();
+ }
+
+ private async realSave(root: Data): Promise {
+ await this.canSave;
+
+ const waiting: Array = [root];
+ const referenceSerializer = (e: Unique): string => {
+ waiting.push(e);
+ return e.id;
+ };
+
+ while (waiting.length > 0) {
+ const candidate = waiting.pop();
+ if (!this.saveEverything && this.state.objects.hasOwnProperty(candidate.id)) {
+ continue;
+ }
+
+ const serialized = candidate.serialize(referenceSerializer);
+
+ this.state.objects[candidate.id] = serialized;
+ this.api.postObject(this.state.token, candidate.id, JSON.stringify(serialized)).catch();
+ }
+
+ this.api.setRootId(this.state.token, root.id).catch();
+ this.state.root = root.id;
+
+ localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(this.state));
+ }
+
+ private async initWithRegister(id?: string) {
+ this.state = MapStoreService.getSeed();
+ if (id) {
+ this.state.token = id;
+ }
+ this._data.next(this.root);
+
+ await this.api.register(this.state.token).catch();
+ this.canSaveTrigger();
+
+ this.saveEverything = true;
+ await this.realSave(this.root);
+ this.saveEverything = false;
+ }
+
+ private async initWithLoad() {
+ this.canSave = new Promise(r => (this.canSaveTrigger = r));
+
+ let realRoot: string;
+ try {
+ realRoot = await this.api.getRootId(this.state.token).catch();
+ } catch {
+ this.initWithRegister(this.state.token).catch();
+ return;
+ }
+
+ if (this.state.root !== realRoot) {
+ this.state.root = realRoot;
+ const root = await this.api.getObject(this.state.token, realRoot);
+ this.state.objects[this.state.root] = root;
+
+ const getUnknowns = async (element: any) => {
+ const childrenAliases = ['pages', 'towers', 'blocks'];
+
+ for (const childrenAlias of childrenAliases) {
+ if (element.hasOwnProperty(childrenAlias)) {
+ for (const p of element[childrenAlias]) {
+ if (!this.state.objects.hasOwnProperty(p)) {
+ const unknown = await this.api.getObject(this.state.token, p);
+ this.state.objects[p] = unknown;
+ await getUnknowns(unknown);
+ }
+ }
+ }
+ }
+ };
+
+ await getUnknowns(root);
+ }
+ this._data.next(this.root);
+ this.canSaveTrigger();
+ }
+}
diff --git a/src/app/services/modal.service.ts b/src/app/services/modal.service.ts
old mode 100644
new mode 100755
index 5054bf0..0f1d3e9
--- a/src/app/services/modal.service.ts
+++ b/src/app/services/modal.service.ts
@@ -4,11 +4,11 @@ import { top } from '../utils/top';
import { CancelService } from './cancel.service';
import { Page } from '../model/page';
import { Observable } from 'rxjs/internal/Observable';
+import { Block } from '../model/block';
+import { Data } from '../model/data';
export enum ModalType {
- createBlock,
- editBlock,
- removeBlock,
+ blocks,
settings,
removeTower,
removePage,
@@ -30,24 +30,12 @@ export class ModalService {
constructor(private cancelService: CancelService) {}
- showCreateBlock(input: {
- options: string[];
- isTask: boolean;
- }): Promise<{ selected: string; description: string; isDone: boolean }> {
- return this.createPromiseAndPushToStack(input, ModalType.createBlock);
+ showBlocks(input: { tower$: Observable; onlyDone: boolean; startBlock?: Block }): Promise {
+ return this.createPromiseAndPushToStack(input, ModalType.blocks);
}
- showEditBlock(data: {
- default: string;
- options: string[];
- description: string;
- isDone: boolean;
- }): Promise<{ selected: string; description: string; isDone: boolean }> {
- return this.createPromiseAndPushToStack(data, ModalType.editBlock);
- }
-
- showSettings(selectedPage: Observable): Promise {
- return this.createPromiseAndPushToStack(selectedPage, ModalType.settings);
+ showSettings(options: { page$: Observable; data$: Observable }): Promise {
+ return this.createPromiseAndPushToStack(options, ModalType.settings);
}
showRemoveTower(tower: Tower): Promise {
@@ -63,13 +51,17 @@ export class ModalService {
}
submit(output?: any) {
- const { resolve } = this.modalStack.pop();
- resolve(output);
+ const modal = this.modalStack.pop();
+ if (modal) {
+ modal.resolve(output);
+ }
}
cancel() {
- const { reject } = this.modalStack.pop();
- reject();
+ const modal = this.modalStack.pop();
+ if (modal) {
+ modal.reject();
+ }
}
private createPromiseAndPushToStack(input: any, type: ModalType): Promise {
diff --git a/src/app/services/store.service.ts b/src/app/services/store.service.ts
deleted file mode 100644
index a4b1596..0000000
--- a/src/app/services/store.service.ts
+++ /dev/null
@@ -1,124 +0,0 @@
-import { Injectable } from '@angular/core';
-import { Page } from '../model/page';
-import { IPage } from '../interfaces/persistance/page';
-
-const LOCAL_STORAGE_KEY = 'life-towers.data.v.2';
-
-@Injectable({
- providedIn: 'root'
-})
-export class StoreService {
- private saveScheduled = false;
- private dataToSave: T;
- private storedData: T;
- 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',
- isDone: false
- },
- {
- 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',
- isDone: false
- },
- {
- created: new Date(2019, 3, 15),
- tag: 'go to school',
- description: 'done it',
- isDone: true
- },
- {
- created: new Date(2019, 3, 15, 19),
- tag: 'go to school',
- isDone: false
- }
- ]
- },
- {
- baseColor: { h: 180, s: 100, l: 50 },
- name: 'life',
- blocks: [
- {
- created: new Date(2019, 3, 13),
- tag: 'go home',
- description: 'done it',
- isDone: false
- },
- {
- created: new Date(2019, 4, 13),
- tag: 'go home',
- isDone: false
- },
- {
- created: new Date(2019, 4, 15),
- tag: 'go to work',
- description: 'done it',
- isDone: false
- },
- {
- created: new Date(2019, 4, 15, 14),
- tag: 'go to work',
- isDone: false
- }
- ]
- }
- ]
- }
- ]);
-
- constructor() {
- const localStorageData = localStorage.getItem(LOCAL_STORAGE_KEY);
- this.storedData = JSON.parse(localStorageData ? localStorageData : this.mockData) as T;
- }
-
- scheduleSave(data: T, timeout: number) {
- this.dataToSave = data;
- if (!this.saveScheduled) {
- this.saveScheduled = true;
- setTimeout(() => {
- this.saveScheduled = false;
- this.save(this.dataToSave).catch();
- }, timeout);
- }
- }
-
- async load(): Promise {
- console.log('load', this.storedData);
- return this.storedData;
- }
-
- async save(data: T): Promise {
- this.storedData = data;
- const stringified = JSON.stringify(this.storedData, null, 2);
- console.log('save');
- // console.log('save', stringified);
- window.localStorage.setItem(LOCAL_STORAGE_KEY, stringified);
- }
-}
diff --git a/src/app/store/cloneable.ts b/src/app/store/cloneable.ts
deleted file mode 100644
index 317e279..0000000
--- a/src/app/store/cloneable.ts
+++ /dev/null
@@ -1,102 +0,0 @@
-import { InnerNode } from './inner-node';
-import { Node } from './node';
-
-export abstract class Cloneable extends InnerNode {
- protected constructor(parent: Node) {
- super(parent);
- }
-
- protected abstract onAfterClone(): void;
-
- protected cloneWithMap(map: (node: this) => void): this {
- const insides = Object.getOwnPropertyDescriptors(this);
-
- const insidesProxy = new Proxy(insides, {
- get: (target, prop, proxy) => {
- if (prop == '__target__') {
- return target;
- }
- if (target.hasOwnProperty(prop)) {
- const value = target[prop as string].value;
- if (typeof value === 'function') {
- return value.bind(proxy);
- }
- return value;
- } else if (target.prototype.hasOwnProperty(prop)) {
- const value = target.prototype[prop];
- if (typeof value === 'function') {
- return value.bind(proxy);
- }
- return value;
- }
- },
- set: (target, prop, value) => {
- return (target[prop as string].value = value);
- }
- });
- map(insidesProxy);
-
- return this.cloneFromInsides(insidesProxy.__target__);
- }
-
- protected cloneWithAdd({ propertyName, value }: { value: any; propertyName: string }): this {
- if (this[propertyName] === value) {
- return this;
- }
-
- const insides = Object.getOwnPropertyDescriptors(this);
- insides[propertyName].value = value;
- return this.cloneFromInsides(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 {
- if (oldValue === newValue) {
- return this;
- }
-
- const insides = Object.getOwnPropertyDescriptors(this);
-
- let wasMatch = false;
- for (let name in insides) {
- if (insides.hasOwnProperty(name) && insides[name].value === oldValue) {
- insides[name].value = newValue;
- wasMatch = true;
- }
- }
-
- if (!wasMatch) {
- throw new TypeError(`Object has no property with value: ${oldValue.toString()}`);
- }
-
- 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;
- }
-}
diff --git a/src/app/store/initiable.ts b/src/app/store/initiable.ts
deleted file mode 100644
index f938e57..0000000
--- a/src/app/store/initiable.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export abstract class Initiable {
- protected constructor() {
- this.initiate();
- }
- protected abstract initiate();
-}
diff --git a/src/app/store/inner-node.ts b/src/app/store/inner-node.ts
old mode 100644
new mode 100755
index 6f37af8..e742ab1
--- a/src/app/store/inner-node.ts
+++ b/src/app/store/inner-node.ts
@@ -1,9 +1,19 @@
-import { Node } from './node';
+import { Node, NodeState } from './node';
-export abstract class InnerNode extends Node {
- readonly children: Array = [];
- protected parent: Node;
+export interface InnerNodeState extends NodeState {
+ dummy: any;
+}
+
+export class InnerNode extends Node implements InnerNodeState {
+ readonly dummy = 3;
+ parent: Node;
private nextVersion: this = null;
+ readonly children: Array;
+
+ protected constructor(children: Array = [], id?: string) {
+ super(children, id);
+ this.children = children;
+ }
get latestVersion(): this {
let version;
@@ -13,49 +23,67 @@ export abstract class InnerNode extends Node {
return version;
}
- mutatedUpdate() {
- this.parent.mutatedUpdate();
+ addChildren(children: Array): this {
+ return super.addChildren.call(this.latestVersion, children);
}
- map(map: (a: this) => void) {
- return this.update((self: this) => this.cloneWithMap.call(self, map));
+ replaceChild(update: { oldValue: InnerNode; newValue: InnerNode }): this {
+ return super.replaceChild.call(this.latestVersion, update);
}
- changeKeys(props: { [propertyName: string]: any }): this {
- return this.update((self: this) => this.cloneWithChangedKeys.call(self, props));
- }
-
- addChild(update: { child: InnerNode }) {
- super.addChild.call(this.latestVersion, update);
- }
-
- changeChild(update: { oldValue: InnerNode; newValue: InnerNode }) {
- super.replaceChild.call(this.latestVersion, update);
- }
-
- protected abstract cloneWithMap(map: (a: this) => void): this;
- protected abstract cloneWithChangedKeys(props: { [propertyName: string]: any }): this;
-
- private update(cloneMethod: (self: this) => this): this {
+ changeKeys(props: Partial): this {
if (this.nextVersion !== null) {
- this.latestVersion.update(cloneMethod);
+ this.latestVersion.changeKeys(props);
}
- const clone = cloneMethod(this);
- if (clone === this) {
- return this;
+ let shouldClone = false;
+
+ for (const prop in props) {
+ // @ts-ignore
+ if (props.hasOwnProperty(prop) && props[prop] !== this[prop]) {
+ shouldClone = true;
+ break;
+ }
+ }
+ if (!shouldClone) {
+ return;
}
- for (let child of clone.children) {
- child.parent = clone;
- }
+ const clone = this.cloneWithChangedKeys(props);
+
+ clone.children.forEach(c => (c.parent = clone));
+
+ this.nextVersion = clone;
this.parent.replaceChild({
oldValue: this,
newValue: clone
});
- this.nextVersion = clone;
+ return clone;
+ }
+
+ protected onAfterClone() {}
+
+ protected cloneWithChangedKeys(props: Partial): this {
+ const insides = Object.getOwnPropertyDescriptors(this);
+
+ for (const key in props) {
+ if (props.hasOwnProperty(key)) {
+ if (insides.hasOwnProperty(key)) {
+ insides[key].value = props[key];
+ } else {
+ // @ts-ignore
+ insides[key] = {
+ value: props[key]
+ };
+ }
+ }
+ }
+
+ const clone = Object.create(Object.getPrototypeOf(this), insides);
+ clone.setUniqueness();
+ clone.onAfterClone();
return clone;
}
}
diff --git a/src/app/store/node.ts b/src/app/store/node.ts
old mode 100644
new mode 100755
index 1bee7cd..1abda69
--- a/src/app/store/node.ts
+++ b/src/app/store/node.ts
@@ -1,46 +1,46 @@
-import { InnerNode } from './inner-node';
import { Unique } from './unique';
+import { InnerNode } from './inner-node';
-export abstract class Node extends Unique {
- readonly children: Array;
- // TODO: fix types.
- protected abstract changeKeys(props: any): this;
- abstract mutatedUpdate(): void;
+export interface NodeState {
+ children: Array;
+}
- private copyCount = 0;
+export abstract class Node extends Unique implements NodeState {
+ abstract readonly children: Array;
- protected initiate() {
- super.initiate();
- this.copyCount++;
+ protected constructor(children: Array = [], id?: string) {
+ super(id);
+ children.forEach(c => (c.parent = this));
}
+ protected abstract changeKeys(props: Partial): this;
- addChild({ child }: { child: InnerNode }) {
- this.changeKeys({
- children: [...this.children, child]
+ addChildren(children: Array): this {
+ return this.changeKeys({
+ children: [...this.children, ...children]
});
}
- replaceChild({ oldValue, newValue }: { oldValue: InnerNode; newValue: InnerNode }) {
+ replaceChild({ oldValue, newValue }: { oldValue: InnerNode; newValue: InnerNode }): this {
if (oldValue === newValue) {
- return;
+ return this;
}
- this.changeKeys({
+ return this.changeKeys({
children: this.children.map(c => (c === oldValue ? newValue : c))
});
}
protected _log(indent = ''): string {
const basicInfo = `${indent} - ${this.constructor.name}, #${this.id}`;
- let response = `${basicInfo}${' '.repeat(25 - basicInfo.length)}siblings: ${this.copyCount}\n`;
- for (let c of this.children) {
+ let response = `${basicInfo}${' '.repeat(70 - basicInfo.length)}copies: ${this.copies}\n`;
+ for (const c of this.children) {
response += `${c._log(indent + ' ')}`;
}
return response;
}
public log() {
- console.log(this._log());
- console.log(`All in all, there are ${Unique.ObjectCount} objects.`);
+ // console.log(this._log());
+ // console.log(`All in all, there are ${Unique.ObjectCount} objects.`);
}
}
diff --git a/src/app/store/root.ts b/src/app/store/root.ts
old mode 100644
new mode 100755
index 54c62e8..e25023d
--- a/src/app/store/root.ts
+++ b/src/app/store/root.ts
@@ -1,11 +1,17 @@
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { Observable } from 'rxjs/internal/Observable';
-import { Node } from './node';
+import { Node, NodeState } from './node';
import { InnerNode } from './inner-node';
export class Root extends Node {
- private readonly _children: BehaviorSubject> = new BehaviorSubject([]);
- readonly children$: Observable> = this._children.asObservable();
+ readonly children$: Observable>;
+ private readonly _children: BehaviorSubject>;
+
+ constructor(children: Array = []) {
+ super(children);
+ this._children = new BehaviorSubject(children);
+ this.children$ = this._children.asObservable();
+ }
get children(): Array {
return this._children.getValue();
@@ -15,27 +21,14 @@ export class Root extends Node {
this._children.next(value);
}
- mutatedUpdate() {
- // pass
- }
-
- changeValue({ oldValue, newValue }: { oldValue: any; newValue: any }) {
- if (this.children !== oldValue) {
- throw new TypeError('Only children can be changed.');
- }
- this.children = newValue;
- for (let child of this.children) {
- child.parent = this;
- }
- }
-
- changeKey({ propertyName, value }: { propertyName: string; value: any }) {
- if (propertyName !== 'children') {
- throw new TypeError('Only children can be changed.');
- }
- this.children = value;
- for (let child of this.children) {
- child.parent = this;
+ changeKeys(props: Partial): this {
+ if (props.hasOwnProperty('children')) {
+ // @ts-ignore
+ this.children = props.children;
+ for (const child of this.children) {
+ child.parent = this;
+ }
}
+ return this;
}
}
diff --git a/src/app/store/unique.ts b/src/app/store/unique.ts
old mode 100644
new mode 100755
index a8228e4..111a01e
--- a/src/app/store/unique.ts
+++ b/src/app/store/unique.ts
@@ -1,17 +1,45 @@
-import { Initiable } from './initiable';
+import * as uuid from 'uuid';
+import { ISerializable } from '../interfaces/serializable';
+import { IUnique } from '../interfaces/persistance/unique';
-export abstract class Unique extends Initiable {
- protected static nextId = 0;
- static get ObjectCount(): number {
- return Unique.nextId;
+export class Unique implements ISerializable, IUnique {
+ private static count = 0;
+
+ constructor(id?: string) {
+ if (id) {
+ this._id = id;
+ // console.log('got id ' + id);
+ } else {
+ this.setUniqueness();
+ // console.log('unique ' + this.id);
+ }
}
- get id(): number {
+ static get ObjectCount(): number {
+ return Unique.count;
+ }
+
+ private _id: string;
+
+ get id(): string {
return this._id;
}
- private _id: number;
- protected initiate() {
- this._id = Unique.nextId++;
+ private _copies = 0;
+
+ get copies(): number {
+ return this._copies;
+ }
+
+ protected setUniqueness() {
+ this._id = uuid.v4();
+ Unique.count++;
+ this._copies++;
+ }
+
+ serialize(referenceSerializer: (ref: object) => any): object {
+ return {
+ id: this.id
+ };
}
}
diff --git a/src/app/utils/color.ts b/src/app/utils/color.ts
old mode 100644
new mode 100755
diff --git a/src/app/utils/hash.ts b/src/app/utils/hash.ts
old mode 100644
new mode 100755
index ae2412b..dc783e3
--- a/src/app/utils/hash.ts
+++ b/src/app/utils/hash.ts
@@ -4,8 +4,7 @@ export const hash = (text: string): number => {
return 0;
}
const hashValue = Array.prototype.reduce.call(
- // tslint:disable-next-line:no-bitwise
- text,
+ text, // tslint:disable-next-line:no-bitwise
(value, char) => ((value << 5) - value + (char.charCodeAt(0) as number)) | 0,
7
);
diff --git a/src/app/utils/range.ts b/src/app/utils/range.ts
old mode 100644
new mode 100755
index 5990ba9..023f8be
--- a/src/app/utils/range.ts
+++ b/src/app/utils/range.ts
@@ -1,7 +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);
+ for (let i = min; i < max; yield i, i += step) {}
}
};
};
diff --git a/src/app/utils/top.ts b/src/app/utils/top.ts
old mode 100644
new mode 100755
diff --git a/src/assets/.gitkeep b/src/assets/.gitkeep
old mode 100644
new mode 100755
diff --git a/src/assets/arrow.svg b/src/assets/arrow.svg
old mode 100644
new mode 100755
diff --git a/src/assets/pen.svg b/src/assets/pen.svg
new file mode 100755
index 0000000..d798204
--- /dev/null
+++ b/src/assets/pen.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/src/assets/plus-sign.svg b/src/assets/plus-sign.svg
old mode 100644
new mode 100755
diff --git a/src/assets/trash.svg b/src/assets/trash.svg
old mode 100644
new mode 100755
diff --git a/src/assets/x-sign.svg b/src/assets/x-sign.svg
old mode 100644
new mode 100755
diff --git a/src/browserslist b/src/browserslist
old mode 100644
new mode 100755
diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts
old mode 100644
new mode 100755
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
old mode 100644
new mode 100755
diff --git a/src/favicon.ico b/src/favicon.ico
old mode 100644
new mode 100755
diff --git a/src/index.html b/src/index.html
old mode 100644
new mode 100755
diff --git a/src/karma.conf.js b/src/karma.conf.js
old mode 100644
new mode 100755
diff --git a/src/library/animations.scss b/src/library/animations.scss
old mode 100644
new mode 100755
diff --git a/src/library/common-variables.scss b/src/library/common-variables.scss
old mode 100644
new mode 100755
index c09ae5c..bf52ff8
--- a/src/library/common-variables.scss
+++ b/src/library/common-variables.scss
@@ -1,8 +1,9 @@
$accent-color: #a2666f;
-$text-color: #5d576bff;
+$text-color: #5d576b;
$light-color: #ffffff;
-$background-gradient: linear-gradient(90deg, #fff9e077 0, #ffd6d677 100%);
+$background-gradient: linear-gradient(90deg, #fff9e07f 0, #ffd6d67f 100%);
+$background-gradient-opaque: linear-gradient(90deg, #fffcf0 0, #ffebeb 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);
diff --git a/src/library/forms.scss b/src/library/forms.scss
old mode 100644
new mode 100755
index 0e730a1..43df3cf
--- a/src/library/forms.scss
+++ b/src/library/forms.scss
@@ -3,6 +3,11 @@
textarea {
@include normal-text();
+
+ &:disabled {
+ background-color: $light-color;
+ }
+
display: block;
width: 100%;
height: 150px;
diff --git a/src/library/main.scss b/src/library/main.scss
old mode 100644
new mode 100755
index 47bddb0..0156b7f
--- a/src/library/main.scss
+++ b/src/library/main.scss
@@ -48,6 +48,7 @@ img {
*::-webkit-scrollbar {
width: 4px;
+ height: 4px;
}
*::-webkit-scrollbar-track {
diff --git a/src/library/spacing.scss b/src/library/spacing.scss
old mode 100644
new mode 100755
diff --git a/src/library/text.scss b/src/library/text.scss
old mode 100644
new mode 100755
diff --git a/src/library/utils.scss b/src/library/utils.scss
old mode 100644
new mode 100755
diff --git a/src/main.ts b/src/main.ts
old mode 100644
new mode 100755
diff --git a/src/polyfills.ts b/src/polyfills.ts
old mode 100644
new mode 100755
diff --git a/src/styles.scss b/src/styles.scss
old mode 100644
new mode 100755
index 36451a1..d03e6c9
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -21,13 +21,14 @@ $line-height: 2px;
}
}
-html,
-body {
+html {
height: 100%;
- background: $background-gradient;
+ background-color: $text-color;
}
body {
+ height: 100%;
+ background: $background-gradient-opaque;
text-align: center;
padding: var(--large-padding);
box-sizing: border-box;
diff --git a/src/test.ts b/src/test.ts
old mode 100644
new mode 100755
diff --git a/src/tsconfig.app.json b/src/tsconfig.app.json
old mode 100644
new mode 100755
diff --git a/src/tsconfig.spec.json b/src/tsconfig.spec.json
old mode 100644
new mode 100755
diff --git a/src/tslint.json b/src/tslint.json
old mode 100644
new mode 100755
diff --git a/tsconfig.json b/tsconfig.json
old mode 100644
new mode 100755
diff --git a/tslint.json b/tslint.json
old mode 100644
new mode 100755
diff --git a/yarn.lock b/yarn.lock
index 2e1e1a5..3593219 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6433,6 +6433,11 @@ uuid@^3.0.1, uuid@^3.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
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:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"