My profile - edit dimensions, new storage service, hydrate state on refresh

This commit is contained in:
Michał Romaszkin 2020-12-13 12:40:03 +01:00
parent a720e80dd9
commit b2c8bd766c
16 changed files with 313 additions and 19 deletions

View File

@ -5163,6 +5163,11 @@
"regexp.prototype.flags": "^1.2.0"
}
},
"deepmerge": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz",
"integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA=="
},
"default-gateway": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz",
@ -8975,6 +8980,15 @@
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
"dev": true
},
"ngrx-store-localstorage": {
"version": "10.1.1",
"resolved": "https://registry.npmjs.org/ngrx-store-localstorage/-/ngrx-store-localstorage-10.1.1.tgz",
"integrity": "sha512-OacpqJMraLrgqr/T3DfyT4T4lqAISZdCPWmYQyHtXWsNgKGid6xL4SgPktJ3vcMbIuuMckOpEFIgIpgBvf8E3g==",
"requires": {
"deepmerge": "^3.2.0",
"tslib": "^2.0.0"
}
},
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",

View File

@ -28,6 +28,7 @@
"@types/papaparse": "^5.0.3",
"d3": "^5.16.0",
"eva-icons": "^1.1.2",
"ngrx-store-localstorage": "^10.1.1",
"papaparse": "^5.2.0",
"rxjs": "~6.5.4",
"tslib": "^2.0.0",

View File

@ -1,12 +1,18 @@
import { createAction, props } from '@ngrx/store';
import { create } from 'domain';
import { Dimension } from '../reducers/dimension.reducers';
import { Category, Dimension } from '../reducers/dimension.reducers';
export const getDimensions = createAction('[Dimension] Get Dimmnsions');
export const getDimensionSuccess = createAction(
'[Dimension] Get Dimensions Success',
props<{ dimensions: Array<Dimension> }>()
);
export const getSelectedDimension = createAction(
'[Dimension] Get Selected Dimension'
);
export const getSelectedDimensionSuccess = createAction(
'[Dimension] Get Selected Dimension Success',
props<{ categories: Array<Category> }>()
);
export const addNewDimension = createAction(
'[Dimension] Add Dimension',
props<{ dimension: Dimension }>()
@ -18,3 +24,10 @@ export const removeDimension = createAction(
'[Dimension] Remove Dimension',
props<{ id: number }>()
);
export const setSelectedDimension = createAction(
'[Dimension] Set Selected Dimension',
props<{ id: number }>()
);
export const clearSelectedDimension = createAction(
'[Dimension] Clear Selected Dimension'
);

View File

@ -1,22 +1,33 @@
import { Injectable } from '@angular/core';
import { Actions, ofType, createEffect } from '@ngrx/effects';
import { State } from '../reducers';
import { Store } from '@ngrx/store';
import { flatMap, catchError, map } from 'rxjs/operators';
import {
flatMap,
catchError,
map,
mergeMap,
withLatestFrom,
concatMap,
} from 'rxjs/operators';
import { EMPTY, of } from 'rxjs';
import {
getDimensions,
getSelectedDimension,
getDimensionSuccess,
addNewDimension,
removeDimension,
getSelectedDimensionSuccess,
} from '../actions/dimension.actions';
import { DimensionsService } from '../services/dimensions.service';
import { addNewDimensionSuccess } from '../actions/dimension.actions';
import { State } from '../reducers';
import { Store } from '@ngrx/store';
@Injectable()
export class DimmensionEffects {
constructor(
private actions$: Actions,
private dimensionService: DimensionsService
private dimensionService: DimensionsService,
private store$: Store<State>
) {}
getDimmensions$ = createEffect(() =>
@ -25,7 +36,6 @@ export class DimmensionEffects {
flatMap(() =>
this.dimensionService.getDimensions().pipe(
map((result) => {
console.log(result);
return getDimensionSuccess({ dimensions: result });
}),
catchError(() => EMPTY)
@ -38,11 +48,35 @@ export class DimmensionEffects {
this.actions$.pipe(
ofType(addNewDimension),
flatMap((action) => {
console.log(action.dimension);
return this.dimensionService
.addDimension(action.dimension)
.pipe(map(() => addNewDimensionSuccess()));
})
)
);
getSelectedDimension$ = createEffect(() =>
this.actions$.pipe(
ofType(getSelectedDimension),
withLatestFrom(
this.store$.select((state) => state.dimension.selectedDimension)
),
concatMap(([_, id]) =>
this.dimensionService.getDimension(id!).pipe(
map((result) => {
return getSelectedDimensionSuccess({ categories: result });
})
)
)
)
);
deleteDimension$ = createEffect(
() =>
this.actions$.pipe(
ofType(removeDimension),
flatMap((action) => this.dimensionService.removeDimension(action.id))
),
{ dispatch: false }
);
}

View File

@ -0,0 +1,37 @@
<div class="editDimension__goBackButtonContainer" (click)="goToMainPage()">
<button nbButton status="info">Wróc do strony głównej</button>
</div>
<form [formGroup]="editDimensionForm" (ngSubmit)="onSubmit()">
<nb-layout>
<nb-layout-column class="editDimension__leftColumn">
<h3>Edytuj wymiar</h3>
<input
fullWidth
nbInput
placeholder="Nazwa wymiaru"
formControlName="name"
class="editDimension__name"
/>
<textarea nbInput fullWidth placeholder="Opis wymiaru"></textarea>
</nb-layout-column>
<nb-layout-column>
<button nbButton outline status="info" class="editDimension__button">
Zapisz
</button>
<button nbButton outline status="danger" class="editDimension__button">
Usuń wymiar
</button>
<nb-card>
<nb-card-header>Kategorie</nb-card-header>
<nb-list>
<nb-list-item
*ngFor="let item of categories.controls; let i = index"
formArrayName="categories"
>
<input nbInput type="text" fullWidth [formControlName]="i" />
</nb-list-item>
</nb-list>
</nb-card>
</nb-layout-column>
</nb-layout>
</form>

View File

@ -0,0 +1,13 @@
.editDimension {
&__leftColumn {
display: flex;
flex-direction: column;
}
&__button {
margin: 30px 10px 30px 0;
}
&__goBackButtonContainer {
display: flex;
justify-content: flex-end;
}
}

View File

@ -0,0 +1,73 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import {
FormBuilder,
FormArray,
Validators,
FormControl,
FormGroup,
} from '@angular/forms';
import { Store } from '@ngrx/store';
import { State } from 'src/app/reducers';
import {
clearSelectedDimension,
getSelectedDimension,
} from 'src/app/actions/dimension.actions';
import {
selectCategories,
selectCurrentDimension,
} from 'src/app/selectors/dimension.selectors';
import { ActivatedRoute, Router } from '@angular/router';
@Component({
selector: 'edit-dimension',
templateUrl: './edit-dimension.component.html',
styleUrls: ['./edit-dimension.component.scss'],
})
export class EditDimensionComponent implements OnInit, OnDestroy {
editDimensionForm = this.fb.group({
name: ['', Validators.required],
categories: this.fb.array([]),
});
constructor(
private fb: FormBuilder,
private store: Store<State>,
private router: Router,
private route: ActivatedRoute
) {}
ngOnInit(): void {
this.store.dispatch(getSelectedDimension());
this.store
.select(selectCurrentDimension)
.subscribe((result) =>
this.editDimensionForm.patchValue({ name: result?.name })
);
this.store.select(selectCategories).subscribe((res) => {
res.forEach((el) => this.addCategory(el.category));
});
}
get name() {
return this.editDimensionForm.get('name') as FormControl;
}
get categories() {
return this.editDimensionForm.get('categories') as FormArray;
}
addCategory(value: string) {
this.categories.push(this.fb.control(value, Validators.required));
}
onSubmit() {}
goToMainPage() {
this.router.navigate(['../'], { relativeTo: this.route });
}
ngOnDestroy() {
this.store.dispatch(clearSelectedDimension());
this.editDimensionForm.reset();
}
}

View File

@ -6,14 +6,16 @@
class="dimension__list"
>
<div>
<span class="subtitle dimension__name">{{ item.name }}</span>
<span class="subtitle dimension__name" (click)="editDimension(i + 1)">{{
item.name
}}</span>
</div>
<div>
<nb-icon
icon="close-outline"
status="danger"
class="dimension__close"
(click)="removeDimension(i)"
(click)="removeDimension(i + 1)"
></nb-icon>
</div>
</nb-list-item>

View File

@ -2,13 +2,17 @@ import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { State } from '../../reducers';
import { getDimensions } from '../../actions/dimension.actions';
import {
getDimensions,
removeDimension,
setSelectedDimension,
} from '../../actions/dimension.actions';
import { Dimension } from 'src/app/reducers/dimension.reducers';
import { removeDimension } from 'src/app/actions/dimension.actions';
import { selectDimensions } from '../../selectors/dimension.selectors';
import { NbDialogService } from '@nebular/theme';
import { AddDimensionDialogComponent } from '../add-dimension-dialog/add-dimension-dialog.component';
import { DeleteDialogComponent } from '../delete-dialog/delete-dialog.component';
import { ActivatedRoute, Router } from '@angular/router';
@Component({
selector: 'manage-dimensions',
@ -18,13 +22,16 @@ import { DeleteDialogComponent } from '../delete-dialog/delete-dialog.component'
export class ManageDimensionsComponent implements OnInit {
constructor(
private store: Store<State>,
private dialogService: NbDialogService
private dialogService: NbDialogService,
private router: Router,
private route: ActivatedRoute
) {}
data$: Observable<Array<Dimension>> = this.store.select(selectDimensions);
ngOnInit(): void {
this.store.dispatch(getDimensions());
this.store.select(selectDimensions).subscribe((res) => console.log(res));
}
removeDimension(id: number) {
@ -38,4 +45,9 @@ export class ManageDimensionsComponent implements OnInit {
openAddDialog(): void {
this.dialogService.open(AddDimensionDialogComponent);
}
editDimension(id: number): void {
this.store.dispatch(setSelectedDimension({ id }));
this.router.navigate([`dimension`], { relativeTo: this.route });
}
}

View File

@ -1,5 +1,6 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { EditDimensionComponent } from './edit-dimension/edit-dimension.component';
import { MainViewComponent } from './main-view/main-view.component';
import { MyProfileComponent } from './my-profile.component';
@ -19,6 +20,10 @@ const routes: Routes = [
(m) => m.DiscussionViewerModule
),
},
{
path: 'dimension',
component: EditDimensionComponent,
},
],
},
];

View File

@ -25,6 +25,7 @@ import { MainViewComponent } from './main-view/main-view.component';
import { DimensionsService } from '../services/dimensions.service';
import { AddDimensionDialogComponent } from './add-dimension-dialog/add-dimension-dialog.component';
import { ReactiveFormsModule } from '@angular/forms';
import { EditDimensionComponent } from './edit-dimension/edit-dimension.component';
@NgModule({
declarations: [
@ -34,6 +35,7 @@ import { ReactiveFormsModule } from '@angular/forms';
ManageDimensionsComponent,
MainViewComponent,
AddDimensionDialogComponent,
EditDimensionComponent,
],
imports: [
CommonModule,

View File

@ -3,22 +3,32 @@ import {
getDimensionSuccess,
addNewDimension,
removeDimension,
setSelectedDimension,
getSelectedDimensionSuccess,
clearSelectedDimension,
} from '../actions/dimension.actions';
import { createEntityAdapter, EntityAdapter, EntityState } from '@ngrx/entity';
export interface Dimension {
name: string;
categories: Array<string>;
id: string;
}
export interface Category {
id: string;
category: string;
}
export interface DimensionsState extends EntityState<Dimension> {
selectedDimension: number | null;
availableCategories: Array<Category>;
}
export const adapter: EntityAdapter<Dimension> = createEntityAdapter<Dimension>();
export const initialState: DimensionsState = adapter.getInitialState({
selectedDimension: null,
availableCategories: [],
});
const _dimensionsReducer = createReducer(
@ -31,7 +41,19 @@ const _dimensionsReducer = createReducer(
),
on(removeDimension, (state, { id }) =>
adapter.removeOne(id.toString(), state)
)
),
on(setSelectedDimension, (state, { id }) => ({
...state,
selectedDimension: id,
})),
on(getSelectedDimensionSuccess, (state, { categories }) => ({
...state,
availableCategories: categories,
})),
on(clearSelectedDimension, (state) => ({
...state,
availableCategories: [],
}))
);
export function dimensionsReducer(

View File

@ -15,6 +15,7 @@ import {
AvailableForumsState,
} from './available-forums.reducers';
import { DimensionsState, dimensionsReducer } from './dimension.reducers';
import { localStorageSync } from 'ngrx-store-localstorage';
export function debug(reducer: ActionReducer<any>): ActionReducer<any> {
return function (state, action) {
@ -43,9 +44,18 @@ export const reducers: ActionReducerMap<State> = {
dimension: dimensionsReducer,
};
export function localStorageSyncReducer(
reducer: ActionReducer<any>
): ActionReducer<any> {
return localStorageSync({
keys: Object.keys(reducers).filter((key) => key !== 'router'),
rehydrate: true,
})(reducer);
}
export const metaReducers: MetaReducer<State>[] = !environment.production
? [debug]
: [];
? [debug, localStorageSyncReducer]
: [localStorageSyncReducer];
export const selectRouter = createFeatureSelector<
State,

View File

@ -6,6 +6,23 @@ export const selectFeature = createFeatureSelector<State, DimensionsState>(
'dimension'
);
const { selectAll } = adapter.getSelectors();
const { selectAll, selectEntities } = adapter.getSelectors();
export const selectDimensions = createSelector(selectFeature, selectAll);
export const selectDimensionsEntities = createSelector(
selectFeature,
selectEntities
);
export const selectDimension = createSelector(
selectFeature,
(state) => state.selectedDimension
);
export const selectCategories = createSelector(
selectFeature,
(state) => state.availableCategories
);
export const selectCurrentDimension = createSelector(
selectDimensionsEntities,
selectDimension,
(entities, id) => entities[id!]
);

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { EMPTY, Observable } from 'rxjs';
import { Dimension } from '../reducers/dimension.reducers';
import { Category, Dimension } from '../reducers/dimension.reducers';
import { map } from 'rxjs/operators';
@Injectable()
@ -14,10 +14,20 @@ export class DimensionsService {
.pipe(map((res) => res.data));
}
getDimension(id: number): Observable<Array<Category>> {
return this.http
.get<any>(`http://127.0.0.1:8000/dimension/${id}`)
.pipe(map((res) => res.data));
}
addDimension(dimension: Dimension): Observable<any> {
const body = JSON.stringify(dimension);
return this.http.post<any>('http://127.0.0.1:8000/dimension/', body, {
responseType: 'json',
});
}
removeDimension(id: number): Observable<any> {
return this.http.delete<any>('http://127.0.0.1:8000/dimension');
}
}

View File

@ -0,0 +1,29 @@
import { Inject, Injectable, InjectionToken } from '@angular/core';
export const BROWSER_STORAGE = new InjectionToken<Storage>('Browser Storage', {
providedIn: 'root',
factory: () => localStorage,
});
@Injectable({
providedIn: 'root',
})
export class BrowserStorageService {
constructor(@Inject(BROWSER_STORAGE) public storage: Storage) {}
get(key: string) {
return this.storage.getItem(key);
}
set(key: string, value: string) {
this.storage.setItem(key, value);
}
remove(key: string) {
this.storage.removeItem(key);
}
clear() {
this.storage.clear();
}
}