mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-05-12 17:04:46 +00:00
[ACA-1430] Initial NgRx setup (#384)
* initial ngrx integration, migrate header * update header tests * update spellcheck rules * fix locked image path for virtual paths
This commit is contained in:
parent
c9cd7ae5c4
commit
a67dd43ad6
@ -4,6 +4,7 @@
|
|||||||
"words": [
|
"words": [
|
||||||
"succes",
|
"succes",
|
||||||
|
|
||||||
|
"ngrx",
|
||||||
"ngstack",
|
"ngstack",
|
||||||
"sidenav",
|
"sidenav",
|
||||||
"injectable",
|
"injectable",
|
||||||
|
20
package-lock.json
generated
20
package-lock.json
generated
@ -445,6 +445,26 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@mat-datetimepicker/moment/-/moment-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@mat-datetimepicker/moment/-/moment-1.0.1.tgz",
|
||||||
"integrity": "sha1-YYUwbd/QeTBlq9XbBjKpQZgjdPQ="
|
"integrity": "sha1-YYUwbd/QeTBlq9XbBjKpQZgjdPQ="
|
||||||
},
|
},
|
||||||
|
"@ngrx/effects": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ngrx/effects/-/effects-5.2.0.tgz",
|
||||||
|
"integrity": "sha1-qnYractv1GRNckoc7NJlyqQrrwk="
|
||||||
|
},
|
||||||
|
"@ngrx/router-store": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ngrx/router-store/-/router-store-5.2.0.tgz",
|
||||||
|
"integrity": "sha1-v0sXTOGaNuuoIR/B3erx41rnQ2g="
|
||||||
|
},
|
||||||
|
"@ngrx/store": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ngrx/store/-/store-5.2.0.tgz",
|
||||||
|
"integrity": "sha1-Yn7XTJzZVGKTBIXZEqVXEXsjkD4="
|
||||||
|
},
|
||||||
|
"@ngrx/store-devtools": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ngrx/store-devtools/-/store-devtools-5.2.0.tgz",
|
||||||
|
"integrity": "sha1-L/+RapqjSTdYJncrNZ27ZLnl1iI="
|
||||||
|
},
|
||||||
"@ngstack/electron": {
|
"@ngstack/electron": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ngstack/electron/-/electron-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ngstack/electron/-/electron-0.1.0.tgz",
|
||||||
|
@ -42,6 +42,10 @@
|
|||||||
"@angular/router": "5.1.1",
|
"@angular/router": "5.1.1",
|
||||||
"@mat-datetimepicker/core": "1.0.1",
|
"@mat-datetimepicker/core": "1.0.1",
|
||||||
"@mat-datetimepicker/moment": "1.0.1",
|
"@mat-datetimepicker/moment": "1.0.1",
|
||||||
|
"@ngrx/effects": "^5.2.0",
|
||||||
|
"@ngrx/router-store": "^5.2.0",
|
||||||
|
"@ngrx/store": "^5.2.0",
|
||||||
|
"@ngrx/store-devtools": "^5.2.0",
|
||||||
"@ngstack/electron": "0.1.0",
|
"@ngstack/electron": "0.1.0",
|
||||||
"@ngx-translate/core": "9.1.1",
|
"@ngx-translate/core": "9.1.1",
|
||||||
"alfresco-js-api": "2.4.0-beta9",
|
"alfresco-js-api": "2.4.0-beta9",
|
||||||
|
@ -30,6 +30,11 @@ import {
|
|||||||
FileModel, UploadService
|
FileModel, UploadService
|
||||||
} from '@alfresco/adf-core';
|
} from '@alfresco/adf-core';
|
||||||
import { ElectronService } from '@ngstack/electron';
|
import { ElectronService } from '@ngstack/electron';
|
||||||
|
import { Store } from '@ngrx/store';
|
||||||
|
import { AcaState } from './store/states/app.state';
|
||||||
|
import { SetHeaderColorAction } from './store/actions/header-color.action';
|
||||||
|
import { SetAppNameAction } from './store/actions/app-name.action';
|
||||||
|
import { SetLogoPathAction } from './store/actions/logo-path.action';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
@ -42,7 +47,8 @@ export class AppComponent implements OnInit {
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
private pageTitle: PageTitleService,
|
private pageTitle: PageTitleService,
|
||||||
preferences: UserPreferencesService,
|
preferences: UserPreferencesService,
|
||||||
config: AppConfigService,
|
private store: Store<AcaState>,
|
||||||
|
private config: AppConfigService,
|
||||||
private electronService: ElectronService,
|
private electronService: ElectronService,
|
||||||
private uploadService: UploadService) {
|
private uploadService: UploadService) {
|
||||||
// TODO: remove once ADF 2.3.0 is out (needs bug fixes)
|
// TODO: remove once ADF 2.3.0 is out (needs bug fixes)
|
||||||
@ -51,6 +57,9 @@ export class AppComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|
||||||
|
this.loadAppSettings();
|
||||||
|
|
||||||
const { router, pageTitle, route } = this;
|
const { router, pageTitle, route } = this;
|
||||||
|
|
||||||
router
|
router
|
||||||
@ -89,4 +98,19 @@ export class AppComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private loadAppSettings() {
|
||||||
|
const headerColor = this.config.get<string>('headerColor');
|
||||||
|
if (headerColor) {
|
||||||
|
this.store.dispatch(new SetHeaderColorAction(headerColor));
|
||||||
|
}
|
||||||
|
const appName = this.config.get<string>('application.name');
|
||||||
|
if (appName) {
|
||||||
|
this.store.dispatch(new SetAppNameAction(appName));
|
||||||
|
}
|
||||||
|
const logoPath = this.config.get<string>('application.logo');
|
||||||
|
if (logoPath) {
|
||||||
|
this.store.dispatch(new SetLogoPathAction(logoPath));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,10 @@ import { TRANSLATION_PROVIDER, CoreModule, AppConfigService } from '@alfresco/ad
|
|||||||
import { ContentModule } from '@alfresco/adf-content-services';
|
import { ContentModule } from '@alfresco/adf-content-services';
|
||||||
import { ElectronModule } from '@ngstack/electron';
|
import { ElectronModule } from '@ngstack/electron';
|
||||||
|
|
||||||
|
import { StoreModule } from '@ngrx/store';
|
||||||
|
import { EffectsModule } from '@ngrx/effects';
|
||||||
|
import { StoreRouterConnectingModule } from '@ngrx/router-store';
|
||||||
|
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
import { APP_ROUTES } from './app.routes';
|
import { APP_ROUTES } from './app.routes';
|
||||||
|
|
||||||
@ -71,6 +75,10 @@ import { SettingsComponent } from './components/settings/settings.component';
|
|||||||
import { HybridAppConfigService } from './common/services/hybrid-app-config.service';
|
import { HybridAppConfigService } from './common/services/hybrid-app-config.service';
|
||||||
import { SortingPreferenceKeyDirective } from './directives/sorting-preference-key.directive';
|
import { SortingPreferenceKeyDirective } from './directives/sorting-preference-key.directive';
|
||||||
|
|
||||||
|
import { INITIAL_STATE } from './store/states/app.state';
|
||||||
|
import { appReducer } from './store/reducers/app.reducer';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
@ -88,7 +96,11 @@ import { SortingPreferenceKeyDirective } from './directives/sorting-preference-k
|
|||||||
MatInputModule,
|
MatInputModule,
|
||||||
CoreModule,
|
CoreModule,
|
||||||
ContentModule,
|
ContentModule,
|
||||||
ElectronModule
|
ElectronModule,
|
||||||
|
|
||||||
|
StoreModule.forRoot({ app: appReducer }, { initialState: INITIAL_STATE }),
|
||||||
|
StoreRouterConnectingModule.forRoot({ stateKey: 'router' }),
|
||||||
|
EffectsModule.forRoot([])
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<adf-toolbar class="app-menu" [style.background-color]="backgroundColor" role="heading" aria-level="1">
|
<adf-toolbar class="app-menu" [style.background-color]="headerColor$ | async" role="heading" aria-level="1">
|
||||||
<adf-toolbar-title>
|
<adf-toolbar-title>
|
||||||
<button
|
<button
|
||||||
title="{{ 'APP.ACTIONS.TOGGLE-SIDENAV' | translate }}"
|
title="{{ 'APP.ACTIONS.TOGGLE-SIDENAV' | translate }}"
|
||||||
@ -8,9 +8,9 @@
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<a class="app-menu__title"
|
<a class="app-menu__title"
|
||||||
title="{{ appName }}"
|
title="{{ appName$ | async }}"
|
||||||
[routerLink]="[ '/' ]">
|
[routerLink]="[ '/' ]">
|
||||||
<img [src]="logo" alt="{{ appName }}" />
|
<img [src]="logo$ | async" alt="{{ appName$ | async }}" />
|
||||||
</a>
|
</a>
|
||||||
</adf-toolbar-title>
|
</adf-toolbar-title>
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||||
import { TestBed } from '@angular/core/testing';
|
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||||
import { RouterTestingModule } from '@angular/router/testing';
|
import { RouterTestingModule } from '@angular/router/testing';
|
||||||
import { AppConfigService, PeopleContentService, TranslationService, TranslationMock } from '@alfresco/adf-core';
|
import { AppConfigService, PeopleContentService, TranslationService, TranslationMock } from '@alfresco/adf-core';
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
@ -32,11 +32,17 @@ import { Observable } from 'rxjs/Rx';
|
|||||||
|
|
||||||
import { HeaderComponent } from './header.component';
|
import { HeaderComponent } from './header.component';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { StoreModule, Store } from '@ngrx/store';
|
||||||
|
import { appReducer } from '../../store/reducers/app.reducer';
|
||||||
|
import { INITIAL_STATE, AcaState } from '../../store/states/app.state';
|
||||||
|
import { SetAppNameAction } from '../../store/actions/app-name.action';
|
||||||
|
import { SetHeaderColorAction } from '../../store/actions/header-color.action';
|
||||||
|
|
||||||
describe('HeaderComponent', () => {
|
describe('HeaderComponent', () => {
|
||||||
let fixture;
|
let fixture: ComponentFixture<HeaderComponent>;
|
||||||
let component;
|
let component: HeaderComponent;
|
||||||
let appConfigService: AppConfigService;
|
let appConfigService: AppConfigService;
|
||||||
|
let store: Store<AcaState>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@ -44,6 +50,7 @@ describe('HeaderComponent', () => {
|
|||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
RouterTestingModule,
|
RouterTestingModule,
|
||||||
TranslateModule.forRoot(),
|
TranslateModule.forRoot(),
|
||||||
|
StoreModule.forRoot({ app: appReducer }, { initialState: INITIAL_STATE }),
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
HeaderComponent
|
HeaderComponent
|
||||||
@ -61,6 +68,10 @@ describe('HeaderComponent', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
store = TestBed.get(Store);
|
||||||
|
store.dispatch(new SetAppNameAction('app-name'));
|
||||||
|
store.dispatch(new SetHeaderColorAction('some-color'));
|
||||||
|
|
||||||
fixture = TestBed.createComponent(HeaderComponent);
|
fixture = TestBed.createComponent(HeaderComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
appConfigService = TestBed.get(AppConfigService);
|
appConfigService = TestBed.get(AppConfigService);
|
||||||
@ -82,11 +93,17 @@ describe('HeaderComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('it should set application name', () => {
|
it('it should set application name', done => {
|
||||||
expect(component.appName).toBe('app-name');
|
component.appName$.subscribe(val => {
|
||||||
|
expect(val).toBe('app-name');
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('it should set header background color', () => {
|
it('it should set header background color', done => {
|
||||||
expect(component.backgroundColor).toBe('some-color');
|
component.headerColor$.subscribe(val => {
|
||||||
|
expect(val).toBe('some-color');
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -23,9 +23,11 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { DomSanitizer } from '@angular/platform-browser';
|
import { Component, Output, EventEmitter, ViewEncapsulation } from '@angular/core';
|
||||||
import { Component, Output, EventEmitter, ViewEncapsulation, SecurityContext } from '@angular/core';
|
import { Store } from '@ngrx/store';
|
||||||
import { AppConfigService } from '@alfresco/adf-core';
|
import { Observable } from 'rxjs/Rx';
|
||||||
|
import { AcaState } from '../../store/states/app.state';
|
||||||
|
import { selectHeaderColor, selectAppName, selectLogoPath } from '../../store/selectors/app.selectors';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-header',
|
selector: 'app-header',
|
||||||
@ -36,28 +38,17 @@ import { AppConfigService } from '@alfresco/adf-core';
|
|||||||
export class HeaderComponent {
|
export class HeaderComponent {
|
||||||
@Output() menu: EventEmitter<any> = new EventEmitter<any>();
|
@Output() menu: EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
|
||||||
private defaultPath = '/assets/images/alfresco-logo-white.svg';
|
appName$: Observable<string>;
|
||||||
private defaultBackgroundColor = '#2196F3';
|
headerColor$: Observable<string>;
|
||||||
|
logo$: Observable<string>;
|
||||||
|
|
||||||
constructor(
|
constructor(store: Store<AcaState>) {
|
||||||
private appConfig: AppConfigService,
|
this.headerColor$ = store.select(selectHeaderColor);
|
||||||
private sanitizer: DomSanitizer
|
this.appName$ = store.select(selectAppName);
|
||||||
) {}
|
this.logo$ = store.select(selectLogoPath);
|
||||||
|
}
|
||||||
|
|
||||||
toggleMenu() {
|
toggleMenu() {
|
||||||
this.menu.emit();
|
this.menu.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
get appName(): string {
|
|
||||||
return <string>this.appConfig.get('application.name');
|
|
||||||
}
|
|
||||||
|
|
||||||
get logo() {
|
|
||||||
return this.appConfig.get('application.logo', this.defaultPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
get backgroundColor() {
|
|
||||||
const color = this.appConfig.get('headerColor', this.defaultBackgroundColor);
|
|
||||||
return this.sanitizer.sanitize(SecurityContext.STYLE, color);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ export abstract class PageComponent implements OnDestroy {
|
|||||||
const entry: MinimalNodeEntryEntity = row.node.entry;
|
const entry: MinimalNodeEntryEntity = row.node.entry;
|
||||||
|
|
||||||
if (PageComponent.isLockedNode(entry)) {
|
if (PageComponent.isLockedNode(entry)) {
|
||||||
return '/assets/images/ic_lock_black_24dp_1x.png';
|
return 'assets/images/ic_lock_black_24dp_1x.png';
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
8
src/app/store/actions/app-name.action.ts
Normal file
8
src/app/store/actions/app-name.action.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Action } from '@ngrx/store';
|
||||||
|
|
||||||
|
export const SET_APP_NAME = 'SET_APP_NAME';
|
||||||
|
|
||||||
|
export class SetAppNameAction implements Action {
|
||||||
|
readonly type = SET_APP_NAME;
|
||||||
|
constructor(public payload: string) {}
|
||||||
|
}
|
8
src/app/store/actions/header-color.action.ts
Normal file
8
src/app/store/actions/header-color.action.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Action } from '@ngrx/store';
|
||||||
|
|
||||||
|
export const SET_HEADER_COLOR = 'SET_HEADER_COLOR';
|
||||||
|
|
||||||
|
export class SetHeaderColorAction implements Action {
|
||||||
|
readonly type = SET_HEADER_COLOR;
|
||||||
|
constructor(public payload: string) {}
|
||||||
|
}
|
8
src/app/store/actions/logo-path.action.ts
Normal file
8
src/app/store/actions/logo-path.action.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Action } from '@ngrx/store';
|
||||||
|
|
||||||
|
export const SET_LOGO_PATH = 'SET_LOGO_PATH';
|
||||||
|
|
||||||
|
export class SetLogoPathAction implements Action {
|
||||||
|
readonly type = SET_LOGO_PATH;
|
||||||
|
constructor(public payload: string) {}
|
||||||
|
}
|
44
src/app/store/reducers/app.reducer.ts
Normal file
44
src/app/store/reducers/app.reducer.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { Action } from '@ngrx/store';
|
||||||
|
import { AppState, INITIAL_APP_STATE } from '../states/app.state';
|
||||||
|
import { SET_HEADER_COLOR, SetHeaderColorAction } from '../actions/header-color.action';
|
||||||
|
import { SET_APP_NAME, SetAppNameAction } from '../actions/app-name.action';
|
||||||
|
import { SET_LOGO_PATH, SetLogoPathAction } from '../actions/logo-path.action';
|
||||||
|
|
||||||
|
|
||||||
|
export function appReducer(state: AppState = INITIAL_APP_STATE, action: Action): AppState {
|
||||||
|
let newState: AppState;
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case SET_APP_NAME:
|
||||||
|
newState = updateAppName(state, <SetAppNameAction> action);
|
||||||
|
break;
|
||||||
|
case SET_HEADER_COLOR:
|
||||||
|
newState = updateHeaderColor(state, <SetHeaderColorAction> action);
|
||||||
|
break;
|
||||||
|
case SET_LOGO_PATH:
|
||||||
|
newState = updateLogoPath(state, <SetLogoPathAction> action);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
newState = Object.assign({}, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateHeaderColor(state: AppState, action: SetHeaderColorAction): AppState {
|
||||||
|
const newState = Object.assign({}, state);
|
||||||
|
newState.headerColor = action.payload;
|
||||||
|
return newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAppName(state: AppState, action: SetAppNameAction): AppState {
|
||||||
|
const newState = Object.assign({}, state);
|
||||||
|
newState.appName = action.payload;
|
||||||
|
return newState;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLogoPath(state: AppState, action: SetLogoPathAction): AppState {
|
||||||
|
const newState = Object.assign({}, state);
|
||||||
|
newState.logoPath = action.payload;
|
||||||
|
return newState;
|
||||||
|
}
|
7
src/app/store/selectors/app.selectors.ts
Normal file
7
src/app/store/selectors/app.selectors.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { createSelector } from '@ngrx/store';
|
||||||
|
import { AcaState, AppState } from '../states/app.state';
|
||||||
|
|
||||||
|
export const selectApp = (state: AcaState) => state.app;
|
||||||
|
export const selectHeaderColor = createSelector(selectApp, (state: AppState) => state.headerColor);
|
||||||
|
export const selectAppName = createSelector(selectApp, (state: AppState) => state.appName);
|
||||||
|
export const selectLogoPath = createSelector(selectApp, (state: AppState) => state.logoPath);
|
19
src/app/store/states/app.state.ts
Normal file
19
src/app/store/states/app.state.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export interface AppState {
|
||||||
|
appName: string;
|
||||||
|
headerColor: string;
|
||||||
|
logoPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const INITIAL_APP_STATE: AppState = {
|
||||||
|
appName: 'Alfresco Example Content Application',
|
||||||
|
headerColor: '#2196F3',
|
||||||
|
logoPath: 'assets/images/alfresco-logo-white.svg'
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface AcaState {
|
||||||
|
app: AppState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const INITIAL_STATE: AcaState = {
|
||||||
|
app: INITIAL_APP_STATE
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user