Merge with store
This commit is contained in:
parent
706fe745d3
commit
3ad2766f82
128 changed files with 1185 additions and 0 deletions
53
frontend/src/app/services/api.service.ts
Normal file
53
frontend/src/app/services/api.service.ts
Normal file
|
|
@ -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<void> {
|
||||
await this.http.post(`${API_URI}me`, {}, { headers: ApiService.getAuthorizationHeader(id) }).toPromise();
|
||||
}
|
||||
|
||||
async register(id: string): Promise<void> {
|
||||
await this.http.post(API_URI, { token: id }).toPromise();
|
||||
}
|
||||
|
||||
async getObject(userId: string, objectId: string): Promise<Unique> {
|
||||
return await this.http
|
||||
.get<Unique>(`${API_URI}me/${objectId}`, { headers: ApiService.getAuthorizationHeader(userId) })
|
||||
.toPromise();
|
||||
}
|
||||
|
||||
async postObject(userId: string, objectId: string, serializedObject: string): Promise<void> {
|
||||
await this.http
|
||||
.post(
|
||||
`${API_URI}me/${objectId}`,
|
||||
{ data: serializedObject },
|
||||
{ headers: ApiService.getAuthorizationHeader(userId) }
|
||||
)
|
||||
.toPromise();
|
||||
}
|
||||
|
||||
async getRootId(userId: string): Promise<string> {
|
||||
return await this.http
|
||||
// @ts-ignore
|
||||
.get<string>(`${API_URI}me/root`, { headers: ApiService.getAuthorizationHeader(userId), responseType: 'text' })
|
||||
.toPromise();
|
||||
}
|
||||
|
||||
async setRootId(userId: string, rootId: string): Promise<void> {
|
||||
await this.http
|
||||
.put(`${API_URI}me/root`, { root_id: rootId }, { headers: ApiService.getAuthorizationHeader(userId) })
|
||||
.toPromise();
|
||||
}
|
||||
}
|
||||
28
frontend/src/app/services/cancel.service.ts
Normal file
28
frontend/src/app/services/cancel.service.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
|
||||
interface Subscriber {
|
||||
callback: () => void;
|
||||
object: object;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CancelService {
|
||||
private subscribers: Subscriber[] = [];
|
||||
|
||||
subscribe(object: object, callback: () => void) {
|
||||
this.subscribers.push({
|
||||
object,
|
||||
callback
|
||||
});
|
||||
}
|
||||
|
||||
cancelAllExcept(except: object) {
|
||||
this.subscribers.filter(s => s.object !== except).map(s => s.callback());
|
||||
}
|
||||
|
||||
cancelAll() {
|
||||
this.subscribers.map(s => s.callback());
|
||||
}
|
||||
}
|
||||
35
frontend/src/app/services/data.service.ts
Normal file
35
frontend/src/app/services/data.service.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Root } from '../store/root';
|
||||
import { MapStoreService } from './map-store.service';
|
||||
import { Data } from '../model/data';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class DataService extends Root<Data> {
|
||||
private shouldSave = true;
|
||||
constructor(private store: MapStoreService) {
|
||||
super();
|
||||
|
||||
this.store.data.subscribe(d => {
|
||||
if (d) {
|
||||
this.shouldSave = false;
|
||||
this.changeKeys({ children: [d] });
|
||||
}
|
||||
});
|
||||
|
||||
this.children$.subscribe(_ => {
|
||||
this.log();
|
||||
});
|
||||
|
||||
this.children$.subscribe(data => {
|
||||
if (data && data.length && data[0]) {
|
||||
if (!this.shouldSave) {
|
||||
this.shouldSave = true;
|
||||
return;
|
||||
}
|
||||
this.store.save(data[0]);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
191
frontend/src/app/services/map-store.service.ts
Normal file
191
frontend/src/app/services/map-store.service.ts
Normal file
|
|
@ -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<Data> = new BehaviorSubject(null);
|
||||
readonly data: Observable<Data> = 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<void> {
|
||||
await this.canSave;
|
||||
|
||||
const waiting: Array<Unique> = [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();
|
||||
}
|
||||
}
|
||||
85
frontend/src/app/services/modal.service.ts
Normal file
85
frontend/src/app/services/modal.service.ts
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Tower } from '../model/tower';
|
||||
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 {
|
||||
blocks,
|
||||
settings,
|
||||
removeTower,
|
||||
removePage,
|
||||
getStarted
|
||||
}
|
||||
|
||||
interface Modal {
|
||||
type: ModalType;
|
||||
input: any;
|
||||
resolve: (output: any) => void;
|
||||
reject: () => void;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ModalService {
|
||||
private modalStack: Modal[] = [];
|
||||
|
||||
constructor(private cancelService: CancelService) {}
|
||||
|
||||
showBlocks(input: { tower$: Observable<Tower>; onlyDone: boolean; startBlock?: Block }): Promise<void> {
|
||||
return this.createPromiseAndPushToStack(input, ModalType.blocks);
|
||||
}
|
||||
|
||||
showSettings(options: { page$: Observable<Page>; data$: Observable<Data> }): Promise<void> {
|
||||
return this.createPromiseAndPushToStack(options, ModalType.settings);
|
||||
}
|
||||
|
||||
showRemoveTower(tower: Tower): Promise<void> {
|
||||
return this.createPromiseAndPushToStack(tower, ModalType.removeTower);
|
||||
}
|
||||
|
||||
showRemovePage(name: string): Promise<void> {
|
||||
return this.createPromiseAndPushToStack(name, ModalType.removePage);
|
||||
}
|
||||
|
||||
get active(): Modal {
|
||||
return top(this.modalStack);
|
||||
}
|
||||
|
||||
submit(output?: any) {
|
||||
const modal = this.modalStack.pop();
|
||||
if (modal) {
|
||||
modal.resolve(output);
|
||||
}
|
||||
}
|
||||
|
||||
cancel() {
|
||||
const modal = this.modalStack.pop();
|
||||
if (modal) {
|
||||
modal.reject();
|
||||
}
|
||||
}
|
||||
|
||||
private createPromiseAndPushToStack(input: any, type: ModalType): Promise<any> {
|
||||
this.cancelService.cancelAll();
|
||||
|
||||
const modal = {
|
||||
input,
|
||||
type,
|
||||
resolve: () => {},
|
||||
reject: () => {}
|
||||
};
|
||||
|
||||
const modalPromise = new Promise((resolve, reject) => {
|
||||
modal.resolve = resolve;
|
||||
modal.reject = reject;
|
||||
});
|
||||
|
||||
this.modalStack.push(modal);
|
||||
return modalPromise;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue