From 4b577f5114e15c7778b50967a34b28ba8bb1eb8d Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Wed, 27 Dec 2017 17:52:18 -0500 Subject: [PATCH] LP#626157 ang2 catalog tweaks Signed-off-by: Bill Erickson --- Open-ILS/eg2-src/src/app/app.module.ts | 17 ++--- Open-ILS/eg2-src/src/app/core/org.ts | 8 +-- .../src/app/share/catalog/catalog-url.service.ts | 4 +- .../src/app/share/catalog/search-context.ts | 12 ++-- Open-ILS/eg2-src/src/app/staff/app.component.ts | 10 +-- .../eg2-src/src/app/staff/catalog/app.component.ts | 5 +- .../eg2-src/src/app/staff/catalog/app.service.ts | 27 +++++-- .../app/staff/catalog/record/copies.component.ts | 2 +- .../src/app/staff/catalog/resolver.service.ts | 23 ++++-- .../app/staff/catalog/result/results.component.ts | 4 +- Open-ILS/eg2-src/src/app/staff/resolver.service.ts | 84 +++++++++++++--------- Open-ILS/eg2-src/src/app/staff/routing.module.ts | 11 ++- 12 files changed, 125 insertions(+), 82 deletions(-) diff --git a/Open-ILS/eg2-src/src/app/app.module.ts b/Open-ILS/eg2-src/src/app/app.module.ts index ce8f2c15a3..e7a6198786 100644 --- a/Open-ILS/eg2-src/src/app/app.module.ts +++ b/Open-ILS/eg2-src/src/app/app.module.ts @@ -1,11 +1,10 @@ /** - * EgBaseModule is the shared starting point for all apps. - * It provides the root router and a simple welcome page for - * users that end up here accidentally. + * EgBaseModule is the shared starting point for all apps. It provides + * the root route and core services, and a simple welcome page for users + * that end up here accidentally. */ import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; -import {Router} from '@angular/router'; // Debugging import {NgbModule} from '@ng-bootstrap/ng-bootstrap'; // ng-bootstrap import {CookieModule} from 'ngx-cookie'; // import CookieMonster @@ -48,11 +47,5 @@ import {EgOrgService} from '@eg/core/org'; bootstrap: [EgBaseComponent] }) -export class EgBaseModule { - constructor(router: Router) { - /* - console.debug('Routes: ', - JSON.stringify(router.config, undefined, 2)); - */ - } -} +export class EgBaseModule {} + diff --git a/Open-ILS/eg2-src/src/app/core/org.ts b/Open-ILS/eg2-src/src/app/core/org.ts index 8508c41da2..0cf87a4ab1 100644 --- a/Open-ILS/eg2-src/src/app/core/org.ts +++ b/Open-ILS/eg2-src/src/app/core/org.ts @@ -22,9 +22,9 @@ interface OrgSettingsBatch { @Injectable() export class EgOrgService { - private orgMap = {}; private orgList: EgIdlObject[] = []; private orgTree: EgIdlObject; // root node + children + private orgMap: {[id:number] : EgIdlObject} = {}; private settingsCache: OrgSettingsBatch = {}; constructor( @@ -37,11 +37,11 @@ export class EgOrgService { if (typeof nodeOrId == 'object') return nodeOrId; return this.orgMap[nodeOrId]; - }; + } list(): EgIdlObject[] { return this.orgList; - }; + } /** * Returns a list of org units that match the selected criteria. @@ -95,7 +95,7 @@ export class EgOrgService { nodes.push(node); if (asId) return nodes.map(n => n.id()); return nodes; - }; + } // tests that a node can have users canHaveUsers(nodeOrId): boolean { diff --git a/Open-ILS/eg2-src/src/app/share/catalog/catalog-url.service.ts b/Open-ILS/eg2-src/src/app/share/catalog/catalog-url.service.ts index 00f3203956..932416f2cf 100644 --- a/Open-ILS/eg2-src/src/app/share/catalog/catalog-url.service.ts +++ b/Open-ILS/eg2-src/src/app/share/catalog/catalog-url.service.ts @@ -122,7 +122,7 @@ export class EgCatalogUrlService { context.addFacet(new FacetFilter(facet.c, facet.n, facet.v)); }); - context.searchOrg = - this.org.get(+params.get('org')) || this.org.root(); + if (params.get('org')) + context.searchOrg = this.org.get(+params.get('org')); } } diff --git a/Open-ILS/eg2-src/src/app/share/catalog/search-context.ts b/Open-ILS/eg2-src/src/app/share/catalog/search-context.ts index b3c21e53a8..6bd538039b 100644 --- a/Open-ILS/eg2-src/src/app/share/catalog/search-context.ts +++ b/Open-ILS/eg2-src/src/app/share/catalog/search-context.ts @@ -99,15 +99,15 @@ export class CatalogSearchContext { * Return search context to its default state, resetting search * parameters and clearing any cached result data. * This does not reset global filters like limit-to-available - * or search-global. + * search-global, or search-org. */ reset(): void { this.pager.offset = 0; this.format = ''; this.sort = ''; - this.query = ['']; - this.fieldClass = ['keyword']; - this.matchOp = ['contains']; + this.query = ['']; + this.fieldClass = ['keyword']; + this.matchOp = ['contains']; this.joinOp = ['']; this.ccvmFilters = {}; this.facetFilters = []; @@ -117,7 +117,9 @@ export class CatalogSearchContext { } isSearchable(): boolean { - return this.query.length && this.query[0] != ''; + return this.query.length + && this.query[0] != '' + && this.searchOrg != null; } compileSearch(): string { diff --git a/Open-ILS/eg2-src/src/app/staff/app.component.ts b/Open-ILS/eg2-src/src/app/staff/app.component.ts index 27a60f9806..788edc2cb9 100644 --- a/Open-ILS/eg2-src/src/app/staff/app.component.ts +++ b/Open-ILS/eg2-src/src/app/staff/app.component.ts @@ -1,7 +1,7 @@ -import { Component, OnInit } from '@angular/core'; -import { Router, ActivatedRoute, NavigationEnd } from '@angular/router'; -import { EgAuthService, EgAuthWsState } from '@eg/core/auth'; -import { EgNetService } from '@eg/core/net'; +import {Component, OnInit} from '@angular/core'; +import {Router, ActivatedRoute, NavigationEnd} from '@angular/router'; +import {EgAuthService, EgAuthWsState} from '@eg/core/auth'; +import {EgNetService} from '@eg/core/net'; @Component({ templateUrl: 'app.component.html', @@ -45,7 +45,7 @@ export class EgStaffComponent implements OnInit { }); this.route.data.subscribe((data: {staffResolver : any}) => { - console.debug('EgStaff ngOnInit complete'); + // Data fetched via EgStaffResolver is available here. }); } diff --git a/Open-ILS/eg2-src/src/app/staff/catalog/app.component.ts b/Open-ILS/eg2-src/src/app/staff/catalog/app.component.ts index a5ca68f3db..8fffa13ed2 100644 --- a/Open-ILS/eg2-src/src/app/staff/catalog/app.component.ts +++ b/Open-ILS/eg2-src/src/app/staff/catalog/app.component.ts @@ -9,8 +9,9 @@ export class EgCatalogComponent implements OnInit { constructor(private staffCat: StaffCatalogService) {} ngOnInit() { - // Create the search context that will be used by all - // of my child components. + // Create the search context that will be used by all of my + // child components. After initial creation, the context is + // reset and updated as needed to apply new search parameters. this.staffCat.createContext(); } } diff --git a/Open-ILS/eg2-src/src/app/staff/catalog/app.service.ts b/Open-ILS/eg2-src/src/app/staff/catalog/app.service.ts index 625206e7f4..0cf04ca7f2 100644 --- a/Open-ILS/eg2-src/src/app/staff/catalog/app.service.ts +++ b/Open-ILS/eg2-src/src/app/staff/catalog/app.service.ts @@ -1,5 +1,6 @@ import {Injectable} from '@angular/core'; import {Router, ActivatedRoute} from '@angular/router'; +import {EgIdlObject} from '@eg/core/idl'; import {EgOrgService} from '@eg/core/org'; import {EgCatalogService} from '@eg/share/catalog/catalog.service'; import {EgCatalogUrlService} from '@eg/share/catalog/catalog-url.service'; @@ -14,6 +15,11 @@ export class StaffCatalogService { searchContext: CatalogSearchContext; routeIndex: number = 0; + defaultSearchOrg: EgIdlObject; + defaultSearchLimit: number; + + // TODO: does unapi support pref-lib for result-page copy counts? + prefOrg: EgIdlObject; constructor( private router: Router, @@ -31,12 +37,20 @@ export class StaffCatalogService { this.searchContext = this.catUrl.fromUrlParams(this.route.snapshot.queryParamMap); - this.searchContext.org = this.org; + this.searchContext.org = this.org; // service, not searchOrg this.searchContext.isStaff = true; + this.applySearchDefaults(); + } + + applySearchDefaults(): void { + if (!this.searchContext.searchOrg) { + this.searchContext.searchOrg = + this.defaultSearchOrg || this.org.root(); + } - // TODO: UI / settings - if (!this.searchContext.pager.limit) - this.searchContext.pager.limit = 20; + if (!this.searchContext.pager.limit) { + this.searchContext.pager.limit = this.defaultSearchLimit || 20; + } } /** @@ -45,10 +59,9 @@ export class StaffCatalogService { * execute the actual search. */ search(): void { - let params = this.catUrl.toUrlParams(this.searchContext); + if (!this.searchContext.isSearchable()) return; - // Avoid redirect on empty-query searches - if (params.query[0] == '') return; + let params = this.catUrl.toUrlParams(this.searchContext); // Force a new search every time this method is called, even if // it's the same as the active search. Since router navigation diff --git a/Open-ILS/eg2-src/src/app/staff/catalog/record/copies.component.ts b/Open-ILS/eg2-src/src/app/staff/catalog/record/copies.component.ts index f234eba8df..a8e9417e54 100644 --- a/Open-ILS/eg2-src/src/app/staff/catalog/record/copies.component.ts +++ b/Open-ILS/eg2-src/src/app/staff/catalog/record/copies.component.ts @@ -54,7 +54,7 @@ export class CopiesComponent implements OnInit { this.staffCat.searchContext.searchOrg.ou_type().depth(), // TODO this.pager.limit, this.pager.offset, - this.staffCat.searchContext.searchOrg.id() // TODO pref_ou + this.staffCat.prefOrg ? this.staffCat.prefOrg.id() : null ).subscribe(copy => { this.copies.push(copy); }); diff --git a/Open-ILS/eg2-src/src/app/staff/catalog/resolver.service.ts b/Open-ILS/eg2-src/src/app/staff/catalog/resolver.service.ts index e36483977b..45fc8c5927 100644 --- a/Open-ILS/eg2-src/src/app/staff/catalog/resolver.service.ts +++ b/Open-ILS/eg2-src/src/app/staff/catalog/resolver.service.ts @@ -8,6 +8,7 @@ import {EgOrgService} from '@eg/core/org'; import {EgAuthService} from '@eg/core/auth'; import {EgPcrudService} from '@eg/core/pcrud'; import {EgCatalogService} from '@eg/share/catalog/catalog.service'; +import {StaffCatalogService} from './app.service'; @Injectable() export class EgCatalogResolver implements Resolve> { @@ -18,7 +19,8 @@ export class EgCatalogResolver implements Resolve> { private org: EgOrgService, private net: EgNetService, private auth: EgAuthService, - private cat: EgCatalogService + private cat: EgCatalogService, + private staffCat: StaffCatalogService ) {} resolve( @@ -35,9 +37,22 @@ export class EgCatalogResolver implements Resolve> { } fetchSettings(): Promise { - return this.org.settings([ - // staff catalog org settings loaded here... - ]); + let promises = []; + + promises.push( + this.store.getItem('eg.search.search_lib').then( + id => this.staffCat.defaultSearchOrg = this.org.get(id) + ) + ); + + promises.push( + this.store.getItem('eg.search.pref_lib').then( + id => this.staffCat.prefOrg = this.org.get(id) + ) + ); + + return Promise.all(promises); } + } diff --git a/Open-ILS/eg2-src/src/app/staff/catalog/result/results.component.ts b/Open-ILS/eg2-src/src/app/staff/catalog/result/results.component.ts index b87a2cdd7d..ff2d36ce29 100644 --- a/Open-ILS/eg2-src/src/app/staff/catalog/result/results.component.ts +++ b/Open-ILS/eg2-src/src/app/staff/catalog/result/results.component.ts @@ -37,7 +37,9 @@ export class ResultsComponent implements OnInit { // ResultsComponent is active, it will not be reinitialized, // even if the route parameters changes (unless we change the // route reuse policy). Watch for changes here to pick up new - // searches. This will also fire on page load. + // searches. + // + // This will also fire on page load. this.route.queryParamMap.subscribe((params: ParamMap) => { // TODO: Angular docs suggest using switchMap(), but diff --git a/Open-ILS/eg2-src/src/app/staff/resolver.service.ts b/Open-ILS/eg2-src/src/app/staff/resolver.service.ts index a9c393e9ec..d37ca2f65d 100644 --- a/Open-ILS/eg2-src/src/app/staff/resolver.service.ts +++ b/Open-ILS/eg2-src/src/app/staff/resolver.service.ts @@ -8,9 +8,7 @@ import {EgNetService} from '@eg/core/net'; import {EgAuthService} from '@eg/core/auth'; /** - * Apply configuration, etc. required by all staff components. - * This resolver is called before authentication is confirmed. - * See EgStaffCommonDataResolver for staff-wide, post-auth activities. + * Load data used by all staff modules. */ @Injectable() export class EgStaffResolver implements Resolve> { @@ -18,6 +16,9 @@ export class EgStaffResolver implements Resolve> { readonly loginPath = '/staff/login'; readonly wsRemPath = '/staff/admin/workstation/workstations/remove/'; + // Tracks the primary resolve observable. + observer: Observer; + constructor( private router: Router, private route: ActivatedRoute, @@ -43,38 +44,55 @@ export class EgStaffResolver implements Resolve> { let path = state.url.split('?')[0] if (path == '/staff/login') return Observable.of(true); - return Observable.create(observer => { - this.auth.testAuthToken().then( - tokenOk => { - console.debug('EgStaffResolver: authtoken verified'); - this.auth.verifyWorkstation().then( - wsOk => { - this.loadStartupData(observer).then( - ok => observer.complete() - ); - }, - wsNotOk => { - if (path.indexOf(this.wsRemPath) < 0) { - this.router.navigate([ - this.wsRemPath + this.auth.workstation() - ]); - } - observer.complete(); - } - ); - }, - tokenNotOk => { - // Authtoken is not OK. - console.debug('EgStaffResolver: authtoken is not valid'); - this.auth.redirectUrl = state.url; - this.router.navigate([this.loginPath]); - observer.error('invalid auth'); - } - ); - }); + let observable: Observable + = Observable.create(o => this.observer = o); + + this.auth.testAuthToken().then( + tokenOk => { + console.debug('EgStaffResolver: authtoken verified'); + this.auth.verifyWorkstation().then( + wsOk => { + this.loadStartupData() + .then(ok => this.observer.complete()) + }, + wsNotOk => this.handleInvalidWorkstation(path) + ); + }, + tokenNotOk => this.handleInvalidToken(state) + ); + + return observable; + } + + // A page that's not the login page was requested without a + // valid auth token. Send the caller back to the login page. + handleInvalidToken(state: RouterStateSnapshot): void { + console.debug('EgStaffResolver: authtoken is not valid'); + this.auth.redirectUrl = state.url; + this.router.navigate([this.loginPath]); + this.observer.error('invalid or no auth token'); + } + + // For the purposes of route resolving, an invalid workstation is + // not an error condition when we're on the workstation admin page. + // However, it does mean that not all of the usual data will be + // loaded and available on the workstation admin page. Once a valid + // workstation is applied and the user re-logs in, the page must be + // reloaded to fetch all of the needed staff data. + handleInvalidWorkstation(path: string): void { + if (path.indexOf(this.wsRemPath) < 0) { + this.router.navigate([ + this.wsRemPath + this.auth.workstation()]); + this.observer.error('invalid workstation, redirecting'); + } else { + this.observer.complete(); + } } - loadStartupData(observer: Observer): Promise { + /** + * Fetches data common to all staff interfaces. + */ + loadStartupData(): Promise { console.debug('EgStaffResolver:loadStartupData()'); return Promise.resolve(); } diff --git a/Open-ILS/eg2-src/src/app/staff/routing.module.ts b/Open-ILS/eg2-src/src/app/staff/routing.module.ts index 81c0609565..a25baf9f23 100644 --- a/Open-ILS/eg2-src/src/app/staff/routing.module.ts +++ b/Open-ILS/eg2-src/src/app/staff/routing.module.ts @@ -6,7 +6,8 @@ import {EgStaffLoginComponent} from './login.component'; import {EgStaffSplashComponent} from './splash.component'; // Not using 'canActivate' because it's called before all resolvers, -// but the resolvers parse the IDL, etc. +// even the parent resolver, but the resolvers parse the IDL, load settings, +// etc. Chicken, meet egg. const routes: Routes = [{ path: '', @@ -35,11 +36,9 @@ const routes: Routes = [{ }]; @NgModule({ - imports: [ RouterModule.forChild(routes) ], - exports: [ RouterModule ], - providers: [ - EgStaffResolver - ] + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [EgStaffResolver] }) export class EgStaffRoutingModule {} -- 2.11.0