EgAuthService,
EgStoreService
],
+ exports: [
+ ],
bootstrap: [EgBaseComponent]
})
@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;
});
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)
--- /dev/null
+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: ^--
+}
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
]
})
-<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>
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,
) {}
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);
}
}
import { EgNetService } from '@eg/core/net.service';
import { EgAuthService } from '@eg/core/auth.service';
-declare var js2JSON;
-
@Component({
templateUrl: 'bcsearch.component.html'
})
-<!--
-<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/>
+/* 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;
}
-<!--
-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>
--- /dev/null
+
+<div>
+ <input type="text"
+ class="form-control"
+ [placeholder]="placeholder"
+ [(ngModel)]="selectedOrg"
+ [ngbTypeahead]="orgFilter"
+ />
+</div>
--- /dev/null
+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)
+ );
+ }
+}
+
+
+
-<br/>
-<br/>
-<br/>
<b>Staff Splash Page</b>
<br/>
<!-- 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>
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
]
})