Open-ILS/web/js/ui/default/staff/node_modules/
Open-ILS/web/js/ui/default/staff/bower_components/
Open-ILS/web/js/ui/default/common/build/
+Open-ILS/web/eg2/
--- /dev/null
+# Editor configuration, see http://editorconfig.org
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*.md]
+max_line_length = off
+trim_trailing_whitespace = false
--- /dev/null
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# ------
+# Added locally...
+# ------
+
+src/test_data/IDL2js.js
+
+# ------
+# compiled output
+/dist
+/tmp
+/out-tsc
+
+# dependencies
+/node_modules
+
+# IDEs and editors
+/.idea
+.project
+.classpath
+.c9/
+*.launch
+.settings/
+*.sublime-workspace
+
+# IDE - VSCode
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+# misc
+/.sass-cache
+/connect.lock
+/coverage
+/libpeerconnection.log
+npm-debug.log
+testem.log
+/typings
+
+# e2e
+/e2e/*.js
+/e2e/*.map
+
+# System Files
+.DS_Store
+Thumbs.db
--- /dev/null
+= Evergreen Angular App Cheatsheet
+
+== Basics
+
+[source,sh]
+---------------------------------------------------------------------
+npm update
+npm install
+ng lint # check code formatting
+npm run test # unit tests
+ng build --watch # compile dev mode
+ng build --prod # compile production mode
+---------------------------------------------------------------------
+
+== OPTIONAL: Adding a Locale
+
+* Using fr-CA as an example.
+* An fr-CA configuration is supplied by default. Additional configs
+ must be added where needed.
+* Currently translation builds are only available on --prod build mode.
+* Uncomment the locale lines in eg_vhost.conf and restart apache.
+* TODO: expand docs on package.json file changes required to add locales.
+
+[source,sh]
+---------------------------------------------------------------------
+npm run export-strings
+npm run merge-strings -- fr-CA
+# APPLY TRANSLATIONS TO src/locale/messages.fr-CA.xlf
+npm run build-fr-CA # modify package.json for other locales
+---------------------------------------------------------------------
+
--- /dev/null
+{
+ "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
+ "version": 1,
+ "newProjectRoot": "projects",
+ "projects": {
+ "eg": {
+ "root": "",
+ "sourceRoot": "src",
+ "projectType": "application",
+ "architect": {
+ "build": {
+ "builder": "@angular-devkit/build-angular:browser",
+ "options": {
+ "baseHref": "/eg2/en-US",
+ "deployUrl": "/eg2/en-US/",
+ "outputPath": "../../web/eg2/en-US",
+ "index": "src/index.html",
+ "main": "src/main.ts",
+ "tsConfig": "src/tsconfig.app.json",
+ "polyfills": "src/polyfills.ts",
+ "assets": [
+ "src/assets",
+ "src/favicon.ico"
+ ],
+ "styles": [
+ "src/styles.css"
+ ],
+ "scripts": []
+ },
+ "configurations": {
+ "production": {
+ "optimization": true,
+ "outputHashing": "all",
+ "sourceMap": false,
+ "extractCss": true,
+ "namedChunks": false,
+ "aot": true,
+ "extractLicenses": true,
+ "vendorChunk": false,
+ "buildOptimizer": true,
+ "fileReplacements": [
+ {
+ "replace": "src/environments/environment.ts",
+ "with": "src/environments/environment.prod.ts"
+ }
+ ]
+ },
+ "production-fr-CA": {
+ "optimization": true,
+ "outputHashing": "all",
+ "sourceMap": false,
+ "extractCss": true,
+ "namedChunks": false,
+ "aot": true,
+ "extractLicenses": true,
+ "vendorChunk": false,
+ "buildOptimizer": true,
+ "i18nFile": "src/locale/messages.fr-CA.xlf",
+ "i18nFormat": "xlf",
+ "i18nLocale": "fr-CA",
+ "i18nMissingTranslation": "ignore",
+ "fileReplacements": [
+ {
+ "replace": "src/environments/environment.ts",
+ "with": "src/environments/environment.prod.ts"
+ }
+ ]
+ }
+ }
+ },
+ "serve": {
+ "builder": "@angular-devkit/build-angular:dev-server",
+ "options": {
+ "browserTarget": "eg:build"
+ },
+ "configurations": {
+ "production": {
+ "browserTarget": "eg:build:production"
+ }
+ }
+ },
+ "extract-i18n": {
+ "builder": "@angular-devkit/build-angular:extract-i18n",
+ "options": {
+ "browserTarget": "eg:build"
+ }
+ },
+ "test": {
+ "builder": "@angular-devkit/build-angular:karma",
+ "options": {
+ "main": "src/test.ts",
+ "karmaConfig": "./karma.conf.js",
+ "polyfills": "src/polyfills.ts",
+ "tsConfig": "src/tsconfig.spec.json",
+ "scripts": [],
+ "styles": [
+ "src/styles.css"
+ ],
+ "assets": [
+ "src/assets",
+ "src/favicon.ico"
+ ]
+ }
+ },
+ "lint": {
+ "builder": "@angular-devkit/build-angular:tslint",
+ "options": {
+ "tsConfig": [
+ "src/tsconfig.app.json",
+ "src/tsconfig.spec.json"
+ ],
+ "exclude": [
+ "**/node_modules/**"
+ ]
+ }
+ }
+ }
+ },
+ "eg-e2e": {
+ "root": "",
+ "sourceRoot": "",
+ "projectType": "application",
+ "architect": {
+ "e2e": {
+ "builder": "@angular-devkit/build-angular:protractor",
+ "options": {
+ "protractorConfig": "./protractor.conf.js",
+ "devServerTarget": "eg:serve"
+ }
+ },
+ "lint": {
+ "builder": "@angular-devkit/build-angular:tslint",
+ "options": {
+ "tsConfig": [
+ "e2e/tsconfig.e2e.json"
+ ],
+ "exclude": [
+ "**/node_modules/**"
+ ]
+ }
+ }
+ }
+ }
+ },
+ "defaultProject": "eg",
+ "schematics": {
+ "@schematics/angular:component": {
+ "prefix": "eg",
+ "styleext": "css"
+ },
+ "@schematics/angular:directive": {
+ "prefix": "eg"
+ }
+ }
+}
--- /dev/null
+import { AppPage } from './app.po';
+
+describe('eg App', () => {
+ let page: AppPage;
+
+ beforeEach(() => {
+ page = new AppPage();
+ });
+
+ it('should display welcome message', () => {
+ page.navigateTo();
+ expect(page.getParagraphText()).toEqual('Welcome to app!');
+ });
+});
--- /dev/null
+import { browser, by, element } from 'protractor';
+
+export class AppPage {
+ navigateTo() {
+ return browser.get('/');
+ }
+
+ getParagraphText() {
+ return element(by.css('app-root h1')).getText();
+ }
+}
--- /dev/null
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/e2e",
+ "baseUrl": "./",
+ "module": "commonjs",
+ "target": "es5",
+ "types": [
+ "jasmine",
+ "jasminewd2",
+ "node"
+ ]
+ }
+}
--- /dev/null
+// Karma configuration file, see link for more information
+// https://karma-runner.github.io/1.0/config/configuration-file.html
+
+module.exports = function (config) {
+ config.set({
+ basePath: '',
+ frameworks: ['jasmine', '@angular-devkit/build-angular'],
+ plugins: [
+ require('karma-jasmine'),
+ require('karma-chrome-launcher'),
+ require('karma-phantomjs-launcher'),
+ require('karma-jasmine-html-reporter'),
+ require('karma-coverage-istanbul-reporter'),
+ require('@angular-devkit/build-angular/plugins/karma')
+ ],
+ client:{
+ clearContext: false // leave Jasmine Spec Runner output visible in browser
+ },
+ coverageIstanbulReporter: {
+ dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
+ fixWebpackSourcePaths: true
+ },
+ angularCli: {
+ environment: 'dev'
+ },
+ reporters: ['progress', 'kjhtml'],
+ port: 9876,
+ colors: true,
+ logLevel: config.LOG_INFO,
+ autoWatch: true,
+ browsers: ['PhantomJS'],
+ singleRun: true,
+ files: [
+ '/openils/lib/javascript/md5.js',
+ '/openils/lib/javascript/JSON_v1.js',
+ '/openils/lib/javascript/opensrf.js',
+ '/openils/lib/javascript/opensrf_ws.js',
+ // mock data for testing only
+ 'src/test_data/IDL2js.js',
+ 'src/test_data/eg_mock.js',
+ ]
+ });
+};
--- /dev/null
+{
+ "name": "eg",
+ "version": "0.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@angular-devkit/architect": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.7.5.tgz",
+ "integrity": "sha512-zwCpGdx3JDE+Y+LiWh9ErRX+fpFPTRHtEd2PDJmfQsdlIWfjxSR5U9vi3+bSRW2n6IFiH2GCYMS31R64rfMwbg==",
+ "dev": true,
+ "requires": {
+ "@angular-devkit/core": "0.7.5",
+ "rxjs": "^6.0.0"
+ }
+ },
+ "@angular-devkit/build-angular": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.7.5.tgz",
+ "integrity": "sha512-FYd2RigCbvm1i0aM1p+jO2145qm56iPgcW2TK3LBxllWFoz5v+wb086/aDzATG+2ETDZO1uENiVTWu5RSkYcSw==",
+ "dev": true,
+ "requires": {
+ "@angular-devkit/architect": "0.7.5",
+ "@angular-devkit/build-optimizer": "0.7.5",
+ "@angular-devkit/build-webpack": "0.7.5",
+ "@angular-devkit/core": "0.7.5",
+ "@ngtools/webpack": "6.1.5",
+ "ajv": "~6.4.0",
+ "autoprefixer": "^8.4.1",
+ "circular-dependency-plugin": "^5.0.2",
+ "clean-css": "^4.1.11",
+ "copy-webpack-plugin": "^4.5.2",
+ "file-loader": "^1.1.11",
+ "glob": "^7.0.3",
+ "html-webpack-plugin": "^3.0.6",
+ "istanbul": "^0.4.5",
+ "istanbul-instrumenter-loader": "^3.0.1",
+ "karma-source-map-support": "^1.2.0",
+ "less": "^3.7.1",
+ "less-loader": "^4.1.0",
+ "license-webpack-plugin": "^1.3.1",
+ "loader-utils": "^1.1.0",
+ "mini-css-extract-plugin": "~0.4.0",
+ "minimatch": "^3.0.4",
+ "node-sass": "^4.9.3",
+ "opn": "^5.1.0",
+ "parse5": "^4.0.0",
+ "portfinder": "^1.0.13",
+ "postcss": "^6.0.22",
+ "postcss-import": "^11.1.0",
+ "postcss-loader": "^2.1.5",
+ "postcss-url": "^7.3.2",
+ "raw-loader": "^0.5.1",
+ "rxjs": "^6.0.0",
+ "sass-loader": "~6.0.7",
+ "semver": "^5.5.0",
+ "source-map-loader": "^0.2.3",
+ "source-map-support": "^0.5.0",
+ "stats-webpack-plugin": "^0.6.2",
+ "style-loader": "^0.21.0",
+ "stylus": "^0.54.5",
+ "stylus-loader": "^3.0.2",
+ "tree-kill": "^1.2.0",
+ "uglifyjs-webpack-plugin": "^1.2.5",
+ "url-loader": "^1.0.1",
+ "webpack": "~4.9.2",
+ "webpack-dev-middleware": "^3.1.3",
+ "webpack-dev-server": "^3.1.4",
+ "webpack-merge": "^4.1.2",
+ "webpack-sources": "^1.1.0",
+ "webpack-subresource-integrity": "^1.1.0-rc.4"
+ }
+ },
+ "@angular-devkit/build-optimizer": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.7.5.tgz",
+ "integrity": "sha512-iZYUjNax6epTA4JjnDxhs6MQUtmwM04ZkJkTE3tVc01e80+wJ/f3+ja22BBVul2MsqchOsTUSQIJY3HxbV5aWw==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^1.1.0",
+ "source-map": "^0.5.6",
+ "typescript": "~2.9.1",
+ "webpack-sources": "^1.1.0"
+ },
+ "dependencies": {
+ "typescript": {
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
+ "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==",
+ "dev": true
+ }
+ }
+ },
+ "@angular-devkit/build-webpack": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.7.5.tgz",
+ "integrity": "sha512-PSkhBwJBLRMiBUGlK15CaVwbU4RzfCdF/GFS/CZSCsA3plLDJy+vXAPrUiuGvqYt/sVKBRavsNaEBCbK1t+1ig==",
+ "dev": true,
+ "requires": {
+ "@angular-devkit/architect": "0.7.5",
+ "@angular-devkit/core": "0.7.5",
+ "rxjs": "^6.0.0"
+ }
+ },
+ "@angular-devkit/core": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-0.7.5.tgz",
+ "integrity": "sha512-r99BZvvuNAqSRm05jXfx0sb3Ip0zvHPtAM6NReXzWPoqaVFpjVUdj/CKA+9HWG/Zt9meG9pEQt/HKK8UXaZDVA==",
+ "dev": true,
+ "requires": {
+ "ajv": "~6.4.0",
+ "chokidar": "^2.0.3",
+ "rxjs": "^6.0.0",
+ "source-map": "^0.5.6"
+ }
+ },
+ "@angular-devkit/schematics": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-0.7.5.tgz",
+ "integrity": "sha512-E7HkQeJawUskf2gPnogMc+cTdjJ2Iv3QEZOgprh/ExEmBYByWkGDRX5fQOuy8wME8VZqUBvQACZaVkEredn5EA==",
+ "dev": true,
+ "requires": {
+ "@angular-devkit/core": "0.7.5",
+ "rxjs": "^6.0.0"
+ }
+ },
+ "@angular/animations": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-6.1.6.tgz",
+ "integrity": "sha512-fK7onQeVsPgUx/sFcBvcGisuIuxvodzATpoKV9SnsQc6xWE5qsvJRZijrzZIN+Hxy/DgsLaVWRCPn1hG75/D2Q==",
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "@angular/cli": {
+ "version": "6.1.5",
+ "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-6.1.5.tgz",
+ "integrity": "sha512-QNVUSC8mPdiaxubneqNZISy+wec3gwbKoXjcaQ9/45baOnp662j2iJXwiMh6Atn0YUM4u1iUsz1uHyARMtgZmw==",
+ "dev": true,
+ "requires": {
+ "@angular-devkit/architect": "0.7.5",
+ "@angular-devkit/core": "0.7.5",
+ "@angular-devkit/schematics": "0.7.5",
+ "@schematics/angular": "0.7.5",
+ "@schematics/update": "0.7.5",
+ "opn": "^5.3.0",
+ "rxjs": "^6.0.0",
+ "semver": "^5.1.0",
+ "symbol-observable": "^1.2.0",
+ "yargs-parser": "^10.0.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
+ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
+ "dev": true
+ },
+ "yargs-parser": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz",
+ "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^4.1.0"
+ }
+ }
+ }
+ },
+ "@angular/common": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@angular/common/-/common-6.1.6.tgz",
+ "integrity": "sha512-aFQcfCB2vFfNqR6/e6R34JjFpIFmF3zqr6Ubti1PJOsRuhITZHG/qRYIYA7mh1KVkkf0VXC56B+8QzYbdGcKOQ==",
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "@angular/compiler": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-6.1.6.tgz",
+ "integrity": "sha512-Z9Og0DVH5krG/xMhfcRJMr5GF2HzqnG3f6Hr+e6d6FB8oehnCX/w9b34zZfVGUWAydAYj32SpXJLE6fQm/ljzA==",
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "@angular/compiler-cli": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-6.1.6.tgz",
+ "integrity": "sha512-CvgQXuuUJDfmCwnuhZec41aMAiY7nJMSMJxvZWNbFLRiwq+05LiHc7EJYDc6uVQmddWmSqGwfyghjVaiaKJGMg==",
+ "dev": true,
+ "requires": {
+ "chokidar": "^1.4.2",
+ "minimist": "^1.2.0",
+ "reflect-metadata": "^0.1.2",
+ "tsickle": "^0.32.1"
+ },
+ "dependencies": {
+ "anymatch": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz",
+ "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==",
+ "dev": true,
+ "requires": {
+ "micromatch": "^2.1.5",
+ "normalize-path": "^2.0.0"
+ }
+ },
+ "arr-diff": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
+ "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "^1.0.1"
+ }
+ },
+ "array-unique": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
+ "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
+ "dev": true
+ },
+ "braces": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz",
+ "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=",
+ "dev": true,
+ "requires": {
+ "expand-range": "^1.8.1",
+ "preserve": "^0.2.0",
+ "repeat-element": "^1.1.2"
+ }
+ },
+ "chokidar": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
+ "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=",
+ "dev": true,
+ "requires": {
+ "anymatch": "^1.3.0",
+ "async-each": "^1.0.0",
+ "fsevents": "^1.0.0",
+ "glob-parent": "^2.0.0",
+ "inherits": "^2.0.1",
+ "is-binary-path": "^1.0.0",
+ "is-glob": "^2.0.0",
+ "path-is-absolute": "^1.0.0",
+ "readdirp": "^2.0.0"
+ }
+ },
+ "expand-brackets": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz",
+ "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=",
+ "dev": true,
+ "requires": {
+ "is-posix-bracket": "^0.1.0"
+ }
+ },
+ "extglob": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz",
+ "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
+ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
+ "dev": true,
+ "requires": {
+ "is-glob": "^2.0.0"
+ }
+ },
+ "is-extglob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+ "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^1.0.0"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ },
+ "micromatch": {
+ "version": "2.3.11",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
+ "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^2.0.0",
+ "array-unique": "^0.2.1",
+ "braces": "^1.8.2",
+ "expand-brackets": "^0.1.4",
+ "extglob": "^0.3.1",
+ "filename-regex": "^2.0.0",
+ "is-extglob": "^1.0.0",
+ "is-glob": "^2.0.1",
+ "kind-of": "^3.0.2",
+ "normalize-path": "^2.0.1",
+ "object.omit": "^2.0.0",
+ "parse-glob": "^3.0.4",
+ "regex-cache": "^0.4.2"
+ }
+ },
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "dev": true
+ }
+ }
+ },
+ "@angular/core": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@angular/core/-/core-6.1.6.tgz",
+ "integrity": "sha512-RFkxNDq8iIfO1SaOuUYqOGD/pujMqifJ9FeVg8M2v7ucW01coXAG0IwqUEMMShQj3GGJGHj+F9BNswN7aD2uvw==",
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "@angular/forms": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-6.1.6.tgz",
+ "integrity": "sha512-6ddk8bhsEtSONctj9PUrEJnTTRL1xHCULaxo2N4GQh5XyV8ScRM0ewOTLcpoL0IU4lgtQmU0VsLWdQvKr3g3Ng==",
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "@angular/http": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@angular/http/-/http-6.1.6.tgz",
+ "integrity": "sha512-V4qF68tUSsc3cKvQERJmpfXgZSKgxhb67I2jAfmwU9mEH66wh9FNfZ0b0GPV9hXoCulw3POz4ZUwZZ1E6mLy4A==",
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "@angular/language-service": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-6.1.6.tgz",
+ "integrity": "sha512-EEtM6mJtiEgmmm3VjzJxv5BavvonaBFtBrPUcevIW851DtIqn4CS8yDcLcGFiSvSLtAYxRX8dkacPv9vvM1Khg==",
+ "dev": true
+ },
+ "@angular/platform-browser": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-6.1.6.tgz",
+ "integrity": "sha512-fwI/w+MhdolVJEfdoCSZFarQo+SctG1pNa+V3PxMkXhxnAbv7oWPQdxzdCrhTWdxJTJ5enSfumMmlJEZtg1bag==",
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "@angular/platform-browser-dynamic": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-6.1.6.tgz",
+ "integrity": "sha512-Ep4vq2ssb1r8XOAw7dJW530vzFKKVY5fj0CYp7VMPfDkwYolEG4TBKQ/ouJkF8n/jdDVFP73+MzU1TLa9/lMQQ==",
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "@angular/router": {
+ "version": "6.1.6",
+ "resolved": "https://registry.npmjs.org/@angular/router/-/router-6.1.6.tgz",
+ "integrity": "sha512-fOFeOe3uBrSRUYhXdWxHjDPf80eq3ZNCeWfujzfBADtcmiezlO7cxc1v5Eu81t577frU/3z+w8JvmF257p4RZg==",
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "@babel/code-frame": {
+ "version": "7.0.0-beta.51",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.51.tgz",
+ "integrity": "sha1-vXHZsZKvl435FYKdOdQJRFZDmgw=",
+ "dev": true,
+ "requires": {
+ "@babel/highlight": "7.0.0-beta.51"
+ }
+ },
+ "@babel/generator": {
+ "version": "7.0.0-beta.51",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.51.tgz",
+ "integrity": "sha1-bHV1/952HQdIXgS67cA5LG2eMPY=",
+ "dev": true,
+ "requires": {
+ "@babel/types": "7.0.0-beta.51",
+ "jsesc": "^2.5.1",
+ "lodash": "^4.17.5",
+ "source-map": "^0.5.0",
+ "trim-right": "^1.0.1"
+ },
+ "dependencies": {
+ "jsesc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz",
+ "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=",
+ "dev": true
+ }
+ }
+ },
+ "@babel/helper-function-name": {
+ "version": "7.0.0-beta.51",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.51.tgz",
+ "integrity": "sha1-IbSHSiJ8+Z7K/MMKkDAtpaJkBWE=",
+ "dev": true,
+ "requires": {
+ "@babel/helper-get-function-arity": "7.0.0-beta.51",
+ "@babel/template": "7.0.0-beta.51",
+ "@babel/types": "7.0.0-beta.51"
+ }
+ },
+ "@babel/helper-get-function-arity": {
+ "version": "7.0.0-beta.51",
+ "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.51.tgz",
+ "integrity": "sha1-MoGy0EWvlcFyzpGyCCXYXqRnZBE=",
+ "dev": true,
+ "requires": {
+ "@babel/types": "7.0.0-beta.51"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.0.0-beta.51",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.51.tgz",
+ "integrity": "sha1-imw/ZsTSZTUvwHdIT59ugKUauXg=",
+ "dev": true,
+ "requires": {
+ "@babel/types": "7.0.0-beta.51"
+ }
+ },
+ "@babel/highlight": {
+ "version": "7.0.0-beta.51",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.51.tgz",
+ "integrity": "sha1-6IRK4loVlcz9QriWI7Q3bKBtIl0=",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.0.0",
+ "esutils": "^2.0.2",
+ "js-tokens": "^3.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.0.0-beta.51",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.0.0-beta.51.tgz",
+ "integrity": "sha1-J87C30Cd9gr1gnDtj2qlVAnqhvY=",
+ "dev": true
+ },
+ "@babel/template": {
+ "version": "7.0.0-beta.51",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.51.tgz",
+ "integrity": "sha1-lgKkCuvPNXrpZ34lMu9fyBD1+/8=",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "7.0.0-beta.51",
+ "@babel/parser": "7.0.0-beta.51",
+ "@babel/types": "7.0.0-beta.51",
+ "lodash": "^4.17.5"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.0.0-beta.51",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.51.tgz",
+ "integrity": "sha1-mB2vLOw0emIx06odnhgDsDqqpKg=",
+ "dev": true,
+ "requires": {
+ "@babel/code-frame": "7.0.0-beta.51",
+ "@babel/generator": "7.0.0-beta.51",
+ "@babel/helper-function-name": "7.0.0-beta.51",
+ "@babel/helper-split-export-declaration": "7.0.0-beta.51",
+ "@babel/parser": "7.0.0-beta.51",
+ "@babel/types": "7.0.0-beta.51",
+ "debug": "^3.1.0",
+ "globals": "^11.1.0",
+ "invariant": "^2.2.0",
+ "lodash": "^4.17.5"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "globals": {
+ "version": "11.7.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.7.0.tgz",
+ "integrity": "sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg==",
+ "dev": true
+ }
+ }
+ },
+ "@babel/types": {
+ "version": "7.0.0-beta.51",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.51.tgz",
+ "integrity": "sha1-2AK3tUO1g2x3iqaReXq/APPZfqk=",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2",
+ "lodash": "^4.17.5",
+ "to-fast-properties": "^2.0.0"
+ },
+ "dependencies": {
+ "to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
+ "dev": true
+ }
+ }
+ },
+ "@ng-bootstrap/ng-bootstrap": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@ng-bootstrap/ng-bootstrap/-/ng-bootstrap-3.2.0.tgz",
+ "integrity": "sha512-P+baWRj0Fs2Hm6ZKN2Mtw/xdC6yeuQ0wv2pXGkI231vUb7Jaso28n+9Qc9HSSkfup2Xpm9WVQzhv8AJ4KUOpyA==",
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "@ngtools/webpack": {
+ "version": "6.1.5",
+ "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-6.1.5.tgz",
+ "integrity": "sha512-vrvFFvUqo4hlrLRBTG7a3gsAneitd0/tj2zHsiN97RmefxHSS+3m0pkVw8G3BMAagp2L42AiVfNV4wvYDe+TXA==",
+ "dev": true,
+ "requires": {
+ "@angular-devkit/core": "0.7.5",
+ "rxjs": "^6.0.0",
+ "tree-kill": "^1.0.0",
+ "webpack-sources": "^1.1.0"
+ }
+ },
+ "@schematics/angular": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-0.7.5.tgz",
+ "integrity": "sha512-NrtvFwHCoWon8KInsvA1jdPu4pVJGa8GAWM/jqnE7HpwPwM7hMML08lV0P8r3NX5t2/i0CKvfp4AAEr5MXorEQ==",
+ "dev": true,
+ "requires": {
+ "@angular-devkit/core": "0.7.5",
+ "@angular-devkit/schematics": "0.7.5",
+ "typescript": ">=2.6.2 <2.10"
+ }
+ },
+ "@schematics/update": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.7.5.tgz",
+ "integrity": "sha512-pwNkXGtlzyCV6tsTPe8AgUuMCkmubcz94zgL6pSMdEe122yXBcKnr/PKqG9QzD/gGwmOcHUE9EWcuRtU5kdFpA==",
+ "dev": true,
+ "requires": {
+ "@angular-devkit/core": "0.7.5",
+ "@angular-devkit/schematics": "0.7.5",
+ "npm-registry-client": "^8.5.1",
+ "rxjs": "^6.0.0",
+ "semver": "^5.3.0",
+ "semver-intersect": "^1.1.2"
+ }
+ },
+ "@types/jasmine": {
+ "version": "2.8.8",
+ "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-2.8.8.tgz",
+ "integrity": "sha512-OJSUxLaxXsjjhob2DBzqzgrkLmukM3+JMpRp0r0E4HTdT1nwDCWhaswjYxazPij6uOdzHCJfNbDjmQ1/rnNbCg==",
+ "dev": true
+ },
+ "@types/jasminewd2": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/jasminewd2/-/jasminewd2-2.0.3.tgz",
+ "integrity": "sha512-hYDVmQZT5VA2kigd4H4bv7vl/OhlympwREUemqBdOqtrYTo5Ytm12a5W5/nGgGYdanGVxj0x/VhZ7J3hOg/YKg==",
+ "dev": true,
+ "requires": {
+ "@types/jasmine": "*"
+ }
+ },
+ "@types/node": {
+ "version": "8.9.5",
+ "resolved": "http://registry.npmjs.org/@types/node/-/node-8.9.5.tgz",
+ "integrity": "sha512-jRHfWsvyMtXdbhnz5CVHxaBgnV6duZnPlQuRSo/dm/GnmikNcmZhxIES4E9OZjUmQ8C+HCl4KJux+cXN/ErGDQ==",
+ "dev": true
+ },
+ "@types/q": {
+ "version": "0.0.32",
+ "resolved": "https://registry.npmjs.org/@types/q/-/q-0.0.32.tgz",
+ "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=",
+ "dev": true
+ },
+ "@types/selenium-webdriver": {
+ "version": "3.0.10",
+ "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.10.tgz",
+ "integrity": "sha512-ikB0JHv6vCR1KYUQAzTO4gi/lXLElT4Tx+6De2pc/OZwizE9LRNiTa+U8TBFKBD/nntPnr/MPSHSnOTybjhqNA==",
+ "dev": true
+ },
+ "@types/xmldom": {
+ "version": "0.1.29",
+ "resolved": "https://registry.npmjs.org/@types/xmldom/-/xmldom-0.1.29.tgz",
+ "integrity": "sha1-xEKLDKhtO4gUdXJv2UmAs4onw4E=",
+ "dev": true
+ },
+ "@webassemblyjs/ast": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.4.3.tgz",
+ "integrity": "sha512-S6npYhPcTHDYe9nlsKa9CyWByFi8Vj8HovcAgtmMAQZUOczOZbQ8CnwMYKYC5HEZzxEE+oY0jfQk4cVlI3J59Q==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/helper-wasm-bytecode": "1.4.3",
+ "@webassemblyjs/wast-parser": "1.4.3",
+ "debug": "^3.1.0",
+ "webassemblyjs": "1.4.3"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "@webassemblyjs/floating-point-hex-parser": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.4.3.tgz",
+ "integrity": "sha512-3zTkSFswwZOPNHnzkP9ONq4bjJSeKVMcuahGXubrlLmZP8fmTIJ58dW7h/zOVWiFSuG2em3/HH3BlCN7wyu9Rw==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-buffer": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.4.3.tgz",
+ "integrity": "sha512-e8+KZHh+RV8MUvoSRtuT1sFXskFnWG9vbDy47Oa166xX+l0dD5sERJ21g5/tcH8Yo95e9IN3u7Jc3NbhnUcSkw==",
+ "dev": true,
+ "requires": {
+ "debug": "^3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "@webassemblyjs/helper-code-frame": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.4.3.tgz",
+ "integrity": "sha512-9FgHEtNsZQYaKrGCtsjswBil48Qp1agrzRcPzCbQloCoaTbOXLJ9IRmqT+uEZbenpULLRNFugz3I4uw18hJM8w==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/wast-printer": "1.4.3"
+ }
+ },
+ "@webassemblyjs/helper-fsm": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.4.3.tgz",
+ "integrity": "sha512-JINY76U+702IRf7ePukOt037RwmtH59JHvcdWbTTyHi18ixmQ+uOuNhcdCcQHTquDAH35/QgFlp3Y9KqtyJsCQ==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-wasm-bytecode": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.4.3.tgz",
+ "integrity": "sha512-I7bS+HaO0K07Io89qhJv+z1QipTpuramGwUSDkwEaficbSvCcL92CUZEtgykfNtk5wb0CoLQwWlmXTwGbNZUeQ==",
+ "dev": true
+ },
+ "@webassemblyjs/helper-wasm-section": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.4.3.tgz",
+ "integrity": "sha512-p0yeeO/h2r30PyjnJX9xXSR6EDcvJd/jC6xa/Pxg4lpfcNi7JUswOpqDToZQ55HMMVhXDih/yqkaywHWGLxqyQ==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.4.3",
+ "@webassemblyjs/helper-buffer": "1.4.3",
+ "@webassemblyjs/helper-wasm-bytecode": "1.4.3",
+ "@webassemblyjs/wasm-gen": "1.4.3",
+ "debug": "^3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "@webassemblyjs/leb128": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.4.3.tgz",
+ "integrity": "sha512-4u0LJLSPzuRDWHwdqsrThYn+WqMFVqbI2ltNrHvZZkzFPO8XOZ0HFQ5eVc4jY/TNHgXcnwrHjONhPGYuuf//KQ==",
+ "dev": true,
+ "requires": {
+ "leb": "^0.3.0"
+ }
+ },
+ "@webassemblyjs/validation": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/validation/-/validation-1.4.3.tgz",
+ "integrity": "sha512-R+rRMKfhd9mq0rj2mhU9A9NKI2l/Rw65vIYzz4lui7eTKPcCu1l7iZNi4b9Gen8D42Sqh/KGiaQNk/x5Tn/iBQ==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.4.3"
+ }
+ },
+ "@webassemblyjs/wasm-edit": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.4.3.tgz",
+ "integrity": "sha512-qzuwUn771PV6/LilqkXcS0ozJYAeY/OKbXIWU3a8gexuqb6De2p4ya/baBeH5JQ2WJdfhWhSvSbu86Vienttpw==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.4.3",
+ "@webassemblyjs/helper-buffer": "1.4.3",
+ "@webassemblyjs/helper-wasm-bytecode": "1.4.3",
+ "@webassemblyjs/helper-wasm-section": "1.4.3",
+ "@webassemblyjs/wasm-gen": "1.4.3",
+ "@webassemblyjs/wasm-opt": "1.4.3",
+ "@webassemblyjs/wasm-parser": "1.4.3",
+ "@webassemblyjs/wast-printer": "1.4.3",
+ "debug": "^3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "@webassemblyjs/wasm-gen": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.4.3.tgz",
+ "integrity": "sha512-eR394T8dHZfpLJ7U/Z5pFSvxl1L63JdREebpv9gYc55zLhzzdJPAuxjBYT4XqevUdW67qU2s0nNA3kBuNJHbaQ==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.4.3",
+ "@webassemblyjs/helper-wasm-bytecode": "1.4.3",
+ "@webassemblyjs/leb128": "1.4.3"
+ }
+ },
+ "@webassemblyjs/wasm-opt": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.4.3.tgz",
+ "integrity": "sha512-7Gp+nschuKiDuAL1xmp4Xz0rgEbxioFXw4nCFYEmy+ytynhBnTeGc9W9cB1XRu1w8pqRU2lbj2VBBA4cL5Z2Kw==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.4.3",
+ "@webassemblyjs/helper-buffer": "1.4.3",
+ "@webassemblyjs/wasm-gen": "1.4.3",
+ "@webassemblyjs/wasm-parser": "1.4.3",
+ "debug": "^3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "@webassemblyjs/wasm-parser": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.4.3.tgz",
+ "integrity": "sha512-KXBjtlwA3BVukR/yWHC9GF+SCzBcgj0a7lm92kTOaa4cbjaTaa47bCjXw6cX4SGQpkncB9PU2hHGYVyyI7wFRg==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.4.3",
+ "@webassemblyjs/helper-wasm-bytecode": "1.4.3",
+ "@webassemblyjs/leb128": "1.4.3",
+ "@webassemblyjs/wasm-parser": "1.4.3",
+ "webassemblyjs": "1.4.3"
+ }
+ },
+ "@webassemblyjs/wast-parser": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.4.3.tgz",
+ "integrity": "sha512-QhCsQzqV0CpsEkRYyTzQDilCNUZ+5j92f+g35bHHNqS22FppNTywNFfHPq8ZWZfYCgbectc+PoghD+xfzVFh1Q==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.4.3",
+ "@webassemblyjs/floating-point-hex-parser": "1.4.3",
+ "@webassemblyjs/helper-code-frame": "1.4.3",
+ "@webassemblyjs/helper-fsm": "1.4.3",
+ "long": "^3.2.0",
+ "webassemblyjs": "1.4.3"
+ }
+ },
+ "@webassemblyjs/wast-printer": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.4.3.tgz",
+ "integrity": "sha512-EgXk4anf8jKmuZJsqD8qy5bz2frEQhBvZruv+bqwNoLWUItjNSFygk8ywL3JTEz9KtxTlAmqTXNrdD1d9gNDtg==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.4.3",
+ "@webassemblyjs/wast-parser": "1.4.3",
+ "long": "^3.2.0"
+ }
+ },
+ "abbrev": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz",
+ "integrity": "sha1-kbR5JYinc4wl813W9jdSovh3YTU=",
+ "dev": true
+ },
+ "accepts": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
+ "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
+ "dev": true,
+ "requires": {
+ "mime-types": "~2.1.18",
+ "negotiator": "0.6.1"
+ }
+ },
+ "acorn": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.2.tgz",
+ "integrity": "sha512-cJrKCNcr2kv8dlDnbw+JPUGjHZzo4myaxOLmpOX8a+rgX94YeTcTMv/LFJUSByRpc+i4GgVnnhLxvMu/2Y+rqw==",
+ "dev": true
+ },
+ "acorn-dynamic-import": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz",
+ "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==",
+ "dev": true,
+ "requires": {
+ "acorn": "^5.0.0"
+ }
+ },
+ "adm-zip": {
+ "version": "0.4.11",
+ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.11.tgz",
+ "integrity": "sha512-L8vcjDTCOIJk7wFvmlEUN7AsSb8T+2JrdP7KINBjzr24TJ5Mwj590sLu3BC7zNZowvJWa/JtPmD8eJCzdtDWjA==",
+ "dev": true
+ },
+ "after": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
+ "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=",
+ "dev": true
+ },
+ "agent-base": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
+ "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
+ "dev": true,
+ "requires": {
+ "es6-promisify": "^5.0.0"
+ }
+ },
+ "ajv": {
+ "version": "6.4.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.4.0.tgz",
+ "integrity": "sha1-06/3jpJ3VJdx2vAWTP9ISCt1T8Y=",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^1.0.0",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.3.0",
+ "uri-js": "^3.0.2"
+ }
+ },
+ "ajv-errors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.0.tgz",
+ "integrity": "sha1-7PAh+hCP0X37Xms4Py3SM+Mf/Fk=",
+ "dev": true
+ },
+ "ajv-keywords": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz",
+ "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=",
+ "dev": true
+ },
+ "amdefine": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
+ "dev": true
+ },
+ "ansi-colors": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.0.5.tgz",
+ "integrity": "sha512-VVjWpkfaphxUBFarydrQ3n26zX5nIK7hcbT3/ielrvwDDyBBjuh2vuSw1P9zkPq0cfqvdw7lkYHnu+OLSfIBsg==",
+ "dev": true
+ },
+ "ansi-html": {
+ "version": "0.0.7",
+ "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz",
+ "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=",
+ "dev": true
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "anymatch": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz",
+ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==",
+ "dev": true,
+ "requires": {
+ "micromatch": "^3.1.4",
+ "normalize-path": "^2.1.1"
+ }
+ },
+ "app-root-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.1.0.tgz",
+ "integrity": "sha1-mL9lmTJ+zqGZMJhm6BQDaP0uZGo=",
+ "dev": true
+ },
+ "append-transform": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz",
+ "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==",
+ "dev": true,
+ "requires": {
+ "default-require-extensions": "^2.0.0"
+ }
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
+ "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
+ "dev": true
+ },
+ "are-we-there-yet": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
+ "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
+ "dev": true,
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^2.0.6"
+ }
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "arr-diff": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
+ "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=",
+ "dev": true
+ },
+ "arr-flatten": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz",
+ "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==",
+ "dev": true
+ },
+ "arr-union": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz",
+ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=",
+ "dev": true
+ },
+ "array-find-index": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
+ "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=",
+ "dev": true
+ },
+ "array-flatten": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.1.tgz",
+ "integrity": "sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=",
+ "dev": true
+ },
+ "array-slice": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz",
+ "integrity": "sha1-3Tz7gO15c6dRF82sabC5nshhhvU=",
+ "dev": true
+ },
+ "array-union": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+ "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
+ "dev": true,
+ "requires": {
+ "array-uniq": "^1.0.1"
+ }
+ },
+ "array-uniq": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+ "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
+ "dev": true
+ },
+ "array-unique": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
+ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=",
+ "dev": true
+ },
+ "arraybuffer.slice": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz",
+ "integrity": "sha1-8zshWfBTKj8xB6JywMz70a0peco=",
+ "dev": true
+ },
+ "arrify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=",
+ "dev": true
+ },
+ "asap": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=",
+ "dev": true,
+ "optional": true
+ },
+ "asn1": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+ "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+ "dev": true,
+ "requires": {
+ "safer-buffer": "~2.1.0"
+ }
+ },
+ "asn1.js": {
+ "version": "4.10.1",
+ "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz",
+ "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
+ "assert": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/assert/-/assert-1.4.1.tgz",
+ "integrity": "sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE=",
+ "dev": true,
+ "requires": {
+ "util": "0.10.3"
+ },
+ "dependencies": {
+ "inherits": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
+ "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
+ "dev": true
+ },
+ "util": {
+ "version": "0.10.3",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
+ "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.1"
+ }
+ }
+ }
+ },
+ "assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+ "dev": true
+ },
+ "assign-symbols": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
+ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=",
+ "dev": true
+ },
+ "async": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
+ "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
+ "dev": true
+ },
+ "async-each": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz",
+ "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=",
+ "dev": true
+ },
+ "async-foreach": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
+ "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=",
+ "dev": true,
+ "optional": true
+ },
+ "asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+ "dev": true
+ },
+ "atob": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
+ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
+ "dev": true
+ },
+ "autoprefixer": {
+ "version": "8.6.5",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-8.6.5.tgz",
+ "integrity": "sha512-PLWJN3Xo/rycNkx+mp8iBDMTm3FeWe4VmYaZDSqL5QQB9sLsQkG5k8n+LNDFnhh9kdq2K+egL/icpctOmDHwig==",
+ "dev": true,
+ "requires": {
+ "browserslist": "^3.2.8",
+ "caniuse-lite": "^1.0.30000864",
+ "normalize-range": "^0.1.2",
+ "num2fraction": "^1.2.2",
+ "postcss": "^6.0.23",
+ "postcss-value-parser": "^3.2.3"
+ }
+ },
+ "aws-sign2": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
+ "dev": true
+ },
+ "aws4": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
+ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
+ "dev": true
+ },
+ "babel-code-frame": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
+ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=",
+ "dev": true,
+ "requires": {
+ "chalk": "^1.1.3",
+ "esutils": "^2.0.2",
+ "js-tokens": "^3.0.2"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "dev": true
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+ "dev": true
+ }
+ }
+ },
+ "babel-generator": {
+ "version": "6.26.1",
+ "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz",
+ "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==",
+ "dev": true,
+ "requires": {
+ "babel-messages": "^6.23.0",
+ "babel-runtime": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "detect-indent": "^4.0.0",
+ "jsesc": "^1.3.0",
+ "lodash": "^4.17.4",
+ "source-map": "^0.5.7",
+ "trim-right": "^1.0.1"
+ }
+ },
+ "babel-messages": {
+ "version": "6.23.0",
+ "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz",
+ "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.22.0"
+ }
+ },
+ "babel-runtime": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
+ "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
+ "dev": true,
+ "requires": {
+ "core-js": "^2.4.0",
+ "regenerator-runtime": "^0.11.0"
+ }
+ },
+ "babel-template": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz",
+ "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "babel-traverse": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "babylon": "^6.18.0",
+ "lodash": "^4.17.4"
+ }
+ },
+ "babel-traverse": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz",
+ "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=",
+ "dev": true,
+ "requires": {
+ "babel-code-frame": "^6.26.0",
+ "babel-messages": "^6.23.0",
+ "babel-runtime": "^6.26.0",
+ "babel-types": "^6.26.0",
+ "babylon": "^6.18.0",
+ "debug": "^2.6.8",
+ "globals": "^9.18.0",
+ "invariant": "^2.2.2",
+ "lodash": "^4.17.4"
+ }
+ },
+ "babel-types": {
+ "version": "6.26.0",
+ "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz",
+ "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=",
+ "dev": true,
+ "requires": {
+ "babel-runtime": "^6.26.0",
+ "esutils": "^2.0.2",
+ "lodash": "^4.17.4",
+ "to-fast-properties": "^1.0.3"
+ }
+ },
+ "babylon": {
+ "version": "6.18.0",
+ "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
+ "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
+ "dev": true
+ },
+ "backo2": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
+ "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=",
+ "dev": true
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true
+ },
+ "base": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz",
+ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==",
+ "dev": true,
+ "requires": {
+ "cache-base": "^1.0.1",
+ "class-utils": "^0.3.5",
+ "component-emitter": "^1.2.1",
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.1",
+ "mixin-deep": "^1.2.0",
+ "pascalcase": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "base64-arraybuffer": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
+ "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=",
+ "dev": true
+ },
+ "base64-js": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz",
+ "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==",
+ "dev": true
+ },
+ "base64id": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/base64id/-/base64id-1.0.0.tgz",
+ "integrity": "sha1-R2iMuZu2gE8OBtPnY7HDLlfY5rY=",
+ "dev": true
+ },
+ "batch": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
+ "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=",
+ "dev": true
+ },
+ "bcrypt-pbkdf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+ "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "tweetnacl": "^0.14.3"
+ }
+ },
+ "better-assert": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
+ "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
+ "dev": true,
+ "requires": {
+ "callsite": "1.0.0"
+ }
+ },
+ "big.js": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz",
+ "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==",
+ "dev": true
+ },
+ "binary-extensions": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz",
+ "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=",
+ "dev": true
+ },
+ "blob": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.4.tgz",
+ "integrity": "sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=",
+ "dev": true
+ },
+ "block-stream": {
+ "version": "0.0.9",
+ "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
+ "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "inherits": "~2.0.0"
+ }
+ },
+ "blocking-proxy": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/blocking-proxy/-/blocking-proxy-1.0.1.tgz",
+ "integrity": "sha512-KE8NFMZr3mN2E0HcvCgRtX7DjhiIQrwle+nSVJVC/yqFb9+xznHl2ZcoBp2L9qzkI4t4cBFJ1efXF8Dwi132RA==",
+ "dev": true,
+ "requires": {
+ "minimist": "^1.2.0"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "dev": true
+ }
+ }
+ },
+ "bluebird": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz",
+ "integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==",
+ "dev": true
+ },
+ "bn.js": {
+ "version": "4.11.8",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
+ "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
+ "dev": true
+ },
+ "body-parser": {
+ "version": "1.18.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz",
+ "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=",
+ "dev": true,
+ "requires": {
+ "bytes": "3.0.0",
+ "content-type": "~1.0.4",
+ "debug": "2.6.9",
+ "depd": "~1.1.1",
+ "http-errors": "~1.6.2",
+ "iconv-lite": "0.4.19",
+ "on-finished": "~2.3.0",
+ "qs": "6.5.1",
+ "raw-body": "2.3.2",
+ "type-is": "~1.6.15"
+ },
+ "dependencies": {
+ "qs": {
+ "version": "6.5.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
+ "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==",
+ "dev": true
+ }
+ }
+ },
+ "bonjour": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz",
+ "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=",
+ "dev": true,
+ "requires": {
+ "array-flatten": "^2.1.0",
+ "deep-equal": "^1.0.1",
+ "dns-equal": "^1.0.0",
+ "dns-txt": "^2.0.2",
+ "multicast-dns": "^6.0.1",
+ "multicast-dns-service-types": "^1.1.0"
+ }
+ },
+ "boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
+ "dev": true
+ },
+ "bootstrap-css-only": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/bootstrap-css-only/-/bootstrap-css-only-4.1.1.tgz",
+ "integrity": "sha512-I/zU5T/KANTy+NNmsNC4lESiHWrdLtvgp9gutOVqbDKY0d4rycmX9fUp1jkFZ0vNd6dhY8oxY9j8RWZkjHVAuA=="
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
+ "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "^1.1.0",
+ "array-unique": "^0.3.2",
+ "extend-shallow": "^2.0.1",
+ "fill-range": "^4.0.0",
+ "isobject": "^3.0.1",
+ "repeat-element": "^1.1.2",
+ "snapdragon": "^0.8.1",
+ "snapdragon-node": "^2.0.1",
+ "split-string": "^3.0.2",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "brorand": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
+ "dev": true
+ },
+ "browserify-aes": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
+ "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==",
+ "dev": true,
+ "requires": {
+ "buffer-xor": "^1.0.3",
+ "cipher-base": "^1.0.0",
+ "create-hash": "^1.1.0",
+ "evp_bytestokey": "^1.0.3",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "browserify-cipher": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz",
+ "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==",
+ "dev": true,
+ "requires": {
+ "browserify-aes": "^1.0.4",
+ "browserify-des": "^1.0.0",
+ "evp_bytestokey": "^1.0.0"
+ }
+ },
+ "browserify-des": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz",
+ "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.1",
+ "des.js": "^1.0.0",
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.1.2"
+ }
+ },
+ "browserify-rsa": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz",
+ "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "randombytes": "^2.0.1"
+ }
+ },
+ "browserify-sign": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.0.4.tgz",
+ "integrity": "sha1-qk62jl17ZYuqa/alfmMMvXqT0pg=",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.1",
+ "browserify-rsa": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "create-hmac": "^1.1.2",
+ "elliptic": "^6.0.0",
+ "inherits": "^2.0.1",
+ "parse-asn1": "^5.0.0"
+ }
+ },
+ "browserify-zlib": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz",
+ "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==",
+ "dev": true,
+ "requires": {
+ "pako": "~1.0.5"
+ }
+ },
+ "browserslist": {
+ "version": "3.2.8",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz",
+ "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==",
+ "dev": true,
+ "requires": {
+ "caniuse-lite": "^1.0.30000844",
+ "electron-to-chromium": "^1.3.47"
+ }
+ },
+ "browserstack": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.1.tgz",
+ "integrity": "sha512-O8VMT64P9NOLhuIoD4YngyxBURefaSdR4QdhG8l6HZ9VxtU7jc3m6jLufFwKA5gaf7fetfB2TnRJnMxyob+heg==",
+ "dev": true,
+ "requires": {
+ "https-proxy-agent": "^2.2.1"
+ }
+ },
+ "buffer": {
+ "version": "4.9.1",
+ "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
+ "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
+ "dev": true,
+ "requires": {
+ "base64-js": "^1.0.2",
+ "ieee754": "^1.1.4",
+ "isarray": "^1.0.0"
+ }
+ },
+ "buffer-alloc": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz",
+ "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==",
+ "dev": true,
+ "requires": {
+ "buffer-alloc-unsafe": "^1.1.0",
+ "buffer-fill": "^1.0.0"
+ }
+ },
+ "buffer-alloc-unsafe": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz",
+ "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==",
+ "dev": true
+ },
+ "buffer-fill": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz",
+ "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=",
+ "dev": true
+ },
+ "buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+ "dev": true
+ },
+ "buffer-indexof": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-indexof/-/buffer-indexof-1.1.1.tgz",
+ "integrity": "sha512-4/rOEg86jivtPTeOUUT61jJO1Ya1TrR/OkqCSZDyq84WJh3LuuiphBYJN+fm5xufIk4XAFcEwte/8WzC8If/1g==",
+ "dev": true
+ },
+ "buffer-xor": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz",
+ "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=",
+ "dev": true
+ },
+ "builtin-modules": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+ "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=",
+ "dev": true
+ },
+ "builtin-status-codes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz",
+ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=",
+ "dev": true
+ },
+ "builtins": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz",
+ "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=",
+ "dev": true
+ },
+ "bytes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+ "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=",
+ "dev": true
+ },
+ "cacache": {
+ "version": "10.0.4",
+ "resolved": "https://registry.npmjs.org/cacache/-/cacache-10.0.4.tgz",
+ "integrity": "sha512-Dph0MzuH+rTQzGPNT9fAnrPmMmjKfST6trxJeK7NQuHRaVw24VzPRWTmg9MpcwOVQZO0E1FBICUlFeNaKPIfHA==",
+ "dev": true,
+ "requires": {
+ "bluebird": "^3.5.1",
+ "chownr": "^1.0.1",
+ "glob": "^7.1.2",
+ "graceful-fs": "^4.1.11",
+ "lru-cache": "^4.1.1",
+ "mississippi": "^2.0.0",
+ "mkdirp": "^0.5.1",
+ "move-concurrently": "^1.0.1",
+ "promise-inflight": "^1.0.1",
+ "rimraf": "^2.6.2",
+ "ssri": "^5.2.4",
+ "unique-filename": "^1.1.0",
+ "y18n": "^4.0.0"
+ }
+ },
+ "cache-base": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz",
+ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==",
+ "dev": true,
+ "requires": {
+ "collection-visit": "^1.0.0",
+ "component-emitter": "^1.2.1",
+ "get-value": "^2.0.6",
+ "has-value": "^1.0.0",
+ "isobject": "^3.0.1",
+ "set-value": "^2.0.0",
+ "to-object-path": "^0.3.0",
+ "union-value": "^1.0.0",
+ "unset-value": "^1.0.0"
+ }
+ },
+ "callsite": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
+ "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=",
+ "dev": true
+ },
+ "camel-case": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
+ "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=",
+ "dev": true,
+ "requires": {
+ "no-case": "^2.2.0",
+ "upper-case": "^1.1.1"
+ }
+ },
+ "camelcase": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
+ "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=",
+ "dev": true,
+ "optional": true
+ },
+ "camelcase-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz",
+ "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "camelcase": "^2.0.0",
+ "map-obj": "^1.0.0"
+ }
+ },
+ "caniuse-lite": {
+ "version": "1.0.30000884",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000884.tgz",
+ "integrity": "sha512-ibROerckpTH6U5zReSjbaitlH4gl5V4NWNCBzRNCa3GEDmzzkfStk+2k5mO4ZDM6pwtdjbZ3hjvsYhPGVLWgNw==",
+ "dev": true
+ },
+ "caseless": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
+ "dev": true
+ },
+ "chalk": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
+ "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "chokidar": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz",
+ "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==",
+ "dev": true,
+ "requires": {
+ "anymatch": "^2.0.0",
+ "async-each": "^1.0.0",
+ "braces": "^2.3.0",
+ "fsevents": "^1.2.2",
+ "glob-parent": "^3.1.0",
+ "inherits": "^2.0.1",
+ "is-binary-path": "^1.0.0",
+ "is-glob": "^4.0.0",
+ "lodash.debounce": "^4.0.8",
+ "normalize-path": "^2.1.1",
+ "path-is-absolute": "^1.0.0",
+ "readdirp": "^2.0.0",
+ "upath": "^1.0.5"
+ }
+ },
+ "chownr": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz",
+ "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=",
+ "dev": true
+ },
+ "chrome-trace-event": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-0.1.3.tgz",
+ "integrity": "sha512-sjndyZHrrWiu4RY7AkHgjn80GfAM2ZSzUkZLV/Js59Ldmh6JDThf0SUmOHU53rFu2rVxxfCzJ30Ukcfch3Gb/A==",
+ "dev": true
+ },
+ "cipher-base": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
+ "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "circular-dependency-plugin": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.0.2.tgz",
+ "integrity": "sha512-oC7/DVAyfcY3UWKm0sN/oVoDedQDQiw/vIiAnuTWTpE5s0zWf7l3WY417Xw/Fbi/QbAjctAkxgMiS9P0s3zkmA==",
+ "dev": true
+ },
+ "class-utils": {
+ "version": "0.3.6",
+ "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
+ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==",
+ "dev": true,
+ "requires": {
+ "arr-union": "^3.1.0",
+ "define-property": "^0.2.5",
+ "isobject": "^3.0.0",
+ "static-extend": "^0.1.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "clean-css": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz",
+ "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==",
+ "dev": true,
+ "requires": {
+ "source-map": "~0.6.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "cliui": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
+ "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wrap-ansi": "^2.0.0"
+ }
+ },
+ "clone": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+ "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=",
+ "dev": true
+ },
+ "clone-deep": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-2.0.2.tgz",
+ "integrity": "sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==",
+ "dev": true,
+ "requires": {
+ "for-own": "^1.0.0",
+ "is-plain-object": "^2.0.4",
+ "kind-of": "^6.0.0",
+ "shallow-clone": "^1.0.0"
+ }
+ },
+ "co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
+ "dev": true
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+ "dev": true
+ },
+ "codelyzer": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/codelyzer/-/codelyzer-4.2.1.tgz",
+ "integrity": "sha512-CKwfgpfkqi9dyzy4s6ELaxJ54QgJ6A8iTSsM4bzHbLuTpbKncvNc3DUlCvpnkHBhK47gEf4qFsWoYqLrJPhy6g==",
+ "dev": true,
+ "requires": {
+ "app-root-path": "^2.0.1",
+ "css-selector-tokenizer": "^0.7.0",
+ "cssauron": "^1.4.0",
+ "semver-dsl": "^1.0.1",
+ "source-map": "^0.5.6",
+ "sprintf-js": "^1.0.3"
+ }
+ },
+ "collection-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
+ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=",
+ "dev": true,
+ "requires": {
+ "map-visit": "^1.0.0",
+ "object-visit": "^1.0.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
+ "dev": true
+ },
+ "colors": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
+ "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
+ "dev": true
+ },
+ "combine-lists": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz",
+ "integrity": "sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.5.0"
+ }
+ },
+ "combined-stream": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
+ "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
+ "dev": true,
+ "requires": {
+ "delayed-stream": "~1.0.0"
+ }
+ },
+ "commander": {
+ "version": "2.17.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
+ "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
+ "dev": true
+ },
+ "commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+ "dev": true
+ },
+ "compare-versions": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.4.0.tgz",
+ "integrity": "sha512-tK69D7oNXXqUW3ZNo/z7NXTEz22TCF0pTE+YF9cxvaAM9XnkLo1fV621xCLrRR6aevJlKxExkss0vWqUCUpqdg==",
+ "dev": true
+ },
+ "component-bind": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
+ "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=",
+ "dev": true
+ },
+ "component-emitter": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
+ "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
+ "dev": true
+ },
+ "component-inherit": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
+ "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=",
+ "dev": true
+ },
+ "compressible": {
+ "version": "2.0.14",
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.14.tgz",
+ "integrity": "sha1-MmxfUH+7BV9UEWeCuWmoG2einac=",
+ "dev": true,
+ "requires": {
+ "mime-db": ">= 1.34.0 < 2"
+ }
+ },
+ "compression": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.3.tgz",
+ "integrity": "sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==",
+ "dev": true,
+ "requires": {
+ "accepts": "~1.3.5",
+ "bytes": "3.0.0",
+ "compressible": "~2.0.14",
+ "debug": "2.6.9",
+ "on-headers": "~1.0.1",
+ "safe-buffer": "5.1.2",
+ "vary": "~1.1.2"
+ }
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "concat-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ }
+ },
+ "connect": {
+ "version": "3.6.6",
+ "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz",
+ "integrity": "sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ=",
+ "dev": true,
+ "requires": {
+ "debug": "2.6.9",
+ "finalhandler": "1.1.0",
+ "parseurl": "~1.3.2",
+ "utils-merge": "1.0.1"
+ },
+ "dependencies": {
+ "finalhandler": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz",
+ "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=",
+ "dev": true,
+ "requires": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.1",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.2",
+ "statuses": "~1.3.1",
+ "unpipe": "~1.0.0"
+ }
+ },
+ "statuses": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
+ "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=",
+ "dev": true
+ }
+ }
+ },
+ "connect-history-api-fallback": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz",
+ "integrity": "sha1-sGhzk0vF40T+9hGhlqb6rgruAVo=",
+ "dev": true
+ },
+ "console-browserify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
+ "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
+ "dev": true,
+ "requires": {
+ "date-now": "^0.1.4"
+ }
+ },
+ "console-control-strings": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+ "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
+ "dev": true
+ },
+ "constants-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz",
+ "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=",
+ "dev": true
+ },
+ "content-disposition": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+ "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=",
+ "dev": true
+ },
+ "content-type": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+ "dev": true
+ },
+ "convert-source-map": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz",
+ "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.1"
+ }
+ },
+ "cookie": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+ "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=",
+ "dev": true
+ },
+ "cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=",
+ "dev": true
+ },
+ "copy-concurrently": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
+ "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==",
+ "dev": true,
+ "requires": {
+ "aproba": "^1.1.1",
+ "fs-write-stream-atomic": "^1.0.8",
+ "iferr": "^0.1.5",
+ "mkdirp": "^0.5.1",
+ "rimraf": "^2.5.4",
+ "run-queue": "^1.0.0"
+ }
+ },
+ "copy-descriptor": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz",
+ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
+ "dev": true
+ },
+ "copy-webpack-plugin": {
+ "version": "4.5.2",
+ "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-4.5.2.tgz",
+ "integrity": "sha512-zmC33E8FFSq3AbflTvqvPvBo621H36Afsxlui91d+QyZxPIuXghfnTsa1CuqiAaCPgJoSUWfTFbKJnadZpKEbQ==",
+ "dev": true,
+ "requires": {
+ "cacache": "^10.0.4",
+ "find-cache-dir": "^1.0.0",
+ "globby": "^7.1.1",
+ "is-glob": "^4.0.0",
+ "loader-utils": "^1.1.0",
+ "minimatch": "^3.0.4",
+ "p-limit": "^1.0.0",
+ "serialize-javascript": "^1.4.0"
+ }
+ },
+ "core-js": {
+ "version": "2.5.7",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz",
+ "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw=="
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+ "dev": true
+ },
+ "cosmiconfig": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-4.0.0.tgz",
+ "integrity": "sha512-6e5vDdrXZD+t5v0L8CrurPeybg4Fmf+FCSYxXKYVAqLUtyCSbuyqE059d0kDthTNRzKVjL7QMgNpEUlsoYH3iQ==",
+ "dev": true,
+ "requires": {
+ "is-directory": "^0.3.1",
+ "js-yaml": "^3.9.0",
+ "parse-json": "^4.0.0",
+ "require-from-string": "^2.0.1"
+ },
+ "dependencies": {
+ "parse-json": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+ "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+ "dev": true,
+ "requires": {
+ "error-ex": "^1.3.1",
+ "json-parse-better-errors": "^1.0.1"
+ }
+ }
+ }
+ },
+ "create-ecdh": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
+ "integrity": "sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "elliptic": "^6.0.0"
+ }
+ },
+ "create-hash": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
+ "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.1",
+ "inherits": "^2.0.1",
+ "md5.js": "^1.3.4",
+ "ripemd160": "^2.0.1",
+ "sha.js": "^2.4.0"
+ }
+ },
+ "create-hmac": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz",
+ "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==",
+ "dev": true,
+ "requires": {
+ "cipher-base": "^1.0.3",
+ "create-hash": "^1.1.0",
+ "inherits": "^2.0.1",
+ "ripemd160": "^2.0.0",
+ "safe-buffer": "^5.0.1",
+ "sha.js": "^2.4.8"
+ }
+ },
+ "cross-spawn": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz",
+ "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "lru-cache": "^4.0.1",
+ "which": "^1.2.9"
+ }
+ },
+ "crypto-browserify": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
+ "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
+ "dev": true,
+ "requires": {
+ "browserify-cipher": "^1.0.0",
+ "browserify-sign": "^4.0.0",
+ "create-ecdh": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "create-hmac": "^1.1.0",
+ "diffie-hellman": "^5.0.0",
+ "inherits": "^2.0.1",
+ "pbkdf2": "^3.0.3",
+ "public-encrypt": "^4.0.0",
+ "randombytes": "^2.0.0",
+ "randomfill": "^1.0.3"
+ }
+ },
+ "css-parse": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-1.7.0.tgz",
+ "integrity": "sha1-Mh9s9zeCpv91ERE5D8BeLGV9jJs=",
+ "dev": true
+ },
+ "css-select": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
+ "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
+ "dev": true,
+ "requires": {
+ "boolbase": "~1.0.0",
+ "css-what": "2.1",
+ "domutils": "1.5.1",
+ "nth-check": "~1.0.1"
+ }
+ },
+ "css-selector-tokenizer": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz",
+ "integrity": "sha1-5piEdK6MlTR3v15+/s/OzNnPTIY=",
+ "dev": true,
+ "requires": {
+ "cssesc": "^0.1.0",
+ "fastparse": "^1.1.1",
+ "regexpu-core": "^1.0.0"
+ }
+ },
+ "css-what": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz",
+ "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=",
+ "dev": true
+ },
+ "cssauron": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/cssauron/-/cssauron-1.4.0.tgz",
+ "integrity": "sha1-pmAt/34EqDBtwNuaVR6S6LVmKtg=",
+ "dev": true,
+ "requires": {
+ "through": "X.X.X"
+ }
+ },
+ "cssesc": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz",
+ "integrity": "sha1-yBSQPkViM3GgR3tAEJqq++6t27Q=",
+ "dev": true
+ },
+ "cuint": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/cuint/-/cuint-0.2.2.tgz",
+ "integrity": "sha1-QICG1AlVDCYxFVYZ6fp7ytw7mRs=",
+ "dev": true
+ },
+ "currently-unhandled": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
+ "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=",
+ "dev": true,
+ "requires": {
+ "array-find-index": "^1.0.1"
+ }
+ },
+ "custom-event": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz",
+ "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=",
+ "dev": true
+ },
+ "cyclist": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz",
+ "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=",
+ "dev": true
+ },
+ "dashdash": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+ "dev": true,
+ "requires": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "date-now": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
+ "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=",
+ "dev": true
+ },
+ "debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
+ "dev": true
+ },
+ "decode-uri-component": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
+ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=",
+ "dev": true
+ },
+ "deep-equal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
+ "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
+ "dev": true
+ },
+ "deep-is": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+ "dev": true
+ },
+ "default-gateway": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-2.7.2.tgz",
+ "integrity": "sha512-lAc4i9QJR0YHSDFdzeBQKfZ1SRDG3hsJNEkrpcZa8QhBfidLAilT60BDEIVUUGqosFp425KOgB3uYqcnQrWafQ==",
+ "dev": true,
+ "requires": {
+ "execa": "^0.10.0",
+ "ip-regex": "^2.1.0"
+ }
+ },
+ "default-require-extensions": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz",
+ "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=",
+ "dev": true,
+ "requires": {
+ "strip-bom": "^3.0.0"
+ },
+ "dependencies": {
+ "strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+ "dev": true
+ }
+ }
+ },
+ "define-properties": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+ "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+ "dev": true,
+ "requires": {
+ "object-keys": "^1.0.12"
+ }
+ },
+ "define-property": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
+ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.2",
+ "isobject": "^3.0.1"
+ },
+ "dependencies": {
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "del": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz",
+ "integrity": "sha1-U+z2mf/LyzljdpGrE7rxYIGXZuU=",
+ "dev": true,
+ "requires": {
+ "globby": "^6.1.0",
+ "is-path-cwd": "^1.0.0",
+ "is-path-in-cwd": "^1.0.0",
+ "p-map": "^1.1.1",
+ "pify": "^3.0.0",
+ "rimraf": "^2.2.8"
+ },
+ "dependencies": {
+ "globby": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
+ "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
+ "dev": true,
+ "requires": {
+ "array-union": "^1.0.1",
+ "glob": "^7.0.3",
+ "object-assign": "^4.0.1",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ }
+ }
+ }
+ }
+ },
+ "delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+ "dev": true
+ },
+ "delegates": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
+ "dev": true
+ },
+ "depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
+ "dev": true
+ },
+ "des.js": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
+ "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
+ "destroy": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=",
+ "dev": true
+ },
+ "detect-indent": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz",
+ "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=",
+ "dev": true,
+ "requires": {
+ "repeating": "^2.0.0"
+ }
+ },
+ "detect-node": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz",
+ "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==",
+ "dev": true
+ },
+ "di": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz",
+ "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=",
+ "dev": true
+ },
+ "diff": {
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+ "dev": true
+ },
+ "diffie-hellman": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
+ "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "miller-rabin": "^4.0.0",
+ "randombytes": "^2.0.0"
+ }
+ },
+ "dir-glob": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz",
+ "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==",
+ "dev": true,
+ "requires": {
+ "arrify": "^1.0.1",
+ "path-type": "^3.0.0"
+ }
+ },
+ "dns-equal": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz",
+ "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=",
+ "dev": true
+ },
+ "dns-packet": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-1.3.1.tgz",
+ "integrity": "sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==",
+ "dev": true,
+ "requires": {
+ "ip": "^1.1.0",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "dns-txt": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz",
+ "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=",
+ "dev": true,
+ "requires": {
+ "buffer-indexof": "^1.0.0"
+ }
+ },
+ "dom-converter": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.1.4.tgz",
+ "integrity": "sha1-pF71cnuJDJv/5tfIduexnLDhfzs=",
+ "dev": true,
+ "requires": {
+ "utila": "~0.3"
+ },
+ "dependencies": {
+ "utila": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz",
+ "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=",
+ "dev": true
+ }
+ }
+ },
+ "dom-serialize": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz",
+ "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=",
+ "dev": true,
+ "requires": {
+ "custom-event": "~1.0.0",
+ "ent": "~2.2.0",
+ "extend": "^3.0.0",
+ "void-elements": "^2.0.0"
+ }
+ },
+ "dom-serializer": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
+ "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=",
+ "dev": true,
+ "requires": {
+ "domelementtype": "~1.1.1",
+ "entities": "~1.1.1"
+ },
+ "dependencies": {
+ "domelementtype": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
+ "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=",
+ "dev": true
+ }
+ }
+ },
+ "domain-browser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
+ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
+ "dev": true
+ },
+ "domelementtype": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz",
+ "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=",
+ "dev": true
+ },
+ "domhandler": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.1.0.tgz",
+ "integrity": "sha1-0mRvXlf2w7qxHPbLBdPArPdBJZQ=",
+ "dev": true,
+ "requires": {
+ "domelementtype": "1"
+ }
+ },
+ "domutils": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
+ "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
+ "dev": true,
+ "requires": {
+ "dom-serializer": "0",
+ "domelementtype": "1"
+ }
+ },
+ "duplexify": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz",
+ "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0",
+ "stream-shift": "^1.0.0"
+ }
+ },
+ "ecc-jsbn": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+ "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
+ "dev": true
+ },
+ "ejs": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz",
+ "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==",
+ "dev": true
+ },
+ "electron-to-chromium": {
+ "version": "1.3.62",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.62.tgz",
+ "integrity": "sha512-x09ndL/Gjnuk3unlAyoGyUg3wbs4w/bXurgL7wL913vXHAOWmMhrLf1VNGRaMLngmadd5Q8gsV9BFuIr6rP+Xg==",
+ "dev": true
+ },
+ "elliptic": {
+ "version": "6.4.1",
+ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz",
+ "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.4.0",
+ "brorand": "^1.0.1",
+ "hash.js": "^1.0.0",
+ "hmac-drbg": "^1.0.0",
+ "inherits": "^2.0.1",
+ "minimalistic-assert": "^1.0.0",
+ "minimalistic-crypto-utils": "^1.0.0"
+ }
+ },
+ "emojis-list": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz",
+ "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=",
+ "dev": true
+ },
+ "encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
+ "dev": true
+ },
+ "end-of-stream": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
+ "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
+ "dev": true,
+ "requires": {
+ "once": "^1.4.0"
+ }
+ },
+ "engine.io": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-1.8.3.tgz",
+ "integrity": "sha1-jef5eJXSDTm4X4ju7nd7K9QrE9Q=",
+ "dev": true,
+ "requires": {
+ "accepts": "1.3.3",
+ "base64id": "1.0.0",
+ "cookie": "0.3.1",
+ "debug": "2.3.3",
+ "engine.io-parser": "1.3.2",
+ "ws": "1.1.2"
+ },
+ "dependencies": {
+ "accepts": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz",
+ "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=",
+ "dev": true,
+ "requires": {
+ "mime-types": "~2.1.11",
+ "negotiator": "0.6.1"
+ }
+ },
+ "debug": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz",
+ "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=",
+ "dev": true,
+ "requires": {
+ "ms": "0.7.2"
+ }
+ },
+ "ms": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
+ "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=",
+ "dev": true
+ }
+ }
+ },
+ "engine.io-client": {
+ "version": "1.8.3",
+ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-1.8.3.tgz",
+ "integrity": "sha1-F5jtk0USRkU9TG9jXXogH+lA1as=",
+ "dev": true,
+ "requires": {
+ "component-emitter": "1.2.1",
+ "component-inherit": "0.0.3",
+ "debug": "2.3.3",
+ "engine.io-parser": "1.3.2",
+ "has-cors": "1.1.0",
+ "indexof": "0.0.1",
+ "parsejson": "0.0.3",
+ "parseqs": "0.0.5",
+ "parseuri": "0.0.5",
+ "ws": "1.1.2",
+ "xmlhttprequest-ssl": "1.5.3",
+ "yeast": "0.1.2"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz",
+ "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=",
+ "dev": true,
+ "requires": {
+ "ms": "0.7.2"
+ }
+ },
+ "ms": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
+ "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=",
+ "dev": true
+ }
+ }
+ },
+ "engine.io-parser": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-1.3.2.tgz",
+ "integrity": "sha1-k3sHnwAH0Ik+xW1GyyILjLQ1Igo=",
+ "dev": true,
+ "requires": {
+ "after": "0.8.2",
+ "arraybuffer.slice": "0.0.6",
+ "base64-arraybuffer": "0.1.5",
+ "blob": "0.0.4",
+ "has-binary": "0.1.7",
+ "wtf-8": "1.0.0"
+ }
+ },
+ "enhanced-resolve": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz",
+ "integrity": "sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "memory-fs": "^0.4.0",
+ "tapable": "^1.0.0"
+ }
+ },
+ "ent": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz",
+ "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=",
+ "dev": true
+ },
+ "entities": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
+ "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=",
+ "dev": true
+ },
+ "errno": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
+ "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
+ "dev": true,
+ "requires": {
+ "prr": "~1.0.1"
+ }
+ },
+ "error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "requires": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "es-abstract": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz",
+ "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==",
+ "dev": true,
+ "requires": {
+ "es-to-primitive": "^1.1.1",
+ "function-bind": "^1.1.1",
+ "has": "^1.0.1",
+ "is-callable": "^1.1.3",
+ "is-regex": "^1.0.4"
+ }
+ },
+ "es-to-primitive": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz",
+ "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=",
+ "dev": true,
+ "requires": {
+ "is-callable": "^1.1.1",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.1"
+ }
+ },
+ "es6-promise": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz",
+ "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==",
+ "dev": true
+ },
+ "es6-promisify": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
+ "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
+ "dev": true,
+ "requires": {
+ "es6-promise": "^4.0.3"
+ }
+ },
+ "escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
+ "dev": true
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "dev": true
+ },
+ "escodegen": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz",
+ "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=",
+ "dev": true,
+ "requires": {
+ "esprima": "^2.7.1",
+ "estraverse": "^1.9.1",
+ "esutils": "^2.0.2",
+ "optionator": "^0.8.1",
+ "source-map": "~0.2.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz",
+ "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "amdefine": ">=0.0.4"
+ }
+ }
+ }
+ },
+ "eslint-scope": {
+ "version": "3.7.3",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz",
+ "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.1.0",
+ "estraverse": "^4.1.1"
+ },
+ "dependencies": {
+ "estraverse": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
+ "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
+ "dev": true
+ }
+ }
+ },
+ "esprima": {
+ "version": "2.7.3",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz",
+ "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=",
+ "dev": true
+ },
+ "esrecurse": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
+ "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^4.1.0"
+ },
+ "dependencies": {
+ "estraverse": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
+ "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
+ "dev": true
+ }
+ }
+ },
+ "estraverse": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz",
+ "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=",
+ "dev": true
+ },
+ "esutils": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz",
+ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=",
+ "dev": true
+ },
+ "etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
+ "dev": true
+ },
+ "eventemitter3": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
+ "integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==",
+ "dev": true
+ },
+ "events": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
+ "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=",
+ "dev": true
+ },
+ "eventsource": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-0.1.6.tgz",
+ "integrity": "sha1-Cs7ehJ7X3RzMMsgRuxG5RNTykjI=",
+ "dev": true,
+ "requires": {
+ "original": ">=0.0.5"
+ }
+ },
+ "evp_bytestokey": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
+ "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==",
+ "dev": true,
+ "requires": {
+ "md5.js": "^1.3.4",
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "execa": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz",
+ "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^6.0.0",
+ "get-stream": "^3.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ },
+ "dependencies": {
+ "cross-spawn": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+ "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+ "dev": true,
+ "requires": {
+ "nice-try": "^1.0.4",
+ "path-key": "^2.0.1",
+ "semver": "^5.5.0",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ }
+ }
+ },
+ "exit": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+ "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
+ "dev": true
+ },
+ "expand-braces": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz",
+ "integrity": "sha1-SIsdHSRRyz06axks/AMPRMWFX+o=",
+ "dev": true,
+ "requires": {
+ "array-slice": "^0.2.3",
+ "array-unique": "^0.2.1",
+ "braces": "^0.1.2"
+ },
+ "dependencies": {
+ "array-unique": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
+ "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
+ "dev": true
+ },
+ "braces": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-0.1.5.tgz",
+ "integrity": "sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=",
+ "dev": true,
+ "requires": {
+ "expand-range": "^0.1.0"
+ }
+ },
+ "expand-range": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-0.1.1.tgz",
+ "integrity": "sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=",
+ "dev": true,
+ "requires": {
+ "is-number": "^0.1.1",
+ "repeat-string": "^0.2.2"
+ }
+ },
+ "is-number": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-0.1.1.tgz",
+ "integrity": "sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=",
+ "dev": true
+ },
+ "repeat-string": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-0.2.2.tgz",
+ "integrity": "sha1-x6jTI2BoNiBZp+RlH8aITosftK4=",
+ "dev": true
+ }
+ }
+ },
+ "expand-brackets": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz",
+ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=",
+ "dev": true,
+ "requires": {
+ "debug": "^2.3.3",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "posix-character-classes": "^0.1.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "expand-range": {
+ "version": "1.8.2",
+ "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz",
+ "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=",
+ "dev": true,
+ "requires": {
+ "fill-range": "^2.1.0"
+ },
+ "dependencies": {
+ "fill-range": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz",
+ "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==",
+ "dev": true,
+ "requires": {
+ "is-number": "^2.1.0",
+ "isobject": "^2.0.0",
+ "randomatic": "^3.0.0",
+ "repeat-element": "^1.1.2",
+ "repeat-string": "^1.5.2"
+ }
+ },
+ "is-number": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz",
+ "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ }
+ },
+ "isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "dev": true,
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "express": {
+ "version": "4.16.3",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz",
+ "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=",
+ "dev": true,
+ "requires": {
+ "accepts": "~1.3.5",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.18.2",
+ "content-disposition": "0.5.2",
+ "content-type": "~1.0.4",
+ "cookie": "0.3.1",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.1.1",
+ "fresh": "0.5.2",
+ "merge-descriptors": "1.0.1",
+ "methods": "~1.1.2",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.2",
+ "path-to-regexp": "0.1.7",
+ "proxy-addr": "~2.0.3",
+ "qs": "6.5.1",
+ "range-parser": "~1.2.0",
+ "safe-buffer": "5.1.1",
+ "send": "0.16.2",
+ "serve-static": "1.13.2",
+ "setprototypeof": "1.1.0",
+ "statuses": "~1.4.0",
+ "type-is": "~1.6.16",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "dependencies": {
+ "array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
+ "dev": true
+ },
+ "qs": {
+ "version": "6.5.1",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
+ "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==",
+ "dev": true
+ },
+ "safe-buffer": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
+ "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
+ "dev": true
+ }
+ }
+ },
+ "extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+ "dev": true
+ },
+ "extend-shallow": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz",
+ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=",
+ "dev": true,
+ "requires": {
+ "assign-symbols": "^1.0.0",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "extglob": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz",
+ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==",
+ "dev": true,
+ "requires": {
+ "array-unique": "^0.3.2",
+ "define-property": "^1.0.0",
+ "expand-brackets": "^2.1.4",
+ "extend-shallow": "^2.0.1",
+ "fragment-cache": "^0.2.1",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "extract-zip": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz",
+ "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=",
+ "dev": true,
+ "requires": {
+ "concat-stream": "1.6.2",
+ "debug": "2.6.9",
+ "mkdirp": "0.5.1",
+ "yauzl": "2.4.1"
+ }
+ },
+ "extsprintf": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
+ "dev": true
+ },
+ "fast-deep-equal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
+ "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=",
+ "dev": true
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
+ "dev": true
+ },
+ "fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+ "dev": true
+ },
+ "fastparse": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz",
+ "integrity": "sha1-0eJkOzipTXWDtHkGDmxK/8lAcfg=",
+ "dev": true
+ },
+ "faye-websocket": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
+ "integrity": "sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=",
+ "dev": true,
+ "requires": {
+ "websocket-driver": ">=0.5.1"
+ }
+ },
+ "fd-slicer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
+ "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
+ "dev": true,
+ "requires": {
+ "pend": "~1.2.0"
+ }
+ },
+ "file-loader": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-1.1.11.tgz",
+ "integrity": "sha512-TGR4HU7HUsGg6GCOPJnFk06RhWgEWFLAGWiT6rcD+GRC2keU3s9RGJ+b3Z6/U73jwwNb2gKLJ7YCrp+jvU4ALg==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^1.0.2",
+ "schema-utils": "^0.4.5"
+ }
+ },
+ "filename-regex": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz",
+ "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=",
+ "dev": true
+ },
+ "fileset": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz",
+ "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=",
+ "dev": true,
+ "requires": {
+ "glob": "^7.0.3",
+ "minimatch": "^3.0.3"
+ }
+ },
+ "fill-range": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
+ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1",
+ "to-regex-range": "^2.1.0"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "finalhandler": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
+ "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
+ "dev": true,
+ "requires": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.2",
+ "statuses": "~1.4.0",
+ "unpipe": "~1.0.0"
+ }
+ },
+ "find-cache-dir": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz",
+ "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=",
+ "dev": true,
+ "requires": {
+ "commondir": "^1.0.1",
+ "make-dir": "^1.0.0",
+ "pkg-dir": "^2.0.0"
+ }
+ },
+ "find-up": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+ "dev": true,
+ "requires": {
+ "locate-path": "^2.0.0"
+ }
+ },
+ "flush-write-stream": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz",
+ "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.4"
+ }
+ },
+ "follow-redirects": {
+ "version": "1.5.7",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.7.tgz",
+ "integrity": "sha512-NONJVIFiX7Z8k2WxfqBjtwqMifx7X42ORLFrOZ2LTKGj71G3C0kfdyTqGqr8fx5zSX6Foo/D95dgGWbPUiwnew==",
+ "dev": true,
+ "requires": {
+ "debug": "^3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "for-in": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
+ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=",
+ "dev": true
+ },
+ "for-own": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz",
+ "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=",
+ "dev": true,
+ "requires": {
+ "for-in": "^1.0.1"
+ }
+ },
+ "forever-agent": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+ "dev": true
+ },
+ "form-data": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
+ "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
+ "dev": true,
+ "requires": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "1.0.6",
+ "mime-types": "^2.1.12"
+ }
+ },
+ "forwarded": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+ "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=",
+ "dev": true
+ },
+ "fragment-cache": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz",
+ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=",
+ "dev": true,
+ "requires": {
+ "map-cache": "^0.2.2"
+ }
+ },
+ "fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
+ "dev": true
+ },
+ "from2": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz",
+ "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.0.0"
+ }
+ },
+ "fs-access": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz",
+ "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=",
+ "dev": true,
+ "requires": {
+ "null-check": "^1.0.0"
+ }
+ },
+ "fs-extra": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz",
+ "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^2.1.0",
+ "klaw": "^1.0.0"
+ }
+ },
+ "fs-write-stream-atomic": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
+ "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "iferr": "^0.1.5",
+ "imurmurhash": "^0.1.4",
+ "readable-stream": "1 || 2"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "fsevents": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz",
+ "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "nan": "^2.9.2",
+ "node-pre-gyp": "^0.10.0"
+ },
+ "dependencies": {
+ "abbrev": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "ansi-regex": {
+ "version": "2.1.1",
+ "bundled": true,
+ "dev": true
+ },
+ "aproba": {
+ "version": "1.2.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "are-we-there-yet": {
+ "version": "1.1.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^2.0.6"
+ }
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "chownr": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "code-point-at": {
+ "version": "1.1.0",
+ "bundled": true,
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "bundled": true,
+ "dev": true
+ },
+ "console-control-strings": {
+ "version": "1.1.0",
+ "bundled": true,
+ "dev": true
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "debug": {
+ "version": "2.6.9",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "deep-extend": {
+ "version": "0.5.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "delegates": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "detect-libc": {
+ "version": "1.0.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "fs-minipass": {
+ "version": "1.2.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minipass": "^2.2.1"
+ }
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "aproba": "^1.0.3",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.0",
+ "object-assign": "^4.1.0",
+ "signal-exit": "^3.0.0",
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wide-align": "^1.1.0"
+ }
+ },
+ "glob": {
+ "version": "7.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "has-unicode": {
+ "version": "2.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "iconv-lite": {
+ "version": "0.4.21",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "ignore-walk": {
+ "version": "3.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minimatch": "^3.0.4"
+ }
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "bundled": true,
+ "dev": true
+ },
+ "ini": {
+ "version": "1.3.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "bundled": true,
+ "dev": true
+ },
+ "minipass": {
+ "version": "2.2.4",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.1.1",
+ "yallist": "^3.0.0"
+ }
+ },
+ "minizlib": {
+ "version": "1.1.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "minipass": "^2.2.1"
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "needle": {
+ "version": "2.2.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "debug": "^2.1.2",
+ "iconv-lite": "^0.4.4",
+ "sax": "^1.2.4"
+ }
+ },
+ "node-pre-gyp": {
+ "version": "0.10.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "detect-libc": "^1.0.2",
+ "mkdirp": "^0.5.1",
+ "needle": "^2.2.0",
+ "nopt": "^4.0.1",
+ "npm-packlist": "^1.1.6",
+ "npmlog": "^4.0.2",
+ "rc": "^1.1.7",
+ "rimraf": "^2.6.1",
+ "semver": "^5.3.0",
+ "tar": "^4"
+ }
+ },
+ "nopt": {
+ "version": "4.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "abbrev": "1",
+ "osenv": "^0.1.4"
+ }
+ },
+ "npm-bundled": {
+ "version": "1.0.3",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "npm-packlist": {
+ "version": "1.1.10",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ignore-walk": "^3.0.1",
+ "npm-bundled": "^1.0.1"
+ }
+ },
+ "npmlog": {
+ "version": "4.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "are-we-there-yet": "~1.1.2",
+ "console-control-strings": "~1.1.0",
+ "gauge": "~2.7.3",
+ "set-blocking": "~2.0.0"
+ }
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "osenv": {
+ "version": "0.1.5",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "os-homedir": "^1.0.0",
+ "os-tmpdir": "^1.0.0"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "rc": {
+ "version": "1.2.7",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "deep-extend": "^0.5.1",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "rimraf": {
+ "version": "2.6.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "glob": "^7.0.5"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.1.1",
+ "bundled": true,
+ "dev": true
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "sax": {
+ "version": "1.2.4",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "semver": {
+ "version": "5.5.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "bundled": true,
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-json-comments": {
+ "version": "2.0.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "tar": {
+ "version": "4.4.1",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "chownr": "^1.0.1",
+ "fs-minipass": "^1.2.5",
+ "minipass": "^2.2.4",
+ "minizlib": "^1.1.0",
+ "mkdirp": "^0.5.0",
+ "safe-buffer": "^5.1.1",
+ "yallist": "^3.0.2"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true
+ },
+ "wide-align": {
+ "version": "1.1.2",
+ "bundled": true,
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "string-width": "^1.0.2"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "bundled": true,
+ "dev": true
+ },
+ "yallist": {
+ "version": "3.0.2",
+ "bundled": true,
+ "dev": true
+ }
+ }
+ },
+ "fstream": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
+ "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "inherits": "~2.0.0",
+ "mkdirp": ">=0.5 0",
+ "rimraf": "2"
+ }
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "gauge": {
+ "version": "2.7.4",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
+ "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
+ "dev": true,
+ "requires": {
+ "aproba": "^1.0.3",
+ "console-control-strings": "^1.0.0",
+ "has-unicode": "^2.0.0",
+ "object-assign": "^4.1.0",
+ "signal-exit": "^3.0.0",
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wide-align": "^1.1.0"
+ }
+ },
+ "gaze": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
+ "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "globule": "^1.0.0"
+ }
+ },
+ "get-caller-file": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
+ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
+ "dev": true
+ },
+ "get-stdin": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+ "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
+ "dev": true
+ },
+ "get-stream": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
+ "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
+ "dev": true
+ },
+ "get-value": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz",
+ "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=",
+ "dev": true
+ },
+ "getpass": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+ "dev": true,
+ "requires": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "glob": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+ "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-base": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz",
+ "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=",
+ "dev": true,
+ "requires": {
+ "glob-parent": "^2.0.0",
+ "is-glob": "^2.0.0"
+ },
+ "dependencies": {
+ "glob-parent": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
+ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
+ "dev": true,
+ "requires": {
+ "is-glob": "^2.0.0"
+ }
+ },
+ "is-extglob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+ "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^1.0.0"
+ }
+ }
+ }
+ },
+ "glob-parent": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
+ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=",
+ "dev": true,
+ "requires": {
+ "is-glob": "^3.1.0",
+ "path-dirname": "^1.0.0"
+ },
+ "dependencies": {
+ "is-glob": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz",
+ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.0"
+ }
+ }
+ }
+ },
+ "globals": {
+ "version": "9.18.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz",
+ "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==",
+ "dev": true
+ },
+ "globby": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz",
+ "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=",
+ "dev": true,
+ "requires": {
+ "array-union": "^1.0.1",
+ "dir-glob": "^2.0.0",
+ "glob": "^7.1.2",
+ "ignore": "^3.3.5",
+ "pify": "^3.0.0",
+ "slash": "^1.0.0"
+ }
+ },
+ "globule": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz",
+ "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "glob": "~7.1.1",
+ "lodash": "~4.17.10",
+ "minimatch": "~3.0.2"
+ }
+ },
+ "graceful-fs": {
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
+ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
+ "dev": true
+ },
+ "handle-thing": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz",
+ "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ=",
+ "dev": true
+ },
+ "handlebars": {
+ "version": "4.0.12",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz",
+ "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==",
+ "dev": true,
+ "requires": {
+ "async": "^2.5.0",
+ "optimist": "^0.6.1",
+ "source-map": "^0.6.1",
+ "uglify-js": "^3.1.4"
+ },
+ "dependencies": {
+ "async": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
+ "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.17.10"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "har-schema": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
+ "dev": true
+ },
+ "har-validator": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz",
+ "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==",
+ "dev": true,
+ "requires": {
+ "ajv": "^5.3.0",
+ "har-schema": "^2.0.0"
+ },
+ "dependencies": {
+ "ajv": {
+ "version": "5.5.2",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
+ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
+ "dev": true,
+ "requires": {
+ "co": "^4.6.0",
+ "fast-deep-equal": "^1.0.0",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.3.0"
+ }
+ }
+ }
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-ansi": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "has-binary": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/has-binary/-/has-binary-0.1.7.tgz",
+ "integrity": "sha1-aOYesWIQyVRaClzOBqhzkS/h5ow=",
+ "dev": true,
+ "requires": {
+ "isarray": "0.0.1"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+ "dev": true
+ }
+ }
+ },
+ "has-cors": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
+ "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "dev": true
+ },
+ "has-unicode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+ "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
+ "dev": true
+ },
+ "has-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz",
+ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=",
+ "dev": true,
+ "requires": {
+ "get-value": "^2.0.6",
+ "has-values": "^1.0.0",
+ "isobject": "^3.0.0"
+ }
+ },
+ "has-values": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz",
+ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=",
+ "dev": true,
+ "requires": {
+ "is-number": "^3.0.0",
+ "kind-of": "^4.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
+ "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "hash-base": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz",
+ "integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "hash.js": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.5.tgz",
+ "integrity": "sha512-eWI5HG9Np+eHV1KQhisXWwM+4EPPYe5dFX1UZZH7k/E3JzDEazVH+VGlZi6R94ZqImq+A3D1mCEtrFIfg/E7sA==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.3",
+ "minimalistic-assert": "^1.0.1"
+ }
+ },
+ "hasha": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz",
+ "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=",
+ "dev": true,
+ "requires": {
+ "is-stream": "^1.0.1",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "he": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz",
+ "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=",
+ "dev": true
+ },
+ "hmac-drbg": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+ "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=",
+ "dev": true,
+ "requires": {
+ "hash.js": "^1.0.3",
+ "minimalistic-assert": "^1.0.0",
+ "minimalistic-crypto-utils": "^1.0.1"
+ }
+ },
+ "hosted-git-info": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
+ "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==",
+ "dev": true
+ },
+ "hpack.js": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
+ "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "obuf": "^1.0.0",
+ "readable-stream": "^2.0.1",
+ "wbuf": "^1.1.0"
+ }
+ },
+ "html-entities": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz",
+ "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=",
+ "dev": true
+ },
+ "html-minifier": {
+ "version": "3.5.20",
+ "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.20.tgz",
+ "integrity": "sha512-ZmgNLaTp54+HFKkONyLFEfs5dd/ZOtlquKaTnqIWFmx3Av5zG6ZPcV2d0o9XM2fXOTxxIf6eDcwzFFotke/5zA==",
+ "dev": true,
+ "requires": {
+ "camel-case": "3.0.x",
+ "clean-css": "4.2.x",
+ "commander": "2.17.x",
+ "he": "1.1.x",
+ "param-case": "2.1.x",
+ "relateurl": "0.2.x",
+ "uglify-js": "3.4.x"
+ }
+ },
+ "html-webpack-plugin": {
+ "version": "3.2.0",
+ "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz",
+ "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=",
+ "dev": true,
+ "requires": {
+ "html-minifier": "^3.2.3",
+ "loader-utils": "^0.2.16",
+ "lodash": "^4.17.3",
+ "pretty-error": "^2.0.2",
+ "tapable": "^1.0.0",
+ "toposort": "^1.0.0",
+ "util.promisify": "1.0.0"
+ },
+ "dependencies": {
+ "loader-utils": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz",
+ "integrity": "sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g=",
+ "dev": true,
+ "requires": {
+ "big.js": "^3.1.3",
+ "emojis-list": "^2.0.0",
+ "json5": "^0.5.0",
+ "object-assign": "^4.0.1"
+ }
+ }
+ }
+ },
+ "htmlparser2": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz",
+ "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=",
+ "dev": true,
+ "requires": {
+ "domelementtype": "1",
+ "domhandler": "2.1",
+ "domutils": "1.1",
+ "readable-stream": "1.0"
+ },
+ "dependencies": {
+ "domutils": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.1.6.tgz",
+ "integrity": "sha1-vdw94Jm5ou+sxRxiPyj0FuzFdIU=",
+ "dev": true,
+ "requires": {
+ "domelementtype": "1"
+ }
+ },
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "1.0.34",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.1",
+ "isarray": "0.0.1",
+ "string_decoder": "~0.10.x"
+ }
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+ "dev": true
+ }
+ }
+ },
+ "http-deceiver": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
+ "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=",
+ "dev": true
+ },
+ "http-errors": {
+ "version": "1.6.3",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+ "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+ "dev": true,
+ "requires": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.1.0",
+ "statuses": ">= 1.4.0 < 2"
+ }
+ },
+ "http-parser-js": {
+ "version": "0.4.13",
+ "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.13.tgz",
+ "integrity": "sha1-O9bW/ebjFyyTNMOzO2wZPYD+ETc=",
+ "dev": true
+ },
+ "http-proxy": {
+ "version": "1.17.0",
+ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz",
+ "integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==",
+ "dev": true,
+ "requires": {
+ "eventemitter3": "^3.0.0",
+ "follow-redirects": "^1.0.0",
+ "requires-port": "^1.0.0"
+ }
+ },
+ "http-proxy-middleware": {
+ "version": "0.18.0",
+ "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz",
+ "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==",
+ "dev": true,
+ "requires": {
+ "http-proxy": "^1.16.2",
+ "is-glob": "^4.0.0",
+ "lodash": "^4.17.5",
+ "micromatch": "^3.1.9"
+ }
+ },
+ "http-signature": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+ "dev": true,
+ "requires": {
+ "assert-plus": "^1.0.0",
+ "jsprim": "^1.2.2",
+ "sshpk": "^1.7.0"
+ }
+ },
+ "https-browserify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
+ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
+ "dev": true
+ },
+ "https-proxy-agent": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz",
+ "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==",
+ "dev": true,
+ "requires": {
+ "agent-base": "^4.1.0",
+ "debug": "^3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ }
+ }
+ },
+ "iconv-lite": {
+ "version": "0.4.19",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
+ "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==",
+ "dev": true
+ },
+ "ieee754": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz",
+ "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==",
+ "dev": true
+ },
+ "iferr": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz",
+ "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
+ "dev": true
+ },
+ "ignore": {
+ "version": "3.3.10",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz",
+ "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==",
+ "dev": true
+ },
+ "image-size": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
+ "integrity": "sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w=",
+ "dev": true,
+ "optional": true
+ },
+ "immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=",
+ "dev": true
+ },
+ "import-cwd": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
+ "integrity": "sha1-qmzzbnInYShcs3HsZRn1PiQ1sKk=",
+ "dev": true,
+ "requires": {
+ "import-from": "^2.1.0"
+ }
+ },
+ "import-from": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz",
+ "integrity": "sha1-M1238qev/VOqpHHUuAId7ja387E=",
+ "dev": true,
+ "requires": {
+ "resolve-from": "^3.0.0"
+ }
+ },
+ "import-local": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz",
+ "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==",
+ "dev": true,
+ "requires": {
+ "pkg-dir": "^2.0.0",
+ "resolve-cwd": "^2.0.0"
+ }
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+ "dev": true
+ },
+ "in-publish": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/in-publish/-/in-publish-2.0.0.tgz",
+ "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=",
+ "dev": true,
+ "optional": true
+ },
+ "indent-string": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
+ "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "repeating": "^2.0.0"
+ }
+ },
+ "indexof": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
+ "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=",
+ "dev": true
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true
+ },
+ "ini": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
+ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
+ "dev": true
+ },
+ "internal-ip": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-3.0.1.tgz",
+ "integrity": "sha512-NXXgESC2nNVtU+pqmC9e6R8B1GpKxzsAQhffvh5AL79qKnodd+L7tnEQmTiUAVngqLalPbSqRA7XGIEL5nCd0Q==",
+ "dev": true,
+ "requires": {
+ "default-gateway": "^2.6.0",
+ "ipaddr.js": "^1.5.2"
+ }
+ },
+ "invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "dev": true,
+ "requires": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "invert-kv": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
+ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
+ "dev": true
+ },
+ "ip": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
+ "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
+ "dev": true
+ },
+ "ip-regex": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
+ "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=",
+ "dev": true
+ },
+ "ipaddr.js": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz",
+ "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=",
+ "dev": true
+ },
+ "is-accessor-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz",
+ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+ "dev": true
+ },
+ "is-binary-path": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz",
+ "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=",
+ "dev": true,
+ "requires": {
+ "binary-extensions": "^1.0.0"
+ }
+ },
+ "is-buffer": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
+ "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==",
+ "dev": true
+ },
+ "is-builtin-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz",
+ "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=",
+ "dev": true,
+ "requires": {
+ "builtin-modules": "^1.0.0"
+ }
+ },
+ "is-callable": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
+ "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
+ "dev": true
+ },
+ "is-data-descriptor": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz",
+ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-date-object": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
+ "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
+ "dev": true
+ },
+ "is-descriptor": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz",
+ "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^0.1.6",
+ "is-data-descriptor": "^0.1.4",
+ "kind-of": "^5.0.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+ "dev": true
+ }
+ }
+ },
+ "is-directory": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz",
+ "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=",
+ "dev": true
+ },
+ "is-dotfile": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz",
+ "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=",
+ "dev": true
+ },
+ "is-equal-shallow": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz",
+ "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=",
+ "dev": true,
+ "requires": {
+ "is-primitive": "^2.0.0"
+ }
+ },
+ "is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=",
+ "dev": true
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=",
+ "dev": true
+ },
+ "is-finite": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz",
+ "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=",
+ "dev": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "dev": true,
+ "requires": {
+ "number-is-nan": "^1.0.0"
+ }
+ },
+ "is-glob": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz",
+ "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-number": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
+ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "is-path-cwd": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz",
+ "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=",
+ "dev": true
+ },
+ "is-path-in-cwd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz",
+ "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==",
+ "dev": true,
+ "requires": {
+ "is-path-inside": "^1.0.0"
+ }
+ },
+ "is-path-inside": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz",
+ "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=",
+ "dev": true,
+ "requires": {
+ "path-is-inside": "^1.0.1"
+ }
+ },
+ "is-plain-object": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
+ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
+ "is-posix-bracket": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz",
+ "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=",
+ "dev": true
+ },
+ "is-primitive": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz",
+ "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
+ "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.1"
+ }
+ },
+ "is-stream": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+ "dev": true
+ },
+ "is-symbol": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz",
+ "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=",
+ "dev": true
+ },
+ "is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+ "dev": true
+ },
+ "is-utf8": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
+ "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=",
+ "dev": true
+ },
+ "is-windows": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz",
+ "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==",
+ "dev": true
+ },
+ "is-wsl": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz",
+ "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=",
+ "dev": true
+ },
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "isbinaryfile": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz",
+ "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==",
+ "dev": true,
+ "requires": {
+ "buffer-alloc": "^1.2.0"
+ }
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+ "dev": true
+ },
+ "isobject": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
+ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+ "dev": true
+ },
+ "isstream": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
+ "dev": true
+ },
+ "istanbul": {
+ "version": "0.4.5",
+ "resolved": "https://registry.npmjs.org/istanbul/-/istanbul-0.4.5.tgz",
+ "integrity": "sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs=",
+ "dev": true,
+ "requires": {
+ "abbrev": "1.0.x",
+ "async": "1.x",
+ "escodegen": "1.8.x",
+ "esprima": "2.7.x",
+ "glob": "^5.0.15",
+ "handlebars": "^4.0.1",
+ "js-yaml": "3.x",
+ "mkdirp": "0.5.x",
+ "nopt": "3.x",
+ "once": "1.x",
+ "resolve": "1.1.x",
+ "supports-color": "^3.1.0",
+ "which": "^1.1.1",
+ "wordwrap": "^1.0.0"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "5.0.15",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
+ "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=",
+ "dev": true,
+ "requires": {
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "2 || 3",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "has-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz",
+ "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz",
+ "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=",
+ "dev": true,
+ "requires": {
+ "has-flag": "^1.0.0"
+ }
+ }
+ }
+ },
+ "istanbul-api": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-2.0.5.tgz",
+ "integrity": "sha512-GE5gqFpZsHKgsAbVsgPXlcWKV7fAKP7Bbrma4BJbzBQ+O7KVd/o94WjXOTn4m6eThMhBjWOGOKmaWPwJ3tHVIA==",
+ "dev": true,
+ "requires": {
+ "async": "^2.6.1",
+ "compare-versions": "^3.2.1",
+ "fileset": "^2.0.3",
+ "istanbul-lib-coverage": "^2.0.1",
+ "istanbul-lib-hook": "^2.0.1",
+ "istanbul-lib-instrument": "^2.3.2",
+ "istanbul-lib-report": "^2.0.1",
+ "istanbul-lib-source-maps": "^2.0.1",
+ "istanbul-reports": "^2.0.0",
+ "js-yaml": "^3.12.0",
+ "make-dir": "^1.3.0",
+ "once": "^1.4.0"
+ },
+ "dependencies": {
+ "async": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
+ "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.17.10"
+ }
+ },
+ "istanbul-lib-coverage": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz",
+ "integrity": "sha512-nPvSZsVlbG9aLhZYaC3Oi1gT/tpyo3Yt5fNyf6NmcKIayz4VV/txxJFFKAK/gU4dcNn8ehsanBbVHVl0+amOLA==",
+ "dev": true
+ },
+ "istanbul-lib-instrument": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-2.3.2.tgz",
+ "integrity": "sha512-l7TD/VnBsIB2OJvSyxaLW/ab1+92dxZNH9wLH7uHPPioy3JZ8tnx2UXUdKmdkgmP2EFPzg64CToUP6dAS3U32Q==",
+ "dev": true,
+ "requires": {
+ "@babel/generator": "7.0.0-beta.51",
+ "@babel/parser": "7.0.0-beta.51",
+ "@babel/template": "7.0.0-beta.51",
+ "@babel/traverse": "7.0.0-beta.51",
+ "@babel/types": "7.0.0-beta.51",
+ "istanbul-lib-coverage": "^2.0.1",
+ "semver": "^5.5.0"
+ }
+ }
+ }
+ },
+ "istanbul-instrumenter-loader": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-instrumenter-loader/-/istanbul-instrumenter-loader-3.0.1.tgz",
+ "integrity": "sha512-a5SPObZgS0jB/ixaKSMdn6n/gXSrK2S6q/UfRJBT3e6gQmVjwZROTODQsYW5ZNwOu78hG62Y3fWlebaVOL0C+w==",
+ "dev": true,
+ "requires": {
+ "convert-source-map": "^1.5.0",
+ "istanbul-lib-instrument": "^1.7.3",
+ "loader-utils": "^1.1.0",
+ "schema-utils": "^0.3.0"
+ },
+ "dependencies": {
+ "ajv": {
+ "version": "5.5.2",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
+ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
+ "dev": true,
+ "requires": {
+ "co": "^4.6.0",
+ "fast-deep-equal": "^1.0.0",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.3.0"
+ }
+ },
+ "schema-utils": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz",
+ "integrity": "sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8=",
+ "dev": true,
+ "requires": {
+ "ajv": "^5.0.0"
+ }
+ }
+ }
+ },
+ "istanbul-lib-coverage": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz",
+ "integrity": "sha512-GvgM/uXRwm+gLlvkWHTjDAvwynZkL9ns15calTrmhGgowlwJBbWMYzWbKqE2DT6JDP1AFXKa+Zi0EkqNCUqY0A==",
+ "dev": true
+ },
+ "istanbul-lib-hook": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.1.tgz",
+ "integrity": "sha512-ufiZoiJ8CxY577JJWEeFuxXZoMqiKpq/RqZtOAYuQLvlkbJWscq9n3gc4xrCGH9n4pW0qnTxOz1oyMmVtk8E1w==",
+ "dev": true,
+ "requires": {
+ "append-transform": "^1.0.0"
+ }
+ },
+ "istanbul-lib-instrument": {
+ "version": "1.10.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz",
+ "integrity": "sha512-1dYuzkOCbuR5GRJqySuZdsmsNKPL3PTuyPevQfoCXJePT9C8y1ga75neU+Tuy9+yS3G/dgx8wgOmp2KLpgdoeQ==",
+ "dev": true,
+ "requires": {
+ "babel-generator": "^6.18.0",
+ "babel-template": "^6.16.0",
+ "babel-traverse": "^6.18.0",
+ "babel-types": "^6.18.0",
+ "babylon": "^6.18.0",
+ "istanbul-lib-coverage": "^1.2.0",
+ "semver": "^5.3.0"
+ }
+ },
+ "istanbul-lib-report": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.1.tgz",
+ "integrity": "sha512-pXYOWwpDNc5AHIY93WjFTuxzkDOOZ7B8eSa0cBHTmTnKRst5ccc/xBfWu/5wcNJqB6/Qy0lDMhpn+Uy0qyyUjA==",
+ "dev": true,
+ "requires": {
+ "istanbul-lib-coverage": "^2.0.1",
+ "make-dir": "^1.3.0",
+ "supports-color": "^5.4.0"
+ },
+ "dependencies": {
+ "istanbul-lib-coverage": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz",
+ "integrity": "sha512-nPvSZsVlbG9aLhZYaC3Oi1gT/tpyo3Yt5fNyf6NmcKIayz4VV/txxJFFKAK/gU4dcNn8ehsanBbVHVl0+amOLA==",
+ "dev": true
+ }
+ }
+ },
+ "istanbul-lib-source-maps": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-2.0.1.tgz",
+ "integrity": "sha512-30l40ySg+gvBLcxTrLzR4Z2XTRj3HgRCA/p2rnbs/3OiTaoj054gAbuP5DcLOtwqmy4XW8qXBHzrmP2/bQ9i3A==",
+ "dev": true,
+ "requires": {
+ "debug": "^3.1.0",
+ "istanbul-lib-coverage": "^2.0.1",
+ "make-dir": "^1.3.0",
+ "rimraf": "^2.6.2",
+ "source-map": "^0.6.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "istanbul-lib-coverage": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz",
+ "integrity": "sha512-nPvSZsVlbG9aLhZYaC3Oi1gT/tpyo3Yt5fNyf6NmcKIayz4VV/txxJFFKAK/gU4dcNn8ehsanBbVHVl0+amOLA==",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "istanbul-reports": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.0.0.tgz",
+ "integrity": "sha512-d2YRSnAOHHb+6vMc5qjJEyPN4VapkgUMhKlMmr3BzKdMDWdJbyYGEi/7m5AjDjkvRRTjs68ttPRZ7W2jBZ31SQ==",
+ "dev": true,
+ "requires": {
+ "handlebars": "^4.0.11"
+ }
+ },
+ "jasmine": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-2.8.0.tgz",
+ "integrity": "sha1-awicChFXax8W3xG4AUbZHU6Lij4=",
+ "dev": true,
+ "requires": {
+ "exit": "^0.1.2",
+ "glob": "^7.0.6",
+ "jasmine-core": "~2.8.0"
+ },
+ "dependencies": {
+ "jasmine-core": {
+ "version": "2.8.0",
+ "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz",
+ "integrity": "sha1-vMl5rh+f0FcB5F5S5l06XWPxok4=",
+ "dev": true
+ }
+ }
+ },
+ "jasmine-core": {
+ "version": "2.99.1",
+ "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.99.1.tgz",
+ "integrity": "sha1-5kAN8ea1bhMLYcS80JPap/boyhU=",
+ "dev": true
+ },
+ "jasmine-diff": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/jasmine-diff/-/jasmine-diff-0.1.3.tgz",
+ "integrity": "sha1-k8zC3MQQKMXd1GBlWAdIOfLe6qg=",
+ "dev": true,
+ "requires": {
+ "diff": "^3.2.0"
+ }
+ },
+ "jasmine-spec-reporter": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz",
+ "integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==",
+ "dev": true,
+ "requires": {
+ "colors": "1.1.2"
+ }
+ },
+ "jasminewd2": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/jasminewd2/-/jasminewd2-2.2.0.tgz",
+ "integrity": "sha1-43zwsX8ZnM4jvqcbIDk5Uka07E4=",
+ "dev": true
+ },
+ "js-base64": {
+ "version": "2.4.9",
+ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.9.tgz",
+ "integrity": "sha512-xcinL3AuDJk7VSzsHgb9DvvIXayBbadtMZ4HFPx8rUszbW1MuNMlwYVC4zzCZ6e1sqZpnNS5ZFYOhXqA39T7LQ==",
+ "dev": true,
+ "optional": true
+ },
+ "js-tokens": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
+ "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=",
+ "dev": true
+ },
+ "js-yaml": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz",
+ "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==",
+ "dev": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "dependencies": {
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true
+ }
+ }
+ },
+ "jsbn": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+ "dev": true,
+ "optional": true
+ },
+ "jsesc": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
+ "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=",
+ "dev": true
+ },
+ "json-parse-better-errors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+ "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+ "dev": true
+ },
+ "json-schema": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+ "dev": true
+ },
+ "json-schema-traverse": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
+ "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=",
+ "dev": true
+ },
+ "json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+ "dev": true
+ },
+ "json3": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz",
+ "integrity": "sha1-PAQ0dD35Pi9cQq7nsZvLSDV19OE=",
+ "dev": true
+ },
+ "json5": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz",
+ "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=",
+ "dev": true
+ },
+ "jsonfile": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
+ "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "jsprim": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+ "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+ "dev": true,
+ "requires": {
+ "assert-plus": "1.0.0",
+ "extsprintf": "1.3.0",
+ "json-schema": "0.2.3",
+ "verror": "1.10.0"
+ }
+ },
+ "jszip": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.1.5.tgz",
+ "integrity": "sha512-5W8NUaFRFRqTOL7ZDDrx5qWHJyBXy6velVudIzQUSoqAAYqzSh2Z7/m0Rf1QbmQJccegD0r+YZxBjzqoBiEeJQ==",
+ "dev": true,
+ "requires": {
+ "core-js": "~2.3.0",
+ "es6-promise": "~3.0.2",
+ "lie": "~3.1.0",
+ "pako": "~1.0.2",
+ "readable-stream": "~2.0.6"
+ },
+ "dependencies": {
+ "core-js": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz",
+ "integrity": "sha1-+rg/uwstjchfpjbEudNMdUIMbWU=",
+ "dev": true
+ },
+ "es6-promise": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz",
+ "integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y=",
+ "dev": true
+ },
+ "process-nextick-args": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
+ "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
+ "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.1",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~1.0.6",
+ "string_decoder": "~0.10.x",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+ "dev": true
+ }
+ }
+ },
+ "karma": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/karma/-/karma-1.7.1.tgz",
+ "integrity": "sha512-k5pBjHDhmkdaUccnC7gE3mBzZjcxyxYsYVaqiL2G5AqlfLyBO5nw2VdNK+O16cveEPd/gIOWULH7gkiYYwVNHg==",
+ "dev": true,
+ "requires": {
+ "bluebird": "^3.3.0",
+ "body-parser": "^1.16.1",
+ "chokidar": "^1.4.1",
+ "colors": "^1.1.0",
+ "combine-lists": "^1.0.0",
+ "connect": "^3.6.0",
+ "core-js": "^2.2.0",
+ "di": "^0.0.1",
+ "dom-serialize": "^2.2.0",
+ "expand-braces": "^0.1.1",
+ "glob": "^7.1.1",
+ "graceful-fs": "^4.1.2",
+ "http-proxy": "^1.13.0",
+ "isbinaryfile": "^3.0.0",
+ "lodash": "^3.8.0",
+ "log4js": "^0.6.31",
+ "mime": "^1.3.4",
+ "minimatch": "^3.0.2",
+ "optimist": "^0.6.1",
+ "qjobs": "^1.1.4",
+ "range-parser": "^1.2.0",
+ "rimraf": "^2.6.0",
+ "safe-buffer": "^5.0.1",
+ "socket.io": "1.7.3",
+ "source-map": "^0.5.3",
+ "tmp": "0.0.31",
+ "useragent": "^2.1.12"
+ },
+ "dependencies": {
+ "anymatch": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz",
+ "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==",
+ "dev": true,
+ "requires": {
+ "micromatch": "^2.1.5",
+ "normalize-path": "^2.0.0"
+ }
+ },
+ "arr-diff": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz",
+ "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=",
+ "dev": true,
+ "requires": {
+ "arr-flatten": "^1.0.1"
+ }
+ },
+ "array-unique": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz",
+ "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=",
+ "dev": true
+ },
+ "braces": {
+ "version": "1.8.5",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz",
+ "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=",
+ "dev": true,
+ "requires": {
+ "expand-range": "^1.8.1",
+ "preserve": "^0.2.0",
+ "repeat-element": "^1.1.2"
+ }
+ },
+ "chokidar": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz",
+ "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=",
+ "dev": true,
+ "requires": {
+ "anymatch": "^1.3.0",
+ "async-each": "^1.0.0",
+ "fsevents": "^1.0.0",
+ "glob-parent": "^2.0.0",
+ "inherits": "^2.0.1",
+ "is-binary-path": "^1.0.0",
+ "is-glob": "^2.0.0",
+ "path-is-absolute": "^1.0.0",
+ "readdirp": "^2.0.0"
+ }
+ },
+ "expand-brackets": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz",
+ "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=",
+ "dev": true,
+ "requires": {
+ "is-posix-bracket": "^0.1.0"
+ }
+ },
+ "extglob": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz",
+ "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz",
+ "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=",
+ "dev": true,
+ "requires": {
+ "is-glob": "^2.0.0"
+ }
+ },
+ "is-extglob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+ "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^1.0.0"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ },
+ "lodash": {
+ "version": "3.10.1",
+ "resolved": "http://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz",
+ "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "2.3.11",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz",
+ "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^2.0.0",
+ "array-unique": "^0.2.1",
+ "braces": "^1.8.2",
+ "expand-brackets": "^0.1.4",
+ "extglob": "^0.3.1",
+ "filename-regex": "^2.0.0",
+ "is-extglob": "^1.0.0",
+ "is-glob": "^2.0.1",
+ "kind-of": "^3.0.2",
+ "normalize-path": "^2.0.1",
+ "object.omit": "^2.0.0",
+ "parse-glob": "^3.0.4",
+ "regex-cache": "^0.4.2"
+ }
+ }
+ }
+ },
+ "karma-chrome-launcher": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz",
+ "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==",
+ "dev": true,
+ "requires": {
+ "fs-access": "^1.0.0",
+ "which": "^1.2.1"
+ }
+ },
+ "karma-coverage-istanbul-reporter": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.0.3.tgz",
+ "integrity": "sha512-UVs9IDulfwkBxjEnUzfR/nIc3oBneOPuorpLVBvEMtz2hy0wnVLhCMxpkqAtuQWqvOZRQlGqs+dDtMUeRydTQA==",
+ "dev": true,
+ "requires": {
+ "istanbul-api": "^2.0.5",
+ "minimatch": "^3.0.4"
+ }
+ },
+ "karma-jasmine": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-1.1.2.tgz",
+ "integrity": "sha1-OU8rJf+0pkS5rabyLUQ+L9CIhsM=",
+ "dev": true
+ },
+ "karma-jasmine-html-reporter": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-0.2.2.tgz",
+ "integrity": "sha1-SKjl7xiAdhfuK14zwRlMNbQ5Ukw=",
+ "dev": true,
+ "requires": {
+ "karma-jasmine": "^1.0.2"
+ }
+ },
+ "karma-phantomjs-launcher": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/karma-phantomjs-launcher/-/karma-phantomjs-launcher-1.0.4.tgz",
+ "integrity": "sha1-0jyjSAG9qYY60xjju0vUBisTrNI=",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.0.1",
+ "phantomjs-prebuilt": "^2.1.7"
+ }
+ },
+ "karma-source-map-support": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.3.0.tgz",
+ "integrity": "sha512-HcPqdAusNez/ywa+biN4EphGz62MmQyPggUsDfsHqa7tSe4jdsxgvTKuDfIazjL+IOxpVWyT7Pr4dhAV+sxX5Q==",
+ "dev": true,
+ "requires": {
+ "source-map-support": "^0.5.5"
+ }
+ },
+ "kew": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz",
+ "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=",
+ "dev": true
+ },
+ "killable": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.0.tgz",
+ "integrity": "sha1-2ouEvUfeU5WHj5XWTQLyRJ/gXms=",
+ "dev": true
+ },
+ "kind-of": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
+ "dev": true
+ },
+ "klaw": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
+ "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.9"
+ }
+ },
+ "lcid": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
+ "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
+ "dev": true,
+ "requires": {
+ "invert-kv": "^1.0.0"
+ }
+ },
+ "leb": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/leb/-/leb-0.3.0.tgz",
+ "integrity": "sha1-Mr7p+tFoMo1q6oUi2DP0GA7tHaM=",
+ "dev": true
+ },
+ "less": {
+ "version": "3.8.1",
+ "resolved": "https://registry.npmjs.org/less/-/less-3.8.1.tgz",
+ "integrity": "sha512-8HFGuWmL3FhQR0aH89escFNBQH/nEiYPP2ltDFdQw2chE28Yx2E3lhAIq9Y2saYwLSwa699s4dBVEfCY8Drf7Q==",
+ "dev": true,
+ "requires": {
+ "clone": "^2.1.2",
+ "errno": "^0.1.1",
+ "graceful-fs": "^4.1.2",
+ "image-size": "~0.5.0",
+ "mime": "^1.4.1",
+ "mkdirp": "^0.5.0",
+ "promise": "^7.1.1",
+ "request": "^2.83.0",
+ "source-map": "~0.6.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "less-loader": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-4.1.0.tgz",
+ "integrity": "sha512-KNTsgCE9tMOM70+ddxp9yyt9iHqgmSs0yTZc5XH5Wo+g80RWRIYNqE58QJKm/yMud5wZEvz50ugRDuzVIkyahg==",
+ "dev": true,
+ "requires": {
+ "clone": "^2.1.1",
+ "loader-utils": "^1.1.0",
+ "pify": "^3.0.0"
+ }
+ },
+ "levn": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+ "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2"
+ }
+ },
+ "license-webpack-plugin": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-1.4.0.tgz",
+ "integrity": "sha512-iwuNFMWbXS76WiQXJBTs8/7Tby4NQnY8AIkBMuJG5El79UT8zWrJQMfpW+KRXt4Y2Bs5uk+Myg/MO7ROSF8jzA==",
+ "dev": true,
+ "requires": {
+ "ejs": "^2.5.7"
+ }
+ },
+ "lie": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
+ "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=",
+ "dev": true,
+ "requires": {
+ "immediate": "~3.0.5"
+ }
+ },
+ "load-json-file": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz",
+ "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "parse-json": "^2.2.0",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0",
+ "strip-bom": "^2.0.0"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ }
+ }
+ },
+ "loader-runner": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz",
+ "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=",
+ "dev": true
+ },
+ "loader-utils": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz",
+ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=",
+ "dev": true,
+ "requires": {
+ "big.js": "^3.1.3",
+ "emojis-list": "^2.0.0",
+ "json5": "^0.5.0"
+ }
+ },
+ "locate-path": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
+ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+ "dev": true,
+ "requires": {
+ "p-locate": "^2.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.10",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
+ "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==",
+ "dev": true
+ },
+ "lodash.assign": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
+ "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=",
+ "dev": true,
+ "optional": true
+ },
+ "lodash.clonedeep": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
+ "dev": true
+ },
+ "lodash.debounce": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
+ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
+ "dev": true
+ },
+ "lodash.mergewith": {
+ "version": "4.6.1",
+ "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
+ "integrity": "sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==",
+ "dev": true,
+ "optional": true
+ },
+ "lodash.tail": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz",
+ "integrity": "sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=",
+ "dev": true
+ },
+ "log4js": {
+ "version": "0.6.38",
+ "resolved": "http://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz",
+ "integrity": "sha1-LElBFmldb7JUgJQ9P8hy5mKlIv0=",
+ "dev": true,
+ "requires": {
+ "readable-stream": "~1.0.2",
+ "semver": "~4.3.3"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "1.0.34",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
+ "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.1",
+ "isarray": "0.0.1",
+ "string_decoder": "~0.10.x"
+ }
+ },
+ "semver": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz",
+ "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=",
+ "dev": true
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+ "dev": true
+ }
+ }
+ },
+ "loglevel": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.1.tgz",
+ "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=",
+ "dev": true
+ },
+ "long": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/long/-/long-3.2.0.tgz",
+ "integrity": "sha1-2CG3E4yhy1gcFymQ7xTbIAtcR0s=",
+ "dev": true
+ },
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dev": true,
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "loud-rejection": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz",
+ "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=",
+ "dev": true,
+ "requires": {
+ "currently-unhandled": "^0.4.1",
+ "signal-exit": "^3.0.0"
+ }
+ },
+ "lower-case": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
+ "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=",
+ "dev": true
+ },
+ "lru-cache": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz",
+ "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==",
+ "dev": true,
+ "requires": {
+ "pseudomap": "^1.0.2",
+ "yallist": "^2.1.2"
+ }
+ },
+ "make-dir": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz",
+ "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==",
+ "dev": true,
+ "requires": {
+ "pify": "^3.0.0"
+ }
+ },
+ "make-error": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz",
+ "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==",
+ "dev": true
+ },
+ "map-cache": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz",
+ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=",
+ "dev": true
+ },
+ "map-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+ "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=",
+ "dev": true
+ },
+ "map-visit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
+ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=",
+ "dev": true,
+ "requires": {
+ "object-visit": "^1.0.0"
+ }
+ },
+ "math-random": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz",
+ "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=",
+ "dev": true
+ },
+ "md5.js": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz",
+ "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=",
+ "dev": true,
+ "requires": {
+ "hash-base": "^3.0.0",
+ "inherits": "^2.0.1"
+ }
+ },
+ "media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
+ "dev": true
+ },
+ "mem": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz",
+ "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=",
+ "dev": true,
+ "requires": {
+ "mimic-fn": "^1.0.0"
+ }
+ },
+ "memory-fs": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
+ "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=",
+ "dev": true,
+ "requires": {
+ "errno": "^0.1.3",
+ "readable-stream": "^2.0.1"
+ }
+ },
+ "meow": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz",
+ "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "camelcase-keys": "^2.0.0",
+ "decamelize": "^1.1.2",
+ "loud-rejection": "^1.0.0",
+ "map-obj": "^1.0.1",
+ "minimist": "^1.1.3",
+ "normalize-package-data": "^2.3.4",
+ "object-assign": "^4.0.1",
+ "read-pkg-up": "^1.0.1",
+ "redent": "^1.0.0",
+ "trim-newlines": "^1.0.0"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "merge-descriptors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=",
+ "dev": true
+ },
+ "methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
+ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "braces": "^2.3.1",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "extglob": "^2.0.4",
+ "fragment-cache": "^0.2.1",
+ "kind-of": "^6.0.2",
+ "nanomatch": "^1.2.9",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.2"
+ }
+ },
+ "miller-rabin": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
+ "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.0.0",
+ "brorand": "^1.0.1"
+ }
+ },
+ "mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "dev": true
+ },
+ "mime-db": {
+ "version": "1.36.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz",
+ "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==",
+ "dev": true
+ },
+ "mime-types": {
+ "version": "2.1.20",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz",
+ "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==",
+ "dev": true,
+ "requires": {
+ "mime-db": "~1.36.0"
+ }
+ },
+ "mimic-fn": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+ "dev": true
+ },
+ "mini-css-extract-plugin": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.2.tgz",
+ "integrity": "sha512-ots7URQH4wccfJq9Ssrzu2+qupbncAce4TmTzunI9CIwlQMp2XI+WNUw6xWF6MMAGAm1cbUVINrSjATaVMyKXg==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^1.1.0",
+ "schema-utils": "^1.0.0",
+ "webpack-sources": "^1.1.0"
+ },
+ "dependencies": {
+ "schema-utils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-errors": "^1.0.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ }
+ }
+ },
+ "minimalistic-assert": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+ "dev": true
+ },
+ "minimalistic-crypto-utils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+ "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+ "dev": true
+ },
+ "mississippi": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-2.0.0.tgz",
+ "integrity": "sha512-zHo8v+otD1J10j/tC+VNoGK9keCuByhKovAvdn74dmxJl9+mWHnx6EMsDN4lgRoMI/eYo2nchAxniIbUPb5onw==",
+ "dev": true,
+ "requires": {
+ "concat-stream": "^1.5.0",
+ "duplexify": "^3.4.2",
+ "end-of-stream": "^1.1.0",
+ "flush-write-stream": "^1.0.0",
+ "from2": "^2.1.0",
+ "parallel-transform": "^1.1.0",
+ "pump": "^2.0.1",
+ "pumpify": "^1.3.3",
+ "stream-each": "^1.1.0",
+ "through2": "^2.0.0"
+ }
+ },
+ "mixin-deep": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
+ "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==",
+ "dev": true,
+ "requires": {
+ "for-in": "^1.0.2",
+ "is-extendable": "^1.0.1"
+ },
+ "dependencies": {
+ "is-extendable": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
+ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
+ "dev": true,
+ "requires": {
+ "is-plain-object": "^2.0.4"
+ }
+ }
+ }
+ },
+ "mixin-object": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz",
+ "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=",
+ "dev": true,
+ "requires": {
+ "for-in": "^0.1.3",
+ "is-extendable": "^0.1.1"
+ },
+ "dependencies": {
+ "for-in": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz",
+ "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=",
+ "dev": true
+ }
+ }
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "dev": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "move-concurrently": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
+ "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=",
+ "dev": true,
+ "requires": {
+ "aproba": "^1.1.1",
+ "copy-concurrently": "^1.0.0",
+ "fs-write-stream-atomic": "^1.0.8",
+ "mkdirp": "^0.5.1",
+ "rimraf": "^2.5.4",
+ "run-queue": "^1.0.3"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+ "dev": true
+ },
+ "multicast-dns": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-6.2.3.tgz",
+ "integrity": "sha512-ji6J5enbMyGRHIAkAOu3WdV8nggqviKCEKtXcOqfphZZtQrmHKycfynJ2V7eVPUA4NhJ6V7Wf4TmGbTwKE9B6g==",
+ "dev": true,
+ "requires": {
+ "dns-packet": "^1.3.1",
+ "thunky": "^1.0.2"
+ }
+ },
+ "multicast-dns-service-types": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz",
+ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=",
+ "dev": true
+ },
+ "nan": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz",
+ "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==",
+ "dev": true,
+ "optional": true
+ },
+ "nanomatch": {
+ "version": "1.2.13",
+ "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
+ "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==",
+ "dev": true,
+ "requires": {
+ "arr-diff": "^4.0.0",
+ "array-unique": "^0.3.2",
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "fragment-cache": "^0.2.1",
+ "is-windows": "^1.0.2",
+ "kind-of": "^6.0.2",
+ "object.pick": "^1.3.0",
+ "regex-not": "^1.0.0",
+ "snapdragon": "^0.8.1",
+ "to-regex": "^3.0.1"
+ }
+ },
+ "negotiator": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
+ "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=",
+ "dev": true
+ },
+ "neo-async": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.2.tgz",
+ "integrity": "sha512-vdqTKI9GBIYcAEbFAcpKPErKINfPF5zIuz3/niBfq8WUZjpT2tytLlFVrBgWdOtqI4uaA/Rb6No0hux39XXDuw==",
+ "dev": true
+ },
+ "ngx-cookie": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/ngx-cookie/-/ngx-cookie-4.0.2.tgz",
+ "integrity": "sha512-YCak+Itql8EDkMfr9lzCNd2wEeV+uflbv2V1mi9LCzUyFcO+W53S/BbuZS5r9M8MZzUiBl4AmpEDEKYiXrb3Sw==",
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "ngx-i18nsupport": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/ngx-i18nsupport/-/ngx-i18nsupport-0.17.0.tgz",
+ "integrity": "sha512-iGH3CnEehukzuU9OFai3Kwi06CsNRMI3zquIjUTBUDlVgRwpfGse0BGrr/RRJ359i9P0aeNWtjnDKsW4apSAOw==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.4.1",
+ "commander": "^2.15.1",
+ "he": "^1.1.1",
+ "ngx-i18nsupport-lib": "^1.10.0",
+ "request": "^2.85.0",
+ "rxjs": "^6.0.0"
+ }
+ },
+ "ngx-i18nsupport-lib": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/ngx-i18nsupport-lib/-/ngx-i18nsupport-lib-1.10.0.tgz",
+ "integrity": "sha512-J+0EvMrG31o5SJrb3sZS9WPdc34Qbmq9CglVlemV68kPcCvPbe8yj3qJthOmtoVz8t9ksGugYkB42KZPEMTSeA==",
+ "dev": true,
+ "requires": {
+ "@types/xmldom": "^0.1.29",
+ "tokenizr": "^1.3.4",
+ "xmldom": "^0.1.27"
+ }
+ },
+ "nice-try": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+ "dev": true
+ },
+ "no-case": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
+ "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
+ "dev": true,
+ "requires": {
+ "lower-case": "^1.1.1"
+ }
+ },
+ "node-forge": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz",
+ "integrity": "sha512-MmbQJ2MTESTjt3Gi/3yG1wGpIMhUfcIypUCGtTizFR9IiccFwxSpfp0vtIZlkFclEqERemxfnSdZEMR9VqqEFQ==",
+ "dev": true
+ },
+ "node-gyp": {
+ "version": "3.8.0",
+ "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",
+ "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "fstream": "^1.0.0",
+ "glob": "^7.0.3",
+ "graceful-fs": "^4.1.2",
+ "mkdirp": "^0.5.0",
+ "nopt": "2 || 3",
+ "npmlog": "0 || 1 || 2 || 3 || 4",
+ "osenv": "0",
+ "request": "^2.87.0",
+ "rimraf": "2",
+ "semver": "~5.3.0",
+ "tar": "^2.0.0",
+ "which": "1"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz",
+ "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "node-libs-browser": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz",
+ "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==",
+ "dev": true,
+ "requires": {
+ "assert": "^1.1.1",
+ "browserify-zlib": "^0.2.0",
+ "buffer": "^4.3.0",
+ "console-browserify": "^1.1.0",
+ "constants-browserify": "^1.0.0",
+ "crypto-browserify": "^3.11.0",
+ "domain-browser": "^1.1.1",
+ "events": "^1.0.0",
+ "https-browserify": "^1.0.0",
+ "os-browserify": "^0.3.0",
+ "path-browserify": "0.0.0",
+ "process": "^0.11.10",
+ "punycode": "^1.2.4",
+ "querystring-es3": "^0.2.0",
+ "readable-stream": "^2.3.3",
+ "stream-browserify": "^2.0.1",
+ "stream-http": "^2.7.2",
+ "string_decoder": "^1.0.0",
+ "timers-browserify": "^2.0.4",
+ "tty-browserify": "0.0.0",
+ "url": "^0.11.0",
+ "util": "^0.10.3",
+ "vm-browserify": "0.0.4"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+ "dev": true
+ }
+ }
+ },
+ "node-sass": {
+ "version": "4.9.3",
+ "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.3.tgz",
+ "integrity": "sha512-XzXyGjO+84wxyH7fV6IwBOTrEBe2f0a6SBze9QWWYR/cL74AcQUks2AsqcCZenl/Fp/JVbuEaLpgrLtocwBUww==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "async-foreach": "^0.1.3",
+ "chalk": "^1.1.1",
+ "cross-spawn": "^3.0.0",
+ "gaze": "^1.0.0",
+ "get-stdin": "^4.0.1",
+ "glob": "^7.0.3",
+ "in-publish": "^2.0.0",
+ "lodash.assign": "^4.2.0",
+ "lodash.clonedeep": "^4.3.2",
+ "lodash.mergewith": "^4.6.0",
+ "meow": "^3.7.0",
+ "mkdirp": "^0.5.1",
+ "nan": "^2.10.0",
+ "node-gyp": "^3.8.0",
+ "npmlog": "^4.0.0",
+ "request": "2.87.0",
+ "sass-graph": "^2.2.4",
+ "stdout-stream": "^1.4.0",
+ "true-case-path": "^1.0.2"
+ },
+ "dependencies": {
+ "ajv": {
+ "version": "5.5.2",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
+ "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "co": "^4.6.0",
+ "fast-deep-equal": "^1.0.0",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.3.0"
+ }
+ },
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "dev": true,
+ "optional": true
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ }
+ },
+ "har-validator": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
+ "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "ajv": "^5.1.0",
+ "har-schema": "^2.0.0"
+ }
+ },
+ "oauth-sign": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
+ "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=",
+ "dev": true,
+ "optional": true
+ },
+ "punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+ "dev": true,
+ "optional": true
+ },
+ "request": {
+ "version": "2.87.0",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz",
+ "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "aws-sign2": "~0.7.0",
+ "aws4": "^1.6.0",
+ "caseless": "~0.12.0",
+ "combined-stream": "~1.0.5",
+ "extend": "~3.0.1",
+ "forever-agent": "~0.6.1",
+ "form-data": "~2.3.1",
+ "har-validator": "~5.0.3",
+ "http-signature": "~1.2.0",
+ "is-typedarray": "~1.0.0",
+ "isstream": "~0.1.2",
+ "json-stringify-safe": "~5.0.1",
+ "mime-types": "~2.1.17",
+ "oauth-sign": "~0.8.2",
+ "performance-now": "^2.1.0",
+ "qs": "~6.5.1",
+ "safe-buffer": "^5.1.1",
+ "tough-cookie": "~2.3.3",
+ "tunnel-agent": "^0.6.0",
+ "uuid": "^3.1.0"
+ }
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+ "dev": true,
+ "optional": true
+ },
+ "tough-cookie": {
+ "version": "2.3.4",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz",
+ "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "punycode": "^1.4.1"
+ }
+ }
+ }
+ },
+ "nopt": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+ "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+ "dev": true,
+ "requires": {
+ "abbrev": "1"
+ }
+ },
+ "normalize-package-data": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
+ "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==",
+ "dev": true,
+ "requires": {
+ "hosted-git-info": "^2.1.4",
+ "is-builtin-module": "^1.0.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "normalize-path": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",
+ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=",
+ "dev": true,
+ "requires": {
+ "remove-trailing-separator": "^1.0.1"
+ }
+ },
+ "normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=",
+ "dev": true
+ },
+ "npm-package-arg": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.0.tgz",
+ "integrity": "sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==",
+ "dev": true,
+ "requires": {
+ "hosted-git-info": "^2.6.0",
+ "osenv": "^0.1.5",
+ "semver": "^5.5.0",
+ "validate-npm-package-name": "^3.0.0"
+ }
+ },
+ "npm-registry-client": {
+ "version": "8.6.0",
+ "resolved": "https://registry.npmjs.org/npm-registry-client/-/npm-registry-client-8.6.0.tgz",
+ "integrity": "sha512-Qs6P6nnopig+Y8gbzpeN/dkt+n7IyVd8f45NTMotGk6Qo7GfBmzwYx6jRLoOOgKiMnaQfYxsuyQlD8Mc3guBhg==",
+ "dev": true,
+ "requires": {
+ "concat-stream": "^1.5.2",
+ "graceful-fs": "^4.1.6",
+ "normalize-package-data": "~1.0.1 || ^2.0.0",
+ "npm-package-arg": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0",
+ "npmlog": "2 || ^3.1.0 || ^4.0.0",
+ "once": "^1.3.3",
+ "request": "^2.74.0",
+ "retry": "^0.10.0",
+ "safe-buffer": "^5.1.1",
+ "semver": "2 >=2.2.1 || 3.x || 4 || 5",
+ "slide": "^1.1.3",
+ "ssri": "^5.2.4"
+ }
+ },
+ "npm-run-path": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
+ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+ "dev": true,
+ "requires": {
+ "path-key": "^2.0.0"
+ }
+ },
+ "npmlog": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
+ "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
+ "dev": true,
+ "requires": {
+ "are-we-there-yet": "~1.1.2",
+ "console-control-strings": "~1.1.0",
+ "gauge": "~2.7.3",
+ "set-blocking": "~2.0.0"
+ }
+ },
+ "nth-check": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz",
+ "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=",
+ "dev": true,
+ "requires": {
+ "boolbase": "~1.0.0"
+ }
+ },
+ "null-check": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz",
+ "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=",
+ "dev": true
+ },
+ "num2fraction": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz",
+ "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=",
+ "dev": true
+ },
+ "number-is-nan": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+ "dev": true
+ },
+ "oauth-sign": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
+ "dev": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+ "dev": true
+ },
+ "object-component": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
+ "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=",
+ "dev": true
+ },
+ "object-copy": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
+ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=",
+ "dev": true,
+ "requires": {
+ "copy-descriptor": "^0.1.0",
+ "define-property": "^0.2.5",
+ "kind-of": "^3.0.3"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "object-keys": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz",
+ "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==",
+ "dev": true
+ },
+ "object-visit": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
+ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.0"
+ }
+ },
+ "object.getownpropertydescriptors": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
+ "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "es-abstract": "^1.5.1"
+ }
+ },
+ "object.omit": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz",
+ "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=",
+ "dev": true,
+ "requires": {
+ "for-own": "^0.1.4",
+ "is-extendable": "^0.1.1"
+ },
+ "dependencies": {
+ "for-own": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz",
+ "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=",
+ "dev": true,
+ "requires": {
+ "for-in": "^1.0.1"
+ }
+ }
+ }
+ },
+ "object.pick": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz",
+ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=",
+ "dev": true,
+ "requires": {
+ "isobject": "^3.0.1"
+ }
+ },
+ "obuf": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
+ "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
+ "dev": true
+ },
+ "on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+ "dev": true,
+ "requires": {
+ "ee-first": "1.1.1"
+ }
+ },
+ "on-headers": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz",
+ "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=",
+ "dev": true
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "opn": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz",
+ "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==",
+ "dev": true,
+ "requires": {
+ "is-wsl": "^1.1.0"
+ }
+ },
+ "optimist": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
+ "dev": true,
+ "requires": {
+ "minimist": "~0.0.1",
+ "wordwrap": "~0.0.2"
+ },
+ "dependencies": {
+ "wordwrap": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+ "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
+ "dev": true
+ }
+ }
+ },
+ "optionator": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
+ "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
+ "dev": true,
+ "requires": {
+ "deep-is": "~0.1.3",
+ "fast-levenshtein": "~2.0.4",
+ "levn": "~0.3.0",
+ "prelude-ls": "~1.1.2",
+ "type-check": "~0.3.2",
+ "wordwrap": "~1.0.0"
+ }
+ },
+ "options": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz",
+ "integrity": "sha1-7CLTEoBrtT5zF3Pnza788cZDEo8=",
+ "dev": true
+ },
+ "original": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz",
+ "integrity": "sha512-hyBVl6iqqUOJ8FqRe+l/gS8H+kKYjrEndd5Pm1MfBtsEKA038HkkdbAl/72EAXGyonD/PFsvmVG+EvcIpliMBg==",
+ "dev": true,
+ "requires": {
+ "url-parse": "^1.4.3"
+ }
+ },
+ "os-browserify": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
+ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=",
+ "dev": true
+ },
+ "os-homedir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+ "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
+ "dev": true
+ },
+ "os-locale": {
+ "version": "1.4.0",
+ "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
+ "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "lcid": "^1.0.0"
+ }
+ },
+ "os-tmpdir": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+ "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+ "dev": true
+ },
+ "osenv": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
+ "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
+ "dev": true,
+ "requires": {
+ "os-homedir": "^1.0.0",
+ "os-tmpdir": "^1.0.0"
+ }
+ },
+ "p-finally": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
+ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+ "dev": true
+ },
+ "p-limit": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
+ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+ "dev": true,
+ "requires": {
+ "p-try": "^1.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
+ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+ "dev": true,
+ "requires": {
+ "p-limit": "^1.1.0"
+ }
+ },
+ "p-map": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz",
+ "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==",
+ "dev": true
+ },
+ "p-try": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
+ "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+ "dev": true
+ },
+ "pako": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz",
+ "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==",
+ "dev": true
+ },
+ "parallel-transform": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz",
+ "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=",
+ "dev": true,
+ "requires": {
+ "cyclist": "~0.2.2",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.1.5"
+ }
+ },
+ "param-case": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
+ "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=",
+ "dev": true,
+ "requires": {
+ "no-case": "^2.2.0"
+ }
+ },
+ "parse-asn1": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz",
+ "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==",
+ "dev": true,
+ "requires": {
+ "asn1.js": "^4.0.0",
+ "browserify-aes": "^1.0.0",
+ "create-hash": "^1.1.0",
+ "evp_bytestokey": "^1.0.0",
+ "pbkdf2": "^3.0.3"
+ }
+ },
+ "parse-glob": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz",
+ "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=",
+ "dev": true,
+ "requires": {
+ "glob-base": "^0.3.0",
+ "is-dotfile": "^1.0.0",
+ "is-extglob": "^1.0.0",
+ "is-glob": "^2.0.0"
+ },
+ "dependencies": {
+ "is-extglob": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz",
+ "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
+ "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^1.0.0"
+ }
+ }
+ }
+ },
+ "parse-json": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+ "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+ "dev": true,
+ "requires": {
+ "error-ex": "^1.2.0"
+ }
+ },
+ "parse5": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
+ "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==",
+ "dev": true
+ },
+ "parsejson": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/parsejson/-/parsejson-0.0.3.tgz",
+ "integrity": "sha1-q343WfIJ7OmUN5c/fQ8fZK4OZKs=",
+ "dev": true,
+ "requires": {
+ "better-assert": "~1.0.0"
+ }
+ },
+ "parseqs": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
+ "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
+ "dev": true,
+ "requires": {
+ "better-assert": "~1.0.0"
+ }
+ },
+ "parseuri": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
+ "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
+ "dev": true,
+ "requires": {
+ "better-assert": "~1.0.0"
+ }
+ },
+ "parseurl": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
+ "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=",
+ "dev": true
+ },
+ "pascalcase": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
+ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=",
+ "dev": true
+ },
+ "path-browserify": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz",
+ "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=",
+ "dev": true
+ },
+ "path-dirname": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz",
+ "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=",
+ "dev": true
+ },
+ "path-exists": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
+ "dev": true
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "path-is-inside": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+ "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+ "dev": true
+ },
+ "path-key": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+ "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+ "dev": true
+ },
+ "path-parse": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+ "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+ "dev": true
+ },
+ "path-to-regexp": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=",
+ "dev": true
+ },
+ "path-type": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+ "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+ "dev": true,
+ "requires": {
+ "pify": "^3.0.0"
+ }
+ },
+ "pbkdf2": {
+ "version": "3.0.16",
+ "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz",
+ "integrity": "sha512-y4CXP3thSxqf7c0qmOF+9UeOTrifiVTIM+u7NWlq+PRsHbr7r7dpCmvzrZxa96JJUNi0Y5w9VqG5ZNeCVMoDcA==",
+ "dev": true,
+ "requires": {
+ "create-hash": "^1.1.2",
+ "create-hmac": "^1.1.4",
+ "ripemd160": "^2.0.1",
+ "safe-buffer": "^5.0.1",
+ "sha.js": "^2.4.8"
+ }
+ },
+ "pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
+ "dev": true
+ },
+ "performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
+ "dev": true
+ },
+ "phantomjs-prebuilt": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz",
+ "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=",
+ "dev": true,
+ "requires": {
+ "es6-promise": "^4.0.3",
+ "extract-zip": "^1.6.5",
+ "fs-extra": "^1.0.0",
+ "hasha": "^2.2.0",
+ "kew": "^0.7.0",
+ "progress": "^1.1.8",
+ "request": "^2.81.0",
+ "request-progress": "^2.0.1",
+ "which": "^1.2.10"
+ }
+ },
+ "pify": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+ "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+ "dev": true
+ },
+ "pinkie": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+ "dev": true
+ },
+ "pinkie-promise": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+ "dev": true,
+ "requires": {
+ "pinkie": "^2.0.0"
+ }
+ },
+ "pkg-dir": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
+ "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
+ "dev": true,
+ "requires": {
+ "find-up": "^2.1.0"
+ }
+ },
+ "portfinder": {
+ "version": "1.0.17",
+ "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.17.tgz",
+ "integrity": "sha512-syFcRIRzVI1BoEFOCaAiizwDolh1S1YXSodsVhncbhjzjZQulhczNRbqnUl9N31Q4dKGOXsNDqxC2BWBgSMqeQ==",
+ "dev": true,
+ "requires": {
+ "async": "^1.5.2",
+ "debug": "^2.2.0",
+ "mkdirp": "0.5.x"
+ }
+ },
+ "posix-character-classes": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
+ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
+ "dev": true
+ },
+ "postcss": {
+ "version": "6.0.23",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz",
+ "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==",
+ "dev": true,
+ "requires": {
+ "chalk": "^2.4.1",
+ "source-map": "^0.6.1",
+ "supports-color": "^5.4.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "postcss-import": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-11.1.0.tgz",
+ "integrity": "sha512-5l327iI75POonjxkXgdRCUS+AlzAdBx4pOvMEhTKTCjb1p8IEeVR9yx3cPbmN7LIWJLbfnIXxAhoB4jpD0c/Cw==",
+ "dev": true,
+ "requires": {
+ "postcss": "^6.0.1",
+ "postcss-value-parser": "^3.2.3",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ }
+ },
+ "postcss-load-config": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.0.0.tgz",
+ "integrity": "sha512-V5JBLzw406BB8UIfsAWSK2KSwIJ5yoEIVFb4gVkXci0QdKgA24jLmHZ/ghe/GgX0lJ0/D1uUK1ejhzEY94MChQ==",
+ "dev": true,
+ "requires": {
+ "cosmiconfig": "^4.0.0",
+ "import-cwd": "^2.0.0"
+ }
+ },
+ "postcss-loader": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.1.6.tgz",
+ "integrity": "sha512-hgiWSc13xVQAq25cVw80CH0l49ZKlAnU1hKPOdRrNj89bokRr/bZF2nT+hebPPF9c9xs8c3gw3Fr2nxtmXYnNg==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^1.1.0",
+ "postcss": "^6.0.0",
+ "postcss-load-config": "^2.0.0",
+ "schema-utils": "^0.4.0"
+ }
+ },
+ "postcss-url": {
+ "version": "7.3.2",
+ "resolved": "https://registry.npmjs.org/postcss-url/-/postcss-url-7.3.2.tgz",
+ "integrity": "sha512-QMV5mA+pCYZQcUEPQkmor9vcPQ2MT+Ipuu8qdi1gVxbNiIiErEGft+eny1ak19qALoBkccS5AHaCaCDzh7b9MA==",
+ "dev": true,
+ "requires": {
+ "mime": "^1.4.1",
+ "minimatch": "^3.0.4",
+ "mkdirp": "^0.5.0",
+ "postcss": "^6.0.1",
+ "xxhashjs": "^0.2.1"
+ }
+ },
+ "postcss-value-parser": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz",
+ "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=",
+ "dev": true
+ },
+ "prelude-ls": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+ "dev": true
+ },
+ "preserve": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",
+ "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=",
+ "dev": true
+ },
+ "pretty-error": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz",
+ "integrity": "sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM=",
+ "dev": true,
+ "requires": {
+ "renderkid": "^2.0.1",
+ "utila": "~0.4"
+ }
+ },
+ "process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
+ "dev": true
+ },
+ "process-nextick-args": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
+ "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==",
+ "dev": true
+ },
+ "progress": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
+ "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=",
+ "dev": true
+ },
+ "promise": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz",
+ "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "asap": "~2.0.3"
+ }
+ },
+ "promise-inflight": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
+ "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
+ "dev": true
+ },
+ "protractor": {
+ "version": "5.4.0",
+ "resolved": "https://registry.npmjs.org/protractor/-/protractor-5.4.0.tgz",
+ "integrity": "sha512-6TSYqMhUUzxr4/wN0ttSISqPMKvcVRXF4k8jOEpGWD8OioLak4KLgfzHK9FJ49IrjzRrZ+Mx1q2Op8Rk0zEcnQ==",
+ "dev": true,
+ "requires": {
+ "@types/node": "^6.0.46",
+ "@types/q": "^0.0.32",
+ "@types/selenium-webdriver": "^3.0.0",
+ "blocking-proxy": "^1.0.0",
+ "browserstack": "^1.5.1",
+ "chalk": "^1.1.3",
+ "glob": "^7.0.3",
+ "jasmine": "2.8.0",
+ "jasminewd2": "^2.1.0",
+ "optimist": "~0.6.0",
+ "q": "1.4.1",
+ "saucelabs": "^1.5.0",
+ "selenium-webdriver": "3.6.0",
+ "source-map-support": "~0.4.0",
+ "webdriver-js-extender": "2.0.0",
+ "webdriver-manager": "^12.0.6"
+ },
+ "dependencies": {
+ "@types/node": {
+ "version": "6.0.117",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.117.tgz",
+ "integrity": "sha512-sihk0SnN8PpiS5ihu5xJQ5ddnURNq4P+XPmW+nORlKkHy21CoZO/IVHK/Wq/l3G8fFW06Fkltgnqx229uPlnRg==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "dev": true
+ },
+ "chalk": {
+ "version": "1.1.3",
+ "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ }
+ },
+ "del": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz",
+ "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=",
+ "dev": true,
+ "requires": {
+ "globby": "^5.0.0",
+ "is-path-cwd": "^1.0.0",
+ "is-path-in-cwd": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0",
+ "rimraf": "^2.2.8"
+ }
+ },
+ "globby": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz",
+ "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=",
+ "dev": true,
+ "requires": {
+ "array-union": "^1.0.1",
+ "arrify": "^1.0.0",
+ "glob": "^7.0.3",
+ "object-assign": "^4.0.1",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "dev": true
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ },
+ "source-map-support": {
+ "version": "0.4.18",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz",
+ "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==",
+ "dev": true,
+ "requires": {
+ "source-map": "^0.5.6"
+ }
+ },
+ "supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+ "dev": true
+ },
+ "webdriver-manager": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/webdriver-manager/-/webdriver-manager-12.1.0.tgz",
+ "integrity": "sha512-oEc5fmkpz6Yh6udhwir5m0eN5mgRPq9P/NU5YWuT3Up5slt6Zz+znhLU7q4+8rwCZz/Qq3Fgpr/4oao7NPCm2A==",
+ "dev": true,
+ "requires": {
+ "adm-zip": "^0.4.9",
+ "chalk": "^1.1.1",
+ "del": "^2.2.0",
+ "glob": "^7.0.3",
+ "ini": "^1.3.4",
+ "minimist": "^1.2.0",
+ "q": "^1.4.1",
+ "request": "^2.87.0",
+ "rimraf": "^2.5.2",
+ "semver": "^5.3.0",
+ "xml2js": "^0.4.17"
+ }
+ }
+ }
+ },
+ "proxy-addr": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz",
+ "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==",
+ "dev": true,
+ "requires": {
+ "forwarded": "~0.1.2",
+ "ipaddr.js": "1.8.0"
+ }
+ },
+ "prr": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
+ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
+ "dev": true
+ },
+ "pseudomap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+ "dev": true
+ },
+ "psl": {
+ "version": "1.1.29",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
+ "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==",
+ "dev": true
+ },
+ "public-encrypt": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz",
+ "integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==",
+ "dev": true,
+ "requires": {
+ "bn.js": "^4.1.0",
+ "browserify-rsa": "^4.0.0",
+ "create-hash": "^1.1.0",
+ "parse-asn1": "^5.0.0",
+ "randombytes": "^2.0.1"
+ }
+ },
+ "pump": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
+ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "pumpify": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz",
+ "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==",
+ "dev": true,
+ "requires": {
+ "duplexify": "^3.6.0",
+ "inherits": "^2.0.3",
+ "pump": "^2.0.0"
+ }
+ },
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "dev": true
+ },
+ "q": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz",
+ "integrity": "sha1-VXBbzZPF82c1MMLCy8DCs63cKG4=",
+ "dev": true
+ },
+ "qjobs": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz",
+ "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==",
+ "dev": true
+ },
+ "qs": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
+ "dev": true
+ },
+ "querystring": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
+ "dev": true
+ },
+ "querystring-es3": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
+ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
+ "dev": true
+ },
+ "querystringify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz",
+ "integrity": "sha512-eTPo5t/4bgaMNZxyjWx6N2a6AuE0mq51KWvpc7nU/MAqixcI6v6KrGUKES0HaomdnolQBBXU/++X6/QQ9KL4tw==",
+ "dev": true
+ },
+ "randomatic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.0.tgz",
+ "integrity": "sha512-KnGPVE0lo2WoXxIZ7cPR8YBpiol4gsSuOwDSg410oHh80ZMp5EiypNqL2K4Z77vJn6lB5rap7IkAmcUlalcnBQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^4.0.0",
+ "kind-of": "^6.0.0",
+ "math-random": "^1.0.1"
+ },
+ "dependencies": {
+ "is-number": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz",
+ "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==",
+ "dev": true
+ }
+ }
+ },
+ "randombytes": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz",
+ "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "randomfill": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz",
+ "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.0.5",
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "range-parser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
+ "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=",
+ "dev": true
+ },
+ "raw-body": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz",
+ "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=",
+ "dev": true,
+ "requires": {
+ "bytes": "3.0.0",
+ "http-errors": "1.6.2",
+ "iconv-lite": "0.4.19",
+ "unpipe": "1.0.0"
+ },
+ "dependencies": {
+ "depd": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz",
+ "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=",
+ "dev": true
+ },
+ "http-errors": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz",
+ "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=",
+ "dev": true,
+ "requires": {
+ "depd": "1.1.1",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.0.3",
+ "statuses": ">= 1.3.1 < 2"
+ }
+ },
+ "setprototypeof": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz",
+ "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=",
+ "dev": true
+ }
+ }
+ },
+ "raw-loader": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-0.5.1.tgz",
+ "integrity": "sha1-DD0L6u2KAclm2Xh793goElKpeao=",
+ "dev": true
+ },
+ "read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha1-5mTvMRYRZsl1HNvo28+GtftY93Q=",
+ "dev": true,
+ "requires": {
+ "pify": "^2.3.0"
+ },
+ "dependencies": {
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ }
+ }
+ },
+ "read-pkg": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
+ "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=",
+ "dev": true,
+ "requires": {
+ "load-json-file": "^1.0.0",
+ "normalize-package-data": "^2.3.2",
+ "path-type": "^1.0.0"
+ },
+ "dependencies": {
+ "path-type": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz",
+ "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "dev": true
+ }
+ }
+ },
+ "read-pkg-up": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz",
+ "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=",
+ "dev": true,
+ "requires": {
+ "find-up": "^1.0.0",
+ "read-pkg": "^1.0.0"
+ },
+ "dependencies": {
+ "find-up": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
+ "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=",
+ "dev": true,
+ "requires": {
+ "path-exists": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ }
+ },
+ "path-exists": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz",
+ "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=",
+ "dev": true,
+ "requires": {
+ "pinkie-promise": "^2.0.0"
+ }
+ }
+ }
+ },
+ "readable-stream": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "readdirp": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz",
+ "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "minimatch": "^3.0.2",
+ "readable-stream": "^2.0.2",
+ "set-immediate-shim": "^1.0.1"
+ }
+ },
+ "redent": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
+ "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "indent-string": "^2.1.0",
+ "strip-indent": "^1.0.1"
+ }
+ },
+ "reflect-metadata": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz",
+ "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==",
+ "dev": true
+ },
+ "regenerate": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz",
+ "integrity": "sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==",
+ "dev": true
+ },
+ "regenerator-runtime": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
+ "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
+ "dev": true
+ },
+ "regex-cache": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz",
+ "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==",
+ "dev": true,
+ "requires": {
+ "is-equal-shallow": "^0.1.3"
+ }
+ },
+ "regex-not": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz",
+ "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^3.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "regexpu-core": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-1.0.0.tgz",
+ "integrity": "sha1-hqdj9Y7k18L2sQLkdkBQ3n7ZDGs=",
+ "dev": true,
+ "requires": {
+ "regenerate": "^1.2.1",
+ "regjsgen": "^0.2.0",
+ "regjsparser": "^0.1.4"
+ }
+ },
+ "regjsgen": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz",
+ "integrity": "sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc=",
+ "dev": true
+ },
+ "regjsparser": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz",
+ "integrity": "sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw=",
+ "dev": true,
+ "requires": {
+ "jsesc": "~0.5.0"
+ },
+ "dependencies": {
+ "jsesc": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz",
+ "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=",
+ "dev": true
+ }
+ }
+ },
+ "relateurl": {
+ "version": "0.2.7",
+ "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
+ "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=",
+ "dev": true
+ },
+ "remove-trailing-separator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
+ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=",
+ "dev": true
+ },
+ "renderkid": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-2.0.1.tgz",
+ "integrity": "sha1-iYyr/Ivt5Le5ETWj/9Mj5YwNsxk=",
+ "dev": true,
+ "requires": {
+ "css-select": "^1.1.0",
+ "dom-converter": "~0.1",
+ "htmlparser2": "~3.3.0",
+ "strip-ansi": "^3.0.0",
+ "utila": "~0.3"
+ },
+ "dependencies": {
+ "utila": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/utila/-/utila-0.3.3.tgz",
+ "integrity": "sha1-1+jn1+MJEHCSsF+NloiCTWM6QiY=",
+ "dev": true
+ }
+ }
+ },
+ "repeat-element": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz",
+ "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==",
+ "dev": true
+ },
+ "repeat-string": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
+ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=",
+ "dev": true
+ },
+ "repeating": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz",
+ "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=",
+ "dev": true,
+ "requires": {
+ "is-finite": "^1.0.0"
+ }
+ },
+ "request": {
+ "version": "2.88.0",
+ "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
+ "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
+ "dev": true,
+ "requires": {
+ "aws-sign2": "~0.7.0",
+ "aws4": "^1.8.0",
+ "caseless": "~0.12.0",
+ "combined-stream": "~1.0.6",
+ "extend": "~3.0.2",
+ "forever-agent": "~0.6.1",
+ "form-data": "~2.3.2",
+ "har-validator": "~5.1.0",
+ "http-signature": "~1.2.0",
+ "is-typedarray": "~1.0.0",
+ "isstream": "~0.1.2",
+ "json-stringify-safe": "~5.0.1",
+ "mime-types": "~2.1.19",
+ "oauth-sign": "~0.9.0",
+ "performance-now": "^2.1.0",
+ "qs": "~6.5.2",
+ "safe-buffer": "^5.1.2",
+ "tough-cookie": "~2.4.3",
+ "tunnel-agent": "^0.6.0",
+ "uuid": "^3.3.2"
+ }
+ },
+ "request-progress": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz",
+ "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=",
+ "dev": true,
+ "requires": {
+ "throttleit": "^1.0.0"
+ }
+ },
+ "require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
+ "dev": true
+ },
+ "require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true
+ },
+ "require-main-filename": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
+ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
+ "dev": true
+ },
+ "requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
+ "dev": true
+ },
+ "resolve": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
+ "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
+ "dev": true
+ },
+ "resolve-cwd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz",
+ "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=",
+ "dev": true,
+ "requires": {
+ "resolve-from": "^3.0.0"
+ }
+ },
+ "resolve-from": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
+ "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
+ "dev": true
+ },
+ "resolve-url": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
+ "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
+ "dev": true
+ },
+ "ret": {
+ "version": "0.1.15",
+ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+ "dev": true
+ },
+ "retry": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz",
+ "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
+ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.0.5"
+ }
+ },
+ "ripemd160": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
+ "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
+ "dev": true,
+ "requires": {
+ "hash-base": "^3.0.0",
+ "inherits": "^2.0.1"
+ }
+ },
+ "run-queue": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",
+ "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=",
+ "dev": true,
+ "requires": {
+ "aproba": "^1.1.1"
+ }
+ },
+ "rxjs": {
+ "version": "6.3.2",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.2.tgz",
+ "integrity": "sha512-hV7criqbR0pe7EeL3O66UYVg92IR0XsA97+9y+BWTePK9SKmEI5Qd3Zj6uPnGkNzXsBywBQWTvujPl+1Kn9Zjw==",
+ "requires": {
+ "tslib": "^1.9.0"
+ }
+ },
+ "rxjs-compat": {
+ "version": "6.3.2",
+ "resolved": "https://registry.npmjs.org/rxjs-compat/-/rxjs-compat-6.3.2.tgz",
+ "integrity": "sha512-eH0ANsX4ufMSDmSDwWbsWYgZDDDxxLHxsSwApbQumHTFm83RP4AI594QtXv3Jup+hVjXfE2dRSAVKbMh2a2hcw=="
+ },
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "safe-regex": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=",
+ "dev": true,
+ "requires": {
+ "ret": "~0.1.10"
+ }
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "sass-graph": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz",
+ "integrity": "sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "glob": "^7.0.0",
+ "lodash": "^4.0.0",
+ "scss-tokenizer": "^0.2.3",
+ "yargs": "^7.0.0"
+ }
+ },
+ "sass-loader": {
+ "version": "6.0.7",
+ "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-6.0.7.tgz",
+ "integrity": "sha512-JoiyD00Yo1o61OJsoP2s2kb19L1/Y2p3QFcCdWdF6oomBGKVYuZyqHWemRBfQ2uGYsk+CH3eCguXNfpjzlcpaA==",
+ "dev": true,
+ "requires": {
+ "clone-deep": "^2.0.1",
+ "loader-utils": "^1.0.1",
+ "lodash.tail": "^4.1.1",
+ "neo-async": "^2.5.0",
+ "pify": "^3.0.0"
+ }
+ },
+ "saucelabs": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/saucelabs/-/saucelabs-1.5.0.tgz",
+ "integrity": "sha512-jlX3FGdWvYf4Q3LFfFWS1QvPg3IGCGWxIc8QBFdPTbpTJnt/v17FHXYVAn7C8sHf1yUXo2c7yIM0isDryfYtHQ==",
+ "dev": true,
+ "requires": {
+ "https-proxy-agent": "^2.2.1"
+ }
+ },
+ "sax": {
+ "version": "0.5.8",
+ "resolved": "http://registry.npmjs.org/sax/-/sax-0.5.8.tgz",
+ "integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE=",
+ "dev": true
+ },
+ "schema-utils": {
+ "version": "0.4.7",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz",
+ "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ },
+ "scss-tokenizer": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
+ "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "js-base64": "^2.1.8",
+ "source-map": "^0.4.2"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
+ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "amdefine": ">=0.0.4"
+ }
+ }
+ }
+ },
+ "select-hose": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
+ "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=",
+ "dev": true
+ },
+ "selenium-webdriver": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-3.6.0.tgz",
+ "integrity": "sha512-WH7Aldse+2P5bbFBO4Gle/nuQOdVwpHMTL6raL3uuBj/vPG07k6uzt3aiahu352ONBr5xXh0hDlM3LhtXPOC4Q==",
+ "dev": true,
+ "requires": {
+ "jszip": "^3.1.3",
+ "rimraf": "^2.5.4",
+ "tmp": "0.0.30",
+ "xml2js": "^0.4.17"
+ },
+ "dependencies": {
+ "tmp": {
+ "version": "0.0.30",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.30.tgz",
+ "integrity": "sha1-ckGdSovn1s51FI/YsyTlk6cRwu0=",
+ "dev": true,
+ "requires": {
+ "os-tmpdir": "~1.0.1"
+ }
+ }
+ }
+ },
+ "selfsigned": {
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.3.tgz",
+ "integrity": "sha512-vmZenZ+8Al3NLHkWnhBQ0x6BkML1eCP2xEi3JE+f3D9wW9fipD9NNJHYtE9XJM4TsPaHGZJIamrSI6MTg1dU2Q==",
+ "dev": true,
+ "requires": {
+ "node-forge": "0.7.5"
+ }
+ },
+ "semver": {
+ "version": "5.5.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz",
+ "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==",
+ "dev": true
+ },
+ "semver-dsl": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/semver-dsl/-/semver-dsl-1.0.1.tgz",
+ "integrity": "sha1-02eN5VVeimH2Ke7QJTZq5fJzQKA=",
+ "dev": true,
+ "requires": {
+ "semver": "^5.3.0"
+ }
+ },
+ "semver-intersect": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/semver-intersect/-/semver-intersect-1.4.0.tgz",
+ "integrity": "sha512-d8fvGg5ycKAq0+I6nfWeCx6ffaWJCsBYU0H2Rq56+/zFePYfT8mXkB3tWBSjR5BerkHNZ5eTPIk1/LBYas35xQ==",
+ "dev": true,
+ "requires": {
+ "semver": "^5.0.0"
+ }
+ },
+ "send": {
+ "version": "0.16.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
+ "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
+ "dev": true,
+ "requires": {
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "destroy": "~1.0.4",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "~1.6.2",
+ "mime": "1.4.1",
+ "ms": "2.0.0",
+ "on-finished": "~2.3.0",
+ "range-parser": "~1.2.0",
+ "statuses": "~1.4.0"
+ },
+ "dependencies": {
+ "mime": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
+ "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==",
+ "dev": true
+ }
+ }
+ },
+ "serialize-javascript": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz",
+ "integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==",
+ "dev": true
+ },
+ "serve-index": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz",
+ "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=",
+ "dev": true,
+ "requires": {
+ "accepts": "~1.3.4",
+ "batch": "0.6.1",
+ "debug": "2.6.9",
+ "escape-html": "~1.0.3",
+ "http-errors": "~1.6.2",
+ "mime-types": "~2.1.17",
+ "parseurl": "~1.3.2"
+ }
+ },
+ "serve-static": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
+ "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
+ "dev": true,
+ "requires": {
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.2",
+ "send": "0.16.2"
+ }
+ },
+ "set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
+ "dev": true
+ },
+ "set-immediate-shim": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz",
+ "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=",
+ "dev": true
+ },
+ "set-value": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz",
+ "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.3",
+ "split-string": "^3.0.1"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=",
+ "dev": true
+ },
+ "setprototypeof": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
+ "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==",
+ "dev": true
+ },
+ "sha.js": {
+ "version": "2.4.11",
+ "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
+ "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
+ "dev": true,
+ "requires": {
+ "inherits": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "shallow-clone": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-1.0.0.tgz",
+ "integrity": "sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.1",
+ "kind-of": "^5.0.0",
+ "mixin-object": "^2.0.1"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz",
+ "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==",
+ "dev": true
+ }
+ }
+ },
+ "shebang-command": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+ "dev": true,
+ "requires": {
+ "shebang-regex": "^1.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+ "dev": true
+ },
+ "signal-exit": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+ "dev": true
+ },
+ "slash": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz",
+ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=",
+ "dev": true
+ },
+ "slide": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz",
+ "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=",
+ "dev": true
+ },
+ "snapdragon": {
+ "version": "0.8.2",
+ "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
+ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==",
+ "dev": true,
+ "requires": {
+ "base": "^0.11.1",
+ "debug": "^2.2.0",
+ "define-property": "^0.2.5",
+ "extend-shallow": "^2.0.1",
+ "map-cache": "^0.2.2",
+ "source-map": "^0.5.6",
+ "source-map-resolve": "^0.5.0",
+ "use": "^3.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ },
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ }
+ }
+ },
+ "snapdragon-node": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz",
+ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==",
+ "dev": true,
+ "requires": {
+ "define-property": "^1.0.0",
+ "isobject": "^3.0.0",
+ "snapdragon-util": "^3.0.1"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz",
+ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^1.0.0"
+ }
+ },
+ "is-accessor-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz",
+ "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-data-descriptor": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz",
+ "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^6.0.0"
+ }
+ },
+ "is-descriptor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz",
+ "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==",
+ "dev": true,
+ "requires": {
+ "is-accessor-descriptor": "^1.0.0",
+ "is-data-descriptor": "^1.0.0",
+ "kind-of": "^6.0.2"
+ }
+ }
+ }
+ },
+ "snapdragon-util": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz",
+ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.2.0"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "socket.io": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-1.7.3.tgz",
+ "integrity": "sha1-uK+cq6AJSeVo42nxMn6pvp6iRhs=",
+ "dev": true,
+ "requires": {
+ "debug": "2.3.3",
+ "engine.io": "1.8.3",
+ "has-binary": "0.1.7",
+ "object-assign": "4.1.0",
+ "socket.io-adapter": "0.5.0",
+ "socket.io-client": "1.7.3",
+ "socket.io-parser": "2.3.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz",
+ "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=",
+ "dev": true,
+ "requires": {
+ "ms": "0.7.2"
+ }
+ },
+ "ms": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
+ "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=",
+ "dev": true
+ },
+ "object-assign": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz",
+ "integrity": "sha1-ejs9DpgGPUP0wD8uiubNUahog6A=",
+ "dev": true
+ }
+ }
+ },
+ "socket.io-adapter": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz",
+ "integrity": "sha1-y21LuL7IHhB4uZZ3+c7QBGBmu4s=",
+ "dev": true,
+ "requires": {
+ "debug": "2.3.3",
+ "socket.io-parser": "2.3.1"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz",
+ "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=",
+ "dev": true,
+ "requires": {
+ "ms": "0.7.2"
+ }
+ },
+ "ms": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
+ "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=",
+ "dev": true
+ }
+ }
+ },
+ "socket.io-client": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-1.7.3.tgz",
+ "integrity": "sha1-sw6GqhDV7zVGYBwJzeR2Xjgdo3c=",
+ "dev": true,
+ "requires": {
+ "backo2": "1.0.2",
+ "component-bind": "1.0.0",
+ "component-emitter": "1.2.1",
+ "debug": "2.3.3",
+ "engine.io-client": "1.8.3",
+ "has-binary": "0.1.7",
+ "indexof": "0.0.1",
+ "object-component": "0.0.3",
+ "parseuri": "0.0.5",
+ "socket.io-parser": "2.3.1",
+ "to-array": "0.1.4"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.3.3.tgz",
+ "integrity": "sha1-QMRT5n5uE8kB3ewxeviYbNqe/4w=",
+ "dev": true,
+ "requires": {
+ "ms": "0.7.2"
+ }
+ },
+ "ms": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz",
+ "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=",
+ "dev": true
+ }
+ }
+ },
+ "socket.io-parser": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-2.3.1.tgz",
+ "integrity": "sha1-3VMgJRA85Clpcya+/WQAX8/ltKA=",
+ "dev": true,
+ "requires": {
+ "component-emitter": "1.1.2",
+ "debug": "2.2.0",
+ "isarray": "0.0.1",
+ "json3": "3.3.2"
+ },
+ "dependencies": {
+ "component-emitter": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz",
+ "integrity": "sha1-KWWU8nU9qmOZbSrwjRWpURbJrsM=",
+ "dev": true
+ },
+ "debug": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
+ "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=",
+ "dev": true,
+ "requires": {
+ "ms": "0.7.1"
+ }
+ },
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+ "dev": true
+ },
+ "ms": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz",
+ "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=",
+ "dev": true
+ }
+ }
+ },
+ "sockjs": {
+ "version": "0.3.19",
+ "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz",
+ "integrity": "sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw==",
+ "dev": true,
+ "requires": {
+ "faye-websocket": "^0.10.0",
+ "uuid": "^3.0.1"
+ }
+ },
+ "sockjs-client": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.1.5.tgz",
+ "integrity": "sha1-G7fA9yIsQPQq3xT0RCy9Eml3GoM=",
+ "dev": true,
+ "requires": {
+ "debug": "^2.6.6",
+ "eventsource": "0.1.6",
+ "faye-websocket": "~0.11.0",
+ "inherits": "^2.0.1",
+ "json3": "^3.3.2",
+ "url-parse": "^1.1.8"
+ },
+ "dependencies": {
+ "faye-websocket": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz",
+ "integrity": "sha1-8O/hjE9W5PQK/H4Gxxn9XuYYjzg=",
+ "dev": true,
+ "requires": {
+ "websocket-driver": ">=0.5.1"
+ }
+ }
+ }
+ },
+ "source-list-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz",
+ "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+ "dev": true
+ },
+ "source-map-loader": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-0.2.4.tgz",
+ "integrity": "sha512-OU6UJUty+i2JDpTItnizPrlpOIBLmQbWMuBg9q5bVtnHACqw1tn9nNwqJLbv0/00JjnJb/Ee5g5WS5vrRv7zIQ==",
+ "dev": true,
+ "requires": {
+ "async": "^2.5.0",
+ "loader-utils": "^1.1.0"
+ },
+ "dependencies": {
+ "async": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
+ "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.17.10"
+ }
+ }
+ }
+ },
+ "source-map-resolve": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
+ "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==",
+ "dev": true,
+ "requires": {
+ "atob": "^2.1.1",
+ "decode-uri-component": "^0.2.0",
+ "resolve-url": "^0.2.1",
+ "source-map-url": "^0.4.0",
+ "urix": "^0.1.0"
+ }
+ },
+ "source-map-support": {
+ "version": "0.5.9",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz",
+ "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==",
+ "dev": true,
+ "requires": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "source-map-url": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
+ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=",
+ "dev": true
+ },
+ "spdx-correct": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz",
+ "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==",
+ "dev": true,
+ "requires": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-exceptions": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz",
+ "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==",
+ "dev": true
+ },
+ "spdx-expression-parse": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+ "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+ "dev": true,
+ "requires": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "spdx-license-ids": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz",
+ "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==",
+ "dev": true
+ },
+ "spdy": {
+ "version": "3.4.7",
+ "resolved": "https://registry.npmjs.org/spdy/-/spdy-3.4.7.tgz",
+ "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=",
+ "dev": true,
+ "requires": {
+ "debug": "^2.6.8",
+ "handle-thing": "^1.2.5",
+ "http-deceiver": "^1.2.7",
+ "safe-buffer": "^5.0.1",
+ "select-hose": "^2.0.0",
+ "spdy-transport": "^2.0.18"
+ }
+ },
+ "spdy-transport": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.1.0.tgz",
+ "integrity": "sha512-bpUeGpZcmZ692rrTiqf9/2EUakI6/kXX1Rpe0ib/DyOzbiexVfXkw6GnvI9hVGvIwVaUhkaBojjCZwLNRGQg1g==",
+ "dev": true,
+ "requires": {
+ "debug": "^2.6.8",
+ "detect-node": "^2.0.3",
+ "hpack.js": "^2.1.6",
+ "obuf": "^1.1.1",
+ "readable-stream": "^2.2.9",
+ "safe-buffer": "^5.0.1",
+ "wbuf": "^1.7.2"
+ }
+ },
+ "split-string": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
+ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^3.0.0"
+ }
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+ "dev": true
+ },
+ "sshpk": {
+ "version": "1.14.2",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz",
+ "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=",
+ "dev": true,
+ "requires": {
+ "asn1": "~0.2.3",
+ "assert-plus": "^1.0.0",
+ "bcrypt-pbkdf": "^1.0.0",
+ "dashdash": "^1.12.0",
+ "ecc-jsbn": "~0.1.1",
+ "getpass": "^0.1.1",
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.0.2",
+ "tweetnacl": "~0.14.0"
+ }
+ },
+ "ssri": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-5.3.0.tgz",
+ "integrity": "sha512-XRSIPqLij52MtgoQavH/x/dU1qVKtWUAAZeOHsR9c2Ddi4XerFy3mc1alf+dLJKl9EUIm/Ht+EowFkTUOA6GAQ==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.1.1"
+ }
+ },
+ "static-extend": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz",
+ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=",
+ "dev": true,
+ "requires": {
+ "define-property": "^0.2.5",
+ "object-copy": "^0.1.0"
+ },
+ "dependencies": {
+ "define-property": {
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz",
+ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=",
+ "dev": true,
+ "requires": {
+ "is-descriptor": "^0.1.0"
+ }
+ }
+ }
+ },
+ "stats-webpack-plugin": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/stats-webpack-plugin/-/stats-webpack-plugin-0.6.2.tgz",
+ "integrity": "sha1-LFlJtTHgf4eojm6k3PrFOqjHWis=",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.17.4"
+ }
+ },
+ "statuses": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
+ "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==",
+ "dev": true
+ },
+ "stdout-stream": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz",
+ "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "readable-stream": "^2.0.1"
+ }
+ },
+ "stream-browserify": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
+ "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=",
+ "dev": true,
+ "requires": {
+ "inherits": "~2.0.1",
+ "readable-stream": "^2.0.2"
+ }
+ },
+ "stream-each": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz",
+ "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==",
+ "dev": true,
+ "requires": {
+ "end-of-stream": "^1.1.0",
+ "stream-shift": "^1.0.0"
+ }
+ },
+ "stream-http": {
+ "version": "2.8.3",
+ "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz",
+ "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==",
+ "dev": true,
+ "requires": {
+ "builtin-status-codes": "^3.0.0",
+ "inherits": "^2.0.1",
+ "readable-stream": "^2.3.6",
+ "to-arraybuffer": "^1.0.0",
+ "xtend": "^4.0.0"
+ }
+ },
+ "stream-shift": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
+ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=",
+ "dev": true
+ },
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "dev": true,
+ "requires": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^2.0.0"
+ }
+ },
+ "strip-bom": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz",
+ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=",
+ "dev": true,
+ "requires": {
+ "is-utf8": "^0.2.0"
+ }
+ },
+ "strip-eof": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
+ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+ "dev": true
+ },
+ "strip-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz",
+ "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "get-stdin": "^4.0.1"
+ }
+ },
+ "style-loader": {
+ "version": "0.21.0",
+ "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.21.0.tgz",
+ "integrity": "sha512-T+UNsAcl3Yg+BsPKs1vd22Fr8sVT+CJMtzqc6LEw9bbJZb43lm9GoeIfUcDEefBSWC0BhYbcdupV1GtI4DGzxg==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^1.1.0",
+ "schema-utils": "^0.4.5"
+ }
+ },
+ "stylus": {
+ "version": "0.54.5",
+ "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.5.tgz",
+ "integrity": "sha1-QrlWCTHKcJDOhRWnmLqeaqPW3Hk=",
+ "dev": true,
+ "requires": {
+ "css-parse": "1.7.x",
+ "debug": "*",
+ "glob": "7.0.x",
+ "mkdirp": "0.5.x",
+ "sax": "0.5.x",
+ "source-map": "0.1.x"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz",
+ "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.2",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "source-map": {
+ "version": "0.1.43",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
+ "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=",
+ "dev": true,
+ "requires": {
+ "amdefine": ">=0.0.4"
+ }
+ }
+ }
+ },
+ "stylus-loader": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/stylus-loader/-/stylus-loader-3.0.2.tgz",
+ "integrity": "sha512-+VomPdZ6a0razP+zinir61yZgpw2NfljeSsdUF5kJuEzlo3khXhY19Fn6l8QQz1GRJGtMCo8nG5C04ePyV7SUA==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^1.0.2",
+ "lodash.clonedeep": "^4.5.0",
+ "when": "~3.6.x"
+ }
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "symbol-observable": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
+ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
+ "dev": true
+ },
+ "tapable": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.0.0.tgz",
+ "integrity": "sha512-dQRhbNQkRnaqauC7WqSJ21EEksgT0fYZX2lqXzGkpo8JNig9zGZTYoMGvyI2nWmXlE2VSVXVDu7wLVGu/mQEsg==",
+ "dev": true
+ },
+ "tar": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
+ "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "block-stream": "*",
+ "fstream": "^1.0.2",
+ "inherits": "2"
+ }
+ },
+ "throttleit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
+ "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=",
+ "dev": true
+ },
+ "through": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+ "dev": true
+ },
+ "through2": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz",
+ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=",
+ "dev": true,
+ "requires": {
+ "readable-stream": "^2.1.5",
+ "xtend": "~4.0.1"
+ }
+ },
+ "thunky": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.0.2.tgz",
+ "integrity": "sha1-qGLgGOP7HqLsP85dVWBc9X8kc3E=",
+ "dev": true
+ },
+ "timers-browserify": {
+ "version": "2.0.10",
+ "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz",
+ "integrity": "sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg==",
+ "dev": true,
+ "requires": {
+ "setimmediate": "^1.0.4"
+ }
+ },
+ "tmp": {
+ "version": "0.0.31",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.31.tgz",
+ "integrity": "sha1-jzirlDjhcxXl29izZX6L+yd65Kc=",
+ "dev": true,
+ "requires": {
+ "os-tmpdir": "~1.0.1"
+ }
+ },
+ "to-array": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
+ "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=",
+ "dev": true
+ },
+ "to-arraybuffer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
+ "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=",
+ "dev": true
+ },
+ "to-fast-properties": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz",
+ "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=",
+ "dev": true
+ },
+ "to-object-path": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz",
+ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=",
+ "dev": true,
+ "requires": {
+ "kind-of": "^3.0.2"
+ },
+ "dependencies": {
+ "kind-of": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
+ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
+ "dev": true,
+ "requires": {
+ "is-buffer": "^1.1.5"
+ }
+ }
+ }
+ },
+ "to-regex": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz",
+ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==",
+ "dev": true,
+ "requires": {
+ "define-property": "^2.0.2",
+ "extend-shallow": "^3.0.2",
+ "regex-not": "^1.0.2",
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "to-regex-range": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
+ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
+ "dev": true,
+ "requires": {
+ "is-number": "^3.0.0",
+ "repeat-string": "^1.6.1"
+ }
+ },
+ "tokenizr": {
+ "version": "1.3.10",
+ "resolved": "https://registry.npmjs.org/tokenizr/-/tokenizr-1.3.10.tgz",
+ "integrity": "sha512-XlYlczHEQrbmj/JInA9vcsBJlukyTJWvjmQodjlbkul5fZ4o1JDNYAvLlrHZs03CSR8nFjNmTEqN3NrjTjmN+A==",
+ "dev": true
+ },
+ "toposort": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/toposort/-/toposort-1.0.7.tgz",
+ "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=",
+ "dev": true
+ },
+ "tough-cookie": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
+ "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
+ "dev": true,
+ "requires": {
+ "psl": "^1.1.24",
+ "punycode": "^1.4.1"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+ "dev": true
+ }
+ }
+ },
+ "tree-kill": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.0.tgz",
+ "integrity": "sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg==",
+ "dev": true
+ },
+ "trim-newlines": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
+ "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",
+ "dev": true,
+ "optional": true
+ },
+ "trim-right": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",
+ "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
+ "dev": true
+ },
+ "true-case-path": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz",
+ "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "glob": "^7.1.2"
+ }
+ },
+ "ts-node": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-5.0.1.tgz",
+ "integrity": "sha512-XK7QmDcNHVmZkVtkiwNDWiERRHPyU8nBqZB1+iv2UhOG0q3RQ9HsZ2CMqISlFbxjrYFGfG2mX7bW4dAyxBVzUw==",
+ "dev": true,
+ "requires": {
+ "arrify": "^1.0.0",
+ "chalk": "^2.3.0",
+ "diff": "^3.1.0",
+ "make-error": "^1.1.1",
+ "minimist": "^1.2.0",
+ "mkdirp": "^0.5.1",
+ "source-map-support": "^0.5.3",
+ "yn": "^2.0.0"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "dev": true
+ }
+ }
+ },
+ "tsickle": {
+ "version": "0.32.1",
+ "resolved": "https://registry.npmjs.org/tsickle/-/tsickle-0.32.1.tgz",
+ "integrity": "sha512-JW9j+W0SaMSZGejIFZBk0AiPfnhljK3oLx5SaqxrJhjlvzFyPml5zqG1/PuScUj6yTe1muEqwk5CnDK0cOZmKw==",
+ "dev": true,
+ "requires": {
+ "jasmine-diff": "^0.1.3",
+ "minimist": "^1.2.0",
+ "mkdirp": "^0.5.1",
+ "source-map": "^0.6.0",
+ "source-map-support": "^0.5.0"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "tslib": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
+ "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
+ },
+ "tslint": {
+ "version": "5.9.1",
+ "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.9.1.tgz",
+ "integrity": "sha1-ElX4ej/1frCw4fDmEKi0dIBGya4=",
+ "dev": true,
+ "requires": {
+ "babel-code-frame": "^6.22.0",
+ "builtin-modules": "^1.1.1",
+ "chalk": "^2.3.0",
+ "commander": "^2.12.1",
+ "diff": "^3.2.0",
+ "glob": "^7.1.1",
+ "js-yaml": "^3.7.0",
+ "minimatch": "^3.0.4",
+ "resolve": "^1.3.2",
+ "semver": "^5.3.0",
+ "tslib": "^1.8.0",
+ "tsutils": "^2.12.1"
+ },
+ "dependencies": {
+ "resolve": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz",
+ "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==",
+ "dev": true,
+ "requires": {
+ "path-parse": "^1.0.5"
+ }
+ }
+ }
+ },
+ "tsutils": {
+ "version": "2.29.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
+ "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.8.1"
+ }
+ },
+ "tty-browserify": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz",
+ "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=",
+ "dev": true
+ },
+ "tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "tweetnacl": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+ "dev": true,
+ "optional": true
+ },
+ "type-check": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+ "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "~1.1.2"
+ }
+ },
+ "type-is": {
+ "version": "1.6.16",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
+ "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
+ "dev": true,
+ "requires": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.18"
+ }
+ },
+ "typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+ "dev": true
+ },
+ "typescript": {
+ "version": "2.7.2",
+ "resolved": "http://registry.npmjs.org/typescript/-/typescript-2.7.2.tgz",
+ "integrity": "sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==",
+ "dev": true
+ },
+ "uglify-js": {
+ "version": "3.4.9",
+ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz",
+ "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==",
+ "dev": true,
+ "requires": {
+ "commander": "~2.17.1",
+ "source-map": "~0.6.1"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "uglifyjs-webpack-plugin": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz",
+ "integrity": "sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw==",
+ "dev": true,
+ "requires": {
+ "cacache": "^10.0.4",
+ "find-cache-dir": "^1.0.0",
+ "schema-utils": "^0.4.5",
+ "serialize-javascript": "^1.4.0",
+ "source-map": "^0.6.1",
+ "uglify-es": "^3.3.4",
+ "webpack-sources": "^1.1.0",
+ "worker-farm": "^1.5.2"
+ },
+ "dependencies": {
+ "commander": {
+ "version": "2.13.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz",
+ "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "uglify-es": {
+ "version": "3.3.9",
+ "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz",
+ "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==",
+ "dev": true,
+ "requires": {
+ "commander": "~2.13.0",
+ "source-map": "~0.6.1"
+ }
+ }
+ }
+ },
+ "ultron": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz",
+ "integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=",
+ "dev": true
+ },
+ "union-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
+ "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=",
+ "dev": true,
+ "requires": {
+ "arr-union": "^3.1.0",
+ "get-value": "^2.0.6",
+ "is-extendable": "^0.1.1",
+ "set-value": "^0.4.3"
+ },
+ "dependencies": {
+ "extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
+ "dev": true,
+ "requires": {
+ "is-extendable": "^0.1.0"
+ }
+ },
+ "set-value": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz",
+ "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=",
+ "dev": true,
+ "requires": {
+ "extend-shallow": "^2.0.1",
+ "is-extendable": "^0.1.1",
+ "is-plain-object": "^2.0.1",
+ "to-object-path": "^0.3.0"
+ }
+ }
+ }
+ },
+ "unique-filename": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz",
+ "integrity": "sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=",
+ "dev": true,
+ "requires": {
+ "unique-slug": "^2.0.0"
+ }
+ },
+ "unique-slug": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz",
+ "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=",
+ "dev": true,
+ "requires": {
+ "imurmurhash": "^0.1.4"
+ }
+ },
+ "unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
+ "dev": true
+ },
+ "unset-value": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz",
+ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=",
+ "dev": true,
+ "requires": {
+ "has-value": "^0.3.1",
+ "isobject": "^3.0.0"
+ },
+ "dependencies": {
+ "has-value": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz",
+ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=",
+ "dev": true,
+ "requires": {
+ "get-value": "^2.0.3",
+ "has-values": "^0.1.4",
+ "isobject": "^2.0.0"
+ },
+ "dependencies": {
+ "isobject": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz",
+ "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=",
+ "dev": true,
+ "requires": {
+ "isarray": "1.0.0"
+ }
+ }
+ }
+ },
+ "has-values": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz",
+ "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=",
+ "dev": true
+ }
+ }
+ },
+ "upath": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz",
+ "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==",
+ "dev": true
+ },
+ "upper-case": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
+ "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=",
+ "dev": true
+ },
+ "uri-js": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-3.0.2.tgz",
+ "integrity": "sha1-+QuFhQf4HepNz7s8TD2/orVX+qo=",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "urix": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
+ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
+ "dev": true
+ },
+ "url": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+ "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+ "dev": true,
+ "requires": {
+ "punycode": "1.3.2",
+ "querystring": "0.2.0"
+ },
+ "dependencies": {
+ "punycode": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=",
+ "dev": true
+ }
+ }
+ },
+ "url-join": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.0.tgz",
+ "integrity": "sha1-TTNA6AfTdzvamZH4MFrNzCpmXSo=",
+ "dev": true
+ },
+ "url-loader": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.1.tgz",
+ "integrity": "sha512-vugEeXjyYFBCUOpX+ZuaunbK3QXMKaQ3zUnRfIpRBlGkY7QizCnzyyn2ASfcxsvyU3ef+CJppVywnl3Kgf13Gg==",
+ "dev": true,
+ "requires": {
+ "loader-utils": "^1.1.0",
+ "mime": "^2.0.3",
+ "schema-utils": "^1.0.0"
+ },
+ "dependencies": {
+ "mime": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz",
+ "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==",
+ "dev": true
+ },
+ "schema-utils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-errors": "^1.0.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ }
+ }
+ },
+ "url-parse": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.3.tgz",
+ "integrity": "sha512-rh+KuAW36YKo0vClhQzLLveoj8FwPJNu65xLb7Mrt+eZht0IPT0IXgSv8gcMegZ6NvjJUALf6Mf25POlMwD1Fw==",
+ "dev": true,
+ "requires": {
+ "querystringify": "^2.0.0",
+ "requires-port": "^1.0.0"
+ }
+ },
+ "use": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
+ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
+ "dev": true
+ },
+ "useragent": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz",
+ "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "4.1.x",
+ "tmp": "0.0.x"
+ }
+ },
+ "util": {
+ "version": "0.10.4",
+ "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
+ "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3"
+ }
+ },
+ "util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+ "dev": true
+ },
+ "util.promisify": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz",
+ "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.2",
+ "object.getownpropertydescriptors": "^2.0.3"
+ }
+ },
+ "utila": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz",
+ "integrity": "sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=",
+ "dev": true
+ },
+ "utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
+ "dev": true
+ },
+ "uuid": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
+ "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
+ "dev": true
+ },
+ "validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "dev": true,
+ "requires": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "validate-npm-package-name": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
+ "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=",
+ "dev": true,
+ "requires": {
+ "builtins": "^1.0.3"
+ }
+ },
+ "vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
+ "dev": true
+ },
+ "verror": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+ "dev": true,
+ "requires": {
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
+ }
+ },
+ "vm-browserify": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz",
+ "integrity": "sha1-XX6kW7755Kb/ZflUOOCofDV9WnM=",
+ "dev": true,
+ "requires": {
+ "indexof": "0.0.1"
+ }
+ },
+ "void-elements": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz",
+ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=",
+ "dev": true
+ },
+ "watchpack": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
+ "integrity": "sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA==",
+ "dev": true,
+ "requires": {
+ "chokidar": "^2.0.2",
+ "graceful-fs": "^4.1.2",
+ "neo-async": "^2.5.0"
+ }
+ },
+ "wbuf": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
+ "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==",
+ "dev": true,
+ "requires": {
+ "minimalistic-assert": "^1.0.0"
+ }
+ },
+ "webassemblyjs": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/webassemblyjs/-/webassemblyjs-1.4.3.tgz",
+ "integrity": "sha512-4lOV1Lv6olz0PJkDGQEp82HempAn147e6BXijWDzz9g7/2nSebVP9GVg62Fz5ZAs55mxq13GA0XLyvY8XkyDjg==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.4.3",
+ "@webassemblyjs/validation": "1.4.3",
+ "@webassemblyjs/wasm-parser": "1.4.3",
+ "@webassemblyjs/wast-parser": "1.4.3",
+ "long": "^3.2.0"
+ }
+ },
+ "webdriver-js-extender": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/webdriver-js-extender/-/webdriver-js-extender-2.0.0.tgz",
+ "integrity": "sha512-fbyKiVu3azzIc5d4+26YfuPQcFTlgFQV5yQ/0OQj4Ybkl4g1YQuIPskf5v5wqwRJhHJnPHthB6tqCjWHOKLWag==",
+ "dev": true,
+ "requires": {
+ "@types/selenium-webdriver": "^3.0.0",
+ "selenium-webdriver": "^3.0.1"
+ }
+ },
+ "webpack": {
+ "version": "4.9.2",
+ "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.9.2.tgz",
+ "integrity": "sha512-jlWrCrJDU3sdWFprel6jHH8esN2C++Q8ehedRo74u7MWLTUJn9SD7RSgsCTEZCSRpVpMascDylAqPoldauOMfA==",
+ "dev": true,
+ "requires": {
+ "@webassemblyjs/ast": "1.4.3",
+ "@webassemblyjs/wasm-edit": "1.4.3",
+ "@webassemblyjs/wasm-parser": "1.4.3",
+ "acorn": "^5.0.0",
+ "acorn-dynamic-import": "^3.0.0",
+ "ajv": "^6.1.0",
+ "ajv-keywords": "^3.1.0",
+ "chrome-trace-event": "^0.1.1",
+ "enhanced-resolve": "^4.0.0",
+ "eslint-scope": "^3.7.1",
+ "json-parse-better-errors": "^1.0.2",
+ "loader-runner": "^2.3.0",
+ "loader-utils": "^1.1.0",
+ "memory-fs": "~0.4.1",
+ "micromatch": "^3.1.8",
+ "mkdirp": "~0.5.0",
+ "neo-async": "^2.5.0",
+ "node-libs-browser": "^2.0.0",
+ "schema-utils": "^0.4.4",
+ "tapable": "^1.0.0",
+ "uglifyjs-webpack-plugin": "^1.2.4",
+ "watchpack": "^1.5.0",
+ "webpack-sources": "^1.0.1"
+ }
+ },
+ "webpack-core": {
+ "version": "0.6.9",
+ "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.9.tgz",
+ "integrity": "sha1-/FcViMhVjad76e+23r3Fo7FyvcI=",
+ "dev": true,
+ "requires": {
+ "source-list-map": "~0.1.7",
+ "source-map": "~0.4.1"
+ },
+ "dependencies": {
+ "source-list-map": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.8.tgz",
+ "integrity": "sha1-xVCyq1Qn9rPyH1r+rYjE9Vh7IQY=",
+ "dev": true
+ },
+ "source-map": {
+ "version": "0.4.4",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz",
+ "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=",
+ "dev": true,
+ "requires": {
+ "amdefine": ">=0.0.4"
+ }
+ }
+ }
+ },
+ "webpack-dev-middleware": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-3.2.0.tgz",
+ "integrity": "sha512-YJLMF/96TpKXaEQwaLEo+Z4NDK8aV133ROF6xp9pe3gQoS7sxfpXh4Rv9eC+8vCvWfmDjRQaMSlRPbO+9G6jgA==",
+ "dev": true,
+ "requires": {
+ "loud-rejection": "^1.6.0",
+ "memory-fs": "~0.4.1",
+ "mime": "^2.3.1",
+ "path-is-absolute": "^1.0.0",
+ "range-parser": "^1.0.3",
+ "url-join": "^4.0.0",
+ "webpack-log": "^2.0.0"
+ },
+ "dependencies": {
+ "mime": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz",
+ "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==",
+ "dev": true
+ }
+ }
+ },
+ "webpack-dev-server": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-3.1.7.tgz",
+ "integrity": "sha512-KagFrNHf3QKndS61cXqzkQ4gpdXo0d1LZTTplAJzNK1Ev2ZyJiu+BzerW/2dixYYfpnGzp0AcvCXpmYXIOkFOA==",
+ "dev": true,
+ "requires": {
+ "ansi-html": "0.0.7",
+ "bonjour": "^3.5.0",
+ "chokidar": "^2.0.0",
+ "compression": "^1.5.2",
+ "connect-history-api-fallback": "^1.3.0",
+ "debug": "^3.1.0",
+ "del": "^3.0.0",
+ "express": "^4.16.2",
+ "html-entities": "^1.2.0",
+ "http-proxy-middleware": "~0.18.0",
+ "import-local": "^1.0.0",
+ "internal-ip": "^3.0.1",
+ "ip": "^1.1.5",
+ "killable": "^1.0.0",
+ "loglevel": "^1.4.1",
+ "opn": "^5.1.0",
+ "portfinder": "^1.0.9",
+ "schema-utils": "^1.0.0",
+ "selfsigned": "^1.9.1",
+ "serve-index": "^1.7.2",
+ "sockjs": "0.3.19",
+ "sockjs-client": "1.1.5",
+ "spdy": "^3.4.1",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^5.1.0",
+ "webpack-dev-middleware": "3.2.0",
+ "webpack-log": "^2.0.0",
+ "yargs": "12.0.1"
+ },
+ "dependencies": {
+ "ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+ "dev": true
+ },
+ "camelcase": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz",
+ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=",
+ "dev": true
+ },
+ "cliui": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
+ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
+ "dev": true,
+ "requires": {
+ "string-width": "^2.1.1",
+ "strip-ansi": "^4.0.0",
+ "wrap-ansi": "^2.0.0"
+ },
+ "dependencies": {
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ }
+ }
+ },
+ "cross-spawn": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
+ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^4.0.1",
+ "shebang-command": "^1.2.0",
+ "which": "^1.2.9"
+ }
+ },
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "dev": true,
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "decamelize": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz",
+ "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==",
+ "dev": true,
+ "requires": {
+ "xregexp": "4.0.0"
+ }
+ },
+ "execa": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
+ "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
+ "dev": true,
+ "requires": {
+ "cross-spawn": "^5.0.1",
+ "get-stream": "^3.0.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ }
+ },
+ "find-up": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^3.0.0"
+ }
+ },
+ "is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "dev": true
+ },
+ "locate-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^3.0.0",
+ "path-exists": "^3.0.0"
+ }
+ },
+ "os-locale": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz",
+ "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==",
+ "dev": true,
+ "requires": {
+ "execa": "^0.7.0",
+ "lcid": "^1.0.0",
+ "mem": "^1.1.0"
+ }
+ },
+ "p-limit": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz",
+ "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==",
+ "dev": true,
+ "requires": {
+ "p-try": "^2.0.0"
+ }
+ },
+ "p-locate": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^2.0.0"
+ }
+ },
+ "p-try": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz",
+ "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==",
+ "dev": true
+ },
+ "schema-utils": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz",
+ "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-errors": "^1.0.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ },
+ "string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "dev": true,
+ "requires": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
+ },
+ "dependencies": {
+ "strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^3.0.0"
+ }
+ }
+ }
+ },
+ "which-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
+ "dev": true
+ },
+ "yargs": {
+ "version": "12.0.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.1.tgz",
+ "integrity": "sha512-B0vRAp1hRX4jgIOWFtjfNjd9OA9RWYZ6tqGA9/I/IrTMsxmKvtWy+ersM+jzpQqbC3YfLzeABPdeTgcJ9eu1qQ==",
+ "dev": true,
+ "requires": {
+ "cliui": "^4.0.0",
+ "decamelize": "^2.0.0",
+ "find-up": "^3.0.0",
+ "get-caller-file": "^1.0.1",
+ "os-locale": "^2.0.0",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^1.0.1",
+ "set-blocking": "^2.0.0",
+ "string-width": "^2.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^3.2.1 || ^4.0.0",
+ "yargs-parser": "^10.1.0"
+ }
+ },
+ "yargs-parser": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz",
+ "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^4.1.0"
+ }
+ }
+ }
+ },
+ "webpack-log": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz",
+ "integrity": "sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg==",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "^3.0.0",
+ "uuid": "^3.3.2"
+ }
+ },
+ "webpack-merge": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.1.4.tgz",
+ "integrity": "sha512-TmSe1HZKeOPey3oy1Ov2iS3guIZjWvMT2BBJDzzT5jScHTjVC3mpjJofgueEzaEd6ibhxRDD6MIblDr8tzh8iQ==",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.17.5"
+ }
+ },
+ "webpack-sources": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.2.0.tgz",
+ "integrity": "sha512-9BZwxR85dNsjWz3blyxdOhTgtnQvv3OEs5xofI0wPYTwu5kaWxS08UuD1oI7WLBLpRO+ylf0ofnXLXWmGb2WMw==",
+ "dev": true,
+ "requires": {
+ "source-list-map": "^2.0.0",
+ "source-map": "~0.6.1"
+ },
+ "dependencies": {
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ }
+ }
+ },
+ "webpack-subresource-integrity": {
+ "version": "1.1.0-rc.4",
+ "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-1.1.0-rc.4.tgz",
+ "integrity": "sha1-xcTj1pD50vZKlVDgeodn+Xlqpdg=",
+ "dev": true,
+ "requires": {
+ "webpack-core": "^0.6.8"
+ }
+ },
+ "websocket-driver": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.0.tgz",
+ "integrity": "sha1-DK+dLXVdk67gSdS90NP+LMoqJOs=",
+ "dev": true,
+ "requires": {
+ "http-parser-js": ">=0.4.0",
+ "websocket-extensions": ">=0.1.1"
+ }
+ },
+ "websocket-extensions": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
+ "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==",
+ "dev": true
+ },
+ "when": {
+ "version": "3.6.4",
+ "resolved": "https://registry.npmjs.org/when/-/when-3.6.4.tgz",
+ "integrity": "sha1-RztRfsFZ4rhQBUl6E5g/CVQS404=",
+ "dev": true
+ },
+ "which": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "which-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz",
+ "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=",
+ "dev": true,
+ "optional": true
+ },
+ "wide-align": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+ "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+ "dev": true,
+ "requires": {
+ "string-width": "^1.0.2 || 2"
+ }
+ },
+ "wordwrap": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
+ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
+ "dev": true
+ },
+ "worker-farm": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz",
+ "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==",
+ "dev": true,
+ "requires": {
+ "errno": "~0.1.7"
+ }
+ },
+ "wrap-ansi": {
+ "version": "2.1.0",
+ "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
+ "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
+ "dev": true,
+ "requires": {
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1"
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "ws": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-1.1.2.tgz",
+ "integrity": "sha1-iiRPoFJAHgjJiGz0SoUYnh/UBn8=",
+ "dev": true,
+ "requires": {
+ "options": ">=0.0.5",
+ "ultron": "1.0.x"
+ }
+ },
+ "wtf-8": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wtf-8/-/wtf-8-1.0.0.tgz",
+ "integrity": "sha1-OS2LotDxw00e4tYw8V0O+2jhBIo=",
+ "dev": true
+ },
+ "xml2js": {
+ "version": "0.4.19",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
+ "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
+ "dev": true,
+ "requires": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~9.0.1"
+ },
+ "dependencies": {
+ "sax": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
+ "dev": true
+ }
+ }
+ },
+ "xmlbuilder": {
+ "version": "9.0.7",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
+ "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=",
+ "dev": true
+ },
+ "xmldom": {
+ "version": "0.1.27",
+ "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz",
+ "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=",
+ "dev": true
+ },
+ "xmlhttprequest-ssl": {
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz",
+ "integrity": "sha1-GFqIjATspGw+QHDZn3tJ3jUomS0=",
+ "dev": true
+ },
+ "xregexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz",
+ "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==",
+ "dev": true
+ },
+ "xtend": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
+ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
+ "dev": true
+ },
+ "xxhashjs": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/xxhashjs/-/xxhashjs-0.2.2.tgz",
+ "integrity": "sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==",
+ "dev": true,
+ "requires": {
+ "cuint": "^0.2.2"
+ }
+ },
+ "y18n": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
+ "dev": true
+ },
+ "yallist": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+ "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+ "dev": true
+ },
+ "yargs": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz",
+ "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "camelcase": "^3.0.0",
+ "cliui": "^3.2.0",
+ "decamelize": "^1.1.1",
+ "get-caller-file": "^1.0.1",
+ "os-locale": "^1.4.0",
+ "read-pkg-up": "^1.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^1.0.1",
+ "set-blocking": "^2.0.0",
+ "string-width": "^1.0.2",
+ "which-module": "^1.0.0",
+ "y18n": "^3.2.1",
+ "yargs-parser": "^5.0.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
+ "dev": true,
+ "optional": true
+ },
+ "y18n": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
+ "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "yargs-parser": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz",
+ "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "camelcase": "^3.0.0"
+ },
+ "dependencies": {
+ "camelcase": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz",
+ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
+ "yauzl": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
+ "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=",
+ "dev": true,
+ "requires": {
+ "fd-slicer": "~1.0.1"
+ }
+ },
+ "yeast": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
+ "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=",
+ "dev": true
+ },
+ "yn": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz",
+ "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=",
+ "dev": true
+ },
+ "zone.js": {
+ "version": "0.8.26",
+ "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.26.tgz",
+ "integrity": "sha512-W9Nj+UmBJG251wkCacIkETgra4QgBo/vgoEkb4a2uoLzpQG7qF9nzwoLXWU5xj3Fg2mxGvEDh47mg24vXccYjA=="
+ }
+ }
+}
--- /dev/null
+{
+ "name": "eg",
+ "version": "0.0.0",
+ "scripts": {
+ "ng": "ng",
+ "start": "ng serve",
+ "build": "ng build",
+ "test": "npm run create-mock-idl; ng test",
+ "lint": "ng lint",
+ "e2e": "ng e2e",
+ "create-mock-idl": "cd src/test_data && perl idl2js.pl",
+ "export-strings": "ng xi18n --output-path locale",
+ "merge-strings": "xliffmerge",
+ "build-fr-CA": "ng build --configuration=production-fr-CA --output-path ../../web/eg2/fr-CA --deploy-url /eg2/fr-CA/ --base-href /eg2/fr-CA; sed -i s/IDL2js\\\"/IDL2js?locale=fr-CA\\\"/g ../../web/eg2/fr-CA/index.html; sed -i s/lang=\\\"en\\\"/lang=\\\"fr\\\"/g ../../web/eg2/fr-CA/index.html"
+ },
+ "private": true,
+ "dependencies": {
+ "@angular/animations": "^6.1.0",
+ "@angular/common": "^6.1.0",
+ "@angular/compiler": "^6.1.0",
+ "@angular/core": "^6.1.0",
+ "@angular/forms": "^6.1.0",
+ "@angular/http": "^6.1.0",
+ "@angular/platform-browser": "^6.1.0",
+ "@angular/platform-browser-dynamic": "^6.1.0",
+ "@angular/router": "^6.1.0",
+ "@ng-bootstrap/ng-bootstrap": "^3.2.0",
+ "bootstrap-css-only": "^4.1.1",
+ "core-js": "^2.5.4",
+ "ngx-cookie": "^4.0.2",
+ "rxjs": "^6.0.0",
+ "rxjs-compat": "^6.3.2",
+ "zone.js": "~0.8.26"
+ },
+ "devDependencies": {
+ "@angular-devkit/build-angular": "~0.7.0",
+ "@angular/cli": "~6.1.5",
+ "@angular/compiler-cli": "^6.1.0",
+ "@angular/language-service": "^6.1.0",
+ "@types/jasmine": "~2.8.6",
+ "@types/jasminewd2": "~2.0.3",
+ "@types/node": "~8.9.4",
+ "codelyzer": "~4.2.1",
+ "jasmine-core": "~2.99.1",
+ "jasmine-spec-reporter": "~4.2.1",
+ "karma": "~1.7.1",
+ "karma-chrome-launcher": "~2.2.0",
+ "karma-coverage-istanbul-reporter": "~2.0.0",
+ "karma-jasmine": "~1.1.1",
+ "karma-jasmine-html-reporter": "^0.2.2",
+ "karma-phantomjs-launcher": "^1.0.4",
+ "ngx-i18nsupport": "^0.17.0",
+ "protractor": "~5.4.0",
+ "ts-node": "~5.0.1",
+ "tslint": "~5.9.1",
+ "typescript": "~2.7.2"
+ },
+ "xliffmergeOptions": {
+ "srcDir": "src/locale",
+ "genDir": "src/locale",
+ "i18nFile": "messages.xlf",
+ "i18nBaseFile": "messages",
+ "i18nFormat": "xlf",
+ "encoding": "UTF-8",
+ "defaultLanguage": "en",
+ "languages": [
+ "en",
+ "fr-CA"
+ ],
+ "removeUnusedIds": true,
+ "supportNgxTranslate": false,
+ "ngxTranslateExtractionPattern": "@@|ngx-translate",
+ "useSourceAsTarget": true,
+ "targetPraefix": "",
+ "targetSuffix": "",
+ "beautifyOutput": false,
+ "allowIdChange": false,
+ "autotranslate": false,
+ "apikey": "",
+ "apikeyfile": "",
+ "verbose": false,
+ "quiet": false
+ }
+}
--- /dev/null
+// Protractor configuration file, see link for more information
+// https://github.com/angular/protractor/blob/master/lib/config.ts
+
+const { SpecReporter } = require('jasmine-spec-reporter');
+
+exports.config = {
+ allScriptsTimeout: 11000,
+ specs: [
+ './e2e/**/*.e2e-spec.ts'
+ ],
+ capabilities: {
+ 'browserName': 'chrome'
+ },
+ directConnect: true,
+ baseUrl: 'http://localhost:4200/',
+ framework: 'jasmine',
+ jasmineNodeOpts: {
+ showColors: true,
+ defaultTimeoutInterval: 30000,
+ print: function() {}
+ },
+ onPrepare() {
+ require('ts-node').register({
+ project: 'e2e/tsconfig.e2e.json'
+ });
+ jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
+ }
+};
--- /dev/null
+import {Component} from '@angular/core';
+
+@Component({
+ selector: 'eg-root',
+ template: '<router-outlet></router-outlet><eg-print></eg-print>'
+})
+
+export class BaseComponent {
+}
+
+
--- /dev/null
+/**
+ * BaseModule is the shared starting point for all apps. It provides
+ * the root route and a simple welcome page for users that end up here
+ * accidentally.
+ */
+import {BrowserModule} from '@angular/platform-browser';
+import {NgModule} from '@angular/core';
+import {NgbModule} from '@ng-bootstrap/ng-bootstrap'; // ng-bootstrap
+import {CookieModule} from 'ngx-cookie'; // import CookieMonster
+
+import {EgCommonModule} from './common.module';
+import {BaseComponent} from './app.component';
+import {BaseRoutingModule} from './routing.module';
+import {WelcomeComponent} from './welcome.component';
+
+@NgModule({
+ declarations: [
+ BaseComponent,
+ WelcomeComponent
+ ],
+ imports: [
+ EgCommonModule.forRoot(),
+ BaseRoutingModule,
+ BrowserModule,
+ NgbModule.forRoot(),
+ CookieModule.forRoot()
+ ],
+ exports: [],
+ bootstrap: [BaseComponent]
+})
+
+export class BaseModule {}
+
--- /dev/null
+/**
+ * Modules, services, and components used by all apps.
+ */
+import {CommonModule, DatePipe, CurrencyPipe} from '@angular/common';
+import {NgModule, ModuleWithProviders} from '@angular/core';
+import {RouterModule} from '@angular/router';
+import {FormsModule} from '@angular/forms';
+import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
+
+/*
+Note core services are injected into 'root'.
+They do not have to be added to the providers list.
+*/
+
+// consider moving these to core...
+import {FormatService} from '@eg/core/format.service';
+import {PrintService} from '@eg/share/print/print.service';
+
+// Globally available components
+import {PrintComponent} from '@eg/share/print/print.component';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
+import {PromptDialogComponent} from '@eg/share/dialog/prompt.component';
+import {ProgressInlineComponent} from '@eg/share/dialog/progress-inline.component';
+import {ProgressDialogComponent} from '@eg/share/dialog/progress.component';
+
+@NgModule({
+ declarations: [
+ PrintComponent,
+ DialogComponent,
+ ConfirmDialogComponent,
+ PromptDialogComponent,
+ ProgressInlineComponent,
+ ProgressDialogComponent
+ ],
+ imports: [
+ CommonModule,
+ FormsModule,
+ RouterModule,
+ NgbModule
+ ],
+ exports: [
+ CommonModule,
+ RouterModule,
+ NgbModule,
+ FormsModule,
+ PrintComponent,
+ DialogComponent,
+ ConfirmDialogComponent,
+ PromptDialogComponent,
+ ProgressInlineComponent,
+ ProgressDialogComponent
+ ]
+})
+
+export class EgCommonModule {
+ /** forRoot() lets us define services that should only be
+ * instantiated once for all loaded routes */
+ static forRoot(): ModuleWithProviders {
+ return {
+ ngModule: EgCommonModule,
+ providers: [
+ DatePipe,
+ CurrencyPipe,
+ PrintService,
+ FormatService
+ ]
+ };
+ }
+}
+
--- /dev/null
+Core Angular services and assocated types/classes.
+
+Core services are imported and exported by the base module and
+automatically added as dependencies to ALL applications.
+
+1. Only add services here that are universally required.
+2. Avoid URL path navigation in the core services as paths will vary
+ by application.
+
--- /dev/null
+import {Injectable, EventEmitter} from '@angular/core';
+import {NetService} from './net.service';
+import {EventService, EgEvent} from './event.service';
+import {IdlService, IdlObject} from './idl.service';
+import {StoreService} from './store.service';
+
+// Not universally available.
+declare var BroadcastChannel;
+
+// Models a login instance.
+class AuthUser {
+ user: IdlObject; // actor.usr (au) object
+ workstation: string; // workstation name
+ token: string;
+ authtime: number;
+
+ constructor(token: string, authtime: number, workstation?: string) {
+ this.token = token;
+ this.workstation = workstation;
+ this.authtime = authtime;
+ }
+}
+
+// Params required for calling the login() method.
+interface AuthLoginArgs {
+ username: string;
+ password: string;
+ type: string;
+ workstation?: string;
+}
+
+export enum AuthWsState {
+ PENDING,
+ NOT_USED,
+ NOT_FOUND_SERVER,
+ NOT_FOUND_LOCAL,
+ VALID
+}
+
+@Injectable({providedIn: 'root'})
+export class AuthService {
+
+ private authChannel: any;
+
+ private activeUser: AuthUser = null;
+
+ workstationState: AuthWsState = AuthWsState.PENDING;
+
+ // Used by auth-checking resolvers
+ redirectUrl: string;
+
+ // reference to active auth validity setTimeout handler.
+ pollTimeout: any;
+
+ constructor(
+ private egEvt: EventService,
+ private net: NetService,
+ private store: StoreService
+ ) {
+
+ // BroadcastChannel is not yet defined in PhantomJS and elsewhere
+ this.authChannel = (typeof BroadcastChannel === 'undefined') ?
+ {} : new BroadcastChannel('eg.auth');
+ }
+
+ // Returns true if we are currently in op-change mode.
+ opChangeIsActive(): boolean {
+ return Boolean(this.store.getLoginSessionItem('eg.auth.time.oc'));
+ }
+
+ // - Accessor functions always refer to the active user.
+
+ user(): IdlObject {
+ return this.activeUser ? this.activeUser.user : null;
+ }
+
+ // Workstation name.
+ workstation(): string {
+ return this.activeUser ? this.activeUser.workstation : null;
+ }
+
+ token(): string {
+ return this.activeUser ? this.activeUser.token : null;
+ }
+
+ authtime(): number {
+ return this.activeUser ? this.activeUser.authtime : 0;
+ }
+
+ // NOTE: NetService emits an event if the auth session has expired.
+ // This only rejects when no authtoken is found.
+ testAuthToken(): Promise<any> {
+
+ if (!this.activeUser) {
+ // Only necessary on new page loads. During op-change,
+ // for example, we already have an activeUser.
+ this.activeUser = new AuthUser(
+ this.store.getLoginSessionItem('eg.auth.token'),
+ this.store.getLoginSessionItem('eg.auth.time')
+ );
+ }
+
+ if (!this.token()) {
+ return Promise.reject('no authtoken');
+ }
+
+ return this.net.request(
+ 'open-ils.auth',
+ 'open-ils.auth.session.retrieve', this.token()).toPromise()
+ .then(user => {
+ // NetService interceps NO_SESSION events.
+ // We can only get here if the session is valid.
+ this.activeUser.user = user;
+ this.listenForLogout();
+ this.sessionPoll();
+ });
+ }
+
+ loginApi(args: AuthLoginArgs, service: string,
+ method: string, isOpChange?: boolean): Promise<void> {
+
+ return this.net.request(service, method, args)
+ .toPromise().then(res => {
+ return this.handleLoginResponse(
+ args, this.egEvt.parse(res), isOpChange);
+ });
+ }
+
+ login(args: AuthLoginArgs, isOpChange?: boolean): Promise<void> {
+ let service = 'open-ils.auth';
+ let method = 'open-ils.auth.login';
+
+ return this.net.request(
+ 'open-ils.auth_proxy',
+ 'open-ils.auth_proxy.enabled')
+ .toPromise().then(
+ enabled => {
+ if (Number(enabled) === 1) {
+ service = 'open-ils.auth_proxy';
+ method = 'open-ils.auth_proxy.login';
+ }
+ return this.loginApi(args, service, method, isOpChange);
+ },
+ error => {
+ // auth_proxy check resulted in a low-level error.
+ // Likely the service is not running. Fall back to
+ // standard auth login.
+ return this.loginApi(args, service, method, isOpChange);
+ }
+ );
+ }
+
+ handleLoginResponse(
+ args: AuthLoginArgs, evt: EgEvent, isOpChange: boolean): Promise<void> {
+
+ switch (evt.textcode) {
+ case 'SUCCESS':
+ return this.handleLoginOk(args, evt, isOpChange);
+
+ case 'WORKSTATION_NOT_FOUND':
+ console.error(`No such workstation "${args.workstation}"`);
+ this.workstationState = AuthWsState.NOT_FOUND_SERVER;
+ delete args.workstation;
+ return this.login(args, isOpChange);
+
+ default:
+ console.error(`Login returned unexpected event: ${evt}`);
+ return Promise.reject('login failed');
+ }
+ }
+
+ // Stash the login data
+ handleLoginOk(args: AuthLoginArgs, evt: EgEvent, isOpChange: boolean): Promise<void> {
+
+ if (isOpChange) {
+ this.store.setLoginSessionItem('eg.auth.token.oc', this.token());
+ this.store.setLoginSessionItem('eg.auth.time.oc', this.authtime());
+ }
+
+ this.activeUser = new AuthUser(
+ evt.payload.authtoken,
+ evt.payload.authtime,
+ args.workstation
+ );
+
+ this.store.setLoginSessionItem('eg.auth.token', this.token());
+ this.store.setLoginSessionItem('eg.auth.time', this.authtime());
+
+ return Promise.resolve();
+ }
+
+ undoOpChange(): Promise<any> {
+ if (this.opChangeIsActive()) {
+ this.deleteSession();
+ this.activeUser = new AuthUser(
+ this.store.getLoginSessionItem('eg.auth.token.oc'),
+ this.store.getLoginSessionItem('eg.auth.time.oc'),
+ this.activeUser.workstation
+ );
+ this.store.removeLoginSessionItem('eg.auth.token.oc');
+ this.store.removeLoginSessionItem('eg.auth.time.oc');
+ this.store.setLoginSessionItem('eg.auth.token', this.token());
+ this.store.setLoginSessionItem('eg.auth.time', this.authtime());
+ }
+ // Re-fetch the user.
+ return this.testAuthToken();
+ }
+
+ /**
+ * Listen for logout events initiated by other browser tabs.
+ */
+ listenForLogout(): void {
+ if (this.authChannel.onmessage) {
+ return;
+ }
+
+ this.authChannel.onmessage = (e) => {
+ console.debug(
+ `received eg.auth broadcast ${JSON.stringify(e.data)}`);
+
+ if (e.data.action === 'logout') {
+ // Logout will be handled by the originating tab.
+ // We just need to clear tab-local memory.
+ this.cleanup();
+ this.net.authExpired$.emit({viaExternal: true});
+ }
+ };
+ }
+
+ /**
+ * Force-check the validity of the authtoken on occasion.
+ * This allows us to redirect an idle staff client back to the login
+ * page after the session times out. Otherwise, the UI would stay
+ * open with potentially sensitive data visible.
+ * TODO: What is the practical difference (for a browser) between
+ * checking auth validity and the ui.general.idle_timeout setting?
+ * Does that setting serve a purpose in a browser environment?
+ */
+ sessionPoll(): void {
+
+ // add a 5 second delay to give the token plenty of time
+ // to expire on the server.
+ let pollTime = this.authtime() * 1000 + 5000;
+
+ if (pollTime < 60000) {
+ // Never poll more often than once per minute.
+ pollTime = 60000;
+ } else if (pollTime > 2147483647) {
+ // Avoid integer overflow resulting in $timeout() effectively
+ // running with timeout=0 in a loop.
+ pollTime = 2147483647;
+ }
+
+ this.pollTimeout = setTimeout(() => {
+ this.net.request(
+ 'open-ils.auth',
+ 'open-ils.auth.session.retrieve',
+ this.token(),
+ 0, // return extra auth details, unneeded here.
+ 1 // avoid extending the auth timeout
+
+ // NetService intercepts NO_SESSION events.
+ // If the promise resolves, the session is valid.
+ ).subscribe(
+ user => this.sessionPoll(),
+ err => console.warn('auth poll error: ' + err)
+ );
+
+ }, pollTime);
+ }
+
+
+ // Resolves if login workstation matches a workstation known to this
+ // browser instance. No attempt is made to see if the workstation
+ // is present on the server. That happens at login time.
+ verifyWorkstation(): Promise<void> {
+
+ if (!this.user()) {
+ this.workstationState = AuthWsState.PENDING;
+ return Promise.reject('Cannot verify workstation without user');
+ }
+
+ if (!this.user().wsid()) {
+ this.workstationState = AuthWsState.NOT_USED;
+ return Promise.reject('User has no workstation ID to verify');
+ }
+
+ return new Promise((resolve, reject) => {
+ const workstations =
+ this.store.getLocalItem('eg.workstation.all');
+
+ if (workstations) {
+ const ws = workstations.filter(
+ w => Number(w.id) === Number(this.user().wsid()))[0];
+
+ if (ws) {
+ this.activeUser.workstation = ws.name;
+ this.workstationState = AuthWsState.VALID;
+ return resolve();
+ }
+ }
+
+ this.workstationState = AuthWsState.NOT_FOUND_LOCAL;
+ reject();
+ });
+ }
+
+ deleteSession(): void {
+ if (this.token()) {
+ // note we have to subscribe to the net.request or it will
+ // not fire -- observables only run when subscribed to.
+ this.net.request(
+ 'open-ils.auth',
+ 'open-ils.auth.session.delete', this.token())
+ .subscribe(x => {});
+ }
+ }
+
+ // Tell all listening browser tabs that it's time to logout.
+ // This should only be invoked by one tab.
+ broadcastLogout(): void {
+ console.debug('Notifying tabs of imminent auth token removal');
+ this.authChannel.postMessage({action : 'logout'});
+ }
+
+ // Remove/reset session data
+ cleanup(): void {
+ this.activeUser = null;
+ if (this.pollTimeout) {
+ clearTimeout(this.pollTimeout);
+ this.pollTimeout = null;
+ }
+ }
+
+ // Invalidate server auth token and clean up.
+ logout(): void {
+ this.deleteSession();
+ this.store.clearLoginSessionItems();
+ this.cleanup();
+ }
+}
--- /dev/null
+import {Injectable} from '@angular/core';
+
+export class EgEvent {
+ code: number;
+ textcode: string;
+ payload: any;
+ desc: string;
+ debug: string;
+ note: string;
+ servertime: string;
+ ilsperm: string;
+ ilspermloc: number;
+ success: Boolean = false;
+
+ toString(): string {
+ let s = `Event: ${this.code}:${this.textcode} -> ${this.desc}`;
+ if (this.ilsperm) {
+ s += ` ${this.ilsperm}@${this.ilspermloc}`;
+ }
+ if (this.note) {
+ s += `\n${this.note}`;
+ }
+ return s;
+ }
+}
+
+@Injectable({providedIn: 'root'})
+export class EventService {
+
+ /**
+ * Returns an Event if 'thing' is an event, null otherwise.
+ */
+ parse(thing: any): EgEvent {
+
+ // All events have a textcode
+ if (thing && typeof thing === 'object' && 'textcode' in thing) {
+
+ const evt = new EgEvent();
+
+ ['textcode', 'payload', 'desc', 'note', 'servertime', 'ilsperm']
+ .forEach(field => { evt[field] = thing[field]; });
+
+ evt.debug = thing.stacktrace;
+ evt.code = +(thing.ilsevent || -1);
+ evt.ilspermloc = +(thing.ilspermloc || -1);
+ evt.success = thing.textcode === 'SUCCESS';
+
+ return evt;
+ }
+
+ return null;
+ }
+}
+
+
--- /dev/null
+import {EventService} from './event.service';
+
+describe('EventService', () => {
+ let service: EventService;
+ beforeEach(() => {
+ service = new EventService();
+ });
+
+ const evt = {
+ ilsevent: '12345',
+ pid: '12345',
+ desc: 'Test Event Description',
+ payload: {test : 'xyz'},
+ textcode: 'TEST_EVENT',
+ servertime: 'Wed Nov 6 16:05:50 2013'
+ };
+
+ it('should parse an event object', () => {
+ expect(service.parse(evt)).not.toBe(null);
+ });
+
+ it('should not parse a non-event', () => {
+ expect(service.parse({})).toBe(null);
+ });
+
+ it('should not parse a non-event', () => {
+ expect(service.parse({abc : '123'})).toBe(null);
+ });
+
+ it('should not parse a non-event', () => {
+ expect(service.parse([])).toBe(null);
+ });
+
+ it('should not parse a non-event', () => {
+ expect(service.parse('STRING')).toBe(null);
+ });
+
+ it('should not parse a non-event', () => {
+ expect(service.parse(true)).toBe(null);
+ });
+
+ it('should stringify an event', () => {
+ expect(service.parse(evt).toString()).toBe(
+ 'Event: 12345:TEST_EVENT -> Test Event Description');
+ });
+
+});
--- /dev/null
+import {Injectable} from '@angular/core';
+import {DatePipe, CurrencyPipe} from '@angular/common';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {OrgService} from '@eg/core/org.service';
+
+/**
+ * Format IDL vield values for display.
+ */
+
+declare var OpenSRF;
+
+export interface FormatParams {
+ value: any;
+ idlClass?: string;
+ idlField?: string;
+ datatype?: string;
+ orgField?: string; // 'shortname' || 'name'
+ datePlusTime?: boolean;
+}
+
+@Injectable({providedIn: 'root'})
+export class FormatService {
+
+ dateFormat = 'shortDate';
+ dateTimeFormat = 'short';
+ wsOrgTimezone: string = OpenSRF.tz;
+
+ constructor(
+ private datePipe: DatePipe,
+ private currencyPipe: CurrencyPipe,
+ private idl: IdlService,
+ private org: OrgService
+ ) {
+
+ // Create an inilne polyfill for Number.isNaN, which is
+ // not available in PhantomJS for unit testing.
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN
+ if (!Number.isNaN) {
+ // "The following works because NaN is the only value
+ // in javascript which is not equal to itself."
+ Number.isNaN = (value: any) => {
+ return value !== value;
+ };
+ }
+ }
+
+ /**
+ * Create a human-friendly display version of any field type.
+ */
+ transform(params: FormatParams): string {
+ const value = params.value;
+
+ if ( value === undefined
+ || value === null
+ || value === ''
+ || Number.isNaN(value)) {
+ return '';
+ }
+
+ let datatype = params.datatype;
+
+ if (!datatype) {
+ if (params.idlClass && params.idlField) {
+ datatype = this.idl.classes[params.idlClass]
+ .field_map[params.idlField].datatype;
+ } else {
+ // Assume it's a primitive value
+ return value + '';
+ }
+ }
+
+ switch (datatype) {
+
+ case 'org_unit':
+ const orgField = params.orgField || 'shortname';
+ const org = this.org.get(value);
+ return org ? org[orgField]() : '';
+
+ case 'timestamp':
+ const date = new Date(value);
+ let fmt = this.dateFormat || 'shortDate';
+ if (params.datePlusTime) {
+ fmt = this.dateTimeFormat || 'short';
+ }
+ return this.datePipe.transform(date, fmt);
+
+ case 'money':
+ return this.currencyPipe.transform(value);
+
+ case 'bool':
+ // Slightly better than a bare 't' or 'f'.
+ // Should probably add a global true/false string.
+ return Boolean(
+ value === 't' || value === 1 ||
+ value === '1' || value === true
+ ).toString();
+
+ default:
+ return value + '';
+ }
+ }
+}
+
--- /dev/null
+import {DatePipe, CurrencyPipe} from '@angular/common';
+import {IdlService} from './idl.service';
+import {EventService} from './event.service';
+import {NetService} from './net.service';
+import {AuthService} from './auth.service';
+import {PcrudService} from './pcrud.service';
+import {StoreService} from './store.service';
+import {OrgService} from './org.service';
+import {FormatService} from './format.service';
+
+
+describe('FormatService', () => {
+
+ let currencyPipe: CurrencyPipe;
+ let datePipe: DatePipe;
+ let idlService: IdlService;
+ let netService: NetService;
+ let authService: AuthService;
+ let pcrudService: PcrudService;
+ let orgService: OrgService;
+ let evtService: EventService;
+ let storeService: StoreService;
+ let service: FormatService;
+
+ beforeEach(() => {
+ currencyPipe = new CurrencyPipe('en');
+ datePipe = new DatePipe('en');
+ idlService = new IdlService();
+ evtService = new EventService();
+ storeService = new StoreService(null /* CookieService */);
+ netService = new NetService(evtService);
+ authService = new AuthService(evtService, netService, storeService);
+ pcrudService = new PcrudService(idlService, netService, authService);
+ orgService = new OrgService(netService, authService, pcrudService);
+ service = new FormatService(
+ datePipe,
+ currencyPipe,
+ idlService,
+ orgService
+ );
+ });
+
+ const initTestData = () => {
+ idlService.parseIdl();
+ const win: any = window; // trick TS
+ win._eg_mock_data.generateOrgTree(idlService, orgService);
+ };
+
+ it('should format an org unit name', () => {
+ initTestData();
+ const str = service.transform({
+ value: orgService.root(),
+ datatype: 'org_unit',
+ orgField: 'shortname' // currently the default
+ });
+ expect(str).toBe('ROOT'); // from eg_mock.js
+ });
+
+ it('should format a date', () => {
+ initTestData();
+ const str = service.transform({
+ value: new Date(2018, 6, 5),
+ datatype: 'timestamp',
+ });
+ expect(str).toBe('7/5/18');
+ });
+
+ it('should format a date plus time', () => {
+ initTestData();
+ const str = service.transform({
+ value: new Date(2018, 6, 5, 12, 30, 1),
+ datatype: 'timestamp',
+ datePlusTime: true
+ });
+ expect(str).toBe('7/5/18, 12:30 PM');
+ });
+
+
+
+ it('should format money', () => {
+ initTestData();
+ const str = service.transform({
+ value: '12.1',
+ datatype: 'money'
+ });
+ expect(str).toBe('$12.10');
+ });
+
+});
+
--- /dev/null
+import {Injectable} from '@angular/core';
+
+// Added globally by /IDL2js
+declare var _preload_fieldmapper_IDL: Object;
+
+/**
+ * Every IDL object class implements this interface.
+ */
+export interface IdlObject {
+ a: any[];
+ classname: string;
+ _isfieldmapper: boolean;
+ // Dynamically appended functions from the IDL.
+ [fields: string]: any;
+}
+
+@Injectable({providedIn: 'root'})
+export class IdlService {
+
+ classes: any = {}; // IDL class metadata
+ constructors = {}; // IDL instance generators
+
+ /**
+ * Create a new IDL object instance.
+ */
+ create(cls: string, seed?: any[]): IdlObject {
+ if (this.constructors[cls]) {
+ return new this.constructors[cls](seed);
+ }
+ throw new Error(`No such IDL class ${cls}`);
+ }
+
+ parseIdl(): void {
+
+ try {
+ this.classes = _preload_fieldmapper_IDL;
+ } catch (E) {
+ console.error('IDL (IDL2js) not found. Is the system running?');
+ return;
+ }
+
+ /**
+ * Creates the class constructor and getter/setter
+ * methods for each IDL class.
+ */
+ const mkclass = (cls, fields) => {
+ this.classes[cls].classname = cls;
+
+ // This dance lets us encode each IDL object with the
+ // IdlObject interface. Useful for adding type restrictions
+ // where desired for functions, etc.
+ const generator: any = ((): IdlObject => {
+
+ const x: any = function(seed) {
+ this.a = seed || [];
+ this.classname = cls;
+ this._isfieldmapper = true;
+ };
+
+ fields.forEach(function(field, idx) {
+ x.prototype[field.name] = function(n) {
+ if (arguments.length === 1) {
+ this.a[idx] = n;
+ }
+ return this.a[idx];
+ };
+
+ if (!field.label) {
+ field.label = field.name;
+ }
+
+ // Coerce 'aou' links to datatype org_unit for consistency.
+ if (field.datatype === 'link' && field.class === 'aou') {
+ field.datatype = 'org_unit';
+ }
+ });
+
+ return x;
+ });
+
+ this.constructors[cls] = generator();
+
+ // global class constructors required for JSON_v1.js
+ // TODO: polluting the window namespace w/ every IDL class
+ // is less than ideal.
+ window[cls] = this.constructors[cls];
+ };
+
+ Object.keys(this.classes).forEach(class_ => {
+ mkclass(class_, this.classes[class_].fields);
+ });
+ }
+
+ // Makes a deep copy of an IdlObject's / structures containing
+ // IdlObject's. Note we don't use JSON cross-walk because our
+ // JSON lib does not handle circular references.
+ // @depth specifies the maximum number of steps through IdlObject'
+ // we will traverse.
+ clone(source: any, depth?: number): any {
+ if (depth === undefined) {
+ depth = 100;
+ }
+
+ let result;
+ if (typeof source === 'undefined' || source === null) {
+ return source;
+
+ } else if (source._isfieldmapper) {
+ // same depth because we're still cloning this same object
+ result = this.create(source.classname, this.clone(source.a, depth));
+
+ } else {
+ if (Array.isArray(source)) {
+ result = [];
+ } else if (typeof source === 'object') { // source is not null
+ result = {};
+ } else {
+ return source; // primitive
+ }
+
+ for (const j in source) {
+ if (source[j] === null || typeof source[j] === 'undefined') {
+ result[j] = source[j];
+ } else if (source[j]._isfieldmapper) {
+ if (depth) {
+ result[j] = this.clone(source[j], depth - 1);
+ }
+ } else {
+ result[j] = this.clone(source[j], depth);
+ }
+ }
+ }
+
+ return result;
+ }
+}
+
--- /dev/null
+import {IdlService} from './idl.service';
+
+describe('IdlService', () => {
+ let service: IdlService;
+ beforeEach(() => {
+ service = new IdlService();
+ });
+
+ it('should parse the IDL', () => {
+ service.parseIdl();
+ expect(service.classes['aou'].fields.length).toBeGreaterThan(0);
+ });
+
+ it('should create an aou object', () => {
+ service.parseIdl();
+ const org = service.create('aou');
+ expect(typeof org.id).toBe('function');
+ });
+
+ it('should create an aou object with accessor/mutators', () => {
+ service.parseIdl();
+ const org = service.create('aou');
+ org.name('AN ORG');
+ expect(org.name()).toBe('AN ORG');
+ });
+
+});
+
--- /dev/null
+import {Injectable} from '@angular/core';
+import {Location} from '@angular/common';
+import {environment} from '../../environments/environment';
+import {Observable} from 'rxjs/Observable';
+import {of} from 'rxjs';
+import {CookieService} from 'ngx-cookie';
+import {IdlObject} from '@eg/core/idl.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+
+@Injectable({providedIn: 'root'})
+export class LocaleService {
+
+ constructor(
+ private ngLocation: Location,
+ private cookieService: CookieService,
+ private pcrud: PcrudService) {
+ }
+
+ setLocale(code: string) {
+ let url = this.ngLocation.prepareExternalUrl('/');
+
+ // The last part of the base path will be the locale
+ // Replace it with the selected locale
+ url = url.replace(/\/[a-z]{2}-[A-Z]{2}\/$/, `/${code}`);
+
+ // Finally tack the path of the current page back onto the URL
+ // which is more friendly than forcing them back to the splash page.
+ url += this.ngLocation.path();
+
+ // Set a 10-year locale cookie to maintain compatibility
+ // with the AngularJS client.
+ // Cookie takes the form aa_bb instead of aa-BB
+ const cookie = code.replace(/-/, '_').toLowerCase();
+ this.cookieService.put('eg_locale',
+ cookie, {path : '/', secure: true, expires: '+10y'});
+
+ window.location.href = url;
+ }
+
+ // Returns codes supported for the current environment.
+ supportedLocaleCodes(): string[] {
+ return environment.locales || [];
+ }
+
+ // Returns i18n_l objects matching the locales supported
+ // in the current environment.
+ supportedLocales(): Observable<IdlObject> {
+ const locales = this.supportedLocaleCodes();
+
+ if (locales.length === 0) {
+ return of();
+ }
+
+ return this.pcrud.search('i18n_l', {code: locales});
+ }
+
+ // Extract the local from the URL.
+ // It's the last component of the base path.
+ // Note we don't extract it from the cookie since using cookies
+ // to store the locale will not be necessary when AngularJS
+ // is deprecated.
+ currentLocaleCode(): string {
+ const base = this.ngLocation.prepareExternalUrl('/');
+ const code = base.match(/\/([a-z]{2}-[A-Z]{2})\/$/);
+ return code ? code[1] : '';
+ }
+}
+
+
--- /dev/null
+/**
+ *
+ * constructor(private net : NetService) {
+ * ...
+ * this.net.request(service, method, param1 [, param2, ...])
+ * .subscribe(
+ * (res) => console.log('received one resopnse: ' + res),
+ * (err) => console.error('recived request error: ' + err),
+ * () => console.log('request complete')
+ * )
+ * );
+ * ...
+ *
+ * // Example translating a net request into a promise.
+ * this.net.request(service, method, param1)
+ * .toPromise().then(result => console.log(result));
+ *
+ * }
+ *
+ * Each response is relayed via Observable.next(). The interface is
+ * the same for streaming and atomic requests.
+ */
+import {Injectable, EventEmitter} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {Observer} from 'rxjs/Observer';
+import {EventService, EgEvent} from './event.service';
+
+// Global vars from opensrf.js
+// These are availavble at runtime, but are not exported.
+declare var OpenSRF, OSRF_TRANSPORT_TYPE_WS;
+
+export class NetRequest {
+ service: string;
+ method: string;
+ params: any[];
+ observer: Observer<any>;
+ superseded = false;
+ // If set, this will be used instead of a one-off OpenSRF.ClientSession.
+ session?: any;
+ // True if we're using a single-use local session
+ localSession = true;
+
+ // Last Event encountered by this request.
+ // Most callers will not need to import Event since the parsed
+ // event will be available here.
+ evt: EgEvent;
+
+ constructor(service: string, method: string, params: any[], session?: any) {
+ this.service = service;
+ this.method = method;
+ this.params = params;
+ if (session) {
+ this.session = session;
+ this.localSession = false;
+ } else {
+ this.session = new OpenSRF.ClientSession(service);
+ }
+ }
+}
+
+export interface AuthExpiredEvent {
+ // request is set when the auth expiration was determined as a
+ // by-product of making an API call.
+ request?: NetRequest;
+
+ // True if this environment (e.g. browser tab) was notified of the
+ // expired auth token from an external source (e.g. another browser tab).
+ viaExternal?: boolean;
+}
+
+@Injectable({providedIn: 'root'})
+export class NetService {
+
+ permFailed$: EventEmitter<NetRequest>;
+ authExpired$: EventEmitter<AuthExpiredEvent>;
+
+ // If true, permission failures are emitted via permFailed$
+ // and the active request is marked as superseded.
+ permFailedHasHandler: Boolean = false;
+
+ constructor(
+ private egEvt: EventService
+ ) {
+ this.permFailed$ = new EventEmitter<NetRequest>();
+ this.authExpired$ = new EventEmitter<AuthExpiredEvent>();
+ }
+
+ // Standard request call -- Variadic params version
+ request(service: string, method: string, ...params: any[]): Observable<any> {
+ return this.requestWithParamList(service, method, params);
+ }
+
+ // Array params version
+ requestWithParamList(service: string,
+ method: string, params: any[]): Observable<any> {
+ return this.requestCompiled(
+ new NetRequest(service, method, params));
+ }
+
+ // Request with pre-compiled NetRequest
+ requestCompiled(request: NetRequest): Observable<any> {
+ return Observable.create(
+ observer => {
+ request.observer = observer;
+ this.sendCompiledRequest(request);
+ }
+ );
+ }
+
+ // Send the compiled request to the server via WebSockets
+ sendCompiledRequest(request: NetRequest): void {
+ OpenSRF.Session.transport = OSRF_TRANSPORT_TYPE_WS;
+ console.debug(`Net: request ${request.method}`);
+
+ request.session.request({
+ async : true, // WS only operates in async mode
+ method : request.method,
+ params : request.params,
+ oncomplete : () => {
+
+ // TODO: teach opensrf.js to call cleanup() inside
+ // disconnect() and teach Pcrud to call cleanup()
+ // as needed to avoid long-lived session data bloat.
+ if (request.localSession) {
+ request.session.cleanup();
+ }
+
+ // A superseded request will be complete()'ed by the
+ // superseder at a later time.
+ if (!request.superseded) {
+ request.observer.complete();
+ }
+ },
+ onresponse : r => {
+ this.dispatchResponse(request, r.recv().content());
+ },
+ onerror : errmsg => {
+ const msg = `${request.method} failed! See server logs. ${errmsg}`;
+ console.error(msg);
+ request.observer.error(msg);
+ },
+ onmethoderror : (req, statCode, statMsg) => {
+ const msg =
+ `${request.method} failed! stat=${statCode} msg=${statMsg}`;
+ console.error(msg);
+
+ if (request.service === 'open-ils.pcrud'
+ && Number(statCode) === 401) {
+ // 401 is the PCRUD equivalent of a NO_SESSION event
+ this.authExpired$.emit({request: request});
+ }
+
+ request.observer.error(msg);
+ }
+
+ }).send();
+ }
+
+ // Relay response object to the caller for typical/successful
+ // responses. Applies special handling to response events that
+ // require global attention.
+ private dispatchResponse(request, response): void {
+ request.evt = this.egEvt.parse(response);
+
+ if (request.evt) {
+ switch (request.evt.textcode) {
+
+ case 'NO_SESSION':
+ console.debug(`Net emitting event: ${request.evt}`);
+ request.observer.error(request.evt.toString());
+ this.authExpired$.emit({request: request});
+ return;
+
+ case 'PERM_FAILURE':
+ if (this.permFailedHasHandler) {
+ console.debug(`Net emitting event: ${request.evt}`);
+ request.superseded = true;
+ this.permFailed$.emit(request);
+ return;
+ }
+ }
+ }
+
+ // Pass the response to the caller.
+ request.observer.next(response);
+ }
+}
--- /dev/null
+import {Injectable} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {IdlObject, IdlService} from './idl.service';
+import {NetService} from './net.service';
+import {AuthService} from './auth.service';
+import {PcrudService} from './pcrud.service';
+
+type OrgNodeOrId = number | IdlObject;
+
+interface OrgFilter {
+ canHaveUsers?: boolean;
+ canHaveVolumes?: boolean;
+ opacVisible?: boolean;
+ inList?: number[];
+ notInList?: number[];
+}
+
+interface OrgSettingsBatch {
+ [key: string]: any;
+}
+
+@Injectable({providedIn: 'root'})
+export class OrgService {
+
+ private orgList: IdlObject[] = [];
+ private orgTree: IdlObject; // root node + children
+ private orgMap: {[id: number]: IdlObject} = {};
+ private settingsCache: OrgSettingsBatch = {};
+
+ constructor(
+ private net: NetService,
+ private auth: AuthService,
+ private pcrud: PcrudService
+ ) {}
+
+ get(nodeOrId: OrgNodeOrId): IdlObject {
+ if (typeof nodeOrId === 'object') {
+ return nodeOrId;
+ }
+ return this.orgMap[nodeOrId];
+ }
+
+ list(): IdlObject[] {
+ return this.orgList;
+ }
+
+ /**
+ * Returns a list of org units that match the selected criteria.
+ * All filters must match for an org to be included in the result set.
+ * Unset filter options are ignored.
+ */
+ filterList(filter: OrgFilter, asId?: boolean): any[] {
+ const list = [];
+ this.list().forEach(org => {
+
+ const chu = filter.canHaveUsers;
+ if (chu && !this.canHaveUsers(org)) { return; }
+ if (chu === false && this.canHaveUsers(org)) { return; }
+
+ const chv = filter.canHaveVolumes;
+ if (chv && !this.canHaveVolumes(org)) { return; }
+ if (chv === false && this.canHaveVolumes(org)) { return; }
+
+ const ov = filter.opacVisible;
+ if (ov && !this.opacVisible(org)) { return; }
+ if (ov === false && this.opacVisible(org)) { return; }
+
+ if (filter.inList && !filter.inList.includes(org.id())) {
+ return;
+ }
+
+ if (filter.notInList && filter.notInList.includes(org.id())) {
+ return;
+ }
+
+ // All filter tests passed. Add it to the list
+ list.push(asId ? org.id() : org);
+ });
+
+ return list;
+ }
+
+ tree(): IdlObject {
+ return this.orgTree;
+ }
+
+ // get the root OU
+ root(): IdlObject {
+ return this.orgList[0];
+ }
+
+ // list of org_unit objects or IDs for ancestors + me
+ ancestors(nodeOrId: OrgNodeOrId, asId?: boolean): any[] {
+ let node = this.get(nodeOrId);
+ if (!node) { return []; }
+ const nodes = [node];
+ while ( (node = this.get(node.parent_ou())) ) {
+ nodes.push(node);
+ }
+ if (asId) {
+ return nodes.map(n => 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';
+ }
+
+ opacVisible(nodeOrId): boolean {
+ return this.get(nodeOrId).opac_visible() === 't';
+ }
+
+ // list of org_unit objects or IDs for me + descendants
+ descendants(nodeOrId: OrgNodeOrId, asId?: boolean): any[] {
+ const node = this.get(nodeOrId);
+ if (!node) { return []; }
+ const nodes = [];
+ const descend = (n) => {
+ nodes.push(n);
+ n.children().forEach(descend);
+ };
+ descend(node);
+ if (asId) {
+ return nodes.map(n => n.id());
+ }
+ return nodes;
+ }
+
+ // list of org_unit objects or IDs for ancestors + me + descendants
+ fullPath(nodeOrId: OrgNodeOrId, asId?: boolean): any[] {
+ const list = this.ancestors(nodeOrId, false).concat(
+ this.descendants(nodeOrId, false).slice(1));
+ if (asId) {
+ return list.map(n => n.id());
+ }
+ return list;
+ }
+
+ sortTree(sortField?: string, node?: IdlObject): void {
+ if (!sortField) { sortField = 'shortname'; }
+ if (!node) { node = this.orgTree; }
+ node.children(
+ node.children.sort((a, b) => {
+ return a[sortField]() < b[sortField]() ? -1 : 1;
+ })
+ );
+ node.children.forEach(n => this.sortTree(n));
+ }
+
+ absorbTree(node?: IdlObject): void {
+ if (!node) {
+ node = this.orgTree;
+ this.orgMap = {};
+ this.orgList = [];
+ }
+ this.orgMap[node.id()] = node;
+ this.orgList.push(node);
+ node.children().forEach(c => this.absorbTree(c));
+ }
+
+ /**
+ * Grabs all of the org units from the server, chops them up into
+ * various shapes, then returns an "all done" promise.
+ */
+ fetchOrgs(): Promise<void> {
+ return this.pcrud.search('aou', {parent_ou : null},
+ {flesh : -1, flesh_fields : {aou : ['children', 'ou_type']}},
+ {anonymous : true}
+ ).toPromise().then(tree => {
+ // ingest tree, etc.
+ this.orgTree = tree;
+ this.absorbTree();
+ });
+ }
+
+ /**
+ * Populate 'target' with settings from cache where available.
+ * Return the list of settings /not/ pulled from cache.
+ */
+ private settingsFromCache(names: string[], target: any) {
+ const cacheKeys = Object.keys(this.settingsCache);
+
+ cacheKeys.forEach(key => {
+ const matchIdx = names.indexOf(key);
+ if (matchIdx > -1) {
+ target[key] = this.settingsCache[key];
+ names.splice(matchIdx, 1);
+ }
+ });
+
+ return names;
+ }
+
+ /**
+ * Fetch org settings from the network.
+ * 'auth' is null for anonymous lookup.
+ */
+ private settingsFromNet(orgId: number,
+ names: string[], auth?: string): Promise<any> {
+
+ const settings = {};
+ return new Promise((resolve, reject) => {
+ this.net.request(
+ 'open-ils.actor',
+ 'open-ils.actor.ou_setting.ancestor_default.batch',
+ orgId, names, auth
+ ).subscribe(
+ blob => {
+ Object.keys(blob).forEach(key => {
+ const val = blob[key]; // null or hash
+ settings[key] = val ? val.value : null;
+ });
+ resolve(settings);
+ },
+ err => reject(err)
+ );
+ });
+ }
+
+
+ /**
+ *
+ */
+ settings(names: string[],
+ orgId?: number, anonymous?: boolean): Promise<OrgSettingsBatch> {
+
+ const settings = {};
+ let auth: string = null;
+ let useCache = false;
+
+ if (this.auth.user()) {
+ if (orgId) {
+ useCache = Number(orgId) === Number(this.auth.user().ws_ou());
+ } else {
+ orgId = this.auth.user().ws_ou();
+ useCache = true;
+ }
+
+ // avoid passing auth token when anonymous is requested.
+ if (!anonymous) {
+ auth = this.auth.token();
+ }
+
+ } else if (!anonymous) {
+ return Promise.reject(
+ 'Use "anonymous" To retrieve org settings without an authtoken');
+ }
+
+ if (useCache) {
+ names = this.settingsFromCache(names, settings);
+ }
+
+ // All requested settings found in cache (or name list is empty)
+ if (names.length === 0) {
+ return Promise.resolve(settings);
+ }
+
+ return this.settingsFromNet(orgId, names, auth)
+ .then(sets => {
+ if (useCache) {
+ Object.keys(sets).forEach(key => {
+ this.settingsCache[key] = sets[key];
+ });
+ }
+ return sets;
+ });
+ }
+}
--- /dev/null
+import {IdlService} from './idl.service';
+import {EventService} from './event.service';
+import {NetService} from './net.service';
+import {AuthService} from './auth.service';
+import {PcrudService} from './pcrud.service';
+import {StoreService} from './store.service';
+import {OrgService} from './org.service';
+
+describe('OrgService', () => {
+ let idlService: IdlService;
+ let netService: NetService;
+ let authService: AuthService;
+ let pcrudService: PcrudService;
+ let orgService: OrgService;
+ let evtService: EventService;
+ let storeService: StoreService;
+
+ beforeEach(() => {
+ idlService = new IdlService();
+ evtService = new EventService();
+ storeService = new StoreService(null /* CookieService */);
+ netService = new NetService(evtService);
+ authService = new AuthService(evtService, netService, storeService);
+ pcrudService = new PcrudService(idlService, netService, authService);
+ orgService = new OrgService(netService, authService, pcrudService);
+ });
+
+ const initTestData = () => {
+ idlService.parseIdl();
+ const win: any = window; // trick TS
+ win._eg_mock_data.generateOrgTree(idlService, orgService);
+ };
+
+ it('should provide get by ID', () => {
+ initTestData();
+ expect(orgService.get(orgService.tree().id())).toBe(orgService.root());
+ });
+
+ it('should provide get by node', () => {
+ initTestData();
+ expect(orgService.get(orgService.tree())).toBe(orgService.root());
+ });
+
+ it('should provide ancestors', () => {
+ initTestData();
+ expect(orgService.ancestors(2, true)).toEqual([2, 1]);
+ });
+
+ it('should provide descendants', () => {
+ initTestData();
+ expect(orgService.descendants(2, true)).toEqual([2, 4]);
+ });
+
+ it('should provide full path', () => {
+ initTestData();
+ expect(orgService.fullPath(4, true)).toEqual([4, 2, 1]);
+ });
+
+ it('should provide root', () => {
+ initTestData();
+ expect(orgService.root().id()).toEqual(1);
+ });
+
+});
+
+
--- /dev/null
+import {Injectable} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {Observer} from 'rxjs/Observer';
+import {IdlService, IdlObject} from './idl.service';
+import {NetService, NetRequest} from './net.service';
+import {AuthService} from './auth.service';
+
+// Externally defined. Used here for debugging.
+declare var js2JSON: (jsThing: any) => string;
+declare var OpenSRF: any; // creating sessions
+
+interface PcrudReqOps {
+ authoritative?: boolean;
+ anonymous?: boolean;
+ idlist?: boolean;
+ atomic?: boolean;
+}
+
+// For for documentation purposes.
+type PcrudResponse = any;
+
+export class PcrudContext {
+
+ static verboseLogging = true; //
+ static identGenerator = 0; // for debug logging
+
+ private ident: number;
+ private authoritative: boolean;
+ private xactCloseMode: string;
+ private cudIdx: number;
+ private cudAction: string;
+ private cudLast: PcrudResponse;
+ private cudList: IdlObject[];
+
+ private idl: IdlService;
+ private net: NetService;
+ private auth: AuthService;
+
+ // Tracks nested CUD actions
+ cudObserver: Observer<PcrudResponse>;
+
+ session: any; // OpenSRF.ClientSession
+
+ constructor( // passed in by parent service -- not injected
+ egIdl: IdlService,
+ egNet: NetService,
+ egAuth: AuthService
+ ) {
+ this.idl = egIdl;
+ this.net = egNet;
+ this.auth = egAuth;
+ this.xactCloseMode = 'rollback';
+ this.ident = PcrudContext.identGenerator++;
+ this.session = new OpenSRF.ClientSession('open-ils.pcrud');
+ }
+
+ toString(): string {
+ return '[PCRUDContext ' + this.ident + ']';
+ }
+
+ log(msg: string): void {
+ if (PcrudContext.verboseLogging) {
+ console.debug(this + ': ' + msg);
+ }
+ }
+
+ err(msg: string): void {
+ console.error(this + ': ' + msg);
+ }
+
+ token(reqOps?: PcrudReqOps): string {
+ return (reqOps && reqOps.anonymous) ?
+ 'ANONYMOUS' : this.auth.token();
+ }
+
+ connect(): Promise<PcrudContext> {
+ this.log('connect');
+ return new Promise( (resolve, reject) => {
+ this.session.connect({
+ onconnect : () => { resolve(this); }
+ });
+ });
+ }
+
+ disconnect(): void {
+ this.log('disconnect');
+ this.session.disconnect();
+ }
+
+ retrieve(fmClass: string, pkey: Number | string,
+ pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
+ reqOps = reqOps || {};
+ this.authoritative = reqOps.authoritative || false;
+ return this.dispatch(
+ `open-ils.pcrud.retrieve.${fmClass}`,
+ [this.token(reqOps), pkey, pcrudOps]);
+ }
+
+ retrieveAll(fmClass: string, pcrudOps?: any,
+ reqOps?: PcrudReqOps): Observable<PcrudResponse> {
+ const search = {};
+ search[this.idl.classes[fmClass].pkey] = {'!=' : null};
+ return this.search(fmClass, search, pcrudOps, reqOps);
+ }
+
+ search(fmClass: string, search: any,
+ pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
+ reqOps = reqOps || {};
+ this.authoritative = reqOps.authoritative || false;
+
+ const returnType = reqOps.idlist ? 'id_list' : 'search';
+ let method = `open-ils.pcrud.${returnType}.${fmClass}`;
+
+ if (reqOps.atomic) { method += '.atomic'; }
+
+ return this.dispatch(method, [this.token(reqOps), search, pcrudOps]);
+ }
+
+ create(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
+ return this.cud('create', list);
+ }
+ update(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
+ return this.cud('update', list);
+ }
+ remove(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
+ return this.cud('delete', list);
+ }
+ autoApply(list: IdlObject | IdlObject[]): Observable<PcrudResponse> { // RENAMED
+ return this.cud('auto', list);
+ }
+
+ xactClose(): Observable<PcrudResponse> {
+ return this.sendRequest(
+ 'open-ils.pcrud.transaction.' + this.xactCloseMode,
+ [this.token()]
+ );
+ }
+
+ xactBegin(): Observable<PcrudResponse> {
+ return this.sendRequest(
+ 'open-ils.pcrud.transaction.begin', [this.token()]
+ );
+ }
+
+ private dispatch(method: string, params: any[]): Observable<PcrudResponse> {
+ if (this.authoritative) {
+ return this.wrapXact(() => {
+ return this.sendRequest(method, params);
+ });
+ } else {
+ return this.sendRequest(method, params);
+ }
+ }
+
+
+ // => connect
+ // => xact_begin
+ // => action
+ // => xact_close(commit/rollback)
+ // => disconnect
+ wrapXact(mainFunc: () => Observable<PcrudResponse>): Observable<PcrudResponse> {
+ return Observable.create(observer => {
+
+ // 1. connect
+ this.connect()
+
+ // 2. start the transaction
+ .then(() => this.xactBegin().toPromise())
+
+ // 3. execute the main body
+ .then(() => {
+
+ mainFunc().subscribe(
+ res => observer.next(res),
+ err => observer.error(err),
+ () => {
+ this.xactClose().toPromise().then(() => {
+ // 5. disconnect
+ this.disconnect();
+ // 6. all done
+ observer.complete();
+ });
+ }
+ );
+ });
+ });
+ }
+
+ private sendRequest(method: string,
+ params: any[]): Observable<PcrudResponse> {
+
+ // this.log(`sendRequest(${method})`);
+
+ return this.net.requestCompiled(
+ new NetRequest(
+ 'open-ils.pcrud', method, params, this.session)
+ );
+ }
+
+ private cud(action: string,
+ list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
+ this.cudList = [].concat(list); // value or array
+
+ this.log(`CUD(): ${action}`);
+
+ this.cudIdx = 0;
+ this.cudAction = action;
+ this.xactCloseMode = 'commit';
+
+ return this.wrapXact(() => {
+ return Observable.create(observer => {
+ this.cudObserver = observer;
+ this.nextCudRequest();
+ });
+ });
+ }
+
+ /**
+ * Loops through the list of objects to update and sends
+ * them one at a time to the server for processing. Once
+ * all are done, the cudObserver is resolved.
+ */
+ nextCudRequest(): void {
+ if (this.cudIdx >= this.cudList.length) {
+ this.cudObserver.complete();
+ return;
+ }
+
+ let action = this.cudAction;
+ const fmObj = this.cudList[this.cudIdx++];
+
+ if (action === 'auto') {
+ if (fmObj.ischanged()) { action = 'update'; }
+ if (fmObj.isnew()) { action = 'create'; }
+ if (fmObj.isdeleted()) { action = 'delete'; }
+
+ if (action === 'auto') {
+ // object does not need updating; move along
+ this.nextCudRequest();
+ }
+ }
+
+ this.sendRequest(
+ `open-ils.pcrud.${action}.${fmObj.classname}`,
+ [this.token(), fmObj]
+ ).subscribe(
+ res => this.cudObserver.next(res),
+ err => this.cudObserver.error(err),
+ () => this.nextCudRequest()
+ );
+ }
+}
+
+@Injectable({providedIn: 'root'})
+export class PcrudService {
+
+ constructor(
+ private idl: IdlService,
+ private net: NetService,
+ private auth: AuthService
+ ) {}
+
+ // Pass-thru functions for one-off PCRUD calls
+
+ connect(): Promise<PcrudContext> {
+ return this.newContext().connect();
+ }
+
+ newContext(): PcrudContext {
+ return new PcrudContext(this.idl, this.net, this.auth);
+ }
+
+ retrieve(fmClass: string, pkey: Number | string,
+ pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
+ return this.newContext().retrieve(fmClass, pkey, pcrudOps, reqOps);
+ }
+
+ retrieveAll(fmClass: string, pcrudOps?: any,
+ reqOps?: PcrudReqOps): Observable<PcrudResponse> {
+ return this.newContext().retrieveAll(fmClass, pcrudOps, reqOps);
+ }
+
+ search(fmClass: string, search: any,
+ pcrudOps?: any, reqOps?: PcrudReqOps): Observable<PcrudResponse> {
+ return this.newContext().search(fmClass, search, pcrudOps, reqOps);
+ }
+
+ create(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
+ return this.newContext().create(list);
+ }
+
+ update(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
+ return this.newContext().update(list);
+ }
+
+ remove(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
+ return this.newContext().remove(list);
+ }
+
+ autoApply(list: IdlObject | IdlObject[]): Observable<PcrudResponse> {
+ return this.newContext().autoApply(list);
+ }
+}
+
+
--- /dev/null
+import {Injectable} from '@angular/core';
+import {NetService} from './net.service';
+import {OrgService} from './org.service';
+import {AuthService} from './auth.service';
+
+interface HasPermAtResult {
+ [permName: string]: any[]; // org IDs or org unit objects
+}
+
+interface HasPermHereResult {
+ [permName: string]: boolean;
+}
+
+@Injectable({providedIn: 'root'})
+export class PermService {
+
+ constructor(
+ private net: NetService,
+ private org: OrgService,
+ private auth: AuthService,
+ ) {}
+
+ // workstation not required.
+ hasWorkPermAt(permNames: string[], asId?: boolean): Promise<HasPermAtResult> {
+ return this.net.request(
+ 'open-ils.actor',
+ 'open-ils.actor.user.has_work_perm_at.batch',
+ this.auth.token(), permNames
+ ).toPromise().then(resp => {
+ const answer: HasPermAtResult = {};
+ permNames.forEach(perm => {
+ let orgs = [];
+ resp[perm].forEach(oneOrg => {
+ orgs = orgs.concat(this.org.descendants(oneOrg, asId));
+ });
+ answer[perm] = orgs;
+ });
+
+ return answer;
+ });
+ }
+
+ // workstation required
+ hasWorkPermHere(permNames: string[]): Promise<HasPermHereResult> {
+ const wsId: number = +this.auth.user().wsid();
+
+ if (!wsId) {
+ return Promise.reject('hasWorkPermHere requires a workstation');
+ }
+
+ return this.hasWorkPermAt(permNames, true).then(resp => {
+ const answer: HasPermHereResult = {};
+ Object.keys(resp).forEach(perm => {
+ answer[perm] = resp[perm].indexOf(wsId) > -1;
+ });
+ return answer;
+ });
+ }
+}
--- /dev/null
+/**
+ * Set and get server-stored settings.
+ */
+import {Injectable} from '@angular/core';
+import {AuthService} from './auth.service';
+import {NetService} from './net.service';
+
+// Settings summary objects returned by the API
+interface ServerSettingSummary {
+ name: string;
+ value: string;
+ has_org_setting: boolean;
+ has_user_setting: boolean;
+ has_workstation_setting: boolean;
+}
+
+@Injectable({providedIn: 'root'})
+export class ServerStoreService {
+
+ cache: {[key: string]: ServerSettingSummary};
+
+ constructor(
+ private net: NetService,
+ private auth: AuthService) {
+ this.cache = {};
+ }
+
+ setItem(key: string, value: any): Promise<any> {
+
+ if (!this.auth.token()) {
+ return Promise.reject('Auth required to apply settings');
+ }
+
+ const setting: any = {};
+ setting[key] = value;
+
+ return this.net.request(
+ 'open-ils.actor',
+ 'open-ils.actor.settings.apply.user_or_ws',
+ this.auth.token(), setting)
+
+ .toPromise().then(appliedCount => {
+
+ if (Number(appliedCount) > 0) { // value applied
+ return this.cache[key] = value;
+ }
+
+ return Promise.reject(
+ `No user or workstation setting type exists for: "${key}".\n` +
+ 'Create a ws/user setting type or use setLocalItem() to ' +
+ 'store the value locally.'
+ );
+ });
+ }
+
+ // Returns a single setting value
+ getItem(key: string): Promise<any> {
+ return this.getItemBatch([key]).then(
+ settings => settings[key]
+ );
+ }
+
+ // Returns a set of key/value pairs for the requested settings
+ getItemBatch(keys: string[]): Promise<any> {
+
+ const values: any = {};
+ keys.forEach(key => {
+ if (this.cache[key]) {
+ values[key] = this.cache[key];
+ }
+ });
+
+ if (keys.length === Object.keys(values).length) {
+ // All values are cached already
+ return Promise.resolve(values);
+ }
+
+ if (!this.auth.token()) {
+ // Authtokens require for fetching server settings, but
+ // calls to retrieve settings could potentially occur
+ // before auth completes -- Ideally not, but just to be safe.
+ return Promise.resolve({});
+ }
+
+ // Server call required. Limit the settings to lookup to those
+ // we don't already have cached.
+ const serverKeys = [];
+ keys.forEach(key => {
+ if (!Object.keys(values).includes(key)) {
+ serverKeys.push(key);
+ }
+ });
+
+ return new Promise((resolve, reject) => {
+ this.net.request(
+ 'open-ils.actor',
+ 'open-ils.actor.settings.retrieve',
+ serverKeys, this.auth.token()
+ ).subscribe(
+ summary => {
+ this.cache[summary.name] =
+ values[summary.name] = summary.value;
+ },
+ err => reject,
+ () => resolve(values)
+ );
+ });
+ }
+
+ removeItem(key: string): Promise<any> {
+ return this.setItem(key, null);
+ }
+}
+
--- /dev/null
+/**
+ * Store and retrieve data from various sources.
+ *
+ * Data Types:
+ * 1. LocalItem: Stored in window.localStorage and persist indefinitely.
+ * 2. SessionItem: Stored in window.sessionStorage and persist until
+ * the end of the current browser tab/window. Data is only available
+ * to the tab/window where the data was set.
+ * 3. LoginItem: Stored as session cookies and persist until the browser
+ * is closed. These values are avalable to all browser windows/tabs.
+ */
+import {Injectable} from '@angular/core';
+import {CookieService} from 'ngx-cookie';
+
+@Injectable({providedIn: 'root'})
+export class StoreService {
+
+ // Base path for cookie-based storage.
+ // Useful for limiting cookies to subsections of the application.
+ // Store cookies globally by default.
+ // Note cookies shared with /eg/staff must be stored at "/"
+ loginSessionBasePath = '/';
+
+ // Set of keys whose values should disappear at logout.
+ loginSessionKeys: string[] = [
+ 'eg.auth.token',
+ 'eg.auth.time',
+ 'eg.auth.token.oc',
+ 'eg.auth.time.oc'
+ ];
+
+ constructor(
+ private cookieService: CookieService) {
+ }
+
+ private parseJson(valJson: string): any {
+ if (valJson === undefined || valJson === null || valJson === '') {
+ return null;
+ }
+ try {
+ return JSON.parse(valJson);
+ } catch (E) {
+ console.error(`Failure to parse JSON: ${E} => ${valJson}`);
+ return null;
+ }
+ }
+
+ /**
+ * Add a an app-local login session key
+ */
+ addLoginSessionKey(key: string): void {
+ this.loginSessionKeys.push(key);
+ }
+
+ setLocalItem(key: string, val: any, isJson?: boolean): void {
+ if (!isJson) {
+ val = JSON.stringify(val);
+ }
+ window.localStorage.setItem(key, val);
+ }
+
+ setSessionItem(key: string, val: any, isJson?: boolean): void {
+ if (!isJson) {
+ val = JSON.stringify(val);
+ }
+ window.sessionStorage.setItem(key, val);
+ }
+
+ setLoginSessionItem(key: string, val: any, isJson?: boolean): void {
+ if (!isJson) {
+ val = JSON.stringify(val);
+ }
+ this.cookieService.put(key, val,
+ {path : this.loginSessionBasePath, secure: true});
+ }
+
+ getLocalItem(key: string): any {
+ return this.parseJson(window.localStorage.getItem(key));
+ }
+
+ getSessionItem(key: string): any {
+ return this.parseJson(window.sessionStorage.getItem(key));
+ }
+
+ getLoginSessionItem(key: string): any {
+ return this.parseJson(this.cookieService.get(key));
+ }
+
+ removeLocalItem(key: string): void {
+ window.localStorage.removeItem(key);
+ }
+
+ removeSessionItem(key: string): void {
+ window.sessionStorage.removeItem(key);
+ }
+
+ removeLoginSessionItem(key: string): void {
+ this.cookieService.remove(key, {path : this.loginSessionBasePath});
+ }
+
+ clearLoginSessionItems(): void {
+ this.loginSessionKeys.forEach(
+ key => this.removeLoginSessionItem(key)
+ );
+ }
+}
+
--- /dev/null
+import {StoreService} from './store.service';
+
+describe('StoreService', () => {
+ let service: StoreService;
+ beforeEach(() => {
+ service = new StoreService(null /* CookieService */);
+ });
+
+ it('should set/get a localStorage value', () => {
+ const str = 'hello, world';
+ service.setLocalItem('testKey', str);
+ expect(service.getLocalItem('testKey')).toBe(str);
+ });
+
+ it('should set/get a sessionStorage value', () => {
+ const str = 'hello, world again';
+ service.setLocalItem('testKey', str);
+ expect(service.getLocalItem('testKey')).toBe(str);
+ });
+
+});
+
--- /dev/null
+import {Injectable} from '@angular/core';
+import {Router, Resolve, RouterStateSnapshot,
+ ActivatedRouteSnapshot} from '@angular/router';
+import {IdlService} from '@eg/core/idl.service';
+import {OrgService} from '@eg/core/org.service';
+import {LocaleService} from '@eg/core/locale.service';
+
+// For locale application
+declare var OpenSRF;
+
+@Injectable()
+export class BaseResolver implements Resolve<Promise<void>> {
+
+ constructor(
+ private router: Router,
+ private idl: IdlService,
+ private org: OrgService,
+ private locale: LocaleService
+ ) {}
+
+ /**
+ * Loads pre-auth data common to all applications.
+ * No auth token is available at this level. When needed, auth is
+ * enforced by application/group-specific resolvers at lower levels.
+ */
+ resolve(
+ route: ActivatedRouteSnapshot,
+ state: RouterStateSnapshot): Promise<void> {
+
+ OpenSRF.locale = this.locale.currentLocaleCode();
+
+ this.idl.parseIdl();
+
+ return this.org.fetchOrgs(); // anonymous PCRUD.
+ }
+}
--- /dev/null
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {BaseResolver} from './resolver.service';
+import {WelcomeComponent} from './welcome.component';
+
+/**
+ * Avoid loading all application JS up front by lazy-loading sub-modules.
+ * When lazy loading, no module references should be directly imported.
+ * The refs are encoded in the loadChildren attribute of each route.
+ * These modules are encoded as separate JS chunks that are fetched
+ * from the server only when needed.
+ */
+const routes: Routes = [
+ { path: '',
+ component: WelcomeComponent
+ }, {
+ path: 'staff',
+ resolve : {startup : BaseResolver},
+ loadChildren: './staff/staff.module#StaffModule'
+ }
+];
+
+@NgModule({
+ imports: [RouterModule.forRoot(routes)],
+ exports: [RouterModule],
+ providers: [BaseResolver]
+})
+
+export class BaseRoutingModule {}
--- /dev/null
+Shared Angular services, components, directives, and associated classes.
+
+These items are NOT automatically imported to the base module, though some
+may already be imported by intermediate modules (e.g. StaffCommonModule).
+Import as needed.
+
--- /dev/null
+<ng-template #dialogContent>
+ <div class="modal-header bg-info">
+ <h4 class="modal-title" i18n>Access Key Assignments</h4>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close"
+ (click)="dismiss('cross_click')">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <div class="row border-bottom">
+ <div class="col-lg-3 p-1 border-right text-center" i18n>Command</div>
+ <div class="col-lg-6 p-1 border-right" i18n>Action</div>
+ <div class="col-lg-3 p-1" i18n>Context</div>
+ </div>
+ <div class="row border-bottom" *ngFor="let a of assignments()">
+ <div class="col-lg-3 p-1 border-right text-center">{{a.key}}</div>
+ <div class="col-lg-6 p-1 border-right">{{a.desc}}</div>
+ <div class="col-lg-3 p-1">{{a.ctx}}</div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-success"
+ (click)="close()" i18n>Close</button>
+ </div>
+</ng-template>
--- /dev/null
+/**
+ */
+import {Component, Input, OnInit} from '@angular/core';
+import {AccessKeyService} from '@eg/share/accesskey/accesskey.service';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+
+@Component({
+ selector: 'eg-accesskey-info',
+ templateUrl: './accesskey-info.component.html'
+})
+export class AccessKeyInfoComponent extends DialogComponent {
+
+ constructor(
+ private modal: NgbModal, // required for passing to parent
+ private keyService: AccessKeyService) {
+ super(modal);
+ }
+
+ assignments(): any[] {
+ return this.keyService.infoIze();
+ }
+}
+
+
--- /dev/null
+/**
+ * Assign access keys to <a> tags.
+ *
+ * Access key action is peformed via .click(). hrefs, routerLinks,
+ * and (click) actions are all supported.
+ *
+ * <a
+ * routerLink="/staff/splash"
+ * egAccessKey
+ * keySpec="alt+h" i18n-keySpec
+ * keyDesc="My Description" 18n-keyDesc
+ * >
+ */
+import {Directive, ElementRef, Input, OnInit} from '@angular/core';
+import {AccessKeyService} from '@eg/share/accesskey/accesskey.service';
+
+@Directive({
+ selector: '[egAccessKey]'
+})
+export class AccessKeyDirective implements OnInit {
+
+ // Space-separated list of key combinations
+ // E.g. "ctrl+h", "alt+h ctrl+y"
+ @Input() keySpec: string;
+
+ // Description to display in the accesskey info dialog
+ @Input() keyDesc: string;
+
+ // Context info to display in the accesskey info dialog
+ // E.g. "navbar"
+ @Input() keyCtx: string;
+
+ constructor(
+ private elm: ElementRef,
+ private keyService: AccessKeyService
+ ) { }
+
+ ngOnInit() {
+
+ if (!this.keySpec) {
+ console.warn('AccessKey no keySpec provided');
+ return;
+ }
+
+ this.keySpec.split(/ /).forEach(keySpec => {
+ this.keyService.assign({
+ key: keySpec,
+ desc: this.keyDesc,
+ ctx: this.keyCtx,
+ action: () => this.elm.nativeElement.click()
+ });
+ });
+ }
+}
+
+
--- /dev/null
+import {Injectable, EventEmitter, HostListener} from '@angular/core';
+
+export interface AccessKeyAssignment {
+ key: string; // keyboard command
+ desc: string; // human-friendly description
+ ctx: string; // template context
+ action: Function; // handler function
+}
+
+@Injectable()
+export class AccessKeyService {
+
+ // Assignments stored as an array with most recently assigned
+ // items toward the front. Most recent items have precedence.
+ assignments: AccessKeyAssignment[] = [];
+
+ constructor() {}
+
+ assign(assn: AccessKeyAssignment): void {
+ this.assignments.unshift(assn);
+ }
+
+ /**
+ * Compress a set of single-fire keyboard events into single
+ * string. For example: Control and 't' becomes 'ctrl+t'.
+ */
+ compressKeys(evt: KeyboardEvent): string {
+
+ let s = '';
+ if (evt.ctrlKey || evt.metaKey) { s += 'ctrl+'; }
+ if (evt.altKey) { s += 'alt+'; }
+ s += evt.key.toLowerCase();
+
+ return s;
+ }
+
+ /**
+ * Checks for a key assignment and fires the assigned action.
+ */
+ fire(evt: KeyboardEvent): void {
+ const keySpec = this.compressKeys(evt);
+ for (const i in this.assignments) { // for-loop to exit early
+ if (keySpec === this.assignments[i].key) {
+ const assign = this.assignments[i];
+ console.debug(`AccessKey assignment found for ${assign.key}`);
+ // Allow the current digest cycle to complete before
+ // firing the access key action.
+ setTimeout(assign.action, 0);
+ evt.preventDefault();
+ return;
+ }
+ }
+ }
+
+ /**
+ * Returns a simplified key assignment list containing just
+ * the key spec and the description. Useful for inspecting
+ * without exposing the actions.
+ */
+ infoIze(): any[] {
+ return this.assignments.map(a => {
+ return {key: a.key, desc: a.desc, ctx: a.ctx};
+ });
+ }
+
+}
+
--- /dev/null
+import {Injectable} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {mergeMap} from 'rxjs/operators/mergeMap';
+import {from} from 'rxjs/observable/from';
+import {map} from 'rxjs/operators/map';
+import {OrgService} from '@eg/core/org.service';
+import {UnapiService} from '@eg/share/catalog/unapi.service';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {NetService} from '@eg/core/net.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+
+export const NAMESPACE_MAPS = {
+ 'mods': 'http://www.loc.gov/mods/v3',
+ 'biblio': 'http://open-ils.org/spec/biblio/v1',
+ 'holdings': 'http://open-ils.org/spec/holdings/v1',
+ 'indexing': 'http://open-ils.org/spec/indexing/v1'
+};
+
+export const HOLDINGS_XPATH =
+ '/holdings:holdings/holdings:counts/holdings:count';
+
+
+export class BibRecordSummary {
+ id: number; // == record.id() for convenience
+ orgId: number;
+ orgDepth: number;
+ record: IdlObject;
+ display: any;
+ attributes: any;
+ holdingsSummary: any;
+ holdCount: number;
+ bibCallNumber: string;
+ net: NetService;
+
+ constructor(record: IdlObject, orgId: number, orgDepth: number) {
+ this.id = record.id();
+ this.record = record;
+ this.orgId = orgId;
+ this.orgDepth = orgDepth;
+ this.display = {};
+ this.attributes = {};
+ this.bibCallNumber = null;
+ }
+
+ ingest() {
+ this.compileDisplayFields();
+ this.compileRecordAttrs();
+
+ // Normalize some data for JS consistency
+ this.record.creator(Number(this.record.creator()));
+ this.record.editor(Number(this.record.editor()));
+ }
+
+ compileDisplayFields() {
+ this.record.flat_display_entries().forEach(entry => {
+ if (entry.multi() === 't') {
+ if (this.display[entry.name()]) {
+ this.display[entry.name()].push(entry.value());
+ } else {
+ this.display[entry.name()] = [entry.value()];
+ }
+ } else {
+ this.display[entry.name()] = entry.value();
+ }
+ });
+ }
+
+ compileRecordAttrs() {
+ // Any attr can be multi-valued.
+ this.record.mattrs().forEach(attr => {
+ if (this.attributes[attr.attr()]) {
+ this.attributes[attr.attr()].push(attr.value());
+ } else {
+ this.attributes[attr.attr()] = [attr.value()];
+ }
+ });
+ }
+
+ // Get -> Set -> Return bib hold count
+ getHoldCount(): Promise<number> {
+
+ if (Number.isInteger(this.holdCount)) {
+ return Promise.resolve(this.holdCount);
+ }
+
+ return this.net.request(
+ 'open-ils.circ',
+ 'open-ils.circ.bre.holds.count', this.id
+ ).toPromise().then(count => this.holdCount = count);
+ }
+
+ // Get -> Set -> Return bib-level call number
+ getBibCallNumber(): Promise<string> {
+
+ if (this.bibCallNumber !== null) {
+ return Promise.resolve(this.bibCallNumber);
+ }
+
+ // TODO labelClass = cat.default_classification_scheme YAOUS
+ const labelClass = 1;
+
+ return this.net.request(
+ 'open-ils.cat',
+ 'open-ils.cat.biblio.record.marc_cn.retrieve',
+ this.id, labelClass
+ ).toPromise().then(cnArray => {
+ if (cnArray && cnArray.length > 0) {
+ const key1 = Object.keys(cnArray[0])[0];
+ this.bibCallNumber = cnArray[0][key1];
+ } else {
+ this.bibCallNumber = '';
+ }
+ return this.bibCallNumber;
+ });
+ }
+}
+
+@Injectable()
+export class BibRecordService {
+
+ // Cache of bib editor / creator objects
+ // Assumption is this list will be limited in size.
+ userCache: {[id: number]: IdlObject};
+
+ constructor(
+ private idl: IdlService,
+ private net: NetService,
+ private org: OrgService,
+ private unapi: UnapiService,
+ private pcrud: PcrudService
+ ) {
+ this.userCache = {};
+ }
+
+ // Avoid fetching the MARC blob by specifying which fields on the
+ // bre to select. Note that fleshed fields are explicitly selected.
+ fetchableBreFields(): string[] {
+ return this.idl.classes.bre.fields
+ .filter(f => !f.virtual && f.name !== 'marc')
+ .map(f => f.name);
+ }
+
+ // Note when multiple IDs are provided, responses are emitted in order
+ // of receipt, not necessarily in the requested ID order.
+ getBibSummary(bibIds: number | number[],
+ orgId?: number, orgDepth?: number): Observable<BibRecordSummary> {
+
+ const ids = [].concat(bibIds);
+
+ if (ids.length === 0) {
+ return from([]);
+ }
+
+ return this.pcrud.search('bre', {id: ids},
+ { flesh: 1,
+ flesh_fields: {bre: ['flat_display_entries', 'mattrs']},
+ select: {bre : this.fetchableBreFields()}
+ },
+ {anonymous: true} // skip unneccesary auth
+ ).pipe(mergeMap(bib => {
+ const summary = new BibRecordSummary(bib, orgId, orgDepth);
+ summary.net = this.net; // inject
+ summary.ingest();
+ return this.getHoldingsSummary(bib.id(), orgId, orgDepth)
+ .then(holdingsSummary => {
+ summary.holdingsSummary = holdingsSummary;
+ return summary;
+ });
+ }));
+ }
+
+ // Flesh the creator and editor fields.
+ // Handling this separately lets us pull from the cache and
+ // avoids the requirement that the main bib query use a staff
+ // (VIEW_USER) auth token.
+ fleshBibUsers(records: IdlObject[]): Promise<void> {
+
+ const search = [];
+
+ records.forEach(rec => {
+ ['creator', 'editor'].forEach(field => {
+ const id = rec[field]();
+ if (Number.isInteger(id)) {
+ if (this.userCache[id]) {
+ rec[field](this.userCache[id]);
+ } else if (!search.includes(id)) {
+ search.push(id);
+ }
+ }
+ });
+ });
+
+ if (search.length === 0) {
+ return Promise.resolve();
+ }
+
+ return this.pcrud.search('au', {id: search})
+ .pipe(map(user => {
+ this.userCache[user.id()] = user;
+ records.forEach(rec => {
+ if (user.id() === rec.creator()) {
+ rec.creator(user);
+ }
+ if (user.id() === rec.editor()) {
+ rec.editor(user);
+ }
+ });
+ })).toPromise();
+ }
+
+ getHoldingsSummary(recordId: number,
+ orgId: number, orgDepth: number): Promise<any> {
+
+ const holdingsSummary = [];
+
+ return this.unapi.getAsXmlDocument({
+ target: 'bre',
+ id: recordId,
+ extras: '{holdings_xml}',
+ format: 'holdings_xml',
+ orgId: orgId,
+ depth: orgDepth
+ }).then(xmlDoc => {
+
+ // namespace resolver
+ const resolver: any = (prefix: string): string => {
+ return NAMESPACE_MAPS[prefix] || null;
+ };
+
+ // Extract the holdings data from the unapi xml doc
+ const result = xmlDoc.evaluate(HOLDINGS_XPATH,
+ xmlDoc, resolver, XPathResult.ANY_TYPE, null);
+
+ let node;
+ while (node = result.iterateNext()) {
+ const counts = {type : node.getAttribute('type')};
+ ['depth', 'org_unit', 'transcendant',
+ 'available', 'count', 'unshadow'].forEach(field => {
+ counts[field] = Number(node.getAttribute(field));
+ });
+ holdingsSummary.push(counts);
+ }
+
+ return holdingsSummary;
+ });
+ }
+}
+
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {EgCommonModule} from '@eg/common.module';
+import {CatalogService} from './catalog.service';
+import {CatalogUrlService} from './catalog-url.service';
+import {BibRecordService} from './bib-record.service';
+import {UnapiService} from './unapi.service';
+import {MarcHtmlComponent} from './marc-html.component';
+
+
+@NgModule({
+ declarations: [
+ MarcHtmlComponent
+ ],
+ imports: [
+ EgCommonModule
+ ],
+ exports: [
+ MarcHtmlComponent
+ ],
+ providers: [
+ CatalogService,
+ CatalogUrlService,
+ UnapiService,
+ BibRecordService
+ ]
+})
+
+export class CatalogCommonModule {}
--- /dev/null
+import {Injectable} from '@angular/core';
+import {ParamMap} from '@angular/router';
+import {OrgService} from '@eg/core/org.service';
+import {CatalogSearchContext, FacetFilter} from './search-context';
+import {CATALOG_CCVM_FILTERS} from './catalog.service';
+
+@Injectable()
+export class CatalogUrlService {
+
+ // consider supporting a param name prefix/namespace
+
+ constructor(private org: OrgService) { }
+
+ /**
+ * Returns a URL query structure suitable for using with
+ * router.navigate(..., {queryParams:...}).
+ * No navigation is performed within.
+ */
+ toUrlParams(context: CatalogSearchContext):
+ {[key: string]: string | string[]} {
+
+ const params = {
+ query: [],
+ fieldClass: [],
+ joinOp: [],
+ matchOp: [],
+ facets: [],
+ identQuery: null,
+ identQueryType: null,
+ org: null,
+ limit: null,
+ offset: null
+ };
+
+ params.org = context.searchOrg.id();
+
+ params.limit = context.pager.limit;
+ if (context.pager.offset) {
+ params.offset = context.pager.offset;
+ }
+
+ // These fields can be copied directly into place
+ ['format', 'sort', 'available', 'global', 'identQuery', 'identQueryType']
+ .forEach(field => {
+ if (context[field]) {
+ // Only propagate applied values to the URL.
+ params[field] = context[field];
+ }
+ });
+
+ if (params.identQuery) {
+ // Ident queries (e.g. tcn search) discards all remaining filters
+ return params;
+ }
+
+ context.query.forEach((q, idx) => {
+ ['query', 'fieldClass', 'joinOp', 'matchOp'].forEach(field => {
+ // Propagate all array-based fields regardless of
+ // whether a value is applied to ensure correct
+ // correlation between values.
+ params[field][idx] = context[field][idx];
+ });
+ });
+
+ // CCVM filters are encoded as comma-separated lists
+ Object.keys(context.ccvmFilters).forEach(code => {
+ if (context.ccvmFilters[code] &&
+ context.ccvmFilters[code][0] !== '') {
+ params[code] = context.ccvmFilters[code].join(',');
+ }
+ });
+
+ // Each facet is a JSON encoded blob of class, name, and value
+ context.facetFilters.forEach(facet => {
+ params.facets.push(JSON.stringify({
+ c : facet.facetClass,
+ n : facet.facetName,
+ v : facet.facetValue
+ }));
+ });
+
+ return params;
+ }
+
+ /**
+ * Creates a new search context from the active route params.
+ */
+ fromUrlParams(params: ParamMap): CatalogSearchContext {
+ const context = new CatalogSearchContext();
+
+ this.applyUrlParams(context, params);
+
+ return context;
+ }
+
+ applyUrlParams(context: CatalogSearchContext, params: ParamMap): void {
+
+ // Reset query/filter args. The will be reconstructed below.
+ context.reset();
+
+ // These fields can be copied directly into place
+ ['format', 'sort', 'available', 'global', 'identQuery', 'identQueryType']
+ .forEach(field => {
+ const val = params.get(field);
+ if (val !== null) {
+ context[field] = val;
+ }
+ });
+
+ if (params.get('limit')) {
+ context.pager.limit = +params.get('limit');
+ }
+
+ if (params.get('offset')) {
+ context.pager.offset = +params.get('offset');
+ }
+
+ ['query', 'fieldClass', 'joinOp', 'matchOp'].forEach(field => {
+ const arr = params.getAll(field);
+ if (arr && arr.length) {
+ context[field] = arr;
+ }
+ });
+
+ CATALOG_CCVM_FILTERS.forEach(code => {
+ const val = params.get(code);
+ if (val) {
+ context.ccvmFilters[code] = val.split(/,/);
+ } else {
+ context.ccvmFilters[code] = [''];
+ }
+ });
+
+ params.getAll('facets').forEach(blob => {
+ const facet = JSON.parse(blob);
+ context.addFacet(new FacetFilter(facet.c, facet.n, facet.v));
+ });
+
+ if (params.get('org')) {
+ context.searchOrg = this.org.get(+params.get('org'));
+ }
+ }
+}
--- /dev/null
+import {Injectable} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {mergeMap} from 'rxjs/operators/mergeMap';
+import {map} from 'rxjs/operators/map';
+import {OrgService} from '@eg/core/org.service';
+import {UnapiService} from '@eg/share/catalog/unapi.service';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {NetService} from '@eg/core/net.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {CatalogSearchContext, CatalogSearchState} from './search-context';
+import {BibRecordService, BibRecordSummary} from './bib-record.service';
+
+// CCVM's we care about in a catalog context
+// Don't fetch them all because there are a lot.
+export const CATALOG_CCVM_FILTERS = [
+ 'item_type',
+ 'item_form',
+ 'item_lang',
+ 'audience',
+ 'audience_group',
+ 'vr_format',
+ 'bib_level',
+ 'lit_form',
+ 'search_format',
+ 'icon_format'
+];
+
+@Injectable()
+export class CatalogService {
+
+ ccvmMap: {[ccvm: string]: IdlObject[]} = {};
+ cmfMap: {[cmf: string]: IdlObject} = {};
+
+ // Keep a reference to the most recently retrieved facet data,
+ // since facet data is consistent across a given search.
+ // No need to re-fetch with every page of search data.
+ lastFacetData: any;
+ lastFacetKey: string;
+
+ constructor(
+ private idl: IdlService,
+ private net: NetService,
+ private org: OrgService,
+ private unapi: UnapiService,
+ private pcrud: PcrudService,
+ private bibService: BibRecordService
+ ) {}
+
+ search(ctx: CatalogSearchContext): Promise<void> {
+ ctx.searchState = CatalogSearchState.SEARCHING;
+
+ const fullQuery = ctx.compileSearch();
+
+ console.debug(`search query: ${fullQuery}`);
+
+ let method = 'open-ils.search.biblio.multiclass.query';
+ if (ctx.isStaff) {
+ method += '.staff';
+ }
+
+ return new Promise((resolve, reject) => {
+ this.net.request(
+ 'open-ils.search', method, {
+ limit : ctx.pager.limit + 1,
+ offset : ctx.pager.offset
+ }, fullQuery, true
+ ).subscribe(result => {
+ this.applyResultData(ctx, result);
+ ctx.searchState = CatalogSearchState.COMPLETE;
+ resolve();
+ });
+ });
+ }
+
+ applyResultData(ctx: CatalogSearchContext, result: any): void {
+ ctx.result = result;
+ ctx.pager.resultCount = result.count;
+
+ // records[] tracks the current page of bib summaries.
+ result.records = [];
+
+ // If this is a new search, reset the result IDs collection.
+ if (this.lastFacetKey !== result.facet_key) {
+ ctx.resultIds = [];
+ }
+
+ result.ids.forEach((blob, idx) => ctx.addResultId(blob[0], idx));
+ }
+
+ // Appends records to the search result set as they arrive.
+ // Returns a void promise once all records have been retrieved
+ fetchBibSummaries(ctx: CatalogSearchContext): Promise<void> {
+
+ const depth = ctx.global ?
+ ctx.org.root().ou_type().depth() :
+ ctx.searchOrg.ou_type().depth();
+
+ return this.bibService.getBibSummary(
+ ctx.currentResultIds(), ctx.searchOrg.id(), depth)
+ .pipe(map(summary => {
+ // Responses are not necessarily returned in request-ID order.
+ const idx = ctx.currentResultIds().indexOf(summary.record.id());
+ if (ctx.result.records) {
+ // May be reset when quickly navigating results.
+ ctx.result.records[idx] = summary;
+ }
+ })).toPromise();
+ }
+
+ fetchFacets(ctx: CatalogSearchContext): Promise<void> {
+
+ if (!ctx.result) {
+ return Promise.reject('Cannot fetch facets without results');
+ }
+
+ if (this.lastFacetKey === ctx.result.facet_key) {
+ ctx.result.facetData = this.lastFacetData;
+ return Promise.resolve();
+ }
+
+ return new Promise((resolve, reject) => {
+ this.net.request('open-ils.search',
+ 'open-ils.search.facet_cache.retrieve',
+ ctx.result.facet_key
+ ).subscribe(facets => {
+ const facetData = {};
+ Object.keys(facets).forEach(cmfId => {
+ const facetHash = facets[cmfId];
+ const cmf = this.cmfMap[cmfId];
+
+ const cmfData = [];
+ Object.keys(facetHash).forEach(value => {
+ const count = facetHash[value];
+ cmfData.push({value : value, count : count});
+ });
+
+ if (!facetData[cmf.field_class()]) {
+ facetData[cmf.field_class()] = {};
+ }
+
+ facetData[cmf.field_class()][cmf.name()] = {
+ cmfLabel : cmf.label(),
+ valueList : cmfData.sort((a, b) => {
+ if (a.count > b.count) { return -1; }
+ if (a.count < b.count) { return 1; }
+ // secondary alpha sort on display value
+ return a.value < b.value ? -1 : 1;
+ })
+ };
+ });
+
+ this.lastFacetKey = ctx.result.facet_key;
+ this.lastFacetData = ctx.result.facetData = facetData;
+ resolve();
+ });
+ });
+ }
+
+ fetchCcvms(): Promise<void> {
+
+ if (Object.keys(this.ccvmMap).length) {
+ return Promise.resolve();
+ }
+
+ return new Promise((resolve, reject) => {
+ this.pcrud.search('ccvm',
+ {ctype : CATALOG_CCVM_FILTERS}, {},
+ {atomic: true, anonymous: true}
+ ).subscribe(list => {
+ this.compileCcvms(list);
+ resolve();
+ });
+ });
+ }
+
+ compileCcvms(ccvms: IdlObject[]): void {
+ ccvms.forEach(ccvm => {
+ if (!this.ccvmMap[ccvm.ctype()]) {
+ this.ccvmMap[ccvm.ctype()] = [];
+ }
+ this.ccvmMap[ccvm.ctype()].push(ccvm);
+ });
+
+ Object.keys(this.ccvmMap).forEach(cType => {
+ this.ccvmMap[cType] =
+ this.ccvmMap[cType].sort((a, b) => {
+ return a.value() < b.value() ? -1 : 1;
+ });
+ });
+ }
+
+
+ fetchCmfs(): Promise<void> {
+ // At the moment, we only need facet CMFs.
+ if (Object.keys(this.cmfMap).length) {
+ return Promise.resolve();
+ }
+
+ return new Promise((resolve, reject) => {
+ this.pcrud.search('cmf',
+ {facet_field : 't'}, {}, {atomic: true, anonymous: true}
+ ).subscribe(
+ cmfs => {
+ cmfs.forEach(c => this.cmfMap[c.id()] = c);
+ resolve();
+ }
+ );
+ });
+ }
+}
--- /dev/null
+import {Component, OnInit, Input, ElementRef} from '@angular/core';
+import {NetService} from '@eg/core/net.service';
+import {OrgService} from '@eg/core/org.service';
+import {AuthService} from '@eg/core/auth.service';
+
+@Component({
+ selector: 'eg-marc-html',
+ // view is generated from MARC HTML
+ template: '<ng-template></ng-template>'
+})
+export class MarcHtmlComponent implements OnInit {
+
+ recId: number;
+ initDone = false;
+
+ @Input() set recordId(id: number) {
+ this.recId = id;
+ // Only force new data collection when recordId()
+ // is invoked after ngInit() has already run.
+ if (this.initDone) {
+ this.collectData();
+ }
+ }
+
+ recType: string;
+ @Input() set recordType(rtype: string) {
+ this.recType = rtype;
+ }
+
+ constructor(
+ private elm: ElementRef,
+ private net: NetService,
+ private auth: AuthService
+ ) {}
+
+ ngOnInit() {
+ this.initDone = true;
+ this.collectData();
+ }
+
+ collectData() {
+ if (!this.recId) { return; }
+
+ let service = 'open-ils.search';
+ let method = 'open-ils.search.biblio.record.html';
+ const params: any[] = [this.recId];
+
+ switch (this.recType) {
+
+ case 'authority':
+ method = 'open-ils.search.authority.to_html';
+ break;
+
+ case 'vandelay-authority':
+ params.unshift(this.auth.token());
+ service = 'open-ils.vandelay';
+ method = 'open-ils.vandelay.queued_authority_record.html';
+ break;
+
+ case 'vandelay-bib':
+ params.unshift(this.auth.token());
+ service = 'open-ils.vandelay';
+ method = 'open-ils.vandelay.queued_bib_record.html';
+ break;
+ }
+
+ this.net.requestWithParamList(service, method, params)
+ .toPromise().then(html => this.injectHtml(html));
+ }
+
+ injectHtml(html: string) {
+
+ // Remove embedded labels and actions.
+ html = html.replace(
+ /<button onclick="window.print(.*?)<\/button>/, '');
+
+ html = html.replace(/<title>(.*?)<\/title>/, '');
+
+ // remove reference to nonexistant CSS file
+ html = html.replace(/<link(.*?)\/>/, '');
+
+ // there shouldn't be any, but while we're at it,
+ // kill any embedded script tags
+ html = html.replace(/<script(.*?)<\/script>/, '');
+
+ this.elm.nativeElement.innerHTML = html;
+ }
+}
+
+
--- /dev/null
+import {OrgService} from '@eg/core/org.service';
+import {IdlObject} from '@eg/core/idl.service';
+import {Pager} from '@eg/share/util/pager';
+import {Params} from '@angular/router';
+
+export enum CatalogSearchState {
+ PENDING,
+ SEARCHING,
+ COMPLETE
+}
+
+export class FacetFilter {
+ facetClass: string;
+ facetName: string;
+ facetValue: string;
+
+ constructor(cls: string, name: string, value: string) {
+ this.facetClass = cls;
+ this.facetName = name;
+ this.facetValue = value;
+ }
+
+ equals(filter: FacetFilter): boolean {
+ return (
+ this.facetClass === filter.facetClass &&
+ this.facetName === filter.facetName &&
+ this.facetValue === filter.facetValue
+ );
+ }
+}
+
+// Not an angular service.
+// It's conceviable there could be multiple contexts.
+export class CatalogSearchContext {
+
+ // Search options and filters
+ available = false;
+ global = false;
+ sort: string;
+ fieldClass: string[];
+ query: string[];
+ identQuery: string;
+ identQueryType: string; // isbn, issn, etc.
+ joinOp: string[];
+ matchOp: string[];
+ format: string;
+ searchOrg: IdlObject;
+ ccvmFilters: {[ccvmCode: string]: string[]};
+ facetFilters: FacetFilter[];
+ isStaff: boolean;
+
+ // Result from most recent search.
+ result: any = {};
+ searchState: CatalogSearchState = CatalogSearchState.PENDING;
+
+ // List of IDs in page/offset context.
+ resultIds: number[] = [];
+
+ // Utility stuff
+ pager: Pager;
+ org: OrgService;
+
+ constructor() {
+ this.pager = new Pager();
+ this.reset();
+ }
+
+ // List of result IDs for the current page of data.
+ currentResultIds(): number[] {
+ const ids = [];
+ const max = Math.min(
+ this.pager.offset + this.pager.limit,
+ this.pager.resultCount
+ );
+ for (let idx = this.pager.offset; idx < max; idx++) {
+ ids.push(this.resultIds[idx]);
+ }
+ return ids;
+ }
+
+ addResultId(id: number, resultIdx: number ): void {
+ this.resultIds[resultIdx + this.pager.offset] = id;
+ }
+
+ // Return the record at the requested index.
+ resultIdAt(index: number): number {
+ return this.resultIds[index] || null;
+ }
+
+ // Return the index of the requested record
+ indexForResult(id: number): number {
+ for (let i = 0; i < this.resultIds.length; i++) {
+ if (this.resultIds[i] === id) {
+ return i;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 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
+ * search-global, or search-org.
+ */
+ reset(): void {
+ this.pager.offset = 0;
+ this.format = '';
+ this.sort = '';
+ this.query = [''];
+ this.identQuery = null;
+ this.identQueryType = 'identifier|isbn';
+ this.fieldClass = ['keyword'];
+ this.matchOp = ['contains'];
+ this.joinOp = [''];
+ this.ccvmFilters = {};
+ this.facetFilters = [];
+ this.result = {};
+ this.resultIds = [];
+ this.searchState = CatalogSearchState.PENDING;
+ }
+
+ isSearchable(): boolean {
+
+ if (this.identQuery && this.identQueryType) {
+ return true;
+ }
+
+ return this.query.length
+ && this.query[0] !== ''
+ && this.searchOrg !== null;
+ }
+
+ compileSearch(): string {
+ let str = '';
+
+ if (this.available) {
+ str += '#available';
+ }
+
+ if (this.sort) {
+ // e.g. title, title.descending
+ const parts = this.sort.split(/\./);
+ if (parts[1]) { str += ' #descending'; }
+ str += ' sort(' + parts[0] + ')';
+ }
+
+ if (this.identQuery && this.identQueryType) {
+ if (str) { str += ' '; }
+ str += this.identQueryType + ':' + this.identQuery;
+
+ } else {
+
+ // -------
+ // Compile boolean sub-query components
+ if (str.length) { str += ' '; }
+ const qcount = this.query.length;
+
+ // if we multiple boolean query components, wrap them in parens.
+ if (qcount > 1) { str += '('; }
+ this.query.forEach((q, idx) => {
+ str += this.compileBoolQuerySet(idx);
+ });
+ if (qcount > 1) { str += ')'; }
+ // -------
+ }
+
+ if (this.format) {
+ str += ' format(' + this.format + ')';
+ }
+
+ if (this.global) {
+ str += ' depth(' +
+ this.org.root().ou_type().depth() + ')';
+ }
+
+ str += ' site(' + this.searchOrg.shortname() + ')';
+
+ Object.keys(this.ccvmFilters).forEach(field => {
+ if (this.ccvmFilters[field][0] !== '') {
+ str += ' ' + field + '(' + this.ccvmFilters[field] + ')';
+ }
+ });
+
+ this.facetFilters.forEach(f => {
+ str += ' ' + f.facetClass + '|'
+ + f.facetName + '[' + f.facetValue + ']';
+ });
+
+ return str;
+ }
+
+ stripQuotes(query: string): string {
+ return query.replace(/"/g, '');
+ }
+
+ stripAnchors(query: string): string {
+ return query.replace(/[\^\$]/g, '');
+ }
+
+ addQuotes(query: string): string {
+ if (query.match(/ /)) {
+ return '"' + query + '"';
+ }
+ return query;
+ }
+
+ compileBoolQuerySet(idx: number): string {
+ let query = this.query[idx];
+ const joinOp = this.joinOp[idx];
+ const matchOp = this.matchOp[idx];
+ const fieldClass = this.fieldClass[idx];
+
+ let str = '';
+ if (!query) { return str; }
+
+ if (idx > 0) { str += ' ' + joinOp + ' '; }
+
+ str += '(';
+ if (fieldClass) { str += fieldClass + ':'; }
+
+ switch (matchOp) {
+ case 'phrase':
+ query = this.addQuotes(this.stripQuotes(query));
+ break;
+ case 'nocontains':
+ query = '-' + this.addQuotes(this.stripQuotes(query));
+ break;
+ case 'exact':
+ query = '^' + this.stripAnchors(query) + '$';
+ break;
+ case 'starts':
+ query = this.addQuotes('^' +
+ this.stripAnchors(this.stripQuotes(query)));
+ break;
+ }
+
+ return str + query + ')';
+ }
+
+ hasFacet(facet: FacetFilter): boolean {
+ return Boolean(
+ this.facetFilters.filter(f => f.equals(facet))[0]
+ );
+ }
+
+ removeFacet(facet: FacetFilter): void {
+ this.facetFilters = this.facetFilters.filter(f => !f.equals(facet));
+ }
+
+ addFacet(facet: FacetFilter): void {
+ if (!this.hasFacet(facet)) {
+ this.facetFilters.push(facet);
+ }
+ }
+
+ toggleFacet(facet: FacetFilter): void {
+ if (this.hasFacet(facet)) {
+ this.removeFacet(facet);
+ } else {
+ this.facetFilters.push(facet);
+ }
+ }
+}
+
+
--- /dev/null
+import {Injectable, EventEmitter} from '@angular/core';
+import {OrgService} from '@eg/core/org.service';
+
+/*
+TODO: Add Display Fields to UNAPI
+https://library.biz/opac/extras/unapi?id=tag::U2@bre/1{bre.extern,holdings_xml,mra}/BR1/0&format=mods32
+*/
+
+const UNAPI_PATH = '/opac/extras/unapi?id=tag::U2@';
+
+interface UnapiParams {
+ target: string; // bre, ...
+ id: number | string; // 1 | 1,2,3,4,5
+ extras: string; // {holdings_xml,mra,...}
+ format: string; // mods32, marxml, ...
+ orgId?: number; // org unit ID
+ depth?: number; // org unit depth
+}
+
+@Injectable()
+export class UnapiService {
+
+ constructor(private org: OrgService) {}
+
+ createUrl(params: UnapiParams): string {
+ const depth = params.depth || 0;
+ const org = params.orgId ? this.org.get(params.orgId) : this.org.root();
+
+ return `${UNAPI_PATH}${params.target}/${params.id}${params.extras}/` +
+ `${org.shortname()}/${depth}&format=${params.format}`;
+ }
+
+ getAsXmlDocument(params: UnapiParams): Promise<XMLDocument> {
+ // XReq creates an XML document for us. Seems like the right
+ // tool for the job.
+ const url = this.createUrl(params);
+ return new Promise((resolve, reject) => {
+ const xhttp = new XMLHttpRequest();
+ xhttp.onreadystatechange = function() { // no () => {} !
+ if (this.readyState === 4) {
+ if (this.status === 200) {
+ resolve(xhttp.responseXML);
+ } else {
+ reject(`UNAPI request failed for ${url}`);
+ }
+ }
+ };
+ xhttp.open('GET', url, true);
+ xhttp.send();
+ });
+ }
+}
+
+
--- /dev/null
+import {Component, Input, Host, OnInit} from '@angular/core';
+import {ComboboxComponent} from './combobox.component';
+
+@Component({
+ selector: 'eg-combobox-entry',
+ template: '<ng-template></ng-template>'
+})
+export class ComboboxEntryComponent implements OnInit {
+
+ @Input() entryId: any;
+ @Input() entryLabel: string;
+ @Input() selected: boolean;
+
+ constructor(@Host() private combobox: ComboboxComponent) {}
+
+ ngOnInit() {
+ if (this.selected) {
+ this.combobox.startId = this.entryId;
+ }
+ this.combobox.addEntry(
+ {id: this.entryId, label: this.entryLabel});
+ }
+}
+
+
--- /dev/null
+
+<!-- todo disabled -->
+<ng-template #displayTemplate let-r="result">
+{{r.label}}
+</ng-template>
+
+<div class="d-flex">
+ <input type="text"
+ class="form-control"
+ [ngClass]="{'text-success font-italic font-weight-bold': selected && selected.freetext}"
+ [placeholder]="placeholder"
+ [name]="name"
+ [disabled]="isDisabled"
+ [required]="isRequired"
+ [(ngModel)]="selected"
+ [ngbTypeahead]="filter"
+ [resultTemplate]="displayTemplate"
+ [inputFormatter]="formatDisplayString"
+ (click)="click$.next($event.target.value)"
+ (blur)="onBlur()"
+ (selectItem)="selectorChanged($event)"
+ #instance="ngbTypeahead"/>
+ <div class="d-flex flex-column icons" (click)="openMe($event)">
+ <span class="material-icons">keyboard_arrow_up</span>
+ <span class="material-icons">keyboard_arrow_down</span>
+ </div>
+</div>
--- /dev/null
+/**
+ * <eg-combobox [allowFreeText]="true" [entries]="comboboxEntryList"/>
+ * <!-- see also <eg-combobox-entry> -->
+ * </eg-combobox>
+ */
+import {Component, OnInit, Input, Output, ViewChild, EventEmitter, ElementRef} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {map} from 'rxjs/operators/map';
+import {tap} from 'rxjs/operators/tap';
+import {reduce} from 'rxjs/operators/reduce';
+import {of} from 'rxjs';
+import {mergeMap} from 'rxjs/operators/mergeMap';
+import {mapTo} from 'rxjs/operators/mapTo';
+import {debounceTime} from 'rxjs/operators/debounceTime';
+import {distinctUntilChanged} from 'rxjs/operators/distinctUntilChanged';
+import {merge} from 'rxjs/operators/merge';
+import {filter} from 'rxjs/operators/filter';
+import {Subject} from 'rxjs/Subject';
+import {NgbTypeahead, NgbTypeaheadSelectItemEvent} from '@ng-bootstrap/ng-bootstrap';
+import {StoreService} from '@eg/core/store.service';
+
+export interface ComboboxEntry {
+ id: any;
+ label: string;
+ freetext?: boolean;
+}
+
+@Component({
+ selector: 'eg-combobox',
+ templateUrl: './combobox.component.html',
+ styles: [`
+ .icons {margin-left:-18px}
+ .material-icons {font-size: 16px;font-weight:bold}
+ `]
+})
+export class ComboboxComponent implements OnInit {
+
+ selected: ComboboxEntry;
+ click$: Subject<string>;
+ entrylist: ComboboxEntry[];
+
+ @ViewChild('instance') instance: NgbTypeahead;
+
+ // Applies a name attribute to the input.
+ // Useful in forms.
+ @Input() name: string;
+
+ // Placeholder text for selector input
+ @Input() placeholder = '';
+
+ @Input() persistKey: string; // TODO
+
+ @Input() allowFreeText = false;
+
+ // Add a 'required' attribute to the input
+ isRequired: boolean;
+ @Input() set required(r: boolean) {
+ this.isRequired = r;
+ }
+
+ // Disable the input
+ isDisabled: boolean;
+ @Input() set disabled(d: boolean) {
+ this.isDisabled = d;
+ }
+
+ // Entry ID of the default entry to select (optional)
+ // onChange() is NOT fired when applying the default value,
+ // unless startIdFiresOnChange is set to true.
+ @Input() startId: any;
+ @Input() startIdFiresOnChange: boolean;
+
+ @Input() asyncDataSource: (term: string) => Observable<ComboboxEntry>;
+
+ // Useful for efficiently preventing duplicate async entries
+ asyncIds: {[idx: string]: boolean};
+
+ // True if a default selection has been made.
+ defaultSelectionApplied: boolean;
+
+ @Input() set entries(el: ComboboxEntry[]) {
+ this.entrylist = el;
+ this.applySelection();
+ }
+
+ // Emitted when the value is changed via UI.
+ // When the UI value is cleared, null is emitted.
+ @Output() onChange: EventEmitter<ComboboxEntry>;
+
+ // Useful for massaging the match string prior to comparison
+ // and display. Default version trims leading/trailing spaces.
+ formatDisplayString: (ComboboxEntry) => string;
+
+ constructor(
+ private elm: ElementRef,
+ private store: StoreService,
+ ) {
+ this.entrylist = [];
+ this.asyncIds = {};
+ this.click$ = new Subject<string>();
+ this.onChange = new EventEmitter<ComboboxEntry>();
+ this.defaultSelectionApplied = false;
+
+ this.formatDisplayString = (result: ComboboxEntry) => {
+ return result.label.trim();
+ };
+ }
+
+ ngOnInit() {
+ }
+
+ openMe($event) {
+ // Give the input a chance to focus then fire the click
+ // handler to force open the typeahead
+ this.elm.nativeElement.getElementsByTagName('input')[0].focus();
+ setTimeout(() => this.click$.next(''));
+ }
+
+ // Apply a default selection where needed
+ applySelection() {
+
+ if (this.startId &&
+ this.entrylist && !this.defaultSelectionApplied) {
+
+ const entry =
+ this.entrylist.filter(e => e.id === this.startId)[0];
+
+ if (entry) {
+ this.selected = entry;
+ this.defaultSelectionApplied = true;
+ if (this.startIdFiresOnChange) {
+ this.selectorChanged(
+ {item: this.selected, preventDefault: () => true});
+ }
+ }
+ }
+ }
+
+ // Called by combobox-entry.component
+ addEntry(entry: ComboboxEntry) {
+ this.entrylist.push(entry);
+ this.applySelection();
+ }
+
+ onBlur() {
+ // When the selected value is a string it means we have either
+ // no value (user cleared the input) or a free-text value.
+
+ if (typeof this.selected === 'string') {
+
+ if (this.allowFreeText && this.selected !== '') {
+ // Free text entered which does not match a known entry
+ // translate it into a dummy ComboboxEntry
+ this.selected = {
+ id: null,
+ label: this.selected,
+ freetext: true
+ };
+
+ } else {
+
+ this.selected = null;
+ }
+
+ // Manually fire the onchange since NgbTypeahead fails
+ // to fire the onchange when the value is cleared.
+ this.selectorChanged(
+ {item: this.selected, preventDefault: () => true});
+ }
+ }
+
+ // Fired by the typeahead to inform us of a change.
+ selectorChanged(selEvent: NgbTypeaheadSelectItemEvent) {
+ this.onChange.emit(selEvent.item);
+ }
+
+ // Adds matching async entries to the entry list
+ // and propagates the search term for pipelining.
+ addAsyncEntries(term: string): Observable<string> {
+
+ if (!term || !this.asyncDataSource) {
+ return of(term);
+ }
+
+ return new Observable(observer => {
+ this.asyncDataSource(term).subscribe(
+ (entry: ComboboxEntry) => {
+ if (!this.asyncIds['' + entry.id]) {
+ this.asyncIds['' + entry.id] = true;
+ this.addEntry(entry);
+ }
+ },
+ err => {},
+ () => {
+ observer.next(term);
+ observer.complete();
+ }
+ );
+ });
+ }
+
+ filter = (text$: Observable<string>): Observable<ComboboxEntry[]> => {
+ return text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged(),
+
+ // Merge click actions in with the stream of text entry
+ merge(
+ // Inject a specifier indicating the source of the
+ // action is a user click instead of a text entry.
+ // This tells the filter to show all values in sync mode.
+ this.click$.pipe(filter(() =>
+ !this.instance.isPopupOpen() && !this.asyncDataSource
+ )).pipe(mapTo('_CLICK_'))
+ ),
+
+ // mergeMap coalesces an observable into our stream.
+ mergeMap(term => this.addAsyncEntries(term)),
+ map((term: string) => {
+
+ if (term === '' || term === '_CLICK_') {
+ if (this.asyncDataSource) {
+ return [];
+ } else {
+ // In sync mode, a post-focus empty search or
+ // click event displays the whole list.
+ return this.entrylist;
+ }
+ }
+
+ // Filter entrylist whose labels substring-match the
+ // text entered.
+ return this.entrylist.filter(entry =>
+ entry.label.toLowerCase().indexOf(term.toLowerCase()) > -1
+ );
+ })
+ );
+ }
+}
+
+
--- /dev/null
+
+<div class="input-group">
+ <input
+ class="form-control"
+ ngbDatepicker
+ #datePicker="ngbDatepicker"
+ placeholder="yyyy-mm-dd"
+ class="form-control"
+ name="{{fieldName}}"
+ [required]="required"
+ [(ngModel)]="current"
+ (dateSelect)="onDateSelect($event)">
+ <div class="input-group-append">
+ <button class="btn btn-outline-secondary"
+ (click)="datePicker.toggle()" type="button">
+ <span title="Select Date" i18n-title
+ class="material-icons mat-icon-in-button">calendar_today</span>
+ </button>
+ </div>
+</div>
+
--- /dev/null
+import {Component, OnInit, Input, Output, ViewChild, EventEmitter} from '@angular/core';
+import {NgbDateStruct} from '@ng-bootstrap/ng-bootstrap';
+
+/**
+ * RE: displaying locale dates in the input field:
+ * https://github.com/ng-bootstrap/ng-bootstrap/issues/754
+ * https://stackoverflow.com/questions/40664523/angular2-ngbdatepicker-how-to-format-date-in-inputfield
+ */
+
+@Component({
+ selector: 'eg-date-select',
+ templateUrl: './date-select.component.html'
+})
+export class DateSelectComponent implements OnInit {
+
+ @Input() initialIso: string; // ISO string
+ @Input() initialYmd: string; // YYYY-MM-DD (uses local time zone)
+ @Input() initialDate: Date; // Date object
+ @Input() required: boolean;
+ @Input() fieldName: string;
+
+ current: NgbDateStruct;
+
+ @Output() onChangeAsDate: EventEmitter<Date>;
+ @Output() onChangeAsIso: EventEmitter<string>;
+ @Output() onChangeAsYmd: EventEmitter<string>;
+
+ constructor() {
+ this.onChangeAsDate = new EventEmitter<Date>();
+ this.onChangeAsIso = new EventEmitter<string>();
+ this.onChangeAsYmd = new EventEmitter<string>();
+ }
+
+ ngOnInit() {
+
+ if (this.initialYmd) {
+ this.initialDate = this.localDateFromYmd(this.initialYmd);
+
+ } else if (this.initialIso) {
+ this.initialDate = new Date(this.initialIso);
+ }
+
+ if (this.initialDate) {
+ this.current = {
+ year: this.initialDate.getFullYear(),
+ month: this.initialDate.getMonth() + 1,
+ day: this.initialDate.getDate()
+ };
+ }
+ }
+
+ onDateSelect(evt) {
+ const ymd = `${evt.year}-${evt.month}-${evt.day}`;
+ const date = this.localDateFromYmd(ymd);
+ const iso = date.toISOString();
+ this.onChangeAsDate.emit(date);
+ this.onChangeAsYmd.emit(ymd);
+ this.onChangeAsIso.emit(iso);
+ }
+
+ // Create a date in the local time zone with selected YMD values.
+ // TODO: Consider moving this to a date service...
+ localDateFromYmd(ymd: string): Date {
+ const parts = ymd.split('-');
+ return new Date(
+ Number(parts[0]), Number(parts[1]) - 1, Number(parts[2]));
+ }
+}
+
+
--- /dev/null
+<ng-template #dialogContent>
+ <div class="modal-header bg-info">
+ <h4 class="modal-title">{{dialogTitle}}</h4>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close"
+ (click)="dismiss('cross_click')">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body"><p>{{dialogBody}}</p></div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-success"
+ (click)="close('confirmed')" i18n>Confirm</button>
+ <button type="button" class="btn btn-warning"
+ (click)="dismiss('canceled')" i18n>Cancel</button>
+ </div>
+</ng-template>
--- /dev/null
+import {Component, Input, ViewChild, TemplateRef} from '@angular/core';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+
+@Component({
+ selector: 'eg-confirm-dialog',
+ templateUrl: './confirm.component.html'
+})
+
+/**
+ * Confirmation dialog that asks a yes/no question.
+ */
+export class ConfirmDialogComponent extends DialogComponent {
+ // What question are we asking?
+ @Input() public dialogBody: string;
+}
+
+
--- /dev/null
+import {Component, Input, OnInit, ViewChild, TemplateRef, EventEmitter} from '@angular/core';
+import {NgbModal, NgbModalRef, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
+
+/**
+ * Dialog base class. Handles the ngbModal logic.
+ * Sub-classed component templates must have a #dialogContent selector
+ * at the root of the template (see ConfirmDialogComponent).
+ */
+
+@Component({
+ selector: 'eg-dialog',
+ template: '<ng-template></ng-template>'
+})
+export class DialogComponent implements OnInit {
+
+ // Assume all dialogs support a title attribute.
+ @Input() public dialogTitle: string;
+
+ // Pointer to the dialog content template.
+ @ViewChild('dialogContent')
+ private dialogContent: TemplateRef<any>;
+
+ // Emitted after open() is called on the ngbModal.
+ // Note when overriding open(), this will not fire unless also
+ // called in the overridding method.
+ onOpen$ = new EventEmitter<any>();
+
+ // The modalRef allows direct control of the modal instance.
+ private modalRef: NgbModalRef = null;
+
+ constructor(private modalService: NgbModal) {}
+
+ ngOnInit() {
+ this.onOpen$ = new EventEmitter<any>();
+ }
+
+ open(options?: NgbModalOptions): Promise<any> {
+
+ if (this.modalRef !== null) {
+ console.warn('Dismissing existing dialog');
+ this.dismiss();
+ }
+
+ this.modalRef = this.modalService.open(this.dialogContent, options);
+
+ if (this.onOpen$) {
+ // Let the digest cycle complete
+ setTimeout(() => this.onOpen$.emit(true));
+ }
+
+ return new Promise( (resolve, reject) => {
+
+ this.modalRef.result.then(
+ (result) => {
+ resolve(result);
+ this.modalRef = null;
+ },
+ (result) => {
+ console.debug('dialog closed with ' + result);
+ reject(result);
+ this.modalRef = null;
+ }
+ );
+ });
+ }
+
+ close(reason?: any): void {
+ if (this.modalRef) {
+ this.modalRef.close(reason);
+ }
+ }
+
+ dismiss(reason?: any): void {
+ if (this.modalRef) {
+ this.modalRef.dismiss(reason);
+ }
+ }
+}
+
+
--- /dev/null
+
+.eg-progress-inline progress {
+ width: 100%;
+ height: 25px;
+}
--- /dev/null
+<div class="eg-progress-inline">
+
+ <div *ngIf="hasValue() && hasMax()">
+ <!-- determinate progress bar. shows max/value progress -->
+ <div class="row">
+ <div class="col-lg-10">
+ <progress max="{{max}}" value="{{value}}"></progress>
+ </div>
+ <div class="col-lg-2">{{percent()}}%</div>
+ </div>
+ </div>
+
+ <div *ngIf="hasValue() && !hasMax()">
+ <div class="row">
+ <!-- semi-determinate progress bar. shows value -->
+ <div class="col-lg-10"><progress max="1"></progress></div>
+ <div class="col-lg-2">{{value}}...</div>
+ </div>
+ </div>
+
+ <div *ngIf="!hasValue()">
+ <div class="row">
+ <!-- indeterminate -->
+ <div class="col-lg-12"><progress max="1"></progress></div>
+ </div>
+ </div>
+
+</div>
--- /dev/null
+import {Component, Input, ViewChild, TemplateRef} from '@angular/core';
+
+/**
+ * Inline Progress Bar
+ *
+ * // assuming a template reference...
+ * @ViewChild('progress')
+ * private progress: progressInlineComponent;
+ *
+ * progress.update({value : 0, max : 123});
+ * progress.increment();
+ * progress.increment();
+ *
+ * Each progress has 2 numbers, 'max' and 'value'.
+ * The content of these values determines how the progress displays.
+ *
+ * There are 3 flavors:
+ *
+ * -- value is set, max is set
+ * determinate: shows a progression with a percent complete.
+ *
+ * -- value is set, max is unset
+ * semi-determinate, with a value report. Shows a value-less
+ * <progress/>, but shows the value as a number in the progress.
+ *
+ * This is useful in cases where the total number of items to retrieve
+ * from the server is unknown, but we know how many items we've
+ * retrieved thus far. It helps to reinforce that something specific
+ * is happening, but we don't know when it will end.
+ *
+ * -- value is unset
+ * indeterminate: shows a generic value-less <progress/> with no
+ * clear indication of progress.
+ */
+@Component({
+ selector: 'eg-progress-inline',
+ templateUrl: './progress-inline.component.html',
+ styleUrls: ['progress-inline.component.css']
+})
+export class ProgressInlineComponent {
+
+ @Input() max: number;
+ @Input() value: number;
+
+ reset() {
+ delete this.max;
+ delete this.value;
+ }
+
+ hasValue(): boolean {
+ return Number.isInteger(this.value);
+ }
+
+ hasMax(): boolean {
+ return Number.isInteger(this.max);
+ }
+
+ percent(): number {
+ if (this.hasValue() &&
+ this.hasMax() &&
+ this.max > 0 &&
+ this.value <= this.max) {
+ return Math.floor((this.value / this.max) * 100);
+ }
+ return 100;
+ }
+
+ // Set the current state of the progress bar.
+ update(args: {[key: string]: number}) {
+ if (args.max !== undefined) {
+ this.max = args.max;
+ }
+ if (args.value !== undefined) {
+ this.value = args.value;
+ }
+ }
+
+ // Increment the current value. If no amount is specified,
+ // it increments by 1. Calling increment() on an indetermite
+ // progress bar will force it to be a (semi-)determinate bar.
+ increment(amt?: number) {
+ if (!Number.isInteger(amt)) { amt = 1; }
+
+ if (!this.hasValue()) {
+ this.value = 0;
+ }
+
+ this.value += amt;
+ }
+}
+
+
--- /dev/null
+
+.eg-progress-dialog progress {
+ width: 100%;
+ height: 25px;
+}
--- /dev/null
+<ng-template #dialogContent>
+ <div class="modal-header bg-info">
+ <h4 *ngIf="dialogTitle" class="modal-title">{{dialogTitle}}</h4>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close"
+ (click)="dismiss('cross_click')">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+
+ <div class="modal-body eg-progress-dialog">
+
+ <div *ngIf="hasValue() && hasMax()">
+ <!-- determinate progress bar. shows max/value progress -->
+ <div class="col-lg-10">
+ <progress max="{{max}}" value="{{value}}"></progress>
+ </div>
+ <div class="col-lg-2">{{percent()}}%</div>
+ </div>
+
+ <div *ngIf="hasValue() && !hasMax()">
+ <!-- semi-determinate progress bar. shows value -->
+ <div class="col-lg-10"><progress max="1"></progress></div>
+ <div class="col-lg-2">{{value}}...</div>
+ </div>
+
+ <div *ngIf="!hasValue()">
+ <!-- indeterminate -->
+ <div class="col-lg-12"><progress max="1"></progress></div>
+ </div>
+
+ </div>
+</ng-template>
--- /dev/null
+import {Component, Input, ViewChild, TemplateRef} from '@angular/core';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+
+@Component({
+ selector: 'eg-progress-dialog',
+ templateUrl: './progress.component.html',
+ styleUrls: ['progress.component.css']
+})
+
+/**
+ * TODO: This duplicates the code from ProgressInlineComponent.
+ * This component should insert to <eg-progress-inline/> into
+ * its template instead of duplicating the code. However, until
+ * Angular bug https://github.com/angular/angular/issues/14842
+ * is fixed, it's not possible to get a reference to the embedded
+ * inline progress, which is needed for access the update/increment
+ * API.
+ * Also consider moving the progress traking logic to a service
+ * to reduce code duplication.
+ */
+
+/**
+ * Progress Dialog.
+ *
+ * // assuming a template reference...
+ * @ViewChild('progressDialog')
+ * private dialog: ProgressDialogComponent;
+ *
+ * dialog.open();
+ * dialog.update({value : 0, max : 123});
+ * dialog.increment();
+ * dialog.increment();
+ * dialog.close();
+ *
+ * Each dialog has 2 numbers, 'max' and 'value'.
+ * The content of these values determines how the dialog displays.
+ *
+ * There are 3 flavors:
+ *
+ * -- value is set, max is set
+ * determinate: shows a progression with a percent complete.
+ *
+ * -- value is set, max is unset
+ * semi-determinate, with a value report. Shows a value-less
+ * <progress/>, but shows the value as a number in the dialog.
+ *
+ * This is useful in cases where the total number of items to retrieve
+ * from the server is unknown, but we know how many items we've
+ * retrieved thus far. It helps to reinforce that something specific
+ * is happening, but we don't know when it will end.
+ *
+ * -- value is unset
+ * indeterminate: shows a generic value-less <progress/> with no
+ * clear indication of progress.
+ */
+export class ProgressDialogComponent extends DialogComponent {
+
+ max: number;
+ value: number;
+
+ reset() {
+ delete this.max;
+ delete this.value;
+ }
+
+ hasValue(): boolean {
+ return Number.isInteger(this.value);
+ }
+
+ hasMax(): boolean {
+ return Number.isInteger(this.max);
+ }
+
+ percent(): number {
+ if (this.hasValue() &&
+ this.hasMax() &&
+ this.max > 0 &&
+ this.value <= this.max) {
+ return Math.floor((this.value / this.max) * 100);
+ }
+ return 100;
+ }
+
+ // Set the current state of the progress bar.
+ update(args: {[key: string]: number}) {
+ if (args.max !== undefined) {
+ this.max = args.max;
+ }
+ if (args.value !== undefined) {
+ this.value = args.value;
+ }
+ }
+
+ // Increment the current value. If no amount is specified,
+ // it increments by 1. Calling increment() on an indetermite
+ // progress bar will force it to be a (semi-)determinate bar.
+ increment(amt?: number) {
+ if (!Number.isInteger(amt)) { amt = 1; }
+
+ if (!this.hasValue()) {
+ this.value = 0;
+ }
+
+ this.value += amt;
+ }
+}
+
+
--- /dev/null
+<ng-template #dialogContent>
+ <div class="modal-header bg-info">
+ <h4 class="modal-title">{{dialogTitle}}</h4>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close"
+ (click)="dismiss('cross_click')">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <p>{{dialogBody}}</p>
+ <div class="text-center">
+ <input class="form-control" [(ngModel)]="promptValue"/>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-success"
+ (click)="close(promptValue)" i18n>Confirm</button>
+ <button type="button" class="btn btn-warning"
+ (click)="dismiss('canceled')" i18n>Cancel</button>
+ </div>
+</ng-template>
--- /dev/null
+import {Component, Input, ViewChild, TemplateRef} from '@angular/core';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+
+@Component({
+ selector: 'eg-prompt-dialog',
+ templateUrl: './prompt.component.html'
+})
+
+/**
+ * Promptation dialog that requests user input.
+ */
+export class PromptDialogComponent extends DialogComponent {
+ // What question are we asking?
+ @Input() public dialogBody: string;
+ // Value to return to the caller
+ @Input() public promptValue: string;
+}
+
+
--- /dev/null
+<ng-template #dialogContent>
+ <div class="modal-header bg-info">
+ <h4 class="modal-title" i18n>Record Editor: {{recordLabel}}</h4>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close"
+ (click)="dismiss('cross_click')">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <form #fmEditForm="ngForm" role="form" class="form-validated common-form striped-odd">
+ <div class="form-group row" *ngFor="let field of fields">
+ <div class="col-lg-3 offset-lg-1">
+ <label for="rec-{{field.name}}">{{field.label}}</label>
+ </div>
+ <div class="col-lg-7">
+
+ <span *ngIf="field.template">
+ <ng-container
+ *ngTemplateOutlet="field.template; context:customTemplateFieldContext(field)">
+ </ng-container>
+ </span>
+
+ <span *ngIf="!field.template">
+
+ <span *ngIf="field.datatype == 'id' && !pkeyIsEditable">
+ {{record[field.name]()}}
+ </span>
+
+ <input *ngIf="field.datatype == 'id' && pkeyIsEditable"
+ class="form-control"
+ name="{{field.name}}"
+ placeholder="{{field.label}}..."
+ i18n-placeholder
+ [readonly]="field.readOnly"
+ [required]="field.isRequired()"
+ [ngModel]="record[field.name]()"
+ (ngModelChange)="record[field.name]($event)"/>
+
+ <input *ngIf="field.datatype == 'text' || field.datatype == 'interval'"
+ class="form-control"
+ name="{{field.name}}"
+ placeholder="{{field.label}}..."
+ i18n-placeholder
+ [readonly]="field.readOnly"
+ [required]="field.isRequired()"
+ [ngModel]="record[field.name]()"
+ (ngModelChange)="record[field.name]($event)"/>
+
+ <span *ngIf="field.datatype == 'timestamp'">
+ <eg-date-select
+ (onChangeAsIso)="record[field.name]($event)"
+ initialIso="{{record[field.name]()}}">
+ </eg-date-select>
+ </span>
+
+ <input *ngIf="field.datatype == 'int'"
+ class="form-control"
+ type="number"
+ name="{{field.name}}"
+ placeholder="{{field.label}}..."
+ i18n-placeholder
+ [readonly]="field.readOnly"
+ [required]="field.isRequired()"
+ [ngModel]="record[field.name]()"
+ (ngModelChange)="record[field.name]($event)"/>
+
+ <input *ngIf="field.datatype == 'float'"
+ class="form-control"
+ type="number" step="0.1"
+ name="{{field.name}}"
+ placeholder="{{field.label}}..."
+ i18n-placeholder
+ [readonly]="field.readOnly"
+ [required]="field.isRequired()"
+ [ngModel]="record[field.name]()"
+ (ngModelChange)="record[field.name]($event)"/>
+
+ <span *ngIf="field.datatype == 'money'">
+ <!-- in read-only mode display the local-aware currency -->
+ <input *ngIf="field.readOnly"
+ class="form-control"
+ type="number" step="0.1"
+ name="{{field.name}}"
+ [readonly]="field.readOnly"
+ [required]="field.isRequired()"
+ [ngModel]="record[field.name]() | currency"/>
+
+ <input *ngIf="!field.readOnly"
+ class="form-control"
+ type="number" step="0.1"
+ name="{{field.name}}"
+ placeholder="{{field.label}}..."
+ i18n-placeholder
+ [readonly]="field.readOnly"
+ [required]="field.isRequired()"
+ [ngModel]="record[field.name]()"
+ (ngModelChange)="record[field.name]($event)"/>
+ </span>
+
+ <input *ngIf="field.datatype == 'bool'"
+ class="form-check-input"
+ type="checkbox"
+ name="{{field.name}}"
+ [readonly]="field.readOnly"
+ [ngModel]="record[field.name]()"
+ (ngModelChange)="record[field.name]($event)"/>
+
+ <span *ngIf="field.datatype == 'link'"
+ [ngClass]="{nullable : !field.isRequired()}">
+ <select
+ class="form-control"
+ name="{{field.name}}"
+ [disabled]="field.readOnly"
+ [required]="field.isRequired()"
+ [ngModel]="record[field.name]()"
+ (ngModelChange)="record[field.name]($event)">
+ <option *ngFor="let item of field.linkedValues"
+ [value]="item.id">{{item.name}}</option>
+ </select>
+ </span>
+
+ <eg-org-select *ngIf="field.datatype == 'org_unit'"
+ placeholder="{{field.label}}..."
+ i18n-placeholder
+ [limitPerms]="modePerms[mode]"
+ [applyDefault]="field.orgDefaultAllowed"
+ [initialOrgId]="record[field.name]()"
+ (onChange)="record[field.name]($event)">
+ </eg-org-select>
+
+ </span>
+ </div>
+ </div>
+ </form>
+ </div>
+ <div class="modal-footer">
+ <button type="button" class="btn btn-success" *ngIf="mode == 'view'"
+ (click)="close()" i18n>Close</button>
+ <button type="button" class="btn btn-info"
+ [disabled]="fmEditForm.invalid" *ngIf="mode != 'view'"
+ (click)="save()" i18n>Save</button>
+ <button type="button" class="btn btn-warning ml-2" *ngIf="mode != 'view'"
+ (click)="cancel()" i18n>Cancel</button>
+ </div>
+</ng-template>
--- /dev/null
+import {Component, OnInit, Input,
+ Output, EventEmitter, TemplateRef} from '@angular/core';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {AuthService} from '@eg/core/auth.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgbModal, NgbModalOptions} from '@ng-bootstrap/ng-bootstrap';
+
+interface CustomFieldTemplate {
+ template: TemplateRef<any>;
+
+ // Allow the caller to pass in a free-form context blob to
+ // be addedto the caller's custom template context, along
+ // with our stock context.
+ context?: {[fields: string]: any};
+}
+
+interface CustomFieldContext {
+ // Current create/edit/view record
+ record: IdlObject;
+
+ // IDL field definition blob
+ field: any;
+
+ // additional context values passed via CustomFieldTemplate
+ [fields: string]: any;
+}
+
+@Component({
+ selector: 'eg-fm-record-editor',
+ templateUrl: './fm-editor.component.html'
+})
+export class FmRecordEditorComponent
+ extends DialogComponent implements OnInit {
+
+ // IDL class hint (e.g. "aou")
+ @Input() idlClass: string;
+
+ // mode: 'create' for creating a new record,
+ // 'update' for editing an existing record
+ // 'view' for viewing an existing record without editing
+ mode: 'create' | 'update' | 'view' = 'create';
+ recId: any;
+ // IDL record we are editing
+ // TODO: allow this to be update in real time by the caller?
+ record: IdlObject;
+
+ // Permissions extracted from the permacrud defs in the IDL
+ // for the current IDL class
+ modePerms: {[mode: string]: string};
+
+ @Input() customFieldTemplates:
+ {[fieldName: string]: CustomFieldTemplate} = {};
+
+ // list of fields that should not be displayed
+ @Input() hiddenFieldsList: string[] = [];
+ @Input() hiddenFields: string; // comma-separated string version
+
+ // list of fields that should always be read-only
+ @Input() readonlyFieldsList: string[] = [];
+ @Input() readonlyFields: string; // comma-separated string version
+
+ // list of required fields; this supplements what the IDL considers
+ // required
+ @Input() requiredFieldsList: string[] = [];
+ @Input() requiredFields: string; // comma-separated string version
+
+ // list of org_unit fields where a default value may be applied by
+ // the org-select if no value is present.
+ @Input() orgDefaultAllowedList: string[] = [];
+ @Input() orgDefaultAllowed: string; // comma-separated string version
+
+ // hash, keyed by field name, of functions to invoke to check
+ // whether a field is required. Each callback is passed the field
+ // name and the record and should return a boolean value. This
+ // supports cases where whether a field is required or not depends
+ // on the current value of another field.
+ @Input() isRequiredOverride:
+ {[field: string]: (field: string, record: IdlObject) => boolean};
+
+ // IDL record display label. Defaults to the IDL label.
+ @Input() recordLabel: string;
+
+ // Emit the modified object when the save action completes.
+ @Output() onSave$ = new EventEmitter<IdlObject>();
+
+ // Emit the original object when the save action is canceled.
+ @Output() onCancel$ = new EventEmitter<IdlObject>();
+
+ // Emit an error message when the save action fails.
+ @Output() onError$ = new EventEmitter<string>();
+
+ // IDL info for the the selected IDL class
+ idlDef: any;
+
+ // Can we edit the primary key?
+ pkeyIsEditable = false;
+
+ // List of IDL field definitions. This is a subset of the full
+ // list of fields on the IDL, since some are hidden, virtual, etc.
+ fields: any[];
+
+ @Input() editMode(mode: 'create' | 'update' | 'view') {
+ this.mode = mode;
+ }
+
+ // Record ID to view/update. Value is dynamic. Records are not
+ // fetched until .open() is called.
+ @Input() set recordId(id: any) {
+ if (id) { this.recId = id; }
+ }
+
+ constructor(
+ private modal: NgbModal, // required for passing to parent
+ private idl: IdlService,
+ private auth: AuthService,
+ private pcrud: PcrudService) {
+ super(modal);
+ }
+
+ // Avoid fetching data on init since that may lead to unnecessary
+ // data retrieval.
+ ngOnInit() {
+ this.listifyInputs();
+ this.idlDef = this.idl.classes[this.idlClass];
+ this.recordLabel = this.idlDef.label;
+ }
+
+ // Opening dialog, fetch data.
+ open(options?: NgbModalOptions): Promise<any> {
+ return this.initRecord().then(
+ ok => super.open(options),
+ err => console.warn(`Error fetching FM data: ${err}`)
+ );
+ }
+
+ // Translate comma-separated string versions of various inputs
+ // to arrays.
+ private listifyInputs() {
+ if (this.hiddenFields) {
+ this.hiddenFieldsList = this.hiddenFields.split(/,/);
+ }
+ if (this.readonlyFields) {
+ this.readonlyFieldsList = this.readonlyFields.split(/,/);
+ }
+ if (this.requiredFields) {
+ this.requiredFieldsList = this.requiredFields.split(/,/);
+ }
+ if (this.orgDefaultAllowed) {
+ this.orgDefaultAllowedList = this.orgDefaultAllowed.split(/,/);
+ }
+ }
+
+ private initRecord(): Promise<any> {
+
+ const pc = this.idlDef.permacrud || {};
+ this.modePerms = {
+ view: pc.retrieve ? pc.retrieve.perms : [],
+ create: pc.create ? pc.create.perms : [],
+ update: pc.update ? pc.update.perms : [],
+ };
+
+ if (this.mode === 'update' || this.mode === 'view') {
+ return this.pcrud.retrieve(this.idlClass, this.recId)
+ .toPromise().then(rec => {
+
+ if (!rec) {
+ return Promise.reject(`No '${this.idlClass}'
+ record found with id ${this.recId}`);
+ }
+
+ this.record = rec;
+ this.convertDatatypesToJs();
+ return this.getFieldList();
+ });
+ }
+
+ // create a new record from scratch
+ this.pkeyIsEditable = !('pkey_sequence' in this.idlDef);
+ this.record = this.idl.create(this.idlClass);
+ return this.getFieldList();
+ }
+
+ // Modifies the FM record in place, replacing IDL-compatible values
+ // with native JS values.
+ private convertDatatypesToJs() {
+ this.idlDef.fields.forEach(field => {
+ if (field.datatype === 'bool') {
+ if (this.record[field.name]() === 't') {
+ this.record[field.name](true);
+ } else if (this.record[field.name]() === 'f') {
+ this.record[field.name](false);
+ }
+ }
+ });
+ }
+
+ // Modifies the provided FM record in place, replacing JS values
+ // with IDL-compatible values.
+ convertDatatypesToIdl(rec: IdlObject) {
+ const fields = this.idlDef.fields;
+ fields.forEach(field => {
+ if (field.datatype === 'bool') {
+ if (rec[field.name]() === true) {
+ rec[field.name]('t');
+ // } else if (rec[field.name]() === false) {
+ } else { // TODO: some bools can be NULL
+ rec[field.name]('f');
+ }
+ } else if (field.datatype === 'org_unit') {
+ const org = rec[field.name]();
+ if (org && typeof org === 'object') {
+ rec[field.name](org.id());
+ }
+ }
+ });
+ }
+
+
+ private flattenLinkedValues(cls: string, list: IdlObject[]): any[] {
+ const idField = this.idl.classes[cls].pkey;
+ const selector =
+ this.idl.classes[cls].field_map[idField].selector || idField;
+
+ return list.map(item => {
+ return {id: item[idField](), name: item[selector]()};
+ });
+ }
+
+ private getFieldList(): Promise<any> {
+
+ this.fields = this.idlDef.fields.filter(f =>
+ !f.virtual && !this.hiddenFieldsList.includes(f.name)
+ );
+
+ const promises = [];
+
+ this.fields.forEach(field => {
+ field.readOnly = this.mode === 'view'
+ || this.readonlyFieldsList.includes(field.name);
+
+ if (this.isRequiredOverride &&
+ field.name in this.isRequiredOverride) {
+ field.isRequired = () => {
+ return this.isRequiredOverride[field.name](field.name, this.record);
+ };
+ } else {
+ field.isRequired = () => {
+ return field.required ||
+ this.requiredFieldsList.includes(field.name);
+ };
+ }
+
+ if (field.datatype === 'link') {
+ promises.push(
+ this.pcrud.retrieveAll(field.class, {}, {atomic : true})
+ .toPromise().then(list => {
+ field.linkedValues =
+ this.flattenLinkedValues(field.class, list);
+ })
+ );
+ } else if (field.datatype === 'org_unit') {
+ field.orgDefaultAllowed =
+ this.orgDefaultAllowedList.includes(field.name);
+ }
+
+ if (this.customFieldTemplates[field.name]) {
+ field.template = this.customFieldTemplates[field.name].template;
+ field.context = this.customFieldTemplates[field.name].context;
+ }
+
+ });
+
+ // Wait for all network calls to complete
+ return Promise.all(promises);
+ }
+
+ // Returns a context object to be inserted into a custom
+ // field template.
+ customTemplateFieldContext(fieldDef: any): CustomFieldContext {
+ return Object.assign(
+ { record : this.record,
+ field: fieldDef // from this.fields
+ }, fieldDef.context || {}
+ );
+ }
+
+ save() {
+ const recToSave = this.idl.clone(this.record);
+ this.convertDatatypesToIdl(recToSave);
+ this.pcrud[this.mode]([recToSave]).toPromise().then(
+ result => this.close(result),
+ error => this.dismiss(error)
+ );
+ }
+
+ cancel() {
+ this.dismiss('canceled');
+ }
+}
+
+
--- /dev/null
+
+<span *ngIf="!column.cellTemplate"
+ [ngbTooltip]="tooltipContent"
+ placement="top-left"
+ class="{{context.cellClassCallback(row, column)}}"
+ triggers="mouseenter:mouseleave">
+ {{context.getRowColumnValue(row, column)}}
+</span>
+<span *ngIf="column.cellTemplate"
+ class="{{context.cellClassCallback(row, column)}}"
+ [ngbTooltip]="tooltipContent"
+ placement="top-left"
+ #tooltip="ngbTooltip"
+ (mouseenter)="tooltip.open(column.getCellContext(row))"
+ (mouseleave)="tooltip.close()" triggers="manual">
+ <ng-container #templateContainer
+ *ngTemplateOutlet="column.cellTemplate; context: column.getCellContext(row)">
+ </ng-container>
+</span>
+
--- /dev/null
+import {Component, Input, OnInit, AfterViewInit,
+ TemplateRef, ElementRef, AfterContentChecked} from '@angular/core';
+import {GridContext, GridColumn, GridRowSelector,
+ GridColumnSet, GridDataSource} from './grid';
+
+@Component({
+ selector: 'eg-grid-body-cell',
+ templateUrl: './grid-body-cell.component.html'
+})
+
+export class GridBodyCellComponent implements OnInit, AfterContentChecked {
+
+ @Input() context: GridContext;
+ @Input() row: any;
+ @Input() column: GridColumn;
+
+ initDone: boolean;
+ tooltipContent: string | TemplateRef<any>;
+
+ constructor(
+ private elm: ElementRef
+ ) {}
+
+ ngOnInit() {}
+
+ ngAfterContentChecked() {
+ this.setTooltip();
+ }
+
+ // Returns true if the contents of this cell exceed the
+ // boundaries of its container.
+ cellOverflows(): boolean {
+ let node = this.elm.nativeElement;
+ if (node) {
+ node = node.parentNode;
+ return node && (
+ node.scrollHeight > node.clientHeight ||
+ node.scrollWidth > node.clientWidth
+ );
+ }
+ return false;
+ }
+
+ // Tooltips are only applied to cells whose contents exceed
+ // their container.
+ // Applying an empty string value prevents a tooltip from rendering.
+ setTooltip() {
+ if (this.cellOverflows()) {
+ this.tooltipContent = this.column.cellTemplate ||
+ this.context.getRowColumnValue(this.row, this.column);
+ } else {
+ // No tooltip
+ this.tooltipContent = '';
+ }
+ }
+}
+
--- /dev/null
+<!--
+ tabindex=1 so the grid body can capture keyboard events.
+-->
+<div class="eg-grid-body" tabindex="1" (keydown)="onGridKeyDown($event)">
+ <div class="eg-grid-row eg-grid-body-row {{context.rowClassCallback(row)}}"
+ [ngClass]="{'selected': context.rowSelector.contains(context.getRowIndex(row))}"
+ *ngFor="let row of context.dataSource.getPageOfRows(context.pager); let idx = index">
+
+ <div class="eg-grid-cell eg-grid-checkbox-cell eg-grid-cell-skinny">
+ <input type='checkbox' [(ngModel)]="context.rowSelector.indexes[context.getRowIndex(row)]">
+ </div>
+ <div class="eg-grid-cell eg-grid-number-cell eg-grid-cell-skinny">
+ {{context.pager.rowNumber(idx)}}
+ </div>
+ <div *ngIf="context.rowFlairIsEnabled" class="eg-grid-cell eg-grid-flair-cell">
+ <!-- using *ngIf allows us to assign the flair callback to a value,
+ obviating the need for multiple calls of the same function -->
+ <ng-container *ngIf="context.rowFlairCallback(row); let flair">
+ <ng-container *ngIf="flair.icon">
+ <!-- tooltip is disabled when no title is set -->
+ <span class="material-icons"
+ ngbTooltip="{{flair.title || ''}}" triggers="mouseenter:mouseleave">
+ {{flair.icon}}
+ </span>
+ </ng-container>
+ </ng-container>
+ </div>
+ <div class="eg-grid-cell eg-grid-body-cell" [ngStyle]="{flex:col.flex}"
+ [ngClass]="{'eg-grid-cell-overflow': context.overflowCells}"
+ (dblclick)="onRowDblClick(row)"
+ (click)="onRowClick($event, row, idx)"
+ *ngFor="let col of context.columnSet.displayColumns()">
+
+ <eg-grid-body-cell [context]="context" [row]="row" [column]="col">
+ </eg-grid-body-cell>
+ </div>
+ </div>
+</div>
+
--- /dev/null
+import {Component, Input, OnInit, Host} from '@angular/core';
+import {GridContext, GridColumn, GridRowSelector,
+ GridColumnSet, GridDataSource} from './grid';
+import {GridComponent} from './grid.component';
+
+@Component({
+ selector: 'eg-grid-body',
+ templateUrl: './grid-body.component.html'
+})
+
+export class GridBodyComponent implements OnInit {
+
+ @Input() context: GridContext;
+
+ constructor(@Host() private grid: GridComponent) {}
+
+ ngOnInit() {}
+
+ // Not using @HostListener because it only works globally.
+ onGridKeyDown(evt: KeyboardEvent) {
+ switch (evt.key) {
+ case 'ArrowUp':
+ this.context.selectPreviousRow();
+ evt.stopPropagation();
+ break;
+ case 'ArrowDown':
+ this.context.selectNextRow();
+ evt.stopPropagation();
+ break;
+ case 'ArrowLeft':
+ this.context.toPrevPage()
+ .then(ok => this.context.selectFirstRow(), err => {});
+ evt.stopPropagation();
+ break;
+ case 'ArrowRight':
+ this.context.toNextPage()
+ .then(ok => this.context.selectFirstRow(), err => {});
+ evt.stopPropagation();
+ break;
+ case 'Enter':
+ if (this.context.lastSelectedIndex) {
+ this.grid.onRowActivate.emit(
+ this.context.getRowByIndex(
+ this.context.lastSelectedIndex)
+ );
+ }
+ evt.stopPropagation();
+ break;
+ }
+ }
+
+ onRowClick($event: any, row: any, idx: number) {
+ const index = this.context.getRowIndex(row);
+
+ if (this.context.disableMultiSelect) {
+ this.context.selectOneRow(index);
+ } else if ($event.ctrlKey || $event.metaKey /* mac command */) {
+ if (this.context.toggleSelectOneRow(index)) {
+ this.context.lastSelectedIndex = index;
+ }
+
+ } else if ($event.shiftKey) {
+ // TODO shift range click
+
+ } else {
+ this.context.selectOneRow(index);
+ }
+
+ this.grid.onRowClick.emit(row);
+ }
+
+ onRowDblClick(row: any) {
+ this.grid.onRowActivate.emit(row);
+ }
+
+}
+
--- /dev/null
+<ng-template #dialogContent>
+ <div class="modal-header bg-info">
+ <h4 class="modal-title" i18n>Grid Columns Configuration</h4>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close"
+ (click)="dismiss('cross_click')">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body eg-grid-column-config-dialog">
+
+ <div class="row">
+ <div class="col-lg-1 eg-grid-header-cell" i18n>Visible</div>
+ <div class="col-lg-3 eg-grid-header-cell" i18n>Column Name</div>
+ <div class="col-lg-1 eg-grid-header-cell" i18n>Move Up</div>
+ <div class="col-lg-1 eg-grid-header-cell" i18n>Move Down</div>
+ <div class="col-lg-2 eg-grid-header-cell" i18n>First Visible</div>
+ <div class="col-lg-2 eg-grid-header-cell" i18n>Last Visible</div>
+ <div class="col-lg-2 eg-grid-header-cell"
+ *ngIf="columnSet.isMultiSortable" i18n>Sort Priority</div>
+ </div>
+ <div class="row pt-1" *ngFor="let col of columnSet.columns"
+ [ngClass]="{visible : col.visible}">
+ <div class="col-lg-1" (click)="col.visible=!col.visible">
+ <span *ngIf="col.visible" class="badge badge-success">✓</span>
+ <span *ngIf="!col.visible" class="badge badge-warning">✗</span>
+ </div>
+ <div class="col-lg-3" (click)="col.visible=!col.visible">{{col.label}}</div>
+ <div class="col-lg-1">
+ <a class="no-href" title="Move column up" i18n-title
+ (click)="columnSet.moveColumn(col, -1)">
+ <span class="material-icons">arrow_upward</span>
+ </a>
+ </div>
+ <div class="col-lg-1">
+ <a class="no-href" title="Move column down" i18n-title
+ (click)="columnSet.moveColumn(col, 1)">
+ <span class="material-icons">arrow_downward</span>
+ </a>
+ </div>
+ <div class="col-lg-2">
+ <a class="no-href" title="Make first visible" i18n-title
+ (click)="columnSet.moveColumn(col, -10000)">
+ <span class="material-icons">vertical_align_top</span>
+ </a>
+ </div>
+ <div class="col-lg-2">
+ <a class="no-href" title="Make last visible" i18n-title
+ (click)="columnSet.moveColumn(col, 10000)">
+ <span class="material-icons">vertical_align_bottom</span>
+ </a>
+ </div>
+ <div class="col-lg-2" *ngIf="columnSet.isMultiSortable">
+ <div *ngIf="col.isMultiSortable">
+ <input type='number' [(ngModel)]="col.sort"
+ title="Sort Priority / Direction" i18n-title style='width:2.8em'/>
+ </div>
+ </div>
+
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button class="btn btn-info" (click)="columnSet.moveVisibleToFront()">
+ Move Visible Columns To Top
+ </button>
+ <button type="button" class="btn btn-success ml-2"
+ (click)="close('confirmed')" i18n>Close</button>
+ </div>
+</ng-template>
--- /dev/null
+import {Component, Input, OnInit} from '@angular/core';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {GridColumnSet} from './grid';
+
+@Component({
+ selector: 'eg-grid-column-config',
+ templateUrl: './grid-column-config.component.html'
+})
+
+/**
+ */
+export class GridColumnConfigComponent extends DialogComponent implements OnInit {
+ @Input() columnSet: GridColumnSet;
+}
+
+
--- /dev/null
+<div *ngIf="isVisible" class="eg-grid-column-width-config">
+ <div class="eg-grid-row">
+ <div class="eg-grid-column-width-header" i18n>Expand</div>
+ <div *ngFor="let col of columnSet.displayColumns()"
+ class="eg-grid-cell text-center" [ngStyle]="{flex:col.flex}">
+ <a (click)="expandColumn(col)" title="Expand Column" i18n-title>
+ <span class="material-icons eg-grid-column-width-icon">call_made</span>
+ </a>
+ </div>
+ </div>
+ <div class="eg-grid-row">
+ <div class="eg-grid-column-width-header" i18n>Shrink</div>
+ <div *ngFor="let col of columnSet.displayColumns()"
+ class="eg-grid-cell text-center" [ngStyle]="{flex:col.flex}">
+ <a (click)="shrinkColumn(col)" title="Shrink Column" i18n-title>
+ <span class="material-icons eg-grid-column-width-icon">call_received</span>
+ </a>
+ </div>
+ </div>
+</div>
--- /dev/null
+import {Component, Input, OnInit, Host} from '@angular/core';
+import {GridContext, GridColumn, GridColumnSet,
+ GridDataSource} from './grid';
+
+@Component({
+ selector: 'eg-grid-column-width',
+ templateUrl: './grid-column-width.component.html'
+})
+
+export class GridColumnWidthComponent implements OnInit {
+
+ @Input() gridContext: GridContext;
+ columnSet: GridColumnSet;
+ isVisible: boolean;
+
+ constructor() {}
+
+ ngOnInit() {
+ this.isVisible = false;
+ this.columnSet = this.gridContext.columnSet;
+ }
+
+ expandColumn(col: GridColumn) {
+ col.flex++;
+ }
+
+ shrinkColumn(col: GridColumn) {
+ if (col.flex > 1) { col.flex--; }
+ }
+
+}
+
--- /dev/null
+import {Component, Input, OnInit, Host, TemplateRef} from '@angular/core';
+import {GridColumn, GridColumnSet} from './grid';
+import {GridComponent} from './grid.component';
+
+@Component({
+ selector: 'eg-grid-column',
+ template: '<ng-template></ng-template>'
+})
+
+export class GridColumnComponent implements OnInit {
+
+ // Note most input fields should match class fields for GridColumn
+ @Input() name: string;
+ @Input() path: string;
+ @Input() label: string;
+ @Input() flex: number;
+ // is this the index field?
+ @Input() index: boolean;
+
+ // Columns are assumed to be visible unless hidden=true.
+ @Input() hidden: boolean;
+
+ @Input() sortable: boolean;
+ @Input() datatype: string;
+ @Input() multiSortable: boolean;
+
+ // Used in conjunction with cellTemplate
+ @Input() cellContext: any;
+ @Input() cellTemplate: TemplateRef<any>;
+
+ // get a reference to our container grid.
+ constructor(@Host() private grid: GridComponent) {}
+
+ ngOnInit() {
+
+ if (!this.grid) {
+ console.warn('GridColumnComponent needs an <eg-grid>');
+ return;
+ }
+
+ const col = new GridColumn();
+ col.name = this.name;
+ col.path = this.path;
+ col.label = this.label;
+ col.flex = this.flex;
+ col.hidden = this.hidden === true;
+ col.isIndex = this.index === true;
+ col.cellTemplate = this.cellTemplate;
+ col.cellContext = this.cellContext;
+ col.isSortable = this.sortable;
+ col.isMultiSortable = this.multiSortable;
+ col.datatype = this.datatype;
+ col.isAuto = false;
+ this.grid.context.columnSet.add(col);
+ }
+}
+
--- /dev/null
+
+<div class="eg-grid-row eg-grid-header-row">
+ <div class="eg-grid-cell eg-grid-header-cell eg-grid-checkbox-cell eg-grid-cell-skinny">
+ <input type='checkbox' (click)="handleBatchSelect($event)">
+ </div>
+ <div class="eg-grid-cell eg-grid-header-cell eg-grid-number-cell eg-grid-cell-skinny">
+ <span i18n="number|Row Number Header">#</span>
+ </div>
+ <div *ngIf="context.rowFlairIsEnabled"
+ class="eg-grid-cell eg-grid-header-cell eg-grid-flair-cell">
+ <span class="material-icons">notifications</span>
+ </div>
+ <div *ngFor="let col of context.columnSet.displayColumns()"
+ draggable="true"
+ (dragstart)="dragColumn = col"
+ (drop)="onColumnDrop(col)"
+ (dragover)="onColumnDragEnter($event, col)"
+ (dragleave)="onColumnDragLeave($event, col)"
+ [ngClass]="{'dragover' : col.isDragTarget}"
+ class="eg-grid-cell eg-grid-header-cell" [ngStyle]="{flex:col.flex}">
+ <a class="sortable label-with-material-icon" *ngIf="col.isSortable"
+ (click)="sortOneColumn(col)">
+ <span class="eg-grid-header-cell-sort-label">{{col.label}}</span>
+ <span class="material-icons eg-grid-header-cell-sort-arrow"
+ *ngIf="isColumnSorting(col, 'ASC')">arrow_downwards</span>
+ <span class="material-icons eg-grid-header-cell-sort-arrow"
+ *ngIf="isColumnSorting(col, 'DESC')">arrow_upwards</span>
+ </a>
+ <span *ngIf="!col.isSortable">{{col.label}}</span>
+ </div>
+</div>
+
--- /dev/null
+import {Component, Input, OnInit} from '@angular/core';
+import {GridContext, GridColumn, GridRowSelector,
+ GridColumnSet, GridDataSource} from './grid';
+
+@Component({
+ selector: 'eg-grid-header',
+ templateUrl: './grid-header.component.html'
+})
+
+export class GridHeaderComponent implements OnInit {
+
+ @Input() context: GridContext;
+
+ dragColumn: GridColumn;
+
+ constructor() {}
+
+ ngOnInit() {}
+
+ onColumnDragEnter($event: any, col: any) {
+ if (this.dragColumn && this.dragColumn.name !== col.name) {
+ col.isDragTarget = true;
+ }
+ $event.preventDefault();
+ }
+
+ onColumnDragLeave($event: any, col: any) {
+ col.isDragTarget = false;
+ $event.preventDefault();
+ }
+
+ onColumnDrop(col: GridColumn) {
+ this.context.columnSet.insertBefore(this.dragColumn, col);
+ this.context.columnSet.columns.forEach(c => c.isDragTarget = false);
+ }
+
+ sortOneColumn(col: GridColumn) {
+ let dir = 'ASC';
+ const sort = this.context.dataSource.sort;
+
+ if (sort.length && sort[0].name === col.name && sort[0].dir === 'ASC') {
+ dir = 'DESC';
+ }
+
+ this.context.dataSource.sort = [{name: col.name, dir: dir}];
+
+ if (this.context.useLocalSort) {
+ this.context.sortLocal();
+ } else {
+ this.context.reload();
+ }
+ }
+
+ // Returns true if the provided column is sorting in the
+ // specified direction.
+ isColumnSorting(col: GridColumn, dir: string): boolean {
+ const sort = this.context.dataSource.sort.filter(c => c.name === col.name)[0];
+ return sort && sort.dir === dir;
+ }
+
+ handleBatchSelect($event) {
+ if ($event.target.checked) {
+ if (this.context.rowSelector.isEmpty() || !this.allRowsAreSelected()) {
+ // clear selections from other pages to avoid confusion.
+ this.context.rowSelector.clear();
+ this.selectAll();
+ }
+ } else {
+ this.context.rowSelector.clear();
+ }
+ }
+
+ selectAll() {
+ const rows = this.context.dataSource.getPageOfRows(this.context.pager);
+ const indexes = rows.map(r => this.context.getRowIndex(r));
+ this.context.rowSelector.select(indexes);
+ }
+
+ allRowsAreSelected(): boolean {
+ const rows = this.context.dataSource.getPageOfRows(this.context.pager);
+ const indexes = rows.map(r => this.context.getRowIndex(r));
+ return this.context.rowSelector.contains(indexes);
+ }
+}
+
--- /dev/null
+
+
+<ng-container>
+ <eg-progress-dialog #progressDialog></eg-progress-dialog>
+ <ng-template #printTemplate let-context>
+ <div>
+ <style>
+ .grid-print-table {
+ border-collapse: collapse;
+ margin: 1px;
+ }
+ .grid-print-table td {
+ padding: 2px;
+ border: 1px solid #aaa;
+ }
+ </style>
+ <table class="grid-print-table">
+ <thead>
+ <tr><th *ngFor="let col of context.columns">{{col.label}}</th></tr>
+ </thead>
+ <tbody>
+ <tr *ngFor="let row of context.rows; trackBy: index">
+ <!-- item values have already been filtered, etc. -->
+ <td *ngFor="let col of context.columns"><span>{{row[col.name]}}</span></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </ng-template>
+</ng-container>
--- /dev/null
+import {Component, Input, TemplateRef, ViewChild} from '@angular/core';
+import {ProgressDialogComponent} from '@eg/share/dialog/progress.component';
+import {PrintService} from '@eg/share/print/print.service';
+import {GridContext} from '@eg/share/grid/grid';
+
+@Component({
+ selector: 'eg-grid-print',
+ templateUrl: './grid-print.component.html'
+})
+
+/**
+ */
+export class GridPrintComponent {
+
+ @Input() gridContext: GridContext;
+ @ViewChild('printTemplate') private printTemplate: TemplateRef<any>;
+ @ViewChild('progressDialog')
+ private progressDialog: ProgressDialogComponent;
+
+ constructor(private printer: PrintService) {}
+
+ printGrid() {
+ this.progressDialog.open();
+ const columns = this.gridContext.columnSet.displayColumns();
+ const textItems = {columns: columns, rows: []};
+
+ this.gridContext.getAllRowsAsText().subscribe(
+ row => {
+ this.progressDialog.increment();
+ textItems.rows.push(row);
+ },
+ err => this.progressDialog.close(),
+ () => {
+ this.progressDialog.close();
+ this.printer.print({
+ template: this.printTemplate,
+ contextData: textItems,
+ printContext: 'default'
+ });
+ }
+ );
+ }
+}
+
+
--- /dev/null
+import {Component, Input, OnInit, Host, TemplateRef} from '@angular/core';
+import {GridToolbarAction} from './grid';
+import {GridComponent} from './grid.component';
+
+@Component({
+ selector: 'eg-grid-toolbar-action',
+ template: '<ng-template></ng-template>'
+})
+
+export class GridToolbarActionComponent implements OnInit {
+
+ // Note most input fields should match class fields for GridColumn
+ @Input() label: string;
+ @Input() action: (rows: any[]) => any;
+
+ // get a reference to our container grid.
+ constructor(@Host() private grid: GridComponent) {}
+
+ ngOnInit() {
+
+ if (!this.grid) {
+ console.warn('GridToolbarActionComponent needs a [grid]');
+ return;
+ }
+
+ const action = new GridToolbarAction();
+ action.label = this.label;
+ action.action = this.action;
+
+ this.grid.context.toolbarActions.push(action);
+ }
+}
+
--- /dev/null
+import {Component, Input, OnInit, Host, TemplateRef} from '@angular/core';
+import {GridToolbarButton} from './grid';
+import {GridComponent} from './grid.component';
+
+@Component({
+ selector: 'eg-grid-toolbar-button',
+ template: '<ng-template></ng-template>'
+})
+
+export class GridToolbarButtonComponent implements OnInit {
+
+ // Note most input fields should match class fields for GridColumn
+ @Input() label: string;
+ @Input() action: () => any;
+
+ @Input() set disabled(d: boolean) {
+ // Support asynchronous disabled values by appling directly
+ // to our button object as values arrive.
+ if (this.button) {
+ this.button.disabled = d;
+ }
+ }
+
+ button: GridToolbarButton;
+
+ // get a reference to our container grid.
+ constructor(@Host() private grid: GridComponent) {
+ this.button = new GridToolbarButton();
+ }
+
+ ngOnInit() {
+
+ if (!this.grid) {
+ console.warn('GridToolbarButtonComponent needs a [grid]');
+ return;
+ }
+
+ this.button.label = this.label;
+ this.button.action = this.action;
+ this.grid.context.toolbarButtons.push(this.button);
+ }
+}
+
--- /dev/null
+import {Component, Input, OnInit, Host, TemplateRef} from '@angular/core';
+import {GridToolbarCheckbox} from './grid';
+import {GridComponent} from './grid.component';
+
+@Component({
+ selector: 'eg-grid-toolbar-checkbox',
+ template: '<ng-template></ng-template>'
+})
+
+export class GridToolbarCheckboxComponent implements OnInit {
+
+ // Note most input fields should match class fields for GridColumn
+ @Input() label: string;
+
+ // This is an input instead of an Output because the handler is
+ // passed off to the grid context for maintenance -- events
+ // are not fired directly from this component.
+ @Input() onChange: (checked: boolean) => void;
+
+ // get a reference to our container grid.
+ constructor(@Host() private grid: GridComponent) {}
+
+ ngOnInit() {
+
+ if (!this.grid) {
+ console.warn('GridToolbarCheckboxComponent needs a [grid]');
+ return;
+ }
+
+ const cb = new GridToolbarCheckbox();
+ cb.label = this.label;
+ cb.onChange = this.onChange;
+
+ this.grid.context.toolbarCheckboxes.push(cb);
+ }
+}
+
--- /dev/null
+
+<div class="eg-grid-toolbar mb-2">
+
+ <div class="btn-toolbar">
+
+ <!-- buttons -->
+ <div class="btn-grp" *ngIf="gridContext.toolbarButtons.length">
+ <button *ngFor="let btn of gridContext.toolbarButtons"
+ [disabled]="btn.disabled"
+ class="btn btn-outline-dark mr-1" (click)="btn.action()">
+ {{btn.label}}
+ </button>
+ </div>
+
+ <!-- checkboxes -->
+ <div class="form-check form-check-inline"
+ *ngIf="gridContext.toolbarCheckboxes.length">
+ <ng-container *ngFor="let cb of gridContext.toolbarCheckboxes">
+ <label class="form-check-label">
+ <input class="form-check-input" type="checkbox"
+ (click)="cb.onChange($event.target.checked)"/>
+ {{cb.label}}
+ </label>
+ </ng-container>
+ </div>
+ </div>
+
+ <!-- push everything else to the right -->
+ <div class="flex-1"></div>
+
+ <div ngbDropdown class="mr-1" placement="bottom-right">
+ <button ngbDropdownToggle [disabled]="!gridContext.toolbarActions.length"
+ class="btn btn-outline-dark no-dropdown-caret">
+ <span title="Actions For Selected Rows" i18n-title
+ class="material-icons mat-icon-in-button">playlist_add_check</span>
+ </button>
+ <div class="dropdown-menu" ngbDropdownMenu>
+ <a class="dropdown-item" (click)="performAction(action)"
+ *ngFor="let action of gridContext.toolbarActions">
+ <span class="ml-2">{{action.label}}</span>
+ </a>
+ </div>
+ </div>
+
+ <button [disabled]="gridContext.pager.isFirstPage()" type="button"
+ class="btn btn-outline-dark mr-1" (click)="gridContext.pager.toFirst()">
+ <span title="First Page" i18n-title
+ class="material-icons mat-icon-in-button">first_page</span>
+ </button>
+ <button [disabled]="gridContext.pager.isFirstPage()" type="button"
+ class="btn btn-outline-dark mr-1" (click)="gridContext.pager.decrement()">
+ <span title="Previous Page" i18n-title
+ class="material-icons mat-icon-in-button">keyboard_arrow_left</span>
+ </button>
+ <button [disabled]="gridContext.pager.isLastPage()" type="button"
+ class="btn btn-outline-dark mr-1" (click)="gridContext.pager.increment()">
+ <span title="Next Page" i18n-title
+ class="material-icons mat-icon-in-button">keyboard_arrow_right</span>
+ </button>
+
+ <!--
+ Hiding jump-to-last since there's no analog in the angularjs grid and
+ it has limited value since the size of the data set is often unknown.
+ <button [disabled]="!gridContext.pager.resultCount || gridContext.pager.isLastPage()"
+ type="button" class="btn btn-outline-dark mr-1" (click)="gridContext.pager.toLast()">
+ <span title="First Page" i18n-title
+ class="material-icons mat-icon-in-button">last_page</span>
+ </button>
+ -->
+
+ <div ngbDropdown class="mr-1" placement="bottom-right">
+ <button ngbDropdownToggle class="btn btn-outline-dark text-button">
+ <span title="Select Row Count" i18n-title i18n>
+ Rows {{gridContext.pager.limit}}
+ </span>
+ </button>
+ <div class="dropdown-menu" ngbDropdownMenu>
+ <a class="dropdown-item"
+ *ngFor="let count of [5, 10, 25, 50, 100]"
+ (click)="gridContext.pager.setLimit(count)">
+ <span class="ml-2">{{count}}</span>
+ </a>
+ </div>
+ </div>
+
+ <button type="button"
+ class="btn btn-outline-dark mr-1"
+ (click)="gridContext.overflowCells=!gridContext.overflowCells">
+ <span *ngIf="!gridContext.overflowCells"
+ title="Expand Cells Vertically" i18n-title
+ class="material-icons mat-icon-in-button">expand_more</span>
+ <span *ngIf="gridContext.overflowCells"
+ title="Collaps Cells Vertically" i18n-title
+ class="material-icons mat-icon-in-button">expand_less</span>
+ </button>
+
+ <eg-grid-column-config #columnConfDialog [columnSet]="gridContext.columnSet">
+ </eg-grid-column-config>
+ <div ngbDropdown placement="bottom-right">
+ <button ngbDropdownToggle class="btn btn-outline-dark no-dropdown-caret">
+ <span title="Show Grid Options" i18n-title
+ class="material-icons mat-icon-in-button">settings</span>
+ </button>
+ <div class="dropdown-menu" ngbDropdownMenu>
+ <a class="dropdown-item label-with-material-icon"
+ (click)="columnConfDialog.open({size:'lg'})">
+ <span class="material-icons">build</span>
+ <span class="ml-2" i18n>Manage Columns</span>
+ </a>
+ <a class="dropdown-item label-with-material-icon"
+ (click)="colWidthConfig.isVisible = !colWidthConfig.isVisible">
+ <span class="material-icons">compare_arrows</span>
+ <span class="ml-2" i18n>Manage Column Widths</span>
+ </a>
+ <a class="dropdown-item label-with-material-icon"
+ (click)="saveGridConfig()">
+ <span class="material-icons">save</span>
+ <span class="ml-2" i18n>Save Grid Settings</span>
+ </a>
+ <a class="dropdown-item label-with-material-icon"
+ (click)="gridContext.columnSet.reset()">
+ <span class="material-icons">restore</span>
+ <span class="ml-2" i18n>Reset Columns</span>
+ </a>
+ <a class="dropdown-item label-with-material-icon"
+ (click)="generateCsvExportUrl($event)"
+ [download]="csvExportFileName"
+ [href]="csvExportUrl">
+ <span class="material-icons">cloud_download</span>
+ <span class="ml-2" i18n>Download Full CSV</span>
+ </a>
+ <a class="dropdown-item label-with-material-icon" (click)="printHtml()">
+ <span class="material-icons">print</span>
+ <span class="ml-2" i18n>Print Full Grid</span>
+ </a>
+
+ <div class="dropdown-divider"></div>
+
+ <a class="dropdown-item label-with-material-icon"
+ (click)="col.visible=!col.visible" *ngFor="let col of gridContext.columnSet.columns">
+ <span *ngIf="col.visible" class="badge badge-success">✓</span>
+ <span *ngIf="!col.visible" class="badge badge-warning">✗</span>
+ <span class="ml-2">{{col.label}}</span>
+ </a>
+
+ </div>
+ </div>
+
+<div>
+
+
+
--- /dev/null
+import {Component, Input, OnInit, Host} from '@angular/core';
+import {DomSanitizer, SafeUrl} from '@angular/platform-browser';
+import {Pager} from '@eg/share/util/pager';
+import {GridColumn, GridColumnSet, GridToolbarButton,
+ GridToolbarAction, GridContext, GridDataSource} from '@eg/share/grid/grid';
+import {GridColumnWidthComponent} from './grid-column-width.component';
+import {GridPrintComponent} from './grid-print.component';
+
+@Component({
+ selector: 'eg-grid-toolbar',
+ templateUrl: 'grid-toolbar.component.html'
+})
+
+export class GridToolbarComponent implements OnInit {
+
+ @Input() gridContext: GridContext;
+ @Input() colWidthConfig: GridColumnWidthComponent;
+ @Input() gridPrinter: GridPrintComponent;
+
+ csvExportInProgress: boolean;
+ csvExportUrl: SafeUrl;
+ csvExportFileName: string;
+
+ constructor(private sanitizer: DomSanitizer) {}
+
+ ngOnInit() {}
+
+ saveGridConfig() {
+ // TODO: when server-side settings are supported, this operation
+ // may offer to save to user/workstation OR org unit settings
+ // depending on perms.
+
+ this.gridContext.saveGridConfig().then(
+ // hide the with config after saving
+ ok => this.colWidthConfig.isVisible = false,
+ err => console.error(`Error saving columns: ${err}`)
+ );
+ }
+
+ performAction(action: GridToolbarAction) {
+ action.action(this.gridContext.getSelectedRows());
+ }
+
+ printHtml() {
+ this.gridPrinter.printGrid();
+ }
+
+ generateCsvExportUrl($event) {
+
+ if (this.csvExportInProgress) {
+ // This is secondary href click handler. Give the
+ // browser a moment to start the download, then reset
+ // the CSV download attributes / state.
+ setTimeout(() => {
+ this.csvExportUrl = null;
+ this.csvExportFileName = '';
+ this.csvExportInProgress = false;
+ }, 500
+ );
+ return;
+ }
+
+ this.csvExportInProgress = true;
+
+ // let the file name describe the grid
+ this.csvExportFileName = (
+ this.gridContext.persistKey || 'eg_grid_data'
+ ).replace(/\s+/g, '_') + '.csv';
+
+ this.gridContext.gridToCsv().then(csv => {
+ const blob = new Blob([csv], {type : 'text/plain'});
+ const win: any = window; // avoid TS errors
+ this.csvExportUrl = this.sanitizer.bypassSecurityTrustUrl(
+ (win.URL || win.webkitURL).createObjectURL(blob)
+ );
+
+ // Fire the 2nd click event now that the browser has
+ // information on how to download the CSV file.
+ setTimeout(() => $event.target.click());
+ });
+
+ $event.preventDefault();
+ }
+}
+
+
--- /dev/null
+
+.eg-grid {
+ width: 100%;
+ color: rgba(0,0,0,.87);
+}
+
+.eg-grid-row {
+ display: flex;
+ border-bottom: 1px solid rgba(0,0,0,.12);
+ padding-left: 10px;
+ padding-right: 10px;
+}
+
+.eg-grid-header-row {
+ /* matches bootstrap card-header css */
+ background-color: rgba(0,0,0,.03);
+ border-bottom: 1px solid rgba(0,0,0,.125);
+}
+
+.eg-grid-body {
+ outline: none; /* for keyboard events */
+}
+
+.eg-grid-body-row {
+}
+
+.eg-grid-body-row.selected,
+.eg-grid-column-config-dialog .visible {
+ color: #004085;
+ background-color: #cce5ff;
+ border-color: #b8daff;
+}
+
+.eg-grid-header-cell {
+ font-weight: bold;
+}
+
+.eg-grid-header-cell.dragover {
+ background-color: #cce5ff;
+ border-color: #b8daff;
+}
+
+.eg-grid-header-cell-sort-label {
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.eg-grid-header-cell-sort-arrow {
+ font-size: 14px;
+}
+
+.eg-grid-cell {
+ flex: 2; /* applied per column */
+ padding: 6px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+/* allow tooltips to be wider than the default 200px */
+.eg-grid-cell .tooltip-inner {
+ max-width: 400px;
+}
+
+/* in overflow mode, allow white space to wrap so the
+ * full contents of the cell can be seen inline. leaving
+ * text-overflow and overlow as-is means long strings with
+ * no space will still be truncated with ellipses to avoid
+ * inconsistent grid column widths
+ */
+.eg-grid-cell-overflow {
+ white-space: normal;
+}
+
+.eg-grid-body-cell {
+}
+
+.eg-grid-toolbar {
+ display: flex;
+}
+
+.eg-grid-toolbar .material-icons {
+ font-size: 20px;
+}
+
+.eg-grid-toolbar .form-check-label:nth-child(even) {
+ padding-left: 5px;
+ padding-right: 5px;
+ margin-left: 3px;
+ margin-right: 3px;
+ border-radius: 5px;
+ background-color: rgba(0,0,0,.03);
+ border: 1px solid rgba(0,0,0,.125);
+}
+
+/* Kind of hacky -- only way to get a toolbar button with no
+ * mat icon to line up horizontally with mat icon buttons */
+.eg-grid-toolbar .text-button {
+ padding-top: 11px;
+ padding-bottom: 11px;
+}
+
+.eg-grid-cell-skinny {
+ width: 2.2em;
+ text-align: center;
+ flex: none;
+}
+
+.eg-grid-flair-cell {
+ /* mat icons currently 22px, unclear why it needs this much space */
+ width: 34px;
+ text-align: center;
+ flex: none;
+}
+
+/* depends on width of .eg-grid-cell-skinny */
+.eg-grid-column-width-header {
+ width: 4.4em;
+ text-align: center;
+ flex: none;
+ display: inline-flex;
+ vertical-align: middle;
+ align-items: center;
+}
+
+.eg-grid-column-width-config .eg-grid-cell {
+ border-left: 2px dashed grey;
+}
+
+.eg-grid-column-width-icon {
+ cursor: pointer;
+ font-size: 18px;
+ color: #007bff;
+}
+
+.eg-grid-column-config-dialog {
+ height: auto;
+ max-height: 400px;
+ overflow: auto;
+ box-shadow: none;
+}
+
--- /dev/null
+
+<div class="eg-grid">
+
+ <eg-grid-toolbar
+ [gridContext]="context"
+ [gridPrinter]="gridPrinter"
+ [colWidthConfig]="colWidthConfig">
+ </eg-grid-toolbar>
+
+ <eg-grid-header [context]="context"></eg-grid-header>
+
+ <eg-grid-column-width #colWidthConfig [gridContext]="context">
+ </eg-grid-column-width>
+
+ <eg-grid-print #gridPrinter [gridContext]="context">
+ </eg-grid-print>
+
+ <!-- move me too -->
+ <div class="row" *ngIf="dataSource.data.length == 0">
+ <div class="col-lg-12 text-center alert alert-light font-italic" i18n>
+ Nothing to Display
+ </div>
+ </div>
+
+ <eg-grid-body [context]="context"></eg-grid-body>
+</div>
+
--- /dev/null
+import {Component, Input, Output, OnInit, AfterViewInit, EventEmitter,
+ OnDestroy, HostListener, ViewEncapsulation} from '@angular/core';
+import {Subscription} from 'rxjs/Subscription';
+import {IdlService} from '@eg/core/idl.service';
+import {OrgService} from '@eg/core/org.service';
+import {ServerStoreService} from '@eg/core/server-store.service';
+import {FormatService} from '@eg/core/format.service';
+import {GridContext, GridColumn, GridDataSource, GridRowFlairEntry} from './grid';
+
+/**
+ * Main grid entry point.
+ */
+
+@Component({
+ selector: 'eg-grid',
+ templateUrl: './grid.component.html',
+ styleUrls: ['grid.component.css'],
+ // share grid css globally once imported so all grid component CSS
+ // can live in grid.component.css and to avoid multiple copies of
+ // the CSS when multiple grids are displayed.
+ encapsulation: ViewEncapsulation.None
+})
+
+export class GridComponent implements OnInit, AfterViewInit, OnDestroy {
+
+ // Source of row data.
+ @Input() dataSource: GridDataSource;
+
+ // IDL class for auto-generation of columns
+ @Input() idlClass: string;
+
+ // True if any columns are sortable
+ @Input() sortable: boolean;
+
+ // True if the grid supports sorting of multiple columns at once
+ @Input() multiSortable: boolean;
+
+ // If true, grid sort requests only operate on data that
+ // already exists in the grid data source -- no row fetching.
+ // The assumption is all data is already available.
+ @Input() useLocalSort: boolean;
+
+ // Storage persist key / per-grid-type unique identifier
+ // The value is prefixed with 'eg.grid.'
+ @Input() persistKey: string;
+
+ // Prevent selection of multiple rows
+ @Input() disableMultiSelect: boolean;
+
+ // Show an extra column in the grid where the caller can apply
+ // row-specific flair (material icons).
+ @Input() rowFlairIsEnabled: boolean;
+
+ // Returns a material icon name to display in the flar column
+ // (if enabled) for the given row.
+ @Input() rowFlairCallback: (row: any) => GridRowFlairEntry;
+
+ // Returns a space-separated list of CSS class names to apply to
+ // a given row
+ @Input() rowClassCallback: (row: any) => string;
+
+ // Returns a space-separated list of CSS class names to apply to
+ // a given cell or all cells in a column.
+ @Input() cellClassCallback: (row: any, col: GridColumn) => string;
+
+ // comma-separated list of fields to show by default.
+ // This field takes precedence over hideFields.
+ // When a value is applied, any field not in this list will
+ // be hidden.
+ @Input() showFields: string;
+
+ // comma-separated list of fields to hide.
+ // This does not imply all other fields should be visible, only that
+ // the selected fields will be hidden.
+ @Input() hideFields: string;
+
+ // Allow the caller to jump directly to a specific page of
+ // grid data.
+ @Input() pageOffset: number;
+
+ context: GridContext;
+
+ // These events are emitted from our grid-body component.
+ // They are defined here for ease of access to the caller.
+ @Output() onRowActivate: EventEmitter<any>;
+ @Output() onRowClick: EventEmitter<any>;
+
+ constructor(
+ private idl: IdlService,
+ private org: OrgService,
+ private store: ServerStoreService,
+ private format: FormatService
+ ) {
+ this.context =
+ new GridContext(this.idl, this.org, this.store, this.format);
+ this.onRowActivate = new EventEmitter<any>();
+ this.onRowClick = new EventEmitter<any>();
+ }
+
+ ngOnInit() {
+
+ if (!this.dataSource) {
+ throw new Error('<eg-grid/> requires a [dataSource]');
+ }
+
+ this.context.idlClass = this.idlClass;
+ this.context.dataSource = this.dataSource;
+ this.context.persistKey = this.persistKey;
+ this.context.isSortable = this.sortable === true;
+ this.context.isMultiSortable = this.multiSortable === true;
+ this.context.useLocalSort = this.useLocalSort === true;
+ this.context.disableMultiSelect = this.disableMultiSelect === true;
+ this.context.rowFlairIsEnabled = this.rowFlairIsEnabled === true;
+ this.context.rowFlairCallback = this.rowFlairCallback;
+ if (this.showFields) {
+ this.context.defaultVisibleFields = this.showFields.split(',');
+ }
+ if (this.hideFields) {
+ this.context.defaultHiddenFields = this.hideFields.split(',');
+ }
+
+ if (this.pageOffset) {
+ this.context.pager.offset = this.pageOffset;
+ }
+
+ // TS doesn't seem to like: let foo = bar || () => '';
+ this.context.rowClassCallback =
+ this.rowClassCallback || function () { return ''; };
+ this.context.cellClassCallback =
+ this.cellClassCallback || function() { return ''; };
+
+ this.context.init();
+ }
+
+ ngAfterViewInit() {
+ this.context.initData();
+ }
+
+ ngOnDestroy() {
+ this.context.destroy();
+ }
+
+ reload() {
+ this.context.reload();
+ }
+}
+
+
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {EgCommonModule} from '@eg/common.module';
+import {GridComponent} from './grid.component';
+import {GridColumnComponent} from './grid-column.component';
+import {GridHeaderComponent} from './grid-header.component';
+import {GridBodyComponent} from './grid-body.component';
+import {GridBodyCellComponent} from './grid-body-cell.component';
+import {GridToolbarComponent} from './grid-toolbar.component';
+import {GridToolbarButtonComponent} from './grid-toolbar-button.component';
+import {GridToolbarCheckboxComponent} from './grid-toolbar-checkbox.component';
+import {GridToolbarActionComponent} from './grid-toolbar-action.component';
+import {GridColumnConfigComponent} from './grid-column-config.component';
+import {GridColumnWidthComponent} from './grid-column-width.component';
+import {GridPrintComponent} from './grid-print.component';
+
+
+@NgModule({
+ declarations: [
+ // public + internal components
+ GridComponent,
+ GridColumnComponent,
+ GridHeaderComponent,
+ GridBodyComponent,
+ GridBodyCellComponent,
+ GridToolbarComponent,
+ GridToolbarButtonComponent,
+ GridToolbarCheckboxComponent,
+ GridToolbarActionComponent,
+ GridColumnConfigComponent,
+ GridColumnWidthComponent,
+ GridPrintComponent
+ ],
+ imports: [
+ EgCommonModule
+ ],
+ exports: [
+ // public components
+ GridComponent,
+ GridColumnComponent,
+ GridToolbarButtonComponent,
+ GridToolbarCheckboxComponent,
+ GridToolbarActionComponent
+ ],
+ providers: [
+ ]
+})
+
+export class GridModule {
+
+}
--- /dev/null
+/**
+ * Collection of grid related classses and interfaces.
+ */
+import {TemplateRef} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {Subscription} from 'rxjs/Subscription';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {OrgService} from '@eg/core/org.service';
+import {ServerStoreService} from '@eg/core/server-store.service';
+import {FormatService} from '@eg/core/format.service';
+import {Pager} from '@eg/share/util/pager';
+
+const MAX_ALL_ROW_COUNT = 10000;
+
+export class GridColumn {
+ name: string;
+ path: string;
+ label: string;
+ flex: number;
+ align: string;
+ hidden: boolean;
+ visible: boolean;
+ sort: number;
+ idlClass: string;
+ idlFieldDef: any;
+ datatype: string;
+ cellTemplate: TemplateRef<any>;
+ cellContext: any;
+ isIndex: boolean;
+ isDragTarget: boolean;
+ isSortable: boolean;
+ isMultiSortable: boolean;
+ comparator: (valueA: any, valueB: any) => number;
+
+ // True if the column was automatically generated.
+ isAuto: boolean;
+
+ flesher: (obj: any, col: GridColumn, item: any) => any;
+
+ getCellContext(row: any) {
+ return {
+ col: this,
+ row: row,
+ userContext: this.cellContext
+ };
+ }
+}
+
+export class GridColumnSet {
+ columns: GridColumn[];
+ idlClass: string;
+ indexColumn: GridColumn;
+ isSortable: boolean;
+ isMultiSortable: boolean;
+ stockVisible: string[];
+ idl: IdlService;
+ defaultHiddenFields: string[];
+ defaultVisibleFields: string[];
+
+ constructor(idl: IdlService, idlClass?: string) {
+ this.idl = idl;
+ this.columns = [];
+ this.stockVisible = [];
+ this.idlClass = idlClass;
+ }
+
+ add(col: GridColumn) {
+
+ this.applyColumnDefaults(col);
+
+ if (!this.insertColumn(col)) {
+ // Column was rejected as a duplicate.
+ return;
+ }
+
+ if (col.isIndex) { this.indexColumn = col; }
+
+ // track which fields are visible on page load.
+ if (col.visible) {
+ this.stockVisible.push(col.name);
+ }
+
+ this.applyColumnSortability(col);
+ }
+
+ // Returns true if the new column was inserted, false otherwise.
+ // Declared columns take precedence over auto-generated columns
+ // when collisions occur.
+ // Declared columns are inserted in front of auto columns.
+ insertColumn(col: GridColumn): boolean {
+
+ if (col.isAuto) {
+ if (this.getColByName(col.name)) {
+ // New auto-generated column conflicts with existing
+ // column. Skip it.
+ return false;
+ } else {
+ // No collisions. Add to the end of the list
+ this.columns.push(col);
+ return true;
+ }
+ }
+
+ // Adding a declared column.
+
+ // Check for dupes.
+ for (let idx = 0; idx < this.columns.length; idx++) {
+ const testCol = this.columns[idx];
+ if (testCol.name === col.name) { // match found
+ if (testCol.isAuto) {
+ // new column takes precedence, remove the existing column.
+ this.columns.splice(idx, 1);
+ break;
+ } else {
+ // New column does not take precedence. Avoid
+ // inserting it.
+ return false;
+ }
+ }
+ }
+
+ // Delcared columns are inserted just before the first auto-column
+ for (let idx = 0; idx < this.columns.length; idx++) {
+ const testCol = this.columns[idx];
+ if (testCol.isAuto) {
+ if (idx === 0) {
+ this.columns.unshift(col);
+ } else {
+ this.columns.splice(idx - 1, 0, col);
+ }
+ return true;
+ }
+ }
+
+ // No insertion point found. Toss the new column on the end.
+ this.columns.push(col);
+ return true;
+ }
+
+ getColByName(name: string): GridColumn {
+ return this.columns.filter(c => c.name === name)[0];
+ }
+
+ idlInfoFromDotpath(dotpath: string): any {
+ if (!dotpath || !this.idlClass) { return null; }
+
+ let idlParent;
+ let idlField;
+ let idlClass = this.idl.classes[this.idlClass];
+
+ const pathParts = dotpath.split(/\./);
+
+ for (let i = 0; i < pathParts.length; i++) {
+ const part = pathParts[i];
+ idlParent = idlField;
+ idlField = idlClass.field_map[part];
+
+ if (idlField) {
+ if (idlField['class'] && (
+ idlField.datatype === 'link' ||
+ idlField.datatype === 'org_unit')) {
+ idlClass = this.idl.classes[idlField['class']];
+ }
+ } else {
+ return null;
+ }
+ }
+
+ return {
+ idlParent: idlParent,
+ idlField : idlField,
+ idlClass : idlClass
+ };
+ }
+
+
+ reset() {
+ this.columns.forEach(col => {
+ col.flex = 2;
+ col.sort = 0;
+ col.align = 'left';
+ col.visible = this.stockVisible.includes(col.name);
+ });
+ }
+
+ applyColumnDefaults(col: GridColumn) {
+
+ if (!col.idlFieldDef && col.path) {
+ const idlInfo = this.idlInfoFromDotpath(col.path);
+ if (idlInfo) {
+ col.idlFieldDef = idlInfo.idlField;
+ if (!col.label) {
+ col.label = col.idlFieldDef.label || col.idlFieldDef.name;
+ col.datatype = col.idlFieldDef.datatype;
+ }
+ }
+ }
+
+ if (!col.name) { col.name = col.path; }
+ if (!col.flex) { col.flex = 2; }
+ if (!col.align) { col.align = 'left'; }
+ if (!col.label) { col.label = col.name; }
+ if (!col.datatype) { col.datatype = 'text'; }
+
+ col.visible = !col.hidden;
+ }
+
+ applyColumnSortability(col: GridColumn) {
+ // column sortability defaults to the sortability of the column set.
+ if (col.isSortable === undefined && this.isSortable) {
+ col.isSortable = true;
+ }
+
+ if (col.isMultiSortable === undefined && this.isMultiSortable) {
+ col.isMultiSortable = true;
+ }
+
+ if (col.isMultiSortable) {
+ col.isSortable = true;
+ }
+ }
+
+ displayColumns(): GridColumn[] {
+ return this.columns.filter(c => c.visible);
+ }
+
+ insertBefore(source: GridColumn, target: GridColumn) {
+ let targetIdx = -1;
+ let sourceIdx = -1;
+ this.columns.forEach((col, idx) => {
+ if (col.name === target.name) { targetIdx = idx; }});
+
+ this.columns.forEach((col, idx) => {
+ if (col.name === source.name) { sourceIdx = idx; }});
+
+ if (sourceIdx >= 0) {
+ this.columns.splice(sourceIdx, 1);
+ }
+
+ this.columns.splice(targetIdx, 0, source);
+ }
+
+ // Move visible columns to the front of the list.
+ moveVisibleToFront() {
+ const newCols = this.displayColumns();
+ this.columns.forEach(col => {
+ if (!col.visible) { newCols.push(col); }});
+ this.columns = newCols;
+ }
+
+ moveColumn(col: GridColumn, diff: number) {
+ let srcIdx, targetIdx;
+
+ this.columns.forEach((c, i) => {
+ if (c.name === col.name) { srcIdx = i; }
+ });
+
+ targetIdx = srcIdx + diff;
+ if (targetIdx < 0) {
+ targetIdx = 0;
+ } else if (targetIdx >= this.columns.length) {
+ // Target index follows the last visible column.
+ let lastVisible = 0;
+ this.columns.forEach((c, idx) => {
+ if (c.visible) { lastVisible = idx; }
+ });
+
+ // When moving a column (down) causes one or more
+ // visible columns to shuffle forward, our column
+ // moves into the slot of the last visible column.
+ // Otherwise, put it into the slot directly following
+ // the last visible column.
+ targetIdx = srcIdx <= lastVisible ? lastVisible : lastVisible + 1;
+ }
+
+ // Splice column out of old position, insert at new position.
+ this.columns.splice(srcIdx, 1);
+ this.columns.splice(targetIdx, 0, col);
+ }
+
+ compileSaveObject(): GridColumnPersistConf[] {
+ // only store information about visible columns.
+ // scrunch the data down to just the needed info.
+ return this.displayColumns().map(col => {
+ const c: GridColumnPersistConf = {name : col.name};
+ if (col.align !== 'left') { c.align = col.align; }
+ if (col.flex !== 2) { c.flex = Number(col.flex); }
+ if (Number(col.sort)) { c.sort = Number(c.sort); }
+ return c;
+ });
+ }
+
+ applyColumnSettings(conf: GridColumnPersistConf[]) {
+
+ if (!conf || conf.length === 0) {
+ // No configuration is available, but we have a list of
+ // fields to show or hide by default
+
+ if (this.defaultVisibleFields) {
+ this.columns.forEach(col => {
+ if (this.defaultVisibleFields.includes(col.name)) {
+ col.visible = true;
+ } else {
+ col.visible = false;
+ }
+ });
+
+ } else if (this.defaultHiddenFields) {
+ this.defaultHiddenFields.forEach(name => {
+ const col = this.getColByName(name);
+ if (col) {
+ col.visible = false;
+ }
+ });
+ }
+
+ return;
+ }
+
+ const newCols = [];
+
+ conf.forEach(colConf => {
+ const col = this.getColByName(colConf.name);
+ if (!col) { return; } // no such column in this grid.
+
+ col.visible = true;
+ if (colConf.align) { col.align = colConf.align; }
+ if (colConf.flex) { col.flex = Number(colConf.flex); }
+ if (colConf.sort) { col.sort = Number(colConf.sort); }
+
+ // Add to new columns array, avoid dupes.
+ if (newCols.filter(c => c.name === col.name).length === 0) {
+ newCols.push(col);
+ }
+ });
+
+ // columns which are not expressed within the saved
+ // configuration are marked as non-visible and
+ // appended to the end of the new list of columns.
+ this.columns.forEach(c => {
+ if (conf.filter(cf => cf.name === c.name).length === 0) {
+ c.visible = false;
+ newCols.push(c);
+ }
+ });
+
+ this.columns = newCols;
+ }
+}
+
+
+export class GridRowSelector {
+ indexes: {[string: string]: boolean};
+
+ constructor() {
+ this.clear();
+ }
+
+ // Returns true if all of the requested indexes exist in the selector.
+ contains(index: string | string[]): boolean {
+ const indexes = [].concat(index);
+ for (let i = 0; i < indexes.length; i++) { // early exit
+ if (!this.indexes[indexes[i]]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ select(index: string | string[]) {
+ const indexes = [].concat(index);
+ indexes.forEach(i => this.indexes[i] = true);
+ }
+
+ deselect(index: string | string[]) {
+ const indexes = [].concat(index);
+ indexes.forEach(i => delete this.indexes[i]);
+ }
+
+ // Returns the list of selected index values.
+ // in some contexts (template checkboxes) the value for an index is
+ // set to false to deselect instead of having it removed (via deselect()).
+ selected() {
+ return Object.keys(this.indexes).filter(
+ ind => Boolean(this.indexes[ind]));
+ }
+
+ isEmpty(): boolean {
+ return this.selected().length === 0;
+ }
+
+ clear() {
+ this.indexes = {};
+ }
+}
+
+export interface GridRowFlairEntry {
+ icon: string; // name of material icon
+ title: string; // tooltip string
+}
+
+export class GridColumnPersistConf {
+ name: string;
+ flex?: number;
+ sort?: number;
+ align?: string;
+}
+
+export class GridPersistConf {
+ version: number;
+ limit: number;
+ columns: GridColumnPersistConf[];
+}
+
+export class GridContext {
+
+ pager: Pager;
+ idlClass: string;
+ isSortable: boolean;
+ isMultiSortable: boolean;
+ useLocalSort: boolean;
+ persistKey: string;
+ disableMultiSelect: boolean;
+ dataSource: GridDataSource;
+ columnSet: GridColumnSet;
+ rowSelector: GridRowSelector;
+ toolbarButtons: GridToolbarButton[];
+ toolbarCheckboxes: GridToolbarCheckbox[];
+ toolbarActions: GridToolbarAction[];
+ lastSelectedIndex: any;
+ pageChanges: Subscription;
+ rowFlairIsEnabled: boolean;
+ rowFlairCallback: (row: any) => GridRowFlairEntry;
+ rowClassCallback: (row: any) => string;
+ cellClassCallback: (row: any, col: GridColumn) => string;
+ defaultVisibleFields: string[];
+ defaultHiddenFields: string[];
+ overflowCells: boolean;
+
+ // Services injected by our grid component
+ idl: IdlService;
+ org: OrgService;
+ store: ServerStoreService;
+ format: FormatService;
+
+ constructor(
+ idl: IdlService,
+ org: OrgService,
+ store: ServerStoreService,
+ format: FormatService) {
+
+ this.idl = idl;
+ this.org = org;
+ this.store = store;
+ this.format = format;
+ this.pager = new Pager();
+ this.pager.limit = 10;
+ this.rowSelector = new GridRowSelector();
+ this.toolbarButtons = [];
+ this.toolbarCheckboxes = [];
+ this.toolbarActions = [];
+ }
+
+ init() {
+ this.columnSet = new GridColumnSet(this.idl, this.idlClass);
+ this.columnSet.isSortable = this.isSortable === true;
+ this.columnSet.isMultiSortable = this.isMultiSortable === true;
+ this.columnSet.defaultHiddenFields = this.defaultHiddenFields;
+ this.columnSet.defaultVisibleFields = this.defaultVisibleFields;
+ this.generateColumns();
+ }
+
+ // Load initial settings and data.
+ initData() {
+ this.applyGridConfig()
+ .then(ok => this.dataSource.requestPage(this.pager))
+ .then(ok => this.listenToPager());
+ }
+
+ destroy() {
+ this.ignorePager();
+ }
+
+ applyGridConfig(): Promise<void> {
+ return this.getGridConfig(this.persistKey)
+ .then(conf => {
+ let columns = [];
+ if (conf) {
+ columns = conf.columns;
+ if (conf.limit) {
+ this.pager.limit = conf.limit;
+ }
+ }
+
+ // This is called regardless of the presence of saved
+ // settings so defaults can be applied.
+ this.columnSet.applyColumnSettings(columns);
+ });
+ }
+
+ reload() {
+ // Give the UI time to settle before reloading grid data.
+ // This can help when data retrieval depends on a value
+ // getting modified by an angular digest cycle.
+ setTimeout(() => {
+ this.pager.reset();
+ this.dataSource.reset();
+ this.dataSource.requestPage(this.pager);
+ });
+ }
+
+ // Sort the existing data source instead of requesting sorted
+ // data from the client. Reset pager to page 1. As with reload(),
+ // give the client a chance to setting before redisplaying.
+ sortLocal() {
+ setTimeout(() => {
+ this.pager.reset();
+ this.sortLocalData();
+ this.dataSource.requestPage(this.pager);
+ });
+ }
+
+ // Subscribe or unsubscribe to page-change events from the pager.
+ listenToPager() {
+ if (this.pageChanges) { return; }
+ this.pageChanges = this.pager.onChange$.subscribe(
+ val => this.dataSource.requestPage(this.pager));
+ }
+
+ ignorePager() {
+ if (!this.pageChanges) { return; }
+ this.pageChanges.unsubscribe();
+ this.pageChanges = null;
+ }
+
+ // Sort data in the data source array
+ sortLocalData() {
+
+ const sortDefs = this.dataSource.sort.map(sort => {
+ const def = {
+ name: sort.name,
+ dir: sort.dir,
+ col: this.columnSet.getColByName(sort.name)
+ };
+
+ if (!def.col.comparator) {
+ def.col.comparator = (a, b) => {
+ if (a < b) { return -1; }
+ if (a > b) { return 1; }
+ return 0;
+ };
+ }
+
+ return def;
+ });
+
+ this.dataSource.data.sort((rowA, rowB) => {
+
+ for (let idx = 0; idx < sortDefs.length; idx++) {
+ const sortDef = sortDefs[idx];
+
+ const valueA = this.getRowColumnValue(rowA, sortDef.col);
+ const valueB = this.getRowColumnValue(rowB, sortDef.col);
+
+ if (valueA === '' && valueB === '') { continue; }
+ if (valueA === '' && valueB !== '') { return 1; }
+ if (valueA !== '' && valueB === '') { return -1; }
+
+ const diff = sortDef.col.comparator(valueA, valueB);
+ if (diff === 0) { continue; }
+
+ console.log(valueA, valueB, diff);
+
+ return sortDef.dir === 'DESC' ? -diff : diff;
+ }
+
+ return 0; // No differences found.
+ });
+ }
+
+ getRowIndex(row: any): any {
+ const col = this.columnSet.indexColumn;
+ if (!col) {
+ throw new Error('grid index column required');
+ }
+ return this.getRowColumnValue(row, col);
+ }
+
+ // Returns position in the data source array of the row with
+ // the provided index.
+ getRowPosition(index: any): number {
+ // for-loop for early exit
+ for (let idx = 0; idx < this.dataSource.data.length; idx++) {
+ const row = this.dataSource.data[idx];
+ if (row !== undefined && index === this.getRowIndex(row)) {
+ return idx;
+ }
+ }
+ }
+
+ // Return the row with the provided index.
+ getRowByIndex(index: any): any {
+ for (let idx = 0; idx < this.dataSource.data.length; idx++) {
+ const row = this.dataSource.data[idx];
+ if (row !== undefined && index === this.getRowIndex(row)) {
+ return row;
+ }
+ }
+ }
+
+ // Returns all selected rows, regardless of whether they are
+ // currently visible in the grid display.
+ getSelectedRows(): any[] {
+ const selected = [];
+ this.rowSelector.selected().forEach(index => {
+ const row = this.getRowByIndex(index);
+ if (row) {
+ selected.push(row);
+ }
+ });
+ return selected;
+ }
+
+ getRowColumnValue(row: any, col: GridColumn): string {
+ let val;
+ if (col.name in row) {
+ val = this.getObjectFieldValue(row, col.name);
+ } else {
+ if (col.path) {
+ val = this.nestedItemFieldValue(row, col);
+ }
+ }
+ return this.format.transform({value: val, datatype: col.datatype});
+ }
+
+ getObjectFieldValue(obj: any, name: string): any {
+ if (typeof obj[name] === 'function') {
+ return obj[name]();
+ } else {
+ return obj[name];
+ }
+ }
+
+ nestedItemFieldValue(obj: any, col: GridColumn): string {
+
+ let idlField;
+ let idlClassDef;
+ const original = obj;
+ const steps = col.path.split('.');
+
+ for (let i = 0; i < steps.length; i++) {
+ const step = steps[i];
+
+ if (typeof obj !== 'object') {
+ // We have run out of data to step through before
+ // reaching the end of the path. Conclude fleshing via
+ // callback if provided then exit.
+ if (col.flesher && obj !== undefined) {
+ return col.flesher(obj, col, original);
+ }
+ return obj;
+ }
+
+ const class_ = obj.classname;
+ if (class_ && (idlClassDef = this.idl.classes[class_])) {
+ idlField = idlClassDef.field_map[step];
+ }
+
+ obj = this.getObjectFieldValue(obj, step);
+ }
+
+ // We found a nested IDL object which may or may not have
+ // been configured as a top-level column. Flesh the column
+ // metadata with our newly found IDL info.
+ if (idlField) {
+ if (!col.datatype) {
+ col.datatype = idlField.datatype;
+ }
+ if (!col.label) {
+ col.label = idlField.label || idlField.name;
+ }
+ }
+
+ return obj;
+ }
+
+
+ getColumnTextContent(row: any, col: GridColumn): string {
+ if (col.cellTemplate) {
+ // TODO
+ // Extract the text content from the rendered template.
+ } else {
+ return this.getRowColumnValue(row, col);
+ }
+ }
+
+ selectOneRow(index: any) {
+ this.rowSelector.clear();
+ this.rowSelector.select(index);
+ this.lastSelectedIndex = index;
+ }
+
+ // selects or deselects an item, without affecting the others.
+ // returns true if the item is selected; false if de-selected.
+ toggleSelectOneRow(index: any) {
+ if (this.rowSelector.contains(index)) {
+ this.rowSelector.deselect(index);
+ return false;
+ }
+
+ this.rowSelector.select(index);
+ return true;
+ }
+
+ selectRowByPos(pos: number) {
+ const row = this.dataSource.data[pos];
+ if (row) {
+ this.selectOneRow(this.getRowIndex(row));
+ }
+ }
+
+ selectPreviousRow() {
+ if (!this.lastSelectedIndex) { return; }
+ const pos = this.getRowPosition(this.lastSelectedIndex);
+ if (pos === this.pager.offset) {
+ this.toPrevPage().then(ok => this.selectLastRow(), err => {});
+ } else {
+ this.selectRowByPos(pos - 1);
+ }
+ }
+
+ selectNextRow() {
+ if (!this.lastSelectedIndex) { return; }
+ const pos = this.getRowPosition(this.lastSelectedIndex);
+ if (pos === (this.pager.offset + this.pager.limit - 1)) {
+ this.toNextPage().then(ok => this.selectFirstRow(), err => {});
+ } else {
+ this.selectRowByPos(pos + 1);
+ }
+ }
+
+ selectFirstRow() {
+ this.selectRowByPos(this.pager.offset);
+ }
+
+ selectLastRow() {
+ this.selectRowByPos(this.pager.offset + this.pager.limit - 1);
+ }
+
+ toPrevPage(): Promise<any> {
+ if (this.pager.isFirstPage()) {
+ return Promise.reject('on first');
+ }
+ // temp ignore pager events since we're calling requestPage manually.
+ this.ignorePager();
+ this.pager.decrement();
+ this.listenToPager();
+ return this.dataSource.requestPage(this.pager);
+ }
+
+ toNextPage(): Promise<any> {
+ if (this.pager.isLastPage()) {
+ return Promise.reject('on last');
+ }
+ // temp ignore pager events since we're calling requestPage manually.
+ this.ignorePager();
+ this.pager.increment();
+ this.listenToPager();
+ return this.dataSource.requestPage(this.pager);
+ }
+
+ getAllRows(): Promise<any> {
+ const pager = new Pager();
+ pager.offset = 0;
+ pager.limit = MAX_ALL_ROW_COUNT;
+ return this.dataSource.requestPage(pager);
+ }
+
+ // Returns a key/value pair object of visible column data as text.
+ getRowAsFlatText(row: any): any {
+ const flatRow = {};
+ this.columnSet.displayColumns().forEach(col => {
+ flatRow[col.name] =
+ this.getColumnTextContent(row, col);
+ });
+ return flatRow;
+ }
+
+ getAllRowsAsText(): Observable<any> {
+ return Observable.create(observer => {
+ this.getAllRows().then(ok => {
+ this.dataSource.data.forEach(row => {
+ observer.next(this.getRowAsFlatText(row));
+ });
+ observer.complete();
+ });
+ });
+ }
+
+ gridToCsv(): Promise<string> {
+
+ let csvStr = '';
+ const columns = this.columnSet.displayColumns();
+
+ // CSV header
+ columns.forEach(col => {
+ csvStr += this.valueToCsv(col.label),
+ csvStr += ',';
+ });
+
+ csvStr = csvStr.replace(/,$/, '\n');
+
+ return new Promise(resolve => {
+ this.getAllRowsAsText().subscribe(
+ row => {
+ columns.forEach(col => {
+ csvStr += this.valueToCsv(row[col.name]);
+ csvStr += ',';
+ });
+ csvStr = csvStr.replace(/,$/, '\n');
+ },
+ err => {},
+ () => resolve(csvStr)
+ );
+ });
+ }
+
+
+ // prepares a string for inclusion within a CSV document
+ // by escaping commas and quotes and removing newlines.
+ valueToCsv(str: string): string {
+ str = '' + str;
+ if (!str) { return ''; }
+ str = str.replace(/\n/g, '');
+ if (str.match(/\,/) || str.match(/"/)) {
+ str = str.replace(/"/g, '""');
+ str = '"' + str + '"';
+ }
+ return str;
+ }
+
+ generateColumns() {
+ if (!this.columnSet.idlClass) { return; }
+
+ const pkeyField = this.idl.classes[this.columnSet.idlClass].pkey;
+
+ // generate columns for all non-virtual fields on the IDL class
+ this.idl.classes[this.columnSet.idlClass].fields
+ .filter(field => !field.virtual)
+ .forEach(field => {
+ const col = new GridColumn();
+ col.name = field.name;
+ col.label = field.label || field.name;
+ col.idlFieldDef = field;
+ col.datatype = field.datatype;
+ col.isIndex = (field.name === pkeyField);
+ col.isAuto = true;
+ this.columnSet.add(col);
+ });
+ }
+
+ saveGridConfig(): Promise<any> {
+ if (!this.persistKey) {
+ throw new Error('Grid persistKey required to save columns');
+ }
+ const conf = new GridPersistConf();
+ conf.version = 2;
+ conf.limit = this.pager.limit;
+ conf.columns = this.columnSet.compileSaveObject();
+
+ return this.store.setItem('eg.grid.' + this.persistKey, conf);
+ }
+
+ // TODO: saveGridConfigAsOrgSetting(...)
+
+ getGridConfig(persistKey: string): Promise<GridPersistConf> {
+ if (!persistKey) { return Promise.resolve(null); }
+ return this.store.getItem('eg.grid.' + persistKey);
+ }
+}
+
+
+// Actions apply to specific rows
+export class GridToolbarAction {
+ label: string;
+ action: (rows: any[]) => any;
+}
+
+// Buttons are global actions
+export class GridToolbarButton {
+ label: string;
+ action: () => any;
+ disabled: boolean;
+}
+
+export class GridToolbarCheckbox {
+ label: string;
+ onChange: (checked: boolean) => void;
+}
+
+export class GridDataSource {
+
+ data: any[];
+ sort: any[];
+ allRowsRetrieved: boolean;
+ getRows: (pager: Pager, sort: any[]) => Observable<any>;
+
+ constructor() {
+ this.sort = [];
+ this.reset();
+ }
+
+ reset() {
+ this.data = [];
+ this.allRowsRetrieved = false;
+ }
+
+ // called from the template -- no data fetching
+ getPageOfRows(pager: Pager): any[] {
+ if (this.data) {
+ return this.data.slice(
+ pager.offset, pager.limit + pager.offset
+ ).filter(row => row !== undefined);
+ }
+ return [];
+ }
+
+ // called on initial component load and user action (e.g. paging, sorting).
+ requestPage(pager: Pager): Promise<any> {
+
+ if (
+ this.getPageOfRows(pager).length === pager.limit
+ // already have all data
+ || this.allRowsRetrieved
+ // have no way to get more data.
+ || !this.getRows
+ ) {
+ return Promise.resolve();
+ }
+
+ return new Promise((resolve, reject) => {
+ let idx = pager.offset;
+ return this.getRows(pager, this.sort).subscribe(
+ row => this.data[idx++] = row,
+ err => {
+ console.error(`grid getRows() error ${err}`);
+ reject(err);
+ },
+ () => {
+ this.checkAllRetrieved(pager, idx);
+ resolve();
+ }
+ );
+ });
+ }
+
+ // See if the last getRows() call resulted in the final set of data.
+ checkAllRetrieved(pager: Pager, idx: number) {
+ if (this.allRowsRetrieved) { return; }
+
+ if (idx === 0 || idx < (pager.limit + pager.offset)) {
+ // last query returned nothing or less than one page.
+ // confirm we have all of the preceding pages.
+ if (!this.data.includes(undefined)) {
+ this.allRowsRetrieved = true;
+ pager.resultCount = this.data.length;
+ }
+ }
+ }
+}
+
+
--- /dev/null
+
+<!-- todo disabled -->
+<ng-template #displayTemplate let-r="result">
+{{r.label}}
+</ng-template>
+
+<input type="text"
+ class="form-control"
+ [placeholder]="placeholder"
+ [(ngModel)]="selected"
+ [ngbTypeahead]="filter"
+ [resultTemplate]="displayTemplate"
+ [inputFormatter]="formatter"
+ (click)="click$.next($event.target.value)"
+ (selectItem)="orgChanged($event)"
+ #instance="ngbTypeahead"
+/>
--- /dev/null
+/** TODO PORT ME TO <eg-combobox> */
+import {Component, OnInit, Input, Output, ViewChild, EventEmitter} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {map} from 'rxjs/operators/map';
+import {mapTo} from 'rxjs/operators/mapTo';
+import {debounceTime} from 'rxjs/operators/debounceTime';
+import {distinctUntilChanged} from 'rxjs/operators/distinctUntilChanged';
+import {merge} from 'rxjs/operators/merge';
+import {filter} from 'rxjs/operators/filter';
+import {Subject} from 'rxjs/Subject';
+import {AuthService} from '@eg/core/auth.service';
+import {StoreService} from '@eg/core/store.service';
+import {OrgService} from '@eg/core/org.service';
+import {IdlObject} from '@eg/core/idl.service';
+import {PermService} from '@eg/core/perm.service';
+import {NgbTypeahead, NgbTypeaheadSelectItemEvent} from '@ng-bootstrap/ng-bootstrap';
+
+// Use a unicode char for spacing instead of ASCII=32 so the browser
+// won't collapse the nested display entries down to a single space.
+const PAD_SPACE = ' '; // U+2007
+
+interface OrgDisplay {
+ id: number;
+ label: string;
+ disabled: boolean;
+}
+
+@Component({
+ selector: 'eg-org-select',
+ templateUrl: './org-select.component.html'
+})
+export class OrgSelectComponent implements OnInit {
+
+ selected: OrgDisplay;
+ hidden: number[] = [];
+ disabled: number[] = [];
+ click$ = new Subject<string>();
+ startOrg: IdlObject;
+
+ @ViewChild('instance') instance: NgbTypeahead;
+
+ // Placeholder text for selector input
+ @Input() placeholder = '';
+ @Input() stickySetting: string;
+
+ // Org unit field displayed in the selector
+ @Input() displayField = 'shortname';
+
+ // Apply a default org unit value when none is set.
+ // First tries workstation org unit, then user home org unit.
+ // An onChange event WILL be generated when a default is applied.
+ @Input() applyDefault = false;
+
+ // List of org unit IDs to exclude from the selector
+ @Input() set hideOrgs(ids: number[]) {
+ if (ids) { this.hidden = ids; }
+ }
+
+ // List of org unit IDs to disable in the selector
+ @Input() set disableOrgs(ids: number[]) {
+ if (ids) { this.disabled = ids; }
+ }
+
+ // Apply an org unit value at load time.
+ // This will NOT result in an onChange event.
+ @Input() set initialOrg(org: IdlObject) {
+ if (org) { this.startOrg = org; }
+ }
+
+ // Apply an org unit value by ID at load time.
+ // This will NOT result in an onChange event.
+ @Input() set initialOrgId(id: number) {
+ if (id) { this.startOrg = this.org.get(id); }
+ }
+
+ // Modify the selected org unit via data binding.
+ // This WILL result in an onChange event firing.
+ @Input() set applyOrg(org: IdlObject) {
+ if (org) {
+ this.selected = this.formatForDisplay(org);
+ }
+ }
+
+ permLimitOrgs: number[];
+ @Input() set limitPerms(perms: string[]) {
+ this.applyPermLimitOrgs(perms);
+ }
+
+ // Modify the selected org unit by ID via data binding.
+ // This WILL result in an onChange event firing.
+ @Input() set applyOrgId(id: number) {
+ if (id) {
+ this.selected = this.formatForDisplay(this.org.get(id));
+ }
+ }
+
+ // Emitted when the org unit value is changed via the selector.
+ // Does not fire on initialOrg
+ @Output() onChange = new EventEmitter<IdlObject>();
+
+ constructor(
+ private auth: AuthService,
+ private store: StoreService,
+ private org: OrgService,
+ private perm: PermService
+ ) { }
+
+ ngOnInit() {
+
+ // Apply a default org unit if desired and possible.
+ if (!this.startOrg && this.applyDefault && this.auth.user()) {
+ // note: ws_ou defaults to home_ou on the server
+ // when when no workstation is used
+ this.startOrg = this.org.get(this.auth.user().ws_ou());
+ this.selected = this.formatForDisplay(
+ this.org.get(this.auth.user().ws_ou())
+ );
+
+ // avoid notifying mid-digest
+ setTimeout(() => this.onChange.emit(this.startOrg), 0);
+ }
+
+ if (this.startOrg) {
+ this.selected = this.formatForDisplay(this.startOrg);
+ }
+ }
+
+ //
+ applyPermLimitOrgs(perms: string[]) {
+
+ if (!perms) {
+ return;
+ }
+
+ // handle lazy clients that pass null perm names
+ perms = perms.filter(p => p !== null && p !== undefined);
+
+ if (perms.length === 0) {
+ return;
+ }
+
+ // NOTE: If permLimitOrgs is useful in a non-staff context
+ // we need to change this to support non-staff perm checks.
+ this.perm.hasWorkPermAt(perms, true).then(permMap => {
+ this.permLimitOrgs =
+ // safari-friendly version of Array.flat()
+ Object.values(permMap).reduce((acc, val) => acc.concat(val), []);
+ });
+ }
+
+ // Format for display in the selector drop-down and input.
+ formatForDisplay(org: IdlObject): OrgDisplay {
+ return {
+ id : org.id(),
+ label : PAD_SPACE.repeat(org.ou_type().depth())
+ + org[this.displayField](),
+ disabled : false
+ };
+ }
+
+ // Fired by the typeahead to inform us of a change.
+ // TODO: this does not fire when the value is cleared :( -- implement
+ // change detection on this.selected to look specifically for NULL.
+ orgChanged(selEvent: NgbTypeaheadSelectItemEvent) {
+ // console.debug('org unit change occurred ' + selEvent.item);
+ this.onChange.emit(this.org.get(selEvent.item.id));
+ }
+
+ // Remove the tree-padding spaces when matching.
+ formatter = (result: OrgDisplay) => result.label.trim();
+
+ filter = (text$: Observable<string>): Observable<OrgDisplay[]> => {
+ return text$.pipe(
+ debounceTime(200),
+ distinctUntilChanged(),
+ merge(
+ // Inject a specifier indicating the source of the
+ // action is a user click
+ this.click$.pipe(filter(() => !this.instance.isPopupOpen()))
+ .pipe(mapTo('_CLICK_'))
+ ),
+ map(term => {
+
+ let orgs = this.org.list().filter(org =>
+ this.hidden.filter(id => org.id() === id).length === 0
+ );
+
+ if (this.permLimitOrgs) {
+ // Avoid showing org units where the user does
+ // not have the requested permission.
+ orgs = orgs.filter(org =>
+ this.permLimitOrgs.includes(org.id()));
+ }
+
+ if (term !== '_CLICK_') {
+ // For search-driven events, limit to the matching
+ // org units.
+ orgs = orgs.filter(org => {
+ return term === '' || // show all
+ org[this.displayField]()
+ .toLowerCase().indexOf(term.toLowerCase()) > -1;
+
+ });
+ }
+
+ return orgs.map(org => this.formatForDisplay(org));
+ })
+ );
+ }
+}
+
+
--- /dev/null
+<!--
+Global print container.
+There should only be one print component active in a page.
+-->
+
+<div id='eg-print-container'>
+ <!-- container for inline template compilation -->
+ <ng-container *ngIf="template">
+ <ng-container *ngTemplateOutlet="template; context:context">
+ </ng-container>
+ </ng-container>
+ <div id='eg-print-html-container'>
+ </div>
+<!-- container for pre-compiled HTML -->
+</div>
+
--- /dev/null
+import {Component, OnInit, TemplateRef, ElementRef, Renderer2} from '@angular/core';
+import {PrintService, PrintRequest} from './print.service';
+import {StoreService} from '@eg/core/store.service';
+
+@Component({
+ selector: 'eg-print',
+ templateUrl: './print.component.html'
+})
+
+export class PrintComponent implements OnInit {
+
+ // Template that requires local processing
+ template: TemplateRef<any>;
+
+ // Context data used for processing the template.
+ context: any;
+
+ // Insertion point for externally-compiled templates
+ htmlContainer: Element;
+
+ isPrinting: boolean;
+
+ printQueue: PrintRequest[];
+
+ constructor(
+ private renderer: Renderer2,
+ private elm: ElementRef,
+ private store: StoreService,
+ private printer: PrintService) {
+ this.isPrinting = false;
+ this.printQueue = [];
+ }
+
+ ngOnInit() {
+ this.printer.onPrintRequest$.subscribe(
+ printReq => this.handlePrintRequest(printReq));
+
+ this.htmlContainer =
+ this.renderer.selectRootElement('#eg-print-html-container');
+ }
+
+ handlePrintRequest(printReq: PrintRequest) {
+
+ if (this.isPrinting) {
+ // Avoid print collisions by queuing requests as needed.
+ this.printQueue.push(printReq);
+ return;
+ }
+
+ this.isPrinting = true;
+
+ this.applyTemplate(printReq);
+
+ // Give templates a chance to render before printing
+ setTimeout(() => {
+ this.dispatchPrint(printReq);
+ this.reset();
+ });
+ }
+
+ applyTemplate(printReq: PrintRequest) {
+
+ if (printReq.template) {
+ // Inline template. Let Angular do the interpolationwork.
+ this.template = printReq.template;
+ this.context = {$implicit: printReq.contextData};
+ return;
+ }
+
+ if (printReq.text && true /* !this.hatch.isActive */) {
+ // Insert HTML into the browser DOM for in-browser printing only.
+
+ if (printReq.contentType === 'text/plain') {
+ // Wrap text/plain content in pre's to prevent
+ // unintended html formatting.
+ printReq.text = `<pre>${printReq.text}</pre>`;
+ }
+
+ this.htmlContainer.innerHTML = printReq.text;
+ }
+ }
+
+ // Clear the print data
+ reset() {
+ this.isPrinting = false;
+ this.template = null;
+ this.context = null;
+ this.htmlContainer.innerHTML = '';
+
+ if (this.printQueue.length) {
+ this.handlePrintRequest(this.printQueue.pop());
+ }
+ }
+
+ dispatchPrint(printReq: PrintRequest) {
+
+ if (!printReq.text) {
+ // Sometimes the results come from an externally-parsed HTML
+ // template, other times they come from an in-page template.
+ printReq.text = this.elm.nativeElement.innerHTML;
+ }
+
+ // Retain a copy of each printed document in localStorage
+ // so it may be reprinted.
+ this.store.setLocalItem('eg.print.last_printed', {
+ content: printReq.text,
+ context: printReq.printContext,
+ content_type: printReq.contentType,
+ show_dialog: printReq.showDialog
+ });
+
+ if (0 /* this.hatch.isActive */) {
+ this.printViaHatch(printReq);
+ } else {
+ // Here the needed HTML is already in the page.
+ window.print();
+ }
+ }
+
+ printViaHatch(printReq: PrintRequest) {
+
+ // Send a full HTML document to Hatch
+ const html = `<html><body>${printReq.text}</body></html>`;
+
+ /*
+ this.hatch.print({
+ printContext: printReq.printContext,
+ content: html
+ });
+ */
+ }
+}
+
--- /dev/null
+import {Injectable, EventEmitter, TemplateRef} from '@angular/core';
+import {StoreService} from '@eg/core/store.service';
+
+export interface PrintRequest {
+ template?: TemplateRef<any>;
+ contextData?: any;
+ text?: string;
+ printContext: string;
+ contentType?: string; // defaults to text/html
+ showDialog?: boolean;
+}
+
+@Injectable()
+export class PrintService {
+
+ onPrintRequest$: EventEmitter<PrintRequest>;
+
+ constructor(private store: StoreService) {
+ this.onPrintRequest$ = new EventEmitter<PrintRequest>();
+ }
+
+ print(printReq: PrintRequest) {
+ this.onPrintRequest$.emit(printReq);
+ }
+
+ reprintLast() {
+ const prev = this.store.getLocalItem('eg.print.last_printed');
+
+ if (prev) {
+ const req: PrintRequest = {
+ text: prev.content,
+ printContext: prev.context || 'default',
+ contentType: prev.content_type || 'text/html',
+ showDialog: Boolean(prev.show_dialog)
+ };
+
+ this.print(req);
+ }
+ }
+}
+
--- /dev/null
+/*j
+ * <eg-string #helloStr text="Hello, {{name}}" i18n-text></eg-string>
+ *
+ * import {StringComponent} from '@eg/share/string.component';
+ * @ViewChild('helloStr') private helloStr: StringComponent;
+ * ...
+ * this.helloStr.currrent().then(s => console.log(s));
+ *
+ */
+import {Component, Input, OnInit, ElementRef, TemplateRef} from '@angular/core';
+import {StringService} from '@eg/share/string/string.service';
+
+@Component({
+ selector: 'eg-string',
+ template: `
+ <span style='display:none'>
+ <ng-container *ngTemplateOutlet="template; context:ctx"></ng-container>
+ </span>
+ `
+})
+
+export class StringComponent implements OnInit {
+
+ // Storage key for future reference by the string service
+ @Input() key: string;
+
+ // Interpolation context
+ @Input() ctx: any;
+
+ // String template to interpolate
+ @Input() template: TemplateRef<any>;
+
+ // Static text -- no interpolation performed.
+ // This supersedes 'template'
+ @Input() text: string;
+
+ constructor(private elm: ElementRef, private strings: StringService) {
+ this.elm = elm;
+ this.strings = strings;
+ }
+
+ ngOnInit() {
+ // No key means it's an unregistered (likely static) string
+ // that does not need interpolation.
+ if (this.key) {
+ this.strings.register({
+ key: this.key,
+ resolver: (ctx: any) => {
+ if (this.text) {
+ // When passed text that does not require any
+ // interpolation, just return it as-is.
+ return Promise.resolve(this.text);
+ } else {
+ // Interpolate
+ return this.current(ctx);
+ }
+ }
+ });
+ }
+ }
+
+ // Apply the new context if provided, give our container a
+ // chance to update, then resolve with the current string.
+ // NOTE: talking to the native DOM element is not so great, but
+ // hopefully we can retire the String* code entirely once
+ // in-code translations are supported (Ang6?)
+ current(ctx?: any): Promise<string> {
+ if (ctx) { this.ctx = ctx; }
+ return new Promise(resolve => {
+ setTimeout(() => resolve(this.elm.nativeElement.textContent));
+ });
+ }
+}
+
--- /dev/null
+import {Injectable} from '@angular/core';
+
+interface StringAssignment {
+ key: string; // keyboard command
+ resolver: (ctx: any) => Promise<string>;
+}
+
+interface PendingInterpolation {
+ key: string;
+ ctx: any;
+ resolve: (string) => any;
+ reject: (string) => any;
+}
+
+@Injectable()
+export class StringService {
+
+ strings: {[key: string]: StringAssignment} = {};
+
+ // This service can only interpolate one string at a time, since it
+ // maintains only one string component instance. Avoid clobbering
+ // in-process interpolation requests by maintaining a request queue.
+ private pending: PendingInterpolation[];
+
+ constructor() {
+ this.pending = [];
+ }
+
+ register(assn: StringAssignment) {
+ this.strings[assn.key] = assn;
+ }
+
+ interpolate(key: string, ctx?: any): Promise<string> {
+
+ if (!this.strings[key]) {
+ return Promise.reject(`String key not found: "${key}"`);
+ }
+
+ return new Promise( (resolve, reject) => {
+ const pend: PendingInterpolation = {
+ key: key,
+ ctx: ctx,
+ resolve: resolve,
+ reject: reject
+ };
+
+ this.pending.push(pend);
+
+ // Avoid launching the pending string processer with >1
+ // pending, because the processor will have already started.
+ if (this.pending.length === 1) {
+ this.processPending();
+ }
+ });
+ }
+
+ processPending() {
+ const pstring = this.pending[0];
+ this.strings[pstring.key].resolver(pstring.ctx).then(
+ txt => {
+ pstring.resolve(txt);
+ this.pending.shift();
+ if (this.pending.length) {
+ this.processPending();
+ }
+ },
+ err => {
+ pstring.reject(err);
+ this.pending.shift();
+ if (this.pending.length) {
+ this.processPending();
+ }
+ }
+ );
+ }
+}
+
+
--- /dev/null
+#eg-toast-container {
+ min-width: 250px;
+ text-align: center;
+ border-radius: 2px;
+ padding: 10px;
+ position: fixed;
+ z-index: 1;
+ right: 15px;
+ bottom: 5px;
+}
+
--- /dev/null
+<div id="eg-toast-container" *ngIf="message">
+ <ngb-alert [type]="message.style" (close)="dismiss(message)">{{message.text}}</ngb-alert>
+</div>
--- /dev/null
+import {Component, Input, OnInit, ViewChild} from '@angular/core';
+import {ToastService, ToastMessage} from '@eg/share/toast/toast.service';
+
+const EG_TOAST_TIMEOUT = 3000;
+
+@Component({
+ selector: 'eg-toast',
+ templateUrl: './toast.component.html',
+ styleUrls: ['./toast.component.css']
+})
+export class ToastComponent implements OnInit {
+
+ message: ToastMessage;
+
+ // track the most recent timeout event
+ timeout: any;
+
+ constructor(private toast: ToastService) {
+ }
+
+ ngOnInit() {
+ this.toast.messages$.subscribe(msg => this.show(msg));
+ }
+
+ show(msg: ToastMessage) {
+ this.dismiss(this.message);
+ this.message = msg;
+ this.timeout = setTimeout(
+ () => this.dismiss(this.message),
+ EG_TOAST_TIMEOUT
+ );
+ }
+
+ dismiss(msg: ToastMessage) {
+ this.message = null;
+ if (this.timeout) {
+ clearTimeout(this.timeout);
+ this.timeout = null;
+ }
+ }
+}
+
+
--- /dev/null
+import {Injectable, EventEmitter} from '@angular/core';
+
+export interface ToastMessage {
+ text: string;
+ style: string;
+}
+
+@Injectable()
+export class ToastService {
+
+ messages$: EventEmitter<ToastMessage>;
+
+ constructor() {
+ this.messages$ = new EventEmitter<ToastMessage>();
+ }
+
+ sendMessage(msg: ToastMessage) {
+ this.messages$.emit(msg);
+ }
+
+ success(text: string) {
+ this.sendMessage({text: text, style: 'success'});
+ }
+
+ info(text: string) {
+ this.sendMessage({text: text, style: 'info'});
+ }
+
+ warning(text: string) {
+ this.sendMessage({text: text, style: 'warning'});
+ }
+
+ danger(text: string) {
+ this.sendMessage({text: text, style: 'danger'});
+ }
+
+ // Others?
+}
+
--- /dev/null
+
+.eg-tree-node-expandy .material-icons {
+ font-size: 16px;
+}
+
+.eg-tree-node {
+ padding: 2px;
+}
+
+.eg-tree-node.active {
+ background-color: rgba(0,0,0,.03);
+ border: 1px solid rgba(0,0,0,.125);
+ font-style: italic;
+}
+
+.eg-tree-node-nochild {
+ border-left: 2px dashed rgba(0,0,0,.125);
+}
+
--- /dev/null
+
+
+<div class="eg-tree" *ngFor="let node of displayNodes()">
+ <div class="eg-tree-node-wrapper d-flex"
+ [ngStyle]="{'padding-left': (node.depth * 20) + 'px'}">
+ <div class="eg-tree-node-expandy">
+ <div *ngIf="node.children.length" (click)="node.toggleExpand()"
+ i18n-title title="Toggle Expand Node">
+ <span *ngIf="!node.expanded" class="material-icons">expand_more</span>
+ <span *ngIf="node.expanded" class="material-icons">expand_less</span>
+ </div>
+ <div *ngIf="!node.children.length" class="eg-tree-node-nochild">
+
+ </div>
+ </div>
+ <div class="eg-tree-node" [ngClass]="{active : node.selected}">
+ <a [routerLink]="" (click)="handleNodeClick(node)">{{node.label}}</a>
+ </div>
+ </div>
+</div>
--- /dev/null
+import {Component, OnInit, Input, Output, EventEmitter} from '@angular/core';
+import {Tree, TreeNode} from './tree';
+
+/*
+Tree Widget:
+
+<eg-tree
+ [tree]="myTree"
+ (nodeClicked)="nodeClicked($event)">
+</eg-tree>
+
+----
+
+constructor() {
+
+ const rootNode = new TreeNode({
+ id: 1,
+ label: 'Root',
+ children: [
+ new TreeNode({id: 2, label: 'Child'}),
+ new TreeNode({id: 3, label: 'Child2'})
+ ]
+ ]});
+
+ this.myTree = new Tree(rootNode);
+}
+
+nodeClicked(node: TreeNode) {
+ console.log('someone clicked on ' + node.label);
+}
+*/
+
+@Component({
+ selector: 'eg-tree',
+ templateUrl: 'tree.component.html',
+ styleUrls: ['tree.component.css']
+})
+export class TreeComponent implements OnInit {
+
+ @Input() tree: Tree;
+ @Output() nodeClicked: EventEmitter<TreeNode>;
+
+ constructor() {
+ this.nodeClicked = new EventEmitter<TreeNode>();
+ }
+
+ ngOnInit() {}
+
+ displayNodes(): TreeNode[] {
+ return this.tree.nodeList(true);
+ }
+
+ handleNodeClick(node: TreeNode) {
+ this.tree.selectNode(node);
+ this.nodeClicked.emit(node);
+ }
+}
+
+
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {EgCommonModule} from '@eg/common.module';
+import {TreeComponent} from './tree.component';
+
+@NgModule({
+ declarations: [
+ TreeComponent
+ ],
+ imports: [
+ EgCommonModule
+ ],
+ exports: [
+ TreeComponent
+ ],
+ providers: [
+ ]
+})
+
+export class TreeModule {}
+
--- /dev/null
+
+export class TreeNode {
+ // Unique identifier
+ id: any;
+
+ // Display label
+ label: string;
+
+ // True if child nodes should be visible
+ expanded: boolean;
+
+ children: TreeNode[];
+
+ // Set by the tree.
+ depth: number;
+
+ // Set by the tree.
+ selected: boolean;
+
+ // Optional link to user-provided stuff.
+ // This field is ignored by the tree.
+ callerData: any;
+
+ constructor(values: {[key: string]: any}) {
+ this.children = [];
+ this.expanded = true;
+ this.depth = 0;
+ this.selected = false;
+
+ if (!values) { return; }
+
+ if ('id' in values) { this.id = values.id; }
+ if ('label' in values) { this.label = values.label; }
+ if ('children' in values) { this.children = values.children; }
+ if ('expanded' in values) { this.expanded = values.expanded; }
+ if ('callerData' in values) { this.callerData = values.callerData; }
+ }
+
+ toggleExpand() {
+ this.expanded = !this.expanded;
+ }
+}
+
+export class Tree {
+
+ rootNode: TreeNode;
+ idMap: {[id: string]: TreeNode};
+
+ constructor(rootNode?: TreeNode) {
+ this.rootNode = rootNode;
+ this.idMap = {};
+ }
+
+ // Returns a depth-first list of tree nodes
+ // Tweaks node attributes along the way to match the shape of the tree.
+ nodeList(filterHidden?: boolean): TreeNode[] {
+
+ const nodes = [];
+
+ const recurseTree =
+ (node: TreeNode, depth: number, hidden: boolean) => {
+ if (!node) { return; }
+
+ node.depth = depth++;
+ this.idMap[node.id + ''] = node;
+
+ if (hidden) {
+ // it could be confusing for a hidden node to be selected.
+ node.selected = false;
+ }
+
+ if (hidden && filterHidden) {
+ // Avoid adding hidden child nodes to the list.
+ } else {
+ nodes.push(node);
+ }
+
+ node.children.forEach(n => recurseTree(n, depth, !node.expanded));
+ };
+
+ recurseTree(this.rootNode, 0, false);
+ return nodes;
+ }
+
+ findNode(id: any): TreeNode {
+ if (this.idMap[id + '']) {
+ return this.idMap[id + ''];
+ } else {
+ // nodeList re-indexes all the nodes.
+ this.nodeList();
+ return this.idMap[id + ''];
+ }
+ }
+
+ findParentNode(node: TreeNode) {
+ const list = this.nodeList();
+ for (let idx = 0; idx < list.length; idx++) {
+ const pnode = list[idx];
+ if (pnode.children.filter(c => c.id === node.id).length) {
+ return pnode;
+ }
+ }
+ return null;
+ }
+
+ removeNode(node: TreeNode) {
+ if (!node) { return; }
+ const pnode = this.findParentNode(node);
+ if (pnode) {
+ pnode.children = pnode.children.filter(n => n.id !== node.id);
+ } else {
+ this.rootNode = null;
+ }
+ }
+
+ expandAll() {
+ this.nodeList().forEach(node => node.expanded = true);
+ }
+
+ collapseAll() {
+ this.nodeList().forEach(node => node.expanded = false);
+ }
+
+ selectedNode(): TreeNode {
+ return this.nodeList().filter(node => node.selected)[0];
+ }
+
+ selectNode(node: TreeNode) {
+ this.nodeList().forEach(n => n.selected = false);
+ node.selected = true;
+ }
+}
+
--- /dev/null
+/**
+ * Plays audio files (alerts, generally) by key name. Each sound uses a
+ * dot-path to indicate the sound.
+ *
+ * For example:
+ *
+ * this.audio.play('warning.checkout.no_item');
+ *
+ * URLs are tested in the following order until an audio file is found
+ * or no other paths are left to check.
+ *
+ * /audio/notifications/warning/checkout/not_found.wav
+ * /audio/notifications/warning/checkout.wav
+ * /audio/notifications/warning.wav
+ *
+ * Files are only played when sounds are configured to play via
+ * workstation settings.
+ */
+import {Injectable, EventEmitter} from '@angular/core';
+import {ServerStoreService} from '@eg/core/server-store.service';
+const AUDIO_BASE_URL = '/audio/notifications/';
+
+@Injectable()
+export class AudioService {
+
+ // map of requested audio path to resolved path
+ private urlCache: {[path: string]: string} = {};
+
+ constructor(private store: ServerStoreService) {}
+
+ play(path: string): void {
+ if (path) {
+ this.playUrl(path, path);
+ }
+ }
+
+ playUrl(path: string, origPath: string): void {
+ // console.debug(`audio: playUrl(${path}, ${origPath})`);
+
+ this.store.getItem('eg.audio.disable').then(audioDisabled => {
+ if (audioDisabled) { return; }
+
+ const url = this.urlCache[path] ||
+ AUDIO_BASE_URL + path.replace(/\./g, '/') + '.wav';
+
+ const player = new Audio(url);
+
+ player.onloadeddata = () => {
+ this.urlCache[origPath] = url;
+ player.play();
+ console.debug(`audio: ${url}`);
+ };
+
+ if (this.urlCache[path]) {
+ // when serving from the cache, avoid secondary URL lookups.
+ return;
+ }
+
+ player.onerror = () => {
+ // Unable to play path at the requested URL.
+
+ if (!path.match(/\./)) {
+ // all fall-through options have been exhausted.
+ // No path to play.
+ console.warn(
+ `No suitable URL found for path "${origPath}"`);
+ return;
+ }
+
+ // Fall through to the next (more generic) option
+ path = path.replace(/\.[^\.]+$/, '');
+ this.playUrl(path, origPath);
+ };
+ });
+ }
+}
+
+
--- /dev/null
+import {EventEmitter} from '@angular/core';
+
+/**
+ * Utility class for manage paged information.
+ */
+export class Pager {
+ offset = 0;
+ limit: number = null;
+ resultCount: number;
+ onChange$: EventEmitter<number>;
+
+ constructor() {
+ this.resultCount = null;
+ this.onChange$ = new EventEmitter<number>();
+ }
+
+ reset() {
+ this.resultCount = null;
+ this.offset = 0;
+ }
+
+ setLimit(l: number) {
+ if (l !== this.limit) {
+ this.limit = l;
+ this.setPage(1);
+ }
+ }
+
+ isFirstPage(): boolean {
+ return this.offset === 0;
+ }
+
+ isLastPage(): boolean {
+ return this.currentPage() === this.pageCount();
+ }
+
+ currentPage(): number {
+ return Math.floor(this.offset / this.limit) + 1;
+ }
+
+ increment(): void {
+ this.setPage(this.currentPage() + 1);
+ }
+
+ decrement(): void {
+ this.setPage(this.currentPage() - 1);
+ }
+
+ toFirst() {
+ if (!this.isFirstPage()) {
+ this.setPage(1);
+ }
+ }
+
+ toLast() {
+ if (!this.isLastPage()) {
+ this.setPage(this.pageCount());
+ }
+ }
+
+ setPage(page: number): void {
+ this.offset = (this.limit * (page - 1));
+ this.onChange$.emit(this.offset);
+ }
+
+ pageCount(): number {
+ if (this.resultCount === null) { return -1; }
+ let pages = this.resultCount / this.limit;
+ if (Math.floor(pages) < pages) {
+ pages = Math.floor(pages) + 1;
+ }
+ return pages;
+ }
+
+ // Returns a list of pages numbers with @pivot at the center
+ // or as close to center as possible.
+ // @pivot is 1-based for consistency with page numbers.
+ // pageRange(25, 10) => [21,22,...29,30]
+ pageRange(pivot: number, size: number): number[] {
+
+ const diff = Math.floor(size / 2);
+ let start = pivot <= diff ? 1 : pivot - diff + 1;
+
+ const pcount = this.pageCount();
+
+ if (start + size > pcount) {
+ start = pcount - size + 1;
+ if (start < 1) { start = 1; }
+ }
+
+ if (start + size > pcount) {
+ size = pcount;
+ }
+
+ return this.pageList().slice(start - 1, start - 1 + size);
+ }
+
+ pageList(): number[] {
+ const list = [];
+ for (let i = 1; i <= this.pageCount(); i++) {
+ list.push(i);
+ }
+ return list;
+ }
+
+ // Given a zero-based page-specific offset, return the where in the
+ // entire data set the row lives, 1-based for UI friendliness.
+ rowNumber(offset: number): number {
+ return this.offset + offset + 1;
+ }
+}
--- /dev/null
+<eg-staff-banner bannerText="About Evergreen" i18n-bannerText>
+</eg-staff-banner>
+
+<div class="row">
+ <div class="col-lg-4">
+ <div class="card">
+ <div class="card-header" i18n>Server Details</div>
+ <ul class="list-group list-group-flush">
+ <li class="list-group-item">
+ <div class="row pt-2">
+ <div class="col-lg-6" i18n>Evergreen Version</div>
+ <div class="col-lg-6">{{version}}</div>
+ </div>
+ </li>
+ <li class="list-group-item">
+ <div class="row pt-2">
+ <div class="col-lg-6" i18n>Hostname</div>
+ <div class="col-lg-6">{{server}}</div>
+ </div>
+ </li>
+ </ul>
+ </div><!-- card -->
+ </div>
+</div>
+<div class="row mt-4">
+ <div class="col-lg-8">
+ <h2 i18n>What is Evergreen?</h2>
+ <p i18n>Evergreen is library automation software that assists libraries
+ in day-to-day operations such as checking out materials, keeping
+ track of users, sharing resources among a group of libraries,
+ acquiring materials, and providing a web-based library catalog for
+ the public.
+ </p>
+ <p i18n>The open-source community developing and supporting Evergreen is
+ marked by a high degree of participation from developers and from
+ the librarians who use the software.
+ </p>
+ <p i18n>
+ More information can be found at
+ <a href="https://evergreen-ils.org">https://evergreen-ils.org</a>.
+ For help in using Evergreen, see our documentation at
+ <a href="http://docs.evergreen-ils.org">http://docs.evergreen-ils.org</a>.
+ </p>
+ <p i18n>
+ Evergreen is Copyright © Georgia Public Library Service -
+ A Unit of the University System of Georgia, and others. The
+ Evergreen software is distributed under the
+ <a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.en.html">
+ GNU General Public License, Version 2.
+ </a>
+ </p>
+ </div>
+</div>
+
+
+
+
--- /dev/null
+import {Component, OnInit} from '@angular/core';
+import {NetService} from '@eg/core/net.service';
+
+@Component({
+ selector: 'eg-about',
+ templateUrl: 'about.component.html'
+})
+
+export class AboutComponent implements OnInit {
+ server: string;
+ version: string;
+
+ constructor(
+ private net: NetService
+ ) {}
+
+ ngOnInit() {
+ this.server = window.location.hostname;
+ this.net.request(
+ 'open-ils.actor',
+ 'opensrf.open-ils.system.ils_version'
+ ).subscribe(v => this.version = v);
+ }
+}
+
--- /dev/null
+<eg-staff-banner bannerText="Acquisitions Administration" i18n-bannerText>
+</eg-staff-banner>
+
+<div class="container">
+ <eg-link-table columnCount="3">
+ <eg-link-table-link i18n-label label="Cancel Reasons"
+ routerLink="/staff/admin/acq/cancel_reason"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Claim Event Types"
+ routerLink="/staff/admin/acq/claim_event_type"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Claim Policies"
+ routerLink="/staff/admin/acq/claim_policy"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Claim Policy Actions"
+ routerLink="/staff/admin/acq/claim_policy_action"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Claim Types"
+ routerLink="/staff/admin/acq/claim_type"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Currency Types"
+ routerLink="/staff/admin/acq/currency_type"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Distribution Formulas"
+ url="/eg/staff/admin/acq/conify/distribution_formula"></eg-link-table-link>
+ <!-- TODO
+ routerLink="/staff/admin/acq/distribution_formula"></eg-link-table-link>
+ -->
+ <eg-link-table-link i18n-label label="EDI Accounts"
+ routerLink="/staff/admin/acq/edi_account"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="EDI Messages"
+ routerLink="/staff/admin/acq/edi_message"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="EDI Attribute Sets"
+ url="/eg/staff/admin/acq/edi_attr_set"></eg-link-table-link>
+ <!-- TODO
+ routerLink="/staff/admin/acq/edi_attr_set"></eg-link-table-link>
+ -->
+ <eg-link-table-link i18n-label label="Exchange Rates"
+ routerLink="/staff/admin/acq/exchange_rate"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Fund Tags"
+ routerLink="/staff/admin/acq/fund_tag"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Funding Sources"
+ url="/eg/staff/admin/acq/funding_source/list"></eg-link-table-link>
+ <!-- TODO
+ routerLink="/staff/admin/acq/funding_source"></eg-link-table-link>
+ -->
+ <!-- TODO fund admin page w/ year filter and rollover -->
+ <eg-link-table-link i18n-label label="Funds"
+ url="/eg/staff/admin/acq/fund/list"></eg-link-table-link>
+ <!-- routerLink="/staff/admin/acq/fund" -->
+ <eg-link-table-link i18n-label label="Invoice Item Types"
+ routerLink="/staff/admin/acq/invoice_item_type"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Invoice Payment Method"
+ routerLink="/staff/admin/acq/invoice_payment_method"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Line Item Alerts"
+ routerLink="/staff/admin/acq/lineitem_alert_text"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Line Item MARC Attribute Definitions"
+ routerLink="/staff/admin/acq/lineitem_marc_attr_definition"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Providers"
+ url="/eg/staff/admin/acq/conify/provider"></eg-link-table-link>
+ <!-- TODO
+ routerLink="/staff/admin/acq/provider"></eg-link-table-link>
+ -->
+ </eg-link-table>
+</div>
+
--- /dev/null
+import {Component, Input, ViewChildren,
+ AfterViewInit, QueryList} from '@angular/core';
+
+@Component({
+ templateUrl: './admin-acq-splash.component.html'
+})
+
+export class AdminAcqSplashComponent {
+}
+
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {StaffCommonModule} from '@eg/staff/common.module';
+import {AdminAcqRoutingModule} from './routing.module';
+import {AdminCommonModule} from '@eg/staff/admin/common.module';
+import {AdminAcqSplashComponent} from './admin-acq-splash.component';
+
+@NgModule({
+ declarations: [
+ AdminAcqSplashComponent
+ ],
+ imports: [
+ AdminCommonModule,
+ AdminAcqRoutingModule
+ ],
+ exports: [
+ ],
+ providers: [
+ ]
+})
+
+export class AdminAcqModule {
+}
+
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {AdminAcqSplashComponent} from './admin-acq-splash.component';
+import {BasicAdminPageComponent} from '@eg/staff/admin/basic-admin-page.component';
+
+const routes: Routes = [{
+ path: 'splash',
+ component: AdminAcqSplashComponent
+}, {
+ path: ':table',
+ component: BasicAdminPageComponent,
+ // All ACQ admin pages cover data in the acq.* schema. No need to
+ // duplicate it within the URL path. Pass it manually instead.
+ data: [{schema: 'acq'}]
+}];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+
+export class AdminAcqRoutingModule {}
--- /dev/null
+import {Component, OnInit} from '@angular/core';
+import {ActivatedRoute} from '@angular/router';
+import {IdlService} from '@eg/core/idl.service';
+
+/**
+ * Generic IDL class editor page.
+ */
+
+@Component({
+ template: `
+ <eg-staff-banner bannerText="{{classLabel}} Configuration" i18n-bannerText>
+ </eg-staff-banner>
+ <eg-admin-page persistKeyPfx="{{persistKeyPfx}}" idlClass="{{idlClass}}"></eg-admin-page>
+ `
+})
+
+export class BasicAdminPageComponent implements OnInit {
+
+ idlClass: string;
+ classLabel: string;
+ persistKeyPfx: string;
+
+ constructor(
+ private route: ActivatedRoute,
+ private idl: IdlService
+ ) {
+ }
+
+ ngOnInit() {
+ let schema = this.route.snapshot.paramMap.get('schema');
+ if (!schema) {
+ // Allow callers to pass the schema via static route data
+ const data = this.route.snapshot.data[0];
+ if (data) { schema = data.schema; }
+ }
+ const table = schema + '.' + this.route.snapshot.paramMap.get('table');
+
+ // Set the prefix to "server", "local", "workstation",
+ // extracted from the URL path.
+ this.persistKeyPfx = this.route.snapshot.parent.url[0].path;
+ if (this.persistKeyPfx === 'acq') {
+ // ACQ is a special case, becaus unlike 'server', 'local',
+ // 'workstation', the schema ('acq') is the root of the path.
+ this.persistKeyPfx = '';
+ }
+
+ Object.keys(this.idl.classes).forEach(class_ => {
+ const classDef = this.idl.classes[class_];
+ if (classDef.table === table) {
+ this.idlClass = class_;
+ this.classLabel = classDef.label;
+ }
+ });
+
+ if (!this.idlClass) {
+ throw new Error('Unable to find IDL class for table ' + table);
+ }
+ }
+}
+
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {StaffCommonModule} from '@eg/staff/common.module';
+import {LinkTableComponent, LinkTableLinkComponent} from '@eg/staff/share/link-table/link-table.component';
+import {BasicAdminPageComponent} from '@eg/staff/admin/basic-admin-page.component';
+
+@NgModule({
+ declarations: [
+ LinkTableComponent,
+ LinkTableLinkComponent,
+ BasicAdminPageComponent
+ ],
+ imports: [
+ StaffCommonModule
+ ],
+ exports: [
+ StaffCommonModule,
+ LinkTableComponent,
+ LinkTableLinkComponent,
+ BasicAdminPageComponent
+ ],
+ providers: [
+ ]
+})
+
+export class AdminCommonModule {
+}
+
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+
+const routes: Routes = [{
+ path: '',
+ children : [
+ { path: 'workstation',
+ loadChildren: '@eg/staff/admin/workstation/routing.module#AdminWsRoutingModule'
+ }, {
+ path: 'server',
+ loadChildren: '@eg/staff/admin/server/admin-server.module#AdminServerModule'
+ }, {
+ path: 'acq',
+ loadChildren: '@eg/staff/admin/acq/admin-acq.module#AdminAcqModule'
+ }]
+}];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+
+export class AdminRoutingModule {}
--- /dev/null
+<eg-staff-banner bannerText="Server Administration" i18n-bannerText>
+</eg-staff-banner>
+
+<div class="container">
+ <eg-link-table columnCount="3">
+ <eg-link-table-link i18n-label label="Actor Stat Cat Sip Fields"
+ routerLink="/staff/admin/server/actor/stat_cat_sip_fields"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Age Hold Protect Rules"
+ routerLink="/staff/admin/server/config/rule_age_hold_protect"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Asset Stat Cat Sip Fields"
+ routerLink="/staff/admin/server/asset/stat_cat_sip_fields"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Authority Browse Axes"
+ routerLink="/staff/admin/server/authority/browse_axis"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Authority Control Sets"
+ routerLink="/staff/admin/server/authority/control_set"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Authority Heading Fields"
+ routerLink="/staff/admin/server/authority/heading_field"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Authority Thesauri"
+ routerLink="/staff/admin/server/authority/thesaurus"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Best-Hold Selection Sort Order"
+ routerLink="/staff/admin/server/config/best_hold_order"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Billing Types"
+ routerLink="/staff/admin/server/config/billing_type"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Call Number Prefixes"
+ routerLink="/staff/admin/server/asset/call_number_prefix"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Call Number Suffixes"
+ routerLink="/staff/admin/server/asset/call_number_suffix"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Circulation Duration Rules"
+ routerLink="/staff/admin/server/config/rule_circ_duration"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Circulation Limit Groups"
+ routerLink="/staff/admin/server/config/circ_limit_group"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Circulation Matchpoint Weights"
+ routerLink="/staff/admin/server/config/circ_matrix_weights"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Circulation Max Fine Rules"
+ routerLink="/staff/admin/server/config/rule_max_fine"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Circulation Modifiers"
+ routerLink="/staff/admin/server/config/circ_modifier"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Circulation Recurring Fine Rules"
+ routerLink="/staff/admin/server/config/rule_recurring_fine"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Copy Statuses"
+ routerLink="/staff/admin/server/config/copy_status"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Copy Tag Types"
+ routerLink="/staff/admin/server/config/copy_tag_type"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Custom Org Unit Trees"
+ url="/eg/staff/admin/server/actor/org_unit_custom_tree"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Floating Groups"
+ routerLink="/staff/admin/server/config/floating_group"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Global Flags"
+ routerLink="/staff/admin/server/config/global_flag"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Hard Due Date Changes"
+ routerLink="/staff/admin/server/config/hard_due_date"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Hold Matchpoint Weights"
+ routerLink="/staff/admin/server/config/hold_matrix_weights"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Import Match Sets"
+ routerLink="/staff/admin/server/vandelay/match_set"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="MARC Coded Value Maps"
+ routerLink="/staff/admin/server/config/coded_value_map"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="MARC Import Remove Fields"
+ routerLink="/staff/admin/server/vandelay/import_bib_trash_group"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="MARC Record Attributes"
+ routerLink="/staff/admin/server/config/record_attr_definition"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="MARC Search/Facet Class FTS Maps"
+ routerLink="/staff/admin/server/config/metabib_class_ts_map"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="MARC Search/Facet Classes"
+ routerLink="/staff/admin/server/config/metabib_class"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="MARC Search/Facet Field FTS Maps"
+ routerLink="/staff/admin/server/config/metabib_field_ts_map"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="MARC Search/Facet Fields"
+ routerLink="/staff/admin/server/config/metabib_field"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="MARC Tag Tables"
+ routerLink="/staff/admin/server/config/marc_field"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Org Unit Proximity Adjustments"
+ routerLink="/staff/admin/server/actor/org_unit_proximity_adjustment"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Organization Types"
+ url="/eg/staff/admin/server/legacy/actor/org_unit_type"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Org Unit Setting Types"
+ routerLink="/staff/admin/server/config/org_unit_setting_type"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Organizational Units"
+ url="/eg/staff/admin/server/legacy/actor/org_unit"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Permission Groups"
+ url="/eg/staff/admin/server/legacy/permission/grp_tree"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Permissions"
+ routerLink="/staff/admin/server/permission/perm_list"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Remote Accounts"
+ routerLink="/staff/admin/server/config/remote_account"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="SMS Carriers"
+ routerLink="/staff/admin/server/config/sms_carrier"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="User Activity Types"
+ routerLink="/staff/admin/server/config/usr_activity_type"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="User Setting Types"
+ routerLink="/staff/admin/server/config/usr_setting_type"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Weights Association"
+ routerLink="/staff/admin/server/config/weight_assoc"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Z39.50 Index Field Maps"
+ routerLink="/staff/admin/server/config/z3950_index_field_map"></eg-link-table-link>
+ <eg-link-table-link i18n-label label="Z39.50 Servers"
+ routerLink="/staff/admin/server/config/z3950_source"></eg-link-table-link>
+ </eg-link-table>
+</div>
--- /dev/null
+import {Component, Input, ViewChildren,
+ AfterViewInit, QueryList} from '@angular/core';
+
+@Component({
+ templateUrl: './admin-server-splash.component.html'
+})
+
+export class AdminServerSplashComponent {
+}
+
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {StaffCommonModule} from '@eg/staff/common.module';
+import {AdminServerRoutingModule} from './routing.module';
+import {AdminCommonModule} from '@eg/staff/admin/common.module';
+import {AdminServerSplashComponent} from './admin-server-splash.component';
+
+@NgModule({
+ declarations: [
+ AdminServerSplashComponent
+ ],
+ imports: [
+ AdminCommonModule,
+ AdminServerRoutingModule
+ ],
+ exports: [
+ ],
+ providers: [
+ ]
+})
+
+export class AdminServerModule {
+}
+
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {AdminServerSplashComponent} from './admin-server-splash.component';
+import {BasicAdminPageComponent} from '@eg/staff/admin/basic-admin-page.component';
+
+const routes: Routes = [{
+ path: 'splash',
+ component: AdminServerSplashComponent
+}, {
+ path: ':schema/:table',
+ component: BasicAdminPageComponent
+}];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+
+export class AdminServerRoutingModule {}
--- /dev/null
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+
+const routes: Routes = [{
+ path: 'workstations',
+ loadChildren: '@eg/staff/admin/workstation/workstations/workstations.module#ManageWorkstationsModule'
+}];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+
+export class AdminWsRoutingModule {}
--- /dev/null
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {WorkstationsComponent} from './workstations.component';
+
+// Note that we need a path value (e.g. 'manage') because without it
+// there is nothing for the router to match, unless we rely on the parent
+// module to handle all of our routing for us.
+const routes: Routes = [
+ {
+ path: 'manage',
+ component: WorkstationsComponent
+ }, {
+ path: 'remove/:remove',
+ component: WorkstationsComponent
+ }
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+
+export class WorkstationsRoutingModule {
+}
+
--- /dev/null
+<eg-staff-banner bannerText="Workstation Administration" i18n-bannerText>
+</eg-staff-banner>
+
+<!-- this will remain hidden until opened -->
+<eg-confirm-dialog
+ #workstationExistsDialog
+ i18n-dialogTitle i18n-dialogBody
+ dialogTitle="Workstation Exists"
+ dialogBody='Workstation "{{newName}}" already exists. Use it anyway?'>
+</eg-confirm-dialog>
+
+<div class="row">
+ <div class="col-lg-8 offset-1 mt-3">
+ <div class="alert alert-warning" *ngIf="removeWorkstation" i18n>
+ Workstation {{removeWorkstation}} 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" i18n>Register a New Workstation For This Browser</div>
+ </div>
+ <div class="row mt-2">
+ <div class="col-lg-2">
+ <eg-org-select
+ [applyDefault]="true"
+ (onChange)="orgOnChange($event)"
+ [hideOrgs]="hideOrgs"
+ [disableOrgs]="disableOrgs"
+ i18n-placeholder
+ placeholder="Owner..." >
+ </eg-org-select>
+ </div>
+ <div class="col-lg-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-outline-dark"
+ [disabled]="!newName || !newOwner"
+ (click)="registerWorkstation()">
+ <span i18n>Register</span>
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <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 class="row">
+ <div class="col-lg-8">
+ <select class="form-control" [(ngModel)]="selectedName">
+ <option *ngFor="let ws of workstations" value="{{ws.name}}">
+ <span *ngIf="ws.name == defaultName" i18n>
+ {{ws.name}} (Default)
+ </span>
+ <span *ngIf="ws.name != defaultName">
+ {{ws.name}}
+ </span>
+ </option>
+ </select>
+ </div>
+ </div>
+ <div class="row mt-2">
+ <div class="col-lg-6">
+ <button i18n class="btn btn-success"
+ (click)="useNow()" [disabled]="!selected">
+ Use Now
+ </button>
+ <button i18n class="btn btn-outline-dark"
+ (click)="setDefault()" [disabled]="!selected">
+ Mark As Default
+ </button>
+ <button i18n class="btn btn-danger"
+ (click)="removeSelected()"
+ [disabled]="!selected || !canDeleteSelected()">
+ Remove
+ </button>
+ </div>
+ </div>
+ </div>
+</div>
+
--- /dev/null
+import {Component, OnInit, ViewChild} from '@angular/core';
+import {Router, ActivatedRoute} from '@angular/router';
+import {StoreService} from '@eg/core/store.service';
+import {IdlObject} from '@eg/core/idl.service';
+import {NetService} from '@eg/core/net.service';
+import {PermService} from '@eg/core/perm.service';
+import {AuthService} from '@eg/core/auth.service';
+import {OrgService} from '@eg/core/org.service';
+import {EventService} from '@eg/core/event.service';
+import {ConfirmDialogComponent} from '@eg/share/dialog/confirm.component';
+
+// 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 WorkstationsComponent implements OnInit {
+
+ selectedName: string;
+ workstations: Workstation[] = [];
+ removeWorkstation: string;
+ newOwner: IdlObject;
+ newName: string;
+ defaultName: string;
+
+ @ViewChild('workstationExistsDialog')
+ private wsExistsDialog: ConfirmDialogComponent;
+
+ // Org selector options.
+ hideOrgs: number[];
+ disableOrgs: number[];
+ orgOnChange = (org: IdlObject): void => {
+ this.newOwner = org;
+ }
+
+ constructor(
+ private router: Router,
+ private route: ActivatedRoute,
+ private evt: EventService,
+ private net: NetService,
+ private store: StoreService,
+ private auth: AuthService,
+ private org: OrgService,
+ private perm: PermService
+ ) {}
+
+ ngOnInit() {
+ this.workstations = this.store.getLocalItem('eg.workstation.all') || [];
+ this.defaultName = this.store.getLocalItem('eg.workstation.default');
+ this.selectedName = this.auth.workstation() || this.defaultName;
+ const rm = this.route.snapshot.paramMap.get('remove');
+ if (rm) {
+ this.removeSelected(this.removeWorkstation = rm);
+ }
+
+ // TODO: use the org selector limitPerm option
+ this.perm.hasWorkPermAt(['REGISTER_WORKSTATION'], true)
+ .then(perms => {
+ // Disable org units that cannot have users and any
+ // that this user does not have work perms for.
+ this.disableOrgs =
+ this.org.filterList({canHaveUsers : false}, true)
+ .concat(this.org.filterList(
+ {notInList : perms.REGISTER_WORKSTATION}, true));
+ });
+ }
+
+ selected(): Workstation {
+ return this.workstations.filter(
+ ws => ws.name === this.selectedName)[0];
+ }
+
+ useNow(): void {
+ if (this.selected()) {
+ this.router.navigate(['/staff/login'],
+ {queryParams: {workstation: this.selected().name}});
+ }
+ }
+
+ setDefault(): void {
+ if (this.selected()) {
+ this.defaultName = this.selected().name;
+ this.store.setLocalItem('eg.workstation.default', this.defaultName);
+ }
+ }
+
+ removeSelected(name?: string): void {
+ if (!name) {
+ name = this.selected().name;
+ }
+
+ this.workstations = this.workstations.filter(w => w.name !== name);
+ this.store.setLocalItem('eg.workstation.all', this.workstations);
+
+ if (this.defaultName === name) {
+ this.defaultName = null;
+ this.store.removeLocalItem('eg.workstation.default');
+ }
+ }
+
+ canDeleteSelected(): boolean {
+ return true;
+ }
+
+ registerWorkstation(): void {
+ console.log(`Registering new workstation ` +
+ `"${this.newName}" at ${this.newOwner.shortname()}`);
+
+ this.newName = this.newOwner.shortname() + '-' + this.newName;
+
+ this.registerWorkstationApi().then(
+ wsId => this.registerWorkstationLocal(wsId),
+ notOk => console.log('Workstation registration canceled/failed')
+ );
+ }
+
+ private handleCollision(): Promise<number> {
+ return new Promise((resolve, reject) => {
+ this.wsExistsDialog.open()
+ .then(
+ confirmed => {
+ this.registerWorkstationApi(true).then(
+ wsId => resolve(wsId),
+ notOk => reject(notOk)
+ );
+ },
+ dismissed => reject(dismissed)
+ );
+ });
+ }
+
+
+ private registerWorkstationApi(override?: boolean): Promise<number> {
+ let method = 'open-ils.actor.workstation.register';
+ if (override) {
+ method += '.override';
+ }
+
+ return new Promise((resolve, reject) => {
+ this.net.request(
+ 'open-ils.actor', method,
+ this.auth.token(), this.newName, this.newOwner.id()
+ ).subscribe(wsId => {
+ const evt = this.evt.parse(wsId);
+ if (evt) {
+ if (evt.textcode === 'WORKSTATION_NAME_EXISTS') {
+ this.handleCollision().then(
+ id => resolve(id),
+ notOk => reject(notOk)
+ );
+ } else {
+ console.error(`Registration failed ${evt}`);
+ reject();
+ }
+ } else {
+ resolve(wsId);
+ }
+ });
+ });
+ }
+
+ private registerWorkstationLocal(wsId: number) {
+ const ws: Workstation = {
+ id: wsId,
+ name: this.newName,
+ owning_lib: this.newOwner.id()
+ };
+
+ this.workstations.push(ws);
+ this.store.setLocalItem('eg.workstation.all', this.workstations);
+ this.newName = '';
+ // when registering our first workstation, mark it as the
+ // default and show it as selected in the ws selector.
+ if (this.workstations.length === 1) {
+ this.selectedName = ws.name;
+ this.setDefault();
+ }
+ }
+}
+
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {StaffCommonModule} from '@eg/staff/common.module';
+import {WorkstationsRoutingModule} from './routing.module';
+import {WorkstationsComponent} from './workstations.component';
+
+@NgModule({
+ declarations: [
+ WorkstationsComponent,
+ ],
+ imports: [
+ StaffCommonModule,
+ WorkstationsRoutingModule
+ ]
+})
+
+export class ManageWorkstationsModule {}
+
+
--- /dev/null
+<!-- search form sits atop every catalog page -->
+<eg-catalog-search-form></eg-catalog-search-form>
+
+<!-- search results, record details, etc. -->
+<router-outlet></router-outlet>
+
--- /dev/null
+import {Component, OnInit} from '@angular/core';
+import {StaffCatalogService} from './catalog.service';
+
+@Component({
+ templateUrl: 'catalog.component.html'
+})
+export class CatalogComponent implements OnInit {
+
+ constructor(private staffCat: StaffCatalogService) {}
+
+ ngOnInit() {
+ // 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();
+ }
+}
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {StaffCommonModule} from '@eg/staff/common.module';
+import {CatalogCommonModule} from '@eg/share/catalog/catalog-common.module';
+import {CatalogRoutingModule} from './routing.module';
+import {CatalogComponent} from './catalog.component';
+import {SearchFormComponent} from './search-form.component';
+import {ResultsComponent} from './result/results.component';
+import {RecordComponent} from './record/record.component';
+import {CopiesComponent} from './record/copies.component';
+import {ResultPaginationComponent} from './result/pagination.component';
+import {ResultFacetsComponent} from './result/facets.component';
+import {ResultRecordComponent} from './result/record.component';
+import {StaffCatalogService} from './catalog.service';
+import {RecordPaginationComponent} from './record/pagination.component';
+import {RecordActionsComponent} from './record/actions.component';
+import {HoldingsService} from '@eg/staff/share/holdings.service';
+
+@NgModule({
+ declarations: [
+ CatalogComponent,
+ ResultsComponent,
+ RecordComponent,
+ CopiesComponent,
+ SearchFormComponent,
+ ResultRecordComponent,
+ ResultFacetsComponent,
+ ResultPaginationComponent,
+ RecordPaginationComponent,
+ RecordActionsComponent
+ ],
+ imports: [
+ StaffCommonModule,
+ CatalogCommonModule,
+ CatalogRoutingModule
+ ],
+ providers: [
+ StaffCatalogService,
+ HoldingsService
+ ]
+})
+
+export class CatalogModule {
+
+}
--- /dev/null
+import {Injectable} from '@angular/core';
+import {Router, ActivatedRoute} from '@angular/router';
+import {IdlObject} from '@eg/core/idl.service';
+import {OrgService} from '@eg/core/org.service';
+import {CatalogService} from '@eg/share/catalog/catalog.service';
+import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service';
+import {CatalogSearchContext} from '@eg/share/catalog/search-context';
+
+/**
+ * Shared bits needed by the staff version of the catalog.
+ */
+
+@Injectable()
+export class StaffCatalogService {
+
+ searchContext: CatalogSearchContext;
+ routeIndex = 0;
+ defaultSearchOrg: IdlObject;
+ defaultSearchLimit: number;
+
+ // TODO: does unapi support pref-lib for result-page copy counts?
+ prefOrg: IdlObject;
+
+ // Cache the currently selected detail record (i.g. catalog/record/123)
+ // summary so the record detail component can avoid duplicate fetches
+ // during record tab navigation.
+ currentDetailRecordSummary: any;
+
+ constructor(
+ private router: Router,
+ private route: ActivatedRoute,
+ private org: OrgService,
+ private cat: CatalogService,
+ private catUrl: CatalogUrlService
+ ) { }
+
+ createContext(): void {
+ // Initialize the search context from the load-time URL params.
+ // Do this here so the search form and other context data are
+ // applied on every page, not just the search results page. The
+ // search results pages will handle running the actual search.
+ this.searchContext =
+ this.catUrl.fromUrlParams(this.route.snapshot.queryParamMap);
+
+ 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();
+ }
+
+ if (!this.searchContext.pager.limit) {
+ this.searchContext.pager.limit = this.defaultSearchLimit || 20;
+ }
+ }
+
+ /**
+ * Redirect to the search results page while propagating the current
+ * search paramters into the URL. Let the search results component
+ * execute the actual search.
+ */
+ search(): void {
+ if (!this.searchContext.isSearchable()) { return; }
+
+ const 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
+ // exits early when the route + params is identical, add a
+ // random token to the route params to force a full navigation.
+ // This also resolves a problem where only removing secondary+
+ // versions of a query param fail to cause a route navigation.
+ // (E.g. going from two query= params to one). Investigation
+ // pending.
+ params.ridx = '' + this.routeIndex++;
+
+ this.router.navigate(
+ ['/staff/catalog/search'], {queryParams: params});
+ }
+
+}
+
+
--- /dev/null
+
+<eg-string key="catalog.record.toast.conjoined"
+ i18n-text text="Conjoined Record Target Set"></eg-string>
+<eg-string key="catalog.record.toast.overlay"
+ i18n-text text="Record Overlay Target Set"></eg-string>
+<eg-string key="catalog.record.toast.holdTransfer"
+ i18n-text text="Hold Transfer Target Set"></eg-string>
+<eg-string key="catalog.record.toast.volumeTransfer"
+ i18n-text text="Volume Transfer Target Set"></eg-string>
+<eg-string key="catalog.record.toast.cleared"
+ text="Record Marks Cleared"></eg-string>
+
+<eg-record-bucket-dialog #recordBucketDialog [recordId]="recId">
+</eg-record-bucket-dialog>
+
+<div class="row ml-0 mr-0">
+
+ <button class="btn btn-info ml-1" (click)="addVolumes()" i18n>
+ Add Volumes
+ </button>
+
+ <div ngbDropdown placement="bottom-right" class="ml-1">
+ <button class="btn btn-info" id="actionsForDd"
+ ngbDropdownToggle i18n>Mark For...</button>
+ <div ngbDropdownMenu aria-labelledby="actionsForDd">
+ <button class="dropdown-item" (click)="mark('conjoined')">
+ <span i18n>
+ Conjoined Items<ng-container *ngIf="targets.conjoined.current">
+ (Currently {{targets.conjoined.current}})</ng-container>
+ </span>
+ </button>
+ <button class="dropdown-item" (click)="mark('overlay')">
+ <span i18n>
+ Overlay Target<ng-container *ngIf="targets.overlay.current">
+ (Currently {{targets.overlay.current}})</ng-container>
+ </span>
+ </button>
+ <button class="dropdown-item" (click)="mark('holdTransfer')">
+ <span i18n>
+ Title Hold Transfer<ng-container *ngIf="targets.holdTransfer.current">
+ (Currently {{targets.holdTransfer.current}})</ng-container>
+ </span>
+ </button>
+ <button class="dropdown-item" (click)="mark('volumeTransfer')">
+ <span i18n>
+ Volume Transfer<ng-container *ngIf="targets.volumeTransfer.current">
+ (Currently {{targets.volumeTransfer.current}})</ng-container>
+ </span>
+ </button>
+ <button class="dropdown-item" (click)="clearMarks()">
+ <span i18n>Reset Record Marks</span>
+ </button>
+ </div>
+ </div>
+
+ <div ngbDropdown placement="bottom-right" class="ml-1">
+ <button class="btn btn-info" id="otherActionsForDd"
+ ngbDropdownToggle i18n>Other Actions</button>
+ <div ngbDropdownMenu aria-labelledby="otherActionsForDd">
+ <button class="dropdown-item" (click)="recordBucketDialog.open({size: 'lg'})">
+ <span i18n>Add To Bucket</span>
+ </button>
+ <a class="dropdown-item"
+ href="/eg/staff/acq/legacy/lineitem/related/{{recId}}?target=bib">
+ <span i18n>View/Place Orders</span>
+ </a>
+ </div>
+ </div>
+</div>
+
--- /dev/null
+import {Component, OnInit, Input} from '@angular/core';
+import {Router} from '@angular/router';
+import {StoreService} from '@eg/core/store.service';
+import {CatalogService} from '@eg/share/catalog/catalog.service';
+import {CatalogSearchContext} from '@eg/share/catalog/search-context';
+import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service';
+import {StaffCatalogService} from '../catalog.service';
+import {StringService} from '@eg/share/string/string.service';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {HoldingsService} from '@eg/staff/share/holdings.service';
+
+@Component({
+ selector: 'eg-catalog-record-actions',
+ templateUrl: 'actions.component.html'
+})
+export class RecordActionsComponent implements OnInit {
+
+ recId: number;
+ initDone = false;
+ searchContext: CatalogSearchContext;
+
+ targets = {
+ conjoined: {
+ key: 'eg.cat.marked_conjoined_record',
+ current: null
+ },
+ overlay: {
+ key: 'eg.cat.marked_overlay_record',
+ current: null
+ },
+ holdTransfer: {
+ key: 'eg.circ.hold.title_transfer_target',
+ current: null
+ },
+ volumeTransfer: {
+ key: 'eg.cat.marked_volume_transfer_record',
+ current: null
+ }
+ };
+
+ @Input() set recordId(recId: number) {
+ this.recId = recId;
+ if (this.initDone) {
+ // Fire any record specific actions here
+ }
+ }
+
+ constructor(
+ private router: Router,
+ private store: StoreService,
+ private strings: StringService,
+ private toast: ToastService,
+ private cat: CatalogService,
+ private catUrl: CatalogUrlService,
+ private staffCat: StaffCatalogService,
+ private holdings: HoldingsService
+ ) {}
+
+ ngOnInit() {
+ this.initDone = true;
+
+ Object.keys(this.targets).forEach(name => {
+ const target = this.targets[name];
+ target.current = this.store.getLocalItem(target.key);
+ });
+ }
+
+ mark(name: string) {
+ const target = this.targets[name];
+ target.current = this.recId;
+ this.store.setLocalItem(target.key, this.recId);
+ this.strings.interpolate('catalog.record.toast.' + name)
+ .then(txt => this.toast.success(txt));
+ }
+
+ clearMarks() {
+ Object.keys(this.targets).forEach(name => {
+ const target = this.targets[name];
+ target.current = null;
+ this.store.removeLocalItem(target.key);
+ });
+ this.strings.interpolate('catalog.record.toast.cleared')
+ .then(txt => this.toast.success(txt));
+ }
+
+ // TODO: Support adding copies to existing volumes by getting
+ // selected volumes from the holdings grid.
+ // TODO: Support adding like volumes by getting selected
+ // volumes from the holdings grid.
+ addVolumes() {
+ this.holdings.spawnAddHoldingsUi(this.recId);
+ }
+
+}
+
+
--- /dev/null
+<ng-template #cnTemplate let-copy="row">
+ {{copy.call_number_prefix_label}}
+ {{copy.call_number_label}}
+ {{copy.call_number_suffix_label}}
+</ng-template>
+
+<ng-template #barcodeTemplate let-copy="row">
+ <div>{{copy.barcode}}</div>
+ <div>
+ <a class="pl-1" href="/eg/staff/cat/item/{{copy.id}}" i18n>View</a>
+ |
+ <a class="pl-1" href="/eg/staff/cat/item/{{copy.id}}/edit" i18n>Edit</a>
+ </div>
+</ng-template>
+
+<ng-template #holdableTemplate let-copy="row" let-context="userContext">
+ <span *ngIf="context.holdable(copy)" i18n>Yes</span>
+ <span *ngIf="!context.holdable(copy)" i18n>No</span>
+</ng-template>
+
+<div class='eg-copies w-100 mt-3'>
+ <eg-grid #copyGrid [dataSource]="gridDataSource"
+ [sortable]="false" persistKey="catalog.record.copies">
+ <eg-grid-column i18n-label label="Copy ID" path="id"
+ [hidden]="true" [index]="true">
+ </eg-grid-column>
+ <eg-grid-column i18n-label label="Location" path="circ_lib" datatype="org_unit">
+ </eg-grid-column>
+ <eg-grid-column i18n-label label="Call Number / Copy Notes"
+ name="callnumber" [cellTemplate]="cnTemplate">
+ </eg-grid-column>
+ <eg-grid-column i18n-label label="Barcode" name="barcode"
+ [cellTemplate]="barcodeTemplate">
+ </eg-grid-column>
+ <eg-grid-column i18n-label label="Shelving Location" path="copy_location">
+ </eg-grid-column>
+ <eg-grid-column i18n-label label="Circulation Modifier" path="circ_modifier">
+ </eg-grid-column>
+ <eg-grid-column i18n-label label="Age Hold Protection" path="age_protect">
+ </eg-grid-column>
+ <eg-grid-column i18n-label label="Active/Create Date"
+ path="active_date" datatype="timestamp">
+ </eg-grid-column>
+ <eg-grid-column i18n-label label="Holdable?" name="holdable"
+ [cellTemplate]="holdableTemplate" [cellContext]="copyContext">
+ </eg-grid-column>
+ <eg-grid-column i18n-label label="Status" path="copy_status">
+ </eg-grid-column>
+ <eg-grid-column i18n-label label="Due Date" path="due_date" datatype="timestamp">
+ </eg-grid-column>
+ </eg-grid>
+</div>
+
--- /dev/null
+import {Component, OnInit, Input, ViewChild} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {map} from 'rxjs/operators/map';
+import {of} from 'rxjs';
+import {NetService} from '@eg/core/net.service';
+import {StaffCatalogService} from '../catalog.service';
+import {Pager} from '@eg/share/util/pager';
+import {OrgService} from '@eg/core/org.service';
+import {GridDataSource} from '@eg/share/grid/grid';
+import {GridComponent} from '@eg/share/grid/grid.component';
+
+@Component({
+ selector: 'eg-catalog-copies',
+ templateUrl: 'copies.component.html'
+})
+export class CopiesComponent implements OnInit {
+
+ recId: number;
+ initDone = false;
+ gridDataSource: GridDataSource;
+ copyContext: any; // grid context
+ @ViewChild('copyGrid') copyGrid: GridComponent;
+
+ @Input() set recordId(id: number) {
+ this.recId = id;
+ // Only force new data collection when recordId()
+ // is invoked after ngInit() has already run.
+ if (this.initDone) {
+ this.copyGrid.reload();
+ }
+ }
+
+ constructor(
+ private net: NetService,
+ private org: OrgService,
+ private staffCat: StaffCatalogService,
+ ) {
+ this.gridDataSource = new GridDataSource();
+ }
+
+ ngOnInit() {
+ this.initDone = true;
+
+ this.gridDataSource.getRows = (pager: Pager, sort: any[]) => {
+ // sorting not currently supported
+ return this.fetchCopies(pager);
+ };
+
+ this.copyContext = {
+ holdable: (copy: any) => {
+ return copy.holdable === 't'
+ && copy.location_holdable === 't'
+ && copy.status_holdable === 't';
+ }
+ };
+ }
+
+ collectData() {
+ if (!this.recId) { return; }
+ }
+
+ orgName(orgId: number): string {
+ return this.org.get(orgId).shortname();
+ }
+
+ fetchCopies(pager: Pager): Observable<any> {
+ if (!this.recId) { return of([]); }
+
+ // "Show Result from All Libraries" i.e. global search displays
+ // copies from all branches, sorted by search/pref libs.
+ const copy_depth = this.staffCat.searchContext.global ?
+ this.org.root().ou_type().depth() :
+ this.staffCat.searchContext.searchOrg.ou_type().depth();
+
+ return this.net.request(
+ 'open-ils.search',
+ 'open-ils.search.bib.copies.staff',
+ this.recId,
+ this.staffCat.searchContext.searchOrg.id(),
+ copy_depth,
+ pager.limit,
+ pager.offset,
+ this.staffCat.prefOrg ? this.staffCat.prefOrg.id() : null
+ ).pipe(map(copy => {
+ copy.active_date = copy.active_date || copy.create_date;
+ return copy;
+ }));
+ }
+}
+
+
--- /dev/null
+<ul class="pagination mb-0" *ngIf="index !== null">
+ <li class="page-item" [ngClass]="{disabled : index == 0}">
+ <a class="no-href page-link"
+ i18n-aria-label aria-label="Start" (click)="firstRecord()">
+ <span i18n>Start</span>
+ </a>
+ </li>
+ <li class="page-item" [ngClass]="{disabled : index == 0}">
+ <a class="no-href page-link"
+ i18n-aria-label aria-label="Previous" (click)="prevRecord()">
+ <span i18n>Previous</span>
+ </a>
+ </li>
+ <li class="page-item"
+ [ngClass]="{disabled : index >= searchContext.result.count - 1}">
+ <a class="no-href page-link"
+ i18n-aria-label aria-label="Next" (click)="nextRecord()">
+ <span i18n>Next</span>
+ </a>
+ </li>
+ <li class="page-item"
+ [ngClass]="{disabled : index >= searchContext.result.count - 1}">
+ <a class="no-href page-link"
+ i18n-aria-label aria-label="End" (click)="lastRecord()">
+ <span i18n>End</span>
+ </a>
+ </li>
+ <li class="page-item">
+ <a class="no-href page-link"
+ i18n-aria-label aria-label="Back to Results" (click)="returnToSearch()">
+ <span i18n>
+ Back to Results ({{index + 1}} / {{searchContext.result.count}})
+ </span>
+ </a>
+ </li>
+</ul>
--- /dev/null
+import {Component, OnInit, Input} from '@angular/core';
+import {Router} from '@angular/router';
+import {CatalogService} from '@eg/share/catalog/catalog.service';
+import {CatalogSearchContext} from '@eg/share/catalog/search-context';
+import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service';
+import {StaffCatalogService} from '../catalog.service';
+import {Pager} from '@eg/share/util/pager';
+
+
+@Component({
+ selector: 'eg-catalog-record-pagination',
+ templateUrl: 'pagination.component.html'
+})
+export class RecordPaginationComponent implements OnInit {
+
+ id: number;
+ index: number;
+ initDone = false;
+ searchContext: CatalogSearchContext;
+
+ @Input() set recordId(id: number) {
+ this.id = id;
+ // Only apply new record data after the initial load
+ if (this.initDone) {
+ this.setIndex();
+ }
+ }
+
+ constructor(
+ private router: Router,
+ private cat: CatalogService,
+ private catUrl: CatalogUrlService,
+ private staffCat: StaffCatalogService,
+ ) {}
+
+ ngOnInit() {
+ this.initDone = true;
+ this.setIndex();
+ }
+
+ firstRecord(): void {
+ this.findRecordAtIndex(0).then(id => {
+ const params = this.catUrl.toUrlParams(this.searchContext);
+ this.router.navigate(
+ ['/staff/catalog/record/' + id], {queryParams: params});
+ });
+ }
+
+ lastRecord(): void {
+ this.findRecordAtIndex(
+ this.searchContext.result.count - 1
+ ).then(id => {
+ const params = this.catUrl.toUrlParams(this.searchContext);
+ this.router.navigate(
+ ['/staff/catalog/record/' + id], {queryParams: params});
+ });
+ }
+
+ nextRecord(): void {
+ this.findRecordAtIndex(this.index + 1).then(id => {
+ const params = this.catUrl.toUrlParams(this.searchContext);
+ this.router.navigate(
+ ['/staff/catalog/record/' + id], {queryParams: params});
+ });
+ }
+
+ prevRecord(): void {
+ this.findRecordAtIndex(this.index - 1).then(id => {
+ const params = this.catUrl.toUrlParams(this.searchContext);
+ this.router.navigate(
+ ['/staff/catalog/record/' + id], {queryParams: params});
+ });
+ }
+
+
+ // Returns the offset of the record within the search results as a whole.
+ searchIndex(idx: number): number {
+ return idx + this.searchContext.pager.offset;
+ }
+
+ // Find the position of the current record in the search results
+ // If no results are present or the record is not found, expand
+ // the search scope to find the record.
+ setIndex(): Promise<void> {
+ this.searchContext = this.staffCat.searchContext;
+ this.index = null;
+
+ return new Promise((resolve, reject) => {
+
+ this.index = this.searchContext.indexForResult(this.id);
+ if (this.index !== null) {
+ return resolve();
+ }
+
+ return this.refreshSearch().then(ok => {
+ this.index = this.searchContext.indexForResult(this.id);
+ if (this.index === null) {
+ console.warn(
+ 'No search results found containing the focused record.');
+ }
+ resolve();
+ });
+ });
+ }
+
+ // Find the record ID at the specified search index.
+ // If no data exists for the requested index, expand the search
+ // to include data for that index.
+ findRecordAtIndex(index: number): Promise<number> {
+
+ // First see if the selected record sits in the current page
+ // of search results.
+ return new Promise((resolve, reject) => {
+ const id = this.searchContext.resultIdAt(index);
+ if (id) { return resolve(id); }
+
+ console.debug(
+ 'Record paginator unable to find record at index ' + index);
+
+ // If we have to re-run the search to find the record,
+ // expand the search limit out just enough to find the
+ // requested record plus one more.
+ return this.refreshSearch(index + 2).then(
+ ok => {
+ const rid = this.searchContext.resultIdAt(index);
+ if (rid) {
+ resolve(rid);
+ } else {
+ reject('no record found');
+ }
+ }
+ );
+ });
+ }
+
+ refreshSearch(limit?: number): Promise<any> {
+
+ console.debug('paginator refreshing search');
+
+ if (!this.searchContext.isSearchable()) {
+ return Promise.resolve();
+ }
+
+ const origPager = this.searchContext.pager;
+ const tmpPager = new Pager();
+ tmpPager.limit = limit || 1000;
+
+ this.searchContext.pager = tmpPager;
+
+ return this.cat.search(this.searchContext)
+ .then(
+ ok => this.searchContext.pager = origPager,
+ notOk => this.searchContext.pager = origPager
+ );
+ }
+
+ returnToSearch(): void {
+ // Fire the main search. This will direct us back to /results/
+ this.staffCat.search();
+ }
+
+}
+
+
--- /dev/null
+
+<div id="staff-catalog-record-container">
+ <div class="row ml-0 mr-0">
+ <div id='staff-catalog-bib-navigation'>
+ <div *ngIf="searchContext.isSearchable()">
+ <eg-catalog-record-pagination [recordId]="recordId">
+ </eg-catalog-record-pagination>
+ </div>
+ </div>
+ <!-- push the actions component to the right -->
+ <div class="flex-1"></div>
+ <div id='staff-catalog-bib-navigation'>
+ <eg-catalog-record-actions [recordId]="recordId">
+ </eg-catalog-record-actions>
+ </div>
+ </div>
+ <div id='staff-catalog-bib-summary-container' class='mt-1'>
+ <eg-bib-summary [bibSummary]="summary">
+ </eg-bib-summary>
+ </div>
+ <div id='staff-catalog-bib-tabs-container' class='mt-3'>
+ <ngb-tabset #recordTabs [activeId]="recordTab" (tabChange)="onTabChange($event)">
+ <ngb-tab title="Copy Table" i18n-title id="copy_table">
+ <ng-template ngbTabContent>
+ <eg-catalog-copies [recordId]="recordId"></eg-catalog-copies>
+ </ng-template>
+ </ngb-tab>
+ <ngb-tab title="MARC View" i18n-title id="marc_view">
+ <ng-template ngbTabContent>
+ <eg-marc-html [recordId]="recordId" recordType="bib"></eg-marc-html>
+ </ng-template>
+ </ngb-tab>
+ </ngb-tabset>
+ </div>
+</div>
+
+
--- /dev/null
+import {Component, OnInit, Input, ViewChild} from '@angular/core';
+import {NgbTabset, NgbTabChangeEvent} from '@ng-bootstrap/ng-bootstrap';
+import {Router, ActivatedRoute, ParamMap} from '@angular/router';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {IdlObject} from '@eg/core/idl.service';
+import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search-context';
+import {CatalogService} from '@eg/share/catalog/catalog.service';
+import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.service';
+import {StaffCatalogService} from '../catalog.service';
+import {BibSummaryComponent} from '@eg/staff/share/bib-summary/bib-summary.component';
+
+@Component({
+ selector: 'eg-catalog-record',
+ templateUrl: 'record.component.html'
+})
+export class RecordComponent implements OnInit {
+
+ recordId: number;
+ recordTab: string;
+ summary: BibRecordSummary;
+ searchContext: CatalogSearchContext;
+ @ViewChild('recordTabs') recordTabs: NgbTabset;
+
+ constructor(
+ private router: Router,
+ private route: ActivatedRoute,
+ private pcrud: PcrudService,
+ private bib: BibRecordService,
+ private cat: CatalogService,
+ private staffCat: StaffCatalogService
+ ) {}
+
+ ngOnInit() {
+ this.searchContext = this.staffCat.searchContext;
+
+ // Watch for URL record ID changes
+ this.route.paramMap.subscribe((params: ParamMap) => {
+ this.recordTab = params.get('tab') || 'copy_table';
+ this.recordId = +params.get('id');
+ this.searchContext = this.staffCat.searchContext;
+ this.loadRecord();
+ });
+ }
+
+ // Changing a tab in the UI means changing the route.
+ // Changing the route ultimately results in changing the tab.
+ onTabChange(evt: NgbTabChangeEvent) {
+ this.recordTab = evt.nextId;
+
+ // prevent tab changing until after route navigation
+ evt.preventDefault();
+
+ let url = '/staff/catalog/record/' + this.recordId;
+ if (this.recordTab !== 'copy_table') {
+ url += '/' + this.recordTab;
+ }
+
+ // Retain search parameters
+ this.router.navigate([url], {queryParamsHandling: 'merge'});
+ }
+
+ loadRecord(): void {
+
+ // Avoid re-fetching the same record summary during tab navigation.
+ if (this.staffCat.currentDetailRecordSummary &&
+ this.recordId === this.staffCat.currentDetailRecordSummary.id) {
+ this.summary = this.staffCat.currentDetailRecordSummary;
+ return;
+ }
+
+ this.summary = null;
+ this.bib.getBibSummary(
+ this.recordId,
+ this.searchContext.searchOrg.id(),
+ this.searchContext.searchOrg.ou_type().depth()).toPromise()
+ .then(summary => {
+ this.summary =
+ this.staffCat.currentDetailRecordSummary = summary;
+ this.bib.fleshBibUsers([summary.record]);
+ });
+ }
+}
+
+
--- /dev/null
+import {Injectable} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {Observer} from 'rxjs/Observer';
+import {Router, Resolve, RouterStateSnapshot,
+ ActivatedRouteSnapshot} from '@angular/router';
+import {ServerStoreService} from '@eg/core/server-store.service';
+import {NetService} from '@eg/core/net.service';
+import {OrgService} from '@eg/core/org.service';
+import {AuthService} from '@eg/core/auth.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {CatalogService} from '@eg/share/catalog/catalog.service';
+import {StaffCatalogService} from './catalog.service';
+
+@Injectable()
+export class CatalogResolver implements Resolve<Promise<any[]>> {
+
+ constructor(
+ private router: Router,
+ private store: ServerStoreService,
+ private org: OrgService,
+ private net: NetService,
+ private auth: AuthService,
+ private cat: CatalogService,
+ private staffCat: StaffCatalogService
+ ) {}
+
+ resolve(
+ route: ActivatedRouteSnapshot,
+ state: RouterStateSnapshot): Promise<any[]> {
+
+ console.debug('CatalogResolver:resolve()');
+
+ return Promise.all([
+ this.cat.fetchCcvms(),
+ this.cat.fetchCmfs(),
+ this.fetchSettings()
+ ]);
+ }
+
+ fetchSettings(): Promise<any> {
+ const 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);
+ }
+
+}
+
--- /dev/null
+<style>
+ .facet-selected {
+ background-color: #DDD;
+ }
+ .card {
+ width: 100%;
+ }
+ .list-group-item {padding: .5rem .75rem .5rem .75rem}
+</style>
+<div *ngIf="searchContext.result.facetData">
+ <div *ngFor="let facetConf of facetConfig.display">
+ <div *ngIf="searchContext.result.facetData[facetConf.facetClass]">
+ <div *ngFor="let name of facetConf.facetOrder">
+ <div class="row"
+ *ngIf="searchContext.result.facetData[facetConf.facetClass][name]">
+ <div class="card mb-2">
+ <h4 class="card-header">
+ {{searchContext.result.facetData[facetConf.facetClass][name].cmfLabel}}
+ </h4>
+ <ul class="list-group list-group-flush">
+ <li class="list-group-item"
+ [ngClass]="{'facet-selected' :
+ facetIsApplied(facetConf.facetClass, name, value.value)}"
+ *ngFor="
+ let value of searchContext.result.facetData[facetConf.facetClass][name].valueList | slice:0:facetConfig.displayCount">
+ <div class="row">
+ <div class="col-lg-9">
+ <a class="card-link"
+ href='javascript:;'
+ (click)="applyFacet(facetConf.facetClass, name, value.value)">
+ {{value.value}}
+ </a>
+ </div>
+ <div class="col-lg-3">{{value.count}}</div>
+ </div>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
--- /dev/null
+import {Component, OnInit, Input} from '@angular/core';
+import {CatalogService} from '@eg/share/catalog/catalog.service';
+import {CatalogSearchContext, FacetFilter} from '@eg/share/catalog/search-context';
+import {StaffCatalogService} from '../catalog.service';
+
+export const FACET_CONFIG = {
+ display: [
+ {facetClass : 'author', facetOrder : ['personal', 'corporate']},
+ {facetClass : 'subject', facetOrder : ['topic']},
+ {facetClass : 'identifier', facetOrder : ['genre']},
+ {facetClass : 'series', facetOrder : ['seriestitle']},
+ {facetClass : 'subject', facetOrder : ['name', 'geographic']}
+ ],
+ displayCount : 5
+};
+
+@Component({
+ selector: 'eg-catalog-result-facets',
+ templateUrl: 'facets.component.html'
+})
+export class ResultFacetsComponent implements OnInit {
+
+ searchContext: CatalogSearchContext;
+ facetConfig: any;
+
+ constructor(
+ private cat: CatalogService,
+ private staffCat: StaffCatalogService
+ ) {
+ this.facetConfig = FACET_CONFIG;
+ }
+
+ ngOnInit() {
+ this.searchContext = this.staffCat.searchContext;
+ }
+
+ facetIsApplied(cls: string, name: string, value: string): boolean {
+ return this.searchContext.hasFacet(new FacetFilter(cls, name, value));
+ }
+
+ applyFacet(cls: string, name: string, value: string): void {
+ this.searchContext.toggleFacet(new FacetFilter(cls, name, value));
+ this.searchContext.pager.offset = 0;
+ this.staffCat.search();
+ }
+}
+
+
--- /dev/null
+
+/* Bootstrap default is 20px */
+.pagination {margin: 0px 0px 0px 0px}
+
+.pagination li:not(.active) a {
+ cursor: pointer;
+}
+
--- /dev/null
+<!--
+Using bare BS pagination instead of ng-bootstrap, which seemed
+unnecessary given we have to track paging externally anyway.
+-->
+<ul class="pagination">
+ <li class="page-item"
+ [ngClass]="{disabled : searchContext.pager.isFirstPage()}">
+ <a (click)="prevPage()"
+ class="page-link"
+ i18n-aria-label
+ aria-label="Previous">
+ <span aria-hidden="true">«</span>
+ </a>
+ </li>
+ <li class="page-item"
+ *ngFor="let page of currentPageList()"
+ [ngClass]="{active : searchContext.pager.currentPage() == page}">
+ <a class="page-link" (click)="setPage(page)">
+ {{page}} <span class="sr-only" i18n>(current)</span></a>
+ </li>
+ <li class="page-item"
+ [ngClass]="{disabled : searchContext.pager.isLastPage()}">
+ <a (click)="nextPage()"
+ class="page-link" aria-label="Next" i18n-aria-label>
+ <span aria-hidden="true">»</span>
+ </a>
+ </li>
+</ul>
--- /dev/null
+import {Component, OnInit, Input} from '@angular/core';
+import {CatalogService} from '@eg/share/catalog/catalog.service';
+import {CatalogSearchContext} from '@eg/share/catalog/search-context';
+import {StaffCatalogService} from '../catalog.service';
+
+@Component({
+ selector: 'eg-catalog-result-pagination',
+ styleUrls: ['pagination.component.css'],
+ templateUrl: 'pagination.component.html'
+})
+export class ResultPaginationComponent implements OnInit {
+
+ searchContext: CatalogSearchContext;
+
+ // Maximum number of jump-to-page buttons displayed.
+ @Input() numPages: number;
+
+ constructor(
+ private cat: CatalogService,
+ private staffCat: StaffCatalogService
+ ) {
+ this.numPages = 10;
+ }
+
+ ngOnInit() {
+ this.searchContext = this.staffCat.searchContext;
+ }
+
+ currentPageList(): number[] {
+ const pgr = this.searchContext.pager;
+ return pgr.pageRange(pgr.currentPage(), this.numPages);
+ }
+
+ nextPage(): void {
+ this.searchContext.pager.increment();
+ this.staffCat.search();
+ }
+
+ prevPage(): void {
+ this.searchContext.pager.decrement();
+ this.staffCat.search();
+ }
+
+ setPage(page: number): void {
+ if (this.searchContext.pager.currentPage() === page) { return; }
+ this.searchContext.pager.setPage(page);
+ this.staffCat.search();
+ }
+}
+
+
--- /dev/null
+<!--
+ TODO
+ routerLink's
+ egDateFilter's
+-->
+<eg-record-bucket-dialog #addToListDialog>
+</eg-record-bucket-dialog>
+
+<div class="col-lg-12 card tight-card mb-2 bg-light">
+ <div class="card-body">
+ <div class="row">
+ <div class="col-lg-1">
+ <a href="javascript:void(0)" (click)="navigatToRecord(summary.id)">
+ <img style="height:80px"
+ src="/opac/extras/ac/jacket/small/r/{{summary.id}}"/>
+ </a>
+ </div>
+ <div class="col-lg-5">
+ <div class="row">
+ <div class="col-lg-12 font-weight-bold">
+ <!-- nbsp allows the column to take shape when no value exists -->
+ <span class="font-weight-light font-italic">
+ #{{index + 1 + searchContext.pager.offset}}
+ </span>
+ <a href="javascript:void(0)"
+ (click)="navigatToRecord(summary.id)">
+ {{summary.display.title || ' '}}
+ </a>
+ </div>
+ </div>
+ <div class="row pt-2">
+ <div class="col-lg-12">
+ <!-- nbsp allows the column to take shape when no value exists -->
+ <a href="javascript:void(0)"
+ (click)="searchAuthor(summary)">
+ {{summary.display.author || ' '}}
+ </a>
+ </div>
+ </div>
+ <div class="row pt-2">
+ <div class="col-lg-12">
+ <!-- only shows the first icon format -->
+ <span *ngIf="summary.attributes.icon_format && summary.attributes.icon_format[0]">
+ <img class="pr-1"
+ src="/images/format_icons/icon_format/{{summary.attributes.icon_format[0]}}.png"/>
+ <span>{{iconFormatLabel(summary.attributes.icon_format[0])}}</span>
+ </span>
+ <span class='pl-1'>{{summary.display.edition}}</span>
+ <span class='pl-1'>{{summary.display.pubdate}}</span>
+ </div>
+ </div>
+ </div>
+ <div class="col-lg-2">
+ <div class="row" [ngClass]="{'pt-2':copyIndex > 0}"
+ *ngFor="let copyCount of summary.holdingsSummary; let copyIdx = index">
+ <div class="w-100" *ngIf="copyCount.type == 'staff'">
+ <div class="float-left text-left w-50">
+ <span class="pr-1">
+ {{copyCount.available}} / {{copyCount.count}} items
+ </span>
+ </div>
+ <div class="float-left w-50">
+ @ {{orgName(copyCount.org_unit)}}
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="col-lg-1">
+ <div class="row">
+ <div class="w-100">
+ TCN: {{summary.record.tcn_value()}}
+ </div>
+ </div>
+ <div class="row">
+ <div class="w-100">
+ Holds: {{summary.holdCount}}
+ </div>
+ </div>
+ </div>
+ <div class="col-lg-3">
+ <div class="row">
+ <div class="col-lg-12">
+ <div class="float-right small-text-1">
+ Created {{summary.record.create_date() | date:'shortDate'}} by
+ <!-- creator if fleshed after the initial data set is loaded -->
+ <a *ngIf="summary.record.creator().usrname" target="_self"
+ href="/eg/staff/circ/patron/{{summary.record.creator().id()}}/checkout">
+ {{summary.record.creator().usrname()}}
+ </a>
+ <!-- add a spacer pending data to reduce page shuffle -->
+ <span *ngIf="!summary.record.creator().usrname"> ... </span>
+ </div>
+ </div>
+ </div>
+ <div class="row pt-2">
+ <div class="col-lg-12">
+ <div class="float-right small-text-1" i18n>
+ Edited {{summary.record.edit_date() | date:'shortDate'}} by
+ <a *ngIf="summary.record.editor().usrname" target="_self"
+ href="/eg/staff/circ/patron/{{summary.record.editor().id()}}/checkout">
+ {{summary.record.editor().usrname()}}
+ </a>
+ <span *ngIf="!summary.record.editor().usrname"> ... </span>
+ </div>
+ </div>
+ </div>
+ <div class="row pt-2">
+ <div class="col-lg-12">
+ <div class="float-right">
+ <span>
+ <button (click)="placeHold()"
+ class="btn btn-sm btn-success label-with-material-icon small-text-1">
+ <span class="material-icons">check</span>
+ <span i18n>Place Hold</span>
+ </button>
+ </span>
+ <span class="pl-1">
+ <button
+ (click)="addToListDialog.recordId=summary.record.id(); addToListDialog.open({size: 'lg'})"
+ class="btn btn-sm btn-info label-with-material-icon small-text-1">
+ <span class="material-icons">playlist_add_check</span>
+ <span i18n>Add to List</span>
+ </button>
+ </span>
+ </div>
+ </div>
+ </div>
+ </div><!-- col -->
+ </div><!-- row -->
+ </div><!-- card-body -->
+</div><!-- card -->
+
--- /dev/null
+import {Component, OnInit, Input} from '@angular/core';
+import {Router} from '@angular/router';
+import {OrgService} from '@eg/core/org.service';
+import {NetService} from '@eg/core/net.service';
+import {CatalogService} from '@eg/share/catalog/catalog.service';
+import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.service';
+import {CatalogSearchContext} from '@eg/share/catalog/search-context';
+import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service';
+import {StaffCatalogService} from '../catalog.service';
+
+@Component({
+ selector: 'eg-catalog-result-record',
+ templateUrl: 'record.component.html'
+})
+export class ResultRecordComponent implements OnInit {
+
+ @Input() index: number; // 0-index display row
+ @Input() summary: BibRecordSummary;
+ searchContext: CatalogSearchContext;
+
+ constructor(
+ private router: Router,
+ private org: OrgService,
+ private net: NetService,
+ private bib: BibRecordService,
+ private cat: CatalogService,
+ private catUrl: CatalogUrlService,
+ private staffCat: StaffCatalogService
+ ) {}
+
+ ngOnInit() {
+ this.searchContext = this.staffCat.searchContext;
+ this.summary.getHoldCount();
+ }
+
+ orgName(orgId: number): string {
+ return this.org.get(orgId).shortname();
+ }
+
+ iconFormatLabel(code: string): string {
+ if (this.cat.ccvmMap) {
+ const ccvm = this.cat.ccvmMap.icon_format.filter(
+ format => format.code() === code)[0];
+ if (ccvm) {
+ return ccvm.search_label();
+ }
+ }
+ }
+
+ placeHold(): void {
+ alert('Placing hold on bib ' + this.summary.id);
+ }
+
+ addToList(): void {
+ alert('Adding to list for bib ' + this.summary.id);
+ }
+
+ searchAuthor(summary: any) {
+ this.searchContext.reset();
+ this.searchContext.fieldClass = ['author'];
+ this.searchContext.query = [summary.display.author];
+ this.staffCat.search();
+ }
+
+ /**
+ * Propagate the search params along when navigating to each record.
+ */
+ navigatToRecord(id: number) {
+ const params = this.catUrl.toUrlParams(this.searchContext);
+
+ this.router.navigate(
+ ['/staff/catalog/record/' + id], {queryParams: params});
+ }
+
+}
+
+
--- /dev/null
+
+<div id="staff-catalog-results-container" *ngIf="searchIsDone()">
+ <div class="row">
+ <div class="col-lg-2"><!--match pagination margin-->
+ <h3 i18n>Search Results ({{searchContext.result.count}})</h3>
+ </div>
+ <div class="col-lg-1"></div>
+ <div class="col-lg-9">
+ <div class="float-right">
+ <eg-catalog-result-pagination></eg-catalog-result-pagination>
+ </div>
+ </div>
+ </div>
+ <div class="row mt-2">
+ <div class="col-lg-2">
+ <eg-catalog-result-facets></eg-catalog-result-facets>
+ </div>
+ <div class="col-lg-10">
+ <div *ngIf="searchContext.result">
+ <div *ngFor="let summary of searchContext.result.records; let idx = index">
+ <div *ngIf="summary">
+ <eg-catalog-result-record [summary]="summary" [index]="idx">
+ </eg-catalog-result-record>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+
--- /dev/null
+import {Component, OnInit, Input} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {map, switchMap, distinctUntilChanged} from 'rxjs/operators';
+import {ActivatedRoute, ParamMap} from '@angular/router';
+import {CatalogService} from '@eg/share/catalog/catalog.service';
+import {BibRecordService} from '@eg/share/catalog/bib-record.service';
+import {CatalogUrlService} from '@eg/share/catalog/catalog-url.service';
+import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search-context';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {StaffCatalogService} from '../catalog.service';
+import {IdlObject} from '@eg/core/idl.service';
+
+@Component({
+ selector: 'eg-catalog-results',
+ templateUrl: 'results.component.html'
+})
+export class ResultsComponent implements OnInit {
+
+ searchContext: CatalogSearchContext;
+
+ // Cache record creator/editor since this will likely be a
+ // reasonably small set of data w/ lots of repitition.
+ userCache: {[id: number]: IdlObject} = {};
+
+ constructor(
+ private route: ActivatedRoute,
+ private pcrud: PcrudService,
+ private cat: CatalogService,
+ private bib: BibRecordService,
+ private catUrl: CatalogUrlService,
+ private staffCat: StaffCatalogService
+ ) {}
+
+ ngOnInit() {
+ this.searchContext = this.staffCat.searchContext;
+
+ // Our search context is initialized on page load. Once
+ // 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.
+ this.route.queryParamMap.subscribe((params: ParamMap) => {
+
+ // TODO: Angular docs suggest using switchMap(), but
+ // it's not firing for some reason. Also, could avoid
+ // firing unnecessary searches when a param unrelated to
+ // searching is changed by .map()'ing out only the desired
+ // params and running through .distinctUntilChanged(), but
+ // .map() is not firing either. I'm missing something.
+ this.searchByUrl(params);
+ });
+ }
+
+ searchByUrl(params: ParamMap): void {
+ this.catUrl.applyUrlParams(this.searchContext, params);
+
+ if (this.searchContext.isSearchable()) {
+
+ this.cat.search(this.searchContext)
+ .then(ok => {
+ this.cat.fetchFacets(this.searchContext);
+ this.cat.fetchBibSummaries(this.searchContext)
+ .then(ok2 => this.fleshSearchResults());
+ });
+ }
+ }
+
+ fleshSearchResults(): void {
+ const records = this.searchContext.result.records;
+ if (!records || records.length === 0) { return; }
+
+ // Flesh the creator / editor fields with the user object.
+ this.bib.fleshBibUsers(records.map(r => r.record));
+ }
+
+ searchIsDone(): boolean {
+ return this.searchContext.searchState === CatalogSearchState.COMPLETE;
+ }
+
+}
+
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {CatalogComponent} from './catalog.component';
+import {ResultsComponent} from './result/results.component';
+import {RecordComponent} from './record/record.component';
+import {CatalogResolver} from './resolver.service';
+
+const routes: Routes = [{
+ path: '',
+ component: CatalogComponent,
+ resolve: {catResolver : CatalogResolver},
+ children : [{
+ path: 'search',
+ component: ResultsComponent
+ }, {
+ path: 'record/:id',
+ component: RecordComponent
+ }, {
+ path: 'record/:id/:tab',
+ component: RecordComponent
+ }]
+}];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule],
+ providers: [CatalogResolver]
+})
+
+export class CatalogRoutingModule {}
--- /dev/null
+
+/* filter checkbox labels move to bottom */
+.checkbox label {
+ margin-bottom: .1rem;
+}
+
+/* BS default height is 2.25rem + 2px which is quite chunky.
+ * This better matches the text input heights */
+select.form-control:not([size]):not([multiple]) {
+ padding: .355rem .55rem;
+ height: 2.2rem;
+}
+
+#staffcat-search-form {
+ border-bottom: 2px dashed rgba(0,0,0,.225);
+}
--- /dev/null
+<!--
+TODO focus search input
+-->
+<div id='staffcat-search-form' class='pb-2 mb-3'>
+ <div class="row"
+ *ngFor="let q of searchContext.query; let idx = index; trackBy:trackByIdx">
+ <div class="col-lg-9 d-flex">
+ <div class="flex-1">
+ <div *ngIf="idx == 0">
+ <select class="form-control" [(ngModel)]="searchContext.format">
+ <option i18n value=''>All Formats</option>
+ <option *ngFor="let fmt of ccvmMap.search_format"
+ value="{{fmt.code()}}">{{fmt.value()}}</option>
+ </select>
+ </div>
+ <div *ngIf="idx > 0">
+ <select class="form-control"
+ [(ngModel)]="searchContext.joinOp[idx]">
+ <option i18n value='&&'>And</option>
+ <option i18n value='||'>Or</option>
+ </select>
+ </div>
+ </div>
+ <div class="flex-1 pl-1">
+ <select class="form-control"
+ [(ngModel)]="searchContext.fieldClass[idx]">
+ <option i18n value='keyword'>Keyword</option>
+ <option i18n value='title'>Title</option>
+ <option i18n value='jtitle'>Journal Title</option>
+ <option i18n value='author'>Author</option>
+ <option i18n value='subject'>Subject</option>
+ <option i18n value='series'>Series</option>
+ </select>
+ </div>
+ <div class="flex-1 pl-1">
+ <select class="form-control"
+ [(ngModel)]="searchContext.matchOp[idx]">
+ <option i18n value='contains'>Contains</option>
+ <option i18n value='nocontains'>Does not contain</option>
+ <option i18n value='phrase'>Contains phrase</option>
+ <option i18n value='exact'>Matches exactly</option>
+ <option i18n value='starts'>Starts with</option>
+ </select>
+ </div>
+ <div class="flex-2 pl-1">
+ <div class="form-group">
+ <div *ngIf="idx == 0">
+ <input type="text" class="form-control"
+ id='first-query-input'
+ [(ngModel)]="searchContext.query[idx]"
+ (keyup.enter)="formEnter('query')"
+ placeholder="Query..."/>
+ </div>
+ <div *ngIf="idx > 0">
+ <input type="text" class="form-control"
+ [(ngModel)]="searchContext.query[idx]"
+ (keyup.enter)="formEnter('query')"
+ placeholder="Query..."/>
+ </div>
+ </div>
+ </div>
+ <div class="flex-1 pl-1">
+ <button class="btn btn-sm material-icon-button"
+ (click)="addSearchRow(idx + 1)">
+ <span class="material-icons">add_circle_outline</span>
+ </button>
+ <button class="btn btn-sm material-icon-button"
+ [disabled]="searchContext.query.length < 2"
+ (click)="delSearchRow(idx)">
+ <span class="material-icons">remove_circle_outline</span>
+ </button>
+ </div>
+ </div><!-- col -->
+ <div class="col-lg-3">
+ <div *ngIf="idx == 0" class="float-right">
+ <button class="btn btn-success mr-1" type="button"
+ [disabled]="searchIsActive()"
+ (click)="searchContext.pager.offset=0;searchByForm()">
+ Search
+ </button>
+ <button class="btn btn-warning mr-1" type="button"
+ [disabled]="searchIsActive()"
+ (click)="searchContext.reset()">
+ Clear Form
+ </button>
+ <button class="btn btn-outline-secondary" type="button"
+ *ngIf="!showAdvanced()"
+ [disabled]="searchIsActive()"
+ (click)="showAdvancedSearch=true">
+ More Filters
+ </button>
+ <button class="btn btn-outline-secondary" type="button"
+ *ngIf="showAdvanced()"
+ (click)="showAdvancedSearch=false">
+ Hide Filters
+ </button>
+ </div>
+ </div>
+ </div><!-- row -->
+
+ <div class="row">
+ <div class="col-lg-9 d-flex">
+ <div class="flex-1">
+ <eg-org-select
+ (onChange)="orgOnChange($event)"
+ [initialOrg]="searchContext.searchOrg"
+ [placeholder]="'Library'" >
+ </eg-org-select>
+ </div>
+ <div class="flex-3 pl-1">
+ <select class="form-control" [(ngModel)]="searchContext.sort">
+ <option value='' i18n>Sort by Relevance</option>
+ <optgroup label="Sort by Title" i18n-label>
+ <option value='titlesort' i18n>Title: A to Z</option>
+ <option value='titlesort.descending' i18n>Title: Z to A</option>
+ </optgroup>
+ <optgroup label="Sort by Author" i18n-label>
+ <option value='authorsort' i18n>Author: A to Z</option>
+ <option value='authorsort.descending' i18n>Author: Z to A</option>
+ </optgroup>
+ <optgroup label="Sort by Publication Date" i18n-label>
+ <option value='pubdate' i18n>Date: A to Z</option>
+ <option value='pubdate.descending' i18n>Date: Z to A</option>
+ </optgroup>
+ <optgroup label="Sort by Popularity" i18n-label>
+ <option value='popularity' i18n>Most Popular</option>
+ <option value='poprel' i18n>Popularity Adjusted Relevance</option>
+ </optgroup>
+ </select>
+ </div>
+ <div class="flex-2 pl-2 align-self-end">
+ <div class="checkbox">
+ <label>
+ <input type="checkbox" [(ngModel)]="searchContext.available"/>
+ <span i18n>Limit to Available</span>
+ </label>
+ </div>
+ </div>
+ <div class="flex-4 pl-2 align-self-end">
+ <div class="checkbox">
+ <label>
+ <input type="checkbox" [(ngModel)]="searchContext.global"/>
+ <span i18n>Show Results from All Libraries</span>
+ </label>
+ </div>
+ </div>
+ <div class="flex-2 pl-1">
+ <!-- alignment -->
+ </div>
+ </div>
+ <div class="col-lg-3">
+ <div *ngIf="searchIsActive()">
+ <div class="progress">
+ <div class="progress-bar progress-bar-striped active w-100"
+ role="progressbar" aria-valuenow="100"
+ aria-valuemin="0" aria-valuemax="100">
+ <span class="sr-only" i18n>Searching..</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="row pt-2" *ngIf="showAdvanced()">
+ <div class="col-lg-2">
+ <select class="form-control" multiple="true"
+ [(ngModel)]="searchContext.ccvmFilters.item_type">
+ <option value='' i18n>All Item Types</option>
+ <option *ngFor="let itemType of ccvmMap.item_type"
+ value="{{itemType.code()}}">{{itemType.value()}}</option>
+ </select>
+ </div>
+ <div class="col-lg-2">
+ <select class="form-control" multiple="true"
+ [(ngModel)]="searchContext.ccvmFilters.item_form">
+ <option value='' i18n>All Item Forms</option>
+ <option *ngFor="let itemForm of ccvmMap.item_form"
+ value="{{itemForm.code()}}">{{itemForm.value()}}</option>
+ </select>
+ </div>
+ <div class="col-lg-2">
+ <select class="form-control"
+ [(ngModel)]="searchContext.ccvmFilters.item_lang" multiple="true">
+ <option value='' i18n>All Languages</option>
+ <option *ngFor="let lang of ccvmMap.item_lang"
+ value="{{lang.code()}}">{{lang.value()}}</option>
+ </select>
+ </div>
+ <div class="col-lg-2">
+ <select class="form-control"
+ [(ngModel)]="searchContext.ccvmFilters.audience" multiple="true">
+ <option value='' i18n>All Audiences</option>
+ <option *ngFor="let audience of ccvmMap.audience"
+ value="{{audience.code()}}">{{audience.value()}}</option>
+ </select>
+ </div>
+ <div class="col-lg-2">
+ <select class="form-control"
+ [(ngModel)]="searchContext.identQueryType">
+ <option i18n value="identifier|isbn">ISBN</option>
+ <option i18n value="identifier|issn">ISSN</option>
+ <option i18n disabled value="cnbrowse">Call Number (Shelf Browse)</option>
+ <option i18n value="identifier|lccn">LCCN</option>
+ <option i18n value="identifier|tcn">TCN</option>
+ <option i18n disabled value="item_barcode">Item Barcode</option>
+ </select>
+ </div>
+ <div class="col-lg-2">
+ <input id='ident-query-input' type="text" class="form-control"
+ [(ngModel)]="searchContext.identQuery"
+ (keyup.enter)="formEnter('ident')"
+ placeholder="Numeric Query..."/>
+ </div>
+ </div>
+ <div class="row pt-2" *ngIf="showAdvanced()">
+ <div class="col-lg-2">
+ <select class="form-control"
+ [(ngModel)]="searchContext.ccvmFilters.vr_format" multiple="true">
+ <option value='' i18n>All Video Formats</option>
+ <option *ngFor="let vrFormat of ccvmMap.vr_format"
+ value="{{vrFormat.code()}}">{{vrFormat.value()}}</option>
+ </select>
+ </div>
+ <div class="col-lg-2">
+ <select class="form-control"
+ [(ngModel)]="searchContext.ccvmFilters.bib_level" multiple="true">
+ <option value='' i18n>All Bib Levels</option>
+ <option *ngFor="let bibLevel of ccvmMap.bib_level"
+ value="{{bibLevel.code()}}">{{bibLevel.value()}}</option>
+ </select>
+ </div>
+ <div class="col-lg-2">
+ <select class="form-control"
+ [(ngModel)]="searchContext.ccvmFilters.lit_form" multiple="true">
+ <option value='' i18n>All Literary Forms</option>
+ <option *ngFor="let litForm of ccvmMap.lit_form"
+ value="{{litForm.code()}}">{{litForm.value()}}</option>
+ </select>
+ </div>
+ <div class="col-lg-2">
+ <i>Copy location filter goes here...</i>
+ </div>
+ </div>
+</div>
+
--- /dev/null
+import {Component, OnInit, AfterViewInit, Renderer2} from '@angular/core';
+import {IdlObject} from '@eg/core/idl.service';
+import {OrgService} from '@eg/core/org.service';
+import {CatalogService} from '@eg/share/catalog/catalog.service';
+import {CatalogSearchContext, CatalogSearchState} from '@eg/share/catalog/search-context';
+import {StaffCatalogService} from './catalog.service';
+
+@Component({
+ selector: 'eg-catalog-search-form',
+ styleUrls: ['search-form.component.css'],
+ templateUrl: 'search-form.component.html'
+})
+export class SearchFormComponent implements OnInit, AfterViewInit {
+
+ searchContext: CatalogSearchContext;
+ ccvmMap: {[ccvm: string]: IdlObject[]} = {};
+ cmfMap: {[cmf: string]: IdlObject} = {};
+ showAdvancedSearch = false;
+
+ constructor(
+ private renderer: Renderer2,
+ private org: OrgService,
+ private cat: CatalogService,
+ private staffCat: StaffCatalogService
+ ) {}
+
+ ngOnInit() {
+ this.ccvmMap = this.cat.ccvmMap;
+ this.cmfMap = this.cat.cmfMap;
+ this.searchContext = this.staffCat.searchContext;
+
+ // Start with advanced search options open
+ // if any filters are active.
+ this.showAdvancedSearch = this.hasAdvancedOptions();
+
+ }
+
+ ngAfterViewInit() {
+ // Query inputs are generated from search context data,
+ // so they are not available until after the first render.
+ // Search context data is extracted synchronously from the URL.
+
+ if (this.searchContext.identQuery) {
+ // Focus identifier query input if identQuery is in progress
+ this.renderer.selectRootElement('#ident-query-input').focus();
+ } else {
+ // Otherwise focus the main query input
+ this.renderer.selectRootElement('#first-query-input').focus();
+ }
+ }
+
+ /**
+ * Display the advanced/extended search options when asked to
+ * or if any advanced options are selected.
+ */
+ showAdvanced(): boolean {
+ return this.showAdvancedSearch;
+ }
+
+ hasAdvancedOptions(): boolean {
+ // ccvm filters may be present without any filters applied.
+ // e.g. if filters were applied then removed.
+ let show = false;
+ Object.keys(this.searchContext.ccvmFilters).forEach(ccvm => {
+ if (this.searchContext.ccvmFilters[ccvm][0] !== '') {
+ show = true;
+ }
+ });
+
+ if (this.searchContext.identQuery) {
+ show = true;
+ }
+
+ return show;
+ }
+
+ orgOnChange = (org: IdlObject): void => {
+ this.searchContext.searchOrg = org;
+ }
+
+ addSearchRow(index: number): void {
+ this.searchContext.query.splice(index, 0, '');
+ this.searchContext.fieldClass.splice(index, 0, 'keyword');
+ this.searchContext.joinOp.splice(index, 0, '&&');
+ this.searchContext.matchOp.splice(index, 0, 'contains');
+ }
+
+ delSearchRow(index: number): void {
+ this.searchContext.query.splice(index, 1);
+ this.searchContext.fieldClass.splice(index, 1);
+ this.searchContext.joinOp.splice(index, 1);
+ this.searchContext.matchOp.splice(index, 1);
+ }
+
+ formEnter(source) {
+ this.searchContext.pager.offset = 0;
+
+ switch (source) {
+
+ case 'query': // main search form query input
+
+ // Be sure a previous ident search does not take precedence
+ // over the newly entered/submitted search query
+ this.searchContext.identQuery = null;
+ break;
+
+ case 'ident': // identifier query input
+ const iq = this.searchContext.identQuery;
+ const qt = this.searchContext.identQueryType;
+ if (iq) {
+ // Ident queries ignore search-specific filters.
+ this.searchContext.reset();
+ this.searchContext.identQuery = iq;
+ this.searchContext.identQueryType = qt;
+ }
+ break;
+ }
+
+ this.searchByForm();
+ }
+
+ // https://stackoverflow.com/questions/42322968/angular2-dynamic-input-field-lose-focus-when-input-changes
+ trackByIdx(index: any, item: any) {
+ return index;
+ }
+
+ searchByForm(): void {
+ this.staffCat.search();
+ }
+
+ searchIsActive(): boolean {
+ return this.searchContext.searchState === CatalogSearchState.SEARCHING;
+ }
+
+}
+
+
--- /dev/null
+
+<eg-staff-banner bannerText="Search for Patron by Barcode" i18n-bannerText>
+</eg-staff-banner>
+
+<div class="col-lg-4">
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <span class="input-group-text" i18n>Barcode:</span>
+ </div>
+ <input type='text' id='barcode-search-input' class="form-control"
+ placeholder="Barcode" i18n-placeholder [ngModel]='barcode'/>
+ <div class="input-group-append">
+ <button class="btn btn-outline-secondary"
+ (click)="findUser()" i18n>Submit</button>
+ </div>
+ </div>
+</div>
+
+
--- /dev/null
+import {Component, OnInit, Renderer2} from '@angular/core';
+import {ActivatedRoute} from '@angular/router';
+import {NetService} from '@eg/core/net.service';
+import {AuthService} from '@eg/core/auth.service';
+
+@Component({
+ templateUrl: 'bcsearch.component.html'
+})
+
+export class BcSearchComponent implements OnInit {
+
+ barcode = '';
+
+ constructor(
+ private route: ActivatedRoute,
+ private renderer: Renderer2,
+ private net: NetService,
+ private auth: AuthService
+ ) {}
+
+ ngOnInit() {
+
+ this.renderer.selectRootElement('#barcode-search-input').focus();
+ this.barcode = this.route.snapshot.paramMap.get('barcode');
+
+ if (this.barcode) {
+ this.findUser();
+ }
+ }
+
+ findUser(): void {
+ alert('Searching for user ' + this.barcode);
+ }
+}
+
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {StaffCommonModule} from '@eg/staff/common.module';
+import {BcSearchRoutingModule} from './routing.module';
+import {BcSearchComponent} from './bcsearch.component';
+
+@NgModule({
+ declarations: [
+ BcSearchComponent
+ ],
+ imports: [
+ StaffCommonModule,
+ BcSearchRoutingModule,
+ ],
+})
+
+export class BcSearchModule {}
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {BcSearchComponent} from './bcsearch.component';
+
+const routes: Routes = [
+ { path: '',
+ component: BcSearchComponent
+ },
+ { path: ':barcode',
+ component: BcSearchComponent
+ },
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+
+export class BcSearchRoutingModule {}
--- /dev/null
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+
+const routes: Routes = [
+ { path: 'bcsearch',
+ loadChildren: '@eg/staff/circ/patron/bcsearch/bcsearch.module#BcSearchModule'
+ }
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+
+export class CircPatronRoutingModule {}
--- /dev/null
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+
+const routes: Routes = [
+ { path: 'patron',
+ loadChildren: '@eg/staff/circ/patron/routing.module#CircPatronRoutingModule'
+ }
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+
+export class CircRoutingModule {}
--- /dev/null
+import {NgModule, ModuleWithProviders} from '@angular/core';
+import {EgCommonModule} from '@eg/common.module';
+import {AudioService} from '@eg/share/util/audio.service';
+import {GridModule} from '@eg/share/grid/grid.module';
+import {StaffBannerComponent} from './share/staff-banner.component';
+import {ComboboxComponent} from '@eg/share/combobox/combobox.component';
+import {ComboboxEntryComponent} from '@eg/share/combobox/combobox-entry.component';
+import {OrgSelectComponent} from '@eg/share/org-select/org-select.component';
+import {AccessKeyDirective} from '@eg/share/accesskey/accesskey.directive';
+import {AccessKeyService} from '@eg/share/accesskey/accesskey.service';
+import {AccessKeyInfoComponent} from '@eg/share/accesskey/accesskey-info.component';
+import {OpChangeComponent} from '@eg/staff/share/op-change/op-change.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {ToastComponent} from '@eg/share/toast/toast.component';
+import {StringComponent} from '@eg/share/string/string.component';
+import {StringService} from '@eg/share/string/string.service';
+import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
+import {DateSelectComponent} from '@eg/share/date-select/date-select.component';
+import {RecordBucketDialogComponent} from '@eg/staff/share/buckets/record-bucket-dialog.component';
+import {BibSummaryComponent} from '@eg/staff/share/bib-summary/bib-summary.component';
+import {TranslateComponent} from '@eg/staff/share/translate/translate.component';
+import {AdminPageComponent} from '@eg/staff/share/admin-page/admin-page.component';
+
+/**
+ * Imports the EG common modules and adds modules common to all staff UI's.
+ */
+
+@NgModule({
+ declarations: [
+ StaffBannerComponent,
+ ComboboxComponent,
+ ComboboxEntryComponent,
+ OrgSelectComponent,
+ AccessKeyDirective,
+ AccessKeyInfoComponent,
+ ToastComponent,
+ StringComponent,
+ OpChangeComponent,
+ FmRecordEditorComponent,
+ DateSelectComponent,
+ RecordBucketDialogComponent,
+ BibSummaryComponent,
+ TranslateComponent,
+ AdminPageComponent
+ ],
+ imports: [
+ EgCommonModule,
+ GridModule
+ ],
+ exports: [
+ EgCommonModule,
+ GridModule,
+ StaffBannerComponent,
+ ComboboxComponent,
+ ComboboxEntryComponent,
+ OrgSelectComponent,
+ AccessKeyDirective,
+ AccessKeyInfoComponent,
+ ToastComponent,
+ StringComponent,
+ OpChangeComponent,
+ FmRecordEditorComponent,
+ DateSelectComponent,
+ RecordBucketDialogComponent,
+ BibSummaryComponent,
+ TranslateComponent,
+ AdminPageComponent
+ ]
+})
+
+export class StaffCommonModule {
+ static forRoot(): ModuleWithProviders {
+ return {
+ ngModule: StaffCommonModule,
+ providers: [ // Export staff-wide services
+ AccessKeyService,
+ AudioService,
+ StringService,
+ ToastService
+ ]
+ };
+ }
+}
+
--- /dev/null
+<div class="container">
+ <div class="col-lg-6 offset-lg-3">
+ <fieldset>
+ <legend class="mb-0" i18n>Sign In</legend>
+ <hr class="mt-1"/>
+ <form (ngSubmit)="handleSubmit()" #loginForm="ngForm" class="form-validated">
+
+ <div class="form-group row">
+ <label class="col-lg-4 text-right font-weight-bold" for="username" i18n>Username</label>
+ <input
+ type="text"
+ class="form-control col-lg-8"
+ id="username"
+ name="username"
+ required
+ autocomplete="username"
+ i18n-placeholder
+ placeholder="Username"
+ [(ngModel)]="args.username"/>
+ </div>
+
+ <div class="form-group row">
+ <label class="col-lg-4 text-right font-weight-bold" for="password" i18n>Password</label>
+ <input
+ type="password"
+ class="form-control col-lg-8"
+ id="password"
+ name="password"
+ required
+ autocomplete="current-password"
+ i18n-placeholder
+ placeholder="Password"
+ [(ngModel)]="args.password"/>
+ </div>
+
+ <div class="form-group row" *ngIf="workstations && workstations.length">
+ <label class="col-lg-4 text-right font-weight-bold" for="workstation" i18n>Workstation</label>
+ <select
+ class="form-control col-lg-8"
+ id="workstation"
+ name="workstation"
+ required
+ [(ngModel)]="args.workstation">
+ <option *ngFor="let ws of workstations" [value]="ws.name">
+ {{ws.name}}
+ </option>
+ </select>
+ </div>
+
+ <div class="row">
+ <div class="col-lg-8 offset-lg-4 pl-0">
+ <button type="submit" class="btn btn-outline-dark" i18n>Sign in</button>
+ </div>
+ </div>
+ </form>
+ </fieldset>
+ </div>
+</div>
--- /dev/null
+import {Component, OnInit, Renderer2} from '@angular/core';
+import {Location} from '@angular/common';
+import {Router, ActivatedRoute} from '@angular/router';
+import {AuthService, AuthWsState} from '@eg/core/auth.service';
+import {StoreService} from '@eg/core/store.service';
+
+@Component({
+ templateUrl : './login.component.html'
+})
+
+export class StaffLoginComponent implements OnInit {
+
+ workstations: any[];
+
+ args = {
+ username : '',
+ password : '',
+ workstation : '',
+ type : 'staff'
+ };
+
+ constructor(
+ private router: Router,
+ private route: ActivatedRoute,
+ private ngLocation: Location,
+ private renderer: Renderer2,
+ private auth: AuthService,
+ private store: StoreService
+ ) {}
+
+ ngOnInit() {
+ // clear out any stale auth data
+ this.auth.logout();
+
+ // Focus username
+ this.renderer.selectRootElement('#username').focus();
+
+ this.workstations = this.store.getLocalItem('eg.workstation.all');
+ this.args.workstation =
+ this.store.getLocalItem('eg.workstation.default');
+ this.applyWorkstation();
+ }
+
+ applyWorkstation() {
+ const wanted = this.route.snapshot.queryParamMap.get('workstation');
+ if (!wanted) { return; } // use the default
+
+ const exists = this.workstations.filter(w => w.name === wanted)[0];
+ if (exists) {
+ this.args.workstation = wanted;
+ } else {
+ console.error(`Unknown workstation requested: ${wanted}`);
+ }
+ }
+
+ handleSubmit() {
+
+ // post-login URL
+ let url: string = this.auth.redirectUrl || '/staff/splash';
+
+ // prevent sending the user back to the login page
+ if (url.startsWith('/staff/login')) {
+ url = '/staff/splash';
+ }
+
+ const workstation: string = this.args.workstation;
+
+ this.auth.login(this.args).then(
+ ok => {
+ this.auth.redirectUrl = null;
+
+ if (this.auth.workstationState === AuthWsState.NOT_FOUND_SERVER) {
+ // User attempted to login with a workstation that is
+ // unknown to the server. Redirect to the WS admin page.
+ // Reset the WS state to avoid looping back to WS removal
+ // page before the new workstation can be activated.
+ this.auth.workstationState = AuthWsState.PENDING;
+ this.router.navigate(
+ [`/staff/admin/workstation/workstations/remove/${workstation}`]);
+ } else {
+ // Force reload of the app after a successful login.
+ // This allows the route resolver to re-run with a
+ // valid auth token and workstation.
+ window.location.href =
+ this.ngLocation.prepareExternalUrl(url);
+ }
+ },
+ notOk => {
+ // indicate failure in the UI.
+ }
+ );
+ }
+}
+
+
+
--- /dev/null
+/* remove dropdown carret for icon-based entries */
+#staff-navbar .no-caret::after {
+ display:none;
+}
+
+/* move the caret closer to the dropdown text */
+#staff-navbar {
+ padding-left: 0px;
+}
+
+#staff-navbar {
+ background: -webkit-linear-gradient(#00593d, #007a54);
+ background-color: #007a54;
+ color: #fff;
+ font-size: 14px;
+}
+
+#staff-navbar .navbar-nav {
+ padding: 4px;
+}
+
+/* align top of dropdown w/ bottom of nav */
+#staff-navbar .dropdown-menu {
+ margin-top: 7px;
+}
+#staff-navbar .material-icons {
+ padding-right:3px;
+}
+#staff-navbar .dropdown-item {
+ font-size: 14px;
+ font-weight: 400;
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ padding-left: 0.7rem;
+ padding-right: 0.7rem;
+ margin: -4px;
+}
+
+#staff-navbar .dropdown-item .material-icons {
+ font-size: 18px;
+}
+
+#staff-navbar .nav-link {
+ color: #fff;
+ padding-top:1px;
+ padding-bottom:1px;
+}
+#staff-navbar .nav-link:hover {
+ color: #ddd;
+ cursor: pointer;
+}
+
+#staff-navbar .navbar-nav > .open > a,
+#staff-navbar .navbar-nav > .open > a:focus,
+#staff-navbar .navbar-nav > .open > a:hover {
+ background-color: #7a7a7a;
+}
+#staff-navbar .navbar-nav>.dropdown>a .caret {
+ border-top-color: #fff;
+ border-bottom-color: #fff;
+}
+#staff-navbar .navbar-nav>.dropdown>a:hover .caret {
+ border-top-color: #ddd;
+ border-bottom-color: #ddd;
+}
+
+/* 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;
+}
+
--- /dev/null
+<div id="staff-navbar" class="navbar fixed-top navbar-expand navbar-default">
+ <div class="collapse navbar-collapse">
+ <div class="navbar-nav">
+ <div class="nav-item">
+ <a i18n class="nav-link with-material-icon"
+ routerLink="/staff/splash"
+ egAccessKey keyCtx="navbar"
+ keySpec="alt+h" i18n-keySpec
+ keyDesc="Navigate Home" i18n-keyDesc>
+ <span class="material-icons">home</span>
+ </a>
+ </div>
+ </div>
+
+ <div class="navbar-nav">
+ <div ngbDropdown class="nav-item dropdown">
+ <a ngbDropdownToggle i18n class="nav-link dropdown-toggle">
+ Search
+ </a>
+ <div class="dropdown-menu" ngbDropdownMenu>
+ <a class="dropdown-item" href="/eg/staff/circ/patron/search">
+ <span class="material-icons">person</span>
+ <span i18n>Search for Patrons</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/cat/item/search">
+ <span class="material-icons">assignment</span>
+ <span i18n>Search for Copies by Barcode</span>
+ </a>
+ <a class="dropdown-item" routerLink="/staff/catalog/search"
+ egAccessKey keyCtx="navbar"
+ keySpec="alt+c" i18n-keySpec
+ keyDesc="Navigate To Catalog" i18n-keyDesc>
+ <span class="material-icons">search</span>
+ <span i18n>Search the Catalog</span>
+ </a>
+ </div>
+ </div>
+ </div>
+
+ <div class="navbar-nav">
+ <div ngbDropdown class="nav-item dropdown">
+ <a ngbDropdownToggle class="nav-link dropdown-toggle">
+ <span i18n>Circulation</span>
+ </a>
+ <div class="dropdown-menu" ngbDropdownMenu>
+ <a class="dropdown-item" href="/eg/staff/circ/patron/bcsearch">
+ <span class="material-icons">trending_up</span>
+ <span i18n>Check Out</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/circ/checkin/checkin">
+ <span class="material-icons">trending_down</span>
+ <span i18n>Check In</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/circ/checkin/capture">
+ <span class="material-icons">pin_drop</span>
+ <span i18n>Capture Holds</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/circ/holds/pull">
+ <span class="material-icons">view_list</span>
+ <span i18n>Pull List for Hold Requests</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/circ/renew/renew">
+ <span class="material-icons">autorenew</span>
+ <span i18n>Renew Items</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/circ/patron/register">
+ <span class="material-icons">person_add</span>
+ <span i18n>Register Patron</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/circ/patron/last">
+ <span class="material-icons">redo</span>
+ <span i18n>Retrieve Last Patron</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/circ/patron/search?show_recent=1">
+ <span class="material-icons">redo</span>
+ <span i18n>Retrieve Recent Patrons</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/circ/patron/pending/list">
+ <span class="material-icons">thumb_up</span>
+ <span i18n>Pending Patrons</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/circ/patron/bucket/view">
+ <span class="material-icons">list</span>
+ <span i18n>User Buckets</span>
+ </a>
+ <div class="dropdown-divider"></div>
+ <a class="dropdown-item" href="/eg/staff/circ/patron/credentials">
+ <span class="material-icons">check_circle</span>
+ <span i18n>Verify Credentials</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/circ/in_house_use/index">
+ <span class="material-icons">playlist_add</span>
+ <span i18n>Record In-House Use</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/circ/holds/shelf">
+ <span class="material-icons">format_list_bulleted</span>
+ <span i18n>Holds Shelf</span>
+ </a>
+ <div class="dropdown-divider"></div>
+ <a class="dropdown-item" href="/eg/staff/cat/item/replace_barcode/index">
+ <span class="material-icons">library_books</span>
+ <span i18n>Replace Barcode</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/cat/item/search"
+ egAccessKey keyCtx="navbar"
+ keySpec="f5" i18n-keySpec
+ keyDesc="Navigate To Item Status" i18n-keyDesc>
+ <span class="material-icons">question_answer</span>
+ <span i18n>Item Status</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/cat/item/missing_pieces">
+ <span class="material-icons">grid_on</span>
+ <span i18n>Scan Item as Missing Pieces</span>
+ </a>
+ <div class="dropdown-divider"></div>
+ <a class="dropdown-item" (click)="reprintLast()">
+ <span class="material-icons">redo</span>
+ <span i18n>Reprint Last Receipt</span>
+ </a>
+ <div class="dropdown-divider"></div>
+ <a class="dropdown-item" href="/eg/staff/offline-interface">
+ <span class="material-icons">signal_wifi_off</span>
+ <span i18n>Offline Circulation</span>
+ </a>
+ </div>
+ </div>
+ </div>
+
+ <!-- CATALOGING -->
+
+ <div class="navbar-nav">
+ <div ngbDropdown class="nav-item dropdown">
+ <a ngbDropdownToggle i18n class="nav-link dropdown-toggle">
+ Cataloging
+ </a>
+ <div class="dropdown-menu" ngbDropdownMenu>
+
+ <a href="/eg/staff/cat/catalog/index" class="dropdown-item">
+ <span class="material-icons">search</span>
+ <span i18n>Search the Catalog</span>
+ </a>
+ <!--
+ Link to experimental Angular staff catalog.
+ Leaving disabled until more functionality can be fleshed out.
+ -->
+ <!--
+ <a class="dropdown-item"
+ routerLink="/staff/catalog/search">
+ <span class="material-icons">search</span>
+ <span i18n>Staff Catalog (Experimental)</span>
+ </a>
+ -->
+ <a href="/eg/staff/cat/bucket/record/view" class="dropdown-item">
+ <span class="material-icons">list_alt</span>
+ <span i18n>Record Buckets</span>
+ </a>
+ <a href="/eg/staff/cat/bucket/copy/view" class="dropdown-item">
+ <span class="material-icons">list_alt</span>
+ <span i18n>Copy Buckets</span>
+ </a>
+ <div class="dropdown-divider"></div>
+ <a href="/eg/staff/cat/catalog/retrieve_by_id" class="dropdown-item">
+ <span class="material-icons">collections</span>
+ <span i18n>Retrieve Bib Record by ID</span>
+ </a>
+ <a href="/eg/staff/cat/catalog/retrieve_by_tcn"
+ eg-accesskey="shift+f3"
+ eg-accesskey-desc="Retrieve Last Bib Record" class="dropdown-item">
+ <span class="material-icons">collections_bookmark</span>
+ <span i18n>Retrieve Bib Record by TCN</span>
+ </a>
+ <a href="" ng-click="retrieveLastRecord()"
+ eg-accesskey="shift+f8"
+ eg-accesskey-desc="Retrieve Last Bib Record" class="dropdown-item">
+ <span class="material-icons">redo</span>
+ <span i18n>Retrieve Last Bib Record</span>
+ </a>
+ <div class="dropdown-divider"></div>
+ <a href="/eg/staff/cat/catalog/new_bib" class="dropdown-item">
+ <span class="material-icons">add</span>
+ <span i18n>Create New MARC Record</span>
+ </a>
+ <a href="/eg/staff/cat/z3950/index" class="dropdown-item">
+ <span class="material-icons">cloud_download</span>
+ <span i18n>Import Record from Z39.50</span>
+ </a>
+ <a href="/eg/staff/cat/catalog/vandelay" class="dropdown-item">
+ <span class="material-icons">import_export</span>
+ <span i18n>MARC Batch Import/Export</span>
+ </a>
+ <a href="/eg/staff/cat/catalog/batchEdit" class="dropdown-item">
+ <span class="material-icons">format_paint</span>
+ <span i18n>MARC Batch Edit</span>
+ </a>
+ <div class="dropdown-divider"></div>
+ <a href="/eg/staff/cat/catalog/verifyURLs" class="dropdown-item">
+ <span class="material-icons">link</span>
+ <span i18n>Link Checker</span>
+ </a>
+ <div class="dropdown-divider"></div>
+ <a href="/eg/staff/cat/catalog/manageAuthorities" class="dropdown-item">
+ <span class="material-icons">lock</span>
+ <span i18n>Manage Authorities</span>
+ </a>
+ </div>
+ </div>
+ </div>
+
+ <!-- ACQUISITIONS -->
+
+ <div class="navbar-nav">
+ <div ngbDropdown class="nav-item dropdown">
+ <a ngbDropdownToggle i18n class="nav-link dropdown-toggle">
+ Acquisitions
+ </a>
+ <div class="dropdown-menu" ngbDropdownMenu>
+ <a class="dropdown-item"
+ href="/eg/staff/acq/legacy/search/unified">
+ <span class="material-icons">search</span>
+ <span i18n>General Search</span>
+ </a>
+ <div class="dropdown-divider"></div>
+ <a class="dropdown-item"
+ href="/eg/staff/acq/legacy/search/unified?ca=pl">
+ <span class="material-icons">view_list</span>
+ <span i18n>My Selection Lists</span>
+ </a>
+ <a class="dropdown-item"
+ href="/eg/staff/acq/legacy/picklist/brief_record">
+ <span class="material-icons">edit</span>
+ <span i18n>New Brief Record</span>
+ </a>
+ <a class="dropdown-item"
+ href="/eg/staff/acq/legacy/picklist/user_request">
+ <span class="material-icons">thumb_up</span>
+ <span i18n>Patron Requests</span>
+ </a>
+ <a class="dropdown-item"
+ href="/eg/staff/acq/legacy/picklist/bib_search">
+ <span class="material-icons">cloud_download</span>
+ <span i18n>MARC Federated Search</span>
+ </a>
+ <a class="dropdown-item"
+ href="/eg/staff/acq/legacy/picklist/from_bib">
+ <span class="material-icons">trending_down</span>
+ <span i18n>Load Catalog Record IDs</span>
+ </a>
+ <div class="dropdown-divider"></div>
+ <a class="dropdown-item"
+ href="/eg/staff/acq/legacy/picklist/upload">
+ <span class="material-icons">cloud_upload</span>
+ <span i18n>Load MARC Order Records</span>
+ </a>
+ <a class="dropdown-item"
+ href="/eg/staff/acq/legacy/search/unified?ca=po">
+ <span class="material-icons">shopping_cart</span>
+ <span i18n>Purchase Orders</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/acq/legacy/po/create">
+ <span class="material-icons">add_shopping_cart</span>
+ <span i18n>Create Purchase Order</span>
+ </a>
+ <div class="dropdown-divider"></div>
+ <a class="dropdown-item"
+ href="/eg/staff/acq/legacy/financial/claim_eligible">
+ <span class="material-icons">contact_phone</span>
+ <span i18n>Claim-Ready Items</span>
+ </a>
+ <a class="dropdown-item"
+ href="/eg/staff/acq/legacy/search/unified?ca=inv">
+ <span class="material-icons">attach_money</span>
+ <span i18n>Open Invoices</span>
+ </a>
+ <a class="dropdown-item"
+ href="/eg/staff/acq/legacy/invoice/view?create=1">
+ <span class="material-icons">monetization_on</span>
+ <span i18n>Create Invoice</span>
+ </a>
+ </div>
+ </div>
+ </div>
+
+ <div class="navbar-nav">
+ <div ngbDropdown class="nav-item dropdown">
+ <a ngbDropdownToggle i18n class="nav-link dropdown-toggle">
+ Booking
+ </a>
+ <div class="dropdown-menu" ngbDropdownMenu>
+ <a class="dropdown-item" href="/eg/staff/booking/legacy/booking/reservation">
+ <span class="material-icons">add</span>
+ <span i18n>Create Reservations</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/booking/legacy/booking/pull_list">
+ <span class="material-icons">list</span>
+ <span i18n>Pull List</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/booking/legacy/booking/capture">
+ <span class="material-icons">pin_drop</span>
+ <span i18n>Capture Resources</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/booking/legacy/booking/pickup">
+ <span class="material-icons">trending_up</span>
+ <span i18n>Pick Up Reservations</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/booking/legacy/booking/return">
+ <span class="material-icons">trending_down</span>
+ <span i18n>Return Reservations</span>
+ </a>
+ </div>
+ </div>
+ </div>
+
+ <div class="navbar-nav">
+ <div ngbDropdown class="nav-item dropdown">
+ <a ngbDropdownToggle i18n class="nav-link dropdown-toggle">
+ Administration
+ </a>
+ <div class="dropdown-menu" ngbDropdownMenu>
+ <a class="dropdown-item" href="/eg/staff/admin/workstation/index">
+ <span class="material-icons">computer</span>
+ <span i18n>Workstation</span>
+ </a>
+ <!--
+ Leaving here as a reminder this UI exists.
+ <a class="dropdown-item"
+ routerLink="/staff/admin/workstation/workstations/manage">
+ <span class="material-icons">computer</span>
+ <span i18n>Registered Workstations</span>
+ </a>
+ -->
+ <a class="dropdown-item" href="/eg/staff/admin/user_perms">
+ <span class="material-icons">person</span>
+ <span i18n>User Permission Editor</span>
+ </a>
+ <!-- Angular version
+ <a class="dropdown-item"
+ routerLink="/staff/admin/server/splash">
+ <span class="material-icons">account_balance</span>
+ <span i18n>Server Administration</span>
+ </a>
+ -->
+ <a class="dropdown-item" href="/eg/staff/admin/server/index">
+ <span class="material-icons">account_balance</span>
+ <span i18n>Server Administration</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/admin/local/index">
+ <span class="material-icons">landscape</span>
+ <span i18n>Local Administration</span>
+ </a>
+ <a class="dropdown-item"
+ routerLink="/staff/admin/acq/splash">
+ <span class="material-icons">attach_money</span>
+ <span i18n>Acquisitions Administration</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/admin/serials/index">
+ <span class="material-icons">layers</span>
+ <span i18n>Serials Administration</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/admin/booking/index">
+ <span class="material-icons">business_center</span>
+ <span i18n>Booking Administration</span>
+ </a>
+ <a class="dropdown-item" href="/eg/staff/reporter/legacy/main">
+ <span class="material-icons">insert_chart_outlined</span>
+ <span i18n>Reports</span>
+ </a>
+ </div>
+ </div>
+ </div>
+
+
+ <div class="navbar-nav mr-auto"></div>
+ <div class="navbar-nav" *ngIf="user()">
+ <span i18n>{{user()}} @ {{workstation()}}</span>
+ </div>
+ <div class="navbar-nav" *ngIf="locales.length > 1 && currentLocale">
+ <div ngbDropdown class="nav-item dropdown" placement="bottom-right">
+ <a ngbDropdownToggle i18n i18n-title
+ title="Select Locale"
+ class="nav-link dropdown-toggle no-caret with-material-icon">
+ <i class="material-icons">flag</i>
+ <span>{{currentLocale.name()}}</span>
+ </a>
+ <div class="dropdown-menu" ngbDropdownMenu>
+ <a class="dropdown-item" (click)="setLocale(locale)"
+ [ngClass]="{disabled: currentLocale.code() == locale.code()}"
+ *ngFor="let locale of locales">
+ <span class="material-icons">add_location</span>
+ <span i18n>{{locale.name()}}</span>
+ </a>
+ </div>
+ </div>
+ </div>
+ <div class="navbar-nav" *ngIf="user()">
+ <div ngbDropdown class="nav-item dropdown" placement="bottom-right">
+ <a ngbDropdownToggle i18n
+ i18n-title
+ title="Log out and more..."
+ class="nav-link dropdown-toggle no-caret with-material-icon">
+ <i class="material-icons">list</i>
+ </a>
+ <div class="dropdown-menu" ngbDropdownMenu>
+ <eg-op-change #navOpChange
+ i18n-failMessage
+ i18n-successMessage
+ failMessage="Operator Change Failed"
+ successMessage="Operator Change Succeeded">
+ </eg-op-change>
+ <a class="dropdown-item" *ngIf="!opChangeActive()"
+ (click)="navOpChange.open()">
+ <span class="material-icons">transform</span>
+ <span i18n>Change Operator</span>
+ </a>
+ <a *ngIf="opChangeActive()" class="dropdown-item"
+ (click)="navOpChange.restore()">
+ <span class="material-icons">transform</span>
+ <span i18n>Restore Operator</span>
+ </a>
+ <a class="dropdown-item" (click)="logout()">
+ <span class="material-icons">lock_outline</span>
+ <span i18n>Logout</span>
+ </a>
+ <a class="dropdown-item" routerLink="/staff/about">
+ <span class="material-icons">info_outline</span>
+ <span i18n>About</span>
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+
--- /dev/null
+import {Component, OnInit, ViewChild} from '@angular/core';
+import {ActivatedRoute, Router} from '@angular/router';
+import {Location} from '@angular/common';
+import {AuthService} from '@eg/core/auth.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {LocaleService} from '@eg/core/locale.service';
+import {PrintService} from '@eg/share/print/print.service';
+
+@Component({
+ selector: 'eg-staff-nav-bar',
+ styleUrls: ['nav.component.css'],
+ templateUrl: 'nav.component.html'
+})
+
+export class StaffNavComponent implements OnInit {
+
+ // Locales that have Angular staff translations
+ locales: any[];
+ currentLocale: any;
+
+ constructor(
+ private router: Router,
+ private auth: AuthService,
+ private pcrud: PcrudService,
+ private locale: LocaleService,
+ private printer: PrintService
+ ) {
+ this.locales = [];
+ }
+
+ ngOnInit() {
+
+ this.locale.supportedLocales().subscribe(
+ l => this.locales.push(l),
+ err => {},
+ () => {
+ this.currentLocale = this.locales.filter(
+ l => l.code() === this.locale.currentLocaleCode())[0];
+ }
+ );
+ }
+
+ user() {
+ return this.auth.user() ? this.auth.user().usrname() : '';
+ }
+
+ workstation() {
+ return this.auth.user() ? this.auth.workstation() : '';
+ }
+
+ setLocale(locale: any) {
+ this.locale.setLocale(locale.code());
+ }
+
+ opChangeActive(): boolean {
+ return this.auth.opChangeIsActive();
+ }
+
+ // Broadcast to all tabs that we're logging out.
+ // Redirect to the login page, which performs the remaining
+ // logout duties.
+ logout(): void {
+ this.auth.broadcastLogout();
+ this.router.navigate(['/staff/login']);
+ }
+
+ reprintLast() {
+ this.printer.reprintLast();
+ }
+}
+
+
--- /dev/null
+import {Injectable} from '@angular/core';
+import {Location} from '@angular/common';
+import {Observable} from 'rxjs/Observable';
+import {Observer} from 'rxjs/Observer';
+import {of} from 'rxjs';
+import {Router, Resolve, RouterStateSnapshot,
+ ActivatedRoute, ActivatedRouteSnapshot} from '@angular/router';
+import {StoreService} from '@eg/core/store.service';
+import {NetService} from '@eg/core/net.service';
+import {AuthService, AuthWsState} from '@eg/core/auth.service';
+import {PermService} from '@eg/core/perm.service';
+import {OrgService} from '@eg/core/org.service';
+import {FormatService} from '@eg/core/format.service';
+
+const LOGIN_PATH = '/staff/login';
+const WS_MANAGE_PATH = '/staff/admin/workstation/workstations/manage';
+
+/**
+ * Load data used by all staff modules.
+ */
+@Injectable()
+export class StaffResolver implements Resolve<Observable<any>> {
+
+ // Tracks the primary resolve observable.
+ observer: Observer<any>;
+
+ constructor(
+ private router: Router,
+ private route: ActivatedRoute,
+ private ngLocation: Location,
+ private store: StoreService,
+ private org: OrgService,
+ private net: NetService,
+ private auth: AuthService,
+ private perm: PermService,
+ private format: FormatService
+ ) {}
+
+ resolve(
+ route: ActivatedRouteSnapshot,
+ state: RouterStateSnapshot): Observable<any> {
+
+ // Staff cookies stay in /$base/staff/
+ // NOTE: storing session data at '/' so it can be shared by
+ // Angularjs apps.
+ this.store.loginSessionBasePath = '/';
+ // ^-- = this.ngLocation.prepareExternalUrl('/staff');
+
+ // Not sure how to get the path without params... using this for now.
+ const path = state.url.split('?')[0];
+ if (path === '/staff/login') {
+ return of(true);
+ }
+
+ const observable: Observable<any>
+ = Observable.create(o => this.observer = o);
+
+ this.auth.testAuthToken().then(
+ tokenOk => {
+ this.confirmStaffPerms().then(
+ hasPerms => {
+ this.auth.verifyWorkstation().then(
+ wsOk => {
+ this.loadStartupData()
+ .then(ok => this.observer.complete());
+ },
+ wsNotOk => this.handleInvalidWorkstation(path)
+ );
+ },
+ hasNotPerms => {
+ this.observer.error(
+ 'User does not have staff permissions');
+ }
+ );
+ },
+ tokenNotOk => this.handleInvalidToken(state)
+ );
+
+ return observable;
+ }
+
+
+ // Confirm the user has the STAFF_LOGIN permission anywhere before
+ // allowing the staff sub-tree to load. This will prevent users
+ // with valid, non-staff authtokens from attempting to connect and
+ // subsequently getting redirected to the workstation admin page
+ // (since they won't have a valid WS either).
+ confirmStaffPerms(): Promise<any> {
+ return new Promise((resolve, reject) => {
+ this.perm.hasWorkPermAt(['STAFF_LOGIN']).then(
+ permMap => {
+ if (permMap.STAFF_LOGIN.length) {
+ resolve('perm check OK');
+ } else {
+ reject('perm check faield');
+ }
+ }
+ );
+ });
+ }
+
+
+ // 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('StaffResolver: authtoken is not valid');
+ this.auth.redirectUrl = state.url;
+ this.router.navigate([LOGIN_PATH]);
+ this.observer.error('invalid or no auth token');
+ }
+
+ handleInvalidWorkstation(path: string): void {
+
+ if (path.startsWith(WS_MANAGE_PATH)) {
+ // user is navigating to the WS admin page.
+ this.observer.complete();
+ } else {
+ this.router.navigate([WS_MANAGE_PATH]);
+ this.observer.error(`Auth session linked to no
+ workstation or a workstation unknown to this browser`);
+ }
+ }
+
+ /**
+ * Fetches data common to all staff interfaces.
+ */
+ loadStartupData(): Promise<void> {
+
+ // Fetch settings needed globally. This will cache the values
+ // in the org service.
+ return this.org.settings([
+ 'lib.timezone',
+ 'webstaff.format.dates',
+ 'webstaff.format.date_and_time',
+ 'ui.staff.max_recent_patrons'
+ ]).then(settings => {
+ this.format.wsOrgTimezone = settings['lib.timezone'];
+ this.format.dateFormat = settings['webstaff.format.dates'];
+ this.format.dateTimeFormat = settings['webstaff.format.date_and_time'];
+ });
+ }
+}
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {StaffResolver} from './resolver.service';
+import {StaffComponent} from './staff.component';
+import {StaffLoginComponent} from './login.component';
+import {StaffSplashComponent} from './splash.component';
+import {AboutComponent} from './about.component';
+
+// Not using 'canActivate' because it's called before all resolvers,
+// even the parent resolver, but the resolvers parse the IDL, load settings,
+// etc. Chicken, meet egg.
+
+const routes: Routes = [{
+ path: '',
+ component: StaffComponent,
+ resolve: {staffResolver : StaffResolver},
+ children: [{
+ path: '',
+ redirectTo: 'splash',
+ pathMatch: 'full',
+ }, {
+ path: 'about',
+ component: AboutComponent
+ }, {
+ path: 'login',
+ component: StaffLoginComponent
+ }, {
+ path: 'splash',
+ component: StaffSplashComponent
+ }, {
+ path: 'circ',
+ loadChildren : '@eg/staff/circ/routing.module#CircRoutingModule'
+ }, {
+ path: 'catalog',
+ loadChildren : '@eg/staff/catalog/catalog.module#CatalogModule'
+ }, {
+ path: 'sandbox',
+ loadChildren : '@eg/staff/sandbox/sandbox.module#SandboxModule'
+ }, {
+ path: 'admin',
+ loadChildren : '@eg/staff/admin/routing.module#AdminRoutingModule'
+ }]
+}];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule],
+ providers: [StaffResolver]
+})
+
+export class StaffRoutingModule {}
+
--- /dev/null
+Place for experimenting with code.
--- /dev/null
+import {NgModule} from '@angular/core';
+import {RouterModule, Routes} from '@angular/router';
+import {SandboxComponent} from './sandbox.component';
+
+const routes: Routes = [{
+ path: '',
+ component: SandboxComponent
+}];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule],
+ providers: []
+})
+
+export class SandboxRoutingModule {}
--- /dev/null
+
+<eg-staff-banner bannerText="Sandbox" i18n-bannerText>
+</eg-staff-banner>
+
+<!-- FM Editor Experiments ----------------------------- -->
+<div class="row mb-3">
+ <ng-template #descriptionTemplate
+ let-field="field" let-record="record" let-hello="hello">
+ <!-- example custom template for editing the 'description' field -->
+ <textarea
+ placeholder="{{hello}}"
+ class="form-control"
+ name="{{field.name}}"
+ [readonly]="field.readOnly"
+ [required]="field.isRequired()"
+ [ngModel]="record[field.name]()"
+ (ngModelChange)="record[field.name]($event)">
+ </textarea>
+ </ng-template>
+ <eg-fm-record-editor #fmRecordEditor
+ idlClass="cmrcfld" mode="create"
+ [customFieldTemplates]="{description:{template:descriptionTemplate,context:{'hello':'goodbye'}}}"
+ recordId="1" orgDefaultAllowed="owner">
+ </eg-fm-record-editor>
+ <button class="btn btn-dark" (click)="fmRecordEditor.open({size:'lg'})">
+ Fm Record Editor
+ </button>
+</div>
+<!-- / FM Editor Experiments ----------------------------- -->
+
+<!-- Progress Dialog Experiments ----------------------------- -->
+<div class="row mb-3">
+ <div class="col-lg-3">
+ <button class="btn btn-outline-danger" (click)="progress.increment()">Increment Inline</button>
+ </div>
+ <div class="col-lg-3">
+ <eg-progress-inline [max]="100" [value]="1" #progress></eg-progress-inline>
+ </div>
+</div>
+<div class="row mb-3">
+ <div class="col-lg-4">
+ <eg-progress-dialog #progressDialog>
+ </eg-progress-dialog>
+ <button class="btn btn-light" (click)="showProgress()">Test Progress Dialog</button>
+ </div>
+ <div class="col-lg-3">
+ <eg-combobox [allowFreeText]="true"
+ placeholder="Combobox with static data"
+ [entries]="cbEntries"></eg-combobox>
+ </div>
+ <div class="col-lg-3">
+ <eg-combobox
+ placeholder="Combobox with dynamic data"
+ [asyncDataSource]="cbAsyncSource"></eg-combobox>
+ </div>
+</div>
+<div class="row mb-3">
+ <div class="col-lg-4">
+ <button class="btn btn-info" (click)="testToast()">Test Toast Message</button>
+ </div>
+ <div class="col-lg-2">
+ Org select with limit perms
+ </div>
+ <div class="col-lg-2">
+ <eg-org-select [limitPerms]="['REGISTER_WORKSTATION']">
+ </eg-org-select>
+ </div>
+</div>
+<!-- /Progress Dialog Experiments ----------------------------- -->
+
+<!-- eg strings -->
+<!--
+<div class="row mb-3">
+ <eg-string #helloString text="Hello, {{name}}" i18n-text></eg-string>
+ <button class="btn btn-success" (click)="testStrings()">Test Strings</button>
+</div>
+-->
+
+<div class="row mb-3">
+ <ng-template #helloStrTmpl let-name="name" i18n>Hello, {{name}}</ng-template>
+ <!--
+ <eg-string #helloStr key="helloKey" [template]="helloStrTmpl"></eg-string>
+ -->
+ <eg-string key="staff.sandbox.test" [template]="helloStrTmpl"></eg-string>
+ <button class="btn btn-success" (click)="testStrings()">Test Strings</button>
+</div>
+
+<div class="row">
+ <div class="form-group">
+ <eg-date-select (onChangeAsDate)="changeDate($event)"
+ initialYmd="2017-03-04">
+ </eg-date-select>
+ </div>
+ <div>HERE: {{testDate}}</div>
+</div>
+
+<!-- printing -->
+
+<button class="btn btn-secondary" (click)="doPrint()">Test Print</button>
+<ng-template #printTemplate let-context>Hello, {{context.world}}!</ng-template>
+
+<br/><br/>
+HERasdfE
+<div class="row">
+ <div class="col-lg-3">
+ <eg-translate #translate [idlObject]="oneBtype" fieldName="name"></eg-translate>
+ <button class="btn btn-info"
+ (click)="translate.open({size:'lg'})">Translate</button>
+ </div>
+</div>
+<br/><br/>
+
+<!-- grid stuff -->
+<ng-template #cellTmpl let-row="row" let-col="col" let-userContext="userContext">
+ HELLO {{userContext.hello}}
+ <button>{{row.id()}}</button>
+</ng-template>
+<eg-grid #cbtGrid idlClass="cbt"
+ [dataSource]="btSource"
+ [rowClassCallback]="btGridRowClassCallback"
+ [rowFlairIsEnabled]="true"
+ [rowFlairCallback]="btGridRowFlairCallback"
+ [cellClassCallback]="btGridCellClassCallback"
+ [sortable]="true">
+ <eg-grid-column name="test" [cellTemplate]="cellTmpl"
+ [cellContext]="btGridTestContext" [sortable]="false">
+ </eg-grid-column>
+ <eg-grid-column [sortable]="false" path="owner.name"></eg-grid-column>
+</eg-grid>
+
+<br/><br/>
+
+
--- /dev/null
+import {Component, OnInit, ViewChild, Input, TemplateRef} from '@angular/core';
+import {ProgressDialogComponent} from '@eg/share/dialog/progress.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {StringService} from '@eg/share/string/string.service';
+import {Observable} from 'rxjs/Observable';
+import 'rxjs/add/observable/timer';
+import {of} from 'rxjs';
+import {map} from 'rxjs/operators/map';
+import {take} from 'rxjs/operators/take';
+import {GridDataSource, GridColumn, GridRowFlairEntry} from '@eg/share/grid/grid';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {OrgService} from '@eg/core/org.service';
+import {Pager} from '@eg/share/util/pager';
+import {DateSelectComponent} from '@eg/share/date-select/date-select.component';
+import {PrintService} from '@eg/share/print/print.service';
+import {ComboboxEntry} from '@eg/share/combobox/combobox.component';
+
+@Component({
+ templateUrl: 'sandbox.component.html'
+})
+export class SandboxComponent implements OnInit {
+
+ @ViewChild('progressDialog')
+ private progressDialog: ProgressDialogComponent;
+
+ @ViewChild('dateSelect')
+ private dateSelector: DateSelectComponent;
+
+ @ViewChild('printTemplate')
+ private printTemplate: TemplateRef<any>;
+
+ // @ViewChild('helloStr') private helloStr: StringComponent;
+
+ gridDataSource: GridDataSource = new GridDataSource();
+
+ cbEntries: ComboboxEntry[];
+ // supplier of async combobox data
+ cbAsyncSource: (term: string) => Observable<ComboboxEntry>;
+
+ btSource: GridDataSource = new GridDataSource();
+ world = 'world'; // for local template version
+ btGridTestContext: any = {hello : this.world};
+
+ renderLocal = false;
+
+ testDate: any;
+
+ testStr: string;
+ @Input() set testString(str: string) {
+ this.testStr = str;
+ }
+
+ oneBtype: IdlObject;
+
+ name = 'Jane';
+
+ constructor(
+ private idl: IdlService,
+ private org: OrgService,
+ private pcrud: PcrudService,
+ private strings: StringService,
+ private toast: ToastService,
+ private printer: PrintService
+ ) {
+ }
+
+ ngOnInit() {
+
+ this.gridDataSource.data = [
+ {name: 'Jane', state: 'AZ'},
+ {name: 'Al', state: 'CA'},
+ {name: 'The Tick', state: 'TX'}
+ ];
+
+ this.pcrud.retrieveAll('cmrcfld', {order_by: {cmrcfld: 'name'}})
+ .subscribe(format => {
+ if (!this.cbEntries) { this.cbEntries = []; }
+ this.cbEntries.push({id: format.id(), label: format.name()});
+ });
+
+ this.cbAsyncSource = term => {
+ return this.pcrud.search(
+ 'cmrcfld',
+ {name: {'ilike': `%${term}%`}}, // could -or search on label
+ {order_by: {cmrcfld: 'name'}}
+ ).pipe(map(marcField => {
+ return {id: marcField.id(), label: marcField.name()};
+ }));
+ };
+
+ this.btSource.getRows = (pager: Pager, sort: any[]) => {
+
+ const orderBy: any = {cbt: 'name'};
+ if (sort.length) {
+ orderBy.cbt = sort[0].name + ' ' + sort[0].dir;
+ }
+
+ return this.pcrud.retrieveAll('cbt', {
+ offset: pager.offset,
+ limit: pager.limit,
+ order_by: orderBy
+ }).pipe(map(cbt => {
+ // example of inline fleshing
+ cbt.owner(this.org.get(cbt.owner()));
+ this.oneBtype = cbt;
+ return cbt;
+ }));
+ };
+ }
+
+ btGridRowClassCallback(row: any): string {
+ if (row.id() === 1) {
+ return 'text-uppercase font-weight-bold text-danger';
+ }
+ }
+
+ btGridRowFlairCallback(row: any): GridRowFlairEntry {
+ const flair = {icon: null, title: null};
+ if (row.id() === 2) {
+ flair.icon = 'priority_high';
+ flair.title = 'I Am ID 2';
+ } else if (row.id() === 3) {
+ flair.icon = 'not_interested';
+ }
+ return flair;
+ }
+
+ // apply to all 'name' columns regardless of row
+ btGridCellClassCallback(row: any, col: GridColumn): string {
+ if (col.name === 'name') {
+ if (row.id() === 7) {
+ return 'text-lowercase font-weight-bold text-info';
+ }
+ return 'text-uppercase font-weight-bold text-success';
+ }
+ }
+
+ doPrint() {
+ this.printer.print({
+ template: this.printTemplate,
+ contextData: {world : this.world},
+ printContext: 'default'
+ });
+
+ this.printer.print({
+ text: '<b>hello</b>',
+ printContext: 'default'
+ });
+
+ }
+
+ changeDate(date) {
+ console.log('HERE WITH ' + date);
+ this.testDate = date;
+ }
+
+ showProgress() {
+ this.progressDialog.open();
+
+ // every 250ms emit x*10 for 0-10
+ Observable.timer(0, 250).pipe(
+ map(x => x * 10),
+ take(11)
+ ).subscribe(
+ val => this.progressDialog.update({value: val, max: 100}),
+ err => {},
+ () => this.progressDialog.close()
+ );
+ }
+
+ testToast() {
+ this.toast.success('HELLO TOAST TEST');
+ setTimeout(() => this.toast.danger('DANGER TEST AHHH!'), 4000);
+ }
+
+ testStrings() {
+ this.strings.interpolate('staff.sandbox.test', {name : 'janey'})
+ .then(txt => this.toast.success(txt));
+
+ setTimeout(() => {
+ this.strings.interpolate('staff.sandbox.test', {name : 'johnny'})
+ .then(txt => this.toast.success(txt));
+ }, 4000);
+ }
+}
+
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {StaffCommonModule} from '@eg/staff/common.module';
+import {SandboxRoutingModule} from './routing.module';
+import {SandboxComponent} from './sandbox.component';
+
+@NgModule({
+ declarations: [
+ SandboxComponent
+ ],
+ imports: [
+ StaffCommonModule,
+ SandboxRoutingModule,
+ ],
+ providers: [
+ ]
+})
+
+export class SandboxModule {
+
+}
--- /dev/null
+Classes, services, and components shared in the staff app.
--- /dev/null
+<ng-template #successStrTmpl i18n>{{idlClassDef.label}} Update Succeeded</ng-template>
+<eg-string #successString [template]="successStrTmpl"></eg-string>
+
+<ng-template #createStrTmpl i18n>{{idlClassDef.label}} Succeessfully Created</ng-template>
+<eg-string #createString [template]="createStrTmpl"></eg-string>
+
+<ng-container *ngIf="orgField">
+ <div class="d-flex">
+ <div>
+ <div class="input-group">
+ <div class="input-group-prepend">
+ <span class="input-group-text">{{orgFieldLabel}}</span>
+ </div>
+ <eg-org-select
+ [limitPerms]="viewPerms"
+ [initialOrg]="contextOrg"
+ (onChange)="orgOnChange($event)">
+ </eg-org-select>
+ </div>
+ </div>
+ <div class="pl-2">
+ <div class="form-check">
+ <input type="checkbox" (click)="grid.reload()"
+ [disabled]="disableAncestorSelector()"
+ [(ngModel)]="includeOrgAncestors"
+ class="form-check-input" id="include-ancestors">
+ <label class="form-check-label" for="include-ancestors" i18n>+ Ancestors</label>
+ </div>
+ <div class="form-check">
+ <input type="checkbox" (click)="grid.reload()"
+ [disabled]="disableDescendantSelector()"
+ [(ngModel)]="includeOrgDescendants"
+ class="form-check-input" id="include-descendants">
+ <label class="form-check-label" for="include-descendants" i18n>+ Descendants</label>
+ </div>
+ </div>
+ </div>
+ <hr/>
+</ng-container>
+
+<!-- idlObject and fieldName applied programmatically -->
+<eg-translate #translator></eg-translate>
+
+<eg-grid #grid idlClass="{{idlClass}}" [dataSource]="dataSource"
+ [sortable]="true" persistKey="{{persistKey}}">
+ <eg-grid-toolbar-button [disabled]="!canCreate"
+ label="New {{idlClassDef.label}}" i18n-label [action]="createNew">
+ </eg-grid-toolbar-button>
+ <eg-grid-toolbar-button [disabled]="translatableFields.length == 0"
+ label="Apply Translations" i18n-label [action]="translate">
+ </eg-grid-toolbar-button>
+ <eg-grid-toolbar-action label="Delete Selected" i18n-label [action]="deleteSelected">
+ </eg-grid-toolbar-action>
+</eg-grid>
+
+<eg-fm-record-editor #editDialog idlClass="{{idlClass}}">
+</eg-fm-record-editor>
+
+
--- /dev/null
+import {Component, Input, OnInit, TemplateRef, ViewChild} from '@angular/core';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {GridDataSource} from '@eg/share/grid/grid';
+import {GridComponent} from '@eg/share/grid/grid.component';
+import {TranslateComponent} from '@eg/staff/share/translate/translate.component';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {Pager} from '@eg/share/util/pager';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {OrgService} from '@eg/core/org.service';
+import {PermService} from '@eg/core/perm.service';
+import {AuthService} from '@eg/core/auth.service';
+import {FmRecordEditorComponent} from '@eg/share/fm-editor/fm-editor.component';
+import {StringComponent} from '@eg/share/string/string.component';
+
+/**
+ * General purpose CRUD interface for IDL objects
+ *
+ * Object types using this component must be editable via PCRUD.
+ */
+
+@Component({
+ selector: 'eg-admin-page',
+ templateUrl: './admin-page.component.html'
+})
+
+export class AdminPageComponent implements OnInit {
+
+ @Input() idlClass: string;
+
+ // Default sort field, used when no grid sorting is applied.
+ @Input() sortField: string;
+
+ // Data source may be provided by the caller. This gives the caller
+ // complete control over the contents of the grid. If no data source
+ // is provided, a generic one is create which is sufficient for data
+ // that requires no special handling, filtering, etc.
+ @Input() dataSource: GridDataSource;
+
+ // Size of create/edito dialog. Uses large by default.
+ @Input() dialogSize: 'sm' | 'lg' = 'lg';
+
+ // If an org unit field is specified, an org unit filter
+ // is added to the top of the page.
+ @Input() orgField: string;
+
+ // Disable the auto-matic org unit field filter
+ @Input() disableOrgFilter: boolean;
+
+ // Include objects linking to org units which are ancestors
+ // of the selected org unit.
+ @Input() includeOrgAncestors: boolean;
+
+ // Ditto includeOrgAncestors, but descendants.
+ @Input() includeOrgDescendants: boolean;
+
+ // Optional grid persist key. This is the part of the key
+ // following eg.grid.
+ @Input() persistKey: string;
+
+ // Optional path component to add to the generated grid persist key,
+ // formatted as (for example):
+ // 'eg.grid.admin.${persistKeyPfx}.config.billing_type'
+ @Input() persistKeyPfx: string;
+
+ @ViewChild('grid') grid: GridComponent;
+ @ViewChild('editDialog') editDialog: FmRecordEditorComponent;
+ @ViewChild('successString') successString: StringComponent;
+ @ViewChild('createString') createString: StringComponent;
+ @ViewChild('translator') translator: TranslateComponent;
+
+ idlClassDef: any;
+ pkeyField: string;
+ createNew: () => void;
+ deleteSelected: (rows: IdlObject[]) => void;
+
+ // True if any columns on the object support translations
+ translateRowIdx: number;
+ translateFieldIdx: number;
+ translatableFields: string[];
+ translate: () => void;
+
+ contextOrg: IdlObject;
+ orgFieldLabel: string;
+ viewPerms: string;
+ canCreate: boolean;
+
+ constructor(
+ private idl: IdlService,
+ private org: OrgService,
+ private auth: AuthService,
+ private pcrud: PcrudService,
+ private perm: PermService,
+ private toast: ToastService
+ ) {
+ this.translatableFields = [];
+ }
+
+ applyOrgValues() {
+
+ if (this.disableOrgFilter) {
+ this.orgField = null;
+ return;
+ }
+
+ if (!this.orgField) {
+ // If no org unit field is specified, try to find one.
+ // If an object type has multiple org unit fields, the
+ // caller should specify one or disable org unit filter.
+ this.idlClassDef.fields.forEach(field => {
+ if (field['class'] === 'aou') {
+ this.orgField = field.name;
+ }
+ });
+ }
+
+ if (this.orgField) {
+ this.orgFieldLabel = this.idlClassDef.field_map[this.orgField].label;
+ this.contextOrg = this.org.root();
+ }
+ }
+
+ ngOnInit() {
+ this.idlClassDef = this.idl.classes[this.idlClass];
+ this.pkeyField = this.idlClassDef.pkey || 'id';
+
+ this.translatableFields =
+ this.idlClassDef.fields.filter(f => f.i18n).map(f => f.name);
+
+ if (!this.persistKey) {
+ this.persistKey =
+ 'admin.' +
+ (this.persistKeyPfx ? this.persistKeyPfx + '.' : '') +
+ this.idlClassDef.table;
+ }
+
+ // Limit the view org selector to orgs where the user has
+ // permacrud-encoded view permissions.
+ const pc = this.idlClassDef.permacrud;
+ if (pc && pc.retrieve) {
+ this.viewPerms = pc.retrieve.perms;
+ }
+
+ this.checkCreatePerms();
+ this.applyOrgValues();
+
+ // If the caller provides not data source, create a generic one.
+ if (!this.dataSource) {
+ this.initDataSource();
+ }
+
+ // TODO: pass the row activate handler via the grid markup
+ this.grid.onRowActivate.subscribe(
+ (idlThing: IdlObject) => {
+ this.editDialog.mode = 'update';
+ this.editDialog.recId = idlThing[this.pkeyField]();
+ this.editDialog.open({size: this.dialogSize}).then(
+ ok => {
+ this.successString.current()
+ .then(str => this.toast.success(str));
+ this.grid.reload();
+ },
+ err => {}
+ );
+ }
+ );
+
+ this.createNew = () => {
+ this.editDialog.mode = 'create';
+ this.editDialog.open({size: this.dialogSize}).then(
+ ok => {
+ this.createString.current()
+ .then(str => this.toast.success(str));
+ this.grid.reload();
+ },
+ err => {}
+ );
+ };
+
+ this.deleteSelected = (idlThings: IdlObject[]) => {
+ idlThings.forEach(idlThing => idlThing.isdeleted(true));
+ this.pcrud.autoApply(idlThings).subscribe(
+ val => console.debug('deleted: ' + val),
+ err => {},
+ () => this.grid.reload()
+ );
+ };
+
+ // Open the field translation dialog.
+ // Link the next/previous actions to cycle through each translatable
+ // field on each row.
+ this.translate = () => {
+ this.translateRowIdx = 0;
+ this.translateFieldIdx = 0;
+ this.translator.fieldName = this.translatableFields[this.translateFieldIdx];
+ this.translator.idlObject = this.dataSource.data[this.translateRowIdx];
+
+ this.translator.nextString = () => {
+
+ if (this.translateFieldIdx < this.translatableFields.length - 1) {
+ this.translateFieldIdx++;
+
+ } else if (this.translateRowIdx < this.dataSource.data.length - 1) {
+ this.translateRowIdx++;
+ this.translateFieldIdx = 0;
+ }
+
+ this.translator.idlObject =
+ this.dataSource.data[this.translateRowIdx];
+ this.translator.fieldName =
+ this.translatableFields[this.translateFieldIdx];
+ };
+
+ this.translator.prevString = () => {
+
+ if (this.translateFieldIdx > 0) {
+ this.translateFieldIdx--;
+
+ } else if (this.translateRowIdx > 0) {
+ this.translateRowIdx--;
+ this.translateFieldIdx = 0;
+ }
+
+ this.translator.idlObject =
+ this.dataSource.data[this.translateRowIdx];
+ this.translator.fieldName =
+ this.translatableFields[this.translateFieldIdx];
+ };
+
+ this.translator.open({size: 'lg'});
+ };
+ }
+
+ checkCreatePerms() {
+ this.canCreate = false;
+ const pc = this.idlClassDef.permacrud || {};
+ const perms = pc.create ? pc.create.perms : [];
+ if (perms.length === 0) { return; }
+
+ this.perm.hasWorkPermAt(perms, true).then(permMap => {
+ Object.keys(permMap).forEach(key => {
+ if (permMap[key].length > 0) {
+ this.canCreate = true;
+ }
+ });
+ });
+ }
+
+ orgOnChange(org: IdlObject) {
+ this.contextOrg = org;
+ this.grid.reload();
+ }
+
+ initDataSource() {
+ this.dataSource = new GridDataSource();
+
+ this.dataSource.getRows = (pager: Pager, sort: any[]) => {
+ const orderBy: any = {};
+
+ if (sort.length) {
+ // Sort specified from grid
+ orderBy[this.idlClass] = sort[0].name + ' ' + sort[0].dir;
+
+ } else if (this.sortField) {
+ // Default sort field
+ orderBy[this.idlClass] = this.sortField;
+ }
+
+ const searchOps = {
+ offset: pager.offset,
+ limit: pager.limit,
+ order_by: orderBy
+ };
+
+ if (this.contextOrg) {
+ // Filter rows by those linking to the context org and
+ // optionally ancestor and descendant org units.
+
+ let orgs = [this.contextOrg.id()];
+
+ if (this.includeOrgAncestors) {
+ orgs = this.org.ancestors(this.contextOrg, true);
+ }
+
+ if (this.includeOrgDescendants) {
+ // can result in duplicate workstation org IDs... meh
+ orgs = orgs.concat(
+ this.org.descendants(this.contextOrg, true));
+ }
+
+ const search = {};
+ search[this.orgField] = orgs;
+ return this.pcrud.search(this.idlClass, search, searchOps);
+ }
+
+ // No org filter -- fetch all rows
+ return this.pcrud.retrieveAll(this.idlClass, searchOps);
+ };
+ }
+
+ disableAncestorSelector(): boolean {
+ return this.contextOrg &&
+ this.contextOrg.id() === this.org.root().id();
+ }
+
+ disableDescendantSelector(): boolean {
+ return this.contextOrg && this.contextOrg.children().length === 0;
+ }
+
+}
+
+
--- /dev/null
+
+<div class='eg-bib-summary card tight-card w-100' *ngIf="summary">
+ <div class="card-header d-flex">
+ <div class="font-weight-bold">
+ Record Summary
+ </div>
+ <div class="flex-1"></div>
+ <div>
+ <a class="with-material-icon no-href text-primary"
+ title="Show More" i18n-title
+ *ngIf="!expandDisplay" (click)="expandDisplay=true">
+ <span class="material-icons">expand_more</span>
+ </a>
+ <a class="with-material-icon no-href text-primary"
+ title="Show Less" i18n-title
+ *ngIf="expandDisplay" (click)="expandDisplay=false">
+ <span class="material-icons">expand_less</span>
+ </a>
+ </div>
+ </div>
+ <div class="card-body">
+ <ul class="list-group list-group-flush">
+ <li class="list-group-item">
+ <div class="d-flex">
+ <div class="flex-1 font-weight-bold" i18n>Title:</div>
+ <div class="flex-3">{{summary.display.title}}</div>
+ <div class="flex-1 font-weight-bold pl-1" i18n>Edition:</div>
+ <div class="flex-1">{{summary.display.edition}}</div>
+ <div class="flex-1 font-weight-bold" i18n>TCN:</div>
+ <div class="flex-1">{{summary.record.tcn_value()}}</div>
+ <div class="flex-1 font-weight-bold pl-1" i18n>Created By:</div>
+ <div class="flex-1" *ngIf="summary.record.creator().usrname">
+ <a href="/eg/staff/circ/patron/{{summary.record.creator().id()}}/checkout">
+ {{summary.record.creator().usrname()}}
+ </a>
+ </div>
+ </div>
+ </li>
+ <li class="list-group-item" *ngIf="expandDisplay">
+ <div class="d-flex">
+ <div class="flex-1 font-weight-bold" i18n>Author:</div>
+ <div class="flex-3">{{summary.display.author}}</div>
+ <div class="flex-1 font-weight-bold pl-1" i18n>Pubdate:</div>
+ <div class="flex-1">{{summary.display.pubdate}}</div>
+ <div class="flex-1 font-weight-bold" i18n>Database ID:</div>
+ <div class="flex-1">{{summary.id}}</div>
+ <div class="flex-1 font-weight-bold pl-1" i18n>Last Edited By:</div>
+ <div class="flex-1" *ngIf="summary.record.editor().usrname">
+ <a href="/eg/staff/circ/patron/{{summary.record.editor().id()}}/checkout">
+ {{summary.record.editor().usrname()}}
+ </a>
+ </div>
+ </div>
+ </li>
+ <li class="list-group-item" *ngIf="expandDisplay">
+ <div class="d-flex">
+ <div class="flex-1 font-weight-bold" i18n>Bib Call #:</div>
+ <div class="flex-3">{{summary.bibCallNumber}}</div>
+ <div class="flex-1 font-weight-bold" i18n>Record Owner:</div>
+ <div class="flex-1">{{orgName(summary.record.owner())}}</div>
+ <div class="flex-1 font-weight-bold pl-1" i18n>Created On:</div>
+ <div class="flex-1">{{summary.record.create_date() | date:'short'}}</div>
+ <div class="flex-1 font-weight-bold pl-1" i18n>Last Edited On:</div>
+ <div class="flex-1">{{summary.record.edit_date() | date:'short'}}</div>
+ </div>
+ </li>
+ </ul>
+ </div>
+</div>
+
--- /dev/null
+import {Component, OnInit, Input} from '@angular/core';
+import {NetService} from '@eg/core/net.service';
+import {OrgService} from '@eg/core/org.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {CatalogService} from '@eg/share/catalog/catalog.service';
+import {BibRecordService, BibRecordSummary} from '@eg/share/catalog/bib-record.service';
+
+@Component({
+ selector: 'eg-bib-summary',
+ templateUrl: 'bib-summary.component.html',
+ styles: ['.eg-bib-summary .card-header {padding: .25rem .5rem}']
+})
+export class BibSummaryComponent implements OnInit {
+
+ initDone = false;
+ expandDisplay = true;
+
+ // If provided, the record will be fetched by the component.
+ @Input() recordId: number;
+
+ // Otherwise, we'll use the provided bib summary object.
+ summary: BibRecordSummary;
+ @Input() set bibSummary(s: any) {
+ this.summary = s;
+ if (this.initDone && this.summary) {
+ this.summary.getBibCallNumber();
+ }
+ }
+
+ constructor(
+ private bib: BibRecordService,
+ private cat: CatalogService,
+ private net: NetService,
+ private org: OrgService,
+ private pcrud: PcrudService
+ ) {}
+
+ ngOnInit() {
+ this.initDone = true;
+ if (this.summary) {
+ this.summary.getBibCallNumber();
+ } else {
+ if (this.recordId) {
+ this.loadSummary();
+ }
+ }
+ }
+
+ loadSummary(): void {
+ this.bib.getBibSummary(this.recordId).toPromise()
+ .then(summary => {
+ summary.getBibCallNumber();
+ this.bib.fleshBibUsers([summary.record]);
+ this.summary = summary;
+ console.log(this.summary.display);
+ });
+ }
+
+ orgName(orgId: number): string {
+ if (orgId) {
+ return this.org.get(orgId).shortname();
+ }
+ }
+
+}
+
+
--- /dev/null
+<ng-template #dialogContent>
+ <div class="modal-header bg-info">
+ <h4 class="modal-title" i18n>Add To Record #{{recId}} to Bucket</h4>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close"
+ (click)="dismiss('cross_click')">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <div class="row">
+ <div class="col-lg-3 font-weight-bold" i18n>Name of existing bucket</div>
+ <div class="col-lg-5">
+ <select
+ class="form-control"
+ placeholder="Existing Bucket..."
+ i18n-placeholder
+ [(ngModel)]="selectedBucket">
+ <option *ngFor="let bkt of buckets"
+ value="{{bkt.id()}}">{{bkt.name()}}</option>
+ </select>
+ </div>
+ <div class="col-lg-4">
+ <button class="btn btn-info" (click)="addToSelected()" i18n
+ [disabled]="!selectedBucket">
+ Add To Selected Bucket
+ </button>
+ </div>
+ </div>
+ <div class="row mt-3">
+ <div class="col-lg-3 font-weight-bold" i18n>Name of new bucket</div>
+ <div class="col-lg-5">
+ <input type="text" class="form-control"
+ placeholder="New Bucket Name..."
+ i18n-placeholder
+ [(ngModel)]="newBucketName"/>
+ </div>
+ <div class="col-lg-4">
+ <button class="btn btn-info" (click)="addToNew()" i18n
+ [disabled]="!newBucketName">
+ Add To New Bucket
+ </button>
+ </div>
+ </div>
+ <div class="row mt-3">
+ <div class="col-lg-3 font-weight-bold" i18n>New bucket description</div>
+ <div class="col-lg-5">
+ <textarea size="3" type="text" class="form-control"
+ placeholder="Optional New Bucket Description..."
+ i18n-placeholder
+ [(ngModel)]="newBucketDesc">
+ </textarea>
+ </div>
+ </div>
+ </div>
+</ng-template>
--- /dev/null
+import {Component, OnInit, Input, Renderer2} from '@angular/core';
+import {NetService} from '@eg/core/net.service';
+import {IdlService} from '@eg/core/idl.service';
+import {EventService} from '@eg/core/event.service';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {AuthService} from '@eg/core/auth.service';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+
+/**
+ * Dialog for adding bib records to new and existing record buckets.
+ */
+
+@Component({
+ selector: 'eg-record-bucket-dialog',
+ templateUrl: 'record-bucket-dialog.component.html'
+})
+
+export class RecordBucketDialogComponent
+ extends DialogComponent implements OnInit {
+
+ selectedBucket: number;
+ newBucketName: string;
+ newBucketDesc: string;
+ buckets: any[];
+
+ recId: number;
+ @Input() set recordId(id: number) {
+ this.recId = id;
+ }
+
+ constructor(
+ private modal: NgbModal, // required for passing to parent
+ private renderer: Renderer2,
+ private toast: ToastService,
+ private idl: IdlService,
+ private net: NetService,
+ private evt: EventService,
+ private auth: AuthService) {
+ super(modal); // required for subclassing
+ }
+
+ ngOnInit() {
+
+ this.onOpen$.subscribe(ok => {
+ // Reset data on dialog open
+
+ this.selectedBucket = null;
+ this.newBucketName = '';
+ this.newBucketDesc = '';
+
+ this.net.request(
+ 'open-ils.actor',
+ 'open-ils.actor.container.retrieve_by_class.authoritative',
+ this.auth.token(), this.auth.user().id(),
+ 'biblio', 'staff_client'
+ ).subscribe(buckets => this.buckets = buckets);
+ });
+ }
+
+ addToSelected() {
+ this.addToBucket(this.selectedBucket);
+ }
+
+ // Create a new bucket then add the record
+ addToNew() {
+ const bucket = this.idl.create('cbreb');
+
+ bucket.owner(this.auth.user().id());
+ bucket.name(this.newBucketName);
+ bucket.description(this.newBucketDesc);
+ bucket.btype('staff_client');
+
+ this.net.request(
+ 'open-ils.actor',
+ 'open-ils.actor.container.create',
+ this.auth.token(), 'biblio', bucket
+ ).subscribe(bktId => {
+ const evt = this.evt.parse(bktId);
+ if (evt) {
+ this.toast.danger(evt.desc);
+ } else {
+ this.addToBucket(bktId);
+ }
+ });
+ }
+
+ // Add the record to the selected existing bucket
+ addToBucket(id: number) {
+ const item = this.idl.create('cbrebi');
+ item.bucket(id);
+ item.target_biblio_record_entry(this.recId);
+ this.net.request(
+ 'open-ils.actor',
+ 'open-ils.actor.container.item.create',
+ this.auth.token(), 'biblio', item
+ ).subscribe(resp => {
+ const evt = this.evt.parse(resp);
+ if (evt) {
+ this.toast.danger(evt.toString());
+ } else {
+ this.close();
+ }
+ });
+ }
+}
+
+
+
--- /dev/null
+/**
+ * Common code for mananging holdings
+ */
+import {Injectable, EventEmitter} from '@angular/core';
+import {NetService} from '@eg/core/net.service';
+
+interface NewVolumeData {
+ owner: number;
+ label?: string;
+}
+
+@Injectable()
+export class HoldingsService {
+
+ constructor(private net: NetService) {}
+
+ // Open the holdings editor UI in a new browser window/tab.
+ spawnAddHoldingsUi(
+ recordId: number, // Bib record ID
+ addToVols: number[] = [], // Add copies to existing volumes
+ volumeData: NewVolumeData[] = []) { // Creating new volumes
+
+ const raw: any[] = [];
+
+ if (addToVols) {
+ addToVols.forEach(volId => raw.push({callnumber: volId}));
+ } else if (volumeData) {
+ volumeData.forEach(data => raw.push(data));
+ }
+
+ if (raw.length === 0) { raw.push({}); }
+
+ this.net.request(
+ 'open-ils.actor',
+ 'open-ils.actor.anon_cache.set_value',
+ null, 'edit-these-copies', {
+ record_id: recordId,
+ raw: raw,
+ hide_vols : false,
+ hide_copies : false
+ }
+ ).subscribe(
+ key => {
+ if (!key) {
+ console.error('Could not create holds cache key!');
+ return;
+ }
+ setTimeout(() => {
+ const url = `/eg/staff/cat/volcopy/${key}`;
+ window.open(url, '_blank');
+ });
+ }
+ );
+ }
+
+}
+
--- /dev/null
+
+<div class="d-flex border-top border-light"
+ *ngFor="let row of rowBuckets; let rowIdx = index">
+ <div class="flex-1 p-2" *ngFor="let col of colList">
+ <ng-container *ngIf="row[col]">
+ <!-- avoid mixing [href] and [routerLink] in one link,
+ because routerLink will take precedence, even if it's empty -->
+ <ng-container *ngIf="row[col].url">
+ <a [href]="row[col].url" class="with-material-icon">
+ <span class="material-icons">edit</span>
+ <span>{{row[col].label}}</span>
+ </a>
+ </ng-container>
+ <ng-container *ngIf="row[col].routerLink">
+ <a [routerLink]="row[col].routerLink" class="with-material-icon">
+ <span class="material-icons">edit</span>
+ <span>{{row[col].label}}</span>
+ </a>
+ </ng-container>
+ </ng-container>
+ </div>
+</div>
--- /dev/null
+import {Component, Input, OnInit, AfterViewInit, Host} from '@angular/core';
+
+interface LinkTableLink {
+ label: string;
+ url?: string;
+ routerLink?: string;
+}
+
+@Component({
+ selector: 'eg-link-table',
+ templateUrl: './link-table.component.html'
+})
+
+export class LinkTableComponent implements AfterViewInit {
+ @Input() columnCount: number;
+ links: LinkTableLink[];
+ rowBuckets: any[];
+ colList: number[];
+ colWidth: number;
+
+ constructor() {
+ this.links = [];
+ this.rowBuckets = [];
+ this.colList = [];
+ }
+
+ ngAfterViewInit() {
+ // table-ize the links
+ const rowCount = Math.ceil(this.links.length / this.columnCount);
+ this.colWidth = Math.floor(12 / this.columnCount); // Bootstrap 12-grid
+
+ for (let col = 0; col < this.columnCount; col++) {
+ this.colList.push(col);
+ }
+
+ // Modifying values in AfterViewInit without other activity
+ // happening can result in the modified values not getting
+ // displayed until some action occurs. Modifing after
+ // via timeout works though.
+ setTimeout(() => {
+ for (let row = 0; row < rowCount; row++) {
+ this.rowBuckets[row] = [
+ this.links[row],
+ this.links[row + Number(rowCount)],
+ this.links[row + Number(rowCount * 2)]
+ ];
+ }
+ });
+ }
+}
+
+@Component({
+ selector: 'eg-link-table-link',
+ template: '<ng-template></ng-template>'
+})
+
+export class LinkTableLinkComponent implements OnInit {
+ @Input() label: string;
+ @Input() url: string;
+ @Input() routerLink: string;
+
+ constructor(@Host() private linkTable: LinkTableComponent) {}
+
+ ngOnInit() {
+ this.linkTable.links.push({
+ label : this.label,
+ url: this.url,
+ routerLink: this.routerLink
+ });
+ }
+}
+
+
--- /dev/null
+<ng-template #dialogContent>
+ <div class="modal-header bg-info">
+ <h4 class="modal-title" i18n>Change Operator</h4>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close"
+ (click)="dismiss('cross_click')">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body">
+ <form class="form-validated">
+ <div class="form-group row">
+ <label class="col-lg-4 text-right font-weight-bold" for="username" i18n>Username</label>
+ <input
+ type="text"
+ class="form-control col-lg-7"
+ id="username"
+ name="username"
+ required
+ (keyup.enter)="login()"
+ autocomplete="username"
+ i18n-placeholder
+ placeholder="Username..."
+ [(ngModel)]="username"/>
+ </div>
+
+ <div class="form-group row">
+ <label class="col-lg-4 text-right font-weight-bold"
+ for="password" i18n>Password</label>
+ <input
+ type="password"
+ class="form-control col-lg-7"
+ id="password"
+ name="password"
+ required
+ (keyup.enter)="login()"
+ autocomplete="current-password"
+ i18n-placeholder
+ placeholder="Password..."
+ [(ngModel)]="password"/>
+ </div>
+
+ <div class="form-group row">
+ <label class="col-lg-4 text-right font-weight-bold"
+ for="loginType" i18n>Login Type</label>
+ <select
+ class="form-control col-lg-7"
+ id="loginType"
+ name="loginType"
+ placeholder="Login Type..."
+ i18n-placeholder
+ required
+ [(ngModel)]="loginType">
+ <option value="temp" selected i18n>Temporary</option>
+ <option value="staff" i18n>Staff</option>
+ <option value="persist" i18n>Persistent</option>
+ </select>
+ </div>
+ </form>
+ </div>
+ <div class="modal-footer">
+ <button (click)="login()" class="btn btn-info" i18n>OK/Continue</button>
+ <button (click)="dismiss('canceled')" class="btn btn-warning ml-2" i18n>Cancel</button>
+ </div>
+</ng-template>
--- /dev/null
+import {Component, OnInit, Input, Renderer2} from '@angular/core';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {AuthService} from '@eg/core/auth.service';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+
+@Component({
+ selector: 'eg-op-change',
+ templateUrl: 'op-change.component.html'
+})
+
+export class OpChangeComponent
+ extends DialogComponent implements OnInit {
+
+ @Input() username: string;
+ @Input() password: string;
+ @Input() loginType = 'temp';
+
+ @Input() successMessage: string;
+ @Input() failMessage: string;
+
+ constructor(
+ private modal: NgbModal, // required for passing to parent
+ private renderer: Renderer2,
+ private toast: ToastService,
+ private auth: AuthService) {
+ super(modal);
+ }
+
+ ngOnInit() {
+
+ // Focus the username any time the dialog is opened.
+ this.onOpen$.subscribe(
+ val => this.renderer.selectRootElement('#username').focus()
+ );
+ }
+
+ login(): Promise<any> {
+ if (!(this.username && this.password)) {
+ return Promise.reject('Missing Params');
+ }
+
+ return this.auth.login(
+ { username : this.username,
+ password : this.password,
+ workstation : this.auth.workstation(),
+ type : this.loginType
+ }, true // isOpChange
+ ).then(
+ ok => {
+ this.password = '';
+ this.username = '';
+
+ // Fetch the user object
+ this.auth.testAuthToken().then(
+ ok2 => {
+ this.close();
+ this.toast.success(this.successMessage);
+ }
+ );
+ },
+ notOk => {
+ this.password = '';
+ this.toast.danger(this.failMessage);
+ }
+ );
+ }
+
+ restore(): Promise<any> {
+ return this.auth.undoOpChange().then(
+ ok => this.toast.success(this.successMessage),
+ err => this.toast.danger(this.failMessage)
+ );
+ }
+}
+
+
--- /dev/null
+import {Component, OnInit, Input} from '@angular/core';
+
+@Component({
+ selector: 'eg-staff-banner',
+ template:
+ '<div class="lead alert alert-primary text-center pt-1 pb-1" role="alert">' +
+ '<span>{{bannerText}}</span>' +
+ '</div>'
+})
+
+export class StaffBannerComponent {
+ @Input() public bannerText: string;
+}
+
+
--- /dev/null
+<ng-template #dialogContent>
+ <div class="modal-header bg-info">
+ <h4 class="modal-title" i18n>
+ {{idlClassDef.label}}
+ </h4>
+ <button type="button" class="close"
+ i18n-aria-label aria-label="Close"
+ (click)="dismiss('cross_click')">
+ <span aria-hidden="true">×</span>
+ </button>
+ </div>
+ <div class="modal-body form-common form-validated" *ngIf="idlObj">
+ <div class="form-group row">
+ <label class="col-lg-4 text-right font-weight-bold"
+ i18n>Field Name</label>
+ <input
+ type="text"
+ [disabled]="true"
+ class="form-control col-lg-7"
+ value="{{idlClassDef.field_map[field].label}}">
+ </div>
+ <div class="form-group row">
+ <label class="col-lg-4 text-right font-weight-bold"
+ i18n>Current Value</label>
+ <input
+ type="text"
+ [disabled]="true"
+ class="form-control col-lg-7"
+ value="{{idlObj[field]()}}">
+ </div>
+ <div class="form-group row">
+ <label class="col-lg-4 text-right font-weight-bold"
+ i18n>Select Locale</label>
+ <select class="form-control col-lg-7"
+ (change)="localeChanged($event)"
+ [(ngModel)]="selectedLocale">
+ <option value="{{locale.code()}}" *ngFor="let locale of locales">
+ {{locale.name()}}
+ </option>
+ </select>
+ </div>
+ <div class="form-group row">
+ <label class="col-lg-4 text-right font-weight-bold" i18n>Translation</label>
+ <input
+ id='translation-input'
+ type="text"
+ class="form-control col-lg-7"
+ required
+ i18n-placeholder
+ (keyup.enter)="translate()"
+ placeholder="Translation..."
+ [(ngModel)]="translatedValue"/>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <button *ngIf="prevString" (click)="prevString()"
+ class="btn btn-info" i18n>Prev String</button>
+ <button *ngIf="nextString" (click)="nextString()"
+ class="btn btn-info mr-3" i18n>Next String</button>
+ <button (click)="translate()" class="btn btn-info" i18n>Apply</button>
+ <button (click)="dismiss('canceled')" class="btn btn-warning ml-2" i18n>Cancel</button>
+ </div>
+</ng-template>
--- /dev/null
+import {Component, OnInit, Input, Renderer2} from '@angular/core';
+import {IdlService, IdlObject} from '@eg/core/idl.service';
+import {ToastService} from '@eg/share/toast/toast.service';
+import {LocaleService} from '@eg/core/locale.service';
+import {AuthService} from '@eg/core/auth.service';
+import {PcrudService} from '@eg/core/pcrud.service';
+import {DialogComponent} from '@eg/share/dialog/dialog.component';
+import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
+
+@Component({
+ selector: 'eg-translate',
+ templateUrl: 'translate.component.html'
+})
+
+export class TranslateComponent
+ extends DialogComponent implements OnInit {
+
+ idlClassDef: any;
+ locales: IdlObject[];
+ selectedLocale: string;
+ translatedValue: string;
+ existingTranslation: IdlObject;
+
+ // These actions should update the idlObject and/or fieldName values,
+ // forcing the dialog to load a new string to translate. When set,
+ // applying a translation in the dialog will leave the dialog window open
+ // so the next/prev buttons can be used to fetch the next string.
+ nextString: () => void;
+ prevString: () => void;
+
+ idlObj: IdlObject;
+ @Input() set idlObject(o: IdlObject) {
+ if (o) {
+ this.idlObj = o;
+ this.idlClassDef = this.idl.classes[o.classname];
+ this.fetchTranslation();
+ }
+ }
+
+ field: string;
+ @Input() set fieldName(n: string) {
+ this.field = n;
+ }
+
+ constructor(
+ private modal: NgbModal, // required for passing to parent
+ private renderer: Renderer2,
+ private idl: IdlService,
+ private toast: ToastService,
+ private locale: LocaleService,
+ private pcrud: PcrudService,
+ private auth: AuthService) {
+ super(modal);
+ }
+
+ ngOnInit() {
+ // Default to the login locale
+ this.selectedLocale = this.locale.currentLocaleCode();
+ this.locales = [];
+ this.locale.supportedLocales().subscribe(l => this.locales.push(l));
+
+ this.onOpen$.subscribe(() => {
+ const elm = this.renderer.selectRootElement('#translation-input');
+ if (elm) {
+ elm.focus();
+ elm.select();
+ }
+ });
+ }
+
+ localeChanged(code: string) {
+ this.fetchTranslation();
+ }
+
+ fetchTranslation() {
+ const exist = this.existingTranslation;
+
+ if (exist
+ && exist.fq_field() === this.fqField()
+ && exist.identity_value() === this.identValue()) {
+ // Already have the current translation object.
+ return;
+ }
+
+ this.translatedValue = '';
+ this.existingTranslation = null;
+
+ this.pcrud.search('i18n', {
+ translation: this.selectedLocale,
+ fq_field : this.fqField(),
+ identity_value: this.identValue()
+ }).subscribe(tr => {
+ this.existingTranslation = tr;
+ this.translatedValue = tr.string();
+ console.debug('found existing translation ', tr);
+ });
+ }
+
+ fqField(): string {
+ return this.idlClassDef.classname + '.' + this.field;
+ }
+
+ identValue(): string {
+ return this.idlObj[this.idlClassDef.pkey || 'id']();
+ }
+
+ translate() {
+ if (!this.translatedValue) { return; }
+
+ let entry;
+
+ if (this.existingTranslation) {
+ entry = this.existingTranslation;
+ entry.string(this.translatedValue);
+
+ this.pcrud.update(entry).toPromise().then(
+ ok => {
+ if (!this.nextString) {
+ this.close('Translation updated');
+ }
+ },
+ err => console.error(err)
+ );
+
+ return;
+ }
+
+ entry = this.idl.create('i18n');
+ entry.fq_field(this.fqField());
+ entry.identity_value(this.identValue());
+ entry.translation(this.selectedLocale);
+ entry.string(this.translatedValue);
+
+ this.pcrud.create(entry).toPromise().then(
+ ok => {
+ if (!this.nextString) {
+ this.close('Translation created');
+ }
+ },
+ err => console.error('Translation creation failed')
+ );
+ }
+}
+
+
--- /dev/null
+
+
+<style>
+ /* TODO change BS color scheme so this isn't necessary */
+ .bg-evergreen {
+ background: -webkit-linear-gradient(#00593d, #007a54);
+ background-color: #007a54;
+ color: #fff;
+ }
+
+ /* Match the ang1 splash page */
+ .card-header {
+ color: #3c763d;
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+ }
+</style>
+
+<div class="container">
+
+ <!-- header icon -->
+ <div class="row mb-3">
+ <div class="col-lg-12 text-center">
+ <img src="/images/portal/logo.png"/>
+ </div>
+ </div>
+
+ <div class="row" id="splash-nav">
+ <div class="col-lg-4">
+ <div class="card">
+ <div class="card-header">
+ <div class="panel-title text-center" i18n>Circulation and Patrons</div>
+ </div>
+ <div class="card-body">
+ <div class="list-group">
+ <div class="list-group-item border-0 p-2">
+ <img src="/images/portal/forward.png"/>
+ <a href="/eg/staff/circ/patron/bcsearch" i18n>Check Out Items</a>
+ </div>
+ <div class="list-group-item border-0 p-2">
+ <img src="/images/portal/back.png"/>
+ <a href="/eg/staff/circ/checkin/index" i18n>Check In Items</a>
+ </div>
+ <div class="list-group-item border-0 p-2">
+ <img src="/images/portal/retreivepatron.png"/>
+ <a href="/eg/staff/circ/patron/search" i18n>Search For Patron By Name</a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="col-lg-4">
+ <div class="card">
+ <div class="card-header">
+ <div class="panel-title text-center" i18n>Item Search and Cataloging</div>
+ </div>
+ <div class="card-body">
+ <div class="list-group">
+ <div class="list-group-item border-0 p-2">
+ <div class="input-group">
+ <input type="text" class="form-control"
+ [(ngModel)]="catSearchQuery"
+ id='catalog-search-input'
+ (keyup.enter)="searchCatalog()"
+ i18n-placeholder placeholder="Search for...">
+ <span class="input-group-btn">
+ <button class="btn btn-outline-secondary"
+ (click)="searchCatalog()" type="button" i18n>
+ Search
+ </button>
+ </span>
+ <!--
+ <input focus-me="focus_search"
+ class="form-control" ng-model="cat_query" type="text"
+ ng-keypress="catalog_search($event)"
+ placeholder="Search catalog for..."/>
+ <button class='btn btn-light' ng-click="catalog_search()">
+ Search
+ </button>
+ -->
+ </div>
+ </div>
+ <div class="list-group-item border-0 p-2">
+ <img src="/images/portal/bucket.png"/>
+ <a href="/eg/staff/cat/bucket/record/" i18n>Record Buckets</a>
+ </div>
+ <div class="list-group-item border-0 p-2">
+ <img src="/images/portal/bucket.png"/>
+ <a href="/eg/staff/cat/bucket/copy/" i18n>Copy Buckets</a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <div class="col-lg-4">
+ <div class="card">
+ <div class="card-header">
+ <div class="panel-title text-center" i18n>Administration</div>
+ </div>
+ <div class="card-body">
+ <div class="list-group">
+ <div class="list-group-item border-0 p-2">
+ <img src="/images/portal/helpdesk.png"/>
+ <a target="_top" href="http://docs.evergreen-ils.org/" i18n>
+ Evergreen Documentation
+ </a>
+ </div>
+ <div class="list-group-item border-0 p-2">
+ <img src="/images/portal/helpdesk.png"/>
+ <a target="_top" href="/eg/staff/admin/workstation/index" i18n>
+ Workstation Administration
+ </a>
+ </div>
+ <div class="list-group-item border-0 p-2">
+ <img src="/images/portal/reports.png"/>
+ <a target="_top" href="/eg/staff/reporter/legacy/main" i18n>
+ Reports
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
+
--- /dev/null
+import {Component, OnInit, Renderer2} from '@angular/core';
+import {Router} from '@angular/router';
+
+@Component({
+ templateUrl: 'splash.component.html'
+})
+
+export class StaffSplashComponent implements OnInit {
+
+ catSearchQuery: string;
+
+ constructor(
+ private renderer: Renderer2,
+ private router: Router
+ ) {}
+
+ ngOnInit() {
+
+ // Focus catalog search form
+ this.renderer.selectRootElement('#catalog-search-input').focus();
+ }
+
+ searchCatalog(): void {
+ if (!this.catSearchQuery) { return; }
+
+ /* Route to angular6 catalog
+ this.router.navigate(
+ ['/staff/catalog/search'],
+ {queryParams: {query : this.catSearchQuery}}
+ );
+ */
+
+ // Route to AngularJS / TPAC catalog
+ window.location.href =
+ '/eg/staff/cat/catalog/results?query=' +
+ encodeURIComponent(this.catSearchQuery);
+ }
+}
+
+
--- /dev/null
+#staff-content-container {
+ width: 95%;
+ margin-top:56px;
+ padding-right: 10px;
+ padding-left: 10px;
+ margin-right: auto;
+ margin-left: auto;
+}
--- /dev/null
+<!-- top navigation bar -->
+<eg-staff-nav-bar></eg-staff-nav-bar>
+
+<div id='staff-content-container'>
+ <!-- page content -->
+ <router-outlet></router-outlet>
+</div>
+
+<!-- EgAccessKey Info Panel -->
+<eg-accesskey-info #egAccessKeyInfo></eg-accesskey-info>
+<a egAccessKey keyCtx="base"
+ keySpec="ctrl+h" i18n-keySpec
+ keyDesc="Display AccessKey Info Dialog" i18n-keyDesc
+ (click)="egAccessKeyInfo.open()">
+</a>
+
+<!-- global toast alerts -->
+<eg-toast></eg-toast>
+
--- /dev/null
+import {Component, OnInit, NgZone, HostListener} from '@angular/core';
+import {Router, ActivatedRoute, NavigationEnd} from '@angular/router';
+import {AuthService, AuthWsState} from '@eg/core/auth.service';
+import {NetService} from '@eg/core/net.service';
+import {AccessKeyService} from '@eg/share/accesskey/accesskey.service';
+import {AccessKeyInfoComponent} from '@eg/share/accesskey/accesskey-info.component';
+
+const LOGIN_PATH = '/staff/login';
+const WS_BASE_PATH = '/staff/admin/workstation/workstations/';
+const WS_MANAGE_PATH = '/staff/admin/workstation/workstations/manage';
+
+@Component({
+ templateUrl: 'staff.component.html',
+ styleUrls: ['staff.component.css']
+})
+
+export class StaffComponent implements OnInit {
+
+ constructor(
+ private router: Router,
+ private route: ActivatedRoute,
+ private zone: NgZone,
+ private net: NetService,
+ private auth: AuthService,
+ private keys: AccessKeyService
+ ) {}
+
+ ngOnInit() {
+
+ // Fires on all in-staff-app router navigation, but not initial
+ // page load.
+ this.router.events.subscribe(routeEvent => {
+ if (routeEvent instanceof NavigationEnd) {
+ // console.debug(`StaffComponent routing to ${routeEvent.url}`);
+ this.preventForbiddenNavigation(routeEvent.url);
+ }
+ });
+
+ // Redirect to the login page on any auth timeout events.
+ this.net.authExpired$.subscribe(expireEvent => {
+
+ // If the expired authtoken was identified locally (i.e.
+ // in this browser tab) notify all tabs of imminent logout.
+ if (!expireEvent.viaExternal) {
+ this.auth.broadcastLogout();
+ }
+
+ console.debug('Auth session has expired. Redirecting to login');
+ this.auth.redirectUrl = this.router.url;
+
+ // https://github.com/angular/angular/issues/18254
+ // When a tab redirects to a login page as a result of
+ // another tab broadcasting a logout, ngOnInit() fails to
+ // fire in the login component, until the user interacts
+ // with the page. Fix it by wrapping it in zone.run().
+ // This is the only navigate() where I have seen this happen.
+ this.zone.run(() => {
+ this.router.navigate([LOGIN_PATH]);
+ });
+ });
+
+ this.route.data.subscribe((data: {staffResolver: any}) => {
+ // Data fetched via StaffResolver is available here.
+ });
+
+
+ }
+
+ /**
+ * Prevent the user from leaving the login page when they don't have
+ * a valid authoken.
+ *
+ * Prevent the user from leaving the workstation admin page when
+ * they don't have a valid workstation.
+ *
+ * This does not verify auth tokens with the server on every route,
+ * because that would be overkill. This is only here to keep
+ * people boxed in with their authenication state was already
+ * known to be less then 100%.
+ */
+ preventForbiddenNavigation(url: string): void {
+
+ // No auth checks needed for login page.
+ if (url.startsWith(LOGIN_PATH)) {
+ return;
+ }
+
+ // We lost our authtoken, go back to the login page.
+ if (!this.auth.token()) {
+ this.router.navigate([LOGIN_PATH]);
+ }
+
+ // No workstation checks needed for workstation admin page.
+ if (url.startsWith(WS_BASE_PATH)) {
+ return;
+ }
+
+ if (this.auth.workstationState !== AuthWsState.VALID) {
+ this.router.navigate([WS_MANAGE_PATH]);
+ }
+ }
+
+ /**
+ * Listen for keyboard events here -- the root directive -- and pass
+ * events down to the key service for processing.
+ */
+ @HostListener('window:keydown', ['$event']) onKeyDown(evt: KeyboardEvent) {
+ this.keys.fire(evt);
+ }
+
+ /*
+ @ViewChild('egAccessKeyInfo')
+ private keyComponent: AccessKeyInfoComponent;
+ */
+
+}
+
+
--- /dev/null
+import {NgModule} from '@angular/core';
+import {StaffCommonModule} from '@eg/staff/common.module';
+
+import {StaffComponent} from './staff.component';
+import {StaffRoutingModule} from './routing.module';
+import {StaffNavComponent} from './nav.component';
+import {StaffLoginComponent} from './login.component';
+import {StaffSplashComponent} from './splash.component';
+import {AboutComponent} from './about.component';
+
+@NgModule({
+ declarations: [
+ StaffComponent,
+ StaffNavComponent,
+ StaffSplashComponent,
+ StaffLoginComponent,
+ AboutComponent
+ ],
+ imports: [
+ StaffCommonModule.forRoot(),
+ StaffRoutingModule
+ ]
+})
+
+export class StaffModule {}
+
--- /dev/null
+<div class="jumbotron">
+ <h1 i18n class="display-3">Welcome to Webby</h1>
+ <p i18n class="lead">
+ If you see this page, you're probably in good shape...
+ </p>
+ <hr class="my-4"/>
+ <p i18n>
+ But maybe you meant to go to the
+ <a routerLink="/staff/splash">staff page</a>
+ </p>
+</div>
--- /dev/null
+import { Component, OnInit } from '@angular/core';
+
+@Component({
+ templateUrl : './welcome.component.html'
+})
+
+export class WelcomeComponent implements OnInit {
+ ngOnInit() {
+ }
+}
+
+
+
--- /dev/null
+export const environment = {
+ production: true,
+ locales: ['en-US', 'fr-CA']
+};
--- /dev/null
+// The file contents for the current environment will overwrite these during build.
+// The build system defaults to the dev environment which uses `environment.ts`, but if you do
+// `ng build --env=prod` then `environment.prod.ts` will be used instead.
+// The list of which env maps to which file can be found in `.angular-cli.json`.
+
+export const environment = {
+ production: false,
+ // currently locales are only supported in production builds.
+ locales: ['en-US']
+};
--- /dev/null
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title i18n="Page Title">AngEG</title>
+ <base href="/eg2">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <link rel="icon" type="image/x-icon" href="favicon.ico">
+ <!-- see notes in styles.css regarding locally served fonts -->
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
+</head>
+<body>
+ <eg-root></eg-root>
+ <script src="/IDL2js"></script>
+ <script src="/js/dojo/opensrf/JSON_v1.js"></script>
+ <script src="/js/dojo/opensrf/opensrf.js"></script>
+ <script src="/js/dojo/opensrf/opensrf_ws.js"></script>
+</body>
+</html>
--- /dev/null
+import { enableProdMode } from '@angular/core';
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { BaseModule } from './app/app.module';
+import { environment } from './environments/environment';
+
+if (environment.production) {
+ enableProdMode();
+}
+
+platformBrowserDynamic().bootstrapModule(BaseModule)
+ .catch(err => console.log(err));
--- /dev/null
+/**
+ * This file includes polyfills needed by Angular and is loaded before the app.
+ * You can add your own extra polyfills to this file.
+ *
+ * This file is divided into 2 sections:
+ * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
+ * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
+ * file.
+ *
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
+ *
+ * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
+ */
+
+/***************************************************************************************************
+ * BROWSER POLYFILLS
+ */
+
+/** IE9, IE10 and IE11 requires all of the following polyfills. **/
+// import 'core-js/es6/symbol';
+// import 'core-js/es6/object';
+// import 'core-js/es6/function';
+// import 'core-js/es6/parse-int';
+// import 'core-js/es6/parse-float';
+// import 'core-js/es6/number';
+// import 'core-js/es6/math';
+// import 'core-js/es6/string';
+// import 'core-js/es6/date';
+// import 'core-js/es6/array';
+// import 'core-js/es6/regexp';
+// import 'core-js/es6/map';
+// import 'core-js/es6/weak-map';
+// import 'core-js/es6/set';
+
+// PhantomJS needs these
+import 'core-js/es6/array';
+import 'core-js/es6/string';
+
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
+// import 'classlist.js'; // Run `npm install --save classlist.js`.
+
+/** IE10 and IE11 requires the following for the Reflect API. */
+// import 'core-js/es6/reflect';
+
+
+/** Evergreen browsers require these. **/
+// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
+import 'core-js/es7/reflect';
+
+
+/**
+ * Required to support Web Animations `@angular/platform-browser/animations`.
+ * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
+ **/
+// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
+
+
+
+/***************************************************************************************************
+ * Zone JS is required by Angular itself.
+ */
+import 'zone.js/dist/zone'; // Included with Angular CLI.
+
+
+
+/***************************************************************************************************
+ * APPLICATION IMPORTS
+ */
+
+/**
+ * Date, currency, decimal and percent pipes.
+ * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
+ */
+// import 'intl'; // Run `npm install --save intl`.
+/**
+ * Need to import at least one locale-data with intl.
+ */
+// import 'intl/locale-data/jsonp/en';
--- /dev/null
+/* You can add global styles to this file, and also import other style files */
+
+/* bootstrap CSS only -- JS bits come from ng-bootstrap */
+@import '~bootstrap-css-only/css/bootstrap.min.css';
+
+/* Locally served material icon fonts.
+ * Note when I first tested these after installing the fonts
+ * via: npm install --save material-design-icons
+ * some of the icons exhibited odd behavior, adding a lot of
+ * excess space to the left or right. It only affected certain
+ * icons. More research needed.
+ * /
+/*
+@import '~material-design-icons/iconfont/material-icons.css';
+*/
+
+/** BS default fonts are huge */
+body, .form-control, .btn, .input-group-text {
+ /* This more or less matches the font size of the angularjs client.
+ * The default BS4 font of 1rem is comically large.
+ */
+ font-size: .88rem;
+}
+h2 {font-size: 1.25rem}
+h3 {font-size: 1.15rem}
+h4 {font-size: 1.05rem}
+h5 {font-size: .95rem}
+
+.small-text-1 {font-size: 85%}
+
+
+/** Ang5 routes on clicks to href's with no values, so we can't have
+ * bare href's to force anchor styling. Use this for anchors w/ no href.
+ * TODO: should we style all of them? a:not([href]) ....
+ * */
+.no-href {
+ cursor: pointer;
+ color: #007bff;
+}
+
+
+/** BS has flex utility classes, but none for specifying flex widths.
+ * BS class="col" is roughly equivelent to flex-1, but col-2 is not
+ * equivalent to flex-2, since col-2 really means 2/12 width. */
+.flex-1 {flex: 1}
+.flex-2 {flex: 2}
+.flex-3 {flex: 3}
+.flex-4 {flex: 4}
+.flex-5 {flex: 5}
+
+
+/* usefuf for mat-icon buttons without any background or borders */
+.material-icon-button {
+ /* Transparent background */
+ border: none;
+ background-color: rgba(0, 0, 0, 0.0);
+ padding-left: .25rem;
+ padding-right: .25rem; /* default .5rem */
+}
+
+.mat-icon-in-button {
+ line-height: inherit;
+}
+
+.material-icons {
+ /** default is 24px which is pretty chunky */
+ font-size: 22px;
+}
+
+/* allow spans/labels to vertically orient with material icons */
+.label-with-material-icon {
+ display: inline-flex;
+ vertical-align: middle;
+ align-items: center;
+}
+
+/* dropdown menu link/button with no downward carrot icon */
+.no-dropdown-caret::after {
+ display: none;
+}
+
+/* Default .card padding is extreme */
+.tight-card .card-body,
+.tight-card .list-group-item {
+ padding: .25rem;
+}
+.tight-card .card-header {
+ padding: .5rem;
+}
+
+@media all and (min-width: 800px) {
+ /* scrollable typeahead menus for full-size screens */
+ ngb-typeahead-window {
+ height: auto;
+ max-height: 200px;
+ overflow-x: hidden;
+ }
+}
+
+/* --------------------------------------------------------------------------
+/* Form Validation CSS - https://angular.io/guide/form-validation
+ * TODO: these colors don't fit the EG color scheme
+ * Required valid fields are left-border styled in green-ish.
+ * Invalid fields are left-border styled in red-ish.
+ */
+.form-validated .ng-valid[required], .form-validated .ng-valid.required {
+ border-left: 5px solid #78FA89;
+}
+.form-validated .ng-invalid:not(form) {
+ border-left: 5px solid #FA787E;
+}
+
+/* Typical form CSS.
+ * Brings font size down 5% to squeeze a bit more in.
+ * Bold labels
+ * Fixes some bootstrap margin funkiness with checkboxes for
+ * better vertical alignment.
+ * Optional faint odd or even row striping.
+ */
+.common-form {
+ font-size: 95%;
+}
+.common-form .row {
+ margin: 5px;
+ padding: 3px;
+}
+
+.common-form label {
+ font-weight: bold;
+}
+.common-form input[type="checkbox"] {
+ /* BS adds a negative left margin */
+ margin-left: 0px;
+}
+.common-form.striped-even .row:nth-child(even) {
+ background-color: rgba(0,0,0,.03);
+ border-top: 1px solid rgba(0,0,0,.125);
+ border-bottom: 1px solid rgba(0,0,0,.125);
+}
+.common-form.striped-odd .row:nth-child(odd) {
+ background-color: rgba(0,0,0,.03);
+ border-top: 1px solid rgba(0,0,0,.125);
+ border-bottom: 1px solid rgba(0,0,0,.125);
+}
+
+
+/**
+ * Only display the print container when printing
+ */
+#eg-print-container {
+ display: none;
+}
+@media print {
+ head {display: none} /* just to be safe */
+ body div:not([id="eg-print-container"]) {display: none}
+ div {display: none}
+ #eg-print-container {display: block}
+ #eg-print-container div {display: block}
+ #eg-print-container pre {border: none}
+}
+
--- /dev/null
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
+
+import 'zone.js/dist/long-stack-trace-zone';
+import 'zone.js/dist/proxy.js';
+import 'zone.js/dist/sync-test';
+import 'zone.js/dist/jasmine-patch';
+import 'zone.js/dist/async-test';
+import 'zone.js/dist/fake-async-test';
+import { getTestBed } from '@angular/core/testing';
+import {
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting
+} from '@angular/platform-browser-dynamic/testing';
+
+// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
+declare const __karma__: any;
+declare const require: any;
+
+// Prevent Karma from running prematurely.
+__karma__.loaded = function () {};
+
+// First, initialize the Angular testing environment.
+getTestBed().initTestEnvironment(
+ BrowserDynamicTestingModule,
+ platformBrowserDynamicTesting()
+);
+// Then we find all the tests.
+const context = require.context('./', true, /\.spec\.ts$/);
+// And load the modules.
+context.keys().map(context);
+// Finally, start Karma to run the tests.
+__karma__.start();
--- /dev/null
+/**
+ * Mock data required by multiple unit tests.
+ */
+
+window._eg_mock_data = {
+
+ // builds a mock org unit tree fleshed with ou_types and
+ // absorbs the tree into egEnv
+ generateOrgTree : function(idlService, orgService) {
+ var type1 = idlService.create('aout');
+ type1.id(1);
+ type1.depth(0);
+
+ var type2 = idlService.create('aout');
+ type2.id(2);
+ type2.depth(1);
+ type2.parent(1);
+
+ var type3 = idlService.create('aout');
+ type3.id(3);
+ type3.depth(2);
+ type3.parent(2);
+
+ var org1 = idlService.create('aou');
+ org1.id(1);
+ org1.ou_type(type1);
+ org1.shortname('ROOT');
+
+ var org2 = idlService.create('aou');
+ org2.id(2);
+ org2.parent_ou(1);
+ org2.ou_type(type2);
+
+ var org3 = idlService.create('aou');
+ org3.id(3);
+ org3.parent_ou(1);
+ org3.ou_type(type2);
+
+ var org4 = idlService.create('aou');
+ org4.id(4);
+ org4.parent_ou(2);
+ org4.ou_type(type3);
+
+ org1.children([org2, org3]);
+ org2.children([org4]);
+ org3.children([]);
+ org4.children([]);
+
+ orgService.orgTree = org1;
+ orgService.absorbTree();
+ }
+}
--- /dev/null
+#!/usr/bin/perl
+use strict; use warnings;
+use XML::LibXML;
+use XML::LibXSLT;
+my $out_file = 'IDL2js.js';
+my $idl_file = '../../../../examples/fm_IDL.xml';
+my $xsl_file = '../../../../xsl/fm_IDL2js.xsl';
+
+my $xslt = XML::LibXSLT->new();
+my $style_doc = XML::LibXML->load_xml(location => $xsl_file, no_cdata=>1);
+my $stylesheet = $xslt->parse_stylesheet($style_doc);
+my $idl_string = preprocess_idl_file($idl_file);
+my $idl_doc = XML::LibXML->load_xml(string => $idl_string);
+my $results = $stylesheet->transform($idl_doc);
+my $output = $stylesheet->output_as_bytes($results);
+
+open(IDL, ">$out_file") or die "Cannot open IDL2js file $out_file : $!\n";
+
+print IDL $output;
+
+close(IDL);
+
+
+sub preprocess_idl_file {
+ my $file = shift;
+ open my $idl_fh, '<', $file or die "Unable to open IDL file $file : $!\n";
+ local $/ = undef;
+ my $xml = <$idl_fh>;
+ close($idl_fh);
+ # These substitutions are taken from OpenILS::WWW::IDL2js
+ $xml =~ s/<!--.*?-->//sg; # filter out XML comments ...
+ $xml =~ s/(?:^|\s+)--.*$//mg; # and SQL comments ...
+ $xml =~ s/^\s+/ /mg; # and extra leading spaces ...
+ $xml =~ s/\R*//g; # and newlines
+ return $xml;
+}
--- /dev/null
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/app",
+ "baseUrl": "./",
+ "module": "es2015",
+ "types": []
+ },
+ "exclude": [
+ "test.ts",
+ "**/*.spec.ts"
+ ]
+}
--- /dev/null
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "../out-tsc/spec",
+ "baseUrl": "./",
+ "module": "commonjs",
+ "target": "es5",
+ "types": [
+ "jasmine",
+ "node"
+ ]
+ },
+ "files": [
+ "test.ts",
+ "polyfills.ts"
+ ],
+ "include": [
+ "**/*.spec.ts",
+ "**/*.d.ts"
+ ]
+}
--- /dev/null
+/* SystemJS module definition */
+declare var module: NodeModule;
+interface NodeModule {
+ id: string;
+}
--- /dev/null
+{
+ "compileOnSave": false,
+ "compilerOptions": {
+ "outDir": "./dist/out-tsc",
+ "sourceMap": true,
+ "declaration": false,
+ "moduleResolution": "node",
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "target": "es5",
+ "baseUrl": "src",
+ "paths": {
+ "@eg/*": ["app/*"],
+ "@env/*": ["environments/*"]
+ },
+ "typeRoots": [
+ "node_modules/@types"
+ ],
+ "lib": [
+ "es2017",
+ "dom"
+ ]
+ }
+}
--- /dev/null
+{
+ "rulesDirectory": [
+ "node_modules/codelyzer"
+ ],
+ "rules": {
+ "arrow-return-shorthand": true,
+ "callable-types": true,
+ "class-name": true,
+ "comment-format": [
+ true,
+ "check-space"
+ ],
+ "curly": true,
+ "eofline": true,
+ "forin": true,
+ "import-blacklist": [
+ true,
+ "rxjs/Rx"
+ ],
+ "import-spacing": true,
+ "indent": [
+ true,
+ "spaces"
+ ],
+ "interface-over-type-literal": true,
+ "label-position": true,
+ "max-line-length": [
+ true,
+ 140
+ ],
+ "member-access": false,
+ "member-ordering": [
+ true,
+ {
+ "order": [
+ "static-field",
+ "instance-field",
+ "static-method",
+ "instance-method"
+ ]
+ }
+ ],
+ "no-arg": true,
+ "no-bitwise": true,
+ "no-console": [
+ true,
+ "time",
+ "timeEnd",
+ "trace"
+ ],
+ "no-construct": true,
+ "no-debugger": true,
+ "no-duplicate-super": true,
+ "no-empty": false,
+ "no-empty-interface": true,
+ "no-eval": true,
+ "no-inferrable-types": [
+ true,
+ "ignore-params"
+ ],
+ "no-misused-new": true,
+ "no-non-null-assertion": true,
+ "no-shadowed-variable": true,
+ "no-string-literal": false,
+ "no-string-throw": true,
+ "no-switch-case-fall-through": true,
+ "no-trailing-whitespace": true,
+ "no-unnecessary-initializer": true,
+ "no-unused-expression": true,
+ "no-use-before-declare": true,
+ "no-var-keyword": true,
+ "object-literal-sort-keys": false,
+ "one-line": [
+ true,
+ "check-open-brace",
+ "check-catch",
+ "check-else",
+ "check-whitespace"
+ ],
+ "prefer-const": true,
+ "quotemark": [
+ true,
+ "single"
+ ],
+ "radix": true,
+ "semicolon": [
+ true,
+ "always"
+ ],
+ "triple-equals": [
+ true,
+ "allow-null-check"
+ ],
+ "typedef-whitespace": [
+ true,
+ {
+ "call-signature": "nospace",
+ "index-signature": "nospace",
+ "parameter": "nospace",
+ "property-declaration": "nospace",
+ "variable-declaration": "nospace"
+ }
+ ],
+ "unified-signatures": true,
+ "variable-name": false,
+ "whitespace": [
+ true,
+ "check-branch",
+ "check-decl",
+ "check-operator",
+ "check-separator",
+ "check-type"
+ ],
+ "directive-selector": [
+ true,
+ "attribute",
+ "eg",
+ "camelCase"
+ ],
+ "component-selector": [
+ true,
+ "element",
+ "eg",
+ "kebab-case"
+ ],
+ "use-input-property-decorator": true,
+ "use-output-property-decorator": true,
+ "use-host-property-decorator": true,
+ "no-input-rename": true,
+ "no-output-rename": true,
+ "use-life-cycle-interface": true,
+ "use-pipe-transform-interface": true,
+ "component-class-suffix": true,
+ "directive-class-suffix": true
+ }
+}