diff --git a/CHANGELOG.md b/CHANGELOG.md index c85f170..4371be3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.3.0 (2020-05-24) + +### Feature +- Allow to get or update context transparently, keeping state and backend in sync + # 1.2.1 (2020-05-03) ### Improvement diff --git a/README.md b/README.md index 66de454..e2e116d 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,16 @@ import { Grange } from 'grange'; constructor(private grange: Grange) {} ``` +### The Grange service itself + +The `grange` service provides handy methods for the most common needs. + +`getContext()`: returns an Observable with the current traversed context. + +`updateContext(changes)`: update the current context with the provided changes. +The update will be performed in the state immediately, and the corresponding PATCH will be done on the backend. +If the backend call fails, a GET call is done to update the state with the actual value. + #### The Traverser service `this.grange.traverser` @@ -247,6 +257,43 @@ Grange uses ngx-schema-form to render any JSON Schema as a dynamic form. The for TO BE COMPLETED +## Guillotina + +Useful Guillotina operations: + +- create an app container for a new project +``` +curl -XPOST --user root:root http://127.0.0.1:8081/db -d '{ + "@type": "Container", + "id": "my-app" +}' +``` +- create a user +``` +curl -XPOST --user root:root http://127.0.0.1:8081/db/site/users -d '{ + "@type": "User", + "id": "inewton", + "username": "inewton", + "name": "Isaac Newton", + "email": "isaac@newton.org", + "password": "inewton", + "user_roles": ["guillotina.Member"] +}' +``` +- give reader access to a user +``` +curl -XPOST --user root:root http://127.0.0.1:8081/db/site/@sharing -d '{ + "prinrole": [ + { + "principal": "inewton", + "role": "guillotina.Reader", + "setting": "Allow" + } + ] +}' +``` + + ## Developers If we want to run Grange into an Anugular project using the GitHub master branches of all the dependencies, we need to use mrs-developer: diff --git a/angular.json b/angular.json index 249976b..72efcde 100644 --- a/angular.json +++ b/angular.json @@ -78,6 +78,7 @@ } ], "styles": [ + "projects/demo/src/pastanaga.scss", "projects/demo/src/styles.scss" ], "scripts": [] diff --git a/g-api/config.yaml b/g-api/config.yaml index 9a23bb0..869da65 100644 --- a/g-api/config.yaml +++ b/g-api/config.yaml @@ -10,6 +10,9 @@ databases: pool_size: 100 store_json: true allow_register: true +jwk: + k: hGplrewNAVxULSApZavdXuxdiR_2Ner73oc-2Z7TVVY + kty: oct cache: driver: guillotina.contrib.redis updates_channel: guillotina @@ -99,6 +102,15 @@ behaviors: title: Text default: Hello contents: + canvas: + title: Canvas + inherited_interface: guillotina.interfaces.IItem + inherited_class: guillotina.content.Item + add_permission: guillotina.AddContent + properties: + points: + type: guillotina.schema.JSONField + title: Points mydoc: title: My Doc inherited_interface: guillotina.interfaces.IFolder diff --git a/package.json b/package.json index 37e317b..953775b 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "test": "jest", "lint": "ng lint", "test:watch": "jest --watch", + "guillotina": "docker-compose -f g-api/docker-compose.yaml up", "get_version": "cat ./projects/grange/package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[\",]//g' | tr -d '[[:space:]]'" }, "private": true, diff --git a/projects/demo/src/app/app.component.ts b/projects/demo/src/app/app.component.ts index a6c17ba..ba90b8d 100644 --- a/projects/demo/src/app/app.component.ts +++ b/projects/demo/src/app/app.component.ts @@ -1,5 +1,6 @@ import { Component } from '@angular/core'; import { GrangeViews, Grange } from '../../../grange/src'; +import { CanvasComponent } from './canvas/canvas.component'; @Component({ selector: 'app-root', @@ -14,6 +15,7 @@ export class AppComponent { ) { this.views.initialize(); this.grange.core.auth.isAuthenticated.subscribe(auth => this.isAuthenticated = auth.state); + this.grange.traverser.addView('view', 'canvas', CanvasComponent); } logout() { diff --git a/projects/demo/src/app/app.module.ts b/projects/demo/src/app/app.module.ts index 9adf982..56e74a1 100644 --- a/projects/demo/src/app/app.module.ts +++ b/projects/demo/src/app/app.module.ts @@ -3,6 +3,7 @@ import { NgModule } from '@angular/core'; import { environment } from '../environments/environment'; import { AppComponent } from './app.component'; +import { CanvasComponent } from './canvas/canvas.component'; import { GrangeRootModule } from '../../../grange/src'; import { TraversalModule } from 'angular-traversal'; import { StoreModule } from '@ngrx/store'; @@ -12,7 +13,8 @@ import { AngularSvgIconModule } from 'angular-svg-icon'; @NgModule({ declarations: [ - AppComponent + AppComponent, + CanvasComponent, ], imports: [ BrowserModule, diff --git a/projects/demo/src/app/canvas/canvas.component.html b/projects/demo/src/app/canvas/canvas.component.html new file mode 100644 index 0000000..215b5ed --- /dev/null +++ b/projects/demo/src/app/canvas/canvas.component.html @@ -0,0 +1,7 @@ + +
+
+
\ No newline at end of file diff --git a/projects/demo/src/app/canvas/canvas.component.scss b/projects/demo/src/app/canvas/canvas.component.scss new file mode 100644 index 0000000..23a075a --- /dev/null +++ b/projects/demo/src/app/canvas/canvas.component.scss @@ -0,0 +1,12 @@ +.row { + height: 20px; + div { + display: inline-block; + height: 20px; + width: 20px; + background-color: grey; + &.fill { + background-color: pink; + } + } +} \ No newline at end of file diff --git a/projects/demo/src/app/canvas/canvas.component.ts b/projects/demo/src/app/canvas/canvas.component.ts new file mode 100644 index 0000000..2d56772 --- /dev/null +++ b/projects/demo/src/app/canvas/canvas.component.ts @@ -0,0 +1,39 @@ +import { Component, OnInit } from '@angular/core'; +import { Grange } from '../../../../grange/src'; +import { select } from '@ngrx/store'; +import { TraverserSelectors } from '@guillotinaweb/ngx-state-traverser'; +import { map, tap, concatMap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'app-canvas', + templateUrl: 'canvas.component.html', + styleUrls: ['./canvas.component.scss'], +}) + +export class CanvasComponent implements OnInit { + points = this.grange.getContext().pipe( + tap(context => { + this.path = context['@id']; + }), + map(context => context.points || {}), + ); + canvas: Observable = this.points.pipe( + map((points: {[coord: string]: boolean}) => Object.entries(points).reduce((all, [coordStr, value]) => { + const coord = coordStr.split('-').map(x => parseInt(x, 10)); + all[coord[0]][coord[1]] = value; + return all; + }, Array.from(new Array(10), x => Array.from(new Array(10), () => false))) + ), + ); + path?: string; + + constructor(private grange: Grange) { } + + ngOnInit() {} + + toggle(x: number, y: number, current: boolean) { + const coord = `${x}-${y}`; + return this.grange.updateContext({points: {[coord]: !current}}); + } +} diff --git a/projects/demo/src/pastanaga.scss b/projects/demo/src/pastanaga.scss new file mode 100644 index 0000000..4bdee96 --- /dev/null +++ b/projects/demo/src/pastanaga.scss @@ -0,0 +1,7 @@ +@import "~@guillotinaweb/pastanaga-angular/lib/styles/common-reset"; +$font-path: "~@guillotinaweb/pastanaga-angular/lib/assets/fonts"; +@import "~@guillotinaweb/pastanaga-angular/lib/styles/fonts"; + +html, body { + overflow: auto; +} diff --git a/projects/demo/src/styles.scss b/projects/demo/src/styles.scss index 2e62a36..e69de29 100644 --- a/projects/demo/src/styles.scss +++ b/projects/demo/src/styles.scss @@ -1,3 +0,0 @@ -@import "../../../node_modules/@guillotinaweb/pastanaga-angular/lib/styles/common-reset"; -$font-path: "../../../node_modules/@guillotinaweb/pastanaga-angular/lib/assets/fonts"; -@import "../../../node_modules/@guillotinaweb/pastanaga-angular/lib/styles/fonts"; diff --git a/projects/grange/package.json b/projects/grange/package.json index 72e8814..a40230d 100644 --- a/projects/grange/package.json +++ b/projects/grange/package.json @@ -1,6 +1,6 @@ { "name": "@guillotinaweb/grange", - "version": "1.2.1", + "version": "1.3.0", "license": "MIT", "author": { "name": "Eric Brehault", diff --git a/projects/grange/src/lib/grange.service.ts b/projects/grange/src/lib/grange.service.ts index df7918e..f0e7006 100644 --- a/projects/grange/src/lib/grange.service.ts +++ b/projects/grange/src/lib/grange.service.ts @@ -1,9 +1,12 @@ import { Injectable } from '@angular/core'; -import { Store } from '@ngrx/store'; +import { Store, select } from '@ngrx/store'; import { Traverser } from 'angular-traversal'; import { GrangeCore } from '@guillotinaweb/grange-core'; import { GrangeState } from './state/state'; import { PastanagaService } from '@guillotinaweb/pastanaga-angular'; +import { Observable } from 'rxjs'; +import { TraverserSelectors, TraverserActions } from '@guillotinaweb/ngx-state-traverser'; +import { take } from 'rxjs/operators'; @Injectable({ providedIn: 'root' @@ -18,4 +21,17 @@ export class Grange { ) { this.store.dispatch({ type: '[Traversing] Watch'}); } + + getContext(): Observable { + return this.store.pipe(select(TraverserSelectors.getContext)); + } + + updateContext(changes: any) { + this.getContext().pipe( + take(1) + ).subscribe(context => this.store.dispatch(new TraverserActions.UpdateTraverserResource({ + path: this.core.api.getPath(context['@id']), + changes + }))); + } } diff --git a/projects/grange/src/lib/state/effects.ts b/projects/grange/src/lib/state/effects.ts index c3fd376..0b48094 100644 --- a/projects/grange/src/lib/state/effects.ts +++ b/projects/grange/src/lib/state/effects.ts @@ -1,7 +1,11 @@ import { Injectable } from '@angular/core'; import { Actions, Effect, ofType } from '@ngrx/effects'; -import { tap } from 'rxjs/operators'; -import { TraverserActions } from '@guillotinaweb/ngx-state-traverser'; +import { tap, switchMap, take, concatMap, filter, catchError, map } from 'rxjs/operators'; +import { TraverserActions, TraverserSelectors } from '@guillotinaweb/ngx-state-traverser'; +import { select, Store } from '@ngrx/store'; +import { GrangeState } from './state'; +import { GrangeCore } from '@guillotinaweb/grange-core'; +import { EMPTY, of } from 'rxjs'; @Injectable() export class GrangeEffects { @@ -12,7 +16,24 @@ export class GrangeEffects { tap(() => window.scrollTo(0, 0)) ); + @Effect() + updateResource = this.actions.pipe( + ofType(TraverserActions.Types.UpdateTraverserResource), + switchMap(action => this.store.pipe( + select(TraverserSelectors.getObjectByPath(action.payload.path)), + take(1), + filter(resource => !!resource['@id']), + concatMap(resource => this.core.resource.update(resource['@id'], resource).pipe( + map(() => EMPTY), + // if error on update, we traverse the resource in order to put the backend version in our local state + catchError(() => of(new TraverserActions.Traverse(this.core.api.getPath(resource['@id'])))) + )), + )), + ); + constructor( private readonly actions: Actions, + private readonly store: Store, + private core: GrangeCore, ) {} } diff --git a/src/pastanaga-overrides.scss b/src/pastanaga-overrides.scss index 62b1787..8b9f764 100644 --- a/src/pastanaga-overrides.scss +++ b/src/pastanaga-overrides.scss @@ -1,5 +1 @@ $labels-text-transform: none; - -html, body { - overflow: auto; -}