From 6c706f454b2b9cdf2d46df3c60e53a0d3cb531d7 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Wed, 5 Sep 2018 16:11:41 -0400 Subject: [PATCH] LP#1775466 Angular(6) base application Signed-off-by: Bill Erickson Signed-off-by: Galen Charlton --- .gitignore | 1 + Open-ILS/src/eg2/.editorconfig | 13 + Open-ILS/src/eg2/.gitignore | 49 + Open-ILS/src/eg2/CHEAT_SHEET.adoc | 31 + Open-ILS/src/eg2/angular.json | 155 + Open-ILS/src/eg2/e2e/app.e2e-spec.ts | 14 + Open-ILS/src/eg2/e2e/app.po.ts | 11 + Open-ILS/src/eg2/e2e/tsconfig.e2e.json | 14 + Open-ILS/src/eg2/karma.conf.js | 43 + Open-ILS/src/eg2/package-lock.json | 10689 ++++++++++++++++ Open-ILS/src/eg2/package.json | 84 + Open-ILS/src/eg2/protractor.conf.js | 28 + Open-ILS/src/eg2/src/app/app.component.ts | 11 + Open-ILS/src/eg2/src/app/app.module.ts | 33 + Open-ILS/src/eg2/src/app/common.module.ts | 71 + Open-ILS/src/eg2/src/app/core/README | 9 + Open-ILS/src/eg2/src/app/core/auth.service.ts | 341 + .../src/eg2/src/app/core/event.service.ts | 55 + Open-ILS/src/eg2/src/app/core/event.spec.ts | 47 + .../src/eg2/src/app/core/format.service.ts | 103 + Open-ILS/src/eg2/src/app/core/format.spec.ts | 90 + Open-ILS/src/eg2/src/app/core/idl.service.ts | 137 + Open-ILS/src/eg2/src/app/core/idl.spec.ts | 28 + .../src/eg2/src/app/core/locale.service.ts | 69 + Open-ILS/src/eg2/src/app/core/net.service.ts | 187 + Open-ILS/src/eg2/src/app/core/org.service.ts | 278 + Open-ILS/src/eg2/src/app/core/org.spec.ts | 66 + .../src/eg2/src/app/core/pcrud.service.ts | 305 + Open-ILS/src/eg2/src/app/core/perm.service.ts | 59 + .../eg2/src/app/core/server-store.service.ts | 114 + .../src/eg2/src/app/core/store.service.ts | 107 + Open-ILS/src/eg2/src/app/core/store.spec.ts | 22 + Open-ILS/src/eg2/src/app/resolver.service.ts | 36 + Open-ILS/src/eg2/src/app/routing.module.ts | 29 + Open-ILS/src/eg2/src/app/share/README | 6 + .../accesskey/accesskey-info.component.html | 26 + .../accesskey/accesskey-info.component.ts | 25 + .../share/accesskey/accesskey.directive.ts | 56 + .../app/share/accesskey/accesskey.service.ts | 67 + .../app/share/catalog/bib-record.service.ts | 249 + .../share/catalog/catalog-common.module.ts | 28 + .../app/share/catalog/catalog-url.service.ts | 143 + .../src/app/share/catalog/catalog.service.ts | 210 + .../app/share/catalog/marc-html.component.ts | 90 + .../src/app/share/catalog/search-context.ts | 266 + .../src/app/share/catalog/unapi.service.ts | 54 + .../combobox/combobox-entry.component.ts | 25 + .../share/combobox/combobox.component.html | 27 + .../app/share/combobox/combobox.component.ts | 241 + .../date-select/date-select.component.html | 21 + .../date-select/date-select.component.ts | 70 + .../app/share/dialog/confirm.component.html | 17 + .../src/app/share/dialog/confirm.component.ts | 17 + .../src/app/share/dialog/dialog.component.ts | 80 + .../dialog/progress-inline.component.css | 5 + .../dialog/progress-inline.component.html | 28 + .../share/dialog/progress-inline.component.ts | 92 + .../app/share/dialog/progress.component.css | 5 + .../app/share/dialog/progress.component.html | 33 + .../app/share/dialog/progress.component.ts | 108 + .../app/share/dialog/prompt.component.html | 22 + .../src/app/share/dialog/prompt.component.ts | 19 + .../share/fm-editor/fm-editor.component.html | 146 + .../share/fm-editor/fm-editor.component.ts | 302 + .../share/grid/grid-body-cell.component.html | 20 + .../share/grid/grid-body-cell.component.ts | 57 + .../app/share/grid/grid-body.component.html | 39 + .../src/app/share/grid/grid-body.component.ts | 77 + .../grid/grid-column-config.component.html | 69 + .../grid/grid-column-config.component.ts | 16 + .../grid/grid-column-width.component.html | 20 + .../share/grid/grid-column-width.component.ts | 32 + .../app/share/grid/grid-column.component.ts | 57 + .../app/share/grid/grid-header.component.html | 32 + .../app/share/grid/grid-header.component.ts | 85 + .../app/share/grid/grid-print.component.html | 30 + .../app/share/grid/grid-print.component.ts | 45 + .../grid/grid-toolbar-action.component.ts | 33 + .../grid/grid-toolbar-button.component.ts | 43 + .../grid/grid-toolbar-checkbox.component.ts | 37 + .../share/grid/grid-toolbar.component.html | 152 + .../app/share/grid/grid-toolbar.component.ts | 86 + .../eg2/src/app/share/grid/grid.component.css | 142 + .../src/app/share/grid/grid.component.html | 27 + .../eg2/src/app/share/grid/grid.component.ts | 149 + .../src/eg2/src/app/share/grid/grid.module.ts | 50 + Open-ILS/src/eg2/src/app/share/grid/grid.ts | 972 ++ .../org-select/org-select.component.html | 17 + .../share/org-select/org-select.component.ts | 212 + .../src/app/share/print/print.component.html | 16 + .../src/app/share/print/print.component.ts | 133 + .../eg2/src/app/share/print/print.service.ts | 41 + .../src/app/share/string/string.component.ts | 74 + .../src/app/share/string/string.service.ts | 78 + .../src/app/share/toast/toast.component.css | 11 + .../src/app/share/toast/toast.component.html | 3 + .../src/app/share/toast/toast.component.ts | 43 + .../eg2/src/app/share/toast/toast.service.ts | 39 + .../eg2/src/app/share/tree/tree.component.css | 19 + .../src/app/share/tree/tree.component.html | 20 + .../eg2/src/app/share/tree/tree.component.ts | 60 + .../src/eg2/src/app/share/tree/tree.module.ts | 20 + Open-ILS/src/eg2/src/app/share/tree/tree.ts | 133 + .../eg2/src/app/share/util/audio.service.ts | 78 + Open-ILS/src/eg2/src/app/share/util/pager.ts | 111 + .../eg2/src/app/staff/about.component.html | 57 + .../src/eg2/src/app/staff/about.component.ts | 25 + .../admin/acq/admin-acq-splash.component.html | 60 + .../admin/acq/admin-acq-splash.component.ts | 11 + .../app/staff/admin/acq/admin-acq.module.ts | 24 + .../src/app/staff/admin/acq/routing.module.ts | 22 + .../staff/admin/basic-admin-page.component.ts | 61 + .../eg2/src/app/staff/admin/common.module.ts | 28 + .../eg2/src/app/staff/admin/routing.module.ts | 23 + .../server/admin-server-splash.component.html | 99 + .../server/admin-server-splash.component.ts | 11 + .../staff/admin/server/admin-server.module.ts | 24 + .../app/staff/admin/server/routing.module.ts | 19 + .../staff/admin/workstation/routing.module.ts | 14 + .../workstations/routing.module.ts | 25 + .../workstations/workstations.component.html | 92 + .../workstations/workstations.component.ts | 186 + .../workstations/workstations.module.ts | 18 + .../app/staff/catalog/catalog.component.html | 6 + .../app/staff/catalog/catalog.component.ts | 18 + .../src/app/staff/catalog/catalog.module.ts | 44 + .../src/app/staff/catalog/catalog.service.ts | 87 + .../catalog/record/actions.component.html | 70 + .../staff/catalog/record/actions.component.ts | 96 + .../catalog/record/copies.component.html | 53 + .../staff/catalog/record/copies.component.ts | 91 + .../catalog/record/pagination.component.html | 36 + .../catalog/record/pagination.component.ts | 164 + .../catalog/record/record.component.html | 37 + .../staff/catalog/record/record.component.ts | 84 + .../src/app/staff/catalog/resolver.service.ts | 59 + .../catalog/result/facets.component.html | 43 + .../staff/catalog/result/facets.component.ts | 48 + .../catalog/result/pagination.component.css | 8 + .../catalog/result/pagination.component.html | 28 + .../catalog/result/pagination.component.ts | 51 + .../catalog/result/record.component.html | 132 + .../staff/catalog/result/record.component.ts | 77 + .../catalog/result/results.component.html | 30 + .../staff/catalog/result/results.component.ts | 84 + .../src/app/staff/catalog/routing.module.ts | 30 + .../staff/catalog/search-form.component.css | 16 + .../staff/catalog/search-form.component.html | 244 + .../staff/catalog/search-form.component.ts | 137 + .../patron/bcsearch/bcsearch.component.html | 19 + .../patron/bcsearch/bcsearch.component.ts | 36 + .../circ/patron/bcsearch/bcsearch.module.ts | 17 + .../circ/patron/bcsearch/routing.module.ts | 19 + .../app/staff/circ/patron/routing.module.ts | 15 + .../eg2/src/app/staff/circ/routing.module.ts | 15 + .../src/eg2/src/app/staff/common.module.ts | 84 + .../eg2/src/app/staff/login.component.html | 58 + .../src/eg2/src/app/staff/login.component.ts | 96 + .../src/eg2/src/app/staff/nav.component.css | 72 + .../src/eg2/src/app/staff/nav.component.html | 432 + .../src/eg2/src/app/staff/nav.component.ts | 72 + .../src/eg2/src/app/staff/resolver.service.ts | 143 + .../src/eg2/src/app/staff/routing.module.ts | 52 + Open-ILS/src/eg2/src/app/staff/sandbox/README | 1 + .../src/app/staff/sandbox/routing.module.ts | 16 + .../app/staff/sandbox/sandbox.component.html | 133 + .../app/staff/sandbox/sandbox.component.ts | 188 + .../src/app/staff/sandbox/sandbox.module.ts | 20 + Open-ILS/src/eg2/src/app/staff/share/README | 1 + .../admin-page/admin-page.component.html | 59 + .../share/admin-page/admin-page.component.ts | 311 + .../bib-summary/bib-summary.component.html | 70 + .../bib-summary/bib-summary.component.ts | 67 + .../record-bucket-dialog.component.html | 56 + .../buckets/record-bucket-dialog.component.ts | 109 + .../src/app/staff/share/holdings.service.ts | 57 + .../link-table/link-table.component.html | 22 + .../share/link-table/link-table.component.ts | 73 + .../share/op-change/op-change.component.html | 65 + .../share/op-change/op-change.component.ts | 77 + .../app/staff/share/staff-banner.component.ts | 15 + .../share/translate/translate.component.html | 63 + .../share/translate/translate.component.ts | 145 + .../eg2/src/app/staff/splash.component.html | 128 + .../src/eg2/src/app/staff/splash.component.ts | 40 + .../src/eg2/src/app/staff/staff.component.css | 8 + .../eg2/src/app/staff/staff.component.html | 19 + .../src/eg2/src/app/staff/staff.component.ts | 118 + .../src/eg2/src/app/staff/staff.module.ts | 26 + .../src/eg2/src/app/welcome.component.html | 11 + Open-ILS/src/eg2/src/app/welcome.component.ts | 13 + Open-ILS/src/eg2/src/assets/.gitkeep | 0 .../eg2/src/environments/environment.prod.ts | 4 + .../src/eg2/src/environments/environment.ts | 10 + Open-ILS/src/eg2/src/favicon.ico | Bin 0 -> 5430 bytes Open-ILS/src/eg2/src/index.html | 19 + Open-ILS/src/eg2/src/locale/.gitkeep | 0 Open-ILS/src/eg2/src/main.ts | 12 + Open-ILS/src/eg2/src/polyfills.ts | 80 + Open-ILS/src/eg2/src/styles.css | 161 + Open-ILS/src/eg2/src/test.ts | 32 + Open-ILS/src/eg2/src/test_data/eg_mock.js | 52 + Open-ILS/src/eg2/src/test_data/idl2js.pl | 36 + Open-ILS/src/eg2/src/tsconfig.app.json | 13 + Open-ILS/src/eg2/src/tsconfig.spec.json | 21 + Open-ILS/src/eg2/src/typings.d.ts | 5 + Open-ILS/src/eg2/tsconfig.json | 24 + Open-ILS/src/eg2/tslint.json | 136 + 208 files changed, 25403 insertions(+) create mode 100644 Open-ILS/src/eg2/.editorconfig create mode 100644 Open-ILS/src/eg2/.gitignore create mode 100644 Open-ILS/src/eg2/CHEAT_SHEET.adoc create mode 100644 Open-ILS/src/eg2/angular.json create mode 100644 Open-ILS/src/eg2/e2e/app.e2e-spec.ts create mode 100644 Open-ILS/src/eg2/e2e/app.po.ts create mode 100644 Open-ILS/src/eg2/e2e/tsconfig.e2e.json create mode 100644 Open-ILS/src/eg2/karma.conf.js create mode 100644 Open-ILS/src/eg2/package-lock.json create mode 100644 Open-ILS/src/eg2/package.json create mode 100644 Open-ILS/src/eg2/protractor.conf.js create mode 100644 Open-ILS/src/eg2/src/app/app.component.ts create mode 100644 Open-ILS/src/eg2/src/app/app.module.ts create mode 100644 Open-ILS/src/eg2/src/app/common.module.ts create mode 100644 Open-ILS/src/eg2/src/app/core/README create mode 100644 Open-ILS/src/eg2/src/app/core/auth.service.ts create mode 100644 Open-ILS/src/eg2/src/app/core/event.service.ts create mode 100644 Open-ILS/src/eg2/src/app/core/event.spec.ts create mode 100644 Open-ILS/src/eg2/src/app/core/format.service.ts create mode 100644 Open-ILS/src/eg2/src/app/core/format.spec.ts create mode 100644 Open-ILS/src/eg2/src/app/core/idl.service.ts create mode 100644 Open-ILS/src/eg2/src/app/core/idl.spec.ts create mode 100644 Open-ILS/src/eg2/src/app/core/locale.service.ts create mode 100644 Open-ILS/src/eg2/src/app/core/net.service.ts create mode 100644 Open-ILS/src/eg2/src/app/core/org.service.ts create mode 100644 Open-ILS/src/eg2/src/app/core/org.spec.ts create mode 100644 Open-ILS/src/eg2/src/app/core/pcrud.service.ts create mode 100644 Open-ILS/src/eg2/src/app/core/perm.service.ts create mode 100644 Open-ILS/src/eg2/src/app/core/server-store.service.ts create mode 100644 Open-ILS/src/eg2/src/app/core/store.service.ts create mode 100644 Open-ILS/src/eg2/src/app/core/store.spec.ts create mode 100644 Open-ILS/src/eg2/src/app/resolver.service.ts create mode 100644 Open-ILS/src/eg2/src/app/routing.module.ts create mode 100644 Open-ILS/src/eg2/src/app/share/README create mode 100644 Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/accesskey/accesskey.directive.ts create mode 100644 Open-ILS/src/eg2/src/app/share/accesskey/accesskey.service.ts create mode 100644 Open-ILS/src/eg2/src/app/share/catalog/bib-record.service.ts create mode 100644 Open-ILS/src/eg2/src/app/share/catalog/catalog-common.module.ts create mode 100644 Open-ILS/src/eg2/src/app/share/catalog/catalog-url.service.ts create mode 100644 Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts create mode 100644 Open-ILS/src/eg2/src/app/share/catalog/marc-html.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/catalog/search-context.ts create mode 100644 Open-ILS/src/eg2/src/app/share/catalog/unapi.service.ts create mode 100644 Open-ILS/src/eg2/src/app/share/combobox/combobox-entry.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/combobox/combobox.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/combobox/combobox.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/date-select/date-select.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/date-select/date-select.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/dialog/confirm.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/dialog/confirm.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/dialog/dialog.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/dialog/progress-inline.component.css create mode 100644 Open-ILS/src/eg2/src/app/share/dialog/progress-inline.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/dialog/progress-inline.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/dialog/progress.component.css create mode 100644 Open-ILS/src/eg2/src/app/share/dialog/progress.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/dialog/progress.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/dialog/prompt.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/dialog/prompt.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-body-cell.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-body-cell.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-body.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-body.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-column-config.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-column-config.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-column-width.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-column-width.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-column.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-header.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-header.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-print.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-print.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-toolbar-action.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-toolbar-button.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-toolbar-checkbox.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid.component.css create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid.module.ts create mode 100644 Open-ILS/src/eg2/src/app/share/grid/grid.ts create mode 100644 Open-ILS/src/eg2/src/app/share/org-select/org-select.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/org-select/org-select.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/print/print.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/print/print.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/print/print.service.ts create mode 100644 Open-ILS/src/eg2/src/app/share/string/string.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/string/string.service.ts create mode 100644 Open-ILS/src/eg2/src/app/share/toast/toast.component.css create mode 100644 Open-ILS/src/eg2/src/app/share/toast/toast.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/toast/toast.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/toast/toast.service.ts create mode 100644 Open-ILS/src/eg2/src/app/share/tree/tree.component.css create mode 100644 Open-ILS/src/eg2/src/app/share/tree/tree.component.html create mode 100644 Open-ILS/src/eg2/src/app/share/tree/tree.component.ts create mode 100644 Open-ILS/src/eg2/src/app/share/tree/tree.module.ts create mode 100644 Open-ILS/src/eg2/src/app/share/tree/tree.ts create mode 100644 Open-ILS/src/eg2/src/app/share/util/audio.service.ts create mode 100644 Open-ILS/src/eg2/src/app/share/util/pager.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/about.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/about.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq-splash.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq-splash.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/acq/routing.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/basic-admin-page.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/common.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/routing.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/server/admin-server-splash.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/server/admin-server-splash.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/server/admin-server.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/server/routing.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/workstation/routing.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/routing.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/workstations.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/workstations.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/workstations.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/catalog.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/catalog.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/catalog.service.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/record/actions.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/record/actions.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/record/copies.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/record/copies.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/record/pagination.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/record/pagination.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/resolver.service.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/result/facets.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/result/facets.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/result/pagination.component.css create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/result/pagination.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/result/pagination.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/routing.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.css create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/routing.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/circ/patron/routing.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/circ/routing.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/common.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/login.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/login.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/nav.component.css create mode 100644 Open-ILS/src/eg2/src/app/staff/nav.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/nav.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/resolver.service.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/routing.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/sandbox/README create mode 100644 Open-ILS/src/eg2/src/app/staff/sandbox/routing.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.module.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/share/README create mode 100644 Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/share/buckets/record-bucket-dialog.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/share/buckets/record-bucket-dialog.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/share/holdings.service.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/share/link-table/link-table.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/share/link-table/link-table.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/share/op-change/op-change.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/share/op-change/op-change.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/share/staff-banner.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/share/translate/translate.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/share/translate/translate.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/splash.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/splash.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/staff.component.css create mode 100644 Open-ILS/src/eg2/src/app/staff/staff.component.html create mode 100644 Open-ILS/src/eg2/src/app/staff/staff.component.ts create mode 100644 Open-ILS/src/eg2/src/app/staff/staff.module.ts create mode 100644 Open-ILS/src/eg2/src/app/welcome.component.html create mode 100644 Open-ILS/src/eg2/src/app/welcome.component.ts create mode 100644 Open-ILS/src/eg2/src/assets/.gitkeep create mode 100644 Open-ILS/src/eg2/src/environments/environment.prod.ts create mode 100644 Open-ILS/src/eg2/src/environments/environment.ts create mode 100644 Open-ILS/src/eg2/src/favicon.ico create mode 100644 Open-ILS/src/eg2/src/index.html create mode 100644 Open-ILS/src/eg2/src/locale/.gitkeep create mode 100644 Open-ILS/src/eg2/src/main.ts create mode 100644 Open-ILS/src/eg2/src/polyfills.ts create mode 100644 Open-ILS/src/eg2/src/styles.css create mode 100644 Open-ILS/src/eg2/src/test.ts create mode 100644 Open-ILS/src/eg2/src/test_data/eg_mock.js create mode 100644 Open-ILS/src/eg2/src/test_data/idl2js.pl create mode 100644 Open-ILS/src/eg2/src/tsconfig.app.json create mode 100644 Open-ILS/src/eg2/src/tsconfig.spec.json create mode 100644 Open-ILS/src/eg2/src/typings.d.ts create mode 100644 Open-ILS/src/eg2/tsconfig.json create mode 100644 Open-ILS/src/eg2/tslint.json diff --git a/.gitignore b/.gitignore index 62dea578ca..bfcf501cf7 100644 --- a/.gitignore +++ b/.gitignore @@ -360,3 +360,4 @@ Open-ILS/web/js/ui/default/staff/build/ 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/ diff --git a/Open-ILS/src/eg2/.editorconfig b/Open-ILS/src/eg2/.editorconfig new file mode 100644 index 0000000000..6e87a003da --- /dev/null +++ b/Open-ILS/src/eg2/.editorconfig @@ -0,0 +1,13 @@ +# 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 diff --git a/Open-ILS/src/eg2/.gitignore b/Open-ILS/src/eg2/.gitignore new file mode 100644 index 0000000000..f59404c847 --- /dev/null +++ b/Open-ILS/src/eg2/.gitignore @@ -0,0 +1,49 @@ +# 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 diff --git a/Open-ILS/src/eg2/CHEAT_SHEET.adoc b/Open-ILS/src/eg2/CHEAT_SHEET.adoc new file mode 100644 index 0000000000..84f4e5dd6c --- /dev/null +++ b/Open-ILS/src/eg2/CHEAT_SHEET.adoc @@ -0,0 +1,31 @@ += 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 +--------------------------------------------------------------------- + diff --git a/Open-ILS/src/eg2/angular.json b/Open-ILS/src/eg2/angular.json new file mode 100644 index 0000000000..e50c8db5f7 --- /dev/null +++ b/Open-ILS/src/eg2/angular.json @@ -0,0 +1,155 @@ +{ + "$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" + } + } +} diff --git a/Open-ILS/src/eg2/e2e/app.e2e-spec.ts b/Open-ILS/src/eg2/e2e/app.e2e-spec.ts new file mode 100644 index 0000000000..c2a69a8a6c --- /dev/null +++ b/Open-ILS/src/eg2/e2e/app.e2e-spec.ts @@ -0,0 +1,14 @@ +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!'); + }); +}); diff --git a/Open-ILS/src/eg2/e2e/app.po.ts b/Open-ILS/src/eg2/e2e/app.po.ts new file mode 100644 index 0000000000..82ea75ba50 --- /dev/null +++ b/Open-ILS/src/eg2/e2e/app.po.ts @@ -0,0 +1,11 @@ +import { browser, by, element } from 'protractor'; + +export class AppPage { + navigateTo() { + return browser.get('/'); + } + + getParagraphText() { + return element(by.css('app-root h1')).getText(); + } +} diff --git a/Open-ILS/src/eg2/e2e/tsconfig.e2e.json b/Open-ILS/src/eg2/e2e/tsconfig.e2e.json new file mode 100644 index 0000000000..1d9e5edf09 --- /dev/null +++ b/Open-ILS/src/eg2/e2e/tsconfig.e2e.json @@ -0,0 +1,14 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/e2e", + "baseUrl": "./", + "module": "commonjs", + "target": "es5", + "types": [ + "jasmine", + "jasminewd2", + "node" + ] + } +} diff --git a/Open-ILS/src/eg2/karma.conf.js b/Open-ILS/src/eg2/karma.conf.js new file mode 100644 index 0000000000..63982de94b --- /dev/null +++ b/Open-ILS/src/eg2/karma.conf.js @@ -0,0 +1,43 @@ +// 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', + ] + }); +}; diff --git a/Open-ILS/src/eg2/package-lock.json b/Open-ILS/src/eg2/package-lock.json new file mode 100644 index 0000000000..e7ce3aa8a2 --- /dev/null +++ b/Open-ILS/src/eg2/package-lock.json @@ -0,0 +1,10689 @@ +{ + "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==" + } + } +} diff --git a/Open-ILS/src/eg2/package.json b/Open-ILS/src/eg2/package.json new file mode 100644 index 0000000000..9174d1bd6a --- /dev/null +++ b/Open-ILS/src/eg2/package.json @@ -0,0 +1,84 @@ +{ + "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 + } +} diff --git a/Open-ILS/src/eg2/protractor.conf.js b/Open-ILS/src/eg2/protractor.conf.js new file mode 100644 index 0000000000..7ee3b5ee86 --- /dev/null +++ b/Open-ILS/src/eg2/protractor.conf.js @@ -0,0 +1,28 @@ +// 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 } })); + } +}; diff --git a/Open-ILS/src/eg2/src/app/app.component.ts b/Open-ILS/src/eg2/src/app/app.component.ts new file mode 100644 index 0000000000..3f95092c5a --- /dev/null +++ b/Open-ILS/src/eg2/src/app/app.component.ts @@ -0,0 +1,11 @@ +import {Component} from '@angular/core'; + +@Component({ + selector: 'eg-root', + template: '' +}) + +export class BaseComponent { +} + + diff --git a/Open-ILS/src/eg2/src/app/app.module.ts b/Open-ILS/src/eg2/src/app/app.module.ts new file mode 100644 index 0000000000..20de8ab017 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/app.module.ts @@ -0,0 +1,33 @@ +/** + * 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 {} + diff --git a/Open-ILS/src/eg2/src/app/common.module.ts b/Open-ILS/src/eg2/src/app/common.module.ts new file mode 100644 index 0000000000..c83ad392bc --- /dev/null +++ b/Open-ILS/src/eg2/src/app/common.module.ts @@ -0,0 +1,71 @@ +/** + * 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 + ] + }; + } +} + diff --git a/Open-ILS/src/eg2/src/app/core/README b/Open-ILS/src/eg2/src/app/core/README new file mode 100644 index 0000000000..3cf0ec4708 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/README @@ -0,0 +1,9 @@ +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. + diff --git a/Open-ILS/src/eg2/src/app/core/auth.service.ts b/Open-ILS/src/eg2/src/app/core/auth.service.ts new file mode 100644 index 0000000000..dad2acdb90 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/auth.service.ts @@ -0,0 +1,341 @@ +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 { + + 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 { + + 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 { + 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 { + + 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 { + + 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 { + 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 { + + 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(); + } +} diff --git a/Open-ILS/src/eg2/src/app/core/event.service.ts b/Open-ILS/src/eg2/src/app/core/event.service.ts new file mode 100644 index 0000000000..0bbf60bdb7 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/event.service.ts @@ -0,0 +1,55 @@ +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; + } +} + + diff --git a/Open-ILS/src/eg2/src/app/core/event.spec.ts b/Open-ILS/src/eg2/src/app/core/event.spec.ts new file mode 100644 index 0000000000..3dfdab2fbb --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/event.spec.ts @@ -0,0 +1,47 @@ +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'); + }); + +}); diff --git a/Open-ILS/src/eg2/src/app/core/format.service.ts b/Open-ILS/src/eg2/src/app/core/format.service.ts new file mode 100644 index 0000000000..2c7e3885d9 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/format.service.ts @@ -0,0 +1,103 @@ +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 + ''; + } + } +} + diff --git a/Open-ILS/src/eg2/src/app/core/format.spec.ts b/Open-ILS/src/eg2/src/app/core/format.spec.ts new file mode 100644 index 0000000000..05991df68f --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/format.spec.ts @@ -0,0 +1,90 @@ +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'); + }); + +}); + diff --git a/Open-ILS/src/eg2/src/app/core/idl.service.ts b/Open-ILS/src/eg2/src/app/core/idl.service.ts new file mode 100644 index 0000000000..89f8411de9 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/idl.service.ts @@ -0,0 +1,137 @@ +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; + } +} + diff --git a/Open-ILS/src/eg2/src/app/core/idl.spec.ts b/Open-ILS/src/eg2/src/app/core/idl.spec.ts new file mode 100644 index 0000000000..8138bf453c --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/idl.spec.ts @@ -0,0 +1,28 @@ +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'); + }); + +}); + diff --git a/Open-ILS/src/eg2/src/app/core/locale.service.ts b/Open-ILS/src/eg2/src/app/core/locale.service.ts new file mode 100644 index 0000000000..0ffbfd410f --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/locale.service.ts @@ -0,0 +1,69 @@ +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 { + 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] : ''; + } +} + + diff --git a/Open-ILS/src/eg2/src/app/core/net.service.ts b/Open-ILS/src/eg2/src/app/core/net.service.ts new file mode 100644 index 0000000000..3c3435b215 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/net.service.ts @@ -0,0 +1,187 @@ +/** + * + * 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; + 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; + authExpired$: EventEmitter; + + // 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(); + this.authExpired$ = new EventEmitter(); + } + + // Standard request call -- Variadic params version + request(service: string, method: string, ...params: any[]): Observable { + return this.requestWithParamList(service, method, params); + } + + // Array params version + requestWithParamList(service: string, + method: string, params: any[]): Observable { + return this.requestCompiled( + new NetRequest(service, method, params)); + } + + // Request with pre-compiled NetRequest + requestCompiled(request: NetRequest): Observable { + 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); + } +} diff --git a/Open-ILS/src/eg2/src/app/core/org.service.ts b/Open-ILS/src/eg2/src/app/core/org.service.ts new file mode 100644 index 0000000000..38faaffbaf --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/org.service.ts @@ -0,0 +1,278 @@ +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 { + 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 { + + 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 { + + 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; + }); + } +} diff --git a/Open-ILS/src/eg2/src/app/core/org.spec.ts b/Open-ILS/src/eg2/src/app/core/org.spec.ts new file mode 100644 index 0000000000..78c2f26657 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/org.spec.ts @@ -0,0 +1,66 @@ +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); + }); + +}); + + diff --git a/Open-ILS/src/eg2/src/app/core/pcrud.service.ts b/Open-ILS/src/eg2/src/app/core/pcrud.service.ts new file mode 100644 index 0000000000..76ee341653 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/pcrud.service.ts @@ -0,0 +1,305 @@ +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; + + 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 { + 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 { + 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 { + 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 { + 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 { + return this.cud('create', list); + } + update(list: IdlObject | IdlObject[]): Observable { + return this.cud('update', list); + } + remove(list: IdlObject | IdlObject[]): Observable { + return this.cud('delete', list); + } + autoApply(list: IdlObject | IdlObject[]): Observable { // RENAMED + return this.cud('auto', list); + } + + xactClose(): Observable { + return this.sendRequest( + 'open-ils.pcrud.transaction.' + this.xactCloseMode, + [this.token()] + ); + } + + xactBegin(): Observable { + return this.sendRequest( + 'open-ils.pcrud.transaction.begin', [this.token()] + ); + } + + private dispatch(method: string, params: any[]): Observable { + 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): Observable { + 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 { + + // 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 { + 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 { + 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 { + return this.newContext().retrieve(fmClass, pkey, pcrudOps, reqOps); + } + + retrieveAll(fmClass: string, pcrudOps?: any, + reqOps?: PcrudReqOps): Observable { + return this.newContext().retrieveAll(fmClass, pcrudOps, reqOps); + } + + search(fmClass: string, search: any, + pcrudOps?: any, reqOps?: PcrudReqOps): Observable { + return this.newContext().search(fmClass, search, pcrudOps, reqOps); + } + + create(list: IdlObject | IdlObject[]): Observable { + return this.newContext().create(list); + } + + update(list: IdlObject | IdlObject[]): Observable { + return this.newContext().update(list); + } + + remove(list: IdlObject | IdlObject[]): Observable { + return this.newContext().remove(list); + } + + autoApply(list: IdlObject | IdlObject[]): Observable { + return this.newContext().autoApply(list); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/core/perm.service.ts b/Open-ILS/src/eg2/src/app/core/perm.service.ts new file mode 100644 index 0000000000..44d3c635fb --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/perm.service.ts @@ -0,0 +1,59 @@ +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 { + 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 { + 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; + }); + } +} diff --git a/Open-ILS/src/eg2/src/app/core/server-store.service.ts b/Open-ILS/src/eg2/src/app/core/server-store.service.ts new file mode 100644 index 0000000000..43415c1951 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/server-store.service.ts @@ -0,0 +1,114 @@ +/** + * 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 { + + 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 { + return this.getItemBatch([key]).then( + settings => settings[key] + ); + } + + // Returns a set of key/value pairs for the requested settings + getItemBatch(keys: string[]): Promise { + + 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 { + return this.setItem(key, null); + } +} + diff --git a/Open-ILS/src/eg2/src/app/core/store.service.ts b/Open-ILS/src/eg2/src/app/core/store.service.ts new file mode 100644 index 0000000000..46dd6214fe --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/store.service.ts @@ -0,0 +1,107 @@ +/** + * 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) + ); + } +} + diff --git a/Open-ILS/src/eg2/src/app/core/store.spec.ts b/Open-ILS/src/eg2/src/app/core/store.spec.ts new file mode 100644 index 0000000000..ae6c27fbf5 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/store.spec.ts @@ -0,0 +1,22 @@ +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); + }); + +}); + diff --git a/Open-ILS/src/eg2/src/app/resolver.service.ts b/Open-ILS/src/eg2/src/app/resolver.service.ts new file mode 100644 index 0000000000..faa6038973 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/resolver.service.ts @@ -0,0 +1,36 @@ +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> { + + 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 { + + OpenSRF.locale = this.locale.currentLocaleCode(); + + this.idl.parseIdl(); + + return this.org.fetchOrgs(); // anonymous PCRUD. + } +} diff --git a/Open-ILS/src/eg2/src/app/routing.module.ts b/Open-ILS/src/eg2/src/app/routing.module.ts new file mode 100644 index 0000000000..db3ee19dce --- /dev/null +++ b/Open-ILS/src/eg2/src/app/routing.module.ts @@ -0,0 +1,29 @@ +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 {} diff --git a/Open-ILS/src/eg2/src/app/share/README b/Open-ILS/src/eg2/src/app/share/README new file mode 100644 index 0000000000..f428f79e42 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/README @@ -0,0 +1,6 @@ +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. + diff --git a/Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.html b/Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.html new file mode 100644 index 0000000000..82ed72a4b0 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.html @@ -0,0 +1,26 @@ + + + + + diff --git a/Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.ts b/Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.ts new file mode 100644 index 0000000000..d713ee60b6 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/accesskey/accesskey-info.component.ts @@ -0,0 +1,25 @@ +/** + */ +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(); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/accesskey/accesskey.directive.ts b/Open-ILS/src/eg2/src/app/share/accesskey/accesskey.directive.ts new file mode 100644 index 0000000000..dfc835d9bd --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/accesskey/accesskey.directive.ts @@ -0,0 +1,56 @@ +/** + * Assign access keys to tags. + * + * Access key action is peformed via .click(). hrefs, routerLinks, + * and (click) actions are all supported. + * + * + */ +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() + }); + }); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/accesskey/accesskey.service.ts b/Open-ILS/src/eg2/src/app/share/accesskey/accesskey.service.ts new file mode 100644 index 0000000000..51dda57940 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/accesskey/accesskey.service.ts @@ -0,0 +1,67 @@ +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}; + }); + } + +} + diff --git a/Open-ILS/src/eg2/src/app/share/catalog/bib-record.service.ts b/Open-ILS/src/eg2/src/app/share/catalog/bib-record.service.ts new file mode 100644 index 0000000000..19924d9306 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/catalog/bib-record.service.ts @@ -0,0 +1,249 @@ +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 { + + 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 { + + 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 { + + 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 { + + 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 { + + 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; + }); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/catalog/catalog-common.module.ts b/Open-ILS/src/eg2/src/app/share/catalog/catalog-common.module.ts new file mode 100644 index 0000000000..c370b300c9 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/catalog/catalog-common.module.ts @@ -0,0 +1,28 @@ +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 {} diff --git a/Open-ILS/src/eg2/src/app/share/catalog/catalog-url.service.ts b/Open-ILS/src/eg2/src/app/share/catalog/catalog-url.service.ts new file mode 100644 index 0000000000..253e3aacdd --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/catalog/catalog-url.service.ts @@ -0,0 +1,143 @@ +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')); + } + } +} diff --git a/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts b/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts new file mode 100644 index 0000000000..95967cb606 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts @@ -0,0 +1,210 @@ +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 { + 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 { + + 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 { + + 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 { + + 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 { + // 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(); + } + ); + }); + } +} diff --git a/Open-ILS/src/eg2/src/app/share/catalog/marc-html.component.ts b/Open-ILS/src/eg2/src/app/share/catalog/marc-html.component.ts new file mode 100644 index 0000000000..38b1da779b --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/catalog/marc-html.component.ts @@ -0,0 +1,90 @@ +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: '' +}) +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( + / + + + diff --git a/Open-ILS/src/eg2/src/app/share/date-select/date-select.component.ts b/Open-ILS/src/eg2/src/app/share/date-select/date-select.component.ts new file mode 100644 index 0000000000..ae3a729a3c --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/date-select/date-select.component.ts @@ -0,0 +1,70 @@ +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; + @Output() onChangeAsIso: EventEmitter; + @Output() onChangeAsYmd: EventEmitter; + + constructor() { + this.onChangeAsDate = new EventEmitter(); + this.onChangeAsIso = new EventEmitter(); + this.onChangeAsYmd = new EventEmitter(); + } + + 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])); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/dialog/confirm.component.html b/Open-ILS/src/eg2/src/app/share/dialog/confirm.component.html new file mode 100644 index 0000000000..21766cac09 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/dialog/confirm.component.html @@ -0,0 +1,17 @@ + + + + + diff --git a/Open-ILS/src/eg2/src/app/share/dialog/confirm.component.ts b/Open-ILS/src/eg2/src/app/share/dialog/confirm.component.ts new file mode 100644 index 0000000000..efcbdeb54e --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/dialog/confirm.component.ts @@ -0,0 +1,17 @@ +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; +} + + diff --git a/Open-ILS/src/eg2/src/app/share/dialog/dialog.component.ts b/Open-ILS/src/eg2/src/app/share/dialog/dialog.component.ts new file mode 100644 index 0000000000..3ffd5db069 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/dialog/dialog.component.ts @@ -0,0 +1,80 @@ +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: '' +}) +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; + + // 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(); + + // The modalRef allows direct control of the modal instance. + private modalRef: NgbModalRef = null; + + constructor(private modalService: NgbModal) {} + + ngOnInit() { + this.onOpen$ = new EventEmitter(); + } + + open(options?: NgbModalOptions): Promise { + + 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); + } + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/dialog/progress-inline.component.css b/Open-ILS/src/eg2/src/app/share/dialog/progress-inline.component.css new file mode 100644 index 0000000000..fa08a1f4b4 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/dialog/progress-inline.component.css @@ -0,0 +1,5 @@ + +.eg-progress-inline progress { + width: 100%; + height: 25px; +} diff --git a/Open-ILS/src/eg2/src/app/share/dialog/progress-inline.component.html b/Open-ILS/src/eg2/src/app/share/dialog/progress-inline.component.html new file mode 100644 index 0000000000..615e867f87 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/dialog/progress-inline.component.html @@ -0,0 +1,28 @@ +
+ +
+ +
+
+ +
+
{{percent()}}%
+
+
+ +
+
+ +
+
{{value}}...
+
+
+ +
+
+ +
+
+
+ +
diff --git a/Open-ILS/src/eg2/src/app/share/dialog/progress-inline.component.ts b/Open-ILS/src/eg2/src/app/share/dialog/progress-inline.component.ts new file mode 100644 index 0000000000..9b131d13d5 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/dialog/progress-inline.component.ts @@ -0,0 +1,92 @@ +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 + * , 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 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; + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/dialog/progress.component.css b/Open-ILS/src/eg2/src/app/share/dialog/progress.component.css new file mode 100644 index 0000000000..a79609e08a --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/dialog/progress.component.css @@ -0,0 +1,5 @@ + +.eg-progress-dialog progress { + width: 100%; + height: 25px; +} diff --git a/Open-ILS/src/eg2/src/app/share/dialog/progress.component.html b/Open-ILS/src/eg2/src/app/share/dialog/progress.component.html new file mode 100644 index 0000000000..78ca3d0c95 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/dialog/progress.component.html @@ -0,0 +1,33 @@ + + + + + diff --git a/Open-ILS/src/eg2/src/app/share/dialog/progress.component.ts b/Open-ILS/src/eg2/src/app/share/dialog/progress.component.ts new file mode 100644 index 0000000000..6bf4edbe3e --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/dialog/progress.component.ts @@ -0,0 +1,108 @@ +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 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 + * , 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 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; + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/dialog/prompt.component.html b/Open-ILS/src/eg2/src/app/share/dialog/prompt.component.html new file mode 100644 index 0000000000..1d7936b176 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/dialog/prompt.component.html @@ -0,0 +1,22 @@ + + + + + diff --git a/Open-ILS/src/eg2/src/app/share/dialog/prompt.component.ts b/Open-ILS/src/eg2/src/app/share/dialog/prompt.component.ts new file mode 100644 index 0000000000..ab7f77ea0c --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/dialog/prompt.component.ts @@ -0,0 +1,19 @@ +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; +} + + diff --git a/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html b/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html new file mode 100644 index 0000000000..721423c05c --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.html @@ -0,0 +1,146 @@ + + + + + diff --git a/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts b/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts new file mode 100644 index 0000000000..308218a96c --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts @@ -0,0 +1,302 @@ +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; + + // 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(); + + // Emit the original object when the save action is canceled. + @Output() onCancel$ = new EventEmitter(); + + // Emit an error message when the save action fails. + @Output() onError$ = new EventEmitter(); + + // 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 { + 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 { + + 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 { + + 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'); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-body-cell.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid-body-cell.component.html new file mode 100644 index 0000000000..3de90e41aa --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-body-cell.component.html @@ -0,0 +1,20 @@ + + + {{context.getRowColumnValue(row, column)}} + + + + + + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-body-cell.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-body-cell.component.ts new file mode 100644 index 0000000000..3d844f380f --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-body-cell.component.ts @@ -0,0 +1,57 @@ +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; + + 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 = ''; + } + } +} + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-body.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid-body.component.html new file mode 100644 index 0000000000..b7284fe6f0 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-body.component.html @@ -0,0 +1,39 @@ + +
+
+ +
+ +
+
+ {{context.pager.rowNumber(idx)}} +
+
+ + + + + + {{flair.icon}} + + + +
+
+ + + +
+
+
+ diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-body.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-body.component.ts new file mode 100644 index 0000000000..e4829cee01 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-body.component.ts @@ -0,0 +1,77 @@ +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); + } + +} + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-column-config.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid-column-config.component.html new file mode 100644 index 0000000000..3af756c9b9 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-column-config.component.html @@ -0,0 +1,69 @@ + + +
+ + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-column-config.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-column-config.component.ts new file mode 100644 index 0000000000..10ad606f2b --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-column-config.component.ts @@ -0,0 +1,16 @@ +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; +} + + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-column-width.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid-column-width.component.html new file mode 100644 index 0000000000..ca24c00afd --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-column-width.component.html @@ -0,0 +1,20 @@ +
+
+
Expand
+ +
+
+
Shrink
+ +
+
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-column-width.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-column-width.component.ts new file mode 100644 index 0000000000..f9bacf4dad --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-column-width.component.ts @@ -0,0 +1,32 @@ +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--; } + } + +} + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-column.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-column.component.ts new file mode 100644 index 0000000000..dffede14b7 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-column.component.ts @@ -0,0 +1,57 @@ +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: '' +}) + +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; + + // get a reference to our container grid. + constructor(@Host() private grid: GridComponent) {} + + ngOnInit() { + + if (!this.grid) { + console.warn('GridColumnComponent needs an '); + 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); + } +} + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-header.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid-header.component.html new file mode 100644 index 0000000000..58e0c66774 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-header.component.html @@ -0,0 +1,32 @@ + +
+
+ +
+
+ # +
+
+ notifications +
+ +
+ diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-header.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-header.component.ts new file mode 100644 index 0000000000..0010a45f38 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-header.component.ts @@ -0,0 +1,85 @@ +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); + } +} + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-print.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid-print.component.html new file mode 100644 index 0000000000..a098792207 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-print.component.html @@ -0,0 +1,30 @@ + + + + + +
+ + + + + + + + + + + +
{{col.label}}
{{row[col.name]}}
+
+
+
diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-print.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-print.component.ts new file mode 100644 index 0000000000..f73e26b460 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-print.component.ts @@ -0,0 +1,45 @@ +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; + @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' + }); + } + ); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar-action.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar-action.component.ts new file mode 100644 index 0000000000..593530a7f9 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar-action.component.ts @@ -0,0 +1,33 @@ +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: '' +}) + +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); + } +} + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar-button.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar-button.component.ts new file mode 100644 index 0000000000..8287483863 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar-button.component.ts @@ -0,0 +1,43 @@ +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: '' +}) + +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); + } +} + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar-checkbox.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar-checkbox.component.ts new file mode 100644 index 0000000000..f078797525 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar-checkbox.component.ts @@ -0,0 +1,37 @@ +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: '' +}) + +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); + } +} + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html new file mode 100644 index 0000000000..ae24021381 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html @@ -0,0 +1,152 @@ + +
+ +
+ + +
+ +
+ + +
+ + + +
+
+ + +
+ +
+ + +
+ + + + + + + +
+ + +
+ + + + + + + +
+ + + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.ts new file mode 100644 index 0000000000..5c8b523498 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.ts @@ -0,0 +1,86 @@ +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(); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.component.css b/Open-ILS/src/eg2/src/app/share/grid/grid.component.css new file mode 100644 index 0000000000..9748c0c4b1 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid.component.css @@ -0,0 +1,142 @@ + +.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; +} + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid.component.html new file mode 100644 index 0000000000..a98e17afaf --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid.component.html @@ -0,0 +1,27 @@ + +
+ + + + + + + + + + + + + +
+
+ Nothing to Display +
+
+ + +
+ diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts new file mode 100644 index 0000000000..1fa4c2cfee --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts @@ -0,0 +1,149 @@ +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; + @Output() onRowClick: EventEmitter; + + 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(); + this.onRowClick = new EventEmitter(); + } + + ngOnInit() { + + if (!this.dataSource) { + throw new Error(' 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(); + } +} + + + diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.module.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.module.ts new file mode 100644 index 0000000000..0773a7e56f --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid.module.ts @@ -0,0 +1,50 @@ +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 { + +} diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.ts new file mode 100644 index 0000000000..e04940dd1a --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/grid/grid.ts @@ -0,0 +1,972 @@ +/** + * 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; + 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 { + 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 { + 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 { + 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 { + 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 { + return Observable.create(observer => { + this.getAllRows().then(ok => { + this.dataSource.data.forEach(row => { + observer.next(this.getRowAsFlatText(row)); + }); + observer.complete(); + }); + }); + } + + gridToCsv(): Promise { + + 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 { + 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 { + 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; + + 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 { + + 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; + } + } + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/org-select/org-select.component.html b/Open-ILS/src/eg2/src/app/share/org-select/org-select.component.html new file mode 100644 index 0000000000..2a4bd3a0c2 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/org-select/org-select.component.html @@ -0,0 +1,17 @@ + + + +{{r.label}} + + + diff --git a/Open-ILS/src/eg2/src/app/share/org-select/org-select.component.ts b/Open-ILS/src/eg2/src/app/share/org-select/org-select.component.ts new file mode 100644 index 0000000000..39e0cff2ea --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/org-select/org-select.component.ts @@ -0,0 +1,212 @@ +/** TODO PORT ME TO */ +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(); + 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(); + + 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): Observable => { + 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)); + }) + ); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/print/print.component.html b/Open-ILS/src/eg2/src/app/share/print/print.component.html new file mode 100644 index 0000000000..12d05bcbcf --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/print/print.component.html @@ -0,0 +1,16 @@ + + +
+ + + + + +
+
+ +
+ diff --git a/Open-ILS/src/eg2/src/app/share/print/print.component.ts b/Open-ILS/src/eg2/src/app/share/print/print.component.ts new file mode 100644 index 0000000000..4f6994982b --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/print/print.component.ts @@ -0,0 +1,133 @@ +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; + + // 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 = `
${printReq.text}
`; + } + + 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 = `${printReq.text}`; + + /* + this.hatch.print({ + printContext: printReq.printContext, + content: html + }); + */ + } +} + diff --git a/Open-ILS/src/eg2/src/app/share/print/print.service.ts b/Open-ILS/src/eg2/src/app/share/print/print.service.ts new file mode 100644 index 0000000000..5ae6844dfd --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/print/print.service.ts @@ -0,0 +1,41 @@ +import {Injectable, EventEmitter, TemplateRef} from '@angular/core'; +import {StoreService} from '@eg/core/store.service'; + +export interface PrintRequest { + template?: TemplateRef; + contextData?: any; + text?: string; + printContext: string; + contentType?: string; // defaults to text/html + showDialog?: boolean; +} + +@Injectable() +export class PrintService { + + onPrintRequest$: EventEmitter; + + constructor(private store: StoreService) { + this.onPrintRequest$ = new EventEmitter(); + } + + 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); + } + } +} + diff --git a/Open-ILS/src/eg2/src/app/share/string/string.component.ts b/Open-ILS/src/eg2/src/app/share/string/string.component.ts new file mode 100644 index 0000000000..f092a7ef5f --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/string/string.component.ts @@ -0,0 +1,74 @@ +/*j + * + * + * 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: ` + + + + ` +}) + +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; + + // 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 { + if (ctx) { this.ctx = ctx; } + return new Promise(resolve => { + setTimeout(() => resolve(this.elm.nativeElement.textContent)); + }); + } +} + diff --git a/Open-ILS/src/eg2/src/app/share/string/string.service.ts b/Open-ILS/src/eg2/src/app/share/string/string.service.ts new file mode 100644 index 0000000000..88d0c8ae2d --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/string/string.service.ts @@ -0,0 +1,78 @@ +import {Injectable} from '@angular/core'; + +interface StringAssignment { + key: string; // keyboard command + resolver: (ctx: any) => Promise; +} + +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 { + + 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(); + } + } + ); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/toast/toast.component.css b/Open-ILS/src/eg2/src/app/share/toast/toast.component.css new file mode 100644 index 0000000000..1f70349f5c --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/toast/toast.component.css @@ -0,0 +1,11 @@ +#eg-toast-container { + min-width: 250px; + text-align: center; + border-radius: 2px; + padding: 10px; + position: fixed; + z-index: 1; + right: 15px; + bottom: 5px; +} + diff --git a/Open-ILS/src/eg2/src/app/share/toast/toast.component.html b/Open-ILS/src/eg2/src/app/share/toast/toast.component.html new file mode 100644 index 0000000000..6aa1545dc6 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/toast/toast.component.html @@ -0,0 +1,3 @@ +
+ {{message.text}} +
diff --git a/Open-ILS/src/eg2/src/app/share/toast/toast.component.ts b/Open-ILS/src/eg2/src/app/share/toast/toast.component.ts new file mode 100644 index 0000000000..9503ffd824 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/toast/toast.component.ts @@ -0,0 +1,43 @@ +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; + } + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/toast/toast.service.ts b/Open-ILS/src/eg2/src/app/share/toast/toast.service.ts new file mode 100644 index 0000000000..58065922ba --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/toast/toast.service.ts @@ -0,0 +1,39 @@ +import {Injectable, EventEmitter} from '@angular/core'; + +export interface ToastMessage { + text: string; + style: string; +} + +@Injectable() +export class ToastService { + + messages$: EventEmitter; + + constructor() { + this.messages$ = new EventEmitter(); + } + + 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? +} + diff --git a/Open-ILS/src/eg2/src/app/share/tree/tree.component.css b/Open-ILS/src/eg2/src/app/share/tree/tree.component.css new file mode 100644 index 0000000000..0d29dd73a4 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/tree/tree.component.css @@ -0,0 +1,19 @@ + +.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); +} + diff --git a/Open-ILS/src/eg2/src/app/share/tree/tree.component.html b/Open-ILS/src/eg2/src/app/share/tree/tree.component.html new file mode 100644 index 0000000000..525fece16a --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/tree/tree.component.html @@ -0,0 +1,20 @@ + + +
+
+
+
+ expand_more + expand_less +
+
+   +
+
+ +
+
diff --git a/Open-ILS/src/eg2/src/app/share/tree/tree.component.ts b/Open-ILS/src/eg2/src/app/share/tree/tree.component.ts new file mode 100644 index 0000000000..d3fccdae8f --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/tree/tree.component.ts @@ -0,0 +1,60 @@ +import {Component, OnInit, Input, Output, EventEmitter} from '@angular/core'; +import {Tree, TreeNode} from './tree'; + +/* +Tree Widget: + + + + +---- + +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; + + constructor() { + this.nodeClicked = new EventEmitter(); + } + + ngOnInit() {} + + displayNodes(): TreeNode[] { + return this.tree.nodeList(true); + } + + handleNodeClick(node: TreeNode) { + this.tree.selectNode(node); + this.nodeClicked.emit(node); + } +} + + + diff --git a/Open-ILS/src/eg2/src/app/share/tree/tree.module.ts b/Open-ILS/src/eg2/src/app/share/tree/tree.module.ts new file mode 100644 index 0000000000..3894fcdd25 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/tree/tree.module.ts @@ -0,0 +1,20 @@ +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 {} + diff --git a/Open-ILS/src/eg2/src/app/share/tree/tree.ts b/Open-ILS/src/eg2/src/app/share/tree/tree.ts new file mode 100644 index 0000000000..cca36d4a70 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/tree/tree.ts @@ -0,0 +1,133 @@ + +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; + } +} + diff --git a/Open-ILS/src/eg2/src/app/share/util/audio.service.ts b/Open-ILS/src/eg2/src/app/share/util/audio.service.ts new file mode 100644 index 0000000000..3f3320aa34 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/util/audio.service.ts @@ -0,0 +1,78 @@ +/** + * 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); + }; + }); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/util/pager.ts b/Open-ILS/src/eg2/src/app/share/util/pager.ts new file mode 100644 index 0000000000..267d4fcf55 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/util/pager.ts @@ -0,0 +1,111 @@ +import {EventEmitter} from '@angular/core'; + +/** + * Utility class for manage paged information. + */ +export class Pager { + offset = 0; + limit: number = null; + resultCount: number; + onChange$: EventEmitter; + + constructor() { + this.resultCount = null; + this.onChange$ = new EventEmitter(); + } + + 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; + } +} diff --git a/Open-ILS/src/eg2/src/app/staff/about.component.html b/Open-ILS/src/eg2/src/app/staff/about.component.html new file mode 100644 index 0000000000..9e8e1c41f4 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/about.component.html @@ -0,0 +1,57 @@ + + + +
+
+
+
Server Details
+
    +
  • +
    +
    Evergreen Version
    +
    {{version}}
    +
    +
  • +
  • +
    +
    Hostname
    +
    {{server}}
    +
    +
  • +
+
+
+
+
+
+

What is Evergreen?

+

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. +

+

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. +

+

+ More information can be found at + https://evergreen-ils.org. + For help in using Evergreen, see our documentation at + http://docs.evergreen-ils.org. +

+

+ Evergreen is Copyright © Georgia Public Library Service - + A Unit of the University System of Georgia, and others. The + Evergreen software is distributed under the + + GNU General Public License, Version 2. + +

+
+
+ + + + diff --git a/Open-ILS/src/eg2/src/app/staff/about.component.ts b/Open-ILS/src/eg2/src/app/staff/about.component.ts new file mode 100644 index 0000000000..a4949567fe --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/about.component.ts @@ -0,0 +1,25 @@ +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); + } +} + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq-splash.component.html b/Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq-splash.component.html new file mode 100644 index 0000000000..58fcbf6aa5 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq-splash.component.html @@ -0,0 +1,60 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq-splash.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq-splash.component.ts new file mode 100644 index 0000000000..dc47db92ec --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq-splash.component.ts @@ -0,0 +1,11 @@ +import {Component, Input, ViewChildren, + AfterViewInit, QueryList} from '@angular/core'; + +@Component({ + templateUrl: './admin-acq-splash.component.html' +}) + +export class AdminAcqSplashComponent { +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq.module.ts new file mode 100644 index 0000000000..5c57b3da0c --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/admin-acq.module.ts @@ -0,0 +1,24 @@ +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 { +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/acq/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/acq/routing.module.ts new file mode 100644 index 0000000000..c07dd5df56 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/acq/routing.module.ts @@ -0,0 +1,22 @@ +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 {} diff --git a/Open-ILS/src/eg2/src/app/staff/admin/basic-admin-page.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/basic-admin-page.component.ts new file mode 100644 index 0000000000..0d6be84dbd --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/basic-admin-page.component.ts @@ -0,0 +1,61 @@ +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: ` + + + + ` +}) + +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); + } + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/common.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/common.module.ts new file mode 100644 index 0000000000..5bd71d3efb --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/common.module.ts @@ -0,0 +1,28 @@ +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 { +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/routing.module.ts new file mode 100644 index 0000000000..a93f9ee37c --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/routing.module.ts @@ -0,0 +1,23 @@ +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 {} diff --git a/Open-ILS/src/eg2/src/app/staff/admin/server/admin-server-splash.component.html b/Open-ILS/src/eg2/src/app/staff/admin/server/admin-server-splash.component.html new file mode 100644 index 0000000000..5e6058da9c --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/server/admin-server-splash.component.html @@ -0,0 +1,99 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/Open-ILS/src/eg2/src/app/staff/admin/server/admin-server-splash.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/server/admin-server-splash.component.ts new file mode 100644 index 0000000000..9debf571d7 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/server/admin-server-splash.component.ts @@ -0,0 +1,11 @@ +import {Component, Input, ViewChildren, + AfterViewInit, QueryList} from '@angular/core'; + +@Component({ + templateUrl: './admin-server-splash.component.html' +}) + +export class AdminServerSplashComponent { +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/server/admin-server.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/server/admin-server.module.ts new file mode 100644 index 0000000000..1f00a8a0af --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/server/admin-server.module.ts @@ -0,0 +1,24 @@ +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 { +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/server/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/server/routing.module.ts new file mode 100644 index 0000000000..ceb60f27f4 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/server/routing.module.ts @@ -0,0 +1,19 @@ +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 {} diff --git a/Open-ILS/src/eg2/src/app/staff/admin/workstation/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/workstation/routing.module.ts new file mode 100644 index 0000000000..acdb9a1752 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/workstation/routing.module.ts @@ -0,0 +1,14 @@ +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 {} diff --git a/Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/routing.module.ts new file mode 100644 index 0000000000..cebf138812 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/routing.module.ts @@ -0,0 +1,25 @@ +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 { +} + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/workstations.component.html b/Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/workstations.component.html new file mode 100644 index 0000000000..a2358d25bf --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/workstations.component.html @@ -0,0 +1,92 @@ + + + + + + + +
+
+
+ Workstation {{removeWorkstation}} is no longer valid. Removing registration. +
+
+ Please register a workstation. +
+ +
+
Register a New Workstation For This Browser
+
+
+
+ + +
+
+
+ +
+ +
+
+
+
+
+
+ Workstations Registered With This Browser +
+
+
+
+ +
+
+
+
+ + + +
+
+
+
+ diff --git a/Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/workstations.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/workstations.component.ts new file mode 100644 index 0000000000..a5c72e2dff --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/workstations.component.ts @@ -0,0 +1,186 @@ +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 { + 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 { + 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(); + } + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/workstations.module.ts b/Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/workstations.module.ts new file mode 100644 index 0000000000..cbd8dd6128 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/workstations.module.ts @@ -0,0 +1,18 @@ +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 {} + + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/catalog.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.component.html new file mode 100644 index 0000000000..1596454ac1 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.component.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/catalog.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.component.ts new file mode 100644 index 0000000000..8b2206c2f5 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.component.ts @@ -0,0 +1,18 @@ +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(); + } +} + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts new file mode 100644 index 0000000000..20e17a091c --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts @@ -0,0 +1,44 @@ +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 { + +} diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/catalog.service.ts b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.service.ts new file mode 100644 index 0000000000..1e50d9ba88 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.service.ts @@ -0,0 +1,87 @@ +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}); + } + +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/actions.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/record/actions.component.html new file mode 100644 index 0000000000..6fd945414c --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/actions.component.html @@ -0,0 +1,70 @@ + + + + + + + + + + +
+ + + +
+ +
+ + + + + +
+
+ +
+ +
+ + + View/Place Orders + +
+
+
+ diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/actions.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/record/actions.component.ts new file mode 100644 index 0000000000..b65bfae3a9 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/actions.component.ts @@ -0,0 +1,96 @@ +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); + } + +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/copies.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/record/copies.component.html new file mode 100644 index 0000000000..e7d82491b5 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/copies.component.html @@ -0,0 +1,53 @@ + + {{copy.call_number_prefix_label}} + {{copy.call_number_label}} + {{copy.call_number_suffix_label}} + + + +
{{copy.barcode}}
+
+ View + | + Edit +
+
+ + + Yes + No + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/copies.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/record/copies.component.ts new file mode 100644 index 0000000000..68908ece80 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/copies.component.ts @@ -0,0 +1,91 @@ +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 { + 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; + })); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/pagination.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/record/pagination.component.html new file mode 100644 index 0000000000..0edcded4cc --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/pagination.component.html @@ -0,0 +1,36 @@ + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/pagination.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/record/pagination.component.ts new file mode 100644 index 0000000000..793767b38f --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/pagination.component.ts @@ -0,0 +1,164 @@ +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 { + 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 { + + // 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 { + + 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(); + } + +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.html new file mode 100644 index 0000000000..4c74316c4e --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.html @@ -0,0 +1,37 @@ + +
+
+
+
+ + +
+
+ +
+
+ + +
+
+
+ + +
+
+ + + + + + + + + + + + +
+
+ + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.ts new file mode 100644 index 0000000000..b217e5c9b6 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/record/record.component.ts @@ -0,0 +1,84 @@ +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]); + }); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/resolver.service.ts b/Open-ILS/src/eg2/src/app/staff/catalog/resolver.service.ts new file mode 100644 index 0000000000..729beea0b0 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/resolver.service.ts @@ -0,0 +1,59 @@ +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> { + + 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 { + + console.debug('CatalogResolver:resolve()'); + + return Promise.all([ + this.cat.fetchCcvms(), + this.cat.fetchCmfs(), + this.fetchSettings() + ]); + } + + fetchSettings(): Promise { + 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); + } + +} + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/facets.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/result/facets.component.html new file mode 100644 index 0000000000..9681747043 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/facets.component.html @@ -0,0 +1,43 @@ + +
+
+
+
+
+
+

+ {{searchContext.result.facetData[facetConf.facetClass][name].cmfLabel}} +

+ +
+
+
+
+
+
diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/facets.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/result/facets.component.ts new file mode 100644 index 0000000000..44583b8780 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/facets.component.ts @@ -0,0 +1,48 @@ +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(); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/pagination.component.css b/Open-ILS/src/eg2/src/app/staff/catalog/result/pagination.component.css new file mode 100644 index 0000000000..c283ff45d5 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/pagination.component.css @@ -0,0 +1,8 @@ + +/* Bootstrap default is 20px */ +.pagination {margin: 0px 0px 0px 0px} + +.pagination li:not(.active) a { + cursor: pointer; +} + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/pagination.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/result/pagination.component.html new file mode 100644 index 0000000000..7cdda006e2 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/pagination.component.html @@ -0,0 +1,28 @@ + + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/pagination.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/result/pagination.component.ts new file mode 100644 index 0000000000..15214b52c7 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/pagination.component.ts @@ -0,0 +1,51 @@ +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(); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html new file mode 100644 index 0000000000..54ad3db0ee --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html @@ -0,0 +1,132 @@ + + + + +
+
+
+
+ + + +
+
+
+
+ + + #{{index + 1 + searchContext.pager.offset}} + + + {{summary.display.title || ' '}} + +
+
+ +
+
+ + + + {{iconFormatLabel(summary.attributes.icon_format[0])}} + + {{summary.display.edition}} + {{summary.display.pubdate}} +
+
+
+
+
+
+
+ + {{copyCount.available}} / {{copyCount.count}} items + +
+
+ @ {{orgName(copyCount.org_unit)}} +
+
+
+
+
+
+
+ TCN: {{summary.record.tcn_value()}} +
+
+
+
+ Holds: {{summary.holdCount}} +
+
+
+
+
+
+
+ Created {{summary.record.create_date() | date:'shortDate'}} by + + + {{summary.record.creator().usrname()}} + + + ... +
+
+
+
+
+
+ Edited {{summary.record.edit_date() | date:'shortDate'}} by + + {{summary.record.editor().usrname()}} + + ... +
+
+
+
+
+
+ + + + + + +
+
+
+
+
+
+
+ diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts new file mode 100644 index 0000000000..bfcfd4572e --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.ts @@ -0,0 +1,77 @@ +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}); + } + +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.html new file mode 100644 index 0000000000..ee9ca8ddf2 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.html @@ -0,0 +1,30 @@ + +
+
+
+

Search Results ({{searchContext.result.count}})

+
+
+
+
+ +
+
+
+
+
+ +
+
+
+
+
+ + +
+
+
+
+
+
+ diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts new file mode 100644 index 0000000000..d9b7062e14 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/results.component.ts @@ -0,0 +1,84 @@ +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; + } + +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/catalog/routing.module.ts new file mode 100644 index 0000000000..0e3c96fd00 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/routing.module.ts @@ -0,0 +1,30 @@ +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 {} diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.css b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.css new file mode 100644 index 0000000000..6201dff923 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.css @@ -0,0 +1,16 @@ + +/* 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); +} diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.html new file mode 100644 index 0000000000..da54f4a9b9 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.html @@ -0,0 +1,244 @@ + +
+
+
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+ + +
+
+
+
+ + + + +
+
+
+ +
+
+
+ + +
+
+ +
+
+
+ +
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+ Searching.. +
+
+
+
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+ +
+
+ Copy location filter goes here... +
+
+
+ diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts new file mode 100644 index 0000000000..52a26f2b2b --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts @@ -0,0 +1,137 @@ +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; + } + +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.component.html b/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.component.html new file mode 100644 index 0000000000..e83cf9e9d8 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.component.html @@ -0,0 +1,19 @@ + + + + +
+
+
+ Barcode: +
+ +
+ +
+
+
+ + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.component.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.component.ts new file mode 100644 index 0000000000..dac5048eb7 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.component.ts @@ -0,0 +1,36 @@ +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); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.module.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.module.ts new file mode 100644 index 0000000000..d1b16df384 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/bcsearch.module.ts @@ -0,0 +1,17 @@ +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 {} + diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/routing.module.ts new file mode 100644 index 0000000000..ce6783d4fd --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/bcsearch/routing.module.ts @@ -0,0 +1,19 @@ +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 {} diff --git a/Open-ILS/src/eg2/src/app/staff/circ/patron/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/circ/patron/routing.module.ts new file mode 100644 index 0000000000..9033f92642 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/patron/routing.module.ts @@ -0,0 +1,15 @@ +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 {} diff --git a/Open-ILS/src/eg2/src/app/staff/circ/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/circ/routing.module.ts new file mode 100644 index 0000000000..2409977d1f --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/circ/routing.module.ts @@ -0,0 +1,15 @@ +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 {} diff --git a/Open-ILS/src/eg2/src/app/staff/common.module.ts b/Open-ILS/src/eg2/src/app/staff/common.module.ts new file mode 100644 index 0000000000..e83143c9a3 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/common.module.ts @@ -0,0 +1,84 @@ +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 + ] + }; + } +} + diff --git a/Open-ILS/src/eg2/src/app/staff/login.component.html b/Open-ILS/src/eg2/src/app/staff/login.component.html new file mode 100644 index 0000000000..ba474f809f --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/login.component.html @@ -0,0 +1,58 @@ +
+
+
+ Sign In +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+
+ +
+
+
+
+
+
diff --git a/Open-ILS/src/eg2/src/app/staff/login.component.ts b/Open-ILS/src/eg2/src/app/staff/login.component.ts new file mode 100644 index 0000000000..2c1ac2a26e --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/login.component.ts @@ -0,0 +1,96 @@ +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. + } + ); + } +} + + + diff --git a/Open-ILS/src/eg2/src/app/staff/nav.component.css b/Open-ILS/src/eg2/src/app/staff/nav.component.css new file mode 100644 index 0000000000..63d3e37b29 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/nav.component.css @@ -0,0 +1,72 @@ +/* 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; +} + diff --git a/Open-ILS/src/eg2/src/app/staff/nav.component.html b/Open-ILS/src/eg2/src/app/staff/nav.component.html new file mode 100644 index 0000000000..419f5d5514 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/nav.component.html @@ -0,0 +1,432 @@ + + diff --git a/Open-ILS/src/eg2/src/app/staff/nav.component.ts b/Open-ILS/src/eg2/src/app/staff/nav.component.ts new file mode 100644 index 0000000000..c477c116b9 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/nav.component.ts @@ -0,0 +1,72 @@ +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(); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/resolver.service.ts b/Open-ILS/src/eg2/src/app/staff/resolver.service.ts new file mode 100644 index 0000000000..ccee922bbb --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/resolver.service.ts @@ -0,0 +1,143 @@ +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> { + + // Tracks the primary resolve observable. + observer: Observer; + + 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 { + + // 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 + = 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 { + 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 { + + // 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']; + }); + } +} + diff --git a/Open-ILS/src/eg2/src/app/staff/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/routing.module.ts new file mode 100644 index 0000000000..b515f389af --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/routing.module.ts @@ -0,0 +1,52 @@ +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 {} + diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/README b/Open-ILS/src/eg2/src/app/staff/sandbox/README new file mode 100644 index 0000000000..66e77dcb4f --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/sandbox/README @@ -0,0 +1 @@ +Place for experimenting with code. diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/routing.module.ts b/Open-ILS/src/eg2/src/app/staff/sandbox/routing.module.ts new file mode 100644 index 0000000000..144f0d2540 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/sandbox/routing.module.ts @@ -0,0 +1,16 @@ +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 {} diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html new file mode 100644 index 0000000000..289ed50268 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.html @@ -0,0 +1,133 @@ + + + + + +
+ + + + + + + +
+ + + +
+
+ +
+
+ +
+
+
+
+ + + +
+
+ +
+
+ +
+
+
+
+ +
+
+ Org select with limit perms +
+
+ + +
+
+ + + + + +
+ Hello, {{name}} + + + +
+ +
+
+ + +
+
HERE: {{testDate}}
+
+ + + + +Hello, {{context.world}}! + +

+HERasdfE +
+
+ + +
+
+

+ + + + HELLO {{userContext.hello}} + + + + + + + + +

+ + diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts new file mode 100644 index 0000000000..92b18dc9a9 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.component.ts @@ -0,0 +1,188 @@ +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; + + // @ViewChild('helloStr') private helloStr: StringComponent; + + gridDataSource: GridDataSource = new GridDataSource(); + + cbEntries: ComboboxEntry[]; + // supplier of async combobox data + cbAsyncSource: (term: string) => Observable; + + 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: 'hello', + 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); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.module.ts b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.module.ts new file mode 100644 index 0000000000..58910dddbb --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/sandbox/sandbox.module.ts @@ -0,0 +1,20 @@ +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 { + +} diff --git a/Open-ILS/src/eg2/src/app/staff/share/README b/Open-ILS/src/eg2/src/app/staff/share/README new file mode 100644 index 0000000000..1d6d167d9c --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/README @@ -0,0 +1 @@ +Classes, services, and components shared in the staff app. diff --git a/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.html b/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.html new file mode 100644 index 0000000000..194f06b515 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.html @@ -0,0 +1,59 @@ +{{idlClassDef.label}} Update Succeeded + + +{{idlClassDef.label}} Succeessfully Created + + + +
+
+
+
+ {{orgFieldLabel}} +
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+
+ + + + + + + + + + + + + + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.ts b/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.ts new file mode 100644 index 0000000000..be4452b1b3 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/admin-page/admin-page.component.ts @@ -0,0 +1,311 @@ +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; + } + +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.html b/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.html new file mode 100644 index 0000000000..d49de1bd7d --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.html @@ -0,0 +1,70 @@ + +
+
+
+ Record Summary +
+
+ +
+
+
    +
  • +
    +
    Title:
    +
    {{summary.display.title}}
    +
    Edition:
    +
    {{summary.display.edition}}
    +
    TCN:
    +
    {{summary.record.tcn_value()}}
    +
    Created By:
    + +
    +
  • +
  • +
    +
    Author:
    +
    {{summary.display.author}}
    +
    Pubdate:
    +
    {{summary.display.pubdate}}
    +
    Database ID:
    +
    {{summary.id}}
    +
    Last Edited By:
    + +
    +
  • +
  • +
    +
    Bib Call #:
    +
    {{summary.bibCallNumber}}
    +
    Record Owner:
    +
    {{orgName(summary.record.owner())}}
    +
    Created On:
    +
    {{summary.record.create_date() | date:'short'}}
    +
    Last Edited On:
    +
    {{summary.record.edit_date() | date:'short'}}
    +
    +
  • +
+
+
+ diff --git a/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.ts b/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.ts new file mode 100644 index 0000000000..78d2653c47 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/bib-summary/bib-summary.component.ts @@ -0,0 +1,67 @@ +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(); + } + } + +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/buckets/record-bucket-dialog.component.html b/Open-ILS/src/eg2/src/app/staff/share/buckets/record-bucket-dialog.component.html new file mode 100644 index 0000000000..f5e4c94652 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/buckets/record-bucket-dialog.component.html @@ -0,0 +1,56 @@ + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/buckets/record-bucket-dialog.component.ts b/Open-ILS/src/eg2/src/app/staff/share/buckets/record-bucket-dialog.component.ts new file mode 100644 index 0000000000..1f127b42c2 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/buckets/record-bucket-dialog.component.ts @@ -0,0 +1,109 @@ +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(); + } + }); + } +} + + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/holdings.service.ts b/Open-ILS/src/eg2/src/app/staff/share/holdings.service.ts new file mode 100644 index 0000000000..d2596b5527 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/holdings.service.ts @@ -0,0 +1,57 @@ +/** + * 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'); + }); + } + ); + } + +} + diff --git a/Open-ILS/src/eg2/src/app/staff/share/link-table/link-table.component.html b/Open-ILS/src/eg2/src/app/staff/share/link-table/link-table.component.html new file mode 100644 index 0000000000..0d82279307 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/link-table/link-table.component.html @@ -0,0 +1,22 @@ + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/link-table/link-table.component.ts b/Open-ILS/src/eg2/src/app/staff/share/link-table/link-table.component.ts new file mode 100644 index 0000000000..9b06c92732 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/link-table/link-table.component.ts @@ -0,0 +1,73 @@ +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: '' +}) + +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 + }); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/op-change/op-change.component.html b/Open-ILS/src/eg2/src/app/staff/share/op-change/op-change.component.html new file mode 100644 index 0000000000..e5a6f493b5 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/op-change/op-change.component.html @@ -0,0 +1,65 @@ + + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/op-change/op-change.component.ts b/Open-ILS/src/eg2/src/app/staff/share/op-change/op-change.component.ts new file mode 100644 index 0000000000..95d4db87e7 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/op-change/op-change.component.ts @@ -0,0 +1,77 @@ +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 { + 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 { + return this.auth.undoOpChange().then( + ok => this.toast.success(this.successMessage), + err => this.toast.danger(this.failMessage) + ); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/staff-banner.component.ts b/Open-ILS/src/eg2/src/app/staff/share/staff-banner.component.ts new file mode 100644 index 0000000000..13ac684e34 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/staff-banner.component.ts @@ -0,0 +1,15 @@ +import {Component, OnInit, Input} from '@angular/core'; + +@Component({ + selector: 'eg-staff-banner', + template: + '' +}) + +export class StaffBannerComponent { + @Input() public bannerText: string; +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/translate/translate.component.html b/Open-ILS/src/eg2/src/app/staff/share/translate/translate.component.html new file mode 100644 index 0000000000..7aa59b46c3 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/translate/translate.component.html @@ -0,0 +1,63 @@ + + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/share/translate/translate.component.ts b/Open-ILS/src/eg2/src/app/staff/share/translate/translate.component.ts new file mode 100644 index 0000000000..9c7361cb4c --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/share/translate/translate.component.ts @@ -0,0 +1,145 @@ +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') + ); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/splash.component.html b/Open-ILS/src/eg2/src/app/staff/splash.component.html new file mode 100644 index 0000000000..b37bec39b8 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/splash.component.html @@ -0,0 +1,128 @@ + + + + +
+ + +
+
+ +
+
+ +
+
+
+
+
Circulation and Patrons
+
+ +
+
+ +
+
+
+
Item Search and Cataloging
+
+
+
+
+
+ + + + + +
+
+ +
+ + Copy Buckets +
+
+
+
+
+ +
+
+
+
Administration
+
+ +
+
+
+
+ diff --git a/Open-ILS/src/eg2/src/app/staff/splash.component.ts b/Open-ILS/src/eg2/src/app/staff/splash.component.ts new file mode 100644 index 0000000000..af6b647dfa --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/splash.component.ts @@ -0,0 +1,40 @@ +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); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/staff.component.css b/Open-ILS/src/eg2/src/app/staff/staff.component.css new file mode 100644 index 0000000000..508d879b9b --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/staff.component.css @@ -0,0 +1,8 @@ +#staff-content-container { + width: 95%; + margin-top:56px; + padding-right: 10px; + padding-left: 10px; + margin-right: auto; + margin-left: auto; +} diff --git a/Open-ILS/src/eg2/src/app/staff/staff.component.html b/Open-ILS/src/eg2/src/app/staff/staff.component.html new file mode 100644 index 0000000000..2a2539c067 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/staff.component.html @@ -0,0 +1,19 @@ + + + +
+ + +
+ + + + + + + + + diff --git a/Open-ILS/src/eg2/src/app/staff/staff.component.ts b/Open-ILS/src/eg2/src/app/staff/staff.component.ts new file mode 100644 index 0000000000..492b1df14d --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/staff.component.ts @@ -0,0 +1,118 @@ +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; + */ + +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/staff.module.ts b/Open-ILS/src/eg2/src/app/staff/staff.module.ts new file mode 100644 index 0000000000..dd22f93e17 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/staff/staff.module.ts @@ -0,0 +1,26 @@ +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 {} + diff --git a/Open-ILS/src/eg2/src/app/welcome.component.html b/Open-ILS/src/eg2/src/app/welcome.component.html new file mode 100644 index 0000000000..eaa1c71896 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/welcome.component.html @@ -0,0 +1,11 @@ +
+

Welcome to Webby

+

+ If you see this page, you're probably in good shape... +

+
+

+ But maybe you meant to go to the + staff page +

+
diff --git a/Open-ILS/src/eg2/src/app/welcome.component.ts b/Open-ILS/src/eg2/src/app/welcome.component.ts new file mode 100644 index 0000000000..a5886614b0 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/welcome.component.ts @@ -0,0 +1,13 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + templateUrl : './welcome.component.html' +}) + +export class WelcomeComponent implements OnInit { + ngOnInit() { + } +} + + + diff --git a/Open-ILS/src/eg2/src/assets/.gitkeep b/Open-ILS/src/eg2/src/assets/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Open-ILS/src/eg2/src/environments/environment.prod.ts b/Open-ILS/src/eg2/src/environments/environment.prod.ts new file mode 100644 index 0000000000..50385bfd07 --- /dev/null +++ b/Open-ILS/src/eg2/src/environments/environment.prod.ts @@ -0,0 +1,4 @@ +export const environment = { + production: true, + locales: ['en-US', 'fr-CA'] +}; diff --git a/Open-ILS/src/eg2/src/environments/environment.ts b/Open-ILS/src/eg2/src/environments/environment.ts new file mode 100644 index 0000000000..113cbe39b9 --- /dev/null +++ b/Open-ILS/src/eg2/src/environments/environment.ts @@ -0,0 +1,10 @@ +// 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'] +}; diff --git a/Open-ILS/src/eg2/src/favicon.ico b/Open-ILS/src/eg2/src/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..8081c7ceaf2be08bf59010158c586170d9d2d517 GIT binary patch literal 5430 zcmc(je{54#6vvCoAI3i*G5%$U7!sA3wtMZ$fH6V9C`=eXGJb@R1%(I_{vnZtpD{6n z5Pl{DmxzBDbrB>}`90e12m8T*36WoeDLA&SD_hw{H^wM!cl_RWcVA!I+x87ee975; z@4kD^=bYPn&pmG@(+JZ`rqQEKxW<}RzhW}I!|ulN=fmjVi@x{p$cC`)5$a!)X&U+blKNvN5tg=uLvuLnuqRM;Yc*swiexsoh#XPNu{9F#c`G zQLe{yWA(Y6(;>y|-efAy11k<09(@Oo1B2@0`PtZSkqK&${ zgEY}`W@t{%?9u5rF?}Y7OL{338l*JY#P!%MVQY@oqnItpZ}?s z!r?*kwuR{A@jg2Chlf0^{q*>8n5Ir~YWf*wmsh7B5&EpHfd5@xVaj&gqsdui^spyL zB|kUoblGoO7G(MuKTfa9?pGH0@QP^b#!lM1yHWLh*2iq#`C1TdrnO-d#?Oh@XV2HK zKA{`eo{--^K&MW66Lgsktfvn#cCAc*(}qsfhrvOjMGLE?`dHVipu1J3Kgr%g?cNa8 z)pkmC8DGH~fG+dlrp(5^-QBeEvkOvv#q7MBVLtm2oD^$lJZx--_=K&Ttd=-krx(Bb zcEoKJda@S!%%@`P-##$>*u%T*mh+QjV@)Qa=Mk1?#zLk+M4tIt%}wagT{5J%!tXAE;r{@=bb%nNVxvI+C+$t?!VJ@0d@HIyMJTI{vEw0Ul ze(ha!e&qANbTL1ZneNl45t=#Ot??C0MHjjgY8%*mGisN|S6%g3;Hlx#fMNcL<87MW zZ>6moo1YD?P!fJ#Jb(4)_cc50X5n0KoDYfdPoL^iV`k&o{LPyaoqMqk92wVM#_O0l z09$(A-D+gVIlq4TA&{1T@BsUH`Bm=r#l$Z51J-U&F32+hfUP-iLo=jg7Xmy+WLq6_tWv&`wDlz#`&)Jp~iQf zZP)tu>}pIIJKuw+$&t}GQuqMd%Z>0?t%&BM&Wo^4P^Y z)c6h^f2R>X8*}q|bblAF?@;%?2>$y+cMQbN{X$)^R>vtNq_5AB|0N5U*d^T?X9{xQnJYeU{ zoZL#obI;~Pp95f1`%X3D$Mh*4^?O?IT~7HqlWguezmg?Ybq|7>qQ(@pPHbE9V?f|( z+0xo!#m@Np9PljsyxBY-UA*{U*la#8Wz2sO|48_-5t8%_!n?S$zlGe+NA%?vmxjS- zHE5O3ZarU=X}$7>;Okp(UWXJxI%G_J-@IH;%5#Rt$(WUX?6*Ux!IRd$dLP6+SmPn= z8zjm4jGjN772R{FGkXwcNv8GBcZI#@Y2m{RNF_w8(Z%^A*!bS*!}s6sh*NnURytky humW;*g7R+&|Ledvc- + + + + AngEG + + + + + + + + + + + + + + diff --git a/Open-ILS/src/eg2/src/locale/.gitkeep b/Open-ILS/src/eg2/src/locale/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Open-ILS/src/eg2/src/main.ts b/Open-ILS/src/eg2/src/main.ts new file mode 100644 index 0000000000..2e303cfac1 --- /dev/null +++ b/Open-ILS/src/eg2/src/main.ts @@ -0,0 +1,12 @@ +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)); diff --git a/Open-ILS/src/eg2/src/polyfills.ts b/Open-ILS/src/eg2/src/polyfills.ts new file mode 100644 index 0000000000..e073082c1c --- /dev/null +++ b/Open-ILS/src/eg2/src/polyfills.ts @@ -0,0 +1,80 @@ +/** + * 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'; diff --git a/Open-ILS/src/eg2/src/styles.css b/Open-ILS/src/eg2/src/styles.css new file mode 100644 index 0000000000..b87ad78acf --- /dev/null +++ b/Open-ILS/src/eg2/src/styles.css @@ -0,0 +1,161 @@ +/* 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} +} + diff --git a/Open-ILS/src/eg2/src/test.ts b/Open-ILS/src/eg2/src/test.ts new file mode 100644 index 0000000000..cd612eeb0e --- /dev/null +++ b/Open-ILS/src/eg2/src/test.ts @@ -0,0 +1,32 @@ +// 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(); diff --git a/Open-ILS/src/eg2/src/test_data/eg_mock.js b/Open-ILS/src/eg2/src/test_data/eg_mock.js new file mode 100644 index 0000000000..3db357974f --- /dev/null +++ b/Open-ILS/src/eg2/src/test_data/eg_mock.js @@ -0,0 +1,52 @@ +/** + * 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(); + } +} diff --git a/Open-ILS/src/eg2/src/test_data/idl2js.pl b/Open-ILS/src/eg2/src/test_data/idl2js.pl new file mode 100644 index 0000000000..fa3f2a4fae --- /dev/null +++ b/Open-ILS/src/eg2/src/test_data/idl2js.pl @@ -0,0 +1,36 @@ +#!/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; +} diff --git a/Open-ILS/src/eg2/src/tsconfig.app.json b/Open-ILS/src/eg2/src/tsconfig.app.json new file mode 100644 index 0000000000..39ba8dbacb --- /dev/null +++ b/Open-ILS/src/eg2/src/tsconfig.app.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "outDir": "../out-tsc/app", + "baseUrl": "./", + "module": "es2015", + "types": [] + }, + "exclude": [ + "test.ts", + "**/*.spec.ts" + ] +} diff --git a/Open-ILS/src/eg2/src/tsconfig.spec.json b/Open-ILS/src/eg2/src/tsconfig.spec.json new file mode 100644 index 0000000000..18bad40ed4 --- /dev/null +++ b/Open-ILS/src/eg2/src/tsconfig.spec.json @@ -0,0 +1,21 @@ +{ + "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" + ] +} diff --git a/Open-ILS/src/eg2/src/typings.d.ts b/Open-ILS/src/eg2/src/typings.d.ts new file mode 100644 index 0000000000..ef5c7bd620 --- /dev/null +++ b/Open-ILS/src/eg2/src/typings.d.ts @@ -0,0 +1,5 @@ +/* SystemJS module definition */ +declare var module: NodeModule; +interface NodeModule { + id: string; +} diff --git a/Open-ILS/src/eg2/tsconfig.json b/Open-ILS/src/eg2/tsconfig.json new file mode 100644 index 0000000000..14a504dc91 --- /dev/null +++ b/Open-ILS/src/eg2/tsconfig.json @@ -0,0 +1,24 @@ +{ + "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" + ] + } +} diff --git a/Open-ILS/src/eg2/tslint.json b/Open-ILS/src/eg2/tslint.json new file mode 100644 index 0000000000..e8fbb4037f --- /dev/null +++ b/Open-ILS/src/eg2/tslint.json @@ -0,0 +1,136 @@ +{ + "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 + } +} -- 2.43.2