diff --git a/package.json b/package.json
index 2680bae..d929e00 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,7 @@
"ng": "ng",
"start": "ng serve",
"build": "ng build",
- "build:prod": "ng build --prod --base-href /life-qa/",
+ "build:prod": "ng build --prod",
"format:fix": "pretty-quick --staged",
"precommit": "run-s format:fix lint",
"format:check": "prettier --config ./.prettierrc --list-different \"src/{app,environments,assets}/**/*{.ts,.js,.json,.css,.scss}\"",
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 744701e..c741b78 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -21,6 +21,7 @@ import { TasksComponent } from './components/pages/page/tower/tasks/tasks.compon
import { ColorPipe } from './pipes/color.pipe';
import { BlocksComponent } from './components/modal/modals/blocks/blocks.component';
import { FormatDatePipe } from './pipes/format-date.pipe';
+import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [
@@ -43,7 +44,7 @@ import { FormatDatePipe } from './pipes/format-date.pipe';
BlocksComponent,
FormatDatePipe
],
- imports: [BrowserModule, AppRoutingModule, FormsModule, BrowserAnimationsModule, DragDropModule],
+ imports: [BrowserModule, AppRoutingModule, FormsModule, BrowserAnimationsModule, DragDropModule, HttpClientModule],
providers: [],
bootstrap: [AppComponent]
})
diff --git a/src/app/components/modal/modals/settings/settings.component.html b/src/app/components/modal/modals/settings/settings.component.html
index 72f00b5..cd67f6c 100644
--- a/src/app/components/modal/modals/settings/settings.component.html
+++ b/src/app/components/modal/modals/settings/settings.component.html
@@ -14,4 +14,8 @@
There can be a maximum of 5 towers on each page.
+
+
+
+
diff --git a/src/app/components/modal/modals/settings/settings.component.scss b/src/app/components/modal/modals/settings/settings.component.scss
index 4c05a7d..2bb765b 100644
--- a/src/app/components/modal/modals/settings/settings.component.scss
+++ b/src/app/components/modal/modals/settings/settings.component.scss
@@ -31,4 +31,12 @@
p {
font-size: var(--medium-font-size);
}
+
+ input[type='text'] {
+ text-align: center;
+ }
+
+ button {
+ display: block;
+ }
}
diff --git a/src/app/components/modal/modals/settings/settings.component.ts b/src/app/components/modal/modals/settings/settings.component.ts
index b39886c..a460c82 100644
--- a/src/app/components/modal/modals/settings/settings.component.ts
+++ b/src/app/components/modal/modals/settings/settings.component.ts
@@ -1,28 +1,56 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnDestroy, OnInit } from '@angular/core';
import { ModalService } from '../../../../services/modal.service';
import { DataService } from '../../../../services/data.service';
import { Page } from '../../../../model/page';
+import { Data } from '../../../../model/data';
+import { Subscription } from 'rxjs/internal/Subscription';
+import { MapStoreService } from '../../../../services/map-store.service';
@Component({
selector: 'app-settings',
templateUrl: './settings.component.html',
styleUrls: ['./settings.component.scss']
})
-export class SettingsComponent implements OnInit {
+export class SettingsComponent implements OnInit, OnDestroy {
+ data: Data;
page: Page;
- constructor(public modalService: ModalService, public dataService: DataService) {}
+
+ private dataSubscription: Subscription;
+ private pageSubscription: Subscription;
+
+ token: string;
+
+ constructor(public modalService: ModalService, private store: MapStoreService) {
+ this.token = store.userToken;
+ }
ngOnInit() {
- this.modalService.active.input.subscribe(p => (this.page = p));
+ const { data$, page$ } = this.modalService.active.input;
+
+ this.dataSubscription = data$.subscribe(d => (this.data = d));
+ this.pageSubscription = page$.subscribe(p => (this.page = p));
}
async deletePage() {
try {
await this.modalService.showRemovePage(this.page.name);
- this.dataService.removePage(this.page);
+ this.data.removePage(this.page);
this.modalService.submit();
} catch {
// pass
}
}
+
+ setNewToken() {
+ this.store.userToken = this.token;
+ }
+
+ ngOnDestroy() {
+ if (this.dataSubscription) {
+ this.dataSubscription.unsubscribe();
+ }
+ if (this.pageSubscription) {
+ this.pageSubscription.unsubscribe();
+ }
+ }
}
diff --git a/src/app/components/pages/page/tower/tower.component.ts b/src/app/components/pages/page/tower/tower.component.ts
index e7717db..2a705d3 100644
--- a/src/app/components/pages/page/tower/tower.component.ts
+++ b/src/app/components/pages/page/tower/tower.component.ts
@@ -36,9 +36,7 @@ export class TowerComponent implements OnInit {
return this.styledBlocks.filter(b => b.shouldDraw);
}
- public constructor(private modalService: ModalService, private changeDetection: ChangeDetectorRef) {
- console.log('oo');
- }
+ public constructor(private modalService: ModalService, private changeDetection: ChangeDetectorRef) {}
ngOnInit() {
this.tower$.subscribe(value => {
diff --git a/src/app/components/pages/pages.component.ts b/src/app/components/pages/pages.component.ts
index 30f0312..8fdc4eb 100644
--- a/src/app/components/pages/pages.component.ts
+++ b/src/app/components/pages/pages.component.ts
@@ -4,6 +4,8 @@ import { DataService } from '../../services/data.service';
import { ModalService } from '../../services/modal.service';
import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject';
import { Observable } from 'rxjs/internal/Observable';
+import { Data } from '../../model/data';
+import { of } from 'rxjs/internal/observable/of';
const USER_DATA_KEY = 'life-towers.user-data.v.2';
@@ -17,6 +19,7 @@ export class PagesComponent implements OnInit {
@ViewChild('page') page: ElementRef;
@ViewChild('bottom') bottom: ElementRef;
+ data: Data;
pages: Array;
isDragHappening = false;
@@ -44,8 +47,10 @@ export class PagesComponent implements OnInit {
}
ngOnInit() {
- this.dataService.children$.subscribe(pages => {
- if (pages) {
+ this.dataService.children$.subscribe(dataContainer => {
+ if (dataContainer && dataContainer.length > 0) {
+ this.data = dataContainer[0];
+ const pages = this.data.pages;
if (this.pages && !pages.includes(this._selectedPage.getValue().latestVersion)) {
this.selectedPageName = null;
}
@@ -82,7 +87,7 @@ export class PagesComponent implements OnInit {
if (this.pages && name) {
if (!this.pageNames.includes(name)) {
- this.dataService.addPage(name);
+ this.data.addPage(name);
}
const index = this.pageNames.indexOf(name);
@@ -95,7 +100,10 @@ export class PagesComponent implements OnInit {
async openSettings() {
try {
- await this.modalService.showSettings(this.selectedPage$);
+ await this.modalService.showSettings({
+ page$: this.selectedPage$,
+ data$: of(this.data)
+ });
} catch {
// pass
} finally {
diff --git a/src/app/interfaces/persistance/block.ts b/src/app/interfaces/persistance/block.ts
index 5021ea3..8cf78d1 100644
--- 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 100644
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
index 75d9bf7..a096fae 100644
--- 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
index 4776161..05d0a51 100644
--- 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 100644
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/serializable.ts b/src/app/interfaces/serializable.ts
new file mode 100644
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
index 74ba9ee..e54cc32 100644
--- a/src/app/model/block.ts
+++ b/src/app/model/block.ts
@@ -1,23 +1,36 @@
-import { Serializable } from './serializable';
import { IBlock } from '../interfaces/persistance/block';
-import { InnerNodeState } from '../store/inner-node';
+import { InnerNode, InnerNodeState } from '../store/inner-node';
export interface BlockState extends IBlock, InnerNodeState {}
-export class Block extends Serializable implements IBlock, BlockState {
- readonly created: Date;
- readonly 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) {
+ constructor(props: IBlock, referenceDeserializer: (from: any) => any = e => e) {
+ super([], props.id);
if (props.created.constructor.name !== 'Date') {
props.created = new Date(props.created);
}
- super(props, 'Block');
+ 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 100644
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
index 142e4f4..76ea7d3 100644
--- a/src/app/model/page.ts
+++ b/src/app/model/page.ts
@@ -1,25 +1,24 @@
-import { Serializable } from './serializable';
import { IPage } from '../interfaces/persistance/page';
+import { Range } from '../interfaces/range';
import { Tower } from './tower';
-import { InnerNodeState } from '../store/inner-node';
+import { InnerNode, InnerNodeState } from '../store/inner-node';
export interface PageState extends InnerNodeState, IPage {
towers: Array;
}
-export class Page extends Serializable implements IPage, PageState {
+export class Page extends InnerNode implements IPage, PageState {
readonly name: string;
readonly userData: {
hideCreateTowerButton: boolean;
- defaultDateRange: {
- from: Date;
- to: Date;
- };
+ defaultDateRange: Range;
};
- constructor(props: IPage) {
- super(props, 'Page', props.towers.map(t => new Tower(t)));
+ 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;
}
get towers(): Array {
@@ -84,4 +83,13 @@ export class Page extends Serializable implements IPage, PageState {
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 e44de18..0000000
--- a/src/app/model/serializable.ts
+++ /dev/null
@@ -1,44 +0,0 @@
-import { InnerNode } from '../store/inner-node';
-
-export class Serializable extends InnerNode {
- static childrenMap: {
- [type: string]: {
- childrenConstructor: typeof Serializable;
- childrenListName: string;
- childrenType: string;
- };
- };
-
- private static propertyList: any = {};
- protected type: string;
-
- protected constructor(properties: any, type: string, children: Array = []) {
- super(children);
-
- const compiledType = this.constructor.name;
- if (!Serializable.propertyList.hasOwnProperty(compiledType)) {
- Serializable.propertyList[compiledType] = [];
- }
- for (const property in properties) {
- if (properties.hasOwnProperty(property)) {
- if (property !== Serializable.childrenMap[type].childrenListName) {
- 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
index f40d76d..0f85056 100644
--- a/src/app/model/tower.ts
+++ b/src/app/model/tower.ts
@@ -1,10 +1,9 @@
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 { IColor } from '../interfaces/color';
-import { InnerNodeState } from '../store/inner-node';
+import { InnerNode, InnerNodeState } from '../store/inner-node';
export type ColoredBlock = Block & { color: IColor };
@@ -12,14 +11,16 @@ export interface TowerState extends ITower, InnerNodeState {
blocks: Array;
}
-export class Tower extends Serializable implements ITower, TowerState {
- tags: string[];
+export class Tower extends InnerNode implements ITower, TowerState {
readonly name: string;
- coloredBlocks: Array;
readonly baseColor: IColor;
+ tags: string[];
+ coloredBlocks: Array;
- constructor(props: ITower) {
- super(props, 'Tower', props.blocks.map(b => new Block(b)));
+ 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();
}
@@ -52,6 +53,15 @@ export class Tower extends Serializable implements ITower, TowerState {
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();
diff --git a/src/app/services/api.service.ts b/src/app/services/api.service.ts
new file mode 100644
index 0000000..d76001f
--- /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://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/data.service.ts b/src/app/services/data.service.ts
index b9da30e..7e1dcef 100644
--- a/src/app/services/data.service.ts
+++ b/src/app/services/data.service.ts
@@ -1,72 +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 { MapStoreService } from './map-store.service';
+import { Data } from '../model/data';
@Injectable({
providedIn: 'root'
})
-export class DataService extends Root {
- constructor(private storeService: StoreService>) {
+export class DataService extends Root {
+ private shouldSave = true;
+ constructor(private store: MapStoreService) {
super();
- this.init().catch();
- }
- get pages(): Array {
- return this.children;
- }
-
- save(timeout: number) {
- this.storeService.scheduleSave(this.pages, timeout);
- }
-
- addPage(name: string) {
- const page = new Page({
- name,
- userData: {},
- towers: []
- });
- this.addChildren([page]);
- page.addTower();
- }
-
- removePage(page: Page) {
- this.changeKeys({
- children: this.children.filter(c => c !== page)
- });
- }
-
- 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] });
}
- };
+ });
+
this.children$.subscribe(_ => {
this.log();
});
- this.addChildren(pages.map(p => new Page(p)));
-
- this.children$.subscribe(_ => {
- this.save(0);
+ 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
index 6858286..322806c 100644
--- a/src/app/services/map-store.service.ts
+++ b/src/app/services/map-store.service.ts
@@ -1,35 +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 {
- readonly storage: {
- [id: number]: T;
- } = {};
+export class MapStoreService {
+ private state: LifeTowersData;
+ private canSaveTrigger: () => void;
+ private canSave = new Promise(r => (this.canSaveTrigger = r));
+ private dataToSave: Data;
- constructor() {
- const localStorageData = localStorage.getItem(LOCAL_STORAGE_KEY);
- if (localStorageData) {
- this.storage = JSON.parse(localStorageData) as {
- [id: number]: T;
+ 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);
+ }
+ }
+ }
+ }
};
- }
- }
- get(id: number) {
- if (this.storage.hasOwnProperty(id)) {
- return this.storage[id];
+ await getUnknowns(root);
}
- }
-
- add(id: number, value: T) {
- if (this.storage.hasOwnProperty(id)) {
- throw new Error('Key already set.');
- }
-
- this.storage[id] = value;
+ this._data.next(this.root);
+ this.canSaveTrigger();
}
}
diff --git a/src/app/services/modal.service.ts b/src/app/services/modal.service.ts
index c376087..0f1d3e9 100644
--- a/src/app/services/modal.service.ts
+++ b/src/app/services/modal.service.ts
@@ -5,6 +5,7 @@ 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 {
blocks,
@@ -33,8 +34,8 @@ export class ModalService {
return this.createPromiseAndPushToStack(input, ModalType.blocks);
}
- 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 {
diff --git a/src/app/services/store.service.ts b/src/app/services/store.service.ts
deleted file mode 100644
index be3c4e0..0000000
--- a/src/app/services/store.service.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import { Injectable } from '@angular/core';
-
-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/inner-node.ts b/src/app/store/inner-node.ts
index e5f6c57..e742ab1 100644
--- a/src/app/store/inner-node.ts
+++ b/src/app/store/inner-node.ts
@@ -10,8 +10,8 @@ export class InnerNode extends Node implements InnerNodeState {
private nextVersion: this = null;
readonly children: Array;
- constructor(children: Array = []) {
- super(children);
+ protected constructor(children: Array = [], id?: string) {
+ super(children, id);
this.children = children;
}
diff --git a/src/app/store/node.ts b/src/app/store/node.ts
index 2dae062..35f0a45 100644
--- a/src/app/store/node.ts
+++ b/src/app/store/node.ts
@@ -8,8 +8,8 @@ export interface NodeState {
export abstract class Node extends Unique implements NodeState {
abstract readonly children: Array;
- protected constructor(children: Array = []) {
- super();
+ protected constructor(children: Array = [], id?: string) {
+ super(id);
children.forEach(c => (c.parent = this));
}
protected abstract changeKeys(props: Partial): this;
@@ -32,7 +32,7 @@ export abstract class Node extends Unique implements NodeState {
protected _log(indent = ''): string {
const basicInfo = `${indent} - ${this.constructor.name}, #${this.id}`;
- let response = `${basicInfo}${' '.repeat(25 - basicInfo.length)}copies: ${this.copies}\n`;
+ let response = `${basicInfo}${' '.repeat(70 - basicInfo.length)}copies: ${this.copies}\n`;
for (const c of this.children) {
response += `${c._log(indent + ' ')}`;
}
diff --git a/src/app/store/store.ts b/src/app/store/store.ts
deleted file mode 100644
index e13f0b0..0000000
--- a/src/app/store/store.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-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];
- }
-}
diff --git a/src/app/store/unique.ts b/src/app/store/unique.ts
index f2c7510..1202c09 100644
--- a/src/app/store/unique.ts
+++ b/src/app/store/unique.ts
@@ -1,18 +1,27 @@
-export class Unique {
- private static nextId = 0;
- private static store;
+import * as uuid from 'uuid';
+import { ISerializable } from '../interfaces/serializable';
+import { IUnique } from '../interfaces/persistance/unique';
- constructor() {
- this.setUniqueness();
+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);
+ }
}
static get ObjectCount(): number {
- return Unique.nextId;
+ return Unique.count;
}
- private _id: number;
+ private _id: string;
- get id(): number {
+ get id(): string {
return this._id;
}
@@ -23,7 +32,14 @@ export class Unique {
}
protected setUniqueness() {
- this._id = Unique.nextId++;
+ this._id = uuid.v4();
+ Unique.count++;
this._copies++;
}
+
+ serialize(referenceSerializer: (ref: object) => any): object {
+ return {
+ id: this.id
+ };
+ }
}