LP#626157 Ang2 experiments
authorBill Erickson <berickxx@gmail.com>
Tue, 28 Nov 2017 20:17:43 +0000 (15:17 -0500)
committerBill Erickson <berickxx@gmail.com>
Mon, 11 Dec 2017 17:39:51 +0000 (12:39 -0500)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
15 files changed:
Open-ILS/webby-src/src/app/base.module.ts
Open-ILS/webby-src/src/app/core/idl.service.ts
Open-ILS/webby-src/src/app/core/org.service.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/admin/workstation/admin-ws.module.ts
Open-ILS/webby-src/src/app/staff/admin/workstation/workstations.component.html
Open-ILS/webby-src/src/app/staff/admin/workstation/workstations.component.ts
Open-ILS/webby-src/src/app/staff/circ/patron/bcsearch/bcsearch.component.ts
Open-ILS/webby-src/src/app/staff/login.component.html
Open-ILS/webby-src/src/app/staff/nav.component.css
Open-ILS/webby-src/src/app/staff/nav.component.html
Open-ILS/webby-src/src/app/staff/share/org-select.component.html [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/share/org-select.component.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/staff/splash.component.html
Open-ILS/webby-src/src/app/staff/staff.component.html
Open-ILS/webby-src/src/app/staff/staff.module.ts

index a464f61..0a5ff2b 100644 (file)
@@ -38,6 +38,8 @@ import {EgStoreService} from '@eg/core/store.service';
     EgAuthService,
     EgStoreService
   ],
+  exports: [
+  ],
   bootstrap: [EgBaseComponent]
 })
 
index 49a4317..e503ba7 100644 (file)
@@ -23,7 +23,17 @@ export interface EgIdlObject {
 @Injectable()
 export class EgIdlService {
 
-    classes: Object;
+    classes = {}; // IDL class metadata
+    constructors = {}; // IDL instance generators
+
+    /**
+     * Create a new IDL object instance.
+     */
+    create(cls: string, seed?:any[]): EgIdlObject {
+        if (this.constructors[cls])
+            return new this.constructors[cls](seed);
+        throw new Error(`No such IDL class ${cls}`);
+    }
 
     parseIdl(): void {
         let this_ = this;
@@ -55,13 +65,14 @@ export class EgIdlService {
                 });
 
                 return x;
-            })
+            });
 
-            this_[cls] = generator();
+            this_.constructors[cls] = generator();
 
             // global class constructors required for JSON_v1.js
-            // TODO: Move away from requiring we add classes to window.
-            window[cls] = this_[cls]; 
+            // TODO: polluting the window namespace w/ every IDL class 
+            // is less than ideal.
+            window[cls] = this_.constructors[cls]; 
         }
 
         for (var cls in this_.classes) 
diff --git a/Open-ILS/webby-src/src/app/core/org.service.ts b/Open-ILS/webby-src/src/app/core/org.service.ts
new file mode 100644 (file)
index 0000000..44eea6a
--- /dev/null
@@ -0,0 +1,87 @@
+import {Injectable} from '@angular/core';
+import {Observable} from 'rxjs/Rx';
+import {EgIdlObject, EgIdlService} from './idl.service';
+
+type EgOrgNodeOrId = number | EgIdlObject;
+
+@Injectable()
+export class EgOrgService {
+
+    private orgMap = {};
+    private orgList: EgIdlObject[] = [];
+    private orgTree: EgIdlObject; // root node + children
+
+    get(nodeOrId: EgOrgNodeOrId): EgIdlObject {
+        if (typeof nodeOrId == 'object')
+            return nodeOrId;
+        return this.orgMap[nodeOrId];
+    };
+
+    list(): EgIdlObject[] {
+        return this.orgList;
+    };
+
+    tree(): EgIdlObject {
+        return this.orgTree;
+    }
+
+    // get the root OU
+    root(): EgIdlObject {
+        return this.orgList[0];
+    }
+
+    // list of org_unit objects or IDs for ancestors + me
+    ancestors(nodeOrId: EgOrgNodeOrId, asId: Boolean): EgIdlObject[] {
+        let node = this.get(nodeOrId);
+        if (!node) return [];
+        let nodes = [node];
+        while( (node = this.get(node.parent_ou())))
+            nodes.push(node);
+        if (asId) 
+            return nodes.map(function(n){return n.id()});
+        return nodes;
+    };
+
+    // tests that a node can have users
+    canHaveUsers(nodeOrId): Boolean {
+           return this
+            .get(nodeOrId)
+            .ou_type()
+            .can_have_users() == 't';
+    }
+
+    // tests that a node can have volumes
+    canHaveVolumes(nodeOrId): Boolean {
+        return this
+            .get(nodeOrId)
+            .ou_type()
+            .can_have_vols() == 't';
+    }
+
+    // list of org_unit objects  or IDs for me + descendants
+    descendants(nodeOrId: EgOrgNodeOrId, asId: Boolean): EgIdlObject[] {
+        let node = this.get(nodeOrId);
+        if (!node) return [];
+        let nodes = [];
+        function descend(n) {
+            nodes.push(n);
+            n.children().forEach(descend);
+        }
+        descend(node);
+        if (asId) 
+            return nodes.map(function(n){return n.id()});
+        return nodes;
+    }
+
+    // list of org_unit objects or IDs for ancestors + me + descendants
+    fullPath(nodeOrId: EgOrgNodeOrId, asId: Boolean): EgIdlObject[] {
+        let list = this.ancestors(nodeOrId, false).concat(
+          this.descendants(nodeOrId, false).slice(1));
+        if (asId) 
+            return list.map(function(n){return n.id()});
+        return list;
+    }
+
+    // NOTE: see ./org-settings.service for settings 
+    // TODO: ^--
+}
index 743ef85..d6c5b0e 100644 (file)
@@ -1,18 +1,17 @@
 import {NgModule} from '@angular/core';
 import {CommonModule} from '@angular/common';
-import {FormsModule} from '@angular/forms';
+import {EgStaffModule} from '../../staff.module';
 import {EgAdminWsRoutingModule} from './routing.module';
 import {EgWorkstationsComponent} from './workstations.component';
 
-
 @NgModule({
   declarations: [
     EgWorkstationsComponent
   ],
   imports: [
+    EgStaffModule,
     EgAdminWsRoutingModule,
-    CommonModule,
-    FormsModule,
+    CommonModule
   ]
 })
 
index ebb2b8f..d3b6bb4 100644 (file)
@@ -1,59 +1,71 @@
-<div class="col-md-10 offset-md-1">
-  <div class="alert alert-warning" *ngIf="removingWs" i18n>
-    Workstation {{removingWs}} is no longer valid.  Removing registration.
-  </div>
-  <div class="alert alert-danger" *ngIf="workstations.length == 0">
-    <span i18n>Please register a workstation.</span>
-  </div>
-  <div class="row">
-    <div class="col-md-6">
-      <span i18n>Register a New Workstation For This Browser</span>
+<div class="row">
+  <div class="col-8 offset-1">
+    <div class="alert alert-warning" *ngIf="removingWs" i18n>
+      Workstation {{removingWs}} is no longer valid.  Removing registration.
+    </div>
+    <div class="alert alert-danger" *ngIf="workstations.length == 0">
+      <span i18n>Please register a workstation.</span>
     </div>
-  </div>
 
-  <!--
-  <div class="row">
-    <div class="col-md-6">
-      <div class="input-group">
-        <div class="input-group-btn">
-          <b>org selector...</b>
-          <eg-org-selector 
-            selected="contextOrg"
-            hidden-test="wsOrgHidden">
-            disable-test="cant_have_users">
-          </eg-org-selector>
-        </div>
-        <input type='text' class='form-control'  
-          i18n-title
-          title="Workstation Name"
-          i18n-placeholder
-          placeholder="Workstation Name"
-          [(ngModel)]='newWSName'/>
-        <div class="input-group-btn">
-          <button class="btn btn-default" *ngClick="registerWs()">
-            <span i18n>Register</span>
-          </button>
+    <div class="row">
+      <div class="col" i18n>Register a New Workstation For This Browser</div>
+    </div>
+    <div class="row mt-2">
+      <div class="col-2">
+        <eg-org-select 
+          [selectedOrg]="newOwner"
+          [placeholder]="'Owner'" >
+        </eg-org-select>
+      </div>
+      <div class="col-6">
+        <div class="input-group">
+          <input type='text'
+            class='form-control'
+            i18n-title
+            title="Workstation Name"
+            i18n-placeholder
+            placeholder="Workstation Name"
+            [(ngModel)]='newName'/>
+          <div class="input-group-btn">
+            <button class="btn btn-light" (click)="registerWorkstation()">
+              <span i18n>Register</span>
+            </button>
+          </div>
         </div>
       </div>
     </div>
-  </div>
-  -->
-
-  <div class="row new-entry">
-    <div class="col-md-6">
-      <span i18n>Workstations Registered With This Browser</span>
+    <div class="row mt-3 pt-3 border border-left-0 border-right-0 border-bottom-0 border-light">
+      <div class="col">
+        <span i18n>Workstations Registered With This Browser</span>
+      </div>
     </div>
-  </div>
-  <div class="row">
-    <div class="col-md-6">
-      <select 
-        class="form-control"
-        i18n-placeholder 
-        [(ngModel)]="selectedWs">
-        <option *ngFor="let ws of workstations" value="{{ws.id}}">
-          {{ws.name}}
-        </option>
-      </select>
+    <div class="row">
+      <div class="col-6">
+        <select
+          class="form-control"
+          [(ngModel)]="selectedId">
+          <option *ngFor="let ws of workstations" value="{{ws.id}}">
+            {{ws.name}}
+          </option>
+        </select>
+      </div>
+    </div>
+    <div class="row mt-2">
+      <div class="col-md-6">
+        <button i18n class="btn btn-success" 
+          (click)="useNow()" [disabled]="!selected">
+          Use Now
+        </button>
+        <button i18n class="btn btn-light" 
+          (click)="setDefault()" [disabled]="!selected">
+          Mark As Default
+        </button>
+        <button i18n class="btn btn-danger"
+          (click)="removeSelected()"
+          [disabled]="!selected || isRemoving || !canDeleteSelected()">
+          Remove
+        </button>
+      </div>
     </div>
   </div>
 </div>
index bf7717d..4e212cf 100644 (file)
@@ -1,17 +1,29 @@
 import {Component, OnInit} from '@angular/core';
 import {ActivatedRoute} from '@angular/router';
-import {EgNetService} from '@eg/core/net.service';
 import {EgStoreService} from '@eg/core/store.service';
+import {EgIdlObject} from '@eg/core/idl.service';
+import {EgNetService} from '@eg/core/net.service';
 import {EgAuthService} from '@eg/core/auth.service';
+import {EgOrgService} from '@eg/core/org.service';
+
+// Slim version of the WS that's stored in the cache.
+interface Workstation {
+    id: number;
+    name: string;
+    owning_lib: number;
+}
 
 @Component({
   templateUrl: 'workstations.component.html'
 })
-
 export class EgWorkstationsComponent implements OnInit {
 
-    workstations: Object[] = [];
-    removingWs: boolean = false;
+    selectedId: Number;
+    workstations: Workstation[] = [];
+    isRemoving: boolean = false;
+
+    newOwner: EgIdlObject;
+    newName: String;
 
     constructor(
         private route: ActivatedRoute,
@@ -21,12 +33,33 @@ export class EgWorkstationsComponent implements OnInit {
     ) {}
 
     ngOnInit() {
+        this.egStore.getItem('eg.workstation.all')
+        .then(res => this.workstations = res);
+    }
 
-        console.log('EgWorkstationsComponent:ngOnInit()');
+    selected(): Workstation {
+        return this.workstations.filter(
+          ws => {return ws.id == this.selectedId})[0];
+    }
+
+    useNow(): void {
+      console.debug('using ' + this.selected().name);
+    }
+
+    setDefault(): void {
+      console.debug('defaulting ' + this.selected().name);
+    }
+
+    removeSelected(): void {
+      console.debug('removing ' + this.selected().name);
+    }
+    
+    canDeleteSelected(): boolean {
+        return true;
+    }
 
-        this.egStore.getItem('eg.workstation.all').then(
-            res => this.workstations = res
-        );
+    registerWorkstation(): void {
+        console.log('registering ' + this.newName);
     }
 }
 
index 9156170..21bb111 100644 (file)
@@ -3,8 +3,6 @@ import { ActivatedRoute } from '@angular/router';
 import { EgNetService } from '@eg/core/net.service';
 import { EgAuthService } from '@eg/core/auth.service';
 
-declare var js2JSON;
-
 @Component({
   templateUrl: 'bcsearch.component.html'
 })
index ae74776..869fe87 100644 (file)
@@ -1,58 +1,4 @@
-<!--
-<mat-grid-list cols="3" rowHeight="3:2">
-  <mat-grid-tile></mat-grid-tile>
-  <mat-grid-tile>
-    <style>
-      #staff-login-form { width: 80% }
-      mat-form-field { width: 80% }
-    </style>
-    <form (ngSubmit)="handleSubmit()" #loginForm="ngForm" id="staff-login-form">
-
-      <h3 i18n>Sign In</h3>
-      <hr/>
-
-      <p>
-        <mat-form-field>
-          <input 
-            type="text" 
-            id="username" 
-            name="username"
-            matInput
-            required
-            i18n-placeholder
-            placeholder="Username" 
-            [(ngModel)]="args.username"/>
-        </mat-form-field>
-      </p>
-
-      <p>
-        <mat-form-field>
-          <input 
-            type="password" 
-            class="form-control"
-            id="password" 
-            name="password"
-            matInput
-            required
-            i18n-placeholder
-            placeholder="Password" 
-            [(ngModel)]="args.password"/>
-        </mat-form-field>
-      </p>
-
-      <p>
-        <button mat-raised-button type='submit' color='primary' i18n>
-          Sign in
-        </button>
-      </p>
-
-    </form>
-  </mat-grid-tile>
-  <mat-grid-tile></mat-grid-tile>
-</mat-grid-list>
--->
-
-<div class="col-md-6 offset-md-3">
+<div class="col-md-4 offset-md-4">
   <fieldset>
     <legend i18n>Sign In</legend>
     <hr/>
index 96946d5..eb037e8 100644 (file)
@@ -1,24 +1,50 @@
+/* remove dropdown carret for icon-based entries */
+#staff-navbar .no-caret::after {
+    display:none;
+}
+
+/* move the caret closer to the dropdown text */
+#staff-navbar .dropdown-toggle::after {
+    margin-left:0px;
+}
+
 #staff-navbar {
     background: -webkit-linear-gradient(#00593d, #007a54);
     background-color: #007a54;
     color: #fff;
     font-size: 14px;
 }
-#staff-navbar .navbar-expander {
-  margin-right: auto;
+
+/* align top of dropdown w/ bottom of nav */
+#staff-navbar .dropdown-menu {
+    margin-top: 7px;
 }
-#staff-navbar .navbar-nav>li>a {
-    color: #fff;
-    padding-top: 5px;
-    padding-bottom: 1px;
+#staff-navbar .material-icons {
+    padding-right:3px;
 }
-#staff-navbar .nav-item {
-  margin-right: 10px;
+#staff-navbar .dropdown-item {
+    font-size: 14px;
+    font-weight: 400;
+    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+    padding-left: 0.5rem;
+    padding-right: 0.5rem;
 }
-#staff-navbar .navbar-nav>li>a:hover {
+
+#staff-navbar .nav-link {
+    color: #fff;
+    padding-top:1px;
+    padding-bottom:1px;
+}
+#staff-navbar .nav-link:hover {
     color: #ddd;
     cursor: pointer;
 }
+#staff-navbar .nav-item {
+  /*
+  margin-right: 8px;
+  */
+}
+
 #staff-navbar .navbar-nav > .open > a,
 #staff-navbar .navbar-nav > .open > a:focus,
 #staff-navbar .navbar-nav > .open > a:hover {
     border-top-color: #ddd;
     border-bottom-color: #ddd;
 }
-/* remove dropdown carret for icon-based entries */
-#staff-navbar .no-caret::after {
-    display:none;
+
+/* Align material-icons with sibling text; otherwise they float up */
+#staff-navbar .with-material-icon, #staff-navbar .dropdown-item {
+    display: inline-flex;
+    vertical-align: middle;
+    align-items: center;
 }
 
index e5f32cd..4206cd4 100644 (file)
@@ -1,40 +1,50 @@
-<!--
-loads new page
-href="./stuff"
-direct routing
-routerLink="/suff"
--->
-
-<nav id="staff-navbar" class="navbar fixed-top navbar-expand navbar-default">
+<div id="staff-navbar" class="navbar fixed-top navbar-expand navbar-default">
   <div class="collapse navbar-collapse">
-    <ul class="navbar-nav">
-      <li class="nav-item">
-        <a i18n class="nav-link" routerLink="/staff/splash">
-          <i class="material-icons">home</i>
+    <div class="navbar-nav">
+      <div class="nav-item">
+        <a i18n class="nav-link with-material-icon" routerLink="/staff/splash">
+          <span class="material-icons">home</span>
         </a>
-      </li>
-      <li ngbDropdown class="nav-item dropdown">
+      </div>
+    </div>
+    <div class="navbar-nav">
+      <div ngbDropdown class="nav-item dropdown">
         <a ngbDropdownToggle i18n class="nav-link dropdown-toggle">
          Circulation 
         </a>
         <div class="dropdown-menu" ngbDropdownMenu>
-          <a i18n class="dropdown-item" routerLink="/staff">Test Action</a>
-          <a i18n class="dropdown-item" 
-            routerLink="/staff/circ/patron/bcsearch/">Checkout</a>
+          <a class="dropdown-item"
+              routerLink="/staff/circ/patron/bcsearch">
+            <span class="material-icons">trending_up</span>
+            <span i18n>Check Out</span>
+          </a>
+          <a class="dropdown-item" routerLink="/staff">
+            <span class="material-icons">trending_down</span>
+            <span i18n>Check In</span>
+          </a>
+          <div class="dropdown-divider"></div>
+          <a i18n class="dropdown-item" routerLink="/staff/">
+            <span class="material-icons">alarm_add</span>
+            <span i18n>Test</span>
+          </a>
         </div>
-      </li>
-    </ul>
-    <ul class="navbar-expander"></ul>
-    <ul class="navbar-nav">
-      <li ngbDropdown class="nav-item dropdown" placement="bottom-right">
-        <a ngbDropdownToggle i18n class="nav-link dropdown-toggle no-caret">
+      </div>
+    </div>
+    <div class="navbar-nav mr-auto"></div>
+    <div class="navbar-nav">
+      <div ngbDropdown class="nav-item dropdown" placement="bottom-right">
+        <a ngbDropdownToggle i18n 
+          class="nav-link dropdown-toggle no-caret with-material-icon">
           <i class="material-icons">more_vert</i>
         </a>
         <div class="dropdown-menu" ngbDropdownMenu>
-          <a i18n class="dropdown-item" routerLink="/staff/login">Logout</a>
+          <a i18n class="dropdown-item" routerLink="/staff/login">
+            <span class="material-icons">lock_outline</span>
+            <span i18n>Logout</span>
+          </a>
         </div>
-      </li>
-    </ul>
+      </div>
+    </div>
   </div>
-</nav>
+</div>
 
diff --git a/Open-ILS/webby-src/src/app/staff/share/org-select.component.html b/Open-ILS/webby-src/src/app/staff/share/org-select.component.html
new file mode 100644 (file)
index 0000000..b8fb2d5
--- /dev/null
@@ -0,0 +1,9 @@
+
+<div>
+  <input type="text" 
+    class="form-control" 
+    [placeholder]="placeholder"
+    [(ngModel)]="selectedOrg" 
+    [ngbTypeahead]="orgFilter"
+  />
+</div>
diff --git a/Open-ILS/webby-src/src/app/staff/share/org-select.component.ts b/Open-ILS/webby-src/src/app/staff/share/org-select.component.ts
new file mode 100644 (file)
index 0000000..f111693
--- /dev/null
@@ -0,0 +1,48 @@
+import {Component, OnInit, Input} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {map, tap, debounceTime, distinctUntilChanged} from 'rxjs/operators';
+import {EgAuthService} from '@eg/core/auth.service';
+import {EgStoreService} from '@eg/core/store.service';
+import {EgIdlObject} from '@eg/core/idl.service';
+
+@Component({
+  selector: 'eg-org-select',
+  templateUrl: './org-select.component.html'
+})
+
+
+export class EgOrgSelectComponent implements OnInit {
+
+    @Input() placeholder: String;
+    @Input() selectedOrg: EgIdlObject;
+    @Input() displayField: String = 'shortname';
+    @Input() stickySetting: String;
+    @Input() onChange: (org:EgIdlObject) => void;
+    @Input() shouldDisable: (org:EgIdlObject) => Boolean;
+    @Input() shouldHide: (org:EgIdlObject) => Boolean;
+    @Input() allDisabled: Boolean = false;
+
+    testString: String = '';
+
+    constructor(
+      private egAuth: EgAuthService,
+      private egStore: EgStoreService 
+    ) {}
+    
+    ngOnInit() {
+    }
+
+    states = ['Alabama', 'Alaska', 'American Samoa', 'Arizona'];
+
+    orgFilter = (text$: Observable<string>): Observable<string[]> => {
+        return text$
+            .debounceTime(100)
+            .distinctUntilChanged()
+            .map(term => this.states.filter(
+                v => v.toLowerCase().indexOf(term.toLowerCase()) > -1)
+            );
+    }
+}
+
+
+
index 0c4993d..544e97f 100644 (file)
@@ -1,9 +1,10 @@
 <!-- top navigation bar -->
 <eg-staff-nav-bar></eg-staff-nav-bar>
 
-<!-- page content -->
 <!-- margin is there to accommodate navbar -->
-<div style="margin-top:74px">
+<div style="margin-top:56px">
+
+  <!-- page content -->
   <router-outlet></router-outlet>
 </div>
 
index 456c582..692e6f6 100644 (file)
@@ -2,24 +2,33 @@ import {CommonModule} from '@angular/common';
 import {NgModule} from '@angular/core';
 import {FormsModule} from '@angular/forms';
 import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
+import {EgBaseModule} from '@eg/base.module';
 
 import {EgStaffComponent} from './staff.component';
 import {EgStaffRoutingModule} from './routing.module';
 import {EgStaffNavComponent} from './nav.component';
 import {EgStaffLoginComponent} from './login.component';
 import {EgStaffSplashComponent} from './splash.component';
+import {EgOrgSelectComponent} from './share/org-select.component';
 
 @NgModule({
   declarations: [
     EgStaffComponent,
     EgStaffNavComponent,
     EgStaffSplashComponent,
-    EgStaffLoginComponent
+    EgStaffLoginComponent,
+    EgOrgSelectComponent
   ],
   imports: [
     EgStaffRoutingModule,
     FormsModule,
     NgbModule
+  ],
+  exports: [
+    // Components available to all staff modules
+    EgOrgSelectComponent,
+    FormsModule,
+    NgbModule
   ]
 })