diff --git a/.flowconfig b/.flowconfig deleted file mode 100644 index 88e6b033..00000000 --- a/.flowconfig +++ /dev/null @@ -1,3 +0,0 @@ -[ignore] - -[include] diff --git a/.jscsrc b/.jscsrc deleted file mode 100644 index 516f92a1..00000000 --- a/.jscsrc +++ /dev/null @@ -1,66 +0,0 @@ -{ - "requireCurlyBraces": [ - "for", - "while", - "do", - "try", - "catch" - ], - "requireOperatorBeforeLineBreak": true, - "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties", - "maximumLineLength": { - "value": 80, - "allowComments": true, - "allowRegex": true - }, - "validateIndentation": 2, - "validateQuoteMarks": "'", - - "disallowMultipleLineStrings": true, - "disallowMixedSpacesAndTabs": true, - "disallowTrailingWhitespace": true, - "disallowSpaceAfterPrefixUnaryOperators": true, - "disallowMultipleVarDecl": true, - - "requireSpaceAfterKeywords": [ - "if", - "else", - "for", - "while", - "do", - "switch", - "return", - "try", - "catch" - ], - "requireSpaceBeforeBinaryOperators": [ - "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", - "&=", "|=", "^=", "+=", - - "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&", - "|", "^", "&&", "||", "===", "==", ">=", - "<=", "<", ">", "!=", "!==" - ], - "requireSpaceAfterBinaryOperators": true, - "requireSpacesInConditionalExpression": true, - "requireSpaceBeforeBlockStatements": true, - "requireLineFeedAtFileEnd": true, - "requireSpacesInFunctionExpression": { - "beforeOpeningCurlyBrace": true - }, - "disallowSpacesInAnonymousFunctionExpression": { - "beforeOpeningRoundBrace": true - }, - "disallowSpacesInsideObjectBrackets": "all", - "disallowSpacesInsideArrayBrackets": "all", - "disallowSpacesInsideParentheses": true, - - - "validateJSDoc": { - "checkParamNames": true, - "requireParamTypes": true - }, - - "disallowMultipleLineBreaks": true, - "disallowNewlineBeforeBlockStatements": true -} diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 0f4a7724..00000000 --- a/.jshintrc +++ /dev/null @@ -1,6 +0,0 @@ -{ - "trailing": false, - "node": true, - "unused": "last-param", - "globalstrict": true -} \ No newline at end of file diff --git a/lamassu-admin-elm/.gitignore b/lamassu-admin-elm/.gitignore new file mode 100644 index 00000000..c15b6774 --- /dev/null +++ b/lamassu-admin-elm/.gitignore @@ -0,0 +1,8 @@ +node_modules +elm-stuff + +build/styles.css +build/elm.js + +.vscode +.idea diff --git a/lamassu-admin-elm/README.md b/lamassu-admin-elm/README.md new file mode 100644 index 00000000..9ca78a32 --- /dev/null +++ b/lamassu-admin-elm/README.md @@ -0,0 +1,28 @@ + +# lamassu-admin + +## Development + +Start the hot-reloading webpack dev server: + + npm start + +Navigate to . + +Any changes you make to your files (.elm, .js, .css, etc.) will trigger +a hot reload. + +## Production + +When you're ready to deploy: + + npm run build + +This will create a `dist` folder: + + . + ├── dist + │   ├── index.html + │   ├── 5df766af1ced8ff1fe0a.css + │   └── 5df766af1ced8ff1fe0a.js + diff --git a/lamassu-admin-elm/UNLICENSE b/lamassu-admin-elm/UNLICENSE new file mode 100644 index 00000000..6bb8a291 --- /dev/null +++ b/lamassu-admin-elm/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower.json b/lamassu-admin-elm/build/bower.json new file mode 100644 index 00000000..d21a9346 --- /dev/null +++ b/lamassu-admin-elm/build/bower.json @@ -0,0 +1,18 @@ +{ + "name": "lamassu-admin-elm", + "homepage": "https://github.com/lamassu/lamassu-admin-elm", + "authors": [ + "Josh Harvey " + ], + "description": "", + "main": "", + "license": "MIT", + "private": true, + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ] +} diff --git a/lamassu-admin-elm/build/bower_components/gridism/.bower.json b/lamassu-admin-elm/build/bower_components/gridism/.bower.json new file mode 100644 index 00000000..38f81cc3 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/gridism/.bower.json @@ -0,0 +1,27 @@ +{ + "name": "gridism", + "version": "0.2.2", + "author": "Coby Chapple", + "homepage": "http://cobyism.com/gridism", + "main": "./gridism.css", + "repository": { + "type": "git", + "url": "git://github.com/cobyism/gridism.git" + }, + "ignore": [ + "shapeshifter/", + "**/*.yml", + "**/*.html" + ], + "license": "MIT", + "_release": "0.2.2", + "_resolution": { + "type": "version", + "tag": "0.2.2", + "commit": "490be0b6813d701dcc35a82b0bcc8f639e5ad63f" + }, + "_source": "https://github.com/cobyism/gridism.git", + "_target": "^0.2.2", + "_originalSource": "gridism", + "_direct": true +} \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/gridism/LICENSE b/lamassu-admin-elm/build/bower_components/gridism/LICENSE new file mode 100644 index 00000000..ee27726e --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/gridism/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013 Coby Chapple. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the 'Software'), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lamassu-admin-elm/build/bower_components/gridism/README.md b/lamassu-admin-elm/build/bower_components/gridism/README.md new file mode 100644 index 00000000..a60072ef --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/gridism/README.md @@ -0,0 +1,80 @@ +# Gridism + +A simple responsive CSS grid. [View the demo →](http://cobyism.com/gridism/) + +## Why? + +### My process + +When I design web layouts, my thought process usually goes something like this: + +> Alright, in this section, I want a bit that’s one third of the section’s width, +> and then next to that I want another bit that’s two thirds of the sections’s width. +> Now, in the next section… + +I don’t think in 12 or 16 column grids. Instead, my mental model basically just consists of the page being divided up into multiple full-width vertical sections, and each vertical section being divided up into simple fractions of the section width. + +### Existing grid frameworks + +Most frameworks I’ve used don’t match that thought process *at all*. I usually have to: + +1. Remember how many columns are in the grid for the particular framework I’m using. +1. Decide how I want to divide up this particular section’s content. +1. Mentally do the conversion from what I want to see (one quarter + three quarters, for example) into the number of columns I need for the grid I’m using. +1. Remember the class naming structure for the framework I’m using. Is it `.span3`, `.grid_3`, `.col-3`, or something else altogether? +1. Deal with other hassles like clearing floats, messing with column padding to have the gutters look right, indicating which elements are the first in a row, and so forth. + +Only the second step should be necessary. + +### Gridism’s Goals + +I couldn’t find a framework that matched this mental model of how I work, so I started hacking on Gridism with the following goals: + +- Class names should be memorable and self-evident. +- Gutters and basic content padding should be taken care of. +- Clearing floats should be done automatically. +- Wrapped grid sections should be independant of vertical page sections. +- Frequently required utility classes should be provided. +- Common patterns for Responsive Design™ should be built-in. + +I hope you find that this project is living up to those goals. If not, please [create an issue](https://github.com/cobyism/gridism/issues/new) and let me know. + +## Installation + +### 1. Get the files + +The easiest way to use Gridism in your project is via the [Bower](http://twitter.github.com/bower) package manager. + +```sh +bower install gridism +``` + +Elsewise, [download the zip folder](https://github.com/cobyism/gridism/archive/gh-pages.zip), extract it, and copy `gridism.css` into your project’s folder. Boom. Done. + +### 2. Link the stylesheet + +Add the following stylesheet to your HTML’s `` section: + +```html + +``` + +**Note:** If you didn’t install using Bower, you need to adjust the path of CSS file to match your file structure. + +### 3. Viewport scale + +Add the following meta tag to your HTML’s `` section: + +```html + +``` + +Without this meta tag, mobiles and tablets might load your page as a scaled-down version of the desktop size, instead of resizing the content to match the device’s actual viewport width. + +## Contributing + +I’d :heart: to receive contributions to this project. It doesn’t matter if it’s just a typo, or if you’re proposing an overhaul of the entire project—I’ll gladly take a look at your changes. Fork at will! :grinning:. + +## License + +Go nuts. See [LICENSE](https://github.com/cobyism/gridism/blob/gh-pages/LICENSE) (MIT). diff --git a/lamassu-admin-elm/build/bower_components/gridism/bower.json b/lamassu-admin-elm/build/bower_components/gridism/bower.json new file mode 100644 index 00000000..fd0bbdc3 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/gridism/bower.json @@ -0,0 +1,17 @@ +{ + "name": "gridism", + "version": "0.2.1", + "author": "Coby Chapple", + "homepage": "http://cobyism.com/gridism", + "main": "./gridism.css", + "repository": { + "type": "git", + "url": "git://github.com/cobyism/gridism.git" + }, + "ignore": [ + "shapeshifter/", + "**/*.yml", + "**/*.html" + ], + "license": "MIT" +} diff --git a/lamassu-admin-elm/build/bower_components/gridism/gridism.css b/lamassu-admin-elm/build/bower_components/gridism/gridism.css new file mode 100644 index 00000000..11894bc6 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/gridism/gridism.css @@ -0,0 +1,132 @@ +/* + * Gridism + * A simple, responsive, and handy CSS grid by @cobyism + * https://github.com/cobyism/gridism + */ + +/* Preserve some sanity */ +.grid, +.unit { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +/* Set up some rules to govern the grid */ +.grid { + display: block; + clear: both; +} +.grid .unit { + float: left; + width: 100%; + padding: 10px; +} + +/* This ensures the outer gutters are equal to the (doubled) inner gutters. */ +.grid .unit:first-child { padding-left: 20px; } +.grid .unit:last-child { padding-right: 20px; } + +/* Nested grids already have padding though, so let's nuke it */ +.unit .unit:first-child { padding-left: 0; } +.unit .unit:last-child { padding-right: 0; } +.unit .grid:first-child > .unit { padding-top: 0; } +.unit .grid:last-child > .unit { padding-bottom: 0; } + +/* Let people nuke the gutters/padding completely in a couple of ways */ +.no-gutters .unit, +.unit.no-gutters { + padding: 0 !important; +} + +/* Wrapping at a maximum width is optional */ +.wrap .grid, +.grid.wrap { + max-width: 978px; + margin: 0 auto; +} + +/* Width classes also have shorthand versions numbered as fractions + * For example: for a grid unit 1/3 (one third) of the parent width, + * simply apply class="w-1-3" to the element. */ +.grid .whole, .grid .w-1-1 { width: 100%; } +.grid .half, .grid .w-1-2 { width: 50%; } +.grid .one-third, .grid .w-1-3 { width: 33.3332%; } +.grid .two-thirds, .grid .w-2-3 { width: 66.6665%; } +.grid .one-quarter, +.grid .one-fourth, .grid .w-1-4 { width: 25%; } +.grid .three-quarters, +.grid .three-fourths, .grid .w-3-4 { width: 75%; } +.grid .one-fifth, .grid .w-1-5 { width: 20%; } +.grid .two-fifths, .grid .w-2-5 { width: 40%; } +.grid .three-fifths, .grid .w-3-5 { width: 60%; } +.grid .four-fifths, .grid .w-4-5 { width: 80%; } +.grid .golden-small, .grid .w-g-s { width: 38.2716%; } /* Golden section: smaller piece */ +.grid .golden-large, .grid .w-g-l { width: 61.7283%; } /* Golden section: larger piece */ + +/* Clearfix after every .grid */ +.grid { + *zoom: 1; +} +.grid:before, .grid:after { + display: table; + content: ""; + line-height: 0; +} +.grid:after { + clear: both; +} + +/* Utility classes */ +.align-center { text-align: center; } +.align-left { text-align: left; } +.align-right { text-align: right; } +.pull-left { float: left; } +.pull-right { float: right; } + +/* A property for a better rendering of images in units: in + this way bigger pictures are just resized if the unit + becomes smaller */ +.unit img { + max-width: 100%; +} + +/* Hide elements using this class by default */ +.only-on-mobiles { + display: none !important; +} + +/* Responsive Stuff */ +@media screen and (max-width: 568px) { + /* Stack anything that isn't full-width on smaller screens + and doesn't provide the no-stacking-on-mobiles class */ + .grid:not(.no-stacking-on-mobiles) > .unit { + width: 100% !important; + padding-left: 20px; + padding-right: 20px; + } + .unit .grid .unit { + padding-left: 0px; + padding-right: 0px; + } + + /* Sometimes, you just want to be different on small screens */ + .center-on-mobiles { + text-align: center !important; + } + .hide-on-mobiles { + display: none !important; + } + .only-on-mobiles { + display: block !important; + } +} + +/* Expand the wrap a bit further on larger screens */ +@media screen and (min-width: 1180px) { + .wider .grid, + .grid.wider { + max-width: 1180px; + margin: 0 auto; + } +} diff --git a/lamassu-admin-elm/build/bower_components/qr-code/.bower.json b/lamassu-admin-elm/build/bower_components/qr-code/.bower.json new file mode 100644 index 00000000..a7cec63b --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qr-code/.bower.json @@ -0,0 +1,40 @@ +{ + "name": "qr-code", + "version": "0.1.8", + "homepage": "https://github.com/educastellano/qr-code", + "authors": [ + "Eduard Castellano " + ], + "description": "Web Component for generating QR codes", + "main": "src/qr-code.js", + "keywords": [ + "qr", + "qrcode", + "qr-code", + "webcomponent", + "customelement", + "web-components" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests", + "demo" + ], + "dependencies": { + "qrjs": "~0.1.2" + }, + "_release": "0.1.8", + "_resolution": { + "type": "version", + "tag": "v0.1.8", + "commit": "28a413834c62d8ec7f5b3f3005fe2ee78e47e647" + }, + "_source": "https://github.com/educastellano/qr-code.git", + "_target": "^0.1.8", + "_originalSource": "webcomponent-qr-code", + "_direct": true +} \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/qr-code/README.md b/lamassu-admin-elm/build/bower_components/qr-code/README.md new file mode 100644 index 00000000..a3841583 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qr-code/README.md @@ -0,0 +1,94 @@ +# <qr-code> + +Web Component for generating QR Codes, using (a [fork](https://github.com/educastellano/qr.js) of) [qr.js](https://github.com/lifthrasiir/qr.js) lib. + +> Maintained by [Eduard Castellano](https://github.com/educastellano). + +## Demo + +> [Check it live](http://educastellano.github.io/qr-code/demo). + +## Usage + +* **NPM and Browserify** ([polyfill](https://github.com/WebComponents/webcomponentsjs) and the component): + + Install: + + ```sh + npm install webcomponents.js + npm install webcomponent-qr-code + ``` + + Import: + + ```js + require('webcomponents.js'); + require('webcomponent-qr-code'); + ``` + +* **Bower** ([polyfill](https://github.com/WebComponents/webcomponentsjs), [qr.js](https://github.com/educastellano/qr.js) and the component): + + Install: + + ```sh + bower install webcomponentsjs + bower install webcomponent-qr-code + ``` + + Import: + + ```html + + + + ``` + + > You can also import the component with [HTML Imports](http://w3c.github.io/webcomponents/spec/imports/), but you still need to import the polyfill and the qr.js lib separately: + > + > ```html + > + > ``` + +* **Start using it!** + + ```html + + ``` + + + +## Options + +Attribute | Options | Default | Description +--- | --- | --- | --- +`data` | *string* | `null` | The information encoded by the QR code. +`format` | `png`, `html`, `svg` | `png` | Format of the QR code rendered inside the component. +`modulesize` | *int* | `5` | Size of the modules in *pixels*. +`margin` | *int* | `4` | Margin of the QR code in *modules*. + + +## Contributing + +1. Fork it! +2. Create your feature branch: `git checkout -b my-new-feature` +3. Commit your changes: `git commit -m 'Add some feature'` +4. Push to the branch: `git push origin my-new-feature` +5. Submit a pull request :D + +## History + +* v0.1.7 April 11, 2015 + * Support for SVG +* v0.1.6 April 10, 2015 + * Default attributes + * qr.js removed and used as a dependency + * Available in NPM +* v0.1.1 March 31, 2015 + * Framework-agnostic webcomponent (no use of Polymer) + * Available in Bower +* v0.0.1 September 18, 2013 + * Started project using [boilerplate-element](https://github.com/customelements/boilerplate-element) + +## License + +[MIT License](http://opensource.org/licenses/MIT) diff --git a/lamassu-admin-elm/build/bower_components/qr-code/bower.json b/lamassu-admin-elm/build/bower_components/qr-code/bower.json new file mode 100644 index 00000000..14e071e0 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qr-code/bower.json @@ -0,0 +1,30 @@ +{ + "name": "qr-code", + "version": "0.1.7", + "homepage": "https://github.com/educastellano/qr-code", + "authors": [ + "Eduard Castellano " + ], + "description": "Web Component for generating QR codes", + "main": "src/qr-code.js", + "keywords": [ + "qr", + "qrcode", + "qr-code", + "webcomponent", + "customelement", + "web-components" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests", + "demo" + ], + "dependencies": { + "qrjs": "~0.1.2" + } +} diff --git a/lamassu-admin-elm/build/bower_components/qr-code/index.html b/lamassu-admin-elm/build/bower_components/qr-code/index.html new file mode 100644 index 00000000..a8d632d7 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qr-code/index.html @@ -0,0 +1,10 @@ + + + + + <qr-code> + + + Demo here + + \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/qr-code/index.js b/lamassu-admin-elm/build/bower_components/qr-code/index.js new file mode 100644 index 00000000..14ece1cf --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qr-code/index.js @@ -0,0 +1 @@ +require('./src/qr-code.js') \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/qr-code/package.json b/lamassu-admin-elm/build/bower_components/qr-code/package.json new file mode 100644 index 00000000..7381b8b5 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qr-code/package.json @@ -0,0 +1,29 @@ +{ + "name": "webcomponent-qr-code", + "version": "0.1.8", + "description": "Web Component for generating QR codes", + "main": "qr-code.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/educastellano/qr-code.git" + }, + "keywords": [ + "qr", + "qrcode", + "qr-code", + "webcomponent", + "custom-element" + ], + "author": "Eduard Castellano", + "license": "MIT", + "bugs": { + "url": "https://github.com/educastellano/qr-code/issues" + }, + "homepage": "https://github.com/educastellano/qr-code", + "dependencies": { + "qrjs": "^0.1.2" + } +} diff --git a/lamassu-admin-elm/build/bower_components/qr-code/src/polymer/qr-code.html b/lamassu-admin-elm/build/bower_components/qr-code/src/polymer/qr-code.html new file mode 100644 index 00000000..a228615a --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qr-code/src/polymer/qr-code.html @@ -0,0 +1,56 @@ + + + + + \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/qr-code/src/qr-code.html b/lamassu-admin-elm/build/bower_components/qr-code/src/qr-code.html new file mode 100644 index 00000000..71882d90 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qr-code/src/qr-code.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/qr-code/src/qr-code.js b/lamassu-admin-elm/build/bower_components/qr-code/src/qr-code.js new file mode 100644 index 00000000..2b11b09b --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qr-code/src/qr-code.js @@ -0,0 +1,144 @@ +'use strict'; + +(function(definition) { + if (typeof define === 'function' && define.amd) { + define(['QRCode'], definition); + } else if (typeof module === 'object' && module.exports) { + var QRCode = require('qrjs'); + module.exports = definition(QRCode); + } else { + definition(window.QRCode); + } +})(function(QRCode) { +// +// Prototype +// +var proto = Object.create(HTMLElement.prototype, { + // + // Attributes + // + attrs: { + value: { + data: null, + format: 'png', + modulesize: 5, + margin: 4 + } + }, + defineAttributes: { + value: function () { + var attrs = Object.keys(this.attrs), + attr; + for (var i=0; i' + } + } + else { + this.shadowRoot.innerHTML = '
qr-code: no data!
' + } + } + }, + generatePNG: { + value: function () { + try { + var img = document.createElement('img'); + img.src = QRCode.generatePNG(this.data, this.getOptions()); + this.clear(); + this.shadowRoot.appendChild(img); + } + catch (e) { + this.shadowRoot.innerHTML = '
qr-code: no canvas support!
' + } + } + }, + generateHTML: { + value: function () { + var div = QRCode.generateHTML(this.data, this.getOptions()); + this.clear(); + this.shadowRoot.appendChild(div); + } + }, + generateSVG: { + value: function () { + var div = QRCode.generateSVG(this.data, this.getOptions()); + this.clear(); + this.shadowRoot.appendChild(div); + } + }, + clear: { + value: function () { + while (this.shadowRoot.lastChild) { + this.shadowRoot.removeChild(this.shadowRoot.lastChild); + } + } + } +}); +// +// Register +// +document.registerElement('qr-code', { + prototype: proto +}); +}); + + diff --git a/lamassu-admin-elm/build/bower_components/qrcodejs/.bower.json b/lamassu-admin-elm/build/bower_components/qrcodejs/.bower.json new file mode 100644 index 00000000..a4bd9ee3 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qrcodejs/.bower.json @@ -0,0 +1,31 @@ +{ + "name": "qrcodejs", + "version": "0.1.0", + "homepage": "https://github.com/CatTail/qrcodejs", + "authors": [ + "davidshimjs ssm0123@gmail.com" + ], + "description": "Cross-browser QRCode generator for javascript", + "main": "qrcode.js", + "keywords": [ + "qrcode" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "_release": "0.1.0", + "_resolution": { + "type": "version", + "tag": "v0.1.0", + "commit": "71340740270b3c9d797ecaa7d7a75af36037217d" + }, + "_source": "https://github.com/CatTail/qrcodejs.git", + "_target": "^0.1.0", + "_originalSource": "qrcodejs", + "_direct": true +} \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/qrcodejs/LICENSE b/lamassu-admin-elm/build/bower_components/qrcodejs/LICENSE new file mode 100644 index 00000000..93c33233 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qrcodejs/LICENSE @@ -0,0 +1,14 @@ +The MIT License (MIT) +--------------------- +Copyright (c) 2012 davidshimjs + +Permission is hereby granted, free of charge, +to any person obtaining a copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, +including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/qrcodejs/README.md b/lamassu-admin-elm/build/bower_components/qrcodejs/README.md new file mode 100644 index 00000000..ce0da3b7 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qrcodejs/README.md @@ -0,0 +1,43 @@ +# QRCode.js +QRCode.js is javascript library for making QRCode. QRCode.js supports Cross-browser with HTML5 Canvas and table tag in DOM. +QRCode.js has no dependencies. + +## Basic Usages +``` +
+ +``` + +or with some options + +``` +var qrcode = new QRCode("test", { + text: "http://jindo.dev.naver.com/collie", + width: 128, + height: 128, + colorDark : "#000000", + colorLight : "#ffffff", + correctLevel : QRCode.CorrectLevel.H +}); +``` + +and you can use some methods + +``` +qrcode.clear(); // clear the code. +qrcode.makeCode("http://naver.com"); // make another code. +``` + +## Browser Compatibility +IE6~10, Chrome, Firefox, Safari, Opera, Mobile Safari, Android, Windows Mobile, ETC. + +## License +MIT License + +## Contact +twitter @davidshimjs + +[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/davidshimjs/qrcodejs/trend.png)](https://bitdeli.com/free "Bitdeli Badge") + diff --git a/lamassu-admin-elm/build/bower_components/qrcodejs/bower.json b/lamassu-admin-elm/build/bower_components/qrcodejs/bower.json new file mode 100644 index 00000000..47aec06c --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qrcodejs/bower.json @@ -0,0 +1,21 @@ +{ + "name": "qrcodejs", + "version": "0.1.0", + "homepage": "https://github.com/CatTail/qrcodejs", + "authors": [ + "davidshimjs ssm0123@gmail.com" + ], + "description": "Cross-browser QRCode generator for javascript", + "main": "qrcode.js", + "keywords": [ + "qrcode" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ] +} diff --git a/lamassu-admin-elm/build/bower_components/qrcodejs/index.html b/lamassu-admin-elm/build/bower_components/qrcodejs/index.html new file mode 100644 index 00000000..fc16f3d9 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qrcodejs/index.html @@ -0,0 +1,44 @@ + + + +Cross-Browser QRCode generator for Javascript + + + + + + +
+
+ + + \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/qrcodejs/index.svg b/lamassu-admin-elm/build/bower_components/qrcodejs/index.svg new file mode 100644 index 00000000..fabe56aa --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qrcodejs/index.svg @@ -0,0 +1,37 @@ + + + + + +
+ +
+ + + +
+
diff --git a/lamassu-admin-elm/build/bower_components/qrcodejs/jquery.min.js b/lamassu-admin-elm/build/bower_components/qrcodejs/jquery.min.js new file mode 100644 index 00000000..2740cc4c --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qrcodejs/jquery.min.js @@ -0,0 +1,2 @@ +/*! jQuery v1.8.3 jquery.com | jquery.org/license */ +(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write(""),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t
a",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="
t
",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="
",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;ti.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="
",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="

",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t0)for(i=r;i=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*\s*$/g,Nt={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X
","
"]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1>");try{for(;r1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]===""&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("
").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.ajQuery&&define("jquery",[],function(){return v})})(window); \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/qrcodejs/qrcode.js b/lamassu-admin-elm/build/bower_components/qrcodejs/qrcode.js new file mode 100644 index 00000000..c1217a38 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qrcodejs/qrcode.js @@ -0,0 +1,609 @@ +/** + * @fileoverview + * - Using the 'QRCode for Javascript library' + * - Fixed dataset of 'QRCode for Javascript library' for support full-spec. + * - this library has no dependencies. + * + * @author davidshimjs + * @see http://www.d-project.com/ + * @see http://jeromeetienne.github.com/jquery-qrcode/ + */ +var QRCode; + +(function () { + //--------------------------------------------------------------------- + // QRCode for JavaScript + // + // Copyright (c) 2009 Kazuhiko Arase + // + // URL: http://www.d-project.com/ + // + // Licensed under the MIT license: + // http://www.opensource.org/licenses/mit-license.php + // + // The word "QR Code" is registered trademark of + // DENSO WAVE INCORPORATED + // http://www.denso-wave.com/qrcode/faqpatent-e.html + // + //--------------------------------------------------------------------- + function QR8bitByte(data) { + this.mode = QRMode.MODE_8BIT_BYTE; + this.data = data; + this.parsedData = []; + + // Added to support UTF-8 Characters + for (var i = 0, l = this.data.length; i < l; i++) { + var byteArray = []; + var code = this.data.charCodeAt(i); + + if (code > 0x10000) { + byteArray[0] = 0xF0 | ((code & 0x1C0000) >>> 18); + byteArray[1] = 0x80 | ((code & 0x3F000) >>> 12); + byteArray[2] = 0x80 | ((code & 0xFC0) >>> 6); + byteArray[3] = 0x80 | (code & 0x3F); + } else if (code > 0x800) { + byteArray[0] = 0xE0 | ((code & 0xF000) >>> 12); + byteArray[1] = 0x80 | ((code & 0xFC0) >>> 6); + byteArray[2] = 0x80 | (code & 0x3F); + } else if (code > 0x80) { + byteArray[0] = 0xC0 | ((code & 0x7C0) >>> 6); + byteArray[1] = 0x80 | (code & 0x3F); + } else { + byteArray[0] = code; + } + + this.parsedData.push(byteArray); + } + + this.parsedData = Array.prototype.concat.apply([], this.parsedData); + + if (this.parsedData.length != this.data.length) { + this.parsedData.unshift(191); + this.parsedData.unshift(187); + this.parsedData.unshift(239); + } + } + + QR8bitByte.prototype = { + getLength: function (buffer) { + return this.parsedData.length; + }, + write: function (buffer) { + for (var i = 0, l = this.parsedData.length; i < l; i++) { + buffer.put(this.parsedData[i], 8); + } + } + }; + + function QRCodeModel(typeNumber, errorCorrectLevel) { + this.typeNumber = typeNumber; + this.errorCorrectLevel = errorCorrectLevel; + this.modules = null; + this.moduleCount = 0; + this.dataCache = null; + this.dataList = []; + } + + QRCodeModel.prototype={addData:function(data){var newData=new QR8bitByte(data);this.dataList.push(newData);this.dataCache=null;},isDark:function(row,col){if(row<0||this.moduleCount<=row||col<0||this.moduleCount<=col){throw new Error(row+","+col);} + return this.modules[row][col];},getModuleCount:function(){return this.moduleCount;},make:function(){this.makeImpl(false,this.getBestMaskPattern());},makeImpl:function(test,maskPattern){this.moduleCount=this.typeNumber*4+17;this.modules=new Array(this.moduleCount);for(var row=0;row=7){this.setupTypeNumber(test);} + if(this.dataCache==null){this.dataCache=QRCodeModel.createData(this.typeNumber,this.errorCorrectLevel,this.dataList);} + this.mapData(this.dataCache,maskPattern);},setupPositionProbePattern:function(row,col){for(var r=-1;r<=7;r++){if(row+r<=-1||this.moduleCount<=row+r)continue;for(var c=-1;c<=7;c++){if(col+c<=-1||this.moduleCount<=col+c)continue;if((0<=r&&r<=6&&(c==0||c==6))||(0<=c&&c<=6&&(r==0||r==6))||(2<=r&&r<=4&&2<=c&&c<=4)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}},getBestMaskPattern:function(){var minLostPoint=0;var pattern=0;for(var i=0;i<8;i++){this.makeImpl(true,i);var lostPoint=QRUtil.getLostPoint(this);if(i==0||minLostPoint>lostPoint){minLostPoint=lostPoint;pattern=i;}} + return pattern;},createMovieClip:function(target_mc,instance_name,depth){var qr_mc=target_mc.createEmptyMovieClip(instance_name,depth);var cs=1;this.make();for(var row=0;row>i)&1)==1);this.modules[Math.floor(i/3)][i%3+this.moduleCount-8-3]=mod;} + for(var i=0;i<18;i++){var mod=(!test&&((bits>>i)&1)==1);this.modules[i%3+this.moduleCount-8-3][Math.floor(i/3)]=mod;}},setupTypeInfo:function(test,maskPattern){var data=(this.errorCorrectLevel<<3)|maskPattern;var bits=QRUtil.getBCHTypeInfo(data);for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<6){this.modules[i][8]=mod;}else if(i<8){this.modules[i+1][8]=mod;}else{this.modules[this.moduleCount-15+i][8]=mod;}} + for(var i=0;i<15;i++){var mod=(!test&&((bits>>i)&1)==1);if(i<8){this.modules[8][this.moduleCount-i-1]=mod;}else if(i<9){this.modules[8][15-i-1+1]=mod;}else{this.modules[8][15-i-1]=mod;}} + this.modules[this.moduleCount-8][8]=(!test);},mapData:function(data,maskPattern){var inc=-1;var row=this.moduleCount-1;var bitIndex=7;var byteIndex=0;for(var col=this.moduleCount-1;col>0;col-=2){if(col==6)col--;while(true){for(var c=0;c<2;c++){if(this.modules[row][col-c]==null){var dark=false;if(byteIndex>>bitIndex)&1)==1);} + var mask=QRUtil.getMask(maskPattern,row,col-c);if(mask){dark=!dark;} + this.modules[row][col-c]=dark;bitIndex--;if(bitIndex==-1){byteIndex++;bitIndex=7;}}} + row+=inc;if(row<0||this.moduleCount<=row){row-=inc;inc=-inc;break;}}}}};QRCodeModel.PAD0=0xEC;QRCodeModel.PAD1=0x11;QRCodeModel.createData=function(typeNumber,errorCorrectLevel,dataList){var rsBlocks=QRRSBlock.getRSBlocks(typeNumber,errorCorrectLevel);var buffer=new QRBitBuffer();for(var i=0;itotalDataCount*8){throw new Error("code length overflow. (" + +buffer.getLengthInBits() + +">" + +totalDataCount*8 + +")");} + if(buffer.getLengthInBits()+4<=totalDataCount*8){buffer.put(0,4);} + while(buffer.getLengthInBits()%8!=0){buffer.putBit(false);} + while(true){if(buffer.getLengthInBits()>=totalDataCount*8){break;} + buffer.put(QRCodeModel.PAD0,8);if(buffer.getLengthInBits()>=totalDataCount*8){break;} + buffer.put(QRCodeModel.PAD1,8);} + return QRCodeModel.createBytes(buffer,rsBlocks);};QRCodeModel.createBytes=function(buffer,rsBlocks){var offset=0;var maxDcCount=0;var maxEcCount=0;var dcdata=new Array(rsBlocks.length);var ecdata=new Array(rsBlocks.length);for(var r=0;r=0)?modPoly.get(modIndex):0;}} + var totalCodeCount=0;for(var i=0;i=0){d^=(QRUtil.G15<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)));} + return((data<<10)|d)^QRUtil.G15_MASK;},getBCHTypeNumber:function(data){var d=data<<12;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)>=0){d^=(QRUtil.G18<<(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G18)));} + return(data<<12)|d;},getBCHDigit:function(data){var digit=0;while(data!=0){digit++;data>>>=1;} + return digit;},getPatternPosition:function(typeNumber){return QRUtil.PATTERN_POSITION_TABLE[typeNumber-1];},getMask:function(maskPattern,i,j){switch(maskPattern){case QRMaskPattern.PATTERN000:return(i+j)%2==0;case QRMaskPattern.PATTERN001:return i%2==0;case QRMaskPattern.PATTERN010:return j%3==0;case QRMaskPattern.PATTERN011:return(i+j)%3==0;case QRMaskPattern.PATTERN100:return(Math.floor(i/2)+Math.floor(j/3))%2==0;case QRMaskPattern.PATTERN101:return(i*j)%2+(i*j)%3==0;case QRMaskPattern.PATTERN110:return((i*j)%2+(i*j)%3)%2==0;case QRMaskPattern.PATTERN111:return((i*j)%3+(i+j)%2)%2==0;default:throw new Error("bad maskPattern:"+maskPattern);}},getErrorCorrectPolynomial:function(errorCorrectLength){var a=new QRPolynomial([1],0);for(var i=0;i5){lostPoint+=(3+sameCount-5);}}} + for(var row=0;row=256){n-=255;} + return QRMath.EXP_TABLE[n];},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)};for(var i=0;i<8;i++){QRMath.EXP_TABLE[i]=1<>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i>>(length-i-1))&1)==1);}},getLengthInBits:function(){return this.length;},putBit:function(bit){var bufIndex=Math.floor(this.length/8);if(this.buffer.length<=bufIndex){this.buffer.push(0);} + if(bit){this.buffer[bufIndex]|=(0x80>>>(this.length%8));} + this.length++;}};var QRCodeLimitLength=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]]; + + function _isSupportCanvas() { + return typeof CanvasRenderingContext2D != "undefined"; + } + + // android 2.x doesn't support Data-URI spec + function _getAndroid() { + var android = false; + var sAgent = navigator.userAgent; + + if (/android/i.test(sAgent)) { // android + android = true; + aMat = sAgent.toString().match(/android ([0-9]\.[0-9])/i); + + if (aMat && aMat[1]) { + android = parseFloat(aMat[1]); + } + } + + return android; + } + + var svgDrawer = (function() { + + var Drawing = function (el, htOption) { + this._el = el; + this._htOption = htOption; + }; + + Drawing.prototype.draw = function (oQRCode) { + var _htOption = this._htOption; + var _el = this._el; + var nCount = oQRCode.getModuleCount(); + var nWidth = Math.floor(_htOption.width / nCount); + var nHeight = Math.floor(_htOption.height / nCount); + + this.clear(); + + function makeSVG(tag, attrs) { + var el = document.createElementNS('http://www.w3.org/2000/svg', tag); + for (var k in attrs) + if (attrs.hasOwnProperty(k)) el.setAttribute(k, attrs[k]); + return el; + } + + var svg = makeSVG("svg" , {'viewBox': '0 0 ' + String(nCount) + " " + String(nCount), 'width': '100%', 'height': '100%', 'fill': _htOption.colorLight}); + svg.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xlink", "http://www.w3.org/1999/xlink"); + _el.appendChild(svg); + + svg.appendChild(makeSVG("rect", {"fill": _htOption.colorDark, "width": "1", "height": "1", "id": "template"})); + + for (var row = 0; row < nCount; row++) { + for (var col = 0; col < nCount; col++) { + if (oQRCode.isDark(row, col)) { + var child = makeSVG("use", {"x": String(row), "y": String(col)}); + child.setAttributeNS("http://www.w3.org/1999/xlink", "href", "#template") + svg.appendChild(child); + } + } + } + }; + Drawing.prototype.clear = function () { + while (this._el.hasChildNodes()) + this._el.removeChild(this._el.lastChild); + }; + return Drawing; + })(); + + var useSVG = document.documentElement.tagName.toLowerCase() === "svg"; + + // Drawing in DOM by using Table tag + var Drawing = useSVG ? svgDrawer : !_isSupportCanvas() ? (function () { + var Drawing = function (el, htOption) { + this._el = el; + this._htOption = htOption; + }; + + /** + * Draw the QRCode + * + * @param {QRCode} oQRCode + */ + Drawing.prototype.draw = function (oQRCode) { + var _htOption = this._htOption; + var _el = this._el; + var nCount = oQRCode.getModuleCount(); + var nWidth = Math.floor(_htOption.width / nCount); + var nHeight = Math.floor(_htOption.height / nCount); + var aHTML = ['
']; + + for (var row = 0; row < nCount; row++) { + aHTML.push(''); + + for (var col = 0; col < nCount; col++) { + aHTML.push(''); + } + + aHTML.push(''); + } + + aHTML.push('
'); + _el.innerHTML = aHTML.join(''); + + // Fix the margin values as real size. + var elTable = _el.childNodes[0]; + var nLeftMarginTable = (_htOption.width - elTable.offsetWidth) / 2; + var nTopMarginTable = (_htOption.height - elTable.offsetHeight) / 2; + + if (nLeftMarginTable > 0 && nTopMarginTable > 0) { + elTable.style.margin = nTopMarginTable + "px " + nLeftMarginTable + "px"; + } + }; + + /** + * Clear the QRCode + */ + Drawing.prototype.clear = function () { + this._el.innerHTML = ''; + }; + + return Drawing; + })() : (function () { // Drawing in Canvas + function _onMakeImage() { + this._elImage.src = this._elCanvas.toDataURL("image/png"); + this._elImage.style.display = "block"; + this._elCanvas.style.display = "none"; + } + + // Android 2.1 bug workaround + // http://code.google.com/p/android/issues/detail?id=5141 + if (this._android && this._android <= 2.1) { + var factor = 1 / window.devicePixelRatio; + var drawImage = CanvasRenderingContext2D.prototype.drawImage; + CanvasRenderingContext2D.prototype.drawImage = function (image, sx, sy, sw, sh, dx, dy, dw, dh) { + if (("nodeName" in image) && /img/i.test(image.nodeName)) { + for (var i = arguments.length - 1; i >= 1; i--) { + arguments[i] = arguments[i] * factor; + } + } else if (typeof dw == "undefined") { + arguments[1] *= factor; + arguments[2] *= factor; + arguments[3] *= factor; + arguments[4] *= factor; + } + + drawImage.apply(this, arguments); + }; + } + + /** + * Check whether the user's browser supports Data URI or not + * + * @private + * @param {Function} fSuccess Occurs if it supports Data URI + * @param {Function} fFail Occurs if it doesn't support Data URI + */ + function _safeSetDataURI(fSuccess, fFail) { + var self = this; + self._fFail = fFail; + self._fSuccess = fSuccess; + + // Check it just once + if (self._bSupportDataURI === null) { + var el = document.createElement("img"); + var fOnError = function() { + self._bSupportDataURI = false; + + if (self._fFail) { + _fFail.call(self); + } + }; + var fOnSuccess = function() { + self._bSupportDataURI = true; + + if (self._fSuccess) { + self._fSuccess.call(self); + } + }; + + el.onabort = fOnError; + el.onerror = fOnError; + el.onload = fOnSuccess; + el.src = "data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=="; // the Image contains 1px data. + return; + } else if (self._bSupportDataURI === true && self._fSuccess) { + self._fSuccess.call(self); + } else if (self._bSupportDataURI === false && self._fFail) { + self._fFail.call(self); + } + }; + + /** + * Drawing QRCode by using canvas + * + * @constructor + * @param {HTMLElement} el + * @param {Object} htOption QRCode Options + */ + var Drawing = function (el, htOption) { + this._bIsPainted = false; + this._android = _getAndroid(); + + this._htOption = htOption; + this._elCanvas = document.createElement("canvas"); + this._elCanvas.width = htOption.width; + this._elCanvas.height = htOption.height; + el.appendChild(this._elCanvas); + this._el = el; + this._oContext = this._elCanvas.getContext("2d"); + this._bIsPainted = false; + this._elImage = document.createElement("img"); + this._elImage.alt = "Scan me!"; + this._elImage.style.display = "none"; + this._el.appendChild(this._elImage); + this._bSupportDataURI = null; + }; + + /** + * Draw the QRCode + * + * @param {QRCode} oQRCode + */ + Drawing.prototype.draw = function (oQRCode) { + var _elImage = this._elImage; + var _oContext = this._oContext; + var _htOption = this._htOption; + + var nCount = oQRCode.getModuleCount(); + var nWidth = _htOption.width / nCount; + var nHeight = _htOption.height / nCount; + var nRoundedWidth = Math.round(nWidth); + var nRoundedHeight = Math.round(nHeight); + + _elImage.style.display = "none"; + this.clear(); + + for (var row = 0; row < nCount; row++) { + for (var col = 0; col < nCount; col++) { + var bIsDark = oQRCode.isDark(row, col); + var nLeft = col * nWidth; + var nTop = row * nHeight; + _oContext.strokeStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight; + _oContext.lineWidth = 1; + _oContext.fillStyle = bIsDark ? _htOption.colorDark : _htOption.colorLight; + _oContext.fillRect(nLeft, nTop, nWidth, nHeight); + + // 안티 앨리어싱 방지 처리 + _oContext.strokeRect( + Math.floor(nLeft) + 0.5, + Math.floor(nTop) + 0.5, + nRoundedWidth, + nRoundedHeight + ); + + _oContext.strokeRect( + Math.ceil(nLeft) - 0.5, + Math.ceil(nTop) - 0.5, + nRoundedWidth, + nRoundedHeight + ); + } + } + + this._bIsPainted = true; + }; + + /** + * Make the image from Canvas if the browser supports Data URI. + */ + Drawing.prototype.makeImage = function () { + if (this._bIsPainted) { + _safeSetDataURI.call(this, _onMakeImage); + } + }; + + /** + * Return whether the QRCode is painted or not + * + * @return {Boolean} + */ + Drawing.prototype.isPainted = function () { + return this._bIsPainted; + }; + + /** + * Clear the QRCode + */ + Drawing.prototype.clear = function () { + this._oContext.clearRect(0, 0, this._elCanvas.width, this._elCanvas.height); + this._bIsPainted = false; + }; + + /** + * @private + * @param {Number} nNumber + */ + Drawing.prototype.round = function (nNumber) { + if (!nNumber) { + return nNumber; + } + + return Math.floor(nNumber * 1000) / 1000; + }; + + return Drawing; + })(); + + /** + * Get the type by string length + * + * @private + * @param {String} sText + * @param {Number} nCorrectLevel + * @return {Number} type + */ + function _getTypeNumber(sText, nCorrectLevel) { + var nType = 1; + var length = _getUTF8Length(sText); + + for (var i = 0, len = QRCodeLimitLength.length; i <= len; i++) { + var nLimit = 0; + + switch (nCorrectLevel) { + case QRErrorCorrectLevel.L : + nLimit = QRCodeLimitLength[i][0]; + break; + case QRErrorCorrectLevel.M : + nLimit = QRCodeLimitLength[i][1]; + break; + case QRErrorCorrectLevel.Q : + nLimit = QRCodeLimitLength[i][2]; + break; + case QRErrorCorrectLevel.H : + nLimit = QRCodeLimitLength[i][3]; + break; + } + + if (length <= nLimit) { + break; + } else { + nType++; + } + } + + if (nType > QRCodeLimitLength.length) { + throw new Error("Too long data"); + } + + return nType; + } + + function _getUTF8Length(sText) { + var replacedText = encodeURI(sText).toString().replace(/\%[0-9a-fA-F]{2}/g, 'a'); + return replacedText.length + (replacedText.length != sText ? 3 : 0); + } + + /** + * @class QRCode + * @constructor + * @example + * new QRCode(document.getElementById("test"), "http://jindo.dev.naver.com/collie"); + * + * @example + * var oQRCode = new QRCode("test", { + * text : "http://naver.com", + * width : 128, + * height : 128 + * }); + * + * oQRCode.clear(); // Clear the QRCode. + * oQRCode.makeCode("http://map.naver.com"); // Re-create the QRCode. + * + * @param {HTMLElement|String} el target element or 'id' attribute of element. + * @param {Object|String} vOption + * @param {String} vOption.text QRCode link data + * @param {Number} [vOption.width=256] + * @param {Number} [vOption.height=256] + * @param {String} [vOption.colorDark="#000000"] + * @param {String} [vOption.colorLight="#ffffff"] + * @param {QRCode.CorrectLevel} [vOption.correctLevel=QRCode.CorrectLevel.H] [L|M|Q|H] + */ + QRCode = function (el, vOption) { + this._htOption = { + width : 256, + height : 256, + typeNumber : 4, + colorDark : "#000000", + colorLight : "#ffffff", + correctLevel : QRErrorCorrectLevel.H + }; + + if (typeof vOption === 'string') { + vOption = { + text : vOption + }; + } + + // Overwrites options + if (vOption) { + for (var i in vOption) { + this._htOption[i] = vOption[i]; + } + } + + if (typeof el == "string") { + el = document.getElementById(el); + } + + this._android = _getAndroid(); + this._el = el; + this._oQRCode = null; + this._oDrawing = new Drawing(this._el, this._htOption); + + if (this._htOption.text) { + this.makeCode(this._htOption.text); + } + }; + + /** + * Make the QRCode + * + * @param {String} sText link data + */ + QRCode.prototype.makeCode = function (sText) { + this._oQRCode = new QRCodeModel(_getTypeNumber(sText, this._htOption.correctLevel), this._htOption.correctLevel); + this._oQRCode.addData(sText); + this._oQRCode.make(); + this._el.title = sText; + this._oDrawing.draw(this._oQRCode); + this.makeImage(); + }; + + /** + * Make the Image from Canvas element + * - It occurs automatically + * - Android below 3 doesn't support Data-URI spec. + * + * @private + */ + QRCode.prototype.makeImage = function () { + if (typeof this._oDrawing.makeImage == "function" && (!this._android || this._android >= 3)) { + this._oDrawing.makeImage(); + } + }; + + /** + * Clear the QRCode + */ + QRCode.prototype.clear = function () { + this._oDrawing.clear(); + }; + + /** + * @name QRCode.CorrectLevel + */ + QRCode.CorrectLevel = QRErrorCorrectLevel; +})(); diff --git a/lamassu-admin-elm/build/bower_components/qrcodejs/qrcode.min.js b/lamassu-admin-elm/build/bower_components/qrcodejs/qrcode.min.js new file mode 100644 index 00000000..993e88f3 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qrcodejs/qrcode.min.js @@ -0,0 +1 @@ +var QRCode;!function(){function a(a){this.mode=c.MODE_8BIT_BYTE,this.data=a,this.parsedData=[];for(var b=[],d=0,e=this.data.length;e>d;d++){var f=this.data.charCodeAt(d);f>65536?(b[0]=240|(1835008&f)>>>18,b[1]=128|(258048&f)>>>12,b[2]=128|(4032&f)>>>6,b[3]=128|63&f):f>2048?(b[0]=224|(61440&f)>>>12,b[1]=128|(4032&f)>>>6,b[2]=128|63&f):f>128?(b[0]=192|(1984&f)>>>6,b[1]=128|63&f):b[0]=f,this.parsedData=this.parsedData.concat(b)}this.parsedData.length!=this.data.length&&(this.parsedData.unshift(191),this.parsedData.unshift(187),this.parsedData.unshift(239))}function b(a,b){this.typeNumber=a,this.errorCorrectLevel=b,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=[]}function i(a,b){if(void 0==a.length)throw new Error(a.length+"/"+b);for(var c=0;c=f;f++){var h=0;switch(b){case d.L:h=l[f][0];break;case d.M:h=l[f][1];break;case d.Q:h=l[f][2];break;case d.H:h=l[f][3]}if(h>=e)break;c++}if(c>l.length)throw new Error("Too long data");return c}function s(a){var b=encodeURI(a).toString().replace(/\%[0-9a-fA-F]{2}/g,"a");return b.length+(b.length!=a?3:0)}a.prototype={getLength:function(){return this.parsedData.length},write:function(a){for(var b=0,c=this.parsedData.length;c>b;b++)a.put(this.parsedData[b],8)}},b.prototype={addData:function(b){var c=new a(b);this.dataList.push(c),this.dataCache=null},isDark:function(a,b){if(0>a||this.moduleCount<=a||0>b||this.moduleCount<=b)throw new Error(a+","+b);return this.modules[a][b]},getModuleCount:function(){return this.moduleCount},make:function(){this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var d=0;d=7&&this.setupTypeNumber(a),null==this.dataCache&&(this.dataCache=b.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,b){for(var c=-1;7>=c;c++)if(!(-1>=a+c||this.moduleCount<=a+c))for(var d=-1;7>=d;d++)-1>=b+d||this.moduleCount<=b+d||(this.modules[a+c][b+d]=c>=0&&6>=c&&(0==d||6==d)||d>=0&&6>=d&&(0==c||6==c)||c>=2&&4>=c&&d>=2&&4>=d?!0:!1)},getBestMaskPattern:function(){for(var a=0,b=0,c=0;8>c;c++){this.makeImpl(!0,c);var d=f.getLostPoint(this);(0==c||a>d)&&(a=d,b=c)}return b},createMovieClip:function(a,b,c){var d=a.createEmptyMovieClip(b,c),e=1;this.make();for(var f=0;f=g;g++)for(var h=-2;2>=h;h++)this.modules[d+g][e+h]=-2==g||2==g||-2==h||2==h||0==g&&0==h?!0:!1}},setupTypeNumber:function(a){for(var b=f.getBCHTypeNumber(this.typeNumber),c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[Math.floor(c/3)][c%3+this.moduleCount-8-3]=d}for(var c=0;18>c;c++){var d=!a&&1==(1&b>>c);this.modules[c%3+this.moduleCount-8-3][Math.floor(c/3)]=d}},setupTypeInfo:function(a,b){for(var c=this.errorCorrectLevel<<3|b,d=f.getBCHTypeInfo(c),e=0;15>e;e++){var g=!a&&1==(1&d>>e);6>e?this.modules[e][8]=g:8>e?this.modules[e+1][8]=g:this.modules[this.moduleCount-15+e][8]=g}for(var e=0;15>e;e++){var g=!a&&1==(1&d>>e);8>e?this.modules[8][this.moduleCount-e-1]=g:9>e?this.modules[8][15-e-1+1]=g:this.modules[8][15-e-1]=g}this.modules[this.moduleCount-8][8]=!a},mapData:function(a,b){for(var c=-1,d=this.moduleCount-1,e=7,g=0,h=this.moduleCount-1;h>0;h-=2)for(6==h&&h--;;){for(var i=0;2>i;i++)if(null==this.modules[d][h-i]){var j=!1;g>>e));var k=f.getMask(b,d,h-i);k&&(j=!j),this.modules[d][h-i]=j,e--,-1==e&&(g++,e=7)}if(d+=c,0>d||this.moduleCount<=d){d-=c,c=-c;break}}}},b.PAD0=236,b.PAD1=17,b.createData=function(a,c,d){for(var e=j.getRSBlocks(a,c),g=new k,h=0;h8*l)throw new Error("code length overflow. ("+g.getLengthInBits()+">"+8*l+")");for(g.getLengthInBits()+4<=8*l&&g.put(0,4);0!=g.getLengthInBits()%8;)g.putBit(!1);for(;;){if(g.getLengthInBits()>=8*l)break;if(g.put(b.PAD0,8),g.getLengthInBits()>=8*l)break;g.put(b.PAD1,8)}return b.createBytes(g,e)},b.createBytes=function(a,b){for(var c=0,d=0,e=0,g=new Array(b.length),h=new Array(b.length),j=0;j=0?p.get(q):0}}for(var r=0,m=0;mm;m++)for(var j=0;jm;m++)for(var j=0;j=0;)b^=f.G15<=0;)b^=f.G18<>>=1;return b},getPatternPosition:function(a){return f.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,b,c){switch(a){case e.PATTERN000:return 0==(b+c)%2;case e.PATTERN001:return 0==b%2;case e.PATTERN010:return 0==c%3;case e.PATTERN011:return 0==(b+c)%3;case e.PATTERN100:return 0==(Math.floor(b/2)+Math.floor(c/3))%2;case e.PATTERN101:return 0==b*c%2+b*c%3;case e.PATTERN110:return 0==(b*c%2+b*c%3)%2;case e.PATTERN111:return 0==(b*c%3+(b+c)%2)%2;default:throw new Error("bad maskPattern:"+a)}},getErrorCorrectPolynomial:function(a){for(var b=new i([1],0),c=0;a>c;c++)b=b.multiply(new i([1,g.gexp(c)],0));return b},getLengthInBits:function(a,b){if(b>=1&&10>b)switch(a){case c.MODE_NUMBER:return 10;case c.MODE_ALPHA_NUM:return 9;case c.MODE_8BIT_BYTE:return 8;case c.MODE_KANJI:return 8;default:throw new Error("mode:"+a)}else if(27>b)switch(a){case c.MODE_NUMBER:return 12;case c.MODE_ALPHA_NUM:return 11;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 10;default:throw new Error("mode:"+a)}else{if(!(41>b))throw new Error("type:"+b);switch(a){case c.MODE_NUMBER:return 14;case c.MODE_ALPHA_NUM:return 13;case c.MODE_8BIT_BYTE:return 16;case c.MODE_KANJI:return 12;default:throw new Error("mode:"+a)}}},getLostPoint:function(a){for(var b=a.getModuleCount(),c=0,d=0;b>d;d++)for(var e=0;b>e;e++){for(var f=0,g=a.isDark(d,e),h=-1;1>=h;h++)if(!(0>d+h||d+h>=b))for(var i=-1;1>=i;i++)0>e+i||e+i>=b||(0!=h||0!=i)&&g==a.isDark(d+h,e+i)&&f++;f>5&&(c+=3+f-5)}for(var d=0;b-1>d;d++)for(var e=0;b-1>e;e++){var j=0;a.isDark(d,e)&&j++,a.isDark(d+1,e)&&j++,a.isDark(d,e+1)&&j++,a.isDark(d+1,e+1)&&j++,(0==j||4==j)&&(c+=3)}for(var d=0;b>d;d++)for(var e=0;b-6>e;e++)a.isDark(d,e)&&!a.isDark(d,e+1)&&a.isDark(d,e+2)&&a.isDark(d,e+3)&&a.isDark(d,e+4)&&!a.isDark(d,e+5)&&a.isDark(d,e+6)&&(c+=40);for(var e=0;b>e;e++)for(var d=0;b-6>d;d++)a.isDark(d,e)&&!a.isDark(d+1,e)&&a.isDark(d+2,e)&&a.isDark(d+3,e)&&a.isDark(d+4,e)&&!a.isDark(d+5,e)&&a.isDark(d+6,e)&&(c+=40);for(var k=0,e=0;b>e;e++)for(var d=0;b>d;d++)a.isDark(d,e)&&k++;var l=Math.abs(100*k/b/b-50)/5;return c+=10*l}},g={glog:function(a){if(1>a)throw new Error("glog("+a+")");return g.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;a>=256;)a-=255;return g.EXP_TABLE[a]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},h=0;8>h;h++)g.EXP_TABLE[h]=1<h;h++)g.EXP_TABLE[h]=g.EXP_TABLE[h-4]^g.EXP_TABLE[h-5]^g.EXP_TABLE[h-6]^g.EXP_TABLE[h-8];for(var h=0;255>h;h++)g.LOG_TABLE[g.EXP_TABLE[h]]=h;i.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var b=new Array(this.getLength()+a.getLength()-1),c=0;cf;f++)for(var g=c[3*f+0],h=c[3*f+1],i=c[3*f+2],k=0;g>k;k++)e.push(new j(h,i));return e},j.getRsBlockTable=function(a,b){switch(b){case d.L:return j.RS_BLOCK_TABLE[4*(a-1)+0];case d.M:return j.RS_BLOCK_TABLE[4*(a-1)+1];case d.Q:return j.RS_BLOCK_TABLE[4*(a-1)+2];case d.H:return j.RS_BLOCK_TABLE[4*(a-1)+3];default:return void 0}},k.prototype={get:function(a){var b=Math.floor(a/8);return 1==(1&this.buffer[b]>>>7-a%8)},put:function(a,b){for(var c=0;b>c;c++)this.putBit(1==(1&a>>>b-c-1))},getLengthInBits:function(){return this.length},putBit:function(a){var b=Math.floor(this.length/8);this.buffer.length<=b&&this.buffer.push(0),a&&(this.buffer[b]|=128>>>this.length%8),this.length++}};var l=[[17,14,11,7],[32,26,20,14],[53,42,32,24],[78,62,46,34],[106,84,60,44],[134,106,74,58],[154,122,86,64],[192,152,108,84],[230,180,130,98],[271,213,151,119],[321,251,177,137],[367,287,203,155],[425,331,241,177],[458,362,258,194],[520,412,292,220],[586,450,322,250],[644,504,364,280],[718,560,394,310],[792,624,442,338],[858,666,482,382],[929,711,509,403],[1003,779,565,439],[1091,857,611,461],[1171,911,661,511],[1273,997,715,535],[1367,1059,751,593],[1465,1125,805,625],[1528,1190,868,658],[1628,1264,908,698],[1732,1370,982,742],[1840,1452,1030,790],[1952,1538,1112,842],[2068,1628,1168,898],[2188,1722,1228,958],[2303,1809,1283,983],[2431,1911,1351,1051],[2563,1989,1423,1093],[2699,2099,1499,1139],[2809,2213,1579,1219],[2953,2331,1663,1273]],o=function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){function g(a,b){var c=document.createElementNS("http://www.w3.org/2000/svg",a);for(var d in b)b.hasOwnProperty(d)&&c.setAttribute(d,b[d]);return c}var b=this._htOption,c=this._el,d=a.getModuleCount();Math.floor(b.width/d),Math.floor(b.height/d),this.clear();var h=g("svg",{viewBox:"0 0 "+String(d)+" "+String(d),width:"100%",height:"100%",fill:b.colorLight});h.setAttributeNS("http://www.w3.org/2000/xmlns/","xmlns:xlink","http://www.w3.org/1999/xlink"),c.appendChild(h),h.appendChild(g("rect",{fill:b.colorDark,width:"1",height:"1",id:"template"}));for(var i=0;d>i;i++)for(var j=0;d>j;j++)if(a.isDark(i,j)){var k=g("use",{x:String(i),y:String(j)});k.setAttributeNS("http://www.w3.org/1999/xlink","href","#template"),h.appendChild(k)}},a.prototype.clear=function(){for(;this._el.hasChildNodes();)this._el.removeChild(this._el.lastChild)},a}(),p="svg"===document.documentElement.tagName.toLowerCase(),q=p?o:m()?function(){function a(){this._elImage.src=this._elCanvas.toDataURL("image/png"),this._elImage.style.display="block",this._elCanvas.style.display="none"}function d(a,b){var c=this;if(c._fFail=b,c._fSuccess=a,null===c._bSupportDataURI){var d=document.createElement("img"),e=function(){c._bSupportDataURI=!1,c._fFail&&_fFail.call(c)},f=function(){c._bSupportDataURI=!0,c._fSuccess&&c._fSuccess.call(c)};return d.onabort=e,d.onerror=e,d.onload=f,d.src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==",void 0}c._bSupportDataURI===!0&&c._fSuccess?c._fSuccess.call(c):c._bSupportDataURI===!1&&c._fFail&&c._fFail.call(c)}if(this._android&&this._android<=2.1){var b=1/window.devicePixelRatio,c=CanvasRenderingContext2D.prototype.drawImage;CanvasRenderingContext2D.prototype.drawImage=function(a,d,e,f,g,h,i,j){if("nodeName"in a&&/img/i.test(a.nodeName))for(var l=arguments.length-1;l>=1;l--)arguments[l]=arguments[l]*b;else"undefined"==typeof j&&(arguments[1]*=b,arguments[2]*=b,arguments[3]*=b,arguments[4]*=b);c.apply(this,arguments)}}var e=function(a,b){this._bIsPainted=!1,this._android=n(),this._htOption=b,this._elCanvas=document.createElement("canvas"),this._elCanvas.width=b.width,this._elCanvas.height=b.height,a.appendChild(this._elCanvas),this._el=a,this._oContext=this._elCanvas.getContext("2d"),this._bIsPainted=!1,this._elImage=document.createElement("img"),this._elImage.style.display="none",this._el.appendChild(this._elImage),this._bSupportDataURI=null};return e.prototype.draw=function(a){var b=this._elImage,c=this._oContext,d=this._htOption,e=a.getModuleCount(),f=d.width/e,g=d.height/e,h=Math.round(f),i=Math.round(g);b.style.display="none",this.clear();for(var j=0;e>j;j++)for(var k=0;e>k;k++){var l=a.isDark(j,k),m=k*f,n=j*g;c.strokeStyle=l?d.colorDark:d.colorLight,c.lineWidth=1,c.fillStyle=l?d.colorDark:d.colorLight,c.fillRect(m,n,f,g),c.strokeRect(Math.floor(m)+.5,Math.floor(n)+.5,h,i),c.strokeRect(Math.ceil(m)-.5,Math.ceil(n)-.5,h,i)}this._bIsPainted=!0},e.prototype.makeImage=function(){this._bIsPainted&&d.call(this,a)},e.prototype.isPainted=function(){return this._bIsPainted},e.prototype.clear=function(){this._oContext.clearRect(0,0,this._elCanvas.width,this._elCanvas.height),this._bIsPainted=!1},e.prototype.round=function(a){return a?Math.floor(1e3*a)/1e3:a},e}():function(){var a=function(a,b){this._el=a,this._htOption=b};return a.prototype.draw=function(a){for(var b=this._htOption,c=this._el,d=a.getModuleCount(),e=Math.floor(b.width/d),f=Math.floor(b.height/d),g=[''],h=0;d>h;h++){g.push("");for(var i=0;d>i;i++)g.push('');g.push("")}g.push("
"),c.innerHTML=g.join("");var j=c.childNodes[0],k=(b.width-j.offsetWidth)/2,l=(b.height-j.offsetHeight)/2;k>0&&l>0&&(j.style.margin=l+"px "+k+"px")},a.prototype.clear=function(){this._el.innerHTML=""},a}();QRCode=function(a,b){if(this._htOption={width:256,height:256,typeNumber:4,colorDark:"#000000",colorLight:"#ffffff",correctLevel:d.H},"string"==typeof b&&(b={text:b}),b)for(var c in b)this._htOption[c]=b[c];"string"==typeof a&&(a=document.getElementById(a)),this._android=n(),this._el=a,this._oQRCode=null,this._oDrawing=new q(this._el,this._htOption),this._htOption.text&&this.makeCode(this._htOption.text)},QRCode.prototype.makeCode=function(a){this._oQRCode=new b(r(a,this._htOption.correctLevel),this._htOption.correctLevel),this._oQRCode.addData(a),this._oQRCode.make(),this._el.title=a,this._oDrawing.draw(this._oQRCode),this.makeImage()},QRCode.prototype.makeImage=function(){"function"==typeof this._oDrawing.makeImage&&(!this._android||this._android>=3)&&this._oDrawing.makeImage()},QRCode.prototype.clear=function(){this._oDrawing.clear()},QRCode.CorrectLevel=d}(); \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/qrious/.bower.json b/lamassu-admin-elm/build/bower_components/qrious/.bower.json new file mode 100644 index 00000000..3891e55c --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qrious/.bower.json @@ -0,0 +1,47 @@ +{ + "name": "qrious", + "version": "2.0.2", + "description": "Library for QR code generation using canvas", + "homepage": "https://github.com/neocotic/qrious", + "authors": [ + { + "name": "Alasdair Mercer", + "email": "mercer.alasdair@gmail.com", + "homepage": "http://neocotic.com" + } + ], + "license": "GPL-3.0", + "keywords": [ + "qr", + "code", + "encode", + "canvas", + "image" + ], + "repository": { + "type": "git", + "url": "https://github.com/neocotic/qrious.git" + }, + "main": "dist/umd/qrious.js", + "ignore": [ + "src/", + ".*", + "AUTHORS.md", + "CHANGES.md", + "CONTRIBUTING.md", + "demo.html", + "Gruntfile.js", + "package.json", + "README.md" + ], + "_release": "2.0.2", + "_resolution": { + "type": "version", + "tag": "2.0.2", + "commit": "1ffd092e97ab3ba212fad21ab865eb1754155296" + }, + "_source": "https://github.com/neocotic/qrious.git", + "_target": "^2.0.2", + "_originalSource": "qrious", + "_direct": true +} \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/qrious/LICENSE.md b/lamassu-admin-elm/build/bower_components/qrious/LICENSE.md new file mode 100644 index 00000000..7ac38638 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qrious/LICENSE.md @@ -0,0 +1,16 @@ +QRious +Copyright (C) 2016 Alasdair Mercer +Copyright (C) 2010 Tom Zerucha + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . diff --git a/lamassu-admin-elm/build/bower_components/qrious/bower.json b/lamassu-admin-elm/build/bower_components/qrious/bower.json new file mode 100644 index 00000000..25a23fb3 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qrious/bower.json @@ -0,0 +1,37 @@ +{ + "name": "qrious", + "version": "2.0.2", + "description": "Library for QR code generation using canvas", + "homepage": "https://github.com/neocotic/qrious", + "authors": [ + { + "name": "Alasdair Mercer", + "email": "mercer.alasdair@gmail.com", + "homepage": "http://neocotic.com" + } + ], + "license": "GPL-3.0", + "keywords": [ + "qr", + "code", + "encode", + "canvas", + "image" + ], + "repository": { + "type": "git", + "url": "https://github.com/neocotic/qrious.git" + }, + "main": "dist/umd/qrious.js", + "ignore": [ + "src/", + ".*", + "AUTHORS.md", + "CHANGES.md", + "CONTRIBUTING.md", + "demo.html", + "Gruntfile.js", + "package.json", + "README.md" + ] +} diff --git a/lamassu-admin-elm/build/bower_components/qrjs/.bower.json b/lamassu-admin-elm/build/bower_components/qrjs/.bower.json new file mode 100644 index 00000000..a754ab2d --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qrjs/.bower.json @@ -0,0 +1,36 @@ +{ + "name": "qrjs", + "main": "qr.js", + "version": "0.1.2", + "homepage": "https://github.com/educastellano/qr.js", + "authors": [ + "Kang Seonghoon ", + "Eduard Castellano " + ], + "description": "QR code generator in Javascript", + "moduleType": [ + "globals" + ], + "keywords": [ + "qr", + "qr.js", + "qrcode" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ], + "_release": "0.1.2", + "_resolution": { + "type": "version", + "tag": "v0.1.2", + "commit": "dbfa732cb309195a51a656b021f309d354154e04" + }, + "_source": "https://github.com/educastellano/qr.js.git", + "_target": "~0.1.2", + "_originalSource": "qrjs" +} \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/qrjs/README.md b/lamassu-admin-elm/build/bower_components/qrjs/README.md new file mode 100644 index 00000000..549a9b24 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qrjs/README.md @@ -0,0 +1,8 @@ +# qr.js: QR code generator in pure Javascript (2011) + +This is a fairly standalone script for producing QR code on the fly. +Originally developed for my own interest, the code is fully commented and well-structured. +The code is in the public domain (or to be exact, [Creative Commons Zero](https://creativecommons.org/publicdomain/zero/1.0/)), +and you can use it for absolutely any purpose. + +See also a [node.js module based on qr.js](https://github.com/shesek/qruri), packaged by Nadav Ivgi. diff --git a/lamassu-admin-elm/build/bower_components/qrjs/bower.json b/lamassu-admin-elm/build/bower_components/qrjs/bower.json new file mode 100644 index 00000000..39261ebf --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qrjs/bower.json @@ -0,0 +1,27 @@ +{ + "name": "qrjs", + "main": "qr.js", + "version": "0.1.2", + "homepage": "https://github.com/educastellano/qr.js", + "authors": [ + "Kang Seonghoon ", + "Eduard Castellano " + ], + "description": "QR code generator in Javascript", + "moduleType": [ + "globals" + ], + "keywords": [ + "qr", + "qr.js", + "qrcode" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "node_modules", + "bower_components", + "test", + "tests" + ] +} diff --git a/lamassu-admin-elm/build/bower_components/qrjs/package.json b/lamassu-admin-elm/build/bower_components/qrjs/package.json new file mode 100644 index 00000000..58b852ba --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qrjs/package.json @@ -0,0 +1,24 @@ +{ + "name": "qrjs", + "version": "0.1.1", + "description": "QR code generator in Javascript", + "main": "qr.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "https://github.com/educastellano/qr.js.git" + }, + "keywords": [ + "qr", + "qr.js", + "qrcode" + ], + "author": "Kang Seonghoon ", + "license": "MIT", + "bugs": { + "url": "https://github.com/educastellano/qr.js/issues" + }, + "homepage": "https://github.com/educastellano/qr.js" +} diff --git a/lamassu-admin-elm/build/bower_components/qrjs/qr.js b/lamassu-admin-elm/build/bower_components/qrjs/qr.js new file mode 100644 index 00000000..2dd08112 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/qrjs/qr.js @@ -0,0 +1,804 @@ +/* qr.js -- QR code generator in Javascript (revision 2011-01-19) + * Written by Kang Seonghoon . + * + * This source code is in the public domain; if your jurisdiction does not + * recognize the public domain the terms of Creative Commons CC0 license + * apply. In the other words, you can always do what you want. + */ +(function(root, name, definition) { + if (typeof define === 'function' && define.amd) { + define([], definition); + } else if (typeof module === 'object' && module.exports) { + module.exports = definition(); + } else { + root[name] = definition(); + } +})(this, 'QRCode', function() { +/* Quick overview: QR code composed of 2D array of modules (a rectangular + * area that conveys one bit of information); some modules are fixed to help + * the recognition of the code, and remaining data modules are further divided + * into 8-bit code words which are augumented by Reed-Solomon error correcting + * codes (ECC). There could be multiple ECCs, in the case the code is so large + * that it is helpful to split the raw data into several chunks. + * + * The number of modules is determined by the code's "version", ranging from 1 + * (21x21) to 40 (177x177). How many ECC bits are used is determined by the + * ECC level (L/M/Q/H). The number and size (and thus the order of generator + * polynomial) of ECCs depend to the version and ECC level. + */ + +// per-version information (cf. JIS X 0510:2004 pp. 30--36, 71) +// +// [0]: the degree of generator polynomial by ECC levels +// [1]: # of code blocks by ECC levels +// [2]: left-top positions of alignment patterns +// +// the number in this table (in particular, [0]) does not exactly match with +// the numbers in the specficiation. see augumenteccs below for the reason. +var VERSIONS = [ + null, + [[10, 7,17,13], [ 1, 1, 1, 1], []], + [[16,10,28,22], [ 1, 1, 1, 1], [4,16]], + [[26,15,22,18], [ 1, 1, 2, 2], [4,20]], + [[18,20,16,26], [ 2, 1, 4, 2], [4,24]], + [[24,26,22,18], [ 2, 1, 4, 4], [4,28]], + [[16,18,28,24], [ 4, 2, 4, 4], [4,32]], + [[18,20,26,18], [ 4, 2, 5, 6], [4,20,36]], + [[22,24,26,22], [ 4, 2, 6, 6], [4,22,40]], + [[22,30,24,20], [ 5, 2, 8, 8], [4,24,44]], + [[26,18,28,24], [ 5, 4, 8, 8], [4,26,48]], + [[30,20,24,28], [ 5, 4,11, 8], [4,28,52]], + [[22,24,28,26], [ 8, 4,11,10], [4,30,56]], + [[22,26,22,24], [ 9, 4,16,12], [4,32,60]], + [[24,30,24,20], [ 9, 4,16,16], [4,24,44,64]], + [[24,22,24,30], [10, 6,18,12], [4,24,46,68]], + [[28,24,30,24], [10, 6,16,17], [4,24,48,72]], + [[28,28,28,28], [11, 6,19,16], [4,28,52,76]], + [[26,30,28,28], [13, 6,21,18], [4,28,54,80]], + [[26,28,26,26], [14, 7,25,21], [4,28,56,84]], + [[26,28,28,30], [16, 8,25,20], [4,32,60,88]], + [[26,28,30,28], [17, 8,25,23], [4,26,48,70,92]], + [[28,28,24,30], [17, 9,34,23], [4,24,48,72,96]], + [[28,30,30,30], [18, 9,30,25], [4,28,52,76,100]], + [[28,30,30,30], [20,10,32,27], [4,26,52,78,104]], + [[28,26,30,30], [21,12,35,29], [4,30,56,82,108]], + [[28,28,30,28], [23,12,37,34], [4,28,56,84,112]], + [[28,30,30,30], [25,12,40,34], [4,32,60,88,116]], + [[28,30,30,30], [26,13,42,35], [4,24,48,72,96,120]], + [[28,30,30,30], [28,14,45,38], [4,28,52,76,100,124]], + [[28,30,30,30], [29,15,48,40], [4,24,50,76,102,128]], + [[28,30,30,30], [31,16,51,43], [4,28,54,80,106,132]], + [[28,30,30,30], [33,17,54,45], [4,32,58,84,110,136]], + [[28,30,30,30], [35,18,57,48], [4,28,56,84,112,140]], + [[28,30,30,30], [37,19,60,51], [4,32,60,88,116,144]], + [[28,30,30,30], [38,19,63,53], [4,28,52,76,100,124,148]], + [[28,30,30,30], [40,20,66,56], [4,22,48,74,100,126,152]], + [[28,30,30,30], [43,21,70,59], [4,26,52,78,104,130,156]], + [[28,30,30,30], [45,22,74,62], [4,30,56,82,108,134,160]], + [[28,30,30,30], [47,24,77,65], [4,24,52,80,108,136,164]], + [[28,30,30,30], [49,25,81,68], [4,28,56,84,112,140,168]]]; + +// mode constants (cf. Table 2 in JIS X 0510:2004 p. 16) +var MODE_TERMINATOR = 0; +var MODE_NUMERIC = 1, MODE_ALPHANUMERIC = 2, MODE_OCTET = 4, MODE_KANJI = 8; + +// validation regexps +var NUMERIC_REGEXP = /^\d*$/; +var ALPHANUMERIC_REGEXP = /^[A-Za-z0-9 $%*+\-./:]*$/; +var ALPHANUMERIC_OUT_REGEXP = /^[A-Z0-9 $%*+\-./:]*$/; + +// ECC levels (cf. Table 22 in JIS X 0510:2004 p. 45) +var ECCLEVEL_L = 1, ECCLEVEL_M = 0, ECCLEVEL_Q = 3, ECCLEVEL_H = 2; + +// GF(2^8)-to-integer mapping with a reducing polynomial x^8+x^4+x^3+x^2+1 +// invariant: GF256_MAP[GF256_INVMAP[i]] == i for all i in [1,256) +var GF256_MAP = [], GF256_INVMAP = [-1]; +for (var i = 0, v = 1; i < 255; ++i) { + GF256_MAP.push(v); + GF256_INVMAP[v] = i; + v = (v * 2) ^ (v >= 128 ? 0x11d : 0); +} + +// generator polynomials up to degree 30 +// (should match with polynomials in JIS X 0510:2004 Appendix A) +// +// generator polynomial of degree K is product of (x-\alpha^0), (x-\alpha^1), +// ..., (x-\alpha^(K-1)). by convention, we omit the K-th coefficient (always 1) +// from the result; also other coefficients are written in terms of the exponent +// to \alpha to avoid the redundant calculation. (see also calculateecc below.) +var GF256_GENPOLY = [[]]; +for (var i = 0; i < 30; ++i) { + var prevpoly = GF256_GENPOLY[i], poly = []; + for (var j = 0; j <= i; ++j) { + var a = (j < i ? GF256_MAP[prevpoly[j]] : 0); + var b = GF256_MAP[(i + (prevpoly[j-1] || 0)) % 255]; + poly.push(GF256_INVMAP[a ^ b]); + } + GF256_GENPOLY.push(poly); +} + +// alphanumeric character mapping (cf. Table 5 in JIS X 0510:2004 p. 19) +var ALPHANUMERIC_MAP = {}; +for (var i = 0; i < 45; ++i) { + ALPHANUMERIC_MAP['0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'.charAt(i)] = i; +} + +// mask functions in terms of row # and column # +// (cf. Table 20 in JIS X 0510:2004 p. 42) +var MASKFUNCS = [ + function(i,j) { return (i+j) % 2 == 0; }, + function(i,j) { return i % 2 == 0; }, + function(i,j) { return j % 3 == 0; }, + function(i,j) { return (i+j) % 3 == 0; }, + function(i,j) { return (((i/2)|0) + ((j/3)|0)) % 2 == 0; }, + function(i,j) { return (i*j) % 2 + (i*j) % 3 == 0; }, + function(i,j) { return ((i*j) % 2 + (i*j) % 3) % 2 == 0; }, + function(i,j) { return ((i+j) % 2 + (i*j) % 3) % 2 == 0; }]; + +// returns true when the version information has to be embeded. +var needsverinfo = function(ver) { return ver > 6; }; + +// returns the size of entire QR code for given version. +var getsizebyver = function(ver) { return 4 * ver + 17; }; + +// returns the number of bits available for code words in this version. +var nfullbits = function(ver) { + /* + * |<--------------- n --------------->| + * | |<----- n-17 ---->| | + * +-------+ ///+-------+ ---- + * | | ///| | ^ + * | 9x9 | @@@@@ ///| 9x8 | | + * | | # # # @5x5@ # # # | | | + * +-------+ @@@@@ +-------+ | + * # ---| + * ^ | + * # | + * @@@@@ @@@@@ @@@@@ | n + * @5x5@ @5x5@ @5x5@ n-17 + * @@@@@ @@@@@ @@@@@ | | + * # | | + * ////// v | + * //////# ---| + * +-------+ @@@@@ @@@@@ | + * | | @5x5@ @5x5@ | + * | 8x9 | @@@@@ @@@@@ | + * | | v + * +-------+ ---- + * + * when the entire code has n^2 modules and there are m^2-3 alignment + * patterns, we have: + * - 225 (= 9x9 + 9x8 + 8x9) modules for finder patterns and + * format information; + * - 2n-34 (= 2(n-17)) modules for timing patterns; + * - 36 (= 3x6 + 6x3) modules for version information, if any; + * - 25m^2-75 (= (m^2-3)(5x5)) modules for alignment patterns + * if any, but 10m-20 (= 2(m-2)x5) of them overlaps with + * timing patterns. + */ + var v = VERSIONS[ver]; + var nbits = 16*ver*ver + 128*ver + 64; // finder, timing and format info. + if (needsverinfo(ver)) nbits -= 36; // version information + if (v[2].length) { // alignment patterns + nbits -= 25 * v[2].length * v[2].length - 10 * v[2].length - 55; + } + return nbits; +}; + +// returns the number of bits available for data portions (i.e. excludes ECC +// bits but includes mode and length bits) in this version and ECC level. +var ndatabits = function(ver, ecclevel) { + var nbits = nfullbits(ver) & ~7; // no sub-octet code words + var v = VERSIONS[ver]; + nbits -= 8 * v[0][ecclevel] * v[1][ecclevel]; // ecc bits + return nbits; +} + +// returns the number of bits required for the length of data. +// (cf. Table 3 in JIS X 0510:2004 p. 16) +var ndatalenbits = function(ver, mode) { + switch (mode) { + case MODE_NUMERIC: return (ver < 10 ? 10 : ver < 27 ? 12 : 14); + case MODE_ALPHANUMERIC: return (ver < 10 ? 9 : ver < 27 ? 11 : 13); + case MODE_OCTET: return (ver < 10 ? 8 : 16); + case MODE_KANJI: return (ver < 10 ? 8 : ver < 27 ? 10 : 12); + } +}; + +// returns the maximum length of data possible in given configuration. +var getmaxdatalen = function(ver, mode, ecclevel) { + var nbits = ndatabits(ver, ecclevel) - 4 - ndatalenbits(ver, mode); // 4 for mode bits + switch (mode) { + case MODE_NUMERIC: + return ((nbits/10) | 0) * 3 + (nbits%10 < 4 ? 0 : nbits%10 < 7 ? 1 : 2); + case MODE_ALPHANUMERIC: + return ((nbits/11) | 0) * 2 + (nbits%11 < 6 ? 0 : 1); + case MODE_OCTET: + return (nbits/8) | 0; + case MODE_KANJI: + return (nbits/13) | 0; + } +}; + +// checks if the given data can be encoded in given mode, and returns +// the converted data for the further processing if possible. otherwise +// returns null. +// +// this function does not check the length of data; it is a duty of +// encode function below (as it depends on the version and ECC level too). +var validatedata = function(mode, data) { + switch (mode) { + case MODE_NUMERIC: + if (!data.match(NUMERIC_REGEXP)) return null; + return data; + + case MODE_ALPHANUMERIC: + if (!data.match(ALPHANUMERIC_REGEXP)) return null; + return data.toUpperCase(); + + case MODE_OCTET: + if (typeof data === 'string') { // encode as utf-8 string + var newdata = []; + for (var i = 0; i < data.length; ++i) { + var ch = data.charCodeAt(i); + if (ch < 0x80) { + newdata.push(ch); + } else if (ch < 0x800) { + newdata.push(0xc0 | (ch >> 6), + 0x80 | (ch & 0x3f)); + } else if (ch < 0x10000) { + newdata.push(0xe0 | (ch >> 12), + 0x80 | ((ch >> 6) & 0x3f), + 0x80 | (ch & 0x3f)); + } else { + newdata.push(0xf0 | (ch >> 18), + 0x80 | ((ch >> 12) & 0x3f), + 0x80 | ((ch >> 6) & 0x3f), + 0x80 | (ch & 0x3f)); + } + } + return newdata; + } else { + return data; + } + } +}; + +// returns the code words (sans ECC bits) for given data and configurations. +// requires data to be preprocessed by validatedata. no length check is +// performed, and everything has to be checked before calling this function. +var encode = function(ver, mode, data, maxbuflen) { + var buf = []; + var bits = 0, remaining = 8; + var datalen = data.length; + + // this function is intentionally no-op when n=0. + var pack = function(x, n) { + if (n >= remaining) { + buf.push(bits | (x >> (n -= remaining))); + while (n >= 8) buf.push((x >> (n -= 8)) & 255); + bits = 0; + remaining = 8; + } + if (n > 0) bits |= (x & ((1 << n) - 1)) << (remaining -= n); + }; + + var nlenbits = ndatalenbits(ver, mode); + pack(mode, 4); + pack(datalen, nlenbits); + + switch (mode) { + case MODE_NUMERIC: + for (var i = 2; i < datalen; i += 3) { + pack(parseInt(data.substring(i-2,i+1), 10), 10); + } + pack(parseInt(data.substring(i-2), 10), [0,4,7][datalen%3]); + break; + + case MODE_ALPHANUMERIC: + for (var i = 1; i < datalen; i += 2) { + pack(ALPHANUMERIC_MAP[data.charAt(i-1)] * 45 + + ALPHANUMERIC_MAP[data.charAt(i)], 11); + } + if (datalen % 2 == 1) { + pack(ALPHANUMERIC_MAP[data.charAt(i-1)], 6); + } + break; + + case MODE_OCTET: + for (var i = 0; i < datalen; ++i) { + pack(data[i], 8); + } + break; + }; + + // final bits. it is possible that adding terminator causes the buffer + // to overflow, but then the buffer truncated to the maximum size will + // be valid as the truncated terminator mode bits and padding is + // identical in appearance (cf. JIS X 0510:2004 sec 8.4.8). + pack(MODE_TERMINATOR, 4); + if (remaining < 8) buf.push(bits); + + // the padding to fill up the remaining space. we should not add any + // words when the overflow already occurred. + while (buf.length + 1 < maxbuflen) buf.push(0xec, 0x11); + if (buf.length < maxbuflen) buf.push(0xec); + return buf; +}; + +// calculates ECC code words for given code words and generator polynomial. +// +// this is quite similar to CRC calculation as both Reed-Solomon and CRC use +// the certain kind of cyclic codes, which is effectively the division of +// zero-augumented polynomial by the generator polynomial. the only difference +// is that Reed-Solomon uses GF(2^8), instead of CRC's GF(2), and Reed-Solomon +// uses the different generator polynomial than CRC's. +var calculateecc = function(poly, genpoly) { + var modulus = poly.slice(0); + var polylen = poly.length, genpolylen = genpoly.length; + for (var i = 0; i < genpolylen; ++i) modulus.push(0); + for (var i = 0; i < polylen; ) { + var quotient = GF256_INVMAP[modulus[i++]]; + if (quotient >= 0) { + for (var j = 0; j < genpolylen; ++j) { + modulus[i+j] ^= GF256_MAP[(quotient + genpoly[j]) % 255]; + } + } + } + return modulus.slice(polylen); +}; + +// auguments ECC code words to given code words. the resulting words are +// ready to be encoded in the matrix. +// +// the much of actual augumenting procedure follows JIS X 0510:2004 sec 8.7. +// the code is simplified using the fact that the size of each code & ECC +// blocks is almost same; for example, when we have 4 blocks and 46 data words +// the number of code words in those blocks are 11, 11, 12, 12 respectively. +var augumenteccs = function(poly, nblocks, genpoly) { + var subsizes = []; + var subsize = (poly.length / nblocks) | 0, subsize0 = 0; + var pivot = nblocks - poly.length % nblocks; + for (var i = 0; i < pivot; ++i) { + subsizes.push(subsize0); + subsize0 += subsize; + } + for (var i = pivot; i < nblocks; ++i) { + subsizes.push(subsize0); + subsize0 += subsize+1; + } + subsizes.push(subsize0); + + var eccs = []; + for (var i = 0; i < nblocks; ++i) { + eccs.push(calculateecc(poly.slice(subsizes[i], subsizes[i+1]), genpoly)); + } + + var result = []; + var nitemsperblock = (poly.length / nblocks) | 0; + for (var i = 0; i < nitemsperblock; ++i) { + for (var j = 0; j < nblocks; ++j) { + result.push(poly[subsizes[j] + i]); + } + } + for (var j = pivot; j < nblocks; ++j) { + result.push(poly[subsizes[j+1] - 1]); + } + for (var i = 0; i < genpoly.length; ++i) { + for (var j = 0; j < nblocks; ++j) { + result.push(eccs[j][i]); + } + } + return result; +}; + +// auguments BCH(p+q,q) code to the polynomial over GF(2), given the proper +// genpoly. the both input and output are in binary numbers, and unlike +// calculateecc genpoly should include the 1 bit for the highest degree. +// +// actual polynomials used for this procedure are as follows: +// - p=10, q=5, genpoly=x^10+x^8+x^5+x^4+x^2+x+1 (JIS X 0510:2004 Appendix C) +// - p=18, q=6, genpoly=x^12+x^11+x^10+x^9+x^8+x^5+x^2+1 (ibid. Appendix D) +var augumentbch = function(poly, p, genpoly, q) { + var modulus = poly << q; + for (var i = p - 1; i >= 0; --i) { + if ((modulus >> (q+i)) & 1) modulus ^= genpoly << i; + } + return (poly << q) | modulus; +}; + +// creates the base matrix for given version. it returns two matrices, one of +// them is the actual one and the another represents the "reserved" portion +// (e.g. finder and timing patterns) of the matrix. +// +// some entries in the matrix may be undefined, rather than 0 or 1. this is +// intentional (no initialization needed!), and putdata below will fill +// the remaining ones. +var makebasematrix = function(ver) { + var v = VERSIONS[ver], n = getsizebyver(ver); + var matrix = [], reserved = []; + for (var i = 0; i < n; ++i) { + matrix.push([]); + reserved.push([]); + } + + var blit = function(y, x, h, w, bits) { + for (var i = 0; i < h; ++i) { + for (var j = 0; j < w; ++j) { + matrix[y+i][x+j] = (bits[i] >> j) & 1; + reserved[y+i][x+j] = 1; + } + } + }; + + // finder patterns and a part of timing patterns + // will also mark the format information area (not yet written) as reserved. + blit(0, 0, 9, 9, [0x7f, 0x41, 0x5d, 0x5d, 0x5d, 0x41, 0x17f, 0x00, 0x40]); + blit(n-8, 0, 8, 9, [0x100, 0x7f, 0x41, 0x5d, 0x5d, 0x5d, 0x41, 0x7f]); + blit(0, n-8, 9, 8, [0xfe, 0x82, 0xba, 0xba, 0xba, 0x82, 0xfe, 0x00, 0x00]); + + // the rest of timing patterns + for (var i = 9; i < n-8; ++i) { + matrix[6][i] = matrix[i][6] = ~i & 1; + reserved[6][i] = reserved[i][6] = 1; + } + + // alignment patterns + var aligns = v[2], m = aligns.length; + for (var i = 0; i < m; ++i) { + var minj = (i==0 || i==m-1 ? 1 : 0), maxj = (i==0 ? m-1 : m); + for (var j = minj; j < maxj; ++j) { + blit(aligns[i], aligns[j], 5, 5, [0x1f, 0x11, 0x15, 0x11, 0x1f]); + } + } + + // version information + if (needsverinfo(ver)) { + var code = augumentbch(ver, 6, 0x1f25, 12); + var k = 0; + for (var i = 0; i < 6; ++i) { + for (var j = 0; j < 3; ++j) { + matrix[i][(n-11)+j] = matrix[(n-11)+j][i] = (code >> k++) & 1; + reserved[i][(n-11)+j] = reserved[(n-11)+j][i] = 1; + } + } + } + + return {matrix: matrix, reserved: reserved}; +}; + +// fills the data portion (i.e. unmarked in reserved) of the matrix with given +// code words. the size of code words should be no more than available bits, +// and remaining bits are padded to 0 (cf. JIS X 0510:2004 sec 8.7.3). +var putdata = function(matrix, reserved, buf) { + var n = matrix.length; + var k = 0, dir = -1; + for (var i = n-1; i >= 0; i -= 2) { + if (i == 6) --i; // skip the entire timing pattern column + var jj = (dir < 0 ? n-1 : 0); + for (var j = 0; j < n; ++j) { + for (var ii = i; ii > i-2; --ii) { + if (!reserved[jj][ii]) { + // may overflow, but (undefined >> x) + // is 0 so it will auto-pad to zero. + matrix[jj][ii] = (buf[k >> 3] >> (~k&7)) & 1; + ++k; + } + } + jj += dir; + } + dir = -dir; + } + return matrix; +}; + +// XOR-masks the data portion of the matrix. repeating the call with the same +// arguments will revert the prior call (convenient in the matrix evaluation). +var maskdata = function(matrix, reserved, mask) { + var maskf = MASKFUNCS[mask]; + var n = matrix.length; + for (var i = 0; i < n; ++i) { + for (var j = 0; j < n; ++j) { + if (!reserved[i][j]) matrix[i][j] ^= maskf(i,j); + } + } + return matrix; +} + +// puts the format information. +var putformatinfo = function(matrix, reserved, ecclevel, mask) { + var n = matrix.length; + var code = augumentbch((ecclevel << 3) | mask, 5, 0x537, 10) ^ 0x5412; + for (var i = 0; i < 15; ++i) { + var r = [0,1,2,3,4,5,7,8,n-7,n-6,n-5,n-4,n-3,n-2,n-1][i]; + var c = [n-1,n-2,n-3,n-4,n-5,n-6,n-7,n-8,7,5,4,3,2,1,0][i]; + matrix[r][8] = matrix[8][c] = (code >> i) & 1; + // we don't have to mark those bits reserved; always done + // in makebasematrix above. + } + return matrix; +}; + +// evaluates the resulting matrix and returns the score (lower is better). +// (cf. JIS X 0510:2004 sec 8.8.2) +// +// the evaluation procedure tries to avoid the problematic patterns naturally +// occuring from the original matrix. for example, it penaltizes the patterns +// which just look like the finder pattern which will confuse the decoder. +// we choose the mask which results in the lowest score among 8 possible ones. +// +// note: zxing seems to use the same procedure and in many cases its choice +// agrees to ours, but sometimes it does not. practically it doesn't matter. +var evaluatematrix = function(matrix) { + // N1+(k-5) points for each consecutive row of k same-colored modules, + // where k >= 5. no overlapping row counts. + var PENALTY_CONSECUTIVE = 3; + // N2 points for each 2x2 block of same-colored modules. + // overlapping block does count. + var PENALTY_TWOBYTWO = 3; + // N3 points for each pattern with >4W:1B:1W:3B:1W:1B or + // 1B:1W:3B:1W:1B:>4W, or their multiples (e.g. highly unlikely, + // but 13W:3B:3W:9B:3W:3B counts). + var PENALTY_FINDERLIKE = 40; + // N4*k points for every (5*k)% deviation from 50% black density. + // i.e. k=1 for 55~60% and 40~45%, k=2 for 60~65% and 35~40%, etc. + var PENALTY_DENSITY = 10; + + var evaluategroup = function(groups) { // assumes [W,B,W,B,W,...,B,W] + var score = 0; + for (var i = 0; i < groups.length; ++i) { + if (groups[i] >= 5) score += PENALTY_CONSECUTIVE + (groups[i]-5); + } + for (var i = 5; i < groups.length; i += 2) { + var p = groups[i]; + if (groups[i-1] == p && groups[i-2] == 3*p && groups[i-3] == p && + groups[i-4] == p && (groups[i-5] >= 4*p || groups[i+1] >= 4*p)) { + // this part differs from zxing... + score += PENALTY_FINDERLIKE; + } + } + return score; + }; + + var n = matrix.length; + var score = 0, nblacks = 0; + for (var i = 0; i < n; ++i) { + var row = matrix[i]; + var groups; + + // evaluate the current row + groups = [0]; // the first empty group of white + for (var j = 0; j < n; ) { + var k; + for (k = 0; j < n && row[j]; ++k) ++j; + groups.push(k); + for (k = 0; j < n && !row[j]; ++k) ++j; + groups.push(k); + } + score += evaluategroup(groups); + + // evaluate the current column + groups = [0]; + for (var j = 0; j < n; ) { + var k; + for (k = 0; j < n && matrix[j][i]; ++k) ++j; + groups.push(k); + for (k = 0; j < n && !matrix[j][i]; ++k) ++j; + groups.push(k); + } + score += evaluategroup(groups); + + // check the 2x2 box and calculate the density + var nextrow = matrix[i+1] || []; + nblacks += row[0]; + for (var j = 1; j < n; ++j) { + var p = row[j]; + nblacks += p; + // at least comparison with next row should be strict... + if (row[j-1] == p && nextrow[j] === p && nextrow[j-1] === p) { + score += PENALTY_TWOBYTWO; + } + } + } + + score += PENALTY_DENSITY * ((Math.abs(nblacks / n / n - 0.5) / 0.05) | 0); + return score; +}; + +// returns the fully encoded QR code matrix which contains given data. +// it also chooses the best mask automatically when mask is -1. +var generate = function(data, ver, mode, ecclevel, mask) { + var v = VERSIONS[ver]; + var buf = encode(ver, mode, data, ndatabits(ver, ecclevel) >> 3); + buf = augumenteccs(buf, v[1][ecclevel], GF256_GENPOLY[v[0][ecclevel]]); + + var result = makebasematrix(ver); + var matrix = result.matrix, reserved = result.reserved; + putdata(matrix, reserved, buf); + + if (mask < 0) { + // find the best mask + maskdata(matrix, reserved, 0); + putformatinfo(matrix, reserved, ecclevel, 0); + var bestmask = 0, bestscore = evaluatematrix(matrix); + maskdata(matrix, reserved, 0); + for (mask = 1; mask < 8; ++mask) { + maskdata(matrix, reserved, mask); + putformatinfo(matrix, reserved, ecclevel, mask); + var score = evaluatematrix(matrix); + if (bestscore > score) { + bestscore = score; + bestmask = mask; + } + maskdata(matrix, reserved, mask); + } + mask = bestmask; + } + + maskdata(matrix, reserved, mask); + putformatinfo(matrix, reserved, ecclevel, mask); + return matrix; +}; + +// the public interface is trivial; the options available are as follows: +// +// - version: an integer in [1,40]. when omitted (or -1) the smallest possible +// version is chosen. +// - mode: one of 'numeric', 'alphanumeric', 'octet'. when omitted the smallest +// possible mode is chosen. +// - ecclevel: one of 'L', 'M', 'Q', 'H'. defaults to 'L'. +// - mask: an integer in [0,7]. when omitted (or -1) the best mask is chosen. +// +// for generate{HTML,PNG}: +// +// - modulesize: a number. this is a size of each modules in pixels, and +// defaults to 5px. +// - margin: a number. this is a size of margin in *modules*, and defaults to +// 4 (white modules). the specficiation mandates the margin no less than 4 +// modules, so it is better not to alter this value unless you know what +// you're doing. +var QRCode = { + 'generate': function(data, options) { + var MODES = {'numeric': MODE_NUMERIC, 'alphanumeric': MODE_ALPHANUMERIC, + 'octet': MODE_OCTET}; + var ECCLEVELS = {'L': ECCLEVEL_L, 'M': ECCLEVEL_M, 'Q': ECCLEVEL_Q, + 'H': ECCLEVEL_H}; + + options = options || {}; + var ver = options.version || -1; + var ecclevel = ECCLEVELS[(options.ecclevel || 'L').toUpperCase()]; + var mode = options.mode ? MODES[options.mode.toLowerCase()] : -1; + var mask = 'mask' in options ? options.mask : -1; + + if (mode < 0) { + if (typeof data === 'string') { + if (data.match(NUMERIC_REGEXP)) { + mode = MODE_NUMERIC; + } else if (data.match(ALPHANUMERIC_OUT_REGEXP)) { + // while encode supports case-insensitive + // encoding, we restrict the data to be + // uppercased when auto-selecting the mode. + mode = MODE_ALPHANUMERIC; + } else { + mode = MODE_OCTET; + } + } else { + mode = MODE_OCTET; + } + } else if (!(mode == MODE_NUMERIC || mode == MODE_ALPHANUMERIC || + mode == MODE_OCTET)) { + throw 'invalid or unsupported mode'; + } + + data = validatedata(mode, data); + if (data === null) throw 'invalid data format'; + + if (ecclevel < 0 || ecclevel > 3) throw 'invalid ECC level'; + + if (ver < 0) { + for (ver = 1; ver <= 40; ++ver) { + if (data.length <= getmaxdatalen(ver, mode, ecclevel)) break; + } + if (ver > 40) throw 'too large data'; + } else if (ver < 1 || ver > 40) { + throw 'invalid version'; + } + + if (mask != -1 && (mask < 0 || mask > 8)) throw 'invalid mask'; + + return generate(data, ver, mode, ecclevel, mask); + }, + + 'generateHTML': function(data, options) { + options = options || {}; + var matrix = QRCode['generate'](data, options); + var modsize = Math.max(options.modulesize || 5, 0.5); + var margin = Math.max(options.margin !== null ? options.margin : 4, 0.0); + + var e = document.createElement('div'); + var n = matrix.length; + var html = ['']; + for (var i = 0; i < n; ++i) { + html.push(''); + for (var j = 0; j < n; ++j) { + html.push(''); + } + html.push(''); + } + e.className = 'qrcode'; + e.innerHTML = html.join('') + '
'; + return e; + }, + + 'generateSVG': function(data, options) { + options = options || {}; + var matrix = QRCode['generate'](data, options); + var n = matrix.length; + var modsize = Math.max(options.modulesize || 5, 0.5); + var margin = Math.max(options.margin !== null ? options.margin : 4, 0.0); + var size = modsize * (n + 2 * margin); + + var common = ' class= "fg"'+' width="'+modsize+'" height="'+modsize+'"/>'; + + var e = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + e.setAttribute('viewBox', '0 0 '+size+' '+size); + e.setAttribute('style', 'shape-rendering:crispEdges'); + if (options.modulesize) { + e.setAttribute('width', size); + e.setAttribute('height', size); + } + + var svg = [ + '', + '', + ]; + + var yo = margin * modsize; + for (var y = 0; y < n; ++y) { + var xo = margin * modsize; + for (var x = 0; x < n; ++x) { + if (matrix[y][x]) + svg.push('>> 0) + (counter++ + "__"); + }; + WeakMap.prototype = { + set: function(key, value) { + var entry = key[this.name]; + if (entry && entry[0] === key) entry[1] = value; else defineProperty(key, this.name, { + value: [ key, value ], + writable: true + }); + return this; + }, + get: function(key) { + var entry; + return (entry = key[this.name]) && entry[0] === key ? entry[1] : undefined; + }, + "delete": function(key) { + var entry = key[this.name]; + if (!entry || entry[0] !== key) return false; + entry[0] = entry[1] = undefined; + return true; + }, + has: function(key) { + var entry = key[this.name]; + if (!entry) return false; + return entry[0] === key; + } + }; + window.WeakMap = WeakMap; + })(); +} + +(function(global) { + if (global.JsMutationObserver) { + return; + } + var registrationsTable = new WeakMap(); + var setImmediate; + if (/Trident|Edge/.test(navigator.userAgent)) { + setImmediate = setTimeout; + } else if (window.setImmediate) { + setImmediate = window.setImmediate; + } else { + var setImmediateQueue = []; + var sentinel = String(Math.random()); + window.addEventListener("message", function(e) { + if (e.data === sentinel) { + var queue = setImmediateQueue; + setImmediateQueue = []; + queue.forEach(function(func) { + func(); + }); + } + }); + setImmediate = function(func) { + setImmediateQueue.push(func); + window.postMessage(sentinel, "*"); + }; + } + var isScheduled = false; + var scheduledObservers = []; + function scheduleCallback(observer) { + scheduledObservers.push(observer); + if (!isScheduled) { + isScheduled = true; + setImmediate(dispatchCallbacks); + } + } + function wrapIfNeeded(node) { + return window.ShadowDOMPolyfill && window.ShadowDOMPolyfill.wrapIfNeeded(node) || node; + } + function dispatchCallbacks() { + isScheduled = false; + var observers = scheduledObservers; + scheduledObservers = []; + observers.sort(function(o1, o2) { + return o1.uid_ - o2.uid_; + }); + var anyNonEmpty = false; + observers.forEach(function(observer) { + var queue = observer.takeRecords(); + removeTransientObserversFor(observer); + if (queue.length) { + observer.callback_(queue, observer); + anyNonEmpty = true; + } + }); + if (anyNonEmpty) dispatchCallbacks(); + } + function removeTransientObserversFor(observer) { + observer.nodes_.forEach(function(node) { + var registrations = registrationsTable.get(node); + if (!registrations) return; + registrations.forEach(function(registration) { + if (registration.observer === observer) registration.removeTransientObservers(); + }); + }); + } + function forEachAncestorAndObserverEnqueueRecord(target, callback) { + for (var node = target; node; node = node.parentNode) { + var registrations = registrationsTable.get(node); + if (registrations) { + for (var j = 0; j < registrations.length; j++) { + var registration = registrations[j]; + var options = registration.options; + if (node !== target && !options.subtree) continue; + var record = callback(options); + if (record) registration.enqueue(record); + } + } + } + } + var uidCounter = 0; + function JsMutationObserver(callback) { + this.callback_ = callback; + this.nodes_ = []; + this.records_ = []; + this.uid_ = ++uidCounter; + } + JsMutationObserver.prototype = { + observe: function(target, options) { + target = wrapIfNeeded(target); + if (!options.childList && !options.attributes && !options.characterData || options.attributeOldValue && !options.attributes || options.attributeFilter && options.attributeFilter.length && !options.attributes || options.characterDataOldValue && !options.characterData) { + throw new SyntaxError(); + } + var registrations = registrationsTable.get(target); + if (!registrations) registrationsTable.set(target, registrations = []); + var registration; + for (var i = 0; i < registrations.length; i++) { + if (registrations[i].observer === this) { + registration = registrations[i]; + registration.removeListeners(); + registration.options = options; + break; + } + } + if (!registration) { + registration = new Registration(this, target, options); + registrations.push(registration); + this.nodes_.push(target); + } + registration.addListeners(); + }, + disconnect: function() { + this.nodes_.forEach(function(node) { + var registrations = registrationsTable.get(node); + for (var i = 0; i < registrations.length; i++) { + var registration = registrations[i]; + if (registration.observer === this) { + registration.removeListeners(); + registrations.splice(i, 1); + break; + } + } + }, this); + this.records_ = []; + }, + takeRecords: function() { + var copyOfRecords = this.records_; + this.records_ = []; + return copyOfRecords; + } + }; + function MutationRecord(type, target) { + this.type = type; + this.target = target; + this.addedNodes = []; + this.removedNodes = []; + this.previousSibling = null; + this.nextSibling = null; + this.attributeName = null; + this.attributeNamespace = null; + this.oldValue = null; + } + function copyMutationRecord(original) { + var record = new MutationRecord(original.type, original.target); + record.addedNodes = original.addedNodes.slice(); + record.removedNodes = original.removedNodes.slice(); + record.previousSibling = original.previousSibling; + record.nextSibling = original.nextSibling; + record.attributeName = original.attributeName; + record.attributeNamespace = original.attributeNamespace; + record.oldValue = original.oldValue; + return record; + } + var currentRecord, recordWithOldValue; + function getRecord(type, target) { + return currentRecord = new MutationRecord(type, target); + } + function getRecordWithOldValue(oldValue) { + if (recordWithOldValue) return recordWithOldValue; + recordWithOldValue = copyMutationRecord(currentRecord); + recordWithOldValue.oldValue = oldValue; + return recordWithOldValue; + } + function clearRecords() { + currentRecord = recordWithOldValue = undefined; + } + function recordRepresentsCurrentMutation(record) { + return record === recordWithOldValue || record === currentRecord; + } + function selectRecord(lastRecord, newRecord) { + if (lastRecord === newRecord) return lastRecord; + if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord)) return recordWithOldValue; + return null; + } + function Registration(observer, target, options) { + this.observer = observer; + this.target = target; + this.options = options; + this.transientObservedNodes = []; + } + Registration.prototype = { + enqueue: function(record) { + var records = this.observer.records_; + var length = records.length; + if (records.length > 0) { + var lastRecord = records[length - 1]; + var recordToReplaceLast = selectRecord(lastRecord, record); + if (recordToReplaceLast) { + records[length - 1] = recordToReplaceLast; + return; + } + } else { + scheduleCallback(this.observer); + } + records[length] = record; + }, + addListeners: function() { + this.addListeners_(this.target); + }, + addListeners_: function(node) { + var options = this.options; + if (options.attributes) node.addEventListener("DOMAttrModified", this, true); + if (options.characterData) node.addEventListener("DOMCharacterDataModified", this, true); + if (options.childList) node.addEventListener("DOMNodeInserted", this, true); + if (options.childList || options.subtree) node.addEventListener("DOMNodeRemoved", this, true); + }, + removeListeners: function() { + this.removeListeners_(this.target); + }, + removeListeners_: function(node) { + var options = this.options; + if (options.attributes) node.removeEventListener("DOMAttrModified", this, true); + if (options.characterData) node.removeEventListener("DOMCharacterDataModified", this, true); + if (options.childList) node.removeEventListener("DOMNodeInserted", this, true); + if (options.childList || options.subtree) node.removeEventListener("DOMNodeRemoved", this, true); + }, + addTransientObserver: function(node) { + if (node === this.target) return; + this.addListeners_(node); + this.transientObservedNodes.push(node); + var registrations = registrationsTable.get(node); + if (!registrations) registrationsTable.set(node, registrations = []); + registrations.push(this); + }, + removeTransientObservers: function() { + var transientObservedNodes = this.transientObservedNodes; + this.transientObservedNodes = []; + transientObservedNodes.forEach(function(node) { + this.removeListeners_(node); + var registrations = registrationsTable.get(node); + for (var i = 0; i < registrations.length; i++) { + if (registrations[i] === this) { + registrations.splice(i, 1); + break; + } + } + }, this); + }, + handleEvent: function(e) { + e.stopImmediatePropagation(); + switch (e.type) { + case "DOMAttrModified": + var name = e.attrName; + var namespace = e.relatedNode.namespaceURI; + var target = e.target; + var record = new getRecord("attributes", target); + record.attributeName = name; + record.attributeNamespace = namespace; + var oldValue = e.attrChange === MutationEvent.ADDITION ? null : e.prevValue; + forEachAncestorAndObserverEnqueueRecord(target, function(options) { + if (!options.attributes) return; + if (options.attributeFilter && options.attributeFilter.length && options.attributeFilter.indexOf(name) === -1 && options.attributeFilter.indexOf(namespace) === -1) { + return; + } + if (options.attributeOldValue) return getRecordWithOldValue(oldValue); + return record; + }); + break; + + case "DOMCharacterDataModified": + var target = e.target; + var record = getRecord("characterData", target); + var oldValue = e.prevValue; + forEachAncestorAndObserverEnqueueRecord(target, function(options) { + if (!options.characterData) return; + if (options.characterDataOldValue) return getRecordWithOldValue(oldValue); + return record; + }); + break; + + case "DOMNodeRemoved": + this.addTransientObserver(e.target); + + case "DOMNodeInserted": + var changedNode = e.target; + var addedNodes, removedNodes; + if (e.type === "DOMNodeInserted") { + addedNodes = [ changedNode ]; + removedNodes = []; + } else { + addedNodes = []; + removedNodes = [ changedNode ]; + } + var previousSibling = changedNode.previousSibling; + var nextSibling = changedNode.nextSibling; + var record = getRecord("childList", e.target.parentNode); + record.addedNodes = addedNodes; + record.removedNodes = removedNodes; + record.previousSibling = previousSibling; + record.nextSibling = nextSibling; + forEachAncestorAndObserverEnqueueRecord(e.relatedNode, function(options) { + if (!options.childList) return; + return record; + }); + } + clearRecords(); + } + }; + global.JsMutationObserver = JsMutationObserver; + if (!global.MutationObserver) { + global.MutationObserver = JsMutationObserver; + JsMutationObserver._isPolyfilled = true; + } +})(self); + +(function(scope) { + "use strict"; + if (!window.performance) { + var start = Date.now(); + window.performance = { + now: function() { + return Date.now() - start; + } + }; + } + if (!window.requestAnimationFrame) { + window.requestAnimationFrame = function() { + var nativeRaf = window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame; + return nativeRaf ? function(callback) { + return nativeRaf(function() { + callback(performance.now()); + }); + } : function(callback) { + return window.setTimeout(callback, 1e3 / 60); + }; + }(); + } + if (!window.cancelAnimationFrame) { + window.cancelAnimationFrame = function() { + return window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || function(id) { + clearTimeout(id); + }; + }(); + } + var workingDefaultPrevented = function() { + var e = document.createEvent("Event"); + e.initEvent("foo", true, true); + e.preventDefault(); + return e.defaultPrevented; + }(); + if (!workingDefaultPrevented) { + var origPreventDefault = Event.prototype.preventDefault; + Event.prototype.preventDefault = function() { + if (!this.cancelable) { + return; + } + origPreventDefault.call(this); + Object.defineProperty(this, "defaultPrevented", { + get: function() { + return true; + }, + configurable: true + }); + }; + } + var isIE = /Trident/.test(navigator.userAgent); + if (!window.CustomEvent || isIE && typeof window.CustomEvent !== "function") { + window.CustomEvent = function(inType, params) { + params = params || {}; + var e = document.createEvent("CustomEvent"); + e.initCustomEvent(inType, Boolean(params.bubbles), Boolean(params.cancelable), params.detail); + return e; + }; + window.CustomEvent.prototype = window.Event.prototype; + } + if (!window.Event || isIE && typeof window.Event !== "function") { + var origEvent = window.Event; + window.Event = function(inType, params) { + params = params || {}; + var e = document.createEvent("Event"); + e.initEvent(inType, Boolean(params.bubbles), Boolean(params.cancelable)); + return e; + }; + window.Event.prototype = origEvent.prototype; + } +})(window.WebComponents); + +window.CustomElements = window.CustomElements || { + flags: {} +}; + +(function(scope) { + var flags = scope.flags; + var modules = []; + var addModule = function(module) { + modules.push(module); + }; + var initializeModules = function() { + modules.forEach(function(module) { + module(scope); + }); + }; + scope.addModule = addModule; + scope.initializeModules = initializeModules; + scope.hasNative = Boolean(document.registerElement); + scope.isIE = /Trident/.test(navigator.userAgent); + scope.useNative = !flags.register && scope.hasNative && !window.ShadowDOMPolyfill && (!window.HTMLImports || window.HTMLImports.useNative); +})(window.CustomElements); + +window.CustomElements.addModule(function(scope) { + var IMPORT_LINK_TYPE = window.HTMLImports ? window.HTMLImports.IMPORT_LINK_TYPE : "none"; + function forSubtree(node, cb) { + findAllElements(node, function(e) { + if (cb(e)) { + return true; + } + forRoots(e, cb); + }); + forRoots(node, cb); + } + function findAllElements(node, find, data) { + var e = node.firstElementChild; + if (!e) { + e = node.firstChild; + while (e && e.nodeType !== Node.ELEMENT_NODE) { + e = e.nextSibling; + } + } + while (e) { + if (find(e, data) !== true) { + findAllElements(e, find, data); + } + e = e.nextElementSibling; + } + return null; + } + function forRoots(node, cb) { + var root = node.shadowRoot; + while (root) { + forSubtree(root, cb); + root = root.olderShadowRoot; + } + } + function forDocumentTree(doc, cb) { + _forDocumentTree(doc, cb, []); + } + function _forDocumentTree(doc, cb, processingDocuments) { + doc = window.wrap(doc); + if (processingDocuments.indexOf(doc) >= 0) { + return; + } + processingDocuments.push(doc); + var imports = doc.querySelectorAll("link[rel=" + IMPORT_LINK_TYPE + "]"); + for (var i = 0, l = imports.length, n; i < l && (n = imports[i]); i++) { + if (n.import) { + _forDocumentTree(n.import, cb, processingDocuments); + } + } + cb(doc); + } + scope.forDocumentTree = forDocumentTree; + scope.forSubtree = forSubtree; +}); + +window.CustomElements.addModule(function(scope) { + var flags = scope.flags; + var forSubtree = scope.forSubtree; + var forDocumentTree = scope.forDocumentTree; + function addedNode(node, isAttached) { + return added(node, isAttached) || addedSubtree(node, isAttached); + } + function added(node, isAttached) { + if (scope.upgrade(node, isAttached)) { + return true; + } + if (isAttached) { + attached(node); + } + } + function addedSubtree(node, isAttached) { + forSubtree(node, function(e) { + if (added(e, isAttached)) { + return true; + } + }); + } + var hasThrottledAttached = window.MutationObserver._isPolyfilled && flags["throttle-attached"]; + scope.hasPolyfillMutations = hasThrottledAttached; + scope.hasThrottledAttached = hasThrottledAttached; + var isPendingMutations = false; + var pendingMutations = []; + function deferMutation(fn) { + pendingMutations.push(fn); + if (!isPendingMutations) { + isPendingMutations = true; + setTimeout(takeMutations); + } + } + function takeMutations() { + isPendingMutations = false; + var $p = pendingMutations; + for (var i = 0, l = $p.length, p; i < l && (p = $p[i]); i++) { + p(); + } + pendingMutations = []; + } + function attached(element) { + if (hasThrottledAttached) { + deferMutation(function() { + _attached(element); + }); + } else { + _attached(element); + } + } + function _attached(element) { + if (element.__upgraded__ && !element.__attached) { + element.__attached = true; + if (element.attachedCallback) { + element.attachedCallback(); + } + } + } + function detachedNode(node) { + detached(node); + forSubtree(node, function(e) { + detached(e); + }); + } + function detached(element) { + if (hasThrottledAttached) { + deferMutation(function() { + _detached(element); + }); + } else { + _detached(element); + } + } + function _detached(element) { + if (element.__upgraded__ && element.__attached) { + element.__attached = false; + if (element.detachedCallback) { + element.detachedCallback(); + } + } + } + function inDocument(element) { + var p = element; + var doc = window.wrap(document); + while (p) { + if (p == doc) { + return true; + } + p = p.parentNode || p.nodeType === Node.DOCUMENT_FRAGMENT_NODE && p.host; + } + } + function watchShadow(node) { + if (node.shadowRoot && !node.shadowRoot.__watched) { + flags.dom && console.log("watching shadow-root for: ", node.localName); + var root = node.shadowRoot; + while (root) { + observe(root); + root = root.olderShadowRoot; + } + } + } + function handler(root, mutations) { + if (flags.dom) { + var mx = mutations[0]; + if (mx && mx.type === "childList" && mx.addedNodes) { + if (mx.addedNodes) { + var d = mx.addedNodes[0]; + while (d && d !== document && !d.host) { + d = d.parentNode; + } + var u = d && (d.URL || d._URL || d.host && d.host.localName) || ""; + u = u.split("/?").shift().split("/").pop(); + } + } + console.group("mutations (%d) [%s]", mutations.length, u || ""); + } + var isAttached = inDocument(root); + mutations.forEach(function(mx) { + if (mx.type === "childList") { + forEach(mx.addedNodes, function(n) { + if (!n.localName) { + return; + } + addedNode(n, isAttached); + }); + forEach(mx.removedNodes, function(n) { + if (!n.localName) { + return; + } + detachedNode(n); + }); + } + }); + flags.dom && console.groupEnd(); + } + function takeRecords(node) { + node = window.wrap(node); + if (!node) { + node = window.wrap(document); + } + while (node.parentNode) { + node = node.parentNode; + } + var observer = node.__observer; + if (observer) { + handler(node, observer.takeRecords()); + takeMutations(); + } + } + var forEach = Array.prototype.forEach.call.bind(Array.prototype.forEach); + function observe(inRoot) { + if (inRoot.__observer) { + return; + } + var observer = new MutationObserver(handler.bind(this, inRoot)); + observer.observe(inRoot, { + childList: true, + subtree: true + }); + inRoot.__observer = observer; + } + function upgradeDocument(doc) { + doc = window.wrap(doc); + flags.dom && console.group("upgradeDocument: ", doc.baseURI.split("/").pop()); + var isMainDocument = doc === window.wrap(document); + addedNode(doc, isMainDocument); + observe(doc); + flags.dom && console.groupEnd(); + } + function upgradeDocumentTree(doc) { + forDocumentTree(doc, upgradeDocument); + } + var originalCreateShadowRoot = Element.prototype.createShadowRoot; + if (originalCreateShadowRoot) { + Element.prototype.createShadowRoot = function() { + var root = originalCreateShadowRoot.call(this); + window.CustomElements.watchShadow(this); + return root; + }; + } + scope.watchShadow = watchShadow; + scope.upgradeDocumentTree = upgradeDocumentTree; + scope.upgradeDocument = upgradeDocument; + scope.upgradeSubtree = addedSubtree; + scope.upgradeAll = addedNode; + scope.attached = attached; + scope.takeRecords = takeRecords; +}); + +window.CustomElements.addModule(function(scope) { + var flags = scope.flags; + function upgrade(node, isAttached) { + if (node.localName === "template") { + if (window.HTMLTemplateElement && HTMLTemplateElement.decorate) { + HTMLTemplateElement.decorate(node); + } + } + if (!node.__upgraded__ && node.nodeType === Node.ELEMENT_NODE) { + var is = node.getAttribute("is"); + var definition = scope.getRegisteredDefinition(node.localName) || scope.getRegisteredDefinition(is); + if (definition) { + if (is && definition.tag == node.localName || !is && !definition.extends) { + return upgradeWithDefinition(node, definition, isAttached); + } + } + } + } + function upgradeWithDefinition(element, definition, isAttached) { + flags.upgrade && console.group("upgrade:", element.localName); + if (definition.is) { + element.setAttribute("is", definition.is); + } + implementPrototype(element, definition); + element.__upgraded__ = true; + created(element); + if (isAttached) { + scope.attached(element); + } + scope.upgradeSubtree(element, isAttached); + flags.upgrade && console.groupEnd(); + return element; + } + function implementPrototype(element, definition) { + if (Object.__proto__) { + element.__proto__ = definition.prototype; + } else { + customMixin(element, definition.prototype, definition.native); + element.__proto__ = definition.prototype; + } + } + function customMixin(inTarget, inSrc, inNative) { + var used = {}; + var p = inSrc; + while (p !== inNative && p !== HTMLElement.prototype) { + var keys = Object.getOwnPropertyNames(p); + for (var i = 0, k; k = keys[i]; i++) { + if (!used[k]) { + Object.defineProperty(inTarget, k, Object.getOwnPropertyDescriptor(p, k)); + used[k] = 1; + } + } + p = Object.getPrototypeOf(p); + } + } + function created(element) { + if (element.createdCallback) { + element.createdCallback(); + } + } + scope.upgrade = upgrade; + scope.upgradeWithDefinition = upgradeWithDefinition; + scope.implementPrototype = implementPrototype; +}); + +window.CustomElements.addModule(function(scope) { + var isIE = scope.isIE; + var upgradeDocumentTree = scope.upgradeDocumentTree; + var upgradeAll = scope.upgradeAll; + var upgradeWithDefinition = scope.upgradeWithDefinition; + var implementPrototype = scope.implementPrototype; + var useNative = scope.useNative; + function register(name, options) { + var definition = options || {}; + if (!name) { + throw new Error("document.registerElement: first argument `name` must not be empty"); + } + if (name.indexOf("-") < 0) { + throw new Error("document.registerElement: first argument ('name') must contain a dash ('-'). Argument provided was '" + String(name) + "'."); + } + if (isReservedTag(name)) { + throw new Error("Failed to execute 'registerElement' on 'Document': Registration failed for type '" + String(name) + "'. The type name is invalid."); + } + if (getRegisteredDefinition(name)) { + throw new Error("DuplicateDefinitionError: a type with name '" + String(name) + "' is already registered"); + } + if (!definition.prototype) { + definition.prototype = Object.create(HTMLElement.prototype); + } + definition.__name = name.toLowerCase(); + if (definition.extends) { + definition.extends = definition.extends.toLowerCase(); + } + definition.lifecycle = definition.lifecycle || {}; + definition.ancestry = ancestry(definition.extends); + resolveTagName(definition); + resolvePrototypeChain(definition); + overrideAttributeApi(definition.prototype); + registerDefinition(definition.__name, definition); + definition.ctor = generateConstructor(definition); + definition.ctor.prototype = definition.prototype; + definition.prototype.constructor = definition.ctor; + if (scope.ready) { + upgradeDocumentTree(document); + } + return definition.ctor; + } + function overrideAttributeApi(prototype) { + if (prototype.setAttribute._polyfilled) { + return; + } + var setAttribute = prototype.setAttribute; + prototype.setAttribute = function(name, value) { + changeAttribute.call(this, name, value, setAttribute); + }; + var removeAttribute = prototype.removeAttribute; + prototype.removeAttribute = function(name) { + changeAttribute.call(this, name, null, removeAttribute); + }; + prototype.setAttribute._polyfilled = true; + } + function changeAttribute(name, value, operation) { + name = name.toLowerCase(); + var oldValue = this.getAttribute(name); + operation.apply(this, arguments); + var newValue = this.getAttribute(name); + if (this.attributeChangedCallback && newValue !== oldValue) { + this.attributeChangedCallback(name, oldValue, newValue); + } + } + function isReservedTag(name) { + for (var i = 0; i < reservedTagList.length; i++) { + if (name === reservedTagList[i]) { + return true; + } + } + } + var reservedTagList = [ "annotation-xml", "color-profile", "font-face", "font-face-src", "font-face-uri", "font-face-format", "font-face-name", "missing-glyph" ]; + function ancestry(extnds) { + var extendee = getRegisteredDefinition(extnds); + if (extendee) { + return ancestry(extendee.extends).concat([ extendee ]); + } + return []; + } + function resolveTagName(definition) { + var baseTag = definition.extends; + for (var i = 0, a; a = definition.ancestry[i]; i++) { + baseTag = a.is && a.tag; + } + definition.tag = baseTag || definition.__name; + if (baseTag) { + definition.is = definition.__name; + } + } + function resolvePrototypeChain(definition) { + if (!Object.__proto__) { + var nativePrototype = HTMLElement.prototype; + if (definition.is) { + var inst = document.createElement(definition.tag); + nativePrototype = Object.getPrototypeOf(inst); + } + var proto = definition.prototype, ancestor; + var foundPrototype = false; + while (proto) { + if (proto == nativePrototype) { + foundPrototype = true; + } + ancestor = Object.getPrototypeOf(proto); + if (ancestor) { + proto.__proto__ = ancestor; + } + proto = ancestor; + } + if (!foundPrototype) { + console.warn(definition.tag + " prototype not found in prototype chain for " + definition.is); + } + definition.native = nativePrototype; + } + } + function instantiate(definition) { + return upgradeWithDefinition(domCreateElement(definition.tag), definition); + } + var registry = {}; + function getRegisteredDefinition(name) { + if (name) { + return registry[name.toLowerCase()]; + } + } + function registerDefinition(name, definition) { + registry[name] = definition; + } + function generateConstructor(definition) { + return function() { + return instantiate(definition); + }; + } + var HTML_NAMESPACE = "http://www.w3.org/1999/xhtml"; + function createElementNS(namespace, tag, typeExtension) { + if (namespace === HTML_NAMESPACE) { + return createElement(tag, typeExtension); + } else { + return domCreateElementNS(namespace, tag); + } + } + function createElement(tag, typeExtension) { + if (tag) { + tag = tag.toLowerCase(); + } + if (typeExtension) { + typeExtension = typeExtension.toLowerCase(); + } + var definition = getRegisteredDefinition(typeExtension || tag); + if (definition) { + if (tag == definition.tag && typeExtension == definition.is) { + return new definition.ctor(); + } + if (!typeExtension && !definition.is) { + return new definition.ctor(); + } + } + var element; + if (typeExtension) { + element = createElement(tag); + element.setAttribute("is", typeExtension); + return element; + } + element = domCreateElement(tag); + if (tag.indexOf("-") >= 0) { + implementPrototype(element, HTMLElement); + } + return element; + } + var domCreateElement = document.createElement.bind(document); + var domCreateElementNS = document.createElementNS.bind(document); + var isInstance; + if (!Object.__proto__ && !useNative) { + isInstance = function(obj, ctor) { + if (obj instanceof ctor) { + return true; + } + var p = obj; + while (p) { + if (p === ctor.prototype) { + return true; + } + p = p.__proto__; + } + return false; + }; + } else { + isInstance = function(obj, base) { + return obj instanceof base; + }; + } + function wrapDomMethodToForceUpgrade(obj, methodName) { + var orig = obj[methodName]; + obj[methodName] = function() { + var n = orig.apply(this, arguments); + upgradeAll(n); + return n; + }; + } + wrapDomMethodToForceUpgrade(Node.prototype, "cloneNode"); + wrapDomMethodToForceUpgrade(document, "importNode"); + document.registerElement = register; + document.createElement = createElement; + document.createElementNS = createElementNS; + scope.registry = registry; + scope.instanceof = isInstance; + scope.reservedTagList = reservedTagList; + scope.getRegisteredDefinition = getRegisteredDefinition; + document.register = document.registerElement; +}); + +(function(scope) { + var useNative = scope.useNative; + var initializeModules = scope.initializeModules; + var isIE = scope.isIE; + if (useNative) { + var nop = function() {}; + scope.watchShadow = nop; + scope.upgrade = nop; + scope.upgradeAll = nop; + scope.upgradeDocumentTree = nop; + scope.upgradeSubtree = nop; + scope.takeRecords = nop; + scope.instanceof = function(obj, base) { + return obj instanceof base; + }; + } else { + initializeModules(); + } + var upgradeDocumentTree = scope.upgradeDocumentTree; + var upgradeDocument = scope.upgradeDocument; + if (!window.wrap) { + if (window.ShadowDOMPolyfill) { + window.wrap = window.ShadowDOMPolyfill.wrapIfNeeded; + window.unwrap = window.ShadowDOMPolyfill.unwrapIfNeeded; + } else { + window.wrap = window.unwrap = function(node) { + return node; + }; + } + } + if (window.HTMLImports) { + window.HTMLImports.__importsParsingHook = function(elt) { + if (elt.import) { + upgradeDocument(wrap(elt.import)); + } + }; + } + function bootstrap() { + upgradeDocumentTree(window.wrap(document)); + window.CustomElements.ready = true; + var requestAnimationFrame = window.requestAnimationFrame || function(f) { + setTimeout(f, 16); + }; + requestAnimationFrame(function() { + setTimeout(function() { + window.CustomElements.readyTime = Date.now(); + if (window.HTMLImports) { + window.CustomElements.elapsed = window.CustomElements.readyTime - window.HTMLImports.readyTime; + } + document.dispatchEvent(new CustomEvent("WebComponentsReady", { + bubbles: true + })); + }); + }); + } + if (document.readyState === "complete" || scope.flags.eager) { + bootstrap(); + } else if (document.readyState === "interactive" && !window.attachEvent && (!window.HTMLImports || window.HTMLImports.ready)) { + bootstrap(); + } else { + var loadEvent = window.HTMLImports && !window.HTMLImports.ready ? "HTMLImportsLoaded" : "DOMContentLoaded"; + window.addEventListener(loadEvent, bootstrap); + } +})(window.CustomElements); \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/webcomponentsjs/CustomElements.min.js b/lamassu-admin-elm/build/bower_components/webcomponentsjs/CustomElements.min.js new file mode 100644 index 00000000..a2761f17 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/webcomponentsjs/CustomElements.min.js @@ -0,0 +1,11 @@ +/** + * @license + * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt + */ +// @version 0.7.22 +"undefined"==typeof WeakMap&&!function(){var e=Object.defineProperty,t=Date.now()%1e9,n=function(){this.name="__st"+(1e9*Math.random()>>>0)+(t++ +"__")};n.prototype={set:function(t,n){var o=t[this.name];return o&&o[0]===t?o[1]=n:e(t,this.name,{value:[t,n],writable:!0}),this},get:function(e){var t;return(t=e[this.name])&&t[0]===e?t[1]:void 0},"delete":function(e){var t=e[this.name];return t&&t[0]===e?(t[0]=t[1]=void 0,!0):!1},has:function(e){var t=e[this.name];return t?t[0]===e:!1}},window.WeakMap=n}(),function(e){function t(e){E.push(e),b||(b=!0,w(o))}function n(e){return window.ShadowDOMPolyfill&&window.ShadowDOMPolyfill.wrapIfNeeded(e)||e}function o(){b=!1;var e=E;E=[],e.sort(function(e,t){return e.uid_-t.uid_});var t=!1;e.forEach(function(e){var n=e.takeRecords();r(e),n.length&&(e.callback_(n,e),t=!0)}),t&&o()}function r(e){e.nodes_.forEach(function(t){var n=v.get(t);n&&n.forEach(function(t){t.observer===e&&t.removeTransientObservers()})})}function i(e,t){for(var n=e;n;n=n.parentNode){var o=v.get(n);if(o)for(var r=0;r0){var r=n[o-1],i=p(r,e);if(i)return void(n[o-1]=i)}else t(this.observer);n[o]=e},addListeners:function(){this.addListeners_(this.target)},addListeners_:function(e){var t=this.options;t.attributes&&e.addEventListener("DOMAttrModified",this,!0),t.characterData&&e.addEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.addEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.addEventListener("DOMNodeRemoved",this,!0)},removeListeners:function(){this.removeListeners_(this.target)},removeListeners_:function(e){var t=this.options;t.attributes&&e.removeEventListener("DOMAttrModified",this,!0),t.characterData&&e.removeEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.removeEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.removeEventListener("DOMNodeRemoved",this,!0)},addTransientObserver:function(e){if(e!==this.target){this.addListeners_(e),this.transientObservedNodes.push(e);var t=v.get(e);t||v.set(e,t=[]),t.push(this)}},removeTransientObservers:function(){var e=this.transientObservedNodes;this.transientObservedNodes=[],e.forEach(function(e){this.removeListeners_(e);for(var t=v.get(e),n=0;n=0)){n.push(e);for(var o,r=e.querySelectorAll("link[rel="+a+"]"),d=0,s=r.length;s>d&&(o=r[d]);d++)o["import"]&&i(o["import"],t,n);t(e)}}var a=window.HTMLImports?window.HTMLImports.IMPORT_LINK_TYPE:"none";e.forDocumentTree=r,e.forSubtree=t}),window.CustomElements.addModule(function(e){function t(e,t){return n(e,t)||o(e,t)}function n(t,n){return e.upgrade(t,n)?!0:void(n&&a(t))}function o(e,t){b(e,function(e){return n(e,t)?!0:void 0})}function r(e){N.push(e),y||(y=!0,setTimeout(i))}function i(){y=!1;for(var e,t=N,n=0,o=t.length;o>n&&(e=t[n]);n++)e();N=[]}function a(e){_?r(function(){d(e)}):d(e)}function d(e){e.__upgraded__&&!e.__attached&&(e.__attached=!0,e.attachedCallback&&e.attachedCallback())}function s(e){u(e),b(e,function(e){u(e)})}function u(e){_?r(function(){c(e)}):c(e)}function c(e){e.__upgraded__&&e.__attached&&(e.__attached=!1,e.detachedCallback&&e.detachedCallback())}function l(e){for(var t=e,n=window.wrap(document);t;){if(t==n)return!0;t=t.parentNode||t.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&t.host}}function f(e){if(e.shadowRoot&&!e.shadowRoot.__watched){g.dom&&console.log("watching shadow-root for: ",e.localName);for(var t=e.shadowRoot;t;)w(t),t=t.olderShadowRoot}}function p(e,n){if(g.dom){var o=n[0];if(o&&"childList"===o.type&&o.addedNodes&&o.addedNodes){for(var r=o.addedNodes[0];r&&r!==document&&!r.host;)r=r.parentNode;var i=r&&(r.URL||r._URL||r.host&&r.host.localName)||"";i=i.split("/?").shift().split("/").pop()}console.group("mutations (%d) [%s]",n.length,i||"")}var a=l(e);n.forEach(function(e){"childList"===e.type&&(M(e.addedNodes,function(e){e.localName&&t(e,a)}),M(e.removedNodes,function(e){e.localName&&s(e)}))}),g.dom&&console.groupEnd()}function m(e){for(e=window.wrap(e),e||(e=window.wrap(document));e.parentNode;)e=e.parentNode;var t=e.__observer;t&&(p(e,t.takeRecords()),i())}function w(e){if(!e.__observer){var t=new MutationObserver(p.bind(this,e));t.observe(e,{childList:!0,subtree:!0}),e.__observer=t}}function v(e){e=window.wrap(e),g.dom&&console.group("upgradeDocument: ",e.baseURI.split("/").pop());var n=e===window.wrap(document);t(e,n),w(e),g.dom&&console.groupEnd()}function h(e){E(e,v)}var g=e.flags,b=e.forSubtree,E=e.forDocumentTree,_=window.MutationObserver._isPolyfilled&&g["throttle-attached"];e.hasPolyfillMutations=_,e.hasThrottledAttached=_;var y=!1,N=[],M=Array.prototype.forEach.call.bind(Array.prototype.forEach),O=Element.prototype.createShadowRoot;O&&(Element.prototype.createShadowRoot=function(){var e=O.call(this);return window.CustomElements.watchShadow(this),e}),e.watchShadow=f,e.upgradeDocumentTree=h,e.upgradeDocument=v,e.upgradeSubtree=o,e.upgradeAll=t,e.attached=a,e.takeRecords=m}),window.CustomElements.addModule(function(e){function t(t,o){if("template"===t.localName&&window.HTMLTemplateElement&&HTMLTemplateElement.decorate&&HTMLTemplateElement.decorate(t),!t.__upgraded__&&t.nodeType===Node.ELEMENT_NODE){var r=t.getAttribute("is"),i=e.getRegisteredDefinition(t.localName)||e.getRegisteredDefinition(r);if(i&&(r&&i.tag==t.localName||!r&&!i["extends"]))return n(t,i,o)}}function n(t,n,r){return a.upgrade&&console.group("upgrade:",t.localName),n.is&&t.setAttribute("is",n.is),o(t,n),t.__upgraded__=!0,i(t),r&&e.attached(t),e.upgradeSubtree(t,r),a.upgrade&&console.groupEnd(),t}function o(e,t){Object.__proto__?e.__proto__=t.prototype:(r(e,t.prototype,t["native"]),e.__proto__=t.prototype)}function r(e,t,n){for(var o={},r=t;r!==n&&r!==HTMLElement.prototype;){for(var i,a=Object.getOwnPropertyNames(r),d=0;i=a[d];d++)o[i]||(Object.defineProperty(e,i,Object.getOwnPropertyDescriptor(r,i)),o[i]=1);r=Object.getPrototypeOf(r)}}function i(e){e.createdCallback&&e.createdCallback()}var a=e.flags;e.upgrade=t,e.upgradeWithDefinition=n,e.implementPrototype=o}),window.CustomElements.addModule(function(e){function t(t,o){var s=o||{};if(!t)throw new Error("document.registerElement: first argument `name` must not be empty");if(t.indexOf("-")<0)throw new Error("document.registerElement: first argument ('name') must contain a dash ('-'). Argument provided was '"+String(t)+"'.");if(r(t))throw new Error("Failed to execute 'registerElement' on 'Document': Registration failed for type '"+String(t)+"'. The type name is invalid.");if(u(t))throw new Error("DuplicateDefinitionError: a type with name '"+String(t)+"' is already registered");return s.prototype||(s.prototype=Object.create(HTMLElement.prototype)),s.__name=t.toLowerCase(),s["extends"]&&(s["extends"]=s["extends"].toLowerCase()),s.lifecycle=s.lifecycle||{},s.ancestry=i(s["extends"]),a(s),d(s),n(s.prototype),c(s.__name,s),s.ctor=l(s),s.ctor.prototype=s.prototype,s.prototype.constructor=s.ctor,e.ready&&v(document),s.ctor}function n(e){if(!e.setAttribute._polyfilled){var t=e.setAttribute;e.setAttribute=function(e,n){o.call(this,e,n,t)};var n=e.removeAttribute;e.removeAttribute=function(e){o.call(this,e,null,n)},e.setAttribute._polyfilled=!0}}function o(e,t,n){e=e.toLowerCase();var o=this.getAttribute(e);n.apply(this,arguments);var r=this.getAttribute(e);this.attributeChangedCallback&&r!==o&&this.attributeChangedCallback(e,o,r)}function r(e){for(var t=0;t<_.length;t++)if(e===_[t])return!0}function i(e){var t=u(e);return t?i(t["extends"]).concat([t]):[]}function a(e){for(var t,n=e["extends"],o=0;t=e.ancestry[o];o++)n=t.is&&t.tag;e.tag=n||e.__name,n&&(e.is=e.__name)}function d(e){if(!Object.__proto__){var t=HTMLElement.prototype;if(e.is){var n=document.createElement(e.tag);t=Object.getPrototypeOf(n)}for(var o,r=e.prototype,i=!1;r;)r==t&&(i=!0),o=Object.getPrototypeOf(r),o&&(r.__proto__=o),r=o;i||console.warn(e.tag+" prototype not found in prototype chain for "+e.is),e["native"]=t}}function s(e){return g(M(e.tag),e)}function u(e){return e?y[e.toLowerCase()]:void 0}function c(e,t){y[e]=t}function l(e){return function(){return s(e)}}function f(e,t,n){return e===N?p(t,n):O(e,t)}function p(e,t){e&&(e=e.toLowerCase()),t&&(t=t.toLowerCase());var n=u(t||e);if(n){if(e==n.tag&&t==n.is)return new n.ctor;if(!t&&!n.is)return new n.ctor}var o;return t?(o=p(e),o.setAttribute("is",t),o):(o=M(e),e.indexOf("-")>=0&&b(o,HTMLElement),o)}function m(e,t){var n=e[t];e[t]=function(){var e=n.apply(this,arguments);return h(e),e}}var w,v=(e.isIE,e.upgradeDocumentTree),h=e.upgradeAll,g=e.upgradeWithDefinition,b=e.implementPrototype,E=e.useNative,_=["annotation-xml","color-profile","font-face","font-face-src","font-face-uri","font-face-format","font-face-name","missing-glyph"],y={},N="http://www.w3.org/1999/xhtml",M=document.createElement.bind(document),O=document.createElementNS.bind(document);w=Object.__proto__||E?function(e,t){return e instanceof t}:function(e,t){if(e instanceof t)return!0;for(var n=e;n;){if(n===t.prototype)return!0;n=n.__proto__}return!1},m(Node.prototype,"cloneNode"),m(document,"importNode"),document.registerElement=t,document.createElement=p,document.createElementNS=f,e.registry=y,e["instanceof"]=w,e.reservedTagList=_,e.getRegisteredDefinition=u,document.register=document.registerElement}),function(e){function t(){i(window.wrap(document)),window.CustomElements.ready=!0;var e=window.requestAnimationFrame||function(e){setTimeout(e,16)};e(function(){setTimeout(function(){window.CustomElements.readyTime=Date.now(),window.HTMLImports&&(window.CustomElements.elapsed=window.CustomElements.readyTime-window.HTMLImports.readyTime),document.dispatchEvent(new CustomEvent("WebComponentsReady",{bubbles:!0}))})})}var n=e.useNative,o=e.initializeModules;e.isIE;if(n){var r=function(){};e.watchShadow=r,e.upgrade=r,e.upgradeAll=r,e.upgradeDocumentTree=r,e.upgradeSubtree=r,e.takeRecords=r,e["instanceof"]=function(e,t){return e instanceof t}}else o();var i=e.upgradeDocumentTree,a=e.upgradeDocument;if(window.wrap||(window.ShadowDOMPolyfill?(window.wrap=window.ShadowDOMPolyfill.wrapIfNeeded,window.unwrap=window.ShadowDOMPolyfill.unwrapIfNeeded):window.wrap=window.unwrap=function(e){return e}),window.HTMLImports&&(window.HTMLImports.__importsParsingHook=function(e){e["import"]&&a(wrap(e["import"]))}),"complete"===document.readyState||e.flags.eager)t();else if("interactive"!==document.readyState||window.attachEvent||window.HTMLImports&&!window.HTMLImports.ready){var d=window.HTMLImports&&!window.HTMLImports.ready?"HTMLImportsLoaded":"DOMContentLoaded";window.addEventListener(d,t)}else t()}(window.CustomElements); \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/webcomponentsjs/HTMLImports.js b/lamassu-admin-elm/build/bower_components/webcomponentsjs/HTMLImports.js new file mode 100644 index 00000000..cc8ac641 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/webcomponentsjs/HTMLImports.js @@ -0,0 +1,1157 @@ +/** + * @license + * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt + */ +// @version 0.7.22 +if (typeof WeakMap === "undefined") { + (function() { + var defineProperty = Object.defineProperty; + var counter = Date.now() % 1e9; + var WeakMap = function() { + this.name = "__st" + (Math.random() * 1e9 >>> 0) + (counter++ + "__"); + }; + WeakMap.prototype = { + set: function(key, value) { + var entry = key[this.name]; + if (entry && entry[0] === key) entry[1] = value; else defineProperty(key, this.name, { + value: [ key, value ], + writable: true + }); + return this; + }, + get: function(key) { + var entry; + return (entry = key[this.name]) && entry[0] === key ? entry[1] : undefined; + }, + "delete": function(key) { + var entry = key[this.name]; + if (!entry || entry[0] !== key) return false; + entry[0] = entry[1] = undefined; + return true; + }, + has: function(key) { + var entry = key[this.name]; + if (!entry) return false; + return entry[0] === key; + } + }; + window.WeakMap = WeakMap; + })(); +} + +(function(global) { + if (global.JsMutationObserver) { + return; + } + var registrationsTable = new WeakMap(); + var setImmediate; + if (/Trident|Edge/.test(navigator.userAgent)) { + setImmediate = setTimeout; + } else if (window.setImmediate) { + setImmediate = window.setImmediate; + } else { + var setImmediateQueue = []; + var sentinel = String(Math.random()); + window.addEventListener("message", function(e) { + if (e.data === sentinel) { + var queue = setImmediateQueue; + setImmediateQueue = []; + queue.forEach(function(func) { + func(); + }); + } + }); + setImmediate = function(func) { + setImmediateQueue.push(func); + window.postMessage(sentinel, "*"); + }; + } + var isScheduled = false; + var scheduledObservers = []; + function scheduleCallback(observer) { + scheduledObservers.push(observer); + if (!isScheduled) { + isScheduled = true; + setImmediate(dispatchCallbacks); + } + } + function wrapIfNeeded(node) { + return window.ShadowDOMPolyfill && window.ShadowDOMPolyfill.wrapIfNeeded(node) || node; + } + function dispatchCallbacks() { + isScheduled = false; + var observers = scheduledObservers; + scheduledObservers = []; + observers.sort(function(o1, o2) { + return o1.uid_ - o2.uid_; + }); + var anyNonEmpty = false; + observers.forEach(function(observer) { + var queue = observer.takeRecords(); + removeTransientObserversFor(observer); + if (queue.length) { + observer.callback_(queue, observer); + anyNonEmpty = true; + } + }); + if (anyNonEmpty) dispatchCallbacks(); + } + function removeTransientObserversFor(observer) { + observer.nodes_.forEach(function(node) { + var registrations = registrationsTable.get(node); + if (!registrations) return; + registrations.forEach(function(registration) { + if (registration.observer === observer) registration.removeTransientObservers(); + }); + }); + } + function forEachAncestorAndObserverEnqueueRecord(target, callback) { + for (var node = target; node; node = node.parentNode) { + var registrations = registrationsTable.get(node); + if (registrations) { + for (var j = 0; j < registrations.length; j++) { + var registration = registrations[j]; + var options = registration.options; + if (node !== target && !options.subtree) continue; + var record = callback(options); + if (record) registration.enqueue(record); + } + } + } + } + var uidCounter = 0; + function JsMutationObserver(callback) { + this.callback_ = callback; + this.nodes_ = []; + this.records_ = []; + this.uid_ = ++uidCounter; + } + JsMutationObserver.prototype = { + observe: function(target, options) { + target = wrapIfNeeded(target); + if (!options.childList && !options.attributes && !options.characterData || options.attributeOldValue && !options.attributes || options.attributeFilter && options.attributeFilter.length && !options.attributes || options.characterDataOldValue && !options.characterData) { + throw new SyntaxError(); + } + var registrations = registrationsTable.get(target); + if (!registrations) registrationsTable.set(target, registrations = []); + var registration; + for (var i = 0; i < registrations.length; i++) { + if (registrations[i].observer === this) { + registration = registrations[i]; + registration.removeListeners(); + registration.options = options; + break; + } + } + if (!registration) { + registration = new Registration(this, target, options); + registrations.push(registration); + this.nodes_.push(target); + } + registration.addListeners(); + }, + disconnect: function() { + this.nodes_.forEach(function(node) { + var registrations = registrationsTable.get(node); + for (var i = 0; i < registrations.length; i++) { + var registration = registrations[i]; + if (registration.observer === this) { + registration.removeListeners(); + registrations.splice(i, 1); + break; + } + } + }, this); + this.records_ = []; + }, + takeRecords: function() { + var copyOfRecords = this.records_; + this.records_ = []; + return copyOfRecords; + } + }; + function MutationRecord(type, target) { + this.type = type; + this.target = target; + this.addedNodes = []; + this.removedNodes = []; + this.previousSibling = null; + this.nextSibling = null; + this.attributeName = null; + this.attributeNamespace = null; + this.oldValue = null; + } + function copyMutationRecord(original) { + var record = new MutationRecord(original.type, original.target); + record.addedNodes = original.addedNodes.slice(); + record.removedNodes = original.removedNodes.slice(); + record.previousSibling = original.previousSibling; + record.nextSibling = original.nextSibling; + record.attributeName = original.attributeName; + record.attributeNamespace = original.attributeNamespace; + record.oldValue = original.oldValue; + return record; + } + var currentRecord, recordWithOldValue; + function getRecord(type, target) { + return currentRecord = new MutationRecord(type, target); + } + function getRecordWithOldValue(oldValue) { + if (recordWithOldValue) return recordWithOldValue; + recordWithOldValue = copyMutationRecord(currentRecord); + recordWithOldValue.oldValue = oldValue; + return recordWithOldValue; + } + function clearRecords() { + currentRecord = recordWithOldValue = undefined; + } + function recordRepresentsCurrentMutation(record) { + return record === recordWithOldValue || record === currentRecord; + } + function selectRecord(lastRecord, newRecord) { + if (lastRecord === newRecord) return lastRecord; + if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord)) return recordWithOldValue; + return null; + } + function Registration(observer, target, options) { + this.observer = observer; + this.target = target; + this.options = options; + this.transientObservedNodes = []; + } + Registration.prototype = { + enqueue: function(record) { + var records = this.observer.records_; + var length = records.length; + if (records.length > 0) { + var lastRecord = records[length - 1]; + var recordToReplaceLast = selectRecord(lastRecord, record); + if (recordToReplaceLast) { + records[length - 1] = recordToReplaceLast; + return; + } + } else { + scheduleCallback(this.observer); + } + records[length] = record; + }, + addListeners: function() { + this.addListeners_(this.target); + }, + addListeners_: function(node) { + var options = this.options; + if (options.attributes) node.addEventListener("DOMAttrModified", this, true); + if (options.characterData) node.addEventListener("DOMCharacterDataModified", this, true); + if (options.childList) node.addEventListener("DOMNodeInserted", this, true); + if (options.childList || options.subtree) node.addEventListener("DOMNodeRemoved", this, true); + }, + removeListeners: function() { + this.removeListeners_(this.target); + }, + removeListeners_: function(node) { + var options = this.options; + if (options.attributes) node.removeEventListener("DOMAttrModified", this, true); + if (options.characterData) node.removeEventListener("DOMCharacterDataModified", this, true); + if (options.childList) node.removeEventListener("DOMNodeInserted", this, true); + if (options.childList || options.subtree) node.removeEventListener("DOMNodeRemoved", this, true); + }, + addTransientObserver: function(node) { + if (node === this.target) return; + this.addListeners_(node); + this.transientObservedNodes.push(node); + var registrations = registrationsTable.get(node); + if (!registrations) registrationsTable.set(node, registrations = []); + registrations.push(this); + }, + removeTransientObservers: function() { + var transientObservedNodes = this.transientObservedNodes; + this.transientObservedNodes = []; + transientObservedNodes.forEach(function(node) { + this.removeListeners_(node); + var registrations = registrationsTable.get(node); + for (var i = 0; i < registrations.length; i++) { + if (registrations[i] === this) { + registrations.splice(i, 1); + break; + } + } + }, this); + }, + handleEvent: function(e) { + e.stopImmediatePropagation(); + switch (e.type) { + case "DOMAttrModified": + var name = e.attrName; + var namespace = e.relatedNode.namespaceURI; + var target = e.target; + var record = new getRecord("attributes", target); + record.attributeName = name; + record.attributeNamespace = namespace; + var oldValue = e.attrChange === MutationEvent.ADDITION ? null : e.prevValue; + forEachAncestorAndObserverEnqueueRecord(target, function(options) { + if (!options.attributes) return; + if (options.attributeFilter && options.attributeFilter.length && options.attributeFilter.indexOf(name) === -1 && options.attributeFilter.indexOf(namespace) === -1) { + return; + } + if (options.attributeOldValue) return getRecordWithOldValue(oldValue); + return record; + }); + break; + + case "DOMCharacterDataModified": + var target = e.target; + var record = getRecord("characterData", target); + var oldValue = e.prevValue; + forEachAncestorAndObserverEnqueueRecord(target, function(options) { + if (!options.characterData) return; + if (options.characterDataOldValue) return getRecordWithOldValue(oldValue); + return record; + }); + break; + + case "DOMNodeRemoved": + this.addTransientObserver(e.target); + + case "DOMNodeInserted": + var changedNode = e.target; + var addedNodes, removedNodes; + if (e.type === "DOMNodeInserted") { + addedNodes = [ changedNode ]; + removedNodes = []; + } else { + addedNodes = []; + removedNodes = [ changedNode ]; + } + var previousSibling = changedNode.previousSibling; + var nextSibling = changedNode.nextSibling; + var record = getRecord("childList", e.target.parentNode); + record.addedNodes = addedNodes; + record.removedNodes = removedNodes; + record.previousSibling = previousSibling; + record.nextSibling = nextSibling; + forEachAncestorAndObserverEnqueueRecord(e.relatedNode, function(options) { + if (!options.childList) return; + return record; + }); + } + clearRecords(); + } + }; + global.JsMutationObserver = JsMutationObserver; + if (!global.MutationObserver) { + global.MutationObserver = JsMutationObserver; + JsMutationObserver._isPolyfilled = true; + } +})(self); + +(function(scope) { + "use strict"; + if (!window.performance) { + var start = Date.now(); + window.performance = { + now: function() { + return Date.now() - start; + } + }; + } + if (!window.requestAnimationFrame) { + window.requestAnimationFrame = function() { + var nativeRaf = window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame; + return nativeRaf ? function(callback) { + return nativeRaf(function() { + callback(performance.now()); + }); + } : function(callback) { + return window.setTimeout(callback, 1e3 / 60); + }; + }(); + } + if (!window.cancelAnimationFrame) { + window.cancelAnimationFrame = function() { + return window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || function(id) { + clearTimeout(id); + }; + }(); + } + var workingDefaultPrevented = function() { + var e = document.createEvent("Event"); + e.initEvent("foo", true, true); + e.preventDefault(); + return e.defaultPrevented; + }(); + if (!workingDefaultPrevented) { + var origPreventDefault = Event.prototype.preventDefault; + Event.prototype.preventDefault = function() { + if (!this.cancelable) { + return; + } + origPreventDefault.call(this); + Object.defineProperty(this, "defaultPrevented", { + get: function() { + return true; + }, + configurable: true + }); + }; + } + var isIE = /Trident/.test(navigator.userAgent); + if (!window.CustomEvent || isIE && typeof window.CustomEvent !== "function") { + window.CustomEvent = function(inType, params) { + params = params || {}; + var e = document.createEvent("CustomEvent"); + e.initCustomEvent(inType, Boolean(params.bubbles), Boolean(params.cancelable), params.detail); + return e; + }; + window.CustomEvent.prototype = window.Event.prototype; + } + if (!window.Event || isIE && typeof window.Event !== "function") { + var origEvent = window.Event; + window.Event = function(inType, params) { + params = params || {}; + var e = document.createEvent("Event"); + e.initEvent(inType, Boolean(params.bubbles), Boolean(params.cancelable)); + return e; + }; + window.Event.prototype = origEvent.prototype; + } +})(window.WebComponents); + +window.HTMLImports = window.HTMLImports || { + flags: {} +}; + +(function(scope) { + var IMPORT_LINK_TYPE = "import"; + var useNative = Boolean(IMPORT_LINK_TYPE in document.createElement("link")); + var hasShadowDOMPolyfill = Boolean(window.ShadowDOMPolyfill); + var wrap = function(node) { + return hasShadowDOMPolyfill ? window.ShadowDOMPolyfill.wrapIfNeeded(node) : node; + }; + var rootDocument = wrap(document); + var currentScriptDescriptor = { + get: function() { + var script = window.HTMLImports.currentScript || document.currentScript || (document.readyState !== "complete" ? document.scripts[document.scripts.length - 1] : null); + return wrap(script); + }, + configurable: true + }; + Object.defineProperty(document, "_currentScript", currentScriptDescriptor); + Object.defineProperty(rootDocument, "_currentScript", currentScriptDescriptor); + var isIE = /Trident/.test(navigator.userAgent); + function whenReady(callback, doc) { + doc = doc || rootDocument; + whenDocumentReady(function() { + watchImportsLoad(callback, doc); + }, doc); + } + var requiredReadyState = isIE ? "complete" : "interactive"; + var READY_EVENT = "readystatechange"; + function isDocumentReady(doc) { + return doc.readyState === "complete" || doc.readyState === requiredReadyState; + } + function whenDocumentReady(callback, doc) { + if (!isDocumentReady(doc)) { + var checkReady = function() { + if (doc.readyState === "complete" || doc.readyState === requiredReadyState) { + doc.removeEventListener(READY_EVENT, checkReady); + whenDocumentReady(callback, doc); + } + }; + doc.addEventListener(READY_EVENT, checkReady); + } else if (callback) { + callback(); + } + } + function markTargetLoaded(event) { + event.target.__loaded = true; + } + function watchImportsLoad(callback, doc) { + var imports = doc.querySelectorAll("link[rel=import]"); + var parsedCount = 0, importCount = imports.length, newImports = [], errorImports = []; + function checkDone() { + if (parsedCount == importCount && callback) { + callback({ + allImports: imports, + loadedImports: newImports, + errorImports: errorImports + }); + } + } + function loadedImport(e) { + markTargetLoaded(e); + newImports.push(this); + parsedCount++; + checkDone(); + } + function errorLoadingImport(e) { + errorImports.push(this); + parsedCount++; + checkDone(); + } + if (importCount) { + for (var i = 0, imp; i < importCount && (imp = imports[i]); i++) { + if (isImportLoaded(imp)) { + newImports.push(this); + parsedCount++; + checkDone(); + } else { + imp.addEventListener("load", loadedImport); + imp.addEventListener("error", errorLoadingImport); + } + } + } else { + checkDone(); + } + } + function isImportLoaded(link) { + return useNative ? link.__loaded || link.import && link.import.readyState !== "loading" : link.__importParsed; + } + if (useNative) { + new MutationObserver(function(mxns) { + for (var i = 0, l = mxns.length, m; i < l && (m = mxns[i]); i++) { + if (m.addedNodes) { + handleImports(m.addedNodes); + } + } + }).observe(document.head, { + childList: true + }); + function handleImports(nodes) { + for (var i = 0, l = nodes.length, n; i < l && (n = nodes[i]); i++) { + if (isImport(n)) { + handleImport(n); + } + } + } + function isImport(element) { + return element.localName === "link" && element.rel === "import"; + } + function handleImport(element) { + var loaded = element.import; + if (loaded) { + markTargetLoaded({ + target: element + }); + } else { + element.addEventListener("load", markTargetLoaded); + element.addEventListener("error", markTargetLoaded); + } + } + (function() { + if (document.readyState === "loading") { + var imports = document.querySelectorAll("link[rel=import]"); + for (var i = 0, l = imports.length, imp; i < l && (imp = imports[i]); i++) { + handleImport(imp); + } + } + })(); + } + whenReady(function(detail) { + window.HTMLImports.ready = true; + window.HTMLImports.readyTime = new Date().getTime(); + var evt = rootDocument.createEvent("CustomEvent"); + evt.initCustomEvent("HTMLImportsLoaded", true, true, detail); + rootDocument.dispatchEvent(evt); + }); + scope.IMPORT_LINK_TYPE = IMPORT_LINK_TYPE; + scope.useNative = useNative; + scope.rootDocument = rootDocument; + scope.whenReady = whenReady; + scope.isIE = isIE; +})(window.HTMLImports); + +(function(scope) { + var modules = []; + var addModule = function(module) { + modules.push(module); + }; + var initializeModules = function() { + modules.forEach(function(module) { + module(scope); + }); + }; + scope.addModule = addModule; + scope.initializeModules = initializeModules; +})(window.HTMLImports); + +window.HTMLImports.addModule(function(scope) { + var CSS_URL_REGEXP = /(url\()([^)]*)(\))/g; + var CSS_IMPORT_REGEXP = /(@import[\s]+(?!url\())([^;]*)(;)/g; + var path = { + resolveUrlsInStyle: function(style, linkUrl) { + var doc = style.ownerDocument; + var resolver = doc.createElement("a"); + style.textContent = this.resolveUrlsInCssText(style.textContent, linkUrl, resolver); + return style; + }, + resolveUrlsInCssText: function(cssText, linkUrl, urlObj) { + var r = this.replaceUrls(cssText, urlObj, linkUrl, CSS_URL_REGEXP); + r = this.replaceUrls(r, urlObj, linkUrl, CSS_IMPORT_REGEXP); + return r; + }, + replaceUrls: function(text, urlObj, linkUrl, regexp) { + return text.replace(regexp, function(m, pre, url, post) { + var urlPath = url.replace(/["']/g, ""); + if (linkUrl) { + urlPath = new URL(urlPath, linkUrl).href; + } + urlObj.href = urlPath; + urlPath = urlObj.href; + return pre + "'" + urlPath + "'" + post; + }); + } + }; + scope.path = path; +}); + +window.HTMLImports.addModule(function(scope) { + var xhr = { + async: true, + ok: function(request) { + return request.status >= 200 && request.status < 300 || request.status === 304 || request.status === 0; + }, + load: function(url, next, nextContext) { + var request = new XMLHttpRequest(); + if (scope.flags.debug || scope.flags.bust) { + url += "?" + Math.random(); + } + request.open("GET", url, xhr.async); + request.addEventListener("readystatechange", function(e) { + if (request.readyState === 4) { + var redirectedUrl = null; + try { + var locationHeader = request.getResponseHeader("Location"); + if (locationHeader) { + redirectedUrl = locationHeader.substr(0, 1) === "/" ? location.origin + locationHeader : locationHeader; + } + } catch (e) { + console.error(e.message); + } + next.call(nextContext, !xhr.ok(request) && request, request.response || request.responseText, redirectedUrl); + } + }); + request.send(); + return request; + }, + loadDocument: function(url, next, nextContext) { + this.load(url, next, nextContext).responseType = "document"; + } + }; + scope.xhr = xhr; +}); + +window.HTMLImports.addModule(function(scope) { + var xhr = scope.xhr; + var flags = scope.flags; + var Loader = function(onLoad, onComplete) { + this.cache = {}; + this.onload = onLoad; + this.oncomplete = onComplete; + this.inflight = 0; + this.pending = {}; + }; + Loader.prototype = { + addNodes: function(nodes) { + this.inflight += nodes.length; + for (var i = 0, l = nodes.length, n; i < l && (n = nodes[i]); i++) { + this.require(n); + } + this.checkDone(); + }, + addNode: function(node) { + this.inflight++; + this.require(node); + this.checkDone(); + }, + require: function(elt) { + var url = elt.src || elt.href; + elt.__nodeUrl = url; + if (!this.dedupe(url, elt)) { + this.fetch(url, elt); + } + }, + dedupe: function(url, elt) { + if (this.pending[url]) { + this.pending[url].push(elt); + return true; + } + var resource; + if (this.cache[url]) { + this.onload(url, elt, this.cache[url]); + this.tail(); + return true; + } + this.pending[url] = [ elt ]; + return false; + }, + fetch: function(url, elt) { + flags.load && console.log("fetch", url, elt); + if (!url) { + setTimeout(function() { + this.receive(url, elt, { + error: "href must be specified" + }, null); + }.bind(this), 0); + } else if (url.match(/^data:/)) { + var pieces = url.split(","); + var header = pieces[0]; + var body = pieces[1]; + if (header.indexOf(";base64") > -1) { + body = atob(body); + } else { + body = decodeURIComponent(body); + } + setTimeout(function() { + this.receive(url, elt, null, body); + }.bind(this), 0); + } else { + var receiveXhr = function(err, resource, redirectedUrl) { + this.receive(url, elt, err, resource, redirectedUrl); + }.bind(this); + xhr.load(url, receiveXhr); + } + }, + receive: function(url, elt, err, resource, redirectedUrl) { + this.cache[url] = resource; + var $p = this.pending[url]; + for (var i = 0, l = $p.length, p; i < l && (p = $p[i]); i++) { + this.onload(url, p, resource, err, redirectedUrl); + this.tail(); + } + this.pending[url] = null; + }, + tail: function() { + --this.inflight; + this.checkDone(); + }, + checkDone: function() { + if (!this.inflight) { + this.oncomplete(); + } + } + }; + scope.Loader = Loader; +}); + +window.HTMLImports.addModule(function(scope) { + var Observer = function(addCallback) { + this.addCallback = addCallback; + this.mo = new MutationObserver(this.handler.bind(this)); + }; + Observer.prototype = { + handler: function(mutations) { + for (var i = 0, l = mutations.length, m; i < l && (m = mutations[i]); i++) { + if (m.type === "childList" && m.addedNodes.length) { + this.addedNodes(m.addedNodes); + } + } + }, + addedNodes: function(nodes) { + if (this.addCallback) { + this.addCallback(nodes); + } + for (var i = 0, l = nodes.length, n, loading; i < l && (n = nodes[i]); i++) { + if (n.children && n.children.length) { + this.addedNodes(n.children); + } + } + }, + observe: function(root) { + this.mo.observe(root, { + childList: true, + subtree: true + }); + } + }; + scope.Observer = Observer; +}); + +window.HTMLImports.addModule(function(scope) { + var path = scope.path; + var rootDocument = scope.rootDocument; + var flags = scope.flags; + var isIE = scope.isIE; + var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE; + var IMPORT_SELECTOR = "link[rel=" + IMPORT_LINK_TYPE + "]"; + var importParser = { + documentSelectors: IMPORT_SELECTOR, + importsSelectors: [ IMPORT_SELECTOR, "link[rel=stylesheet]:not([type])", "style:not([type])", "script:not([type])", 'script[type="application/javascript"]', 'script[type="text/javascript"]' ].join(","), + map: { + link: "parseLink", + script: "parseScript", + style: "parseStyle" + }, + dynamicElements: [], + parseNext: function() { + var next = this.nextToParse(); + if (next) { + this.parse(next); + } + }, + parse: function(elt) { + if (this.isParsed(elt)) { + flags.parse && console.log("[%s] is already parsed", elt.localName); + return; + } + var fn = this[this.map[elt.localName]]; + if (fn) { + this.markParsing(elt); + fn.call(this, elt); + } + }, + parseDynamic: function(elt, quiet) { + this.dynamicElements.push(elt); + if (!quiet) { + this.parseNext(); + } + }, + markParsing: function(elt) { + flags.parse && console.log("parsing", elt); + this.parsingElement = elt; + }, + markParsingComplete: function(elt) { + elt.__importParsed = true; + this.markDynamicParsingComplete(elt); + if (elt.__importElement) { + elt.__importElement.__importParsed = true; + this.markDynamicParsingComplete(elt.__importElement); + } + this.parsingElement = null; + flags.parse && console.log("completed", elt); + }, + markDynamicParsingComplete: function(elt) { + var i = this.dynamicElements.indexOf(elt); + if (i >= 0) { + this.dynamicElements.splice(i, 1); + } + }, + parseImport: function(elt) { + elt.import = elt.__doc; + if (window.HTMLImports.__importsParsingHook) { + window.HTMLImports.__importsParsingHook(elt); + } + if (elt.import) { + elt.import.__importParsed = true; + } + this.markParsingComplete(elt); + if (elt.__resource && !elt.__error) { + elt.dispatchEvent(new CustomEvent("load", { + bubbles: false + })); + } else { + elt.dispatchEvent(new CustomEvent("error", { + bubbles: false + })); + } + if (elt.__pending) { + var fn; + while (elt.__pending.length) { + fn = elt.__pending.shift(); + if (fn) { + fn({ + target: elt + }); + } + } + } + this.parseNext(); + }, + parseLink: function(linkElt) { + if (nodeIsImport(linkElt)) { + this.parseImport(linkElt); + } else { + linkElt.href = linkElt.href; + this.parseGeneric(linkElt); + } + }, + parseStyle: function(elt) { + var src = elt; + elt = cloneStyle(elt); + src.__appliedElement = elt; + elt.__importElement = src; + this.parseGeneric(elt); + }, + parseGeneric: function(elt) { + this.trackElement(elt); + this.addElementToDocument(elt); + }, + rootImportForElement: function(elt) { + var n = elt; + while (n.ownerDocument.__importLink) { + n = n.ownerDocument.__importLink; + } + return n; + }, + addElementToDocument: function(elt) { + var port = this.rootImportForElement(elt.__importElement || elt); + port.parentNode.insertBefore(elt, port); + }, + trackElement: function(elt, callback) { + var self = this; + var done = function(e) { + elt.removeEventListener("load", done); + elt.removeEventListener("error", done); + if (callback) { + callback(e); + } + self.markParsingComplete(elt); + self.parseNext(); + }; + elt.addEventListener("load", done); + elt.addEventListener("error", done); + if (isIE && elt.localName === "style") { + var fakeLoad = false; + if (elt.textContent.indexOf("@import") == -1) { + fakeLoad = true; + } else if (elt.sheet) { + fakeLoad = true; + var csr = elt.sheet.cssRules; + var len = csr ? csr.length : 0; + for (var i = 0, r; i < len && (r = csr[i]); i++) { + if (r.type === CSSRule.IMPORT_RULE) { + fakeLoad = fakeLoad && Boolean(r.styleSheet); + } + } + } + if (fakeLoad) { + setTimeout(function() { + elt.dispatchEvent(new CustomEvent("load", { + bubbles: false + })); + }); + } + } + }, + parseScript: function(scriptElt) { + var script = document.createElement("script"); + script.__importElement = scriptElt; + script.src = scriptElt.src ? scriptElt.src : generateScriptDataUrl(scriptElt); + scope.currentScript = scriptElt; + this.trackElement(script, function(e) { + if (script.parentNode) { + script.parentNode.removeChild(script); + } + scope.currentScript = null; + }); + this.addElementToDocument(script); + }, + nextToParse: function() { + this._mayParse = []; + return !this.parsingElement && (this.nextToParseInDoc(rootDocument) || this.nextToParseDynamic()); + }, + nextToParseInDoc: function(doc, link) { + if (doc && this._mayParse.indexOf(doc) < 0) { + this._mayParse.push(doc); + var nodes = doc.querySelectorAll(this.parseSelectorsForNode(doc)); + for (var i = 0, l = nodes.length, n; i < l && (n = nodes[i]); i++) { + if (!this.isParsed(n)) { + if (this.hasResource(n)) { + return nodeIsImport(n) ? this.nextToParseInDoc(n.__doc, n) : n; + } else { + return; + } + } + } + } + return link; + }, + nextToParseDynamic: function() { + return this.dynamicElements[0]; + }, + parseSelectorsForNode: function(node) { + var doc = node.ownerDocument || node; + return doc === rootDocument ? this.documentSelectors : this.importsSelectors; + }, + isParsed: function(node) { + return node.__importParsed; + }, + needsDynamicParsing: function(elt) { + return this.dynamicElements.indexOf(elt) >= 0; + }, + hasResource: function(node) { + if (nodeIsImport(node) && node.__doc === undefined) { + return false; + } + return true; + } + }; + function nodeIsImport(elt) { + return elt.localName === "link" && elt.rel === IMPORT_LINK_TYPE; + } + function generateScriptDataUrl(script) { + var scriptContent = generateScriptContent(script); + return "data:text/javascript;charset=utf-8," + encodeURIComponent(scriptContent); + } + function generateScriptContent(script) { + return script.textContent + generateSourceMapHint(script); + } + function generateSourceMapHint(script) { + var owner = script.ownerDocument; + owner.__importedScripts = owner.__importedScripts || 0; + var moniker = script.ownerDocument.baseURI; + var num = owner.__importedScripts ? "-" + owner.__importedScripts : ""; + owner.__importedScripts++; + return "\n//# sourceURL=" + moniker + num + ".js\n"; + } + function cloneStyle(style) { + var clone = style.ownerDocument.createElement("style"); + clone.textContent = style.textContent; + path.resolveUrlsInStyle(clone); + return clone; + } + scope.parser = importParser; + scope.IMPORT_SELECTOR = IMPORT_SELECTOR; +}); + +window.HTMLImports.addModule(function(scope) { + var flags = scope.flags; + var IMPORT_LINK_TYPE = scope.IMPORT_LINK_TYPE; + var IMPORT_SELECTOR = scope.IMPORT_SELECTOR; + var rootDocument = scope.rootDocument; + var Loader = scope.Loader; + var Observer = scope.Observer; + var parser = scope.parser; + var importer = { + documents: {}, + documentPreloadSelectors: IMPORT_SELECTOR, + importsPreloadSelectors: [ IMPORT_SELECTOR ].join(","), + loadNode: function(node) { + importLoader.addNode(node); + }, + loadSubtree: function(parent) { + var nodes = this.marshalNodes(parent); + importLoader.addNodes(nodes); + }, + marshalNodes: function(parent) { + return parent.querySelectorAll(this.loadSelectorsForNode(parent)); + }, + loadSelectorsForNode: function(node) { + var doc = node.ownerDocument || node; + return doc === rootDocument ? this.documentPreloadSelectors : this.importsPreloadSelectors; + }, + loaded: function(url, elt, resource, err, redirectedUrl) { + flags.load && console.log("loaded", url, elt); + elt.__resource = resource; + elt.__error = err; + if (isImportLink(elt)) { + var doc = this.documents[url]; + if (doc === undefined) { + doc = err ? null : makeDocument(resource, redirectedUrl || url); + if (doc) { + doc.__importLink = elt; + this.bootDocument(doc); + } + this.documents[url] = doc; + } + elt.__doc = doc; + } + parser.parseNext(); + }, + bootDocument: function(doc) { + this.loadSubtree(doc); + this.observer.observe(doc); + parser.parseNext(); + }, + loadedAll: function() { + parser.parseNext(); + } + }; + var importLoader = new Loader(importer.loaded.bind(importer), importer.loadedAll.bind(importer)); + importer.observer = new Observer(); + function isImportLink(elt) { + return isLinkRel(elt, IMPORT_LINK_TYPE); + } + function isLinkRel(elt, rel) { + return elt.localName === "link" && elt.getAttribute("rel") === rel; + } + function hasBaseURIAccessor(doc) { + return !!Object.getOwnPropertyDescriptor(doc, "baseURI"); + } + function makeDocument(resource, url) { + var doc = document.implementation.createHTMLDocument(IMPORT_LINK_TYPE); + doc._URL = url; + var base = doc.createElement("base"); + base.setAttribute("href", url); + if (!doc.baseURI && !hasBaseURIAccessor(doc)) { + Object.defineProperty(doc, "baseURI", { + value: url + }); + } + var meta = doc.createElement("meta"); + meta.setAttribute("charset", "utf-8"); + doc.head.appendChild(meta); + doc.head.appendChild(base); + doc.body.innerHTML = resource; + if (window.HTMLTemplateElement && HTMLTemplateElement.bootstrap) { + HTMLTemplateElement.bootstrap(doc); + } + return doc; + } + if (!document.baseURI) { + var baseURIDescriptor = { + get: function() { + var base = document.querySelector("base"); + return base ? base.href : window.location.href; + }, + configurable: true + }; + Object.defineProperty(document, "baseURI", baseURIDescriptor); + Object.defineProperty(rootDocument, "baseURI", baseURIDescriptor); + } + scope.importer = importer; + scope.importLoader = importLoader; +}); + +window.HTMLImports.addModule(function(scope) { + var parser = scope.parser; + var importer = scope.importer; + var dynamic = { + added: function(nodes) { + var owner, parsed, loading; + for (var i = 0, l = nodes.length, n; i < l && (n = nodes[i]); i++) { + if (!owner) { + owner = n.ownerDocument; + parsed = parser.isParsed(owner); + } + loading = this.shouldLoadNode(n); + if (loading) { + importer.loadNode(n); + } + if (this.shouldParseNode(n) && parsed) { + parser.parseDynamic(n, loading); + } + } + }, + shouldLoadNode: function(node) { + return node.nodeType === 1 && matches.call(node, importer.loadSelectorsForNode(node)); + }, + shouldParseNode: function(node) { + return node.nodeType === 1 && matches.call(node, parser.parseSelectorsForNode(node)); + } + }; + importer.observer.addCallback = dynamic.added.bind(dynamic); + var matches = HTMLElement.prototype.matches || HTMLElement.prototype.matchesSelector || HTMLElement.prototype.webkitMatchesSelector || HTMLElement.prototype.mozMatchesSelector || HTMLElement.prototype.msMatchesSelector; +}); + +(function(scope) { + var initializeModules = scope.initializeModules; + var isIE = scope.isIE; + if (scope.useNative) { + return; + } + initializeModules(); + var rootDocument = scope.rootDocument; + function bootstrap() { + window.HTMLImports.importer.bootDocument(rootDocument); + } + if (document.readyState === "complete" || document.readyState === "interactive" && !window.attachEvent) { + bootstrap(); + } else { + document.addEventListener("DOMContentLoaded", bootstrap); + } +})(window.HTMLImports); \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/webcomponentsjs/HTMLImports.min.js b/lamassu-admin-elm/build/bower_components/webcomponentsjs/HTMLImports.min.js new file mode 100644 index 00000000..dd5b1175 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/webcomponentsjs/HTMLImports.min.js @@ -0,0 +1,11 @@ +/** + * @license + * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt + */ +// @version 0.7.22 +"undefined"==typeof WeakMap&&!function(){var e=Object.defineProperty,t=Date.now()%1e9,n=function(){this.name="__st"+(1e9*Math.random()>>>0)+(t++ +"__")};n.prototype={set:function(t,n){var r=t[this.name];return r&&r[0]===t?r[1]=n:e(t,this.name,{value:[t,n],writable:!0}),this},get:function(e){var t;return(t=e[this.name])&&t[0]===e?t[1]:void 0},"delete":function(e){var t=e[this.name];return t&&t[0]===e?(t[0]=t[1]=void 0,!0):!1},has:function(e){var t=e[this.name];return t?t[0]===e:!1}},window.WeakMap=n}(),function(e){function t(e){E.push(e),g||(g=!0,f(r))}function n(e){return window.ShadowDOMPolyfill&&window.ShadowDOMPolyfill.wrapIfNeeded(e)||e}function r(){g=!1;var e=E;E=[],e.sort(function(e,t){return e.uid_-t.uid_});var t=!1;e.forEach(function(e){var n=e.takeRecords();o(e),n.length&&(e.callback_(n,e),t=!0)}),t&&r()}function o(e){e.nodes_.forEach(function(t){var n=v.get(t);n&&n.forEach(function(t){t.observer===e&&t.removeTransientObservers()})})}function i(e,t){for(var n=e;n;n=n.parentNode){var r=v.get(n);if(r)for(var o=0;o0){var o=n[r-1],i=m(o,e);if(i)return void(n[r-1]=i)}else t(this.observer);n[r]=e},addListeners:function(){this.addListeners_(this.target)},addListeners_:function(e){var t=this.options;t.attributes&&e.addEventListener("DOMAttrModified",this,!0),t.characterData&&e.addEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.addEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.addEventListener("DOMNodeRemoved",this,!0)},removeListeners:function(){this.removeListeners_(this.target)},removeListeners_:function(e){var t=this.options;t.attributes&&e.removeEventListener("DOMAttrModified",this,!0),t.characterData&&e.removeEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.removeEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.removeEventListener("DOMNodeRemoved",this,!0)},addTransientObserver:function(e){if(e!==this.target){this.addListeners_(e),this.transientObservedNodes.push(e);var t=v.get(e);t||v.set(e,t=[]),t.push(this)}},removeTransientObservers:function(){var e=this.transientObservedNodes;this.transientObservedNodes=[],e.forEach(function(e){this.removeListeners_(e);for(var t=v.get(e),n=0;nm&&(h=s[m]);m++)a(h)?(u.push(this),d++,n()):(h.addEventListener("load",r),h.addEventListener("error",i));else n()}function a(e){return l?e.__loaded||e["import"]&&"loading"!==e["import"].readyState:e.__importParsed}function s(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)d(t)&&c(t)}function d(e){return"link"===e.localName&&"import"===e.rel}function c(e){var t=e["import"];t?o({target:e}):(e.addEventListener("load",o),e.addEventListener("error",o))}var u="import",l=Boolean(u in document.createElement("link")),h=Boolean(window.ShadowDOMPolyfill),m=function(e){return h?window.ShadowDOMPolyfill.wrapIfNeeded(e):e},p=m(document),f={get:function(){var e=window.HTMLImports.currentScript||document.currentScript||("complete"!==document.readyState?document.scripts[document.scripts.length-1]:null);return m(e)},configurable:!0};Object.defineProperty(document,"_currentScript",f),Object.defineProperty(p,"_currentScript",f);var v=/Trident/.test(navigator.userAgent),w=v?"complete":"interactive",b="readystatechange";l&&(new MutationObserver(function(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)t.addedNodes&&s(t.addedNodes)}).observe(document.head,{childList:!0}),function(){if("loading"===document.readyState)for(var e,t=document.querySelectorAll("link[rel=import]"),n=0,r=t.length;r>n&&(e=t[n]);n++)c(e)}()),t(function(e){window.HTMLImports.ready=!0,window.HTMLImports.readyTime=(new Date).getTime();var t=p.createEvent("CustomEvent");t.initCustomEvent("HTMLImportsLoaded",!0,!0,e),p.dispatchEvent(t)}),e.IMPORT_LINK_TYPE=u,e.useNative=l,e.rootDocument=p,e.whenReady=t,e.isIE=v}(window.HTMLImports),function(e){var t=[],n=function(e){t.push(e)},r=function(){t.forEach(function(t){t(e)})};e.addModule=n,e.initializeModules=r}(window.HTMLImports),window.HTMLImports.addModule(function(e){var t=/(url\()([^)]*)(\))/g,n=/(@import[\s]+(?!url\())([^;]*)(;)/g,r={resolveUrlsInStyle:function(e,t){var n=e.ownerDocument,r=n.createElement("a");return e.textContent=this.resolveUrlsInCssText(e.textContent,t,r),e},resolveUrlsInCssText:function(e,r,o){var i=this.replaceUrls(e,o,r,t);return i=this.replaceUrls(i,o,r,n)},replaceUrls:function(e,t,n,r){return e.replace(r,function(e,r,o,i){var a=o.replace(/["']/g,"");return n&&(a=new URL(a,n).href),t.href=a,a=t.href,r+"'"+a+"'"+i})}};e.path=r}),window.HTMLImports.addModule(function(e){var t={async:!0,ok:function(e){return e.status>=200&&e.status<300||304===e.status||0===e.status},load:function(n,r,o){var i=new XMLHttpRequest;return(e.flags.debug||e.flags.bust)&&(n+="?"+Math.random()),i.open("GET",n,t.async),i.addEventListener("readystatechange",function(e){if(4===i.readyState){var n=null;try{var a=i.getResponseHeader("Location");a&&(n="/"===a.substr(0,1)?location.origin+a:a)}catch(e){console.error(e.message)}r.call(o,!t.ok(i)&&i,i.response||i.responseText,n)}}),i.send(),i},loadDocument:function(e,t,n){this.load(e,t,n).responseType="document"}};e.xhr=t}),window.HTMLImports.addModule(function(e){var t=e.xhr,n=e.flags,r=function(e,t){this.cache={},this.onload=e,this.oncomplete=t,this.inflight=0,this.pending={}};r.prototype={addNodes:function(e){this.inflight+=e.length;for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)this.require(t);this.checkDone()},addNode:function(e){this.inflight++,this.require(e),this.checkDone()},require:function(e){var t=e.src||e.href;e.__nodeUrl=t,this.dedupe(t,e)||this.fetch(t,e)},dedupe:function(e,t){if(this.pending[e])return this.pending[e].push(t),!0;return this.cache[e]?(this.onload(e,t,this.cache[e]),this.tail(),!0):(this.pending[e]=[t],!1)},fetch:function(e,r){if(n.load&&console.log("fetch",e,r),e)if(e.match(/^data:/)){var o=e.split(","),i=o[0],a=o[1];a=i.indexOf(";base64")>-1?atob(a):decodeURIComponent(a),setTimeout(function(){this.receive(e,r,null,a)}.bind(this),0)}else{var s=function(t,n,o){this.receive(e,r,t,n,o)}.bind(this);t.load(e,s)}else setTimeout(function(){this.receive(e,r,{error:"href must be specified"},null)}.bind(this),0)},receive:function(e,t,n,r,o){this.cache[e]=r;for(var i,a=this.pending[e],s=0,d=a.length;d>s&&(i=a[s]);s++)this.onload(e,i,r,n,o),this.tail();this.pending[e]=null},tail:function(){--this.inflight,this.checkDone()},checkDone:function(){this.inflight||this.oncomplete()}},e.Loader=r}),window.HTMLImports.addModule(function(e){var t=function(e){this.addCallback=e,this.mo=new MutationObserver(this.handler.bind(this))};t.prototype={handler:function(e){for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)"childList"===t.type&&t.addedNodes.length&&this.addedNodes(t.addedNodes)},addedNodes:function(e){this.addCallback&&this.addCallback(e);for(var t,n=0,r=e.length;r>n&&(t=e[n]);n++)t.children&&t.children.length&&this.addedNodes(t.children)},observe:function(e){this.mo.observe(e,{childList:!0,subtree:!0})}},e.Observer=t}),window.HTMLImports.addModule(function(e){function t(e){return"link"===e.localName&&e.rel===u}function n(e){var t=r(e);return"data:text/javascript;charset=utf-8,"+encodeURIComponent(t)}function r(e){return e.textContent+o(e)}function o(e){var t=e.ownerDocument;t.__importedScripts=t.__importedScripts||0;var n=e.ownerDocument.baseURI,r=t.__importedScripts?"-"+t.__importedScripts:"";return t.__importedScripts++,"\n//# sourceURL="+n+r+".js\n"}function i(e){var t=e.ownerDocument.createElement("style");return t.textContent=e.textContent,a.resolveUrlsInStyle(t),t}var a=e.path,s=e.rootDocument,d=e.flags,c=e.isIE,u=e.IMPORT_LINK_TYPE,l="link[rel="+u+"]",h={documentSelectors:l,importsSelectors:[l,"link[rel=stylesheet]:not([type])","style:not([type])","script:not([type])",'script[type="application/javascript"]','script[type="text/javascript"]'].join(","),map:{link:"parseLink",script:"parseScript",style:"parseStyle"},dynamicElements:[],parseNext:function(){var e=this.nextToParse();e&&this.parse(e)},parse:function(e){if(this.isParsed(e))return void(d.parse&&console.log("[%s] is already parsed",e.localName));var t=this[this.map[e.localName]];t&&(this.markParsing(e),t.call(this,e))},parseDynamic:function(e,t){this.dynamicElements.push(e),t||this.parseNext()},markParsing:function(e){d.parse&&console.log("parsing",e),this.parsingElement=e},markParsingComplete:function(e){e.__importParsed=!0,this.markDynamicParsingComplete(e),e.__importElement&&(e.__importElement.__importParsed=!0,this.markDynamicParsingComplete(e.__importElement)),this.parsingElement=null,d.parse&&console.log("completed",e)},markDynamicParsingComplete:function(e){var t=this.dynamicElements.indexOf(e);t>=0&&this.dynamicElements.splice(t,1)},parseImport:function(e){if(e["import"]=e.__doc,window.HTMLImports.__importsParsingHook&&window.HTMLImports.__importsParsingHook(e),e["import"]&&(e["import"].__importParsed=!0),this.markParsingComplete(e),e.__resource&&!e.__error?e.dispatchEvent(new CustomEvent("load",{bubbles:!1})):e.dispatchEvent(new CustomEvent("error",{bubbles:!1})),e.__pending)for(var t;e.__pending.length;)t=e.__pending.shift(),t&&t({target:e});this.parseNext()},parseLink:function(e){t(e)?this.parseImport(e):(e.href=e.href,this.parseGeneric(e))},parseStyle:function(e){var t=e;e=i(e),t.__appliedElement=e,e.__importElement=t,this.parseGeneric(e)},parseGeneric:function(e){this.trackElement(e),this.addElementToDocument(e)},rootImportForElement:function(e){for(var t=e;t.ownerDocument.__importLink;)t=t.ownerDocument.__importLink;return t},addElementToDocument:function(e){var t=this.rootImportForElement(e.__importElement||e);t.parentNode.insertBefore(e,t)},trackElement:function(e,t){var n=this,r=function(o){e.removeEventListener("load",r),e.removeEventListener("error",r),t&&t(o),n.markParsingComplete(e),n.parseNext()};if(e.addEventListener("load",r),e.addEventListener("error",r),c&&"style"===e.localName){var o=!1;if(-1==e.textContent.indexOf("@import"))o=!0;else if(e.sheet){o=!0;for(var i,a=e.sheet.cssRules,s=a?a.length:0,d=0;s>d&&(i=a[d]);d++)i.type===CSSRule.IMPORT_RULE&&(o=o&&Boolean(i.styleSheet))}o&&setTimeout(function(){e.dispatchEvent(new CustomEvent("load",{bubbles:!1}))})}},parseScript:function(t){var r=document.createElement("script");r.__importElement=t,r.src=t.src?t.src:n(t),e.currentScript=t,this.trackElement(r,function(t){r.parentNode&&r.parentNode.removeChild(r),e.currentScript=null}),this.addElementToDocument(r)},nextToParse:function(){return this._mayParse=[],!this.parsingElement&&(this.nextToParseInDoc(s)||this.nextToParseDynamic())},nextToParseInDoc:function(e,n){if(e&&this._mayParse.indexOf(e)<0){this._mayParse.push(e);for(var r,o=e.querySelectorAll(this.parseSelectorsForNode(e)),i=0,a=o.length;a>i&&(r=o[i]);i++)if(!this.isParsed(r))return this.hasResource(r)?t(r)?this.nextToParseInDoc(r.__doc,r):r:void 0}return n},nextToParseDynamic:function(){return this.dynamicElements[0]},parseSelectorsForNode:function(e){var t=e.ownerDocument||e;return t===s?this.documentSelectors:this.importsSelectors},isParsed:function(e){return e.__importParsed},needsDynamicParsing:function(e){return this.dynamicElements.indexOf(e)>=0},hasResource:function(e){return!t(e)||void 0!==e.__doc}};e.parser=h,e.IMPORT_SELECTOR=l}),window.HTMLImports.addModule(function(e){function t(e){return n(e,a)}function n(e,t){return"link"===e.localName&&e.getAttribute("rel")===t}function r(e){return!!Object.getOwnPropertyDescriptor(e,"baseURI")}function o(e,t){var n=document.implementation.createHTMLDocument(a);n._URL=t;var o=n.createElement("base");o.setAttribute("href",t),n.baseURI||r(n)||Object.defineProperty(n,"baseURI",{value:t});var i=n.createElement("meta");return i.setAttribute("charset","utf-8"),n.head.appendChild(i),n.head.appendChild(o),n.body.innerHTML=e,window.HTMLTemplateElement&&HTMLTemplateElement.bootstrap&&HTMLTemplateElement.bootstrap(n),n}var i=e.flags,a=e.IMPORT_LINK_TYPE,s=e.IMPORT_SELECTOR,d=e.rootDocument,c=e.Loader,u=e.Observer,l=e.parser,h={documents:{},documentPreloadSelectors:s,importsPreloadSelectors:[s].join(","),loadNode:function(e){m.addNode(e)},loadSubtree:function(e){var t=this.marshalNodes(e);m.addNodes(t)},marshalNodes:function(e){return e.querySelectorAll(this.loadSelectorsForNode(e))},loadSelectorsForNode:function(e){var t=e.ownerDocument||e;return t===d?this.documentPreloadSelectors:this.importsPreloadSelectors},loaded:function(e,n,r,a,s){if(i.load&&console.log("loaded",e,n),n.__resource=r,n.__error=a,t(n)){var d=this.documents[e];void 0===d&&(d=a?null:o(r,s||e),d&&(d.__importLink=n,this.bootDocument(d)),this.documents[e]=d),n.__doc=d}l.parseNext()},bootDocument:function(e){this.loadSubtree(e),this.observer.observe(e),l.parseNext()},loadedAll:function(){l.parseNext()}},m=new c(h.loaded.bind(h),h.loadedAll.bind(h));if(h.observer=new u,!document.baseURI){var p={get:function(){var e=document.querySelector("base");return e?e.href:window.location.href},configurable:!0};Object.defineProperty(document,"baseURI",p),Object.defineProperty(d,"baseURI",p)}e.importer=h,e.importLoader=m}),window.HTMLImports.addModule(function(e){var t=e.parser,n=e.importer,r={added:function(e){for(var r,o,i,a,s=0,d=e.length;d>s&&(a=e[s]);s++)r||(r=a.ownerDocument,o=t.isParsed(r)),i=this.shouldLoadNode(a),i&&n.loadNode(a),this.shouldParseNode(a)&&o&&t.parseDynamic(a,i)},shouldLoadNode:function(e){return 1===e.nodeType&&o.call(e,n.loadSelectorsForNode(e))},shouldParseNode:function(e){return 1===e.nodeType&&o.call(e,t.parseSelectorsForNode(e))}};n.observer.addCallback=r.added.bind(r);var o=HTMLElement.prototype.matches||HTMLElement.prototype.matchesSelector||HTMLElement.prototype.webkitMatchesSelector||HTMLElement.prototype.mozMatchesSelector||HTMLElement.prototype.msMatchesSelector}),function(e){function t(){window.HTMLImports.importer.bootDocument(r)}var n=e.initializeModules;e.isIE;if(!e.useNative){n();var r=e.rootDocument;"complete"===document.readyState||"interactive"===document.readyState&&!window.attachEvent?t():document.addEventListener("DOMContentLoaded",t)}}(window.HTMLImports); \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/webcomponentsjs/MutationObserver.js b/lamassu-admin-elm/build/bower_components/webcomponentsjs/MutationObserver.js new file mode 100644 index 00000000..e58713b6 --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/webcomponentsjs/MutationObserver.js @@ -0,0 +1,350 @@ +/** + * @license + * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt + */ +// @version 0.7.22 +if (typeof WeakMap === "undefined") { + (function() { + var defineProperty = Object.defineProperty; + var counter = Date.now() % 1e9; + var WeakMap = function() { + this.name = "__st" + (Math.random() * 1e9 >>> 0) + (counter++ + "__"); + }; + WeakMap.prototype = { + set: function(key, value) { + var entry = key[this.name]; + if (entry && entry[0] === key) entry[1] = value; else defineProperty(key, this.name, { + value: [ key, value ], + writable: true + }); + return this; + }, + get: function(key) { + var entry; + return (entry = key[this.name]) && entry[0] === key ? entry[1] : undefined; + }, + "delete": function(key) { + var entry = key[this.name]; + if (!entry || entry[0] !== key) return false; + entry[0] = entry[1] = undefined; + return true; + }, + has: function(key) { + var entry = key[this.name]; + if (!entry) return false; + return entry[0] === key; + } + }; + window.WeakMap = WeakMap; + })(); +} + +(function(global) { + if (global.JsMutationObserver) { + return; + } + var registrationsTable = new WeakMap(); + var setImmediate; + if (/Trident|Edge/.test(navigator.userAgent)) { + setImmediate = setTimeout; + } else if (window.setImmediate) { + setImmediate = window.setImmediate; + } else { + var setImmediateQueue = []; + var sentinel = String(Math.random()); + window.addEventListener("message", function(e) { + if (e.data === sentinel) { + var queue = setImmediateQueue; + setImmediateQueue = []; + queue.forEach(function(func) { + func(); + }); + } + }); + setImmediate = function(func) { + setImmediateQueue.push(func); + window.postMessage(sentinel, "*"); + }; + } + var isScheduled = false; + var scheduledObservers = []; + function scheduleCallback(observer) { + scheduledObservers.push(observer); + if (!isScheduled) { + isScheduled = true; + setImmediate(dispatchCallbacks); + } + } + function wrapIfNeeded(node) { + return window.ShadowDOMPolyfill && window.ShadowDOMPolyfill.wrapIfNeeded(node) || node; + } + function dispatchCallbacks() { + isScheduled = false; + var observers = scheduledObservers; + scheduledObservers = []; + observers.sort(function(o1, o2) { + return o1.uid_ - o2.uid_; + }); + var anyNonEmpty = false; + observers.forEach(function(observer) { + var queue = observer.takeRecords(); + removeTransientObserversFor(observer); + if (queue.length) { + observer.callback_(queue, observer); + anyNonEmpty = true; + } + }); + if (anyNonEmpty) dispatchCallbacks(); + } + function removeTransientObserversFor(observer) { + observer.nodes_.forEach(function(node) { + var registrations = registrationsTable.get(node); + if (!registrations) return; + registrations.forEach(function(registration) { + if (registration.observer === observer) registration.removeTransientObservers(); + }); + }); + } + function forEachAncestorAndObserverEnqueueRecord(target, callback) { + for (var node = target; node; node = node.parentNode) { + var registrations = registrationsTable.get(node); + if (registrations) { + for (var j = 0; j < registrations.length; j++) { + var registration = registrations[j]; + var options = registration.options; + if (node !== target && !options.subtree) continue; + var record = callback(options); + if (record) registration.enqueue(record); + } + } + } + } + var uidCounter = 0; + function JsMutationObserver(callback) { + this.callback_ = callback; + this.nodes_ = []; + this.records_ = []; + this.uid_ = ++uidCounter; + } + JsMutationObserver.prototype = { + observe: function(target, options) { + target = wrapIfNeeded(target); + if (!options.childList && !options.attributes && !options.characterData || options.attributeOldValue && !options.attributes || options.attributeFilter && options.attributeFilter.length && !options.attributes || options.characterDataOldValue && !options.characterData) { + throw new SyntaxError(); + } + var registrations = registrationsTable.get(target); + if (!registrations) registrationsTable.set(target, registrations = []); + var registration; + for (var i = 0; i < registrations.length; i++) { + if (registrations[i].observer === this) { + registration = registrations[i]; + registration.removeListeners(); + registration.options = options; + break; + } + } + if (!registration) { + registration = new Registration(this, target, options); + registrations.push(registration); + this.nodes_.push(target); + } + registration.addListeners(); + }, + disconnect: function() { + this.nodes_.forEach(function(node) { + var registrations = registrationsTable.get(node); + for (var i = 0; i < registrations.length; i++) { + var registration = registrations[i]; + if (registration.observer === this) { + registration.removeListeners(); + registrations.splice(i, 1); + break; + } + } + }, this); + this.records_ = []; + }, + takeRecords: function() { + var copyOfRecords = this.records_; + this.records_ = []; + return copyOfRecords; + } + }; + function MutationRecord(type, target) { + this.type = type; + this.target = target; + this.addedNodes = []; + this.removedNodes = []; + this.previousSibling = null; + this.nextSibling = null; + this.attributeName = null; + this.attributeNamespace = null; + this.oldValue = null; + } + function copyMutationRecord(original) { + var record = new MutationRecord(original.type, original.target); + record.addedNodes = original.addedNodes.slice(); + record.removedNodes = original.removedNodes.slice(); + record.previousSibling = original.previousSibling; + record.nextSibling = original.nextSibling; + record.attributeName = original.attributeName; + record.attributeNamespace = original.attributeNamespace; + record.oldValue = original.oldValue; + return record; + } + var currentRecord, recordWithOldValue; + function getRecord(type, target) { + return currentRecord = new MutationRecord(type, target); + } + function getRecordWithOldValue(oldValue) { + if (recordWithOldValue) return recordWithOldValue; + recordWithOldValue = copyMutationRecord(currentRecord); + recordWithOldValue.oldValue = oldValue; + return recordWithOldValue; + } + function clearRecords() { + currentRecord = recordWithOldValue = undefined; + } + function recordRepresentsCurrentMutation(record) { + return record === recordWithOldValue || record === currentRecord; + } + function selectRecord(lastRecord, newRecord) { + if (lastRecord === newRecord) return lastRecord; + if (recordWithOldValue && recordRepresentsCurrentMutation(lastRecord)) return recordWithOldValue; + return null; + } + function Registration(observer, target, options) { + this.observer = observer; + this.target = target; + this.options = options; + this.transientObservedNodes = []; + } + Registration.prototype = { + enqueue: function(record) { + var records = this.observer.records_; + var length = records.length; + if (records.length > 0) { + var lastRecord = records[length - 1]; + var recordToReplaceLast = selectRecord(lastRecord, record); + if (recordToReplaceLast) { + records[length - 1] = recordToReplaceLast; + return; + } + } else { + scheduleCallback(this.observer); + } + records[length] = record; + }, + addListeners: function() { + this.addListeners_(this.target); + }, + addListeners_: function(node) { + var options = this.options; + if (options.attributes) node.addEventListener("DOMAttrModified", this, true); + if (options.characterData) node.addEventListener("DOMCharacterDataModified", this, true); + if (options.childList) node.addEventListener("DOMNodeInserted", this, true); + if (options.childList || options.subtree) node.addEventListener("DOMNodeRemoved", this, true); + }, + removeListeners: function() { + this.removeListeners_(this.target); + }, + removeListeners_: function(node) { + var options = this.options; + if (options.attributes) node.removeEventListener("DOMAttrModified", this, true); + if (options.characterData) node.removeEventListener("DOMCharacterDataModified", this, true); + if (options.childList) node.removeEventListener("DOMNodeInserted", this, true); + if (options.childList || options.subtree) node.removeEventListener("DOMNodeRemoved", this, true); + }, + addTransientObserver: function(node) { + if (node === this.target) return; + this.addListeners_(node); + this.transientObservedNodes.push(node); + var registrations = registrationsTable.get(node); + if (!registrations) registrationsTable.set(node, registrations = []); + registrations.push(this); + }, + removeTransientObservers: function() { + var transientObservedNodes = this.transientObservedNodes; + this.transientObservedNodes = []; + transientObservedNodes.forEach(function(node) { + this.removeListeners_(node); + var registrations = registrationsTable.get(node); + for (var i = 0; i < registrations.length; i++) { + if (registrations[i] === this) { + registrations.splice(i, 1); + break; + } + } + }, this); + }, + handleEvent: function(e) { + e.stopImmediatePropagation(); + switch (e.type) { + case "DOMAttrModified": + var name = e.attrName; + var namespace = e.relatedNode.namespaceURI; + var target = e.target; + var record = new getRecord("attributes", target); + record.attributeName = name; + record.attributeNamespace = namespace; + var oldValue = e.attrChange === MutationEvent.ADDITION ? null : e.prevValue; + forEachAncestorAndObserverEnqueueRecord(target, function(options) { + if (!options.attributes) return; + if (options.attributeFilter && options.attributeFilter.length && options.attributeFilter.indexOf(name) === -1 && options.attributeFilter.indexOf(namespace) === -1) { + return; + } + if (options.attributeOldValue) return getRecordWithOldValue(oldValue); + return record; + }); + break; + + case "DOMCharacterDataModified": + var target = e.target; + var record = getRecord("characterData", target); + var oldValue = e.prevValue; + forEachAncestorAndObserverEnqueueRecord(target, function(options) { + if (!options.characterData) return; + if (options.characterDataOldValue) return getRecordWithOldValue(oldValue); + return record; + }); + break; + + case "DOMNodeRemoved": + this.addTransientObserver(e.target); + + case "DOMNodeInserted": + var changedNode = e.target; + var addedNodes, removedNodes; + if (e.type === "DOMNodeInserted") { + addedNodes = [ changedNode ]; + removedNodes = []; + } else { + addedNodes = []; + removedNodes = [ changedNode ]; + } + var previousSibling = changedNode.previousSibling; + var nextSibling = changedNode.nextSibling; + var record = getRecord("childList", e.target.parentNode); + record.addedNodes = addedNodes; + record.removedNodes = removedNodes; + record.previousSibling = previousSibling; + record.nextSibling = nextSibling; + forEachAncestorAndObserverEnqueueRecord(e.relatedNode, function(options) { + if (!options.childList) return; + return record; + }); + } + clearRecords(); + } + }; + global.JsMutationObserver = JsMutationObserver; + if (!global.MutationObserver) { + global.MutationObserver = JsMutationObserver; + JsMutationObserver._isPolyfilled = true; + } +})(self); \ No newline at end of file diff --git a/lamassu-admin-elm/build/bower_components/webcomponentsjs/MutationObserver.min.js b/lamassu-admin-elm/build/bower_components/webcomponentsjs/MutationObserver.min.js new file mode 100644 index 00000000..267b374e --- /dev/null +++ b/lamassu-admin-elm/build/bower_components/webcomponentsjs/MutationObserver.min.js @@ -0,0 +1,11 @@ +/** + * @license + * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. + * This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt + * The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt + * The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt + * Code distributed by Google as part of the polymer project is also + * subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt + */ +// @version 0.7.22 +"undefined"==typeof WeakMap&&!function(){var e=Object.defineProperty,t=Date.now()%1e9,r=function(){this.name="__st"+(1e9*Math.random()>>>0)+(t++ +"__")};r.prototype={set:function(t,r){var i=t[this.name];return i&&i[0]===t?i[1]=r:e(t,this.name,{value:[t,r],writable:!0}),this},get:function(e){var t;return(t=e[this.name])&&t[0]===e?t[1]:void 0},"delete":function(e){var t=e[this.name];return t&&t[0]===e?(t[0]=t[1]=void 0,!0):!1},has:function(e){var t=e[this.name];return t?t[0]===e:!1}},window.WeakMap=r}(),function(e){function t(e){N.push(e),O||(O=!0,b(i))}function r(e){return window.ShadowDOMPolyfill&&window.ShadowDOMPolyfill.wrapIfNeeded(e)||e}function i(){O=!1;var e=N;N=[],e.sort(function(e,t){return e.uid_-t.uid_});var t=!1;e.forEach(function(e){var r=e.takeRecords();n(e),r.length&&(e.callback_(r,e),t=!0)}),t&&i()}function n(e){e.nodes_.forEach(function(t){var r=p.get(t);r&&r.forEach(function(t){t.observer===e&&t.removeTransientObservers()})})}function a(e,t){for(var r=e;r;r=r.parentNode){var i=p.get(r);if(i)for(var n=0;n0){var n=r[i-1],a=l(n,e);if(a)return void(r[i-1]=a)}else t(this.observer);r[i]=e},addListeners:function(){this.addListeners_(this.target)},addListeners_:function(e){var t=this.options;t.attributes&&e.addEventListener("DOMAttrModified",this,!0),t.characterData&&e.addEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.addEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.addEventListener("DOMNodeRemoved",this,!0)},removeListeners:function(){this.removeListeners_(this.target)},removeListeners_:function(e){var t=this.options;t.attributes&&e.removeEventListener("DOMAttrModified",this,!0),t.characterData&&e.removeEventListener("DOMCharacterDataModified",this,!0),t.childList&&e.removeEventListener("DOMNodeInserted",this,!0),(t.childList||t.subtree)&&e.removeEventListener("DOMNodeRemoved",this,!0)},addTransientObserver:function(e){if(e!==this.target){this.addListeners_(e),this.transientObservedNodes.push(e);var t=p.get(e);t||p.set(e,t=[]),t.push(this)}},removeTransientObservers:function(){var e=this.transientObservedNodes;this.transientObservedNodes=[],e.forEach(function(e){this.removeListeners_(e);for(var t=p.get(e),r=0;r` tags in the main document block the loading of such imports. This is to ensure the imports have loaded and any registered elements in them have been upgraded. + +The webcomponents.js and webcomponents-lite.js polyfills parse element definitions and handle their upgrade asynchronously. If prematurely fetching the element from the DOM before it has an opportunity to upgrade, you'll be working with an `HTMLUnknownElement`. + +For these situations (or when you need an approximate replacement for the Polymer 0.5 `polymer-ready` behavior), you can use the `WebComponentsReady` event as a signal before interacting with the element. The criteria for this event to fire is all Custom Elements with definitions registered by the time HTML Imports available at load time have loaded have upgraded. + +```js +window.addEventListener('WebComponentsReady', function(e) { + // imports are loaded and elements have been registered + console.log('Components are ready'); +}); +``` + +## Known Issues + + * [Limited CSS encapsulation](#encapsulation) + * [Element wrapping / unwrapping limitations](#wrapping) + * [Custom element's constructor property is unreliable](#constructor) + * [Contenteditable elements do not trigger MutationObserver](#contentedit) + * [ShadowCSS: :host-context(...):host(...) doesn't work](#hostcontext) + * [ShadowCSS: :host(.zot:not(.bar:nth-child(2))) doesn't work](#nestedparens) + * [HTML imports: document.currentScript doesn't work as expected](#currentscript) + * [execCommand isn't supported under Shadow DOM](#execcommand) + +### Limited CSS encapsulation +Under native Shadow DOM, CSS selectors cannot cross the shadow boundary. This means document level styles don't apply to shadow roots, and styles defined within a shadow root don't apply outside of that shadow root. [Several selectors](http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201/) are provided to be able to deal with the shadow boundary. + +The Shadow DOM polyfill can't prevent document styles from leaking into shadow roots. It can, however, encapsulate styles within shadow roots to some extent. This behavior isn't automatically emulated by the Shadow DOM polyfill, but it can be achieved by manually using the included ShadowCSS shim: + +``` +WebComponents.ShadowCSS.shimStyling( shadowRoot, scope ); +``` + +... where `shadowRoot` is the shadow root of a DOM element, and `scope` is the name of the scope used to prefix the selectors. This removes all ` + + + diff --git a/lamassu-admin-elm/cli.txt b/lamassu-admin-elm/cli.txt new file mode 100644 index 00000000..1fdc46c4 --- /dev/null +++ b/lamassu-admin-elm/cli.txt @@ -0,0 +1,3 @@ +ls src/Css/* | entr elm-css src/Stylesheets.elm +find src | entr elm-make src/Main.elm --output ../lamassu-server/public/elm.js +find src | entr elm-make src/Lamassu.elm --output ../lamassu-server/public/lamassu-elm.js diff --git a/lamassu-admin-elm/elm-package.json b/lamassu-admin-elm/elm-package.json new file mode 100644 index 00000000..77fc941c --- /dev/null +++ b/lamassu-admin-elm/elm-package.json @@ -0,0 +1,37 @@ +{ + "version": "1.0.0", + "summary": "helpful summary of your project, less than 80 characters", + "repository": "https://github.com/user/project.git", + "license": "Unlicense", + "source-directories": [ + "src" + ], + "exposed-modules": [], + "dependencies": { + "NoRedInk/elm-decode-pipeline": "3.0.0 <= v < 4.0.0", + "arturopala/elm-monocle": "1.3.1 <= v < 2.0.0", + "elm-community/json-extra": "2.0.0 <= v < 3.0.0", + "elm-community/maybe-extra": "4.0.0 <= v < 5.0.0", + "elm-lang/core": "5.0.0 <= v < 6.0.0", + "elm-lang/html": "2.0.0 <= v < 3.0.0", + "elm-lang/http": "1.0.0 <= v < 2.0.0", + "elm-lang/keyboard": "1.0.1 <= v < 2.0.0", + "elm-lang/navigation": "2.0.1 <= v < 3.0.0", + "elm-lang/virtual-dom": "2.0.0 <= v < 3.0.0", + "elm-lang/websocket": "1.0.2 <= v < 2.0.0", + "evancz/elm-markdown": "3.0.1 <= v < 4.0.0", + "evancz/url-parser": "2.0.1 <= v < 3.0.0", + "ggb/numeral-elm": "1.2.3 <= v < 2.0.0", + "justinmimbs/elm-date-extra": "2.0.2 <= v < 3.0.0", + "krisajenkins/remotedata": "4.0.0 <= v < 5.0.0", + "lukewestby/elm-http-builder": "5.1.0 <= v < 6.0.0", + "pablohirafuji/elm-qrcode": "1.0.1 <= v < 2.0.0", + "rluiten/stringdistance": "1.0.3 <= v < 2.0.0", + "rtfeldman/elm-css": "10.0.0 <= v < 11.0.0", + "rtfeldman/elm-css-helpers": "2.1.0 <= v < 3.0.0", + "rtfeldman/elm-css-util": "1.0.2 <= v < 2.0.0", + "rtfeldman/elm-validate": "1.1.3 <= v < 2.0.0", + "tripokey/elm-fuzzy": "5.0.3 <= v < 6.0.0" + }, + "elm-version": "0.18.0 <= v < 0.19.0" +} diff --git a/lamassu-admin-elm/src/Account.elm b/lamassu-admin-elm/src/Account.elm new file mode 100644 index 00000000..9c477f42 --- /dev/null +++ b/lamassu-admin-elm/src/Account.elm @@ -0,0 +1,164 @@ +module Account exposing (..) + +import Html exposing (..) +import Html.Events exposing (..) +import Html.Keyed +import RemoteData exposing (..) +import Http +import HttpBuilder exposing (..) +import AccountTypes exposing (..) +import AccountDecoder exposing (..) +import AccountEncoder exposing (..) +import FieldSet.Types +import FieldSet.State +import FieldSet.View +import Css.Admin exposing (..) +import Css.Classes +import Process +import Time exposing (second) +import Task + + +type alias SubModel = + { status : SavingStatus + , account : Account + } + + +type alias Model = + RemoteData.WebData SubModel + + +type SavingStatus + = Saving + | Saved + | Editing + | NotSaving + + +toModel : SavingStatus -> Account -> SubModel +toModel status account = + { status = status, account = account } + + +getForm : String -> Cmd Msg +getForm code = + get ("/api/account/" ++ code) + |> withExpect (Http.expectJson accountDecoder) + |> send (Result.map (toModel NotSaving) >> RemoteData.fromResult) + |> Cmd.map Load + + +postForm : Account -> Cmd Msg +postForm account = + post "/api/account" + |> withJsonBody (encodeAccount account) + |> withExpect (Http.expectJson accountDecoder) + |> send (Result.map (toModel Saved) >> RemoteData.fromResult) + |> Cmd.map Load + + +init : Model +init = + RemoteData.NotAsked + + +load : String -> ( Model, Cmd Msg ) +load code = + ( RemoteData.Loading, getForm code ) + + + +-- UPDATE + + +type Msg + = Load Model + | Submit + | FieldSetMsg FieldSet.Types.Msg + | HideSaveIndication + + +saveUpdate : SubModel -> ( SubModel, Cmd Msg ) +saveUpdate model = + let + cmd = + if (model.status == Saved) then + Process.sleep (2 * second) + |> Task.perform (\_ -> HideSaveIndication) + else + Cmd.none + in + model ! [ cmd ] + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg webModel = + case msg of + Load newModel -> + RemoteData.update saveUpdate newModel + + Submit -> + RemoteData.update (\model -> model ! [ postForm model.account ]) webModel + + HideSaveIndication -> + RemoteData.update (\model -> { model | status = NotSaving } ! []) webModel + + FieldSetMsg fieldSetMsg -> + let + updateFields model = + FieldSet.State.update fieldSetMsg model.account.fields + + newAccount account fields = + { account | fields = fields } + + toModel model fieldsUpdate = + { model + | account = + newAccount model.account + (Tuple.first fieldsUpdate) + } + ! [ Cmd.map FieldSetMsg (Tuple.second fieldsUpdate) ] + + mapper model = + updateFields model + |> (toModel model) + in + RemoteData.update mapper webModel + + +view : Model -> Html Msg +view webModel = + case webModel of + NotAsked -> + div [] [] + + Loading -> + div [] [ text "Loading..." ] + + Failure err -> + div [] [ text (toString err) ] + + Success model -> + let + fieldSetView = + Html.Keyed.node "div" [] [ ( model.account.code, (Html.map FieldSetMsg (FieldSet.View.view model.account.fields)) ) ] + + statusString = + case model.status of + Saved -> + "Saved" + + _ -> + "" + in + div [] + [ div [ class [ Css.Classes.SectionLabel ] ] [ text model.account.display ] + , form [ id model.account.code ] + [ fieldSetView + , div [ class [ Css.Classes.ButtonRow ] ] + [ div [ onClick Submit, class [ Css.Classes.Button ] ] [ text "Submit" ] + , div [] [ text statusString ] + ] + ] + ] diff --git a/lamassu-admin-elm/src/AccountDecoder.elm b/lamassu-admin-elm/src/AccountDecoder.elm new file mode 100644 index 00000000..d19dbbea --- /dev/null +++ b/lamassu-admin-elm/src/AccountDecoder.elm @@ -0,0 +1,22 @@ +module AccountDecoder exposing (..) + +import Json.Decode exposing (..) +import FieldSet.Rest exposing (..) +import AccountTypes exposing (..) + + +accountDecoder : Decoder Account +accountDecoder = + map3 Account + (field "code" string) + (field "display" string) + (field "fields" (list fieldDecoder)) + + +type alias AccountResult = + Result String Account + + +decodeAccount : String -> AccountResult +decodeAccount string = + decodeString accountDecoder string diff --git a/lamassu-admin-elm/src/AccountEncoder.elm b/lamassu-admin-elm/src/AccountEncoder.elm new file mode 100644 index 00000000..84689917 --- /dev/null +++ b/lamassu-admin-elm/src/AccountEncoder.elm @@ -0,0 +1,15 @@ +module AccountEncoder exposing (..) + +import Json.Encode exposing (..) +import AccountTypes exposing (..) +import List +import FieldSet.Rest exposing (..) + + +encodeAccount : Account -> Value +encodeAccount account = + Json.Encode.object + [ ( "code", string account.code ) + , ( "display", string account.display ) + , ( "fields", list (List.filterMap encodeField account.fields) ) + ] diff --git a/lamassu-admin-elm/src/AccountTypes.elm b/lamassu-admin-elm/src/AccountTypes.elm new file mode 100644 index 00000000..0ddf0d4e --- /dev/null +++ b/lamassu-admin-elm/src/AccountTypes.elm @@ -0,0 +1,10 @@ +module AccountTypes exposing (..) + +import FieldSet.Types exposing (..) + + +type alias Account = + { code : String + , display : String + , fields : List Field + } diff --git a/lamassu-admin-elm/src/AccountsDecoder.elm b/lamassu-admin-elm/src/AccountsDecoder.elm new file mode 100644 index 00000000..e64beb3b --- /dev/null +++ b/lamassu-admin-elm/src/AccountsDecoder.elm @@ -0,0 +1,16 @@ +module AccountsDecoder exposing (..) + +import Json.Decode exposing (..) + + +accountDecoder : Decoder ( String, String ) +accountDecoder = + map2 (,) + (field "code" string) + (field "display" string) + + +accountsDecoder : Decoder (List ( String, String )) +accountsDecoder = + map identity + (field "accounts" (list accountDecoder)) diff --git a/lamassu-admin-elm/src/BasicTypes.elm b/lamassu-admin-elm/src/BasicTypes.elm new file mode 100644 index 00000000..50f07aeb --- /dev/null +++ b/lamassu-admin-elm/src/BasicTypes.elm @@ -0,0 +1,8 @@ +module BasicTypes exposing (..) + + +type SavingStatus + = Saving + | Saved + | Editing + | NotSaving diff --git a/lamassu-admin-elm/src/ClientServerWebsocket.elm b/lamassu-admin-elm/src/ClientServerWebsocket.elm new file mode 100644 index 00000000..b1aef931 --- /dev/null +++ b/lamassu-admin-elm/src/ClientServerWebsocket.elm @@ -0,0 +1,45 @@ +module ClientServerWebsocket exposing (..) + +-- Elm might not be the best platform for this kind of thing +-- Hard to do a global lookup table +-- Might be easiest to just use HTTP for this for now +-- No need to prematurely optimize and go against the flow + +import WebSocket + + +type Msg + = OK + | Timeout + + +type alias Client = + Sub Msg + + +init : String -> Client +init url = + let + sub = + WebSocket.listen url parsePacket + in + sub + + +request : Client -> String -> String -> Cmd Msg +request client url payload = + let + cmd = + Cmd.map (respond client) (WebSocket.send url payload) + in + cmd + + +parsePacket : String -> Msg +parsePacket packet = + OK + + +respond : client -> (a -> Msg) +respond client = + (\_ -> OK) diff --git a/lamassu-admin-elm/src/Common/Customer/Decoder.elm b/lamassu-admin-elm/src/Common/Customer/Decoder.elm new file mode 100644 index 00000000..0eeda1b1 --- /dev/null +++ b/lamassu-admin-elm/src/Common/Customer/Decoder.elm @@ -0,0 +1,77 @@ +module Common.Customer.Decoder exposing (..) + +import Json.Decode exposing (..) +import Json.Decode.Extra exposing (date, fromResult) +import Json.Decode.Pipeline exposing (decode, required, optional, hardcoded) +import Common.Customer.Types exposing (..) + + +customersDecoder : Decoder (List Customer) +customersDecoder = + field "customers" (list customerDecoder) + + +mapAuthorizedTypes : String -> Decoder Authorized +mapAuthorizedTypes s = + case s of + "blocked" -> + succeed Blocked + + "verified" -> + succeed Verified + + "automatic" -> + succeed Automatic + + _ -> + fail ("No such type " ++ s) + + +authorizedDecoder : Decoder Authorized +authorizedDecoder = + string + |> andThen mapAuthorizedTypes + + +idCardDataDecoder : Decoder IdCardData +idCardDataDecoder = + decode IdCardData + |> required "uid" string + +customerDecoder : Decoder Customer +customerDecoder = + decode Customer + |> required "id" string + |> required "name" (nullable string) + |> required "phone" (nullable string) + |> required "phoneAt" (nullable date) + |> required "smsOverride" authorizedDecoder + |> required "smsOverrideByName" (nullable string) + |> required "smsOverrideAt" (nullable date) + |> required "created" date + |> required "status" (nullable string) + |> required "authorizedOverride" authorizedDecoder + |> required "authorizedOverrideByName" (nullable string) + |> required "authorizedOverrideAt" (nullable date) + |> required "authorizedAt" (nullable date) + |> required "idCardData" (nullable idCardDataDecoder) + |> required "idCardDataOverride" authorizedDecoder + |> required "idCardDataOverrideByName" (nullable string) + |> required "idCardDataOverrideAt" (nullable date) + |> required "idCardDataAt" (nullable date) + |> required "idCardPhotoPath" (nullable string) + |> required "idCardPhotoOverride" authorizedDecoder + |> required "idCardPhotoOverrideByName" (nullable string) + |> required "idCardPhotoOverrideAt" (nullable date) + |> required "idCardPhotoAt" (nullable date) + |> required "sanctions" (nullable bool) + |> required "sanctionsOverride" authorizedDecoder + |> required "sanctionsOverrideByName" (nullable string) + |> required "sanctionsOverrideAt" (nullable date) + |> required "sanctionsAt" (nullable date) + |> required "frontCameraPath" (nullable string) + |> required "frontCameraOverride" authorizedDecoder + |> required "frontCameraOverrideByName" (nullable string) + |> required "frontCameraOverrideAt" (nullable date) + |> required "frontCameraAt" (nullable date) + |> required "dailyVolume" (nullable string) diff --git a/lamassu-admin-elm/src/Common/Customer/Types.elm b/lamassu-admin-elm/src/Common/Customer/Types.elm new file mode 100644 index 00000000..026b9d5e --- /dev/null +++ b/lamassu-admin-elm/src/Common/Customer/Types.elm @@ -0,0 +1,67 @@ +module Common.Customer.Types exposing (..) + +import Date exposing (Date) + + +type Authorized + = Automatic + | Blocked + | Verified + + +type alias Customers = + List Customer + + +type alias IdCardData = + { uid : String } + +type alias Customer = + { id : String + , name : Maybe String + , phone : Maybe String + , phoneAt : Maybe Date + , smsOverride : Authorized + , smsOverrideByName : Maybe String + , smsOverrideAt : Maybe Date + , created : Date + , status : Maybe String + , authorizedOverride : Authorized + , authorizedOverrideByName : Maybe String + , authorizedOverrideAt : Maybe Date + , authorizedAt : Maybe Date + , idCardData : Maybe IdCardData + , idCardDataOverride : Authorized + , idCardDataOverrideByName : Maybe String + , idCardDataOverrideAt : Maybe Date + , idCardDataAt : Maybe Date + , idCardPhotoPath : Maybe String + , idCardPhotoOverride : Authorized + , idCardPhotoOverrideByName : Maybe String + , idCardPhotoOverrideAt : Maybe Date + , idCardPhotoAt : Maybe Date + , sanctions : Maybe Bool + , sanctionsOverride : Authorized + , sanctionsOverrideByName : Maybe String + , sanctionsOverrideAt : Maybe Date + , sanctionsAt : Maybe Date + , frontCameraPath : Maybe String + , frontCameraOverride : Authorized + , frontCameraOverrideByName : Maybe String + , frontCameraOverrideAt : Maybe Date + , frontCameraAt : Maybe Date + , dailyVolume : Maybe String + } + + +authorizedToString : Authorized -> String +authorizedToString model = + case model of + Verified -> + "verified" + + Blocked -> + "blocked" + + Automatic -> + "automatic" diff --git a/lamassu-admin-elm/src/Common/Logs/Decoder.elm b/lamassu-admin-elm/src/Common/Logs/Decoder.elm new file mode 100644 index 00000000..4cccd797 --- /dev/null +++ b/lamassu-admin-elm/src/Common/Logs/Decoder.elm @@ -0,0 +1,55 @@ +module Common.Logs.Decoder exposing (..) + +import Json.Decode exposing (..) +import Json.Decode.Extra exposing (date, fromResult) +import Json.Decode.Pipeline exposing (decode, required, optional, hardcoded) +import Common.Logs.Types exposing (..) + + +logsDecoder : Decoder Logs +logsDecoder = + decode Logs + |> required "logs" (list logDecoder) + |> required "currentMachine" machineDecoder + + +logDecoder : Decoder Log +logDecoder = + decode Log + |> required "id" string + |> required "timestamp" date + |> required "logLevel" string + |> required "message" string + + +supportLogsDecoder : Decoder SupportLogs +supportLogsDecoder = + field "supportLogs" (list supportLogDecoder) + + +latestLogSnapshotDecoder : Decoder SupportLogSnapshot +latestLogSnapshotDecoder = + decode SupportLogSnapshot + |> required "deviceId" string + |> required "timestamp" date + + +supportLogDecoder : Decoder SupportLog +supportLogDecoder = + decode SupportLog + |> required "id" string + |> required "deviceId" string + |> required "timestamp" date + |> required "name" string + + +machinesDecoder : Decoder Machines +machinesDecoder = + field "machines" (list machineDecoder) + + +machineDecoder : Decoder Machine +machineDecoder = + decode Machine + |> required "deviceId" string + |> required "name" string diff --git a/lamassu-admin-elm/src/Common/Logs/Types.elm b/lamassu-admin-elm/src/Common/Logs/Types.elm new file mode 100644 index 00000000..e526e0cb --- /dev/null +++ b/lamassu-admin-elm/src/Common/Logs/Types.elm @@ -0,0 +1,43 @@ +module Common.Logs.Types exposing (..) + +import Date exposing (Date) + + +type alias Machine = + { deviceId : String + , name : String + } + + +type alias Machines = + List Machine + + +type alias Log = + { id : String + , timestamp : Date + , logLevel : String + , message : String + } + + +type alias SupportLogSnapshot = + { deviceId : String + , timestamp : Date + } + + +type alias SupportLog = + { id : String + , deviceId : String + , timestamp : Date + , name : String + } + + +type alias SupportLogs = + List SupportLog + + +type alias Logs = + { logs : List Log, currentMachine : Machine } diff --git a/lamassu-admin-elm/src/Common/TransactionTypes.elm b/lamassu-admin-elm/src/Common/TransactionTypes.elm new file mode 100644 index 00000000..cd801b54 --- /dev/null +++ b/lamassu-admin-elm/src/Common/TransactionTypes.elm @@ -0,0 +1,54 @@ +module Common.TransactionTypes exposing (..) + +import Date exposing (Date) + +type CryptoCode + = BTC + | BCH + | ETH + | ZEC + | DASH + | LTC + + +type alias CashInTxRec = + { id : String + , machineName : String + , toAddress : String + , cryptoAtoms : Int + , cryptoCode : CryptoCode + , fiat : Float + , fiatCode : String + , txHash : Maybe String + , phone : Maybe String + , error : Maybe String + , operatorCompleted : Bool + , send : Bool + , sendConfirmed : Bool + , expired : Bool + , created : Date + } + + +type alias CashOutTxRec = + { id : String + , machineName : String + , toAddress : String + , cryptoAtoms : Int + , cryptoCode : CryptoCode + , fiat : Float + , fiatCode : String + , status : String + , dispense : Bool + , notified : Bool + , redeemed : Bool + , phone : Maybe String + , error : Maybe String + , created : Date + , confirmed : Bool + } + + +type Tx + = CashInTx CashInTxRec + | CashOutTx CashOutTxRec diff --git a/lamassu-admin-elm/src/Config.elm b/lamassu-admin-elm/src/Config.elm new file mode 100644 index 00000000..00d73e1b --- /dev/null +++ b/lamassu-admin-elm/src/Config.elm @@ -0,0 +1,1855 @@ +module Config exposing (..) + +import Html exposing (..) +import Html.Events exposing (..) +import Html.Attributes exposing (defaultValue, placeholder, type_, disabled, colspan) +import Html.Keyed +import Navigation +import RemoteData exposing (..) +import Http +import HttpBuilder exposing (..) +import ConfigTypes exposing (..) +import ConfigDecoder exposing (..) +import ConfigEncoder exposing (..) +import Css.Admin exposing (..) +import Css.Classes as C +import Selectize +import Maybe +import SelectizeHelper exposing (buildConfig) +import FuzzyMatch +import Process +import Time exposing (second) +import Task +import StatusTypes +import Maybe.Extra + + +type alias WebConfigGroup = + RemoteData.WebData ConfigGroup + + +type SavingStatus + = Saving + | Saved + | Editing + | NotSaving + + +type alias Model = + { webConfigGroup : WebConfigGroup + , fieldCollection : FieldCollection + , crypto : Maybe Crypto + , fiat : Maybe String + , status : SavingStatus + , focused : Maybe FieldLocator + , rates : List StatusTypes.Rate + } + + +type alias ResolvedModel = + { configGroup : ConfigGroup + , fieldCollection : FieldCollection + , crypto : Crypto + , fiat : String + , status : SavingStatus + , focused : Maybe FieldLocator + } + + +toResolvedModel : Model -> ConfigGroup -> ResolvedModel +toResolvedModel model configGroup = + { configGroup = configGroup + , fieldCollection = model.fieldCollection + , crypto = Maybe.withDefault GlobalCrypto model.crypto + , fiat = Maybe.withDefault "Fiat" model.fiat + , status = model.status + , focused = model.focused + } + + +getForm : String -> Cmd Msg +getForm code = + get ("/api/config/" ++ code) + |> withExpect (Http.expectJson configGroupDecoder) + |> send RemoteData.fromResult + |> Cmd.map Load + + +postForm : String -> List FieldInstance -> Cmd Msg +postForm configGroupCode fieldInstances = + let + maybeResults = + encodeResults configGroupCode fieldInstances + in + case maybeResults of + Nothing -> + Cmd.none + + Just results -> + post ("/api/config") + |> withJsonBody (results) + |> withExpect (Http.expectJson configGroupDecoder) + |> send RemoteData.fromResult + |> Cmd.map Load + + +postFormNoLoad : String -> List FieldInstance -> Cmd Msg +postFormNoLoad configGroupCode fieldInstances = + postForm configGroupCode fieldInstances + |> Cmd.map (\_ -> NoOp) + + +init : Model +init = + { webConfigGroup = RemoteData.NotAsked + , fieldCollection = initFieldCollection + , crypto = Nothing + , fiat = Nothing + , status = NotSaving + , focused = Nothing + , rates = [] + } + + +load : Model -> String -> Maybe String -> ( Model, Cmd Msg ) +load model code maybeCryptoCodeString = + let + crypto = + Maybe.map stringToCrypto maybeCryptoCodeString + in + ( { model | crypto = crypto }, getForm code ) + + + +-- UPDATE + + +similar : (x -> y) -> x -> x -> Bool +similar mapper a b = + (==) (mapper a) (mapper b) + + +placeField : List Field -> Field -> List Field +placeField fieldList field = + let + maybeOldField = + List.filter (similar .fieldLocator field) fieldList + |> List.head + + newField = + case maybeOldField of + Nothing -> + field + + Just oldField -> + { oldField | fieldValue = field.fieldValue } + in + newField :: (List.filter (not << (similar .fieldLocator field)) fieldList) + + +fieldHolderToList : FieldHolder -> List String +fieldHolderToList fieldHolder = + case fieldHolder of + FieldOk fieldValue -> + case fieldValue of + FieldLanguageValue v -> + v + + FieldCryptoCurrencyValue v -> + v + + _ -> + Debug.crash "Not a list type" + + _ -> + [] + + +emptyToNothing : List x -> Maybe (List x) +emptyToNothing list = + if (List.isEmpty list) then + Nothing + else + Just list + + +listToFieldHolder : (List a -> FieldValue) -> List a -> FieldHolder +listToFieldHolder modifier list = + if List.isEmpty list then + FieldEmpty + else + FieldOk <| modifier <| list + + +updateStringFieldInstance : List FieldInstance -> FieldLocator -> Maybe String -> FieldInstance -> FieldInstance +updateStringFieldInstance fieldInstances fieldLocator maybeString fieldInstance = + if fieldInstance.fieldLocator == fieldLocator then + case fieldLocator.fieldType of + FieldLanguageType -> + let + list = + fieldHolderToList fieldInstance.fieldHolder + + newList = + case maybeString of + Nothing -> + List.take ((List.length list) - 1) list + + Just s -> + list ++ [ s ] + in + { fieldInstance | fieldHolder = listToFieldHolder FieldLanguageValue newList } + + FieldCryptoCurrencyType -> + let + list = + fieldHolderToList fieldInstance.fieldHolder + + newList = + case maybeString of + Nothing -> + List.take ((List.length list) - 1) list + + Just s -> + list ++ [ s ] + in + { fieldInstance | fieldHolder = listToFieldHolder FieldCryptoCurrencyValue newList } + + _ -> + let + fieldHolder = + case maybeString of + Nothing -> + FieldEmpty + + Just s -> + stringToFieldHolder fieldLocator.fieldType s + in + { fieldInstance | fieldHolder = fieldHolder } + else + fieldInstance + + +updateInput : FieldLocator -> Maybe String -> Model -> Model +updateInput fieldLocator maybeValueString model = + let + oldFieldInstances = + model.fieldCollection.fieldInstances + + fieldInstances = + List.map (updateStringFieldInstance oldFieldInstances fieldLocator maybeValueString) + oldFieldInstances + + fieldCollection = + model.fieldCollection + + newFieldCollection = + { fieldCollection | fieldInstances = fieldInstances } + in + { model | fieldCollection = newFieldCollection } + + + +-- View + + +fieldTypeToInputType : FieldType -> String +fieldTypeToInputType fieldType = + "string" + + +unitDisplay : String -> FieldInstance -> Html Msg +unitDisplay fiat fieldInstance = + case fieldInstance.fieldLocator.fieldType of + FieldPercentageType -> + div [ class [ C.UnitDisplay ] ] [ text "%" ] + + FieldIntegerType -> + case fieldInstance.fieldLocator.fieldClass of + Just "fiat" -> + div [ class [ C.UnitDisplay ] ] [ text fiat ] + + Just "banknotes" -> + div [ class [ C.UnitDisplay ] ] [ text "notes" ] + + Just _ -> + div [] [] + + Nothing -> + div [] [] + + _ -> + div [] [] + + +fieldInstanceClasses : FieldInstance -> List C.CssClasses +fieldInstanceClasses fieldInstance = + case fieldInstance.fieldLocator.fieldType of + FieldPercentageType -> + [ C.ShortCell ] + + FieldIntegerType -> + [ C.ShortCell ] + + FieldAccountType -> + [ C.MediumCell ] + + FieldStringType -> + [ C.MediumCell, C.TextCell ] + + FieldLanguageType -> + [ C.MediumCell ] + + FieldCryptoCurrencyType -> + [ C.MediumCell ] + + _ -> + [ C.ShortCell ] + + +textInput : String -> FieldInstance -> Maybe FieldValue -> Maybe FieldValue -> Bool -> Html Msg +textInput fiat fieldInstance maybeFieldValue maybeFallbackFieldValue enabled = + let + fieldLocator = + fieldInstance.fieldLocator + + maybeSpecificString = + Maybe.map fieldValueToString maybeFieldValue + + maybeFallbackString = + Maybe.map fieldValueToString maybeFallbackFieldValue + + defaultString = + Maybe.withDefault "" maybeSpecificString + + fallbackString = + Maybe.withDefault "" maybeFallbackString + + inputType = + fieldTypeToInputType fieldLocator.fieldType + + fieldClasses = + fieldInstanceClasses fieldInstance + + fieldValid = + validateFieldInstance + + isReadOnly = + fieldInstance.readOnly || (not enabled) + + parentClasses = + if isReadOnly then + [ C.InputContainer, C.ReadOnly ] + else + [ C.InputContainer ] + + inputComponent = + if isReadOnly then + div [ class [ C.BasicInputReadOnly ] ] [ text fallbackString ] + else + input + [ onInput (Input fieldLocator) + , onFocus (Focus fieldLocator) + , onBlur (Blur fieldLocator) + , defaultValue defaultString + , placeholder fallbackString + , class (C.BasicInput :: fieldClasses) + , type_ inputType + ] + [] + in + div [ class parentClasses ] + [ inputComponent + , unitDisplay fiat fieldInstance + ] + + +textareaInput : String -> FieldInstance -> Maybe FieldValue -> Maybe FieldValue -> Bool -> Html Msg +textareaInput fiat fieldInstance maybeFieldValue maybeFallbackFieldValue enabled = + let + fieldLocator = + fieldInstance.fieldLocator + + maybeSpecificString = + Maybe.map fieldValueToString maybeFieldValue + + maybeFallbackString = + Maybe.map fieldValueToString maybeFallbackFieldValue + + defaultString = + Maybe.withDefault "" maybeSpecificString + + fallbackString = + Maybe.withDefault "" maybeFallbackString + + inputType = + fieldTypeToInputType fieldLocator.fieldType + + fieldClasses = + fieldInstanceClasses fieldInstance + + fieldValid = + validateFieldInstance + + isReadOnly = + fieldInstance.readOnly || (not enabled) + + parentClasses = + if isReadOnly then + [ C.InputContainer, C.ReadOnly ] + else + [ C.InputContainer ] + + inputComponent = + textarea + [ onInput (Input fieldLocator) + , onFocus (Focus fieldLocator) + , onBlur (Blur fieldLocator) + , defaultValue defaultString + , placeholder fallbackString + , class (C.Textarea :: fieldClasses) + , disabled isReadOnly + ] + [] + in + div [ class parentClasses ] + [ inputComponent + , unitDisplay fiat fieldInstance + ] + + +type alias LocalConfig = + SelectizeHelper.LocalConfig Msg String DisplayRec + + +accountSelectizeView : + ResolvedModel + -> LocalConfig + -> FieldInstance + -> Selectize.State + -> Maybe FieldValue + -> Maybe FieldValue + -> Html Msg +accountSelectizeView model localConfig fieldInstance selectizeState maybeFieldValue maybeFallbackFieldValue = + let + specificConfig = + { maxItems = 1 + , selectedDisplay = .display + , optionDisplay = .display + , match = FuzzyMatch.match + , customCssClass = C.SelectizeAccount + } + + matchAccount accountRec = + case fieldInstance.fieldLocator.fieldClass of + Nothing -> + True + + Just fieldClass -> + (accountRec.class + == fieldClass + ) + && (case accountRec.cryptos of + Nothing -> + True + + Just cryptos -> + List.member model.crypto cryptos + ) + + availableItems = + List.filter matchAccount model.configGroup.data.accounts + |> List.map accountRecToDisplayRec + + selectedIds = + Maybe.map fieldValueToString maybeFieldValue + |> maybeToList + + fallbackIds = + Maybe.map fieldValueToString maybeFallbackFieldValue + |> maybeToList + in + Selectize.view (buildConfig localConfig specificConfig) + selectedIds + availableItems + fallbackIds + selectizeState + + +fiatCurrencySelectizeView : + ResolvedModel + -> LocalConfig + -> FieldInstance + -> Selectize.State + -> Maybe FieldValue + -> Maybe FieldValue + -> Html Msg +fiatCurrencySelectizeView model localConfig fieldInstance selectizeState maybeFieldValue maybeFallbackFieldValue = + let + specificConfig = + { maxItems = 1 + , selectedDisplay = .code + , optionDisplay = .display + , match = FuzzyMatch.match + , customCssClass = C.SelectizeFiatCurrency + } + + availableItems = + model.configGroup.data.currencies + + selectedIds = + Maybe.map fieldValueToString maybeFieldValue + |> maybeToList + + fallbackIds = + Maybe.map fieldValueToString maybeFallbackFieldValue + |> maybeToList + in + Selectize.view (buildConfig localConfig specificConfig) + selectedIds + availableItems + fallbackIds + selectizeState + + +cryptoCurrencySelectizeView : + ResolvedModel + -> LocalConfig + -> FieldInstance + -> Selectize.State + -> Maybe FieldValue + -> Maybe FieldValue + -> Html Msg +cryptoCurrencySelectizeView model localConfig fieldInstance selectizeState maybeFieldValue maybeFallbackFieldValue = + let + specificConfig = + { maxItems = 6 + , selectedDisplay = .code + , optionDisplay = .display + , match = FuzzyMatch.match + , customCssClass = C.SelectizeCryptoCurrency + } + + toDisplay crypto = + { code = cryptoToString crypto.crypto, display = crypto.display } + + availableItems = + List.map toDisplay model.configGroup.data.cryptoCurrencies + + toList maybeValue = + case maybeValue of + Nothing -> + [] + + Just fieldValue -> + case fieldValue of + FieldCryptoCurrencyValue list -> + list + + _ -> + Debug.crash "Shouldn't be here" + + selectedIds = + toList maybeFieldValue + + fallbackIds = + toList maybeFallbackFieldValue + in + Selectize.view (buildConfig localConfig specificConfig) + selectedIds + availableItems + fallbackIds + selectizeState + + +languageSelectizeView : + ResolvedModel + -> LocalConfig + -> FieldInstance + -> Selectize.State + -> Maybe FieldValue + -> Maybe FieldValue + -> Html Msg +languageSelectizeView model localConfig fieldInstance selectizeState maybeFieldValue maybeFallbackFieldValue = + let + specificConfig = + { maxItems = 4 + , selectedDisplay = .code + , optionDisplay = .display + , match = FuzzyMatch.match + , customCssClass = C.SelectizeLanguage + } + + availableItems = + model.configGroup.data.languages + + toList maybeValue = + case maybeValue of + Nothing -> + [] + + Just fieldValue -> + case fieldValue of + FieldLanguageValue list -> + list + + _ -> + Debug.crash "Shouldn't be here" + + selectedIds = + toList maybeFieldValue + + fallbackIds = + toList maybeFallbackFieldValue + in + Selectize.view (buildConfig localConfig specificConfig) + selectedIds + availableItems + fallbackIds + selectizeState + + +countrySelectizeView : + ResolvedModel + -> LocalConfig + -> FieldInstance + -> Selectize.State + -> Maybe FieldValue + -> Maybe FieldValue + -> Html Msg +countrySelectizeView model localConfig fieldInstance selectizeState maybeFieldValue maybeFallbackFieldValue = + let + specificConfig = + { maxItems = 1 + , selectedDisplay = .code + , optionDisplay = .display + , match = FuzzyMatch.match + , customCssClass = C.SelectizeCountry + } + + availableItems = + model.configGroup.data.countries + + selectedIds = + Maybe.map fieldValueToString maybeFieldValue + |> maybeToList + + fallbackIds = + Maybe.map fieldValueToString maybeFallbackFieldValue + |> maybeToList + in + Selectize.view (buildConfig localConfig specificConfig) + selectedIds + availableItems + fallbackIds + selectizeState + + +selectizeView : + ResolvedModel + -> FieldInstance + -> Selectize.State + -> Maybe FieldValue + -> Maybe FieldValue + -> Bool + -> Html Msg +selectizeView model fieldInstance selectizeState maybeFieldValue maybeFallbackFieldValue enabled = + let + fieldLocator = + fieldInstance.fieldLocator + + localConfig = + { toMsg = SelectizeMsg fieldLocator + , onAdd = Add fieldLocator + , onRemove = Remove fieldLocator + , onFocus = FocusSelectize fieldLocator + , onBlur = BlurSelectize fieldLocator + , toId = .code + , enabled = True + } + + fallbackFieldValue = + Maybe.withDefault "" (Maybe.map fieldValueToString maybeFallbackFieldValue) + in + if fieldInstance.readOnly || (not enabled) then + div [ class [ C.InputContainer, C.ReadOnly ] ] + [ div [ class [ C.BasicInputReadOnly ] ] [ text fallbackFieldValue ] + ] + else + case fieldLocator.fieldType of + FieldAccountType -> + accountSelectizeView model + localConfig + fieldInstance + selectizeState + maybeFieldValue + maybeFallbackFieldValue + + FieldFiatCurrencyType -> + fiatCurrencySelectizeView model + localConfig + fieldInstance + selectizeState + maybeFieldValue + maybeFallbackFieldValue + + FieldCryptoCurrencyType -> + cryptoCurrencySelectizeView model + localConfig + fieldInstance + selectizeState + maybeFieldValue + maybeFallbackFieldValue + + FieldLanguageType -> + languageSelectizeView model + localConfig + fieldInstance + selectizeState + maybeFieldValue + maybeFallbackFieldValue + + FieldCountryType -> + countrySelectizeView model + localConfig + fieldInstance + selectizeState + maybeFieldValue + maybeFallbackFieldValue + + FieldOnOffType -> + onOffSelectizeView model + localConfig + fieldInstance + selectizeState + maybeFieldValue + maybeFallbackFieldValue + + _ -> + Debug.crash "Not a Selectize field" + + +onOffSelectizeView : + ResolvedModel + -> LocalConfig + -> FieldInstance + -> Selectize.State + -> Maybe FieldValue + -> Maybe FieldValue + -> Html Msg +onOffSelectizeView model localConfig fieldInstance selectizeState maybeFieldValue maybeFallbackFieldValue = + let + specificConfig = + { maxItems = 1 + , selectedDisplay = .display + , optionDisplay = .display + , match = FuzzyMatch.match + , customCssClass = C.SelectizeOnOff + } + + availableItems = + [ { display = "On", code = "on" }, { display = "Off", code = "off" } ] + + selectedIds = + Maybe.map fieldValueToString maybeFieldValue + |> maybeToList + + fallbackIds = + Maybe.map fieldValueToString maybeFallbackFieldValue + |> maybeToList + in + Selectize.view (buildConfig localConfig specificConfig) + selectedIds + availableItems + fallbackIds + selectizeState + + +isJust : Maybe a -> Bool +isJust maybe = + case maybe of + Just a -> + True + + Nothing -> + False + + +fieldInput : ResolvedModel -> FieldInstance -> Maybe FieldValue -> Maybe FieldValue -> Bool -> Html Msg +fieldInput model fieldInstance maybeFieldValue maybeFallbackFieldValue enabled = + if not enabled && (not <| isJust maybeFallbackFieldValue) then + div [ class [ C.BasicInputDisabled ] ] [] + else + case fieldInstance.component of + InputBoxComponent -> + textInput model.fiat fieldInstance maybeFieldValue maybeFallbackFieldValue enabled + + TextAreaComponent -> + textareaInput model.fiat fieldInstance maybeFieldValue maybeFallbackFieldValue enabled + + SelectizeComponent selectizeState -> + selectizeView model fieldInstance selectizeState maybeFieldValue maybeFallbackFieldValue enabled + + +referenceFields : FieldScope -> List Field -> List String -> List FieldValue +referenceFields fieldScope fields fieldCodes = + let + fallback fieldCode = + fallbackValue fieldScope fields fieldCode + in + List.filterMap fallback fieldCodes + + +fallbackValue : FieldScope -> List Field -> String -> Maybe FieldValue +fallbackValue fieldScope fields fieldCode = + let + pick = + pickFieldValue fieldCode fields + + maybeGlobal = + pick GlobalCrypto GlobalMachine + + maybeGlobalCrypto = + pick GlobalCrypto fieldScope.machine + + maybeGlobalMachine = + pick fieldScope.crypto GlobalMachine + + maybeSpecific = + pick fieldScope.crypto fieldScope.machine + in + List.filterMap identity [ maybeSpecific, maybeGlobalMachine, maybeGlobalCrypto, maybeGlobal ] + |> List.head + + +fieldToFieldMeta : Field -> FieldMeta +fieldToFieldMeta field = + { fieldLocator = field.fieldLocator + , fieldEnabledIfAny = field.fieldEnabledIfAny + , fieldEnabledIfAll = field.fieldEnabledIfAll + , inScope = field.inScope + } + + +fieldInstanceToFieldMeta : FieldInstance -> FieldMeta +fieldInstanceToFieldMeta fieldInstance = + { fieldLocator = fieldInstance.fieldLocator + , fieldEnabledIfAny = fieldInstance.fieldEnabledIfAny + , fieldEnabledIfAll = fieldInstance.fieldEnabledIfAll + , inScope = fieldInstance.inScope + } + + +fieldInstanceToField : FieldInstance -> Maybe Field +fieldInstanceToField fieldInstance = + let + maybeFieldValue = + fieldHolderToMaybe fieldInstance.fieldHolder + + buildFieldInstance fieldValue = + { fieldLocator = fieldInstance.fieldLocator + , fieldValue = fieldValue + , fieldEnabledIfAny = fieldInstance.fieldEnabledIfAny + , fieldEnabledIfAll = fieldInstance.fieldEnabledIfAll + , inScope = fieldInstance.inScope + } + in + Maybe.map buildFieldInstance maybeFieldValue + + +checkEnabled : List Field -> FieldMeta -> Bool +checkEnabled fields fieldMeta = + if not fieldMeta.inScope then + False + else + let + enabledIfAnyInstances = + referenceFields fieldMeta.fieldLocator.fieldScope fields fieldMeta.fieldEnabledIfAny + + enabledIfAllInstances = + referenceFields fieldMeta.fieldLocator.fieldScope fields fieldMeta.fieldEnabledIfAll + + enabledIfAny = + (List.isEmpty fieldMeta.fieldEnabledIfAny) || (List.any isField enabledIfAnyInstances) + + enabledIfAll = + (List.isEmpty fieldMeta.fieldEnabledIfAll) || (List.all isField enabledIfAllInstances) + in + enabledIfAny && enabledIfAll + + +fieldComponent : ResolvedModel -> FieldInstance -> Html Msg +fieldComponent model fieldInstance = + let + fieldLocator = + fieldInstance.fieldLocator + + fieldScope = + fieldLocator.fieldScope + + fieldCode = + fieldLocator.code + + fieldClass = + fieldLocator.fieldClass + + fieldInstances : List FieldInstance + fieldInstances = + model.fieldCollection.fieldInstances + + fieldType = + fieldLocator.fieldType + + maybeSpecific = + case fieldInstance.fieldHolder of + FieldOk fieldValue -> + Just fieldValue + + _ -> + Nothing + + allFields = + buildAllFields model.fieldCollection + + maybeFallbackFieldValue = + fallbackValue fieldScope allFields fieldCode + + enabled = + checkEnabled allFields (fieldInstanceToFieldMeta fieldInstance) + + focused = + (Just fieldLocator) == model.focused + + fieldValid = + validateFieldInstance model.fieldCollection fieldInstance + + fieldLengthClasses = + List.map (\class -> ( class, True )) (fieldInstanceClasses fieldInstance) + in + div + [ classList + ([ ( C.Component, True ) + , ( C.FocusedComponent, focused ) + , ( C.InvalidComponent, not fieldValid ) + ] + ++ fieldLengthClasses + ) + ] + [ fieldInput model fieldInstance maybeSpecific maybeFallbackFieldValue enabled ] + +textareaComponent : ResolvedModel -> FieldInstance -> Html Msg +textareaComponent model fieldInstance = + let + fieldLocator = + fieldInstance.fieldLocator + + fieldScope = + fieldLocator.fieldScope + + fieldCode = + fieldLocator.code + + fieldClass = + fieldLocator.fieldClass + + fieldInstances : List FieldInstance + fieldInstances = + model.fieldCollection.fieldInstances + + fieldType = + fieldLocator.fieldType + + maybeSpecific = + case fieldInstance.fieldHolder of + FieldOk fieldValue -> + Just fieldValue + + _ -> + Nothing + + allFields = + buildAllFields model.fieldCollection + + maybeFallbackFieldValue = + fallbackValue fieldScope allFields fieldCode + + enabled = + checkEnabled allFields (fieldInstanceToFieldMeta fieldInstance) + + focused = + (Just fieldLocator) == model.focused + + fieldValid = + validateFieldInstance model.fieldCollection fieldInstance + + fieldLengthClasses = + List.map (\class -> ( class, True )) (fieldInstanceClasses fieldInstance) + in + div + [ classList + ([ ( C.Component, True ) + , ( C.FocusedComponent, focused ) + , ( C.InvalidComponent, not fieldValid ) + ] + ++ fieldLengthClasses + ) + ] + [ fieldInput model fieldInstance maybeSpecific maybeFallbackFieldValue enabled ] + + +cellView : ResolvedModel -> FieldInstance -> Html Msg +cellView model fieldInstance = + -- Note: keying here is needed to clear out fields when switching cryptos + let + fieldLocator = + fieldInstance.fieldLocator + + fieldScope = + fieldLocator.fieldScope + + machine = + fieldScope.machine + + crypto = + fieldScope.crypto + in + Html.Keyed.node "td" + [] + [ ( (cryptoToString crypto) + ++ "-" + ++ (machineToString machine) + ++ "-" + ++ fieldLocator.code + , fieldComponent model fieldInstance + ) + ] + + +rowView : ResolvedModel -> List FieldInstance -> Bool -> MachineDisplay -> Html Msg +rowView model fieldInstances displayMachineName machineDisplay = + let + machine = + machineDisplay.machine + + globalRowClass = + case machine of + GlobalMachine -> + class [ C.ConfigTableGlobalRow ] + + _ -> + class [] + + fieldScope = + { crypto = model.crypto + , machine = machineDisplay.machine + } + + toFieldLocator entry = + { fieldScope = fieldScope + , code = entry.code + } + + machineScoped fieldInstance = + fieldInstance.fieldLocator.fieldScope.machine == machine + + filteredFieldInstances : List FieldInstance + filteredFieldInstances = + List.filter machineScoped fieldInstances + in + if displayMachineName then + tr [ globalRowClass ] + ((td [ class [ C.ShortCell ] ] [ text (machineDisplay.display) ]) + :: (List.map (cellView model) + filteredFieldInstances + ) + ) + else + tr [ globalRowClass ] (List.map (cellView model) filteredFieldInstances) + + +topHeaderRowView : ConfigGroup -> Crypto -> Bool -> Html Msg +topHeaderRowView configGroup crypto displayMachineName = + let + headerCellView fieldDescriptor = + case fieldDescriptor.displayTop of + DisplayTopLeader cols display -> + Just <| th [ colspan cols, class [ C.MultiDisplay ] ] [ text display ] + + DisplayTopSolo display -> + Just <| th [] [ text display ] + + DisplayTopNone -> + Nothing + + cells = + if displayMachineName then + ((th [] []) :: List.filterMap headerCellView configGroup.schema.entries) + else + List.filterMap headerCellView configGroup.schema.entries + in + tr [ class [ C.TopDisplay ] ] cells + + +bottomHeaderRowView : ConfigGroup -> Crypto -> Bool -> Html Msg +bottomHeaderRowView configGroup crypto displayMachineName = + let + headerCellView fieldDescriptor = + th [] [ text fieldDescriptor.displayBottom ] + + cells = + if displayMachineName then + ((th [] []) :: List.map headerCellView configGroup.schema.entries) + else + List.map headerCellView configGroup.schema.entries + in + tr [] cells + + +complianceTableView : ResolvedModel -> Html Msg +complianceTableView model = + let + cryptoScoped fieldInstance = + fieldInstance.fieldLocator.fieldScope.crypto == model.crypto + + instances : List FieldInstance + instances = + List.filter cryptoScoped model.fieldCollection.fieldInstances + + pickField code = + pickFieldInstance code { crypto = GlobalCrypto, machine = GlobalMachine } instances + + emptyCell = + td [] [ text "--" ] + + fieldCodeCellView code = + Maybe.Extra.unwrap emptyCell (cellView model) (pickField code) + + header = + tr [] + [ th [] [] + , th [] [ text "Active" ] + , th [] [ text "Threshold" ] + ] + + row label activeFieldCode thresholdFieldCode = + tr [] + ((td [ class [ C.ShortCell ] ] [ text label ]) + :: [ fieldCodeCellView activeFieldCode + , fieldCodeCellView thresholdFieldCode + ] + ) + in + table [ class [ C.ConfigTable ] ] + [ tbody [] + [ header + , row "SMS" "smsVerificationActive" "smsVerificationThreshold" + , row "ID Card Data" "idCardDataVerificationActive" "idCardDataVerificationThreshold" + , row "ID Card Photo" "idCardPhotoVerificationActive" "idCardPhotoVerificationThreshold" + , row "Front Facing Camera" "frontCameraVerificationActive" "frontCameraVerificationThreshold" + , row "Sanctions" "sanctionsVerificationActive" "sanctionsVerificationThreshold" + , row "Cross Reference" "crossRefVerificationActive" "crossRefVerificationThreshold" + , row "Hard Limit" "hardLimitVerificationActive" "hardLimitVerificationThreshold" + ] + ] + +termsTableView : ResolvedModel -> Html Msg +termsTableView model = + let + cryptoScoped fieldInstance = + fieldInstance.fieldLocator.fieldScope.crypto == model.crypto + + instances : List FieldInstance + instances = + List.filter cryptoScoped model.fieldCollection.fieldInstances + + pickField code = + pickFieldInstance code { crypto = GlobalCrypto, machine = GlobalMachine } instances + + emptyCell = + td [] [ text "--" ] + + fieldCodeCellView code = + Maybe.Extra.unwrap emptyCell (cellView model) (pickField code) + + row label activeFieldCode = + tr [] + ((td [ class [ C.ShortCell ] ] [ text label ]) + :: [ fieldCodeCellView activeFieldCode ] + ) + in + table [ class [ C.ConfigTable ] ] + [ tbody [] + [ row "Show on screen" "termsScreenActive" + , row "Screen title" "termsScreenTitle" + , row "Text content" "termsScreenText" + , row "Accept button text" "termsAcceptButtonText" + , row "Cancel button text" "termsCancelButtonText" + ] + ] + +tableView : ResolvedModel -> Html Msg +tableView model = + let + configGroup = + model.configGroup + + crypto = + model.crypto + + displayMachineName = + configGroup.schema.code /= "definition" + + topHeaderRow = + topHeaderRowView configGroup crypto displayMachineName + + bottomHeaderRow = + bottomHeaderRowView configGroup crypto displayMachineName + + machines = + listMachines configGroup + + cryptoScoped fieldInstance = + fieldInstance.fieldLocator.fieldScope.crypto == crypto + + instances : List FieldInstance + instances = + List.filter cryptoScoped model.fieldCollection.fieldInstances + + rows = + List.map (rowView model instances displayMachineName) machines + in + table [ class [ C.ConfigTable ] ] + [ thead [] [ topHeaderRow, bottomHeaderRow ] + , tbody [] rows + ] + + +isField : FieldValue -> Bool +isField fieldValue = + case fieldValue of + FieldOnOffValue bool -> + bool + + _ -> + Debug.crash "Referenced field must be boolean" + + +type Msg + = Load WebConfigGroup + | Submit + | Input FieldLocator String + | CryptoSwitch Crypto + | SelectizeMsg FieldLocator Selectize.State + | Blur FieldLocator + | Focus FieldLocator + | BlurSelectize FieldLocator Selectize.State + | FocusSelectize FieldLocator Selectize.State + | Add FieldLocator String Selectize.State + | Remove FieldLocator Selectize.State + | HideSaveIndication + | NoOp + + +maybeToList : Maybe a -> List a +maybeToList maybe = + case maybe of + Nothing -> + [] + + Just x -> + [ x ] + + +buildFieldComponent : ConfigGroup -> FieldType -> FieldScope -> Maybe FieldValue -> FieldComponent +buildFieldComponent configGroup fieldType fieldScope fieldValue = + case fieldType of + FieldStringType -> + InputBoxComponent + + FieldPercentageType -> + InputBoxComponent + + FieldIntegerType -> + InputBoxComponent + + FieldDecimalType -> + InputBoxComponent + + FieldOnOffType -> + SelectizeComponent Selectize.initialSelectize + + FieldAccountType -> + SelectizeComponent Selectize.initialSelectize + + FieldFiatCurrencyType -> + SelectizeComponent Selectize.initialSelectize + + FieldCryptoCurrencyType -> + SelectizeComponent Selectize.initialSelectize + + FieldLanguageType -> + SelectizeComponent Selectize.initialSelectize + + FieldCountryType -> + SelectizeComponent Selectize.initialSelectize + + FieldTextAreaType -> + TextAreaComponent + + FieldMarkdownType -> + TextAreaComponent + + +isInScope : ConfigScope -> ConfigScope -> FieldScope -> Bool +isInScope cryptoScope machineScope fieldScope = + not + ((cryptoScope == Specific && fieldScope.crypto == GlobalCrypto) + || (machineScope == Specific && fieldScope.machine == GlobalMachine) + || (cryptoScope == Global && fieldScope.crypto /= GlobalCrypto) + || (machineScope == Global && fieldScope.machine /= GlobalMachine) + ) + + +initFieldInstance : ConfigGroup -> FieldDescriptor -> FieldScope -> FieldInstance +initFieldInstance configGroup fieldDescriptor fieldScope = + let + fieldLocator : FieldLocator + fieldLocator = + { fieldScope = fieldScope + , code = fieldDescriptor.code + , fieldType = fieldDescriptor.fieldType + , fieldClass = fieldDescriptor.fieldClass + } + + equivalentFieldLocator a b = + a.fieldScope + == b.fieldScope + && a.code + == b.code + + inScope = + isInScope fieldDescriptor.cryptoScope fieldDescriptor.machineScope fieldScope + + maybeValue = + List.filter ((equivalentFieldLocator fieldLocator) << .fieldLocator) configGroup.values + |> List.head + |> Maybe.map .fieldValue + + component = + buildFieldComponent configGroup fieldDescriptor.fieldType fieldScope maybeValue + + maybeToFieldHolder maybe = + Maybe.map FieldOk maybe + |> Maybe.withDefault FieldEmpty + + fieldHolder = + maybeToFieldHolder maybeValue + + readOnly = + if fieldLocator.code == "cashOutEnabled" && fieldScope.machine == GlobalMachine then + True + else + fieldDescriptor.readOnly + in + { fieldLocator = fieldLocator + , component = component + , fieldHolder = fieldHolder + , loadedFieldHolder = fieldHolder + , fieldValidation = fieldDescriptor.fieldValidation + , fieldEnabledIfAny = fieldDescriptor.fieldEnabledIfAny + , fieldEnabledIfAll = fieldDescriptor.fieldEnabledIfAll + , readOnly = readOnly + , inScope = inScope + } + + +validateRequired : List Field -> FieldInstance -> Bool +validateRequired fields fieldInstance = + let + fieldScope = + fieldInstance.fieldLocator.fieldScope + + fieldCode = + fieldInstance.fieldLocator.code + + maybeFallbackFieldValue = + fallbackValue fieldScope fields fieldCode + + maybeFallbackString = + Maybe.map fieldValueToString maybeFallbackFieldValue + + isEmpty = + Maybe.map String.isEmpty maybeFallbackString + |> Maybe.withDefault True + in + not isEmpty + + +validateMin : Int -> FieldValue -> Bool +validateMin min fieldValue = + case fieldValue of + FieldPercentageValue v -> + (ceiling v) >= min + + FieldIntegerValue v -> + v >= min + + _ -> + True + + +validateMax : Int -> FieldValue -> Bool +validateMax max fieldValue = + case fieldValue of + FieldPercentageValue v -> + (floor v) <= max + + FieldIntegerValue v -> + v <= max + + _ -> + True + + +validate : List Field -> FieldInstance -> FieldValidator -> Bool +validate fields fieldInstance fieldValidator = + case fieldValidator of + FieldRequired -> + validateRequired fields fieldInstance + + FieldMin min -> + fieldHolderMap True (validateMin min) fieldInstance.fieldHolder + + FieldMax max -> + fieldHolderMap True (validateMax max) fieldInstance.fieldHolder + + +buildAllFields : FieldCollection -> List Field +buildAllFields fieldCollection = + List.filterMap fieldInstanceToField fieldCollection.fieldInstances + ++ fieldCollection.fields + + +validateFieldInstance : FieldCollection -> FieldInstance -> Bool +validateFieldInstance fieldCollection fieldInstance = + let + allFields = + buildAllFields fieldCollection + + enabled = + checkEnabled allFields (fieldInstanceToFieldMeta fieldInstance) + in + not enabled || List.all (validate allFields fieldInstance) fieldInstance.fieldValidation + + +initFieldInstancesPerEntry : ConfigGroup -> FieldDescriptor -> List FieldInstance +initFieldInstancesPerEntry configGroup fieldDescriptor = + List.map (initFieldInstance configGroup fieldDescriptor) (fieldScopes configGroup) + + +initFieldInstances : ConfigGroup -> List FieldInstance +initFieldInstances configGroup = + List.concatMap (initFieldInstancesPerEntry configGroup) configGroup.schema.entries + + +buildFieldCollection : ConfigGroup -> FieldCollection +buildFieldCollection configGroup = + { fields = configGroup.values + , fieldInstances = initFieldInstances configGroup + } + + +pickFieldInstance : String -> FieldScope -> List FieldInstance -> Maybe FieldInstance +pickFieldInstance fieldCode fieldScope fieldInstances = + let + sameScope fieldInstance = + fieldInstance.fieldLocator.code + == fieldCode + && fieldInstance.fieldLocator.fieldScope + == fieldScope + in + List.filter sameScope fieldInstances + |> List.head + + +fieldInstanceToMaybeFieldValue : FieldInstance -> Maybe FieldValue +fieldInstanceToMaybeFieldValue fieldInstance = + case fieldInstance.fieldHolder of + FieldOk fieldValue -> + Just fieldValue + + _ -> + Nothing + + +pickFieldValue : String -> List Field -> Crypto -> Machine -> Maybe FieldValue +pickFieldValue fieldCode fields crypto machine = + let + fieldScope = + { crypto = crypto, machine = machine } + + sameScope field = + field.fieldLocator.code + == fieldCode + && field.fieldLocator.fieldScope + == fieldScope + in + List.filter sameScope fields |> List.head |> Maybe.map .fieldValue + + +updateFocus : FieldLocator -> Bool -> Model -> Model +updateFocus fieldLocator focused model = + if focused then + { model | focused = Just fieldLocator } + else if model.focused == Just fieldLocator then + { model | focused = Nothing } + else + model + + +isCashOutEnabled : FieldInstance -> Bool +isCashOutEnabled fieldInstance = + let + toBool fieldValue = + case fieldValue of + FieldOnOffValue v -> + v + + _ -> + False + in + fieldInstance.fieldLocator.code + == "cashOutEnabled" + && fieldInstance.fieldLocator.fieldScope.machine + /= GlobalMachine + && fieldHolderMap False toBool fieldInstance.fieldHolder + + +selectizeEdgeCases : FieldInstance -> List FieldInstance -> FieldInstance +selectizeEdgeCases fieldInstance fieldInstances = + if + (fieldInstance.fieldLocator.code + == "cashOutEnabled" + && fieldInstance.fieldLocator.fieldScope.machine + == GlobalMachine + ) + then + { fieldInstance + | fieldHolder = + FieldOk <| + FieldOnOffValue <| + List.any isCashOutEnabled fieldInstances + } + else + fieldInstance + + +updateSelectize : FieldLocator -> Selectize.State -> Model -> Model +updateSelectize fieldLocator state model = + let + fieldInstances = + model.fieldCollection.fieldInstances + + updateInstance fieldInstance = + if (fieldInstance.fieldLocator == fieldLocator) then + case fieldInstance.component of + SelectizeComponent _ -> + { fieldInstance | component = SelectizeComponent state } + + _ -> + Debug.crash "Shouldn't be here" + else + selectizeEdgeCases fieldInstance fieldInstances + + fieldCollection = + model.fieldCollection + + newFieldCollection = + { fieldCollection | fieldInstances = List.map updateInstance fieldInstances } + in + { model | fieldCollection = newFieldCollection } + + +pickFiat : List Field -> Maybe String +pickFiat fields = + List.filter (((==) "fiatCurrency") << .code << .fieldLocator) fields + |> List.head + |> Maybe.map (fieldValueToString << .fieldValue) + + +updateRates : List StatusTypes.Rate -> Model -> Model +updateRates rates model = + { model | rates = rates } + + +submit : Model -> ( Model, Cmd Msg ) +submit model = + case model.webConfigGroup of + Success configGroup -> + { model | status = Saving } + ! [ postForm configGroup.schema.code model.fieldCollection.fieldInstances ] + + _ -> + model ! [] + + +submitNoLoad : Model -> ( Model, Cmd Msg ) +submitNoLoad model = + case model.webConfigGroup of + Success configGroup -> + model + ! [ postFormNoLoad configGroup.schema.code model.fieldCollection.fieldInstances ] + + _ -> + model ! [] + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + Load webConfigGroup -> + let + status = + if model.status == Saving then + Saved + else + model.status + + fieldCollection : FieldCollection + fieldCollection = + case webConfigGroup of + Success configGroup -> + buildFieldCollection configGroup + + _ -> + initFieldCollection + + fiat = + case webConfigGroup of + Success configGroup -> + pickFiat configGroup.values + + _ -> + Nothing + + defaultCrypto = + case webConfigGroup of + Success configGroup -> + (allCryptos configGroup.data.cryptoCurrencies + configGroup.schema.cryptoScope + configGroup.selectedCryptos + ) + |> List.head + |> Maybe.map .crypto + + _ -> + Nothing + + crypto = + case model.crypto of + Nothing -> + defaultCrypto + + Just crypto -> + Just crypto + + cmd = + if status == Saved then + Process.sleep (2 * second) + |> Task.perform (\_ -> HideSaveIndication) + else + Cmd.none + in + ( { model + | webConfigGroup = webConfigGroup + , fieldCollection = fieldCollection + , status = status + , crypto = crypto + , fiat = fiat + } + , cmd + ) + + Submit -> + submit model + + Input fieldLocator valueString -> + updateInput fieldLocator (Just valueString) model ! [] + + CryptoSwitch crypto -> + case model.webConfigGroup of + Success configGroup -> + let + cryptoCode = + cryptoToString crypto + + path = + "#config/" ++ configGroup.schema.code ++ "/" ++ cryptoCode + + command = + Navigation.newUrl path + in + { model | crypto = Just crypto } ! [ command ] + + _ -> + model ! [] + + Focus fieldLocator -> + updateFocus fieldLocator True model ! [] + + Blur fieldLocator -> + updateFocus fieldLocator False model ! [] + + SelectizeMsg fieldLocator selectizeState -> + updateSelectize fieldLocator selectizeState model ! [] + + BlurSelectize fieldLocator selectizeState -> + (updateSelectize fieldLocator selectizeState model + |> updateFocus fieldLocator False + ) + ! [] + + FocusSelectize fieldLocator selectizeState -> + (updateSelectize fieldLocator selectizeState model + |> updateFocus fieldLocator True + ) + ! [] + + Add fieldLocator code selectizeState -> + (updateSelectize fieldLocator selectizeState model + |> updateInput fieldLocator (Just code) + ) + ! [] + + Remove fieldLocator selectizeState -> + (updateSelectize fieldLocator selectizeState model + |> updateInput fieldLocator Nothing + ) + ! [] + + HideSaveIndication -> + { model | status = NotSaving } ! [] + + NoOp -> + model ! [] + + +cryptoView : Maybe Crypto -> CryptoDisplay -> Html Msg +cryptoView maybeActiveCrypto cryptoDisplay = + let + activeClass = + case maybeActiveCrypto of + Nothing -> + class [] + + Just activeCrypto -> + if (activeCrypto == cryptoDisplay.crypto) then + class [ C.Active ] + else + class [] + in + div [ activeClass, class [ C.CryptoTab ], onClick (CryptoSwitch cryptoDisplay.crypto) ] + [ text cryptoDisplay.display ] + + +cryptosView : List CryptoDisplay -> Maybe Crypto -> Html Msg +cryptosView cryptos activeCrypto = + nav [ class [ C.CryptoTabs ] ] (List.map (cryptoView activeCrypto) cryptos) + + +view : Model -> Html Msg +view model = + case model.webConfigGroup of + NotAsked -> + div [] [] + + Loading -> + div [] [ text "Loading..." ] + + Failure err -> + div [] [ text (toString err) ] + + Success configGroup -> + let + resolvedModel = + toResolvedModel model configGroup + + getView = + if configGroup.schema.code == "compliance" then + complianceTableView + else if configGroup.schema.code == "terms" then + termsTableView + else + tableView + + configGroupView = + div [ class [ C.ConfigContainer ] ] + [ getView resolvedModel ] + + cryptos = + allCryptos configGroup.data.cryptoCurrencies + configGroup.schema.cryptoScope + configGroup.selectedCryptos + + statusString = + case model.status of + Saved -> + "Saved" + + _ -> + "" + + machines = + listMachines resolvedModel.configGroup + + fieldInstances = + resolvedModel.fieldCollection.fieldInstances + + cryptoFieldInstances = + List.filter (\fi -> fi.fieldLocator.fieldScope.crypto == resolvedModel.crypto) + fieldInstances + + submitButton = + if List.all (validateFieldInstance model.fieldCollection) cryptoFieldInstances then + div [ onClick Submit, class [ C.Button ] ] [ text "Submit" ] + else + div [ class [ C.Button, C.Disabled ] ] [ text "Submit" ] + + form = + if List.isEmpty machines then + div [ class [ C.EmptyTable ] ] [ text "No paired machines." ] + else + Html.form [] + [ div [] [ configGroupView ] + , div [ class [ C.ButtonRow ] ] + [ submitButton + , div [] [ text statusString ] + ] + ] + in + if (configGroup.schema.cryptoScope == Global) then + div [] + [ div [ class [ C.SectionLabel ] ] [ text configGroup.schema.display ] + , form + ] + else if List.isEmpty cryptos then + div [] + [ div [ class [ C.SectionLabel ] ] [ text configGroup.schema.display ] + , div [] [ text "No Crypto currencies have been set. You can set them under Machine settings." ] + ] + else + div [] + [ div [ class [ C.SectionLabel ] ] [ text configGroup.schema.display ] + , div [] [ (cryptosView cryptos model.crypto) ] + , form + ] + + +loaded : Msg -> Bool +loaded msg = + case msg of + Load webConfigGroup -> + RemoteData.isSuccess webConfigGroup + + _ -> + False diff --git a/lamassu-admin-elm/src/ConfigDecoder.elm b/lamassu-admin-elm/src/ConfigDecoder.elm new file mode 100644 index 00000000..00c42bce --- /dev/null +++ b/lamassu-admin-elm/src/ConfigDecoder.elm @@ -0,0 +1,305 @@ +module ConfigDecoder exposing (..) + +import Json.Decode exposing (..) +import ConfigTypes exposing (..) +import Json.Decode.Pipeline exposing (decode, required, optional, hardcoded, custom) +import BasicTypes exposing (..) + + +fieldValueTypeDecoder : String -> Decoder FieldValue +fieldValueTypeDecoder fieldType = + case fieldType of + "string" -> + map FieldStringValue (field "value" string) + + "percentage" -> + map FieldPercentageValue (field "value" float) + + "integer" -> + map FieldIntegerValue (field "value" int) + + "decimal" -> + map FieldDecimalValue (field "value" float) + + "onOff" -> + map FieldOnOffValue (field "value" bool) + + "account" -> + map FieldFiatCurrencyValue (field "value" string) + + "fiatCurrency" -> + map FieldFiatCurrencyValue (field "value" string) + + "cryptoCurrency" -> + map FieldCryptoCurrencyValue (field "value" (list string)) + + "language" -> + map FieldLanguageValue (field "value" (list string)) + + "country" -> + map FieldCountryValue (field "value" string) + + "textarea" -> + map FieldTextAreaValue (field "value" string) + + "markdown" -> + map FieldMarkdownValue (field "value" string) + + _ -> + fail ("Unsupported field type: " ++ fieldType) + + +fieldValueDecoder : Decoder FieldValue +fieldValueDecoder = + (field "fieldType" string) |> andThen fieldValueTypeDecoder + + +fieldScopeDecoder : Decoder FieldScope +fieldScopeDecoder = + map2 FieldScope + (field "crypto" cryptoDecoder) + (field "machine" machineDecoder) + + +nullOr : Decoder a -> Decoder (Maybe a) +nullOr decoder = + oneOf + [ null Nothing + , map Just decoder + ] + + +fieldLocatorDecoder : Decoder FieldLocator +fieldLocatorDecoder = + map4 FieldLocator + (field "fieldScope" fieldScopeDecoder) + (field "code" string) + ((field "fieldType" string) |> andThen fieldTypeDecoder) + (field "fieldClass" (nullOr string)) + + +fieldDecoder : Decoder Field +fieldDecoder = + map5 Field + (field "fieldLocator" fieldLocatorDecoder) + (field "fieldValue" fieldValueDecoder) + (field "fieldEnabledIfAny" (list string)) + (field "fieldEnabledIfAll" (list string)) + (succeed True) + + +string2machine : String -> Machine +string2machine s = + if s == "global" then + GlobalMachine + else + MachineId s + + +machineDecoder : Decoder Machine +machineDecoder = + map string2machine string + + +cryptoDecoder : Decoder Crypto +cryptoDecoder = + map stringToCrypto string + + +displayRecDecoder : Decoder DisplayRec +displayRecDecoder = + map2 DisplayRec + (field "code" string) + (field "display" string) + + +machineDisplayDecoder : Decoder MachineDisplay +machineDisplayDecoder = + map2 MachineDisplay + (field "machine" machineDecoder) + (field "display" string) + + +cryptoDisplayDecoder : Decoder CryptoDisplay +cryptoDisplayDecoder = + map2 CryptoDisplay + (field "crypto" cryptoDecoder) + (field "display" string) + + +stringToConfigScope : String -> Decoder ConfigScope +stringToConfigScope s = + case s of + "global" -> + succeed Global + + "specific" -> + succeed Specific + + "both" -> + succeed Both + + _ -> + fail ("No such ConfigScope " ++ s) + + +basicFieldTypeDecoder : String -> Decoder FieldType +basicFieldTypeDecoder s = + case s of + "string" -> + succeed FieldStringType + + "percentage" -> + succeed FieldPercentageType + + "integer" -> + succeed FieldIntegerType + + "decimal" -> + succeed FieldDecimalType + + "onOff" -> + succeed FieldOnOffType + + "account" -> + succeed FieldAccountType + + "fiatCurrency" -> + succeed FieldFiatCurrencyType + + "cryptoCurrency" -> + succeed FieldCryptoCurrencyType + + "language" -> + succeed FieldLanguageType + + "country" -> + succeed FieldCountryType + + "textarea" -> + succeed FieldTextAreaType + + "markdown" -> + succeed FieldMarkdownType + + _ -> + fail ("No such FieldType " ++ s) + + +configScopeDecoder : Decoder ConfigScope +configScopeDecoder = + string + |> andThen stringToConfigScope + + +fieldTypeDecoder : String -> Decoder FieldType +fieldTypeDecoder fieldType = + basicFieldTypeDecoder fieldType + + +fieldValidatorDecode : String -> Decoder FieldValidator +fieldValidatorDecode code = + case code of + "min" -> + map FieldMin (field "min" int) + + "max" -> + map FieldMax (field "max" int) + + "required" -> + succeed FieldRequired + + _ -> + fail ("Unsupported fieldValidator: " ++ code) + + +fieldValidatorDecoder : Decoder FieldValidator +fieldValidatorDecoder = + (field "code" string) + |> andThen fieldValidatorDecode + + +displayTopDecoderHelper : Maybe Int -> Decoder DisplayTop +displayTopDecoderHelper maybeDisplayTopCount = + case maybeDisplayTopCount of + Nothing -> + (maybe <| (field "displayTop" string)) + |> map (DisplayTopSolo << (Maybe.withDefault "")) + + Just 0 -> + succeed DisplayTopNone + + Just 1 -> + succeed DisplayTopNone + + Just x -> + map (DisplayTopLeader x) (field "displayTop" string) + + +displayTopDecoder : Decoder DisplayTop +displayTopDecoder = + (maybe <| (field "displayTopCount" int)) + |> andThen displayTopDecoderHelper + + +fieldDescriptorDecoder : Decoder FieldDescriptor +fieldDescriptorDecoder = + decode FieldDescriptor + |> required "code" string + |> required "cryptoScope" configScopeDecoder + |> required "machineScope" configScopeDecoder + |> custom displayTopDecoder + |> required "displayBottom" string + |> custom (maybe (field "displayCount" int)) + |> custom (field "fieldType" string |> andThen fieldTypeDecoder) + |> custom (field "fieldValidation" <| list fieldValidatorDecoder) + |> required "fieldClass" (nullable string) + |> required "fieldEnabledIfAny" (list string) + |> required "fieldEnabledIfAll" (list string) + |> optional "readOnly" bool False + + +configSchemaDecoder : Decoder ConfigSchema +configSchemaDecoder = + map5 ConfigSchema + (field "code" string) + (field "display" string) + (field "cryptoScope" configScopeDecoder) + (field "machineScope" configScopeDecoder) + (field "entries" (list fieldDescriptorDecoder)) + + +configGroupDecoder : Decoder ConfigGroup +configGroupDecoder = + map4 ConfigGroup + (field "schema" configSchemaDecoder) + (field "values" (list fieldDecoder)) + (field "selectedCryptos" (list string)) + (field "data" configDataDecoder) + + +accountRecDecoder : Decoder AccountRec +accountRecDecoder = + oneOf + [ map4 AccountRec + (field "code" string) + (field "display" string) + (field "class" string) + (field "cryptos" (map Just (list cryptoDecoder))) + , map4 AccountRec + (field "code" string) + (field "display" string) + (field "class" string) + (succeed Nothing) + ] + + +configDataDecoder : Decoder ConfigData +configDataDecoder = + map6 ConfigData + (field "cryptoCurrencies" (list cryptoDisplayDecoder)) + (field "currencies" (list displayRecDecoder)) + (field "languages" (list displayRecDecoder)) + (field "countries" (list displayRecDecoder)) + (field "accounts" (list accountRecDecoder)) + (field "machines" (list machineDisplayDecoder)) diff --git a/lamassu-admin-elm/src/ConfigEncoder.elm b/lamassu-admin-elm/src/ConfigEncoder.elm new file mode 100644 index 00000000..c7976f4e --- /dev/null +++ b/lamassu-admin-elm/src/ConfigEncoder.elm @@ -0,0 +1,187 @@ +module ConfigEncoder exposing (..) + +import Json.Encode exposing (..) +import List +import ConfigTypes exposing (..) +import BasicTypes exposing (..) + + +encodeFieldValueObject : String -> Value -> Value +encodeFieldValueObject fieldTypeStr value = + object [ ( "fieldType", string fieldTypeStr ), ( "value", value ) ] + + +encodeFieldValue : FieldValue -> Value +encodeFieldValue fieldValue = + case fieldValue of + FieldStringValue value -> + encodeFieldValueObject "string" (string value) + + FieldPercentageValue value -> + encodeFieldValueObject "percentage" (float value) + + FieldIntegerValue value -> + encodeFieldValueObject "integer" (int value) + + FieldDecimalValue value -> + encodeFieldValueObject "decimal" (float value) + + FieldOnOffValue value -> + encodeFieldValueObject "onOff" (bool value) + + FieldAccountValue value -> + encodeFieldValueObject "account" (string value) + + FieldFiatCurrencyValue value -> + encodeFieldValueObject "fiatCurrency" (string value) + + FieldCryptoCurrencyValue value -> + encodeFieldValueObject "cryptoCurrency" (list (List.map string value)) + + FieldLanguageValue value -> + encodeFieldValueObject "language" (list (List.map string value)) + + FieldCountryValue value -> + encodeFieldValueObject "country" (string value) + + FieldTextAreaValue value -> + encodeFieldValueObject "textarea" (string value) + + FieldMarkdownValue value -> + encodeFieldValueObject "markdown" (string value) + + +encodeCrypto : Crypto -> Value +encodeCrypto crypto = + case crypto of + CryptoCode cryptoCode -> + string cryptoCode + + GlobalCrypto -> + string "global" + + +encodeMachine : Machine -> Value +encodeMachine machine = + case machine of + MachineId machineId -> + string machineId + + GlobalMachine -> + string "global" + + +encodeFieldScope : FieldScope -> Value +encodeFieldScope fieldScope = + Json.Encode.object + [ ( "crypto", encodeCrypto fieldScope.crypto ) + , ( "machine", encodeMachine fieldScope.machine ) + ] + + +fieldTypeEncoder : FieldType -> Value +fieldTypeEncoder fieldType = + case fieldType of + FieldStringType -> + string "string" + + FieldPercentageType -> + string "percentage" + + FieldIntegerType -> + string "integer" + + FieldDecimalType -> + string "decimal" + + FieldOnOffType -> + string "onOff" + + FieldAccountType -> + string "account" + + FieldFiatCurrencyType -> + string "fiatCurrency" + + FieldCryptoCurrencyType -> + string "cryptoCurrency" + + FieldLanguageType -> + string "language" + + FieldCountryType -> + string "country" + + FieldTextAreaType -> + string "textarea" + + FieldMarkdownType -> + string "markdown" + + +maybeString : Maybe String -> Value +maybeString maybeString = + case maybeString of + Nothing -> + null + + Just s -> + string s + + +encodeFieldLocator : FieldLocator -> Value +encodeFieldLocator fieldLocator = + Json.Encode.object + [ ( "fieldScope", encodeFieldScope fieldLocator.fieldScope ) + , ( "code", string fieldLocator.code ) + , ( "fieldType", fieldTypeEncoder fieldLocator.fieldType ) + , ( "fieldClass", maybeString fieldLocator.fieldClass ) + ] + + +encodeFieldResult : FieldInstance -> Maybe Value +encodeFieldResult fieldInstance = + let + encode value = + Json.Encode.object + [ ( "fieldLocator", encodeFieldLocator fieldInstance.fieldLocator ) + , ( "fieldValue", value ) + ] + + dirtyEncode fieldHolder = + case fieldHolder of + ParsingError fieldValue -> + Nothing + + ValidationError fieldValue -> + Nothing + + FieldOk fieldValue -> + if (fieldInstance.loadedFieldHolder == fieldHolder) then + Nothing + else + Just <| encode <| encodeFieldValue fieldValue + + FieldEmpty -> + if (fieldInstance.loadedFieldHolder == fieldHolder) then + Nothing + else + Just <| encode null + in + dirtyEncode fieldInstance.fieldHolder + + +encodeResults : String -> List FieldInstance -> Maybe Value +encodeResults configGroupCode fieldInstances = + let + results = + List.filterMap encodeFieldResult fieldInstances + in + if List.isEmpty results then + Nothing + else + Json.Encode.object + [ ( "groupCode", string configGroupCode ) + , ( "values", list (List.filterMap encodeFieldResult fieldInstances) ) + ] + |> Just diff --git a/lamassu-admin-elm/src/ConfigTypes.elm b/lamassu-admin-elm/src/ConfigTypes.elm new file mode 100644 index 00000000..b9aae9aa --- /dev/null +++ b/lamassu-admin-elm/src/ConfigTypes.elm @@ -0,0 +1,495 @@ +module ConfigTypes exposing (..) + +import String +import Selectize + + +type alias DisplayRec = + { code : String + , display : String + } + + +type Machine + = MachineId String + | GlobalMachine + + +type alias MachineDisplay = + { machine : Machine + , display : String + } + + +type ConfigScope + = Global + | Specific + | Both + + +type FieldHolder + = ParsingError String + | ValidationError String + | FieldOk FieldValue + | FieldEmpty + + +type alias FieldScope = + { crypto : Crypto + , machine : Machine + } + + +type alias FieldLocator = + { fieldScope : FieldScope + , code : String + , fieldType : FieldType + , fieldClass : Maybe String + } + + +type FieldComponent + = InputBoxComponent + | TextAreaComponent + | SelectizeComponent Selectize.State + + +type alias FieldInstance = + { fieldLocator : FieldLocator + , component : FieldComponent + , fieldHolder : FieldHolder + , loadedFieldHolder : FieldHolder + , fieldValidation : List FieldValidator + , fieldEnabledIfAny : List String + , fieldEnabledIfAll : List String + , readOnly : Bool + , inScope : Bool + } + + +type alias ResolvedFieldInstance = + { fieldLocator : FieldLocator + , fieldValue : Maybe FieldValue + } + + +type alias Field = + { fieldLocator : FieldLocator + , fieldValue : FieldValue + , fieldEnabledIfAny : List String + , fieldEnabledIfAll : List String + , inScope : Bool + } + + +type alias FieldMeta = + { fieldLocator : FieldLocator + , fieldEnabledIfAny : List String + , fieldEnabledIfAll : List String + , inScope : Bool + } + + +type FieldType + = FieldStringType + | FieldPercentageType + | FieldIntegerType + | FieldDecimalType + | FieldOnOffType + | FieldAccountType + | FieldFiatCurrencyType + | FieldCryptoCurrencyType + | FieldLanguageType + | FieldCountryType + | FieldTextAreaType + | FieldMarkdownType + + +type FieldValue + = FieldStringValue String + | FieldPercentageValue Float + | FieldIntegerValue Int + | FieldDecimalValue Float + | FieldOnOffValue Bool + | FieldAccountValue String + | FieldFiatCurrencyValue String + | FieldCryptoCurrencyValue (List String) + | FieldLanguageValue (List String) + | FieldCountryValue String + | FieldTextAreaValue String + | FieldMarkdownValue String + + +type FieldValidator + = FieldMin Int + | FieldMax Int + | FieldRequired + + +type DisplayTop + = DisplayTopLeader Int String + | DisplayTopSolo String + | DisplayTopNone + + +type alias FieldDescriptor = + { code : String + , cryptoScope : ConfigScope + , machineScope : ConfigScope + , displayTop : DisplayTop + , displayBottom : String + , displayCount : Maybe Int + , fieldType : FieldType + , fieldValidation : List FieldValidator + , fieldClass : Maybe String + , fieldEnabledIfAny : List String + , fieldEnabledIfAll : List String + , readOnly : Bool + } + + +type alias ConfigSchema = + { code : String + , display : String + , cryptoScope : ConfigScope + , machineScope : ConfigScope + , entries : List FieldDescriptor + } + + +type alias ConfigGroup = + { schema : ConfigSchema + , values : List Field + , selectedCryptos : List String + , data : ConfigData + } + + +type alias AccountRec = + { code : String + , display : String + , class : String + , cryptos : Maybe (List Crypto) + } + + +accountRecToDisplayRec : AccountRec -> DisplayRec +accountRecToDisplayRec accountRec = + { code = accountRec.code + , display = accountRec.display + } + + +type alias ConfigData = + { cryptoCurrencies : List CryptoDisplay + , currencies : List DisplayRec + , languages : List DisplayRec + , countries : List DisplayRec + , accounts : List AccountRec + , machines : List MachineDisplay + } + + +type alias FieldCollection = + { fields : List Field + , fieldInstances : List FieldInstance + } + + +initFieldCollection : FieldCollection +initFieldCollection = + { fields = [] + , fieldInstances = [] + } + + +globalCryptoDisplay : CryptoDisplay +globalCryptoDisplay = + { crypto = GlobalCrypto + , display = "Global" + } + + +globalMachineDisplay : MachineDisplay +globalMachineDisplay = + { machine = GlobalMachine + , display = "Global" + } + + +fieldValueToDisplay : FieldValue -> String +fieldValueToDisplay fieldValue = + case fieldValue of + FieldOnOffValue v -> + if v then + "On" + else + "Off" + + _ -> + fieldValueToString fieldValue + + +fieldValueToString : FieldValue -> String +fieldValueToString fieldValue = + case fieldValue of + FieldStringValue v -> + v + + FieldPercentageValue v -> + toString v + + FieldIntegerValue v -> + toString v + + FieldDecimalValue v -> + toString v + + FieldOnOffValue v -> + if v then + "on" + else + "off" + + FieldAccountValue v -> + v + + FieldFiatCurrencyValue v -> + v + + FieldCryptoCurrencyValue v -> + String.join "," v + + FieldLanguageValue v -> + String.join "," v + + FieldCountryValue v -> + v + + FieldTextAreaValue v -> + v + + FieldMarkdownValue v -> + v + + +machineToString : Machine -> String +machineToString machine = + case machine of + GlobalMachine -> + "global" + + MachineId machineId -> + machineId + + +listMachines : ConfigGroup -> List MachineDisplay +listMachines configGroup = + case configGroup.schema.machineScope of + Specific -> + configGroup.data.machines + + Global -> + [ globalMachineDisplay ] + + Both -> + globalMachineDisplay :: configGroup.data.machines + + +isCrypto : String -> CryptoDisplay -> Bool +isCrypto cryptoString cryptoDisplay = + case cryptoDisplay.crypto of + GlobalCrypto -> + cryptoString == "global" + + CryptoCode string -> + cryptoString == string + + +lookupCryptoDisplay : List CryptoDisplay -> String -> Maybe CryptoDisplay +lookupCryptoDisplay cryptoDisplays cryptoString = + List.filter (isCrypto cryptoString) cryptoDisplays + |> List.head + + +fieldHolderToCryptoStrings : FieldHolder -> List String +fieldHolderToCryptoStrings fieldHolder = + case fieldHolder of + FieldOk fieldValue -> + case fieldValue of + FieldCryptoCurrencyValue cryptoStrings -> + cryptoStrings + + _ -> + [] + + _ -> + [] + + +allCryptos : List CryptoDisplay -> ConfigScope -> List String -> List CryptoDisplay +allCryptos cryptoDisplays cryptoScope cryptoStrings = + let + allSpecificCryptos = + List.filterMap (lookupCryptoDisplay cryptoDisplays) cryptoStrings + in + case cryptoScope of + Global -> + [ globalCryptoDisplay ] + + Specific -> + allSpecificCryptos + + Both -> + globalCryptoDisplay :: allSpecificCryptos + + +listCryptos : ConfigGroup -> List CryptoDisplay +listCryptos configGroup = + case configGroup.schema.cryptoScope of + Specific -> + configGroup.data.cryptoCurrencies + + Global -> + [ globalCryptoDisplay ] + + Both -> + globalCryptoDisplay :: configGroup.data.cryptoCurrencies + + +fieldScopes : ConfigGroup -> List FieldScope +fieldScopes configGroup = + let + machines = + List.map .machine (listMachines configGroup) + + cryptos = + List.map .crypto (listCryptos configGroup) + + cryptoScopes crypto = + List.map (\machine -> { machine = machine, crypto = crypto }) machines + in + List.concatMap cryptoScopes cryptos + + +stringToCrypto : String -> Crypto +stringToCrypto string = + case string of + "global" -> + GlobalCrypto + + _ -> + CryptoCode string + + +fieldHolderToMaybe : FieldHolder -> Maybe FieldValue +fieldHolderToMaybe fieldHolder = + case fieldHolder of + FieldOk fieldValue -> + Just fieldValue + + _ -> + Nothing + + +resultToFieldHolder : Result String FieldValue -> FieldHolder +resultToFieldHolder result = + case result of + Ok fieldValue -> + FieldOk fieldValue + + Err s -> + ParsingError s + + +stringToFieldHolder : FieldType -> String -> FieldHolder +stringToFieldHolder fieldType s = + if (String.isEmpty s) then + FieldEmpty + else + case fieldType of + FieldStringType -> + FieldOk (FieldStringValue s) + + FieldPercentageType -> + String.toFloat s + |> Result.map FieldPercentageValue + |> resultToFieldHolder + + FieldIntegerType -> + String.toInt s + |> Result.map FieldIntegerValue + |> resultToFieldHolder + + FieldDecimalType -> + String.toFloat s + |> Result.map FieldDecimalValue + |> resultToFieldHolder + + FieldOnOffType -> + case s of + "on" -> + FieldOk (FieldOnOffValue True) + + "off" -> + FieldOk (FieldOnOffValue False) + + _ -> + ParsingError ("Unsupported value for OnOff: " ++ s) + + FieldAccountType -> + FieldOk (FieldAccountValue s) + + FieldFiatCurrencyType -> + FieldOk (FieldFiatCurrencyValue s) + + FieldCryptoCurrencyType -> + FieldOk (FieldCryptoCurrencyValue [ s ]) + + FieldLanguageType -> + FieldOk (FieldLanguageValue [ s ]) + + FieldCountryType -> + FieldOk (FieldCountryValue s) + + FieldTextAreaType -> + FieldOk (FieldTextAreaValue s) + + FieldMarkdownType -> + FieldOk (FieldMarkdownValue s) + + +groupMember : ConfigGroup -> String -> Bool +groupMember configGroup fieldCode = + List.any (.code >> ((==) fieldCode)) configGroup.schema.entries + + +fieldHolderMap : a -> (FieldValue -> a) -> FieldHolder -> a +fieldHolderMap default mapper fieldHolder = + case fieldHolder of + FieldOk v -> + mapper v + + _ -> + default + + +type Crypto + = CryptoCode String + | GlobalCrypto + + +type alias CryptoDisplay = + { crypto : Crypto + , display : String + } + + +cryptoToString : Crypto -> String +cryptoToString crypto = + case crypto of + GlobalCrypto -> + "global" + + CryptoCode code -> + code diff --git a/lamassu-admin-elm/src/CoreTypes.elm b/lamassu-admin-elm/src/CoreTypes.elm new file mode 100644 index 00000000..f9c69722 --- /dev/null +++ b/lamassu-admin-elm/src/CoreTypes.elm @@ -0,0 +1,62 @@ +module CoreTypes + exposing + ( Msg(..) + , Category(..) + , Route(..) + ) + +import Navigation +import Pair +import Account +import Config +import MaintenanceMachines.Types +import MaintenanceFunding.Types +import Transaction.Types +import Transactions +import Customers.Types +import Customer.Types +import Logs.Types +import SupportLogs.Types +import StatusTypes + + +type Category + = AccountCat + | MachineSettingsCat + | GlobalSettingsCat + | MaintenanceCat + + +type Route + = AccountRoute String + | PairRoute + | ConfigRoute String (Maybe String) + | TransactionsRoute + | TransactionRoute String + | CustomersRoute + | CustomerRoute String + | LogsRoute (Maybe String) + | SupportLogsRoute (Maybe String) + | MaintenanceMachinesRoute + | MaintenanceFundingRoute (Maybe String) + | NotFoundRoute + + +type Msg + = AccountMsg Account.Msg + | PairMsg Pair.Msg + | ConfigMsg Config.Msg + | MaintenanceMachinesMsg MaintenanceMachines.Types.Msg + | MaintenanceFundingMsg MaintenanceFunding.Types.Msg + | TransactionsMsg Transactions.Msg + | TransactionMsg Transaction.Types.Msg + | CustomersMsg Customers.Types.Msg + | CustomerMsg Customer.Types.Msg + | LogsMsg Logs.Types.Msg + | SupportLogsMsg SupportLogs.Types.Msg + | LoadAccounts (List ( String, String )) + | LoadStatus StatusTypes.WebStatus + | NewUrl String + | UrlChange Navigation.Location + | Interval + | WebSocketMsg String diff --git a/lamassu-admin-elm/src/Css/Admin.elm b/lamassu-admin-elm/src/Css/Admin.elm new file mode 100644 index 00000000..d2577331 --- /dev/null +++ b/lamassu-admin-elm/src/Css/Admin.elm @@ -0,0 +1,35 @@ +module Css.Admin exposing (className, class, classList, id) + +import Css.Helpers +import Html +import Html.CssHelpers + + +name : String +name = + "lamassuAdmin" + + +helpers : Html.CssHelpers.Namespace String class id msg +helpers = + Html.CssHelpers.withNamespace name + + +className : class -> String +className class = + Css.Helpers.identifierToString name class + + +class : List class -> Html.Attribute msg +class = + helpers.class + + +classList : List ( class, Bool ) -> Html.Attribute msg +classList = + helpers.classList + + +id : id -> Html.Attribute msg +id = + helpers.id diff --git a/lamassu-admin-elm/src/Css/Classes.elm b/lamassu-admin-elm/src/Css/Classes.elm new file mode 100644 index 00000000..e3185545 --- /dev/null +++ b/lamassu-admin-elm/src/Css/Classes.elm @@ -0,0 +1,83 @@ +module Css.Classes exposing (..) + + + +type CssClasses + = Layout + | Main + | PaneWrapper + | LeftPane + | ContentPane + | NavBar + | MainLeft + | MainRight + | NavBarItemActive + | NavBarCategoryContainer + | NavBarCategory + | NavBarRoute + | Container + | Content + | CryptoTabs + | CryptoTab + | CryptoTabsActive + | SectionLabel + | ConfigTable + | ConfigTableGlobalRow + | ConfigContainer + | TopDisplay + | BottomDisplay + | MultiDisplay + | ShortCell + | MediumCell + | LongCell + | TextCell + | FormRow + | Button + | ButtonRow + | Active + | BasicInput + | BasicInputDisabled + | BasicInputReadOnly + | CellDisabled + | NoInput + | Component + | FocusedComponent + | InvalidComponent + | TableButton + | Fail + | Success + | StatusBar + | InvalidGroup + | NumberColumn + | DirectionColumn + | TruncatedColumn + | DateColumn + | TxTable + | InputContainer + | UnitDisplay + | EmptyTable + | Saving + | Enabled + | Disabled + | TxId + | TxDate + | TxMachine + | TxAmount + | TxFiat + | TxCrypto + | TxPhone + | TxAddress + | TxCancelled + | QrCode + | CashOut + | CashIn + | ReadOnly + | CryptoAddress + | BalanceSection + | Textarea + | SelectizeAccount + | SelectizeFiatCurrency + | SelectizeCryptoCurrency + | SelectizeLanguage + | SelectizeCountry + | SelectizeOnOff diff --git a/lamassu-admin-elm/src/Css/ColorSchemes.elm b/lamassu-admin-elm/src/Css/ColorSchemes.elm new file mode 100644 index 00000000..673733c1 --- /dev/null +++ b/lamassu-admin-elm/src/Css/ColorSchemes.elm @@ -0,0 +1,72 @@ +module Css.ColorSchemes exposing (..) + +import Css exposing (..) +import Css.LocalColors as Colors +import Css.Classes exposing (..) + + +type alias ColorScheme = + { bg : Color + , fg : Color + , bgHover : Color + , fgActive : Color + , bgActive : Color + } + + +darkGreyScheme : ColorScheme +darkGreyScheme = + { bg = Colors.darkGrey + , fg = Colors.sandstone + , bgHover = Colors.darkerGrey + , fgActive = Colors.amazonite + , bgActive = Colors.darkerGrey + } + + +darkerGreyScheme : ColorScheme +darkerGreyScheme = + { bg = Colors.darkerGrey + , fg = Colors.sandstone + , bgHover = Colors.darkerGrey + , fgActive = Colors.amazonite + , bgActive = Colors.darkerGrey + } + + +lightGreyScheme : ColorScheme +lightGreyScheme = + { bg = Colors.darkerLightGrey + , fg = Colors.sandstone + , bgHover = Colors.lighterLightGrey + , fgActive = Colors.sandstone + , bgActive = Colors.lightGrey + } + + +cobaltScheme : ColorScheme +cobaltScheme = + { bg = Colors.cobalt + , fg = Colors.white + , bgHover = Colors.darkCobalt + , fgActive = Colors.amazonite + , bgActive = Colors.darkCobalt + } + + +colorize : ColorScheme -> Style +colorize scheme = + batch + [ color scheme.fg + , fontWeight bold + , cursor pointer + , backgroundColor scheme.bg + , hover + [ backgroundColor scheme.bgHover + ] + , active [ color scheme.fgActive ] + , withClass Active + [ color scheme.fgActive + , backgroundColor scheme.bgActive + ] + ] diff --git a/lamassu-admin-elm/src/Css/LocalColors.elm b/lamassu-admin-elm/src/Css/LocalColors.elm new file mode 100644 index 00000000..1cb35f4d --- /dev/null +++ b/lamassu-admin-elm/src/Css/LocalColors.elm @@ -0,0 +1,68 @@ +module Css.LocalColors exposing (..) + +import Css exposing (..) + + +cobalt : Color +cobalt = + hex "004062" + + +darkCobalt : Color +darkCobalt = + hex "042c47" + + +amazonite : Color +amazonite = + hex "37e8d7" + + +white : Color +white = + hex "ffffff" + + +sandstone : Color +sandstone = + hex "5f5f56" + + +lightGrey : Color +lightGrey = + hex "f6f6f4" + + +lighterLightGrey : Color +lighterLightGrey = + hex "fcfcfa" + + +darkerLightGrey : Color +darkerLightGrey = + hex "E6E6E3" + + +darkGrey : Color +darkGrey = + hex "2d2d2d" + + +darkerGrey : Color +darkerGrey = + hex "282828" + + +red : Color +red = + hex "eb6b6e" + + +lightRed : Color +lightRed = + hex "efd1d2" + + +disabledGrey : Color +disabledGrey = + hex "757575" diff --git a/lamassu-admin-elm/src/Css/Main.elm b/lamassu-admin-elm/src/Css/Main.elm new file mode 100644 index 00000000..4c553318 --- /dev/null +++ b/lamassu-admin-elm/src/Css/Main.elm @@ -0,0 +1,523 @@ +module Css.Main exposing (..) + +import Css exposing (..) +import Css.Elements + exposing + ( body + , li + , a + , div + , td + , th + , tr + , thead + , tbody + , input + , button + , label + , p + , svg + , h2 + ) +import Css.Namespace exposing (namespace) +import Css.LocalColors as Colors +import Css.ColorSchemes exposing (..) +import Css.Classes exposing (..) +import Css.Selectize + + +type CssIds + = Page + + +mainBackgroundColor : Color +mainBackgroundColor = + Colors.lightGrey + + +contentBackgroundColor : Color +contentBackgroundColor = + Colors.white + + +navBackgroundColor : Color +navBackgroundColor = + Colors.darkGrey + + +navItemActiveBackgroundColor : Color +navItemActiveBackgroundColor = + Colors.darkerGrey + + +navItemActiveColor : Color +navItemActiveColor = + Colors.amazonite + + +navItemColor : Color +navItemColor = + Colors.sandstone + + +cryptoTabsBackgroundColor : Color +cryptoTabsBackgroundColor = + Colors.cobalt + + +cryptoTabsHoverBackgroundColor : Color +cryptoTabsHoverBackgroundColor = + Colors.darkCobalt + + +cryptoTabsColor : Color +cryptoTabsColor = + Colors.white + + +cryptoTabsActiveColor : Color +cryptoTabsActiveColor = + Colors.amazonite + + +cobaltBG : Color +cobaltBG = + Colors.cobalt + + +cobaltHoverBG : Color +cobaltHoverBG = + Colors.darkCobalt + + +cobaltColor : Color +cobaltColor = + Colors.white + + +cobaltActiveColor : Color +cobaltActiveColor = + Colors.amazonite + + +codeFonts : List String +codeFonts = + [ "Inconsolata", "monospace" ] + + +css : Stylesheet +css = + (stylesheet << namespace "lamassuAdmin") + [ body + [ fontFamilies [ "Nunito", "sans-serif" ] + , margin zero + ] + , p + [ margin zero ] + , class QrCode + [ backgroundColor Colors.lightGrey + , padding (px 10) + , marginBottom (px 20) + , borderRadius (px 6) + , descendants + [ svg + [ height (px 400) + , width (px 400) + ] + ] + ] + , class Layout + [] + , class Main + [ displayFlex + , marginBottom (px 40) + ] + , class PaneWrapper + [ displayFlex + ] + , class LeftPane + [ minWidth (px 270) + ] + , class ContentPane + [ maxHeight (pct 100) + ] + , class StatusBar + [ position fixed + , bottom zero + , padding2 (px 10) (px 20) + , backgroundColor Colors.sandstone + , color Colors.white + , width (pct 100) + ] + , class CashOut + [ backgroundColor Colors.lightGrey + ] + , class FormRow + [ margin2 (px 20) zero + , firstChild + [ margin zero + ] + , descendants + [ label + [ fontSize (px 11) + , fontWeight bold + , children + [ div + [ margin3 zero zero (px 5) + , color Colors.sandstone + ] + ] + ] + , input + [ border zero + , backgroundColor Colors.white + , borderRadius (px 3) + , padding (px 6) + , textAlign left + , fontFamilies codeFonts + , fontSize (px 14) + , fontWeight (int 600) + , width (pct 90) + , property "outline" "none" + ] + ] + ] + , class ButtonRow + [ textAlign right ] + , class Button + [ colorize cobaltScheme + , padding2 (px 10) (px 15) + , display inlineBlock + , borderRadius (px 5) + , withClass Disabled + [ backgroundColor Colors.darkerLightGrey + , color Colors.white + , cursor default + ] + ] + , class MainLeft + [ backgroundColor navBackgroundColor + , height (pct 100) + ] + , class MainRight + [ backgroundColor mainBackgroundColor + , height (pct 100) + ] + , class Content + [ margin (px 20) + , backgroundColor contentBackgroundColor + , borderRadius (px 5) + ] + , class Container + [ padding (px 30) + , backgroundColor Colors.lightGrey + , borderRadius4 (px 0) (px 5) (px 5) (px 5) + , width (em 30) + ] + , class CryptoAddress + [ fontFamilies codeFonts ] + , class BalanceSection + [ marginTop (em 2) + , descendants + [ h2 + [ fontSize (em 1.2) + , marginBottom (em 0.2) + ] + ] + ] + , class Textarea + [ width (pct 100) + , border (px 0) + , backgroundColor transparent + ] + , class CryptoTabs + [ displayFlex + , children + [ class CryptoTab + [ padding2 (px 10) (px 15) + , colorize lightGreyScheme + , textDecoration none + , firstChild + [ borderRadius4 (px 5) (px 0) (px 0) (px 0) + ] + , lastChild + [ borderRadius4 (px 0) (px 5) (px 0) (px 0) + ] + ] + ] + ] + , class SectionLabel + [ fontWeight bold + , fontSize (px 30) + , marginBottom (px 10) + ] + , class ConfigContainer + [ padding2 (px 20) (px 60) + , borderRadius4 (px 0) (px 7) (px 7) (px 7) + , backgroundColor mainBackgroundColor + , margin3 zero zero (px 10) + , property "animation" "fadein 0.8s" + , overflow hidden + , minHeight (em 15) + , minWidth (em 20) + ] + , class NoInput + [ fontFamilies codeFonts + , color Colors.sandstone + , fontWeight normal + , textAlign left |> important + ] + , class TxTable + [ borderRadius (px 7) + , margin2 (px 20) zero + , property "border-collapse" "collapse" + , fontSize (px 14) + , width (pct 100) + , backgroundColor Colors.white + , descendants + [ a + [ textDecoration none + , color Colors.sandstone + , borderBottom3 (px 1) solid Colors.amazonite + ] + , class NumberColumn + [ textAlign right + , width (em 10) + ] + , class DirectionColumn + [ textAlign left + , fontWeight bold + , fontSize (pct 90) + ] + , class TxCancelled + [ backgroundColor Colors.lightRed ] + , tbody + [ fontFamilies codeFonts + , color Colors.sandstone + , descendants + [ td + [ padding2 (px 2) (px 14) + , borderBottom3 (px 1) solid Colors.lightGrey + , whiteSpace noWrap + ] + , class TruncatedColumn + [ maxWidth zero + , overflow hidden + , width (px 300) + , textOverflow ellipsis + ] + , class TxDate [ width (em 10) ] + , class TxAddress + [ width (em 25) + ] + ] + ] + , thead + [ fontSize (px 14) + , textAlign center + , color Colors.sandstone + , descendants + [ td + [ borderBottom3 (px 2) solid Colors.lightGrey + , padding (px 5) + ] + ] + ] + ] + ] + , class EmptyTable + [ fontSize (px 20) + , fontWeight normal + ] + , class ConfigTable + [ fontSize (px 14) + , fontWeight bold + , borderRadius (px 7) + , margin2 (px 20) zero + , property "border-collapse" "collapse" + , descendants + [ class Css.Selectize.SelectizeContainer + [ Css.Selectize.component + , border3 (px 2) solid Colors.darkerLightGrey + , borderRadius (px 3) + ] + , class InputContainer + [ displayFlex + , property "justify-content" "flex-end" + , border3 (px 2) solid Colors.darkerLightGrey + , borderRadius (px 3) + ] + , class UnitDisplay + [ backgroundColor Colors.darkerLightGrey + , color Colors.sandstone + , padding2 zero (px 5) + , fontWeight (int 700) + , fontSize (pct 80) + , lineHeight (px 25) + , cursor default + , fontFamilies [ "Nunito", "sans-serif" ] + ] + , input + [ border zero + , borderRadius (px 3) + , padding (px 6) + , textAlign right + , width (pct 100) + , fontFamilies codeFonts + , fontWeight (int 600) + , fontSize (px 14) + , outline none + , backgroundColor Colors.white + ] + , class CellDisabled + [ property "background" "repeating-linear-gradient(45deg,#dfdfdc,#dfdfdc 2px,#e6e6e3 5px)" + ] + , class BasicInput + [ pseudoElement "placeholder" + [ color Colors.amazonite + , opacity (num 1) + ] + ] + , class BasicInputDisabled + [ height (px 25) + , lineHeight (px 25) + , fontSize (px 14) + , fontWeight (int 500) + , color Colors.sandstone + , opacity (num 0.7) + , textAlign left + , padding2 zero (em 1) + , property "background" "repeating-linear-gradient(45deg,#dfdfdc,#dfdfdc 2px,#e6e6e3 5px)" + ] + , class ReadOnly + [ lineHeight (px 25) + , backgroundColor Colors.lightGrey + , fontFamilies codeFonts + , fontSize (px 14) + , fontWeight (int 600) + , color Colors.sandstone + , cursor default + , children + [ class BasicInputReadOnly + [ padding2 zero (px 5) + ] + ] + ] + , td + [ padding2 (px 3) (px 4) + , textAlign center + , verticalAlign middle + , width (em 5) + ] + , class Component + [ borderRadius (px 3) + , border3 (px 2) solid Colors.lightGrey + , backgroundColor Colors.white + ] + , class FocusedComponent + [ children + [ class InputContainer + [ borderColor Colors.amazonite ] + ] + ] + , class InvalidComponent + [ children + [ class InputContainer [ borderColor Colors.red ] + , class Css.Selectize.SelectizeContainer [ borderColor Colors.red ] + ] + , descendants + [ input + [ color Colors.red + ] + ] + ] + , tbody + [ descendants + [ td + [ textAlign right + , whiteSpace noWrap + ] + , td + [ firstChild + [ fontWeight normal + ] + ] + ] + ] + , thead + [ fontWeight bold + , textAlign left + ] + , class MultiDisplay + [ backgroundColor Colors.darkerLightGrey + , borderLeft3 (px 3) solid Colors.lightGrey + , borderRight3 (px 3) solid Colors.lightGrey + , borderRadius (px 3) + ] + , th + [ padding2 (px 3) (px 4) + , textAlign center + ] + , class ConfigTableGlobalRow + [ descendants + [ td + [ firstChild + [ fontWeight bold + ] + ] + ] + ] + , class TextCell + [ textAlign left ] + , class ShortCell + [ minWidth (em 5) ] + , class MediumCell + [ minWidth (em 10) ] + , class LongCell + [ minWidth (em 20) ] + ] + ] + , class Saving + [ fontSize (px 18) + , fontWeight normal + , textAlign right + ] + , class NavBar + [ margin zero + , padding4 zero zero (px 110) zero + , backgroundColor Colors.darkGrey + , fontSize (px 18) + , width (em 15) + , maxWidth (em 15) + , minWidth (em 15) + , height (pct 100) + , descendants + [ class NavBarRoute + [ height (px 60) + , display block + , lineHeight (px 60) + , padding2 (px 0) (px 20) + , colorize darkGreyScheme + ] + , class NavBarCategory + [ height (px 60) + , display block + , lineHeight (px 60) + , padding2 (px 0) (px 20) + , colorize darkGreyScheme + ] + , class InvalidGroup + [ color Colors.red |> important ] + , class NavBarCategoryContainer + [ descendants + [ class NavBarRoute + [ colorize darkGreyScheme + , padding4 zero (px 20) zero (px 30) + , fontWeight (int 500) + , property "animation" "fadein 0.8s" + ] + ] + ] + ] + ] + ] diff --git a/lamassu-admin-elm/src/Css/Selectize.elm b/lamassu-admin-elm/src/Css/Selectize.elm new file mode 100644 index 00000000..e4a5832d --- /dev/null +++ b/lamassu-admin-elm/src/Css/Selectize.elm @@ -0,0 +1,164 @@ +module Css.Selectize exposing (..) + +import Css exposing (..) +import Css.LocalColors as Colors +import Selectize +import Css.Admin exposing (className) +import Css.Elements exposing (input) +import Css.Classes as C + +codeFonts : List String +codeFonts = + [ "Inconsolata", "monospace" ] + + +component : Style +component = + batch + [ borderRadius (px 3) + , position relative + , margin zero + , descendants + [ class NoOptions + [ backgroundColor Colors.lighterLightGrey + , fontSize (px 14) + , fontWeight (int 500) + , color Colors.sandstone + , padding (px 5) + , textAlign center + , cursor default + , property "-webkit-user-select" "none" + ] + , class SelectBox + [ displayFlex + , alignItems center + , padding2 zero (px 5) + , property "background-color" "inherit" + , width (px 60) + ] + , class BoxContainer + [ position absolute + , property "z-index" "100" + , left (px -3) + , backgroundColor Colors.white + , textAlign left + , fontWeight (int 500) + , fontSize (pct 80) + , borderRadius (px 3) + , backgroundColor Colors.white + , border3 (px 2) solid Colors.darkerLightGrey + , borderTop zero + , color Colors.sandstone + , width (em 15) + , cursor pointer + , padding (px 5) + ] + , class BoxItems + [] + , class BoxItemActive + [ color Colors.cobalt + , fontWeight (int 900) + ] + , class BoxItem + [ padding2 (px 3) (px 6) + , overflow hidden + , textOverflow ellipsis + ] + , class Info + [ padding2 (px 3) (px 6) + , color Colors.darkGrey + ] + , class MultiItemContainer + [ descendants + [ class SelectedItem + [ backgroundColor Colors.cobalt + , color Colors.white + , padding (px 2) + , margin2 zero (px 1) + , fontFamilies codeFonts + , fontSize (pct 70) + , fontWeight normal + , borderRadius (px 3) + ] + , class FallbackItem + [ backgroundColor Colors.amazonite + ] + ] + ] + , class SingleItemContainer + [ descendants + [ class SelectedItem + [ fontFamilies codeFonts + , fontSize (px 14) + , padding zero + , borderRadius zero + ] + , class FallbackItem + [ color Colors.sandstone + ] + ] + ] + , class C.SelectizeLanguage + [ descendants + [ class SelectBox + [ width (px 140) + ] + ] + ] + , class C.SelectizeCryptoCurrency + [ descendants + [ class SelectBox + [ width (px 150) + ] + ] + ] + , input + [ textAlign left + , property "background-color" "inherit" + , padding2 (px 6) (px 2) + , width (em 6) + , cursor default + ] + ] + ] + + +type Class + = SelectizeContainer + | SelectBox + | BoxItems + | BoxItem + | BoxItemActive + | SelectedItems + | FallbackItems + | FallbackItem + | SelectedItem + | InputEditing + | SingleItemContainer + | MultiItemContainer + | BoxContainer + | Info + | InfoNoMatches + | NoOptions + | Disabled + + +classes : Selectize.HtmlClasses +classes = + { container = className SelectizeContainer + , singleItemContainer = className SingleItemContainer + , multiItemContainer = className MultiItemContainer + , selectBox = className SelectBox + , selectedItems = className SelectedItems + , fallbackItems = className FallbackItems + , fallbackItem = className FallbackItem + , selectedItem = className SelectedItem + , boxContainer = className BoxContainer + , boxItems = className BoxItems + , boxItem = className BoxItem + , boxItemActive = className BoxItemActive + , info = className Info + , infoNoMatches = className InfoNoMatches + , inputEditing = className InputEditing + , noOptions = className NoOptions + } diff --git a/lamassu-admin-elm/src/Customer/Rest.elm b/lamassu-admin-elm/src/Customer/Rest.elm new file mode 100644 index 00000000..dfdae6a8 --- /dev/null +++ b/lamassu-admin-elm/src/Customer/Rest.elm @@ -0,0 +1,24 @@ +module Customer.Rest exposing (..) + +import RemoteData exposing (..) +import Http +import HttpBuilder exposing (..) +import Common.Customer.Decoder exposing (customerDecoder) +import Common.Customer.Types exposing (..) +import Customer.Types exposing (..) + + +patchCustomer : String -> String -> Authorized -> Cmd Msg +patchCustomer id field value = + patch ("/api/customer/" ++ id ++ "?" ++ field ++ "=" ++ authorizedToString value) + |> withExpect (Http.expectJson customerDecoder) + |> send RemoteData.fromResult + |> Cmd.map Load + + +getCustomer : String -> Cmd Msg +getCustomer id = + get ("/api/customer/" ++ id) + |> withExpect (Http.expectJson customerDecoder) + |> send RemoteData.fromResult + |> Cmd.map Load diff --git a/lamassu-admin-elm/src/Customer/State.elm b/lamassu-admin-elm/src/Customer/State.elm new file mode 100644 index 00000000..14334493 --- /dev/null +++ b/lamassu-admin-elm/src/Customer/State.elm @@ -0,0 +1,25 @@ +module Customer.State exposing (..) + +import RemoteData exposing (..) +import Customer.Rest exposing (..) +import Customer.Types exposing (..) + + +init : Model +init = + NotAsked + + +load : String -> ( Model, Cmd Msg ) +load id = + ( Loading, getCustomer id ) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + Load loadedModel -> + loadedModel ! [] + + PatchCustomer id fieldName value -> + model ! [ patchCustomer id fieldName value ] diff --git a/lamassu-admin-elm/src/Customer/Types.elm b/lamassu-admin-elm/src/Customer/Types.elm new file mode 100644 index 00000000..cbf4d304 --- /dev/null +++ b/lamassu-admin-elm/src/Customer/Types.elm @@ -0,0 +1,13 @@ +module Customer.Types exposing (..) + +import RemoteData exposing (..) +import Common.Customer.Types exposing (..) + + +type alias Model = + RemoteData.WebData Customer + + +type Msg + = Load Model + | PatchCustomer String String Authorized diff --git a/lamassu-admin-elm/src/Customer/View.elm b/lamassu-admin-elm/src/Customer/View.elm new file mode 100644 index 00000000..d1370051 --- /dev/null +++ b/lamassu-admin-elm/src/Customer/View.elm @@ -0,0 +1,197 @@ +module Customer.View exposing (..) + +import Html exposing (..) +import Html.Attributes exposing (..) +import Html.Events exposing (onClick) +import RemoteData exposing (..) +import Css.Admin as CSSAdmin exposing (..) +import Css.Classes as C +import Common.Customer.Types exposing (..) +import Customer.Types exposing (..) +import Date exposing (..) +import Date.Extra exposing (toFormattedString) + + +customerActions : String -> Authorized -> Html Msg +customerActions id authorizedOverride = + case authorizedOverride of + Blocked -> + button [ onClick (PatchCustomer id "authorizedOverride" Verified) ] [ text "Unblock" ] + + Verified -> + button [ onClick (PatchCustomer id "authorizedOverride" Blocked) ] [ text "Block" ] + + Automatic -> + button [ onClick (PatchCustomer id "authorizedOverride" Blocked) ] [ text "Block" ] + + +formatDate : Maybe Date -> String +formatDate date = + case date of + Just date -> + toFormattedString "yyyy-MM-dd HH:mm" date + + Nothing -> + "" + + +maybeText : Maybe String -> Html Msg +maybeText maybeString = + text (Maybe.withDefault "" maybeString) + + +actions : String -> String -> Authorized -> Html Msg +actions id fieldKey checkedValue = + (div [] + [ div [] + [ radio fieldKey checkedValue Automatic (PatchCustomer id fieldKey Automatic) + , radio fieldKey checkedValue Blocked (PatchCustomer id fieldKey Blocked) + , radio fieldKey checkedValue Verified (PatchCustomer id fieldKey Verified) + ] + ] + ) + + +radio : String -> Authorized -> Authorized -> msg -> Html msg +radio inputName checkedValue value msg = + label + [ style [ ( "padding", "5px" ) ] ] + [ input [ checked (checkedValue == value), type_ "radio", name inputName, onClick msg ] [] + , text (authorizedToString value) + ] + + +verifyStatus : Maybe a -> Authorized -> Html Msg +verifyStatus complianceType fieldOverride = + if fieldOverride == Verified || (complianceType /= Nothing && fieldOverride == Automatic) then + text "Verified" + else + text "Unverified" + + +customerView : Customer -> Html Msg +customerView customer = + div [] + [ h1 [] [ text "Customer Details" ] + , table [ CSSAdmin.class [ C.TxTable ] ] + [ tbody [] + [ tr [] + [ td [] [ text "Customer ID" ] + , td [] [ text customer.id ] + ] + , tr [] + [ td [] [ text "Name" ] + , td [] [ maybeText customer.name ] + ] + , tr [] + [ td [] [ text "Phone" ] + , td [] [ maybeText customer.phone ] + ] + , tr [] + [ td [] [ text "Completed phone at" ] + , td [] [ text (formatDate customer.phoneAt) ] + ] + , tr [] + [ td [] [ text "Created" ] + , td [] [ text (toFormattedString "yyyy-MM-dd HH:mm" customer.created) ] + ] + , tr [] + [ td [] [ text "Block Customer" ] + , td [] + [ customerActions customer.id customer.authorizedOverride ] + ] + , tr [] + [ td [] [ text "Authorized at " ] + , td [] [ text (formatDate customer.authorizedAt) ] + ] + , tr [] + [ td [] [ text "Daily Volume " ] + , td [] [ maybeText customer.dailyVolume ] + ] + ] + ] + , h2 [] [ text "Compliance types" ] + , table [ CSSAdmin.class [ C.TxTable ] ] + [ thead [] + [ tr [] + [ td [] [ text "Name" ] + , td [] [ text "Date" ] + , td [] [ text "Verify Status" ] + , td [] [ text "Override Status" ] + , td [] [ text "User who overrode" ] + , td [] [ text "Actions" ] + ] + ] + , tbody [] + [ tr [] + [ td [] [ text "SMS" ] + , td [] [ text (formatDate customer.phoneAt) ] + , td [] [ verifyStatus customer.phone customer.smsOverride ] + , td [] [ text (authorizedToString customer.smsOverride) ] + , td [] [ maybeText customer.smsOverrideByName ] + , td [] [ actions customer.id "smsOverride" customer.smsOverride ] + ] + , tr [] + [ td [] [ text "ID Card Data" ] + , td [] [ text (formatDate customer.idCardDataAt) ] + , td [] [ verifyStatus customer.idCardData customer.idCardDataOverride ] + , td [] [ text (authorizedToString customer.idCardDataOverride) ] + , td [] [ maybeText customer.idCardDataOverrideByName ] + , td [] [ actions customer.id "idCardDataOverride" customer.idCardDataOverride ] + ] + , tr [] + [ td [] [ text "ID Card Photo" ] + , td [] [ text (formatDate customer.idCardPhotoAt) ] + , td [] [ verifyStatus customer.idCardPhotoPath customer.idCardPhotoOverride ] + , td [] [ text (authorizedToString customer.idCardPhotoOverride) ] + , td [] [ maybeText customer.idCardPhotoOverrideByName ] + , td [] [ actions customer.id "idCardPhotoOverride" customer.idCardPhotoOverride ] + ] + , tr [] + [ td [] [ text "Front Facing Camera" ] + , td [] [ text (formatDate customer.frontCameraAt) ] + , td [] [ verifyStatus customer.frontCameraPath customer.frontCameraOverride ] + , td [] [ text (authorizedToString customer.frontCameraOverride) ] + , td [] [ maybeText customer.frontCameraOverrideByName ] + , td [] [ actions customer.id "frontCameraOverride" customer.frontCameraOverride ] + ] + , tr [] + [ td [] [ text "Sanctions Check" ] + , td [] [ text (formatDate customer.sanctionsAt) ] + , td [] [ verifyStatus customer.sanctions customer.sanctionsOverride ] + , td [] [ text (authorizedToString customer.sanctionsOverride) ] + , td [] [ maybeText customer.sanctionsOverrideByName ] + , td [] [ actions customer.id "sanctionsOverride" customer.sanctionsOverride ] + ] + ] + ] + , h2 [] [ text "ID Card Photo" ] + , case customer.idCardPhotoPath of + Nothing -> + text "N/A" + + Just idCardPhotoPath -> + div [] + [ img + [ src ("/id-card-photo/" ++ idCardPhotoPath) + , height 200 + , alt "N/A" + ] [] + ] + ] + + +view : Model -> Html Msg +view model = + case model of + NotAsked -> + div [] [] + + Loading -> + div [] [ text "Loading..." ] + + Failure err -> + div [] [ text (toString err) ] + + Success customer -> + div [] [ customerView customer ] diff --git a/lamassu-admin-elm/src/Customers/Rest.elm b/lamassu-admin-elm/src/Customers/Rest.elm new file mode 100644 index 00000000..511eacf5 --- /dev/null +++ b/lamassu-admin-elm/src/Customers/Rest.elm @@ -0,0 +1,15 @@ +module Customers.Rest exposing (..) + +import RemoteData exposing (..) +import Http +import HttpBuilder exposing (..) +import Common.Customer.Decoder exposing (customersDecoder) +import Customers.Types exposing (..) + + +getCustomers : Cmd Msg +getCustomers = + get ("/api/customers") + |> withExpect (Http.expectJson customersDecoder) + |> send RemoteData.fromResult + |> Cmd.map Load diff --git a/lamassu-admin-elm/src/Customers/State.elm b/lamassu-admin-elm/src/Customers/State.elm new file mode 100644 index 00000000..f8980842 --- /dev/null +++ b/lamassu-admin-elm/src/Customers/State.elm @@ -0,0 +1,27 @@ +module Customers.State exposing (..) + +import RemoteData exposing (..) +import Customers.Rest exposing (..) +import Customers.Types exposing (..) + + +init : Model +init = + NotAsked + + +loadCmd : Cmd Msg +loadCmd = + getCustomers + + +load : ( Model, Cmd Msg ) +load = + ( Loading, loadCmd ) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + Load loadedModel -> + loadedModel ! [] diff --git a/lamassu-admin-elm/src/Customers/Types.elm b/lamassu-admin-elm/src/Customers/Types.elm new file mode 100644 index 00000000..e73afc41 --- /dev/null +++ b/lamassu-admin-elm/src/Customers/Types.elm @@ -0,0 +1,12 @@ +module Customers.Types exposing (..) + +import RemoteData exposing (..) +import Common.Customer.Types exposing (..) + + +type alias Model = + RemoteData.WebData Customers + + +type Msg + = Load Model diff --git a/lamassu-admin-elm/src/Customers/View.elm b/lamassu-admin-elm/src/Customers/View.elm new file mode 100644 index 00000000..4b0d4a18 --- /dev/null +++ b/lamassu-admin-elm/src/Customers/View.elm @@ -0,0 +1,70 @@ +module Customers.View exposing (..) + +import Html exposing (..) +import Html.Attributes exposing (colspan, href) +import Css.Admin exposing (..) +import Css.Classes as C +import RemoteData exposing (..) +import List +import Common.Customer.Types exposing (..) +import Customers.Types exposing (..) +import Date.Extra exposing (toFormattedString) + + +customerLink : String -> Html Msg +customerLink id = + a [ href ("/#customer/" ++ id) ] [ text (String.left 8 id) ] + + +maybeText : Maybe String -> Html Msg +maybeText maybeString = + text (Maybe.withDefault "" maybeString) + + +rowView : Customer -> Html Msg +rowView customer = + tr [ class [] ] + [ td [] [ customerLink customer.id ] + , td [] [ text (toFormattedString "yyyy-MM-dd HH:mm" customer.created) ] + , td [] [ maybeText customer.phone ] + , td [] [ maybeText customer.name ] + , td [] [ maybeText customer.status ] + ] + + +tableView : Customers -> Html Msg +tableView customers = + if List.isEmpty customers then + div [] [ text "No customers yet." ] + else + div [] + [ h1 [] [ text "Customers" ] + , table [ class [ C.TxTable ] ] + [ thead [] + [ tr [] + [ td [] [ text "Id" ] + , td [] [ text "Created" ] + , td [] [ text "Phone" ] + , td [] [ text "Name" ] + , td [] [ text "Status" ] + ] + ] + , tbody [] (List.map rowView customers) + ] + ] + + +view : Model -> Html Msg +view model = + case model of + NotAsked -> + div [] [] + + Loading -> + div [] [ text "Loading..." ] + + Failure err -> + div [] [ text (toString err) ] + + Success customers -> + div [] [ tableView customers ] diff --git a/lamassu-admin-elm/src/FieldSet/Rest.elm b/lamassu-admin-elm/src/FieldSet/Rest.elm new file mode 100644 index 00000000..f00bd2d2 --- /dev/null +++ b/lamassu-admin-elm/src/FieldSet/Rest.elm @@ -0,0 +1,95 @@ +module FieldSet.Rest exposing (..) + +import Json.Decode as D +import Json.Encode as E +import FieldSet.Types exposing (..) + + +fieldPasswordDecoder : Bool -> FieldValue +fieldPasswordDecoder present = + if present then + FieldPassword PasswordHidden + else + FieldPassword PasswordEmpty + + +badInt : D.Decoder Int +badInt = + D.oneOf [ D.int ] + + +fieldValueDecoder : String -> D.Decoder FieldValue +fieldValueDecoder fieldType = + case fieldType of + "string" -> + D.map FieldString D.string + + "password" -> + D.map fieldPasswordDecoder D.bool + + "integer" -> + D.map FieldInteger badInt + + _ -> + D.fail ("Unsupported field type: " ++ fieldType) + + +fieldDecoder : D.Decoder Field +fieldDecoder = + (D.field "fieldType" D.string) + |> D.andThen + (\fieldType -> + D.map6 Field + (D.field "code" D.string) + (D.field "display" D.string) + (D.oneOf [ D.field "placeholder" D.string, D.succeed "" ]) + (D.field "required" D.bool) + (D.field "value" (fieldValueDecoder fieldType)) + (D.field "value" (fieldValueDecoder fieldType)) + ) + + +encodeFieldValue : FieldValue -> E.Value +encodeFieldValue fieldValue = + case fieldValue of + FieldString value -> + E.string value + + FieldPassword value -> + case value of + Password s -> + E.string s + + _ -> + E.null + + FieldInteger value -> + E.int value + + +maybeString : Maybe String -> E.Value +maybeString maybeString = + case maybeString of + Nothing -> + E.null + + Just s -> + E.string s + + +encodeField : Field -> Maybe E.Value +encodeField field = + if isDirty field then + Just + (E.object + [ ( "code", E.string field.code ) + , ( "value", encodeFieldValue field.value ) + ] + ) + else + Nothing + + +isDirty : Field -> Bool +isDirty field = + field.value /= field.loadedValue diff --git a/lamassu-admin-elm/src/FieldSet/State.elm b/lamassu-admin-elm/src/FieldSet/State.elm new file mode 100644 index 00000000..98fcc671 --- /dev/null +++ b/lamassu-admin-elm/src/FieldSet/State.elm @@ -0,0 +1,23 @@ +module FieldSet.State exposing (update) + +import FieldSet.Types exposing (..) + + +updateField : String -> String -> Field -> Field +updateField fieldCode fieldValueString field = + if .code field == fieldCode then + { field | value = updateFieldValue fieldValueString field.value } + else + field + + +updateFieldSet : String -> String -> List Field -> List Field +updateFieldSet fieldCode fieldValueString fields = + List.map (updateField fieldCode fieldValueString) fields + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + Input fieldCode valueString -> + updateFieldSet fieldCode valueString model ! [] diff --git a/lamassu-admin-elm/src/FieldSet/Types.elm b/lamassu-admin-elm/src/FieldSet/Types.elm new file mode 100644 index 00000000..2d36f4c4 --- /dev/null +++ b/lamassu-admin-elm/src/FieldSet/Types.elm @@ -0,0 +1,44 @@ +module FieldSet.Types exposing (..) + + +type alias Model = + List Field + + +type Msg + = Input String String + + +type alias Field = + { code : String + , display : String + , placeholder : String + , required : Bool + , value : FieldValue + , loadedValue : FieldValue + } + + +type FieldPasswordType + = Password String + | PasswordEmpty + | PasswordHidden + + +type FieldValue + = FieldString String + | FieldPassword FieldPasswordType + | FieldInteger Int + + +updateFieldValue : String -> FieldValue -> FieldValue +updateFieldValue stringValue oldFieldValue = + case oldFieldValue of + FieldString _ -> + FieldString stringValue + + FieldPassword _ -> + FieldPassword (Password stringValue) + + FieldInteger oldValue -> + FieldInteger <| Result.withDefault oldValue <| String.toInt stringValue diff --git a/lamassu-admin-elm/src/FieldSet/View.elm b/lamassu-admin-elm/src/FieldSet/View.elm new file mode 100644 index 00000000..0991fabd --- /dev/null +++ b/lamassu-admin-elm/src/FieldSet/View.elm @@ -0,0 +1,52 @@ +module FieldSet.View exposing (view) + +import Html exposing (..) +import Html.Attributes as HA exposing (defaultValue, name, type_, placeholder) +import Html.Events exposing (..) +import FieldSet.Types exposing (..) +import List +import Css.Admin exposing (..) +import Css.Classes as C + + +fieldComponent : Field -> Html Msg +fieldComponent field = + let + inputEl = + case field.value of + FieldString string -> + input + [ onInput (Input field.code), placeholder field.placeholder, defaultValue string ] + [] + + FieldPassword pass -> + case pass of + PasswordEmpty -> + input + [ onInput (Input field.code), name field.code, type_ "password" ] + [] + + _ -> + input + [ onInput (Input field.code), name field.code, type_ "password", placeholder "••• Field is set •••" ] + [] + + FieldInteger int -> + input + [ onInput (Input field.code), type_ "number", defaultValue (toString int) ] + [] + in + label [] + [ div [] [ text field.display ] + , inputEl + ] + + +fieldView : Field -> Html Msg +fieldView field = + div [ class [ C.FormRow ] ] [ fieldComponent field ] + + +view : Model -> Html Msg +view model = + div [ class [ C.ConfigContainer ] ] (List.map fieldView model) diff --git a/lamassu-admin-elm/src/FuzzyMatch.elm b/lamassu-admin-elm/src/FuzzyMatch.elm new file mode 100644 index 00000000..39aac394 --- /dev/null +++ b/lamassu-admin-elm/src/FuzzyMatch.elm @@ -0,0 +1,48 @@ +module FuzzyMatch exposing (match) + +import String +import Fuzzy +import Tuple + + +clean : String -> String +clean s = + String.trim s + |> String.toLower + + +type alias DisplayRec = + { code : String + , display : String + } + + +score : String -> Int -> DisplayRec -> ( ( Int, Int ), DisplayRec ) +score needle index hay = + let + match keyword = + Fuzzy.match [] [] needle keyword + |> .score + + score = + List.map match ((String.split " " (clean hay.display)) ++ [ clean hay.code, clean hay.display ]) + |> List.minimum + |> Maybe.withDefault + 10000 + in + ( ( score, index ), hay ) + + +match : String -> List DisplayRec -> List DisplayRec +match rawString list = + let + s = + clean rawString + in + if String.isEmpty s then + list + else + List.indexedMap (score s) list + |> List.sortBy Tuple.first + |> List.filter (((>) 1100) << Tuple.first << Tuple.first) + |> List.map Tuple.second diff --git a/lamassu-admin-elm/src/Lamassu.elm b/lamassu-admin-elm/src/Lamassu.elm new file mode 100644 index 00000000..24514a82 --- /dev/null +++ b/lamassu-admin-elm/src/Lamassu.elm @@ -0,0 +1,153 @@ +module Main exposing (..) + +import Html exposing (Html, Attribute, a, div, hr, input, span, text, map) +import Html.Attributes exposing (class) +import Navigation +import SupportLogs.Types +import SupportLogs.State +import SupportLogs.View +import UrlParser exposing ((), s, string, top, parseHash) +import Navigation exposing (newUrl, Location) +import StatusTypes exposing (..) + + +type Category + = AccountCat + | MachineSettingsCat + | GlobalSettingsCat + | MaintenanceCat + + +type Route + = SupportLogsRoute (Maybe String) + | NotFoundRoute + + +type Msg + = SupportLogsMsg SupportLogs.Types.Msg + | UrlChange Navigation.Location + + +main : Program Never Model Msg +main = + Navigation.program UrlChange + { init = init + , update = update + , view = view + , subscriptions = subscriptions + } + + + +-- URL PARSERS + + +parseRoute : UrlParser.Parser (Route -> a) a +parseRoute = + UrlParser.oneOf + [ UrlParser.map (\id -> SupportLogsRoute (Just id)) (s "support_logs" string) + , UrlParser.map (SupportLogsRoute Nothing) (s "support_logs") + ] + + + +-- MODEL + + +type alias Model = + { location : Location + , supportLogs : SupportLogs.Types.Model + , status : Maybe StatusRec + , err : Maybe String + } + + +init : Location -> ( Model, Cmd Msg ) +init location = + let + model = + { location = location + , supportLogs = SupportLogs.State.init + , status = Nothing + , err = Nothing + } + + ( newModel, newCmd ) = + urlUpdate location model + in + newModel ! [ newCmd ] + + + +-- UPDATE + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + SupportLogsMsg supportLogsMsg -> + let + ( supportLogsModel, cmd ) = + SupportLogs.State.update supportLogsMsg model.supportLogs + in + { model | supportLogs = supportLogsModel } ! [ Cmd.map SupportLogsMsg cmd ] + + UrlChange location -> + urlUpdate location model + + +content : Model -> Route -> Html Msg +content model route = + case route of + SupportLogsRoute _ -> + map SupportLogsMsg (SupportLogs.View.view model.supportLogs) + + NotFoundRoute -> + div [] [ text ("No such route") ] + + +view : Model -> Html Msg +view model = + let + route = + Maybe.withDefault NotFoundRoute (parseHash parseRoute model.location) + + invalidConfigGroups = + Maybe.map .invalidConfigGroups model.status + |> Maybe.withDefault [] + in + div [ class "lamassuAdminLayout" ] + [ div + [ class "lamassuAdminMain" ] + [ div [ class "lamassuAdminContent" ] + [ content model route ] + ] + ] + + +urlUpdate : Location -> Model -> ( Model, Cmd Msg ) +urlUpdate location model = + let + route = + Maybe.withDefault NotFoundRoute (parseHash parseRoute location) + in + case route of + SupportLogsRoute maybeId -> + let + ( supportLogsModel, cmd ) = + SupportLogs.State.load maybeId + in + { model | location = location, supportLogs = supportLogsModel } ! [ Cmd.map SupportLogsMsg cmd ] + + NotFoundRoute -> + { model | location = location } ! [] + + + +-- SUBSCRIPTIONS + + +subscriptions : Model -> Sub Msg +subscriptions model = + Sub.batch + [] diff --git a/lamassu-admin-elm/src/LamassuAdminServerClient.elm b/lamassu-admin-elm/src/LamassuAdminServerClient.elm new file mode 100644 index 00000000..2062bfd8 --- /dev/null +++ b/lamassu-admin-elm/src/LamassuAdminServerClient.elm @@ -0,0 +1,22 @@ +module ClientServerWebsocket exposing (..) + +import RemoteData exposing (..) +import HttpBuilder exposing (..) + + +-- Fetch stuff: different configurations for starters + + +type alias NewsResponse = + () + + +type alias Msg = + NewsResponse (WebData News) + + +getNews : Cmd Msg +getNews = + Http.get decodeNews "/news" + |> RemoteData.asCmd + |> Cmd.map NewsResponse diff --git a/lamassu-admin-elm/src/Logs/Rest.elm b/lamassu-admin-elm/src/Logs/Rest.elm new file mode 100644 index 00000000..9443a25f --- /dev/null +++ b/lamassu-admin-elm/src/Logs/Rest.elm @@ -0,0 +1,29 @@ +module Logs.Rest exposing (..) + +import RemoteData exposing (..) +import Http +import HttpBuilder exposing (..) +import Common.Logs.Decoder exposing (logsDecoder, machinesDecoder, latestLogSnapshotDecoder) +import Logs.Types exposing (..) + + +getLogs : Maybe String -> Cmd Msg +getLogs maybeId = + Http.get ("/api/logs/" ++ (Maybe.withDefault "" maybeId)) logsDecoder + |> RemoteData.sendRequest + |> Cmd.map LoadLogs + + +getMachines : Cmd Msg +getMachines = + Http.get "/api/machines/" machinesDecoder + |> RemoteData.sendRequest + |> Cmd.map LoadMachines + + +shareLogs : String -> Cmd Msg +shareLogs id = + post ("/api/support_logs?deviceId=" ++ id) + |> withExpect (Http.expectJson latestLogSnapshotDecoder) + |> send RemoteData.fromResult + |> Cmd.map LoadSupportLog diff --git a/lamassu-admin-elm/src/Logs/State.elm b/lamassu-admin-elm/src/Logs/State.elm new file mode 100644 index 00000000..f565bb2f --- /dev/null +++ b/lamassu-admin-elm/src/Logs/State.elm @@ -0,0 +1,42 @@ +module Logs.State exposing (..) + +import RemoteData exposing (..) +import Logs.Rest exposing (..) +import Logs.Types exposing (..) + + +init : Model +init = + { logs = NotAsked, machines = NotAsked, latestLogSnapshot = NotAsked } + + +load : Maybe String -> ( Model, Cmd Msg ) +load maybeId = + ( { logs = Loading, machines = Loading, latestLogSnapshot = NotAsked }, getData maybeId ) + + +getData : Maybe String -> Cmd Msg +getData maybeId = + Cmd.batch [ getLogs maybeId, getMachines ] + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + LoadLogs response -> + ( { model | logs = response } + , Cmd.none + ) + + LoadMachines response -> + ( { model | machines = response } + , Cmd.none + ) + + ShareLogs machine -> + model ! [ shareLogs machine.deviceId ] + + LoadSupportLog supportLog -> + ( { model | latestLogSnapshot = supportLog } + , Cmd.none + ) diff --git a/lamassu-admin-elm/src/Logs/Types.elm b/lamassu-admin-elm/src/Logs/Types.elm new file mode 100644 index 00000000..4e7ac24a --- /dev/null +++ b/lamassu-admin-elm/src/Logs/Types.elm @@ -0,0 +1,18 @@ +module Logs.Types exposing (..) + +import RemoteData exposing (..) +import Common.Logs.Types exposing (..) + + +type alias Model = + { logs : WebData Logs + , machines : WebData Machines + , latestLogSnapshot : WebData SupportLogSnapshot + } + + +type Msg + = LoadLogs (WebData Logs) + | LoadMachines (WebData Machines) + | ShareLogs Machine + | LoadSupportLog (WebData SupportLogSnapshot) diff --git a/lamassu-admin-elm/src/Logs/View.elm b/lamassu-admin-elm/src/Logs/View.elm new file mode 100644 index 00000000..8d6934a6 --- /dev/null +++ b/lamassu-admin-elm/src/Logs/View.elm @@ -0,0 +1,149 @@ +module Logs.View exposing (..) + +import Html exposing (..) +import Html.Attributes exposing (href) +import Html.Events exposing (onClick) +import Css.Admin exposing (..) +import Css.Classes as C +import RemoteData exposing (..) +import List +import Common.Logs.Types exposing (..) +import Logs.Types exposing (..) +import Date exposing (..) +import Date.Extra exposing (toFormattedString) + + +machineLink : Machine -> Html Msg +machineLink machine = + a [ href ("/#logs/" ++ machine.deviceId) ] [ text machine.name ] + + +logsActions : Logs -> Html Msg +logsActions logs = + button [ onClick (ShareLogs logs.currentMachine) ] [ text "Share log snapshot" ] + + +formatDate : Date -> String +formatDate date = + toFormattedString "yyyy-MM-dd HH:mm" date + + +rowView : Log -> Html Msg +rowView log = + tr [ class [] ] + [ td [] [ text (formatDate log.timestamp) ] + , td [] [ text log.logLevel ] + , td [] [ text log.message ] + ] + + +machineRowView : Machine -> Html Msg +machineRowView machine = + tr [ class [] ] + [ td [] [ machineLink machine ] + ] + + +machineItemView : Machine -> Html Msg +machineItemView machine = + li [] [ machineLink machine ] + + +machinesView : Machines -> Html Msg +machinesView machines = + if List.isEmpty machines then + div [ class [ C.EmptyTable ] ] [ text "No paired machines." ] + else + div [] + [ div [ class [ C.TxTable ] ] + [ ul [] (List.map machineItemView machines) + ] + ] + + +machines : Model -> Html Msg +machines model = + case model.machines of + NotAsked -> + div [] [] + + Loading -> + div [] [ text "Loading machines ..." ] + + Failure err -> + div [] [ text (toString err) ] + + Success machines -> + div [] [ machinesView machines ] + + +latestLogSnapshot : Model -> Html Msg +latestLogSnapshot model = + case model.latestLogSnapshot of + NotAsked -> + div [] [] + + Loading -> + div [] [] + + Failure err -> + div [] [ text (toString err) ] + + Success latestLogSnapshot -> + h4 [] [ text "✓ Saved latest snapshot" ] + + +logsView : Logs -> Html Msg +logsView logs = + if List.isEmpty logs.logs then + div [] [ text "No logs yet." ] + else + div [] + [ logsActions logs + , table [ class [ C.TxTable ] ] + [ thead [] + [ tr [] + [ td [] [ text "Date" ] + , td [] [ text "Level" ] + , td [] [ text "Message" ] + ] + ] + , tbody [] (List.map rowView logs.logs) + ] + ] + + +logs : Model -> Html Msg +logs model = + case model.logs of + NotAsked -> + div [] [] + + Loading -> + div [] [ text "Loading logs..." ] + + Failure err -> + div [] [ text (toString err) ] + + Success logs -> + div [] + [ logsView logs + ] + + +view : Model -> Html Msg +view model = + div [] + [ h1 [] [ text "Latest Logs" ] + , div [ class [ C.PaneWrapper ] ] + [ div [ class [ C.LeftPane ] ] + [ h2 [] [ text "Machines" ] + , machines model + ] + , div [ class [ C.ContentPane ] ] + [ h2 [] [ text "Logs" ] + , latestLogSnapshot model + , logs model + ] + ] + ] diff --git a/lamassu-admin-elm/src/Main.elm b/lamassu-admin-elm/src/Main.elm new file mode 100644 index 00000000..76b1f83e --- /dev/null +++ b/lamassu-admin-elm/src/Main.elm @@ -0,0 +1,479 @@ +module Main exposing (..) + +import Html exposing (Html, Attribute, a, div, hr, input, span, text, map) +import Html.Attributes exposing (class) +import Navigation +import Pair +import Account +import Config +import Transactions +import Customers.Types +import Customers.State +import Customers.View +import Logs.Types +import Logs.State +import Logs.View +import SupportLogs.Types +import SupportLogs.State +import SupportLogs.View +import Customer.Types +import Customer.State +import Customer.View +import MaintenanceFunding.Types +import NavBar exposing (..) +import UrlParser exposing ((), s, string, top, parseHash) +import Http +import HttpBuilder exposing (..) +import RemoteData +import Navigation exposing (newUrl, Location) +import CoreTypes exposing (Msg(..), Route(..), Category(..)) +import AccountsDecoder exposing (accountsDecoder) +import StatusTypes exposing (..) +import StatusDecoder exposing (..) +import Time exposing (..) +import Css.Admin +import Css.Classes as C +import Markdown +import MaintenanceMachines.Types +import MaintenanceMachines.State +import MaintenanceMachines.View +import MaintenanceFunding.Types +import MaintenanceFunding.State +import MaintenanceFunding.View +import Transaction.Types +import Transaction.State +import Transaction.View + + +main : Program Never Model Msg +main = + Navigation.program UrlChange + { init = init + , update = update + , view = view + , subscriptions = subscriptions + } + + + +-- URL PARSERS + + +parseRoute : UrlParser.Parser (Route -> a) a +parseRoute = + UrlParser.oneOf + [ UrlParser.map AccountRoute (s "account" string) + , UrlParser.map PairRoute (s "pair") + , UrlParser.map (\config crypto -> ConfigRoute config (Just crypto)) (s "config" string string) + , UrlParser.map (\config -> ConfigRoute config Nothing) (s "config" string) + , UrlParser.map MaintenanceMachinesRoute (s "machines") + , UrlParser.map (\crypto -> MaintenanceFundingRoute (Just crypto)) (s "funding" string) + , UrlParser.map (MaintenanceFundingRoute Nothing) (s "funding") + , UrlParser.map TransactionsRoute (s "transactions") + , UrlParser.map TransactionRoute (s "transaction" string) + , UrlParser.map CustomersRoute (s "customers") + , UrlParser.map CustomerRoute (s "customer" string) + , UrlParser.map (\id -> LogsRoute (Just id)) (s "logs" string) + , UrlParser.map (LogsRoute Nothing) (s "logs") + , UrlParser.map (\id -> SupportLogsRoute (Just id)) (s "support_logs" string) + , UrlParser.map (SupportLogsRoute Nothing) (s "support_logs") + , UrlParser.map (ConfigRoute "setup" Nothing) top + ] + + +getAccounts : Cmd Msg +getAccounts = + get ("/api/accounts") + |> withExpect (Http.expectJson accountsDecoder) + |> send RemoteData.fromResult + |> Cmd.map (RemoteData.withDefault []) + |> Cmd.map LoadAccounts + + +getStatus : Cmd Msg +getStatus = + get ("/api/status/") + |> withExpect (Http.expectJson statusDecoder) + |> send RemoteData.fromResult + |> Cmd.map LoadStatus + + + +-- MODEL + + +type alias Model = + { location : Location + , pair : Pair.Model + , account : Account.Model + , config : Config.Model + , maintenanceMachines : MaintenanceMachines.Types.Model + , maintenanceFunding : MaintenanceFunding.Types.Model + , transactions : Transactions.Model + , transaction : Transaction.Types.Model + , customers : Customers.Types.Model + , customer : Customer.Types.Model + , logs : Logs.Types.Model + , supportLogs : SupportLogs.Types.Model + , accounts : List ( String, String ) + , status : Maybe StatusRec + , err : Maybe String + } + + +init : Location -> ( Model, Cmd Msg ) +init location = + let + model = + { location = location + , account = Account.init + , pair = Pair.init False + , config = Config.init + , maintenanceMachines = MaintenanceMachines.State.init + , maintenanceFunding = MaintenanceFunding.State.init + , transactions = Transactions.init + , transaction = Transaction.State.init + , customers = Customers.State.init + , customer = Customer.State.init + , logs = Logs.State.init + , supportLogs = SupportLogs.State.init + , accounts = [] + , status = Nothing + , err = Nothing + } + + ( newModel, newCmd ) = + urlUpdate location model + in + newModel ! [ newCmd, getAccounts, getStatus ] + + + +-- UPDATE + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + PairMsg pairMsg -> + let + ( pairModel, cmd ) = + Pair.update pairMsg model.pair + in + { model | pair = pairModel } ! [ Cmd.map PairMsg cmd ] + + AccountMsg accountMsg -> + let + ( accountModel, cmd ) = + Account.update accountMsg model.account + in + { model | account = accountModel } ! [ Cmd.map AccountMsg cmd ] + + ConfigMsg configMsg -> + let + ( configModel, cmd ) = + Config.update configMsg model.config + + loaded = + Config.loaded configMsg + + extraCmds = + if loaded then + [ getAccounts, getStatus ] + else + [] + in + { model | config = configModel } ! ([ Cmd.map ConfigMsg cmd ] ++ extraCmds) + + MaintenanceMachinesMsg maintenanceMachinesMsg -> + let + ( maintenanceMachines, cmd ) = + MaintenanceMachines.State.update maintenanceMachinesMsg model.maintenanceMachines + in + { model | maintenanceMachines = maintenanceMachines } ! [ Cmd.map MaintenanceMachinesMsg cmd ] + + MaintenanceFundingMsg maintenanceFundingMsg -> + let + ( maintenanceFunding, cmd ) = + MaintenanceFunding.State.update maintenanceFundingMsg model.maintenanceFunding + in + { model | maintenanceFunding = maintenanceFunding } ! [ Cmd.map MaintenanceFundingMsg cmd ] + + TransactionsMsg transactionsMsg -> + let + ( transactionsModel, cmd ) = + Transactions.update transactionsMsg model.transactions + in + { model | transactions = transactionsModel } ! [ Cmd.map TransactionsMsg cmd ] + + TransactionMsg transactionMsg -> + let + ( transaction, cmd ) = + Transaction.State.update transactionMsg model.transaction + in + { model | transaction = transaction } ! [ Cmd.map TransactionMsg cmd ] + + CustomersMsg customersMsg -> + let + ( customersModel, cmd ) = + Customers.State.update customersMsg model.customers + in + { model | customers = customersModel } ! [ Cmd.map CustomersMsg cmd ] + + CustomerMsg customerMsg -> + let + ( customerModel, cmd ) = + Customer.State.update customerMsg model.customer + in + { model | customer = customerModel } ! [ Cmd.map CustomerMsg cmd ] + + LogsMsg logsMsg -> + let + ( logsModel, cmd ) = + Logs.State.update logsMsg model.logs + in + { model | logs = logsModel } ! [ Cmd.map LogsMsg cmd ] + + SupportLogsMsg supportLogsMsg -> + let + ( supportLogsModel, cmd ) = + SupportLogs.State.update supportLogsMsg model.supportLogs + in + { model | supportLogs = supportLogsModel } ! [ Cmd.map SupportLogsMsg cmd ] + + LoadAccounts accounts -> + { model | accounts = accounts } ! [] + + LoadStatus webStatus -> + let + newStatus = + List.filterMap identity [ RemoteData.toMaybe webStatus, model.status ] + |> List.head + + serverStatus = + Maybe.withDefault False <| Maybe.map (\status -> status.server.up) newStatus + + newPair = + Pair.updateStatus serverStatus model.pair + + rates = + Maybe.withDefault [] <| Maybe.map (\status -> status.server.rates) newStatus + + newConfig = + Config.updateRates rates model.config + in + { model | status = newStatus, pair = newPair, config = newConfig } ! [] + + NewUrl url -> + let + ( configModel, configCmd ) = + Config.submitNoLoad model.config + in + { model | config = configModel } ! [ Navigation.newUrl url, Cmd.map ConfigMsg configCmd ] + + UrlChange location -> + urlUpdate location model + + Interval -> + let + route = + Maybe.withDefault NotFoundRoute (parseHash parseRoute model.location) + + extraCmds = + if route == TransactionsRoute then + [ Cmd.map TransactionsMsg Transactions.loadCmd ] + else + [] + in + model ! ([ getStatus ] ++ extraCmds) + + WebSocketMsg msg -> + model ! [] + + +content : Model -> Route -> Html Msg +content model route = + case route of + PairRoute -> + map PairMsg (Pair.view model.pair) + + AccountRoute _ -> + map AccountMsg (Account.view model.account) + + ConfigRoute _ _ -> + map ConfigMsg (Config.view model.config) + + MaintenanceMachinesRoute -> + map MaintenanceMachinesMsg (MaintenanceMachines.View.view model.maintenanceMachines) + + MaintenanceFundingRoute _ -> + map MaintenanceFundingMsg (MaintenanceFunding.View.view model.maintenanceFunding) + + TransactionsRoute -> + map TransactionsMsg (Transactions.view model.transactions) + + TransactionRoute _ -> + map TransactionMsg (Transaction.View.view model.transaction) + + CustomersRoute -> + map CustomersMsg (Customers.View.view model.customers) + + CustomerRoute _ -> + map CustomerMsg (Customer.View.view model.customer) + + LogsRoute _ -> + map LogsMsg (Logs.View.view model.logs) + + SupportLogsRoute _ -> + map SupportLogsMsg (SupportLogs.View.view model.supportLogs) + + NotFoundRoute -> + div [] [ text ("No such route") ] + + +statusBar : Maybe StatusRec -> Html Msg +statusBar maybeStatus = + case maybeStatus of + Nothing -> + div [ Css.Admin.class [ C.StatusBar ] ] [ text "Loading ..." ] + + Just status -> + let + serverStatus = + if not status.server.wasConfigured then + [ Markdown.toHtml [] "**lamassu-server** not configured yet" ] + else if status.server.up then + [ Markdown.toHtml [] ("**lamassu-server** is up **/** " ++ status.server.machineStatus) ] + else + case status.server.lastPing of + Nothing -> + [ Markdown.toHtml [] "**lamassu-server** not up yet" ] + + Just lastPing -> + [ Markdown.toHtml [] ("**lamassu-server** has been down for " ++ lastPing) ] + in + div [ Css.Admin.class [ C.StatusBar ] ] serverStatus + + +view : Model -> Html Msg +view model = + let + route = + Maybe.withDefault NotFoundRoute (parseHash parseRoute model.location) + + invalidConfigGroups = + Maybe.map .invalidConfigGroups model.status + |> Maybe.withDefault [] + in + div [ class "lamassuAdminLayout" ] + [ div + [ class "lamassuAdminMain" ] + [ NavBar.view route invalidConfigGroups + , div [ class "lamassuAdminContent" ] + [ content model route ] + ] + , statusBar model.status + ] + + +urlUpdate : Location -> Model -> ( Model, Cmd Msg ) +urlUpdate location model = + let + route = + Maybe.withDefault NotFoundRoute (parseHash parseRoute location) + in + case route of + PairRoute -> + case model.status of + Just status -> + { model | location = location, pair = Pair.init status.server.up } ! [] + + Nothing -> + { model | location = location, pair = Pair.init False } ! [] + + AccountRoute account -> + let + ( accountModel, cmd ) = + Account.load account + in + { model | location = location, account = accountModel } ! [ Cmd.map AccountMsg cmd ] + + ConfigRoute config maybeCryptoCodeString -> + let + ( configModel, cmd ) = + Config.load model.config config maybeCryptoCodeString + in + { model | location = location, config = configModel } ! [ Cmd.map ConfigMsg cmd ] + + MaintenanceMachinesRoute -> + let + ( maintenanceMachines, cmd ) = + MaintenanceMachines.State.load + in + { model | location = location, maintenanceMachines = maintenanceMachines } + ! [ Cmd.map MaintenanceMachinesMsg cmd ] + + MaintenanceFundingRoute maybeCrypto -> + let + ( maintenanceFunding, cmd ) = + MaintenanceFunding.State.load maybeCrypto + in + { model | location = location, maintenanceFunding = maintenanceFunding } + ! [ Cmd.map MaintenanceFundingMsg cmd ] + + TransactionsRoute -> + let + ( transactionsModel, cmd ) = + Transactions.load + in + { model | location = location, transactions = transactionsModel } ! [ Cmd.map TransactionsMsg cmd ] + + CustomersRoute -> + let + ( customersModel, cmd ) = + Customers.State.load + in + { model | location = location, customers = customersModel } ! [ Cmd.map CustomersMsg cmd ] + + CustomerRoute id -> + let + ( customerModel, cmd ) = + Customer.State.load id + in + { model | location = location, customer = customerModel } ! [ Cmd.map CustomerMsg cmd ] + + LogsRoute maybeId -> + let + ( logsModel, cmd ) = + Logs.State.load maybeId + in + { model | location = location, logs = logsModel } ! [ Cmd.map LogsMsg cmd ] + + SupportLogsRoute maybeId -> + let + ( supportLogsModel, cmd ) = + SupportLogs.State.load maybeId + in + { model | location = location, supportLogs = supportLogsModel } ! [ Cmd.map SupportLogsMsg cmd ] + + TransactionRoute txId -> + let + ( transaction, cmd ) = + Transaction.State.load txId + in + { model | location = location, transaction = transaction } + ! [ Cmd.map TransactionMsg cmd ] + + NotFoundRoute -> + { model | location = location } ! [] + + + +-- SUBSCRIPTIONS + + +subscriptions : Model -> Sub Msg +subscriptions model = + Sub.batch + [ every (5 * second) (\_ -> Interval) + ] diff --git a/lamassu-admin-elm/src/MaintenanceFunding/Rest.elm b/lamassu-admin-elm/src/MaintenanceFunding/Rest.elm new file mode 100644 index 00000000..c02b8575 --- /dev/null +++ b/lamassu-admin-elm/src/MaintenanceFunding/Rest.elm @@ -0,0 +1,38 @@ +module MaintenanceFunding.Rest exposing (..) + +import RemoteData exposing (..) +import HttpBuilder exposing (..) +import Json.Decode as D +import Json.Decode.Pipeline exposing (decode, required) +import Http +import HttpBuilder exposing (..) +import MaintenanceFunding.Types exposing (..) + + +getForm : Maybe String -> Cmd Msg +getForm maybeCrypto = + get ("/api/funding/" ++ (Maybe.withDefault "" maybeCrypto)) + |> withExpect (Http.expectJson subModelDecoder) + |> send RemoteData.fromResult + |> Cmd.map Load + + +cryptoDisplayDecoder : D.Decoder CryptoDisplay +cryptoDisplayDecoder = + decode CryptoDisplay + |> required "cryptoCode" D.string + |> required "display" D.string + + +subModelDecoder : D.Decoder SubModel +subModelDecoder = + decode SubModel + |> required "cryptoCode" D.string + |> required "cryptoDisplays" (D.list cryptoDisplayDecoder) + |> required "fundingAddress" D.string + |> required "fundingAddressUrl" D.string + |> required "confirmedBalance" D.string + |> required "pending" D.string + |> required "fiatConfirmedBalance" D.string + |> required "fiatPending" D.string + |> required "fiatCode" D.string diff --git a/lamassu-admin-elm/src/MaintenanceFunding/State.elm b/lamassu-admin-elm/src/MaintenanceFunding/State.elm new file mode 100644 index 00000000..e932ec4c --- /dev/null +++ b/lamassu-admin-elm/src/MaintenanceFunding/State.elm @@ -0,0 +1,35 @@ +module MaintenanceFunding.State exposing (..) + +import MaintenanceFunding.Rest exposing (..) +import MaintenanceFunding.Types exposing (..) +import RemoteData exposing (..) + + +init : Model +init = + NotAsked + + +load : Maybe String -> ( Model, Cmd Msg ) +load maybeCrypto = + ( Loading, getForm maybeCrypto ) + + +fundingUpdate : SubModel -> ( SubModel, Cmd Msg ) +fundingUpdate model = + model ! [] + + +switchCrypto : String -> Model -> ( Model, Cmd Msg ) +switchCrypto crypto model = + ( Loading, getForm (Just crypto) ) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + Load newModel -> + RemoteData.update fundingUpdate newModel + + CryptoSwitch crypto -> + switchCrypto crypto model diff --git a/lamassu-admin-elm/src/MaintenanceFunding/Types.elm b/lamassu-admin-elm/src/MaintenanceFunding/Types.elm new file mode 100644 index 00000000..3587d579 --- /dev/null +++ b/lamassu-admin-elm/src/MaintenanceFunding/Types.elm @@ -0,0 +1,31 @@ +module MaintenanceFunding.Types exposing (..) + +import RemoteData exposing (..) + + +type alias CryptoDisplay = + { cryptoCode : String + , display : String + } + + +type alias SubModel = + { cryptoCode : String + , cryptoDisplays : List CryptoDisplay + , fundingAddress : String + , fundingAddressUrl : String + , confirmedBalance : String + , pending : String + , fiatConfirmedBalance : String + , fiatPending : String + , fiatCode : String + } + + +type alias Model = + RemoteData.WebData SubModel + + +type Msg + = Load Model + | CryptoSwitch String diff --git a/lamassu-admin-elm/src/MaintenanceFunding/View.elm b/lamassu-admin-elm/src/MaintenanceFunding/View.elm new file mode 100644 index 00000000..ed0cb654 --- /dev/null +++ b/lamassu-admin-elm/src/MaintenanceFunding/View.elm @@ -0,0 +1,82 @@ +module MaintenanceFunding.View exposing (..) + +import Html exposing (..) +import Html.Attributes exposing (href) +import RemoteData exposing (..) +import MaintenanceFunding.Types exposing (..) +import QRCode +import QRCode.ECLevel as ECLevel +import Css.Admin exposing (..) +import Css.Classes as C + + +qrCode : String -> Html msg +qrCode s = + let + resultQRCode = + QRCode.toSvgWithECLevel s ECLevel.L + in + case resultQRCode of + Result.Ok view -> + view + + Result.Err err -> + Html.text (toString err) + + +fundingView : SubModel -> Html Msg +fundingView subModel = + div [] + [ cryptosView subModel.cryptoDisplays (Just subModel.cryptoCode) + , section [ class [ C.Container ] ] + [ div [] [ text ("Deposit " ++ subModel.cryptoCode ++ " to this address.") ] + , div [] [ qrCode subModel.fundingAddressUrl ] + , div [ class [ C.CryptoAddress ] ] [ text subModel.fundingAddress ] + , section [ class [ C.BalanceSection ] ] + [ h2 [] [ text "Balance" ] + , div [] [ text (subModel.confirmedBalance ++ " " ++ subModel.cryptoCode ++ " (" ++ subModel.pending ++ " pending)") ] + , div [] [ text (subModel.fiatConfirmedBalance ++ " " ++ subModel.fiatCode ++ " (" ++ subModel.fiatPending ++ " pending)") ] + ] + ] + ] + + +cryptoView : Maybe String -> CryptoDisplay -> Html Msg +cryptoView maybeActiveCrypto cryptoDisplay = + let + activeClass = + case maybeActiveCrypto of + Nothing -> + class [] + + Just activeCrypto -> + if (activeCrypto == cryptoDisplay.cryptoCode) then + class [ C.Active ] + else + class [] + + url = + "/#funding/" ++ cryptoDisplay.cryptoCode + in + a [ activeClass, class [ C.CryptoTab ], href url ] [ text cryptoDisplay.display ] + + +cryptosView : List CryptoDisplay -> Maybe String -> Html Msg +cryptosView cryptos activeCrypto = + nav [ class [ C.CryptoTabs ] ] (List.map (cryptoView activeCrypto) cryptos) + + +view : Model -> Html Msg +view model = + case model of + NotAsked -> + div [] [] + + Loading -> + div [] [ text "Loading..." ] + + Failure err -> + div [] [ text (toString err) ] + + Success subModel -> + fundingView subModel diff --git a/lamassu-admin-elm/src/MaintenanceMachines/Rest.elm b/lamassu-admin-elm/src/MaintenanceMachines/Rest.elm new file mode 100644 index 00000000..d009dae2 --- /dev/null +++ b/lamassu-admin-elm/src/MaintenanceMachines/Rest.elm @@ -0,0 +1,73 @@ +module MaintenanceMachines.Rest exposing (..) + +import RemoteData exposing (..) +import HttpBuilder exposing (..) +import Json.Decode as D +import Json.Encode as E +import Http +import HttpBuilder exposing (..) +import MaintenanceMachines.Types exposing (..) +import BasicTypes exposing (..) + + +toModel : SavingStatus -> Machines -> SubModel +toModel status machines = + { status = status, machines = machines } + + +getForm : Cmd Msg +getForm = + get ("/api/machines") + |> withExpect (Http.expectJson machinesDecoder) + |> send (Result.map (toModel NotSaving) >> RemoteData.fromResult) + |> Cmd.map Load + + +postForm : MachineAction -> Cmd Msg +postForm action = + post "/api/machines" + |> withJsonBody (encodeAction action) + |> withExpect (Http.expectJson machinesDecoder) + |> send (Result.map (toModel Saved) >> RemoteData.fromResult) + |> Cmd.map Load + + +machineDecoder : D.Decoder Machine +machineDecoder = + D.map7 Machine + (D.field "deviceId" D.string) + (D.field "name" D.string) + (D.field "cashbox" D.int) + (D.field "cassette1" D.int) + (D.field "cassette2" D.int) + (D.field "paired" D.bool) + (D.field "cashOut" D.bool) + + +machinesDecoder : D.Decoder Machines +machinesDecoder = + D.map identity + (D.field "machines" (D.list machineDecoder)) + + +encodeAction : MachineAction -> E.Value +encodeAction action = + case action of + ResetCashOutBills machine -> + E.object + [ ( "action", E.string "resetCashOutBills" ) + , ( "deviceId", E.string machine.deviceId ) + , ( "cassettes", E.list [ E.int machine.cassette1, E.int machine.cassette2 ] ) + ] + + UnpairMachine machine -> + E.object + [ ( "action", E.string "unpair" ) + , ( "deviceId", E.string machine.deviceId ) + ] + + RebootMachine machine -> + E.object + [ ( "action", E.string "reboot" ) + , ( "deviceId", E.string machine.deviceId ) + ] diff --git a/lamassu-admin-elm/src/MaintenanceMachines/State.elm b/lamassu-admin-elm/src/MaintenanceMachines/State.elm new file mode 100644 index 00000000..5401e251 --- /dev/null +++ b/lamassu-admin-elm/src/MaintenanceMachines/State.elm @@ -0,0 +1,91 @@ +module MaintenanceMachines.State exposing (..) + +import RemoteData exposing (..) +import String +import List +import Process +import Task +import Time exposing (second) +import MaintenanceMachines.Types exposing (..) +import MaintenanceMachines.Rest exposing (..) +import BasicTypes exposing (..) + + +init : Model +init = + NotAsked + + +load : ( Model, Cmd Msg ) +load = + ( Loading, getForm ) + + +updateMachine : Machine -> Machine -> Machine +updateMachine machine oldMachine = + if machine.deviceId == oldMachine.deviceId then + machine + else + oldMachine + + +updateCassette : Machine -> Position -> String -> SubModel -> ( SubModel, Cmd Msg ) +updateCassette machine position str subModel = + let + countResult = + String.toInt str + + updatedMachine = + case countResult of + Ok count -> + case position of + Top -> + { machine | cassette1 = count } + + Bottom -> + { machine | cassette2 = count } + + Err _ -> + machine + + machines = + List.map (updateMachine updatedMachine) subModel.machines + in + { subModel | machines = machines } ! [] + + +updateAction : MachineAction -> SubModel -> ( SubModel, Cmd Msg ) +updateAction action subModel = + subModel ! [ postForm action ] + + +saveUpdate : SubModel -> ( SubModel, Cmd Msg ) +saveUpdate model = + let + cmd = + if (model.status == Saved) then + Process.sleep (2 * second) + |> Task.perform (\_ -> HideSaveIndication) + else + Cmd.none + in + model ! [ cmd ] + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + Action -> + model ! [] + + Load newModel -> + RemoteData.update saveUpdate newModel + + InputCassette machine position str -> + RemoteData.update (updateCassette machine position str) model + + Submit action -> + RemoteData.update (updateAction action) model + + HideSaveIndication -> + RemoteData.update (\subModel -> { subModel | status = NotSaving } ! []) model diff --git a/lamassu-admin-elm/src/MaintenanceMachines/Types.elm b/lamassu-admin-elm/src/MaintenanceMachines/Types.elm new file mode 100644 index 00000000..850ee243 --- /dev/null +++ b/lamassu-admin-elm/src/MaintenanceMachines/Types.elm @@ -0,0 +1,48 @@ +module MaintenanceMachines.Types exposing (..) + +import RemoteData exposing (..) +import BasicTypes exposing (..) + + +type alias SubModel = + { status : SavingStatus + , machines : Machines + } + + +type alias Model = + RemoteData.WebData SubModel + + +type alias Machine = + { deviceId : String + , name : String + , cashbox : Int + , cassette1 : Int + , cassette2 : Int + , paired : Bool + , cashOut : Bool + } + + +type alias Machines = + List Machine + + +type MachineAction + = ResetCashOutBills Machine + | UnpairMachine Machine + | RebootMachine Machine + + +type Msg + = Action + | Load Model + | InputCassette Machine Position String + | Submit MachineAction + | HideSaveIndication + + +type Position + = Top + | Bottom diff --git a/lamassu-admin-elm/src/MaintenanceMachines/View.elm b/lamassu-admin-elm/src/MaintenanceMachines/View.elm new file mode 100644 index 00000000..5403aa2f --- /dev/null +++ b/lamassu-admin-elm/src/MaintenanceMachines/View.elm @@ -0,0 +1,112 @@ +module MaintenanceMachines.View exposing (..) + +import Html exposing (..) +import Html.Attributes exposing (defaultValue) +import Html.Events exposing (onClick, onInput) +import Css.Admin exposing (..) +import Css.Classes as C +import List +import RemoteData exposing (..) +import MaintenanceMachines.Types exposing (..) +import BasicTypes exposing (..) + + +inputCassetteView : Machine -> Position -> Int -> Html Msg +inputCassetteView machine position count = + input + [ class [ C.BasicInput ] + , onInput (InputCassette machine position) + , defaultValue (toString count) + ] + [] + + +rowView : Machine -> Html Msg +rowView machine = + let + resetBills = + if machine.cashOut then + td [] + [ button [ class [ C.TableButton ], onClick (Submit (ResetCashOutBills machine)) ] [ text "Update Counts" ] + ] + else + td [] [] + + actions = + [ td [] + [ button [ class [ C.TableButton ], onClick (Submit (UnpairMachine machine)) ] [ text "Unpair" ] ] + , td [] + [ button [ class [ C.TableButton ], onClick (Submit (RebootMachine machine)) ] [ text "Reboot" ] ] + , resetBills + ] + + cassetteCounts = + if machine.cashOut then + [ td [] + [ div [ classList [ ( C.Component, True ), ( C.FocusedComponent, False ) ] ] + [ inputCassetteView machine Top machine.cassette1 ] + ] + , td [] + [ div [ classList [ ( C.Component, True ), ( C.FocusedComponent, False ) ] ] + [ inputCassetteView machine Bottom machine.cassette2 ] + ] + ] + else + [ td [ class [ C.CellDisabled ] ] [], td [ class [ C.CellDisabled ] ] [] ] + in + tr [] + ([ td [] [ text machine.name ] ] + ++ cassetteCounts + ++ actions + ) + + +tableView : Machines -> Html Msg +tableView machines = + if List.isEmpty machines then + div [ class [ C.EmptyTable ] ] [ text "No paired machines." ] + else + table [ class [ C.ConfigTable ] ] + [ thead [] + [ tr [] + [ td [] [] + , td [] [ text "Top Bill Count" ] + , td [] [ text "Bottom Bill Count" ] + ] + ] + , tbody [] (List.map rowView machines) + ] + + +view : Model -> Html Msg +view model = + case model of + NotAsked -> + div [] [] + + Loading -> + div [] [ text "Loading..." ] + + Failure err -> + div [] [ text (toString err) ] + + Success subModel -> + let + statusString = + case subModel.status of + Saved -> + "Saved" + + _ -> + "" + in + div [] + [ div [ class [ C.SectionLabel ] ] + [ div [] + [ div [ class [ C.ConfigContainer ] ] + [ tableView subModel.machines + , div [ class [ C.Saving ] ] [ text statusString ] + ] + ] + ] + ] diff --git a/lamassu-admin-elm/src/NavBar.elm b/lamassu-admin-elm/src/NavBar.elm new file mode 100644 index 00000000..d0cfb091 --- /dev/null +++ b/lamassu-admin-elm/src/NavBar.elm @@ -0,0 +1,268 @@ +module NavBar exposing (view, routeToUrl) + +import Html exposing (Html, Attribute, a, div, hr, input, span, text, ul, li, nav) +import Html.Events exposing (onClick) +import Html.CssHelpers +import Css.Classes +import String +import CoreTypes + exposing + ( Msg(..) + , Category(..) + , Route(..) + ) + + +{ id, class, classList } = + Html.CssHelpers.withNamespace "lamassuAdmin" + + +maybeUrl : String -> List (Maybe String) -> String +maybeUrl root maybeStrings = + List.filterMap identity maybeStrings + |> List.append [ root ] + |> String.join "/" + + +routeToUrl : Route -> String +routeToUrl route = + case route of + PairRoute -> + "/#pair" + + AccountRoute account -> + "/#account/" ++ account + + ConfigRoute configGroup maybeCrypto -> + maybeUrl ("/#config/" ++ configGroup) [ maybeCrypto ] + + MaintenanceMachinesRoute -> + "/#machines/" + + MaintenanceFundingRoute maybeCrypto -> + maybeUrl ("/#funding") [ maybeCrypto ] + + TransactionsRoute -> + "/#transactions/" + + TransactionRoute txId -> + "/#transaction/" ++ txId + + CustomersRoute -> + "/#customers/" + + CustomerRoute id -> + "/#customer/" ++ id + + LogsRoute maybeId -> + "/#logs/" ++ (Maybe.withDefault "" maybeId) + + SupportLogsRoute maybeId -> + "/#support_logs/" ++ (Maybe.withDefault "" maybeId) + + NotFoundRoute -> + Debug.crash "Need unknown route" + + +linkClasses : Route -> Route -> Bool -> Attribute msg +linkClasses linkRoute route isValid = + let + validityClass = + if isValid then + [] + else + [ Css.Classes.InvalidGroup ] + + active = + case route of + ConfigRoute config _ -> + linkRoute == ConfigRoute config Nothing + + MaintenanceFundingRoute _ -> + linkRoute == MaintenanceFundingRoute Nothing + + LogsRoute _ -> + linkRoute == LogsRoute Nothing + + _ -> + linkRoute == route + in + if (active) then + class ([ Css.Classes.NavBarRoute, Css.Classes.Active ] ++ validityClass) + else + class ([ Css.Classes.NavBarRoute ] ++ validityClass) + + +type alias Link = + ( String, Route, Bool ) + + +activeCategory : Maybe Category -> Category -> Bool -> Attribute msg +activeCategory maybeCurrentCategory linkedCategory isValid = + let + validityClass = + if isValid then + [] + else + [ Css.Classes.InvalidGroup ] + in + case maybeCurrentCategory of + Nothing -> + class ([ Css.Classes.NavBarCategory ] ++ validityClass) + + Just currentCategory -> + if currentCategory == linkedCategory then + class ([ Css.Classes.NavBarCategory, Css.Classes.Active ] ++ validityClass) + else + class ([ Css.Classes.NavBarCategory ] ++ validityClass) + + +categoryView : Maybe Category -> ( String, Category, Route, Bool ) -> Html Msg +categoryView currentCategory link = + let + ( desc, category, linkRoute, isValid ) = + link + in + div + [ onClick (NewUrl (routeToUrl linkRoute)) + , activeCategory currentCategory category isValid + ] + [ text desc ] + + +linkView : Maybe Category -> Route -> Maybe Category -> Link -> Html Msg +linkView maybeCategory currentRoute maybeLinkedCategory link = + let + ( desc, linkRoute, isValid ) = + link + in + div [ onClick (NewUrl (routeToUrl linkRoute)), linkClasses linkRoute currentRoute isValid ] [ text desc ] + + +linksView : Maybe Category -> Route -> ( String, Category, Route, Bool ) -> List Link -> Html Msg +linksView maybeCurrentCategory currentRoute ( catDesc, cat, route, isValid ) links = + if maybeCurrentCategory == (Just cat) then + div [ class [ Css.Classes.NavBarCategoryContainer ] ] + [ categoryView maybeCurrentCategory ( catDesc, cat, route, isValid ) + , div [] (List.map (linkView maybeCurrentCategory currentRoute (Just cat)) links) + ] + else + div [ class [ Css.Classes.NavBarCategoryContainer ] ] + [ categoryView maybeCurrentCategory ( catDesc, cat, route, isValid ) + ] + + +determineConfigCategory : String -> Maybe Category +determineConfigCategory configCode = + if List.member configCode [ "definition", "setup", "cashOut", "commissions", "balanceAlerts" ] then + Just MachineSettingsCat + else if List.member configCode [ "walletSettings", "notifications", "compliance", "coinAtmRadar", "terms" ] then + Just GlobalSettingsCat + else + Nothing + + +allClear : List String -> Category -> Bool +allClear invalidGroups cat = + not <| List.any (\groupCode -> determineConfigCategory groupCode == Just cat) invalidGroups + + +determineCategory : Route -> Maybe Category +determineCategory route = + case route of + AccountRoute account -> + Just AccountCat + + ConfigRoute config _ -> + determineConfigCategory config + + MaintenanceMachinesRoute -> + Just MaintenanceCat + + MaintenanceFundingRoute _ -> + Just MaintenanceCat + + PairRoute -> + Nothing + + TransactionsRoute -> + Nothing + + TransactionRoute _ -> + Nothing + + CustomersRoute -> + Just MaintenanceCat + + CustomerRoute _ -> + Just MaintenanceCat + + LogsRoute _ -> + Just MaintenanceCat + + SupportLogsRoute _ -> + Nothing + + NotFoundRoute -> + Nothing + + +view : Route -> List String -> Html Msg +view route invalidGroups = + let + maybeCategory = + determineCategory route + + l = + linkView maybeCategory route Nothing + + ll = + linksView maybeCategory route + + isValid group = + not (List.member group invalidGroups) + + allClearMachine = + allClear invalidGroups MachineSettingsCat + + allClearGlobal = + allClear invalidGroups GlobalSettingsCat + + configLink code display = + ( display, ConfigRoute code Nothing, isValid code ) + in + nav [ class [ Css.Classes.NavBar ] ] + [ l ( "Transactions", TransactionsRoute, True ) + , ll ( "Maintenance", MaintenanceCat, MaintenanceMachinesRoute, True ) + [ ( "Machines", MaintenanceMachinesRoute, True ) + , ( "Funding", MaintenanceFundingRoute Nothing, True ) + , ( "Customers", CustomersRoute, True ) + , ( "Logs", LogsRoute Nothing, True ) + ] + , ll ( "Machine Settings", MachineSettingsCat, ConfigRoute "definition" Nothing, allClearMachine ) + [ configLink "definition" "Definition" + , configLink "setup" "Setup" + , configLink "cashOut" "Cash Out" + , configLink "commissions" "Commissions" + , configLink "balanceAlerts" "Balance Alerts" + ] + , ll ( "Global Settings", GlobalSettingsCat, ConfigRoute "walletSettings" Nothing, allClearGlobal ) + [ configLink "walletSettings" "Wallet Settings" + , configLink "notifications" "Notifications" + , configLink "compliance" "Compliance" + , configLink "coinAtmRadar" "Coin ATM Radar" + , configLink "terms" "Terms and Conditions" + ] + , ll ( "Third Party Services", AccountCat, AccountRoute "bitgo", True ) + [ ( "BitGo", AccountRoute "bitgo", True ) + , ( "Bitstamp", AccountRoute "bitstamp", True ) + , ( "Blockcypher", AccountRoute "blockcypher", True ) + , ( "Infura", AccountRoute "infura", True ) + , ( "Kraken", AccountRoute "kraken", True ) + , ( "Mailgun", AccountRoute "mailgun", True ) + , ( "QuadrigaCX", AccountRoute "quadrigacx", True ) + , ( "Strike", AccountRoute "strike", True ) + , ( "Twilio", AccountRoute "twilio", True ) + ] + , l ( "+ Add Machine", PairRoute, True ) + ] diff --git a/lamassu-admin-elm/src/Pair.elm b/lamassu-admin-elm/src/Pair.elm new file mode 100644 index 00000000..45a4d795 --- /dev/null +++ b/lamassu-admin-elm/src/Pair.elm @@ -0,0 +1,132 @@ +module Pair exposing (..) + +import Html exposing (Html, Attribute, h1, a, div, hr, input, span, text, node, button, strong, label) +import Html.Attributes exposing (id, attribute, placeholder, disabled, style, size) +import Html.Events exposing (onClick, onInput) +import Http +import HttpBuilder exposing (..) +import String +import RemoteData exposing (RemoteData(NotAsked, Loading, Failure, Success)) +import QRCode +import Css.Admin exposing (..) +import Css.Classes as C +import QRCode.ECLevel as ECLevel + + +-- MODEL + + +type alias Model = + { totem : RemoteData.WebData String + , name : String + , serverStatus : Bool + } + + +getTotem : String -> Cmd Msg +getTotem name = + get "/api/totem" + |> withQueryParams [ ( "name", name ) ] + |> withExpect Http.expectString + |> send RemoteData.fromResult + |> Cmd.map Load + + +init : Bool -> Model +init serverStatus = + { totem = RemoteData.NotAsked + , name = "" + , serverStatus = serverStatus + } + + + +-- UPDATE + + +type Msg + = Load (RemoteData.WebData String) + | InputName String + | SubmitName + + +updateStatus : Bool -> Model -> Model +updateStatus isUp model = + { model | serverStatus = isUp } + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + Load webData -> + let + _ = + Debug.log "TOTEM" (RemoteData.withDefault "Network Error" webData) + in + { model | totem = webData } ! [] + + InputName name -> + { model | name = name } ! [] + + SubmitName -> + model ! [ getTotem model.name ] + + +qrCode : String -> Html msg +qrCode s = + let + resultQRCode = + QRCode.toSvgWithECLevel s ECLevel.L + in + case resultQRCode of + Result.Ok view -> + view + + Result.Err err -> + Html.text (toString err) + + +view : Model -> Html Msg +view model = + if model.serverStatus then + case model.totem of + NotAsked -> + div [] + [ h1 [] [ text "Pair a new Lamassu cryptomat" ] + , div [] + [ label [] + [ text "Cryptomat name" + , input + [ onInput InputName + , placeholder "Coffee shop, 43 Elm St." + , size 50 + , style [ ( "margin-left", "1em" ) ] + ] + [] + , button + [ onClick SubmitName, disabled (String.isEmpty model.name) ] + [ text "Pair" ] + ] + ] + ] + + Loading -> + div [] + [ div [] [ text "..." ] ] + + Failure err -> + div [] [ text (toString err) ] + + Success totem -> + div + [] + [ div + [ class [ C.QrCode ] ] + [ qrCode totem ] + , div [] + [ span [] [ text "Scan this QR Code to pair " ] + , strong [] [ text model.name ] + ] + ] + else + div [] [ text "Make sure lamassu-server is up before pairing" ] diff --git a/lamassu-admin-elm/src/Selectize.elm b/lamassu-admin-elm/src/Selectize.elm new file mode 100644 index 00000000..e2db66a5 --- /dev/null +++ b/lamassu-admin-elm/src/Selectize.elm @@ -0,0 +1,552 @@ +module Selectize + exposing + ( initialSelectize + , view + , State + , Config + , HtmlOptions + , HtmlClasses + ) + +import Html exposing (..) +import Html.Attributes exposing (value, defaultValue, maxlength, class, classList, id, disabled) +import Html.Events as E exposing (on, onWithOptions) +import String +import Json.Decode as Json +import Css.Classes exposing (CssClasses) +import Css.Admin exposing (className) + +-- MODEL + +type alias HtmlOptions = + { instructionsForBlank : String + , noMatches : String + , typeForMore : String + , atMaxLength : String + , noOptions : String + , notAvailable : String + , classes : HtmlClasses + , customCssClass : CssClasses + } + + +type alias HtmlClasses = + { container : String + , noOptions : String + , singleItemContainer : String + , multiItemContainer : String + , selectBox : String + , selectedItems : String + , fallbackItems : String + , fallbackItem : String + , selectedItem : String + , boxContainer : String + , boxItems : String + , boxItem : String + , boxItemActive : String + , info : String + , infoNoMatches : String + , inputEditing : String + } + + +type alias H = + HtmlOptions + + +type Status + = Initial + | Editing + | Cleared + | Idle + | Blurred + + +type alias State = + { boxPosition : Int + , status : Status + , string : String + } + + +type alias Config msg idType itemType = + { maxItems : Int + , boxLength : Int + , toMsg : State -> msg + , onAdd : idType -> State -> msg + , onRemove : State -> msg + , onFocus : State -> msg + , onBlur : State -> msg + , toId : itemType -> idType + , enabled : Bool + , selectedDisplay : itemType -> String + , optionDisplay : itemType -> String + , match : String -> List itemType -> List itemType + , htmlOptions : HtmlOptions + } + + +type alias Items itemType = + { selectedItems : List itemType + , availableItems : List itemType + , boxItems : List itemType + } + + +initialSelectize : State +initialSelectize = + { boxPosition = -1, string = "", status = Blurred } + + + +-- UPDATE + + +clean : String -> String +clean s = + String.trim s + |> String.toLower + + +updateKeyUp : Config msg idType itemType -> Items itemType -> State -> Int -> msg +updateKeyUp config items state keyCode = + if keyCode == 13 || keyCode == 9 then + config.toMsg { state | status = Initial } + else + config.toMsg state + + +addSelection : Config msg idType itemType -> Items itemType -> State -> msg +addSelection config items state = + let + maybeItem = + if state.boxPosition < 0 then + Nothing + else + (List.head << (List.drop state.boxPosition)) items.boxItems + in + case maybeItem of + Nothing -> + config.toMsg state + + Just item -> + config.onAdd (config.toId item) { state | status = Cleared, string = "", boxPosition = -1 } + + +updateKeyDown : Config msg idType itemType -> Items itemType -> State -> Int -> msg +updateKeyDown config items state keyCode = + if config.maxItems > 1 && List.length items.selectedItems == config.maxItems then + case keyCode of + -- backspace + 8 -> + if String.isEmpty state.string && (not << List.isEmpty) items.selectedItems then + config.onRemove state + else + config.toMsg state + + _ -> + config.toMsg state + else + case keyCode of + -- up + 38 -> + config.toMsg { state | boxPosition = (max -1 (state.boxPosition - 1)) } + + -- down + 40 -> + config.toMsg + { state + | boxPosition = + (min ((List.length items.boxItems) - 1) + (state.boxPosition + 1) + ) + } + + -- enter + 13 -> + addSelection config items state + + -- backspace + 8 -> + if String.isEmpty state.string && (not << List.isEmpty) items.selectedItems then + config.onRemove state + else + config.toMsg state + + -- tab + 9 -> + addSelection config items state + + _ -> + config.toMsg state + + + +-- VIEW + + +itemView : Config msg idType itemType -> Bool -> itemType -> Html msg +itemView config isFallback item = + let + c = + config.htmlOptions.classes + in + span + [ classList + [ ( c.selectedItem, True ) + , ( c.fallbackItem, isFallback ) + ] + ] + [ text (config.selectedDisplay item) ] + + +fallbackItemsView : Config msg idType itemType -> Items itemType -> List itemType -> State -> Html msg +fallbackItemsView config items fallbackItems state = + let + c = + config.htmlOptions.classes + + selectedItems = + items.selectedItems + + isFallback = + List.length selectedItems == 0 + + classes = + classList + [ ( c.selectedItems, True ) + , ( c.fallbackItems, isFallback ) + ] + + itemsView = + if isFallback then + fallbackItems + else + items.selectedItems + in + span [ classes ] (List.map (itemView config isFallback) itemsView) + + +itemsView : Config msg idType itemType -> Items itemType -> List itemType -> State -> Html msg +itemsView config items fallbackItems state = + case state.status of + Editing -> + fallbackItemsView config items [] state + + Initial -> + fallbackItemsView config items [] state + + Idle -> + fallbackItemsView config items [] state + + Cleared -> + fallbackItemsView config items fallbackItems state + + Blurred -> + fallbackItemsView config items fallbackItems state + + +editingBoxView : Config msg idType itemType -> Items itemType -> State -> Html msg +editingBoxView config items state = + let + h = + config.htmlOptions + + c = + h.classes + + boxItemHtml pos item = + div + [ classList + [ ( c.boxItem, True ) + , ( c.boxItemActive, state.boxPosition == pos ) + ] + , onMouseDown config state (config.toId item) + ] + [ text (config.optionDisplay item) + ] + in + div [ class c.boxItems ] (List.indexedMap boxItemHtml items.boxItems) + + +idleBoxView : Config msg idType itemType -> Items itemType -> State -> Html msg +idleBoxView config items state = + let + h = + config.htmlOptions + + numSelected = + List.length items.selectedItems + + remainingItems = + List.length items.availableItems - numSelected + + typeForMore = + if remainingItems > config.boxLength then + if numSelected < config.maxItems then + div [ class h.classes.info ] [ text h.typeForMore ] + else + div [ class h.classes.info ] [ text "Backspace for more" ] + else + span [] [] + in + if config.maxItems > 1 && List.length items.selectedItems == config.maxItems then + span [] [] + else + div [ class h.classes.boxContainer ] + [ editingBoxView config items state + , typeForMore + ] + + +noMatches : Config msg idType itemType -> List itemType -> State -> Html msg +noMatches config boxItems state = + let + h = + config.htmlOptions + in + if List.length boxItems == 0 then + div + [ classList + [ ( h.classes.info, True ) + , ( h.classes.infoNoMatches, True ) + ] + ] + [ text h.noMatches ] + else + span [] [] + + +boxView : Config msg idType itemType -> Items itemType -> State -> Html msg +boxView config items state = + let + h = + config.htmlOptions + in + case state.status of + Editing -> + div [ class h.classes.boxContainer ] + [ editingBoxView config items state + , noMatches config items.boxItems state + ] + + Initial -> + idleBoxView config items state + + Idle -> + idleBoxView config items state + + Cleared -> + idleBoxView config items state + + Blurred -> + span [] [] + + +buildItems : List itemType -> List itemType -> List itemType -> Items itemType +buildItems selectedItems availableItems boxItems = + { selectedItems = selectedItems + , availableItems = availableItems + , boxItems = boxItems + } + + +diffItems : Config msg idType itemType -> List itemType -> List itemType -> List itemType +diffItems config a b = + let + isEqual itemA itemB = + config.toId itemA == config.toId itemB + + notInB b item = + (List.any (isEqual item) b) + |> not + in + List.filter (notInB b) a + + +mapToItem : (itemType -> idType) -> List itemType -> idType -> Maybe itemType +mapToItem toId available id = + List.filter (((==) id) << toId) available + |> List.head + + +view : Config msg idType itemType -> List idType -> List itemType -> List idType -> State -> Html msg +view config selectedIds availableItems fallbackIds state = + if List.length availableItems == 0 then + div [ class config.htmlOptions.classes.container ] + [ div [ class config.htmlOptions.classes.noOptions ] [ text config.htmlOptions.noOptions ] ] + else if not config.enabled then + div [ class config.htmlOptions.classes.container ] + [ div [ class config.htmlOptions.classes.noOptions ] [ text "\x2008" ] ] + else + let + h = + config.htmlOptions + + selectedItems = + List.filterMap (mapToItem config.toId availableItems) selectedIds + + fallbackItems = + List.filterMap (mapToItem config.toId availableItems) fallbackIds + + remainingItems = + diffItems config availableItems selectedItems + + boxItems = + config.match state.string remainingItems + |> List.take 5 + + items = + buildItems selectedItems availableItems boxItems + + onInputAtt = + onInput config state + + onBlurAtt = + onBlur config state + + onFocusAtt = + onFocus config state + + keyDown = + if config.maxItems > 1 then + if String.isEmpty state.string then + onKeyDownNoDelete config items state + else + onKeyDownDelete config items state + else + onKeyDown config items state + + editInput = + case state.status of + Initial -> + if (List.length selectedItems) < config.maxItems then + input [ onBlurAtt, onInputAtt ] [] + else + input [ onBlurAtt, onInputAtt, maxlength 0 ] [] + + Idle -> + if (List.length selectedItems) < config.maxItems then + input [ onBlurAtt, onInputAtt ] [] + else + input [ onBlurAtt, onInputAtt, maxlength 0 ] [] + + Editing -> + let + actualMaxlength = + if List.length boxItems == 0 then + 0 + else + 524288 + in + input [ maxlength actualMaxlength, onBlurAtt, onInputAtt, class h.classes.inputEditing ] [] + + Cleared -> + input [ onKeyUp config items state, value "", onBlurAtt, onInputAtt ] [] + + Blurred -> + input [ maxlength 0, onFocusAtt, value "" ] [] + in + div [ class h.classes.container ] + [ label + [ classList + [ ( h.classes.singleItemContainer, config.maxItems == 1 ) + , ( h.classes.multiItemContainer, config.maxItems > 1 ) + , ( className config.htmlOptions.customCssClass, True) + ] + ] + [ span [ class h.classes.selectBox, keyDown ] + [ span [] [ itemsView config items fallbackItems state ] + , editInput + ] + , boxView config items state + ] + ] + + +onInput : Config msg idType itemType -> State -> Attribute msg +onInput config state = + let + tagger s = + if (String.length s == 0) then + config.toMsg { state | status = Idle, string = s } + else + config.toMsg { state | status = Editing, string = s } + in + E.onInput tagger + + +onMouseDown : Config msg idType itemType -> State -> idType -> Attribute msg +onMouseDown config state id = + E.onMouseDown (config.onAdd id state) + + +onBlur : Config msg idType itemType -> State -> Attribute msg +onBlur config state = + E.onBlur (config.onBlur { state | status = Blurred }) + + +onFocus : Config msg idType itemType -> State -> Attribute msg +onFocus config state = + E.onFocus (config.onFocus { state | status = Initial, boxPosition = -1 }) + + +onKeyDownDelete : Config msg idType itemType -> Items itemType -> State -> Attribute msg +onKeyDownDelete config items state = + rawOnKeyDown deleteSpecialKeys (updateKeyDown config items state) + + +onKeyDownNoDelete : Config msg idType itemType -> Items itemType -> State -> Attribute msg +onKeyDownNoDelete config items state = + rawOnKeyDown noDeleteSpecialKeys (updateKeyDown config items state) + + +onKeyDown : Config msg idType itemType -> Items itemType -> State -> Attribute msg +onKeyDown config items state = + rawOnKeyDownNoPrevent (updateKeyDown config items state) + + +onKeyUp : Config msg idType itemType -> Items itemType -> State -> Attribute msg +onKeyUp config items state = + rawOnKeyUp (updateKeyUp config items state) + + +noDeleteSpecialKeys : List Int +noDeleteSpecialKeys = + [ 8, 38, 40, 9, 13, 27 ] + + +deleteSpecialKeys : List Int +deleteSpecialKeys = + [ 38, 40, 9, 13, 27 ] + + +preventSpecialDecoder : List Int -> Json.Decoder Int +preventSpecialDecoder specialKeys = + E.keyCode + |> Json.andThen + (\code -> + if List.member code specialKeys then + Json.succeed code + else + Json.fail "don't prevent" + ) + + +rawOnKeyDown : List Int -> (Int -> msg) -> Attribute msg +rawOnKeyDown specialKeys tagger = + let + options = + { stopPropagation = False, preventDefault = True } + in + onWithOptions "keydown" options (Json.map tagger (preventSpecialDecoder specialKeys)) + + +rawOnKeyDownNoPrevent : (Int -> msg) -> Attribute msg +rawOnKeyDownNoPrevent tagger = + onWithOptions "keydown" { stopPropagation = False, preventDefault = False } (Json.map tagger E.keyCode) + + +rawOnKeyUp : (Int -> msg) -> Attribute msg +rawOnKeyUp tagger = + on "keyup" (Json.map tagger E.keyCode) diff --git a/lamassu-admin-elm/src/SelectizeHelper.elm b/lamassu-admin-elm/src/SelectizeHelper.elm new file mode 100644 index 00000000..0596b06c --- /dev/null +++ b/lamassu-admin-elm/src/SelectizeHelper.elm @@ -0,0 +1,55 @@ +module SelectizeHelper exposing (LocalConfig, buildConfig) + +import Selectize exposing (..) +import Css.Selectize +import Css.Classes exposing (CssClasses) + + +type alias LocalConfig msg idType itemType = + { toMsg : State -> msg + , onAdd : idType -> State -> msg + , onRemove : State -> msg + , onFocus : State -> msg + , onBlur : State -> msg + , toId : itemType -> idType + , enabled : Bool + } + + +type alias SpecificConfig itemType = + { selectedDisplay : itemType -> String + , optionDisplay : itemType -> String + , maxItems : Int + , match : String -> List itemType -> List itemType + , customCssClass: CssClasses + } + + +buildConfig : + LocalConfig msg idType itemType + -> SpecificConfig itemType + -> Config msg idType itemType +buildConfig localConfig specificConfig = + { maxItems = specificConfig.maxItems + , boxLength = 5 + , toMsg = localConfig.toMsg + , onAdd = localConfig.onAdd + , onRemove = localConfig.onRemove + , onFocus = localConfig.onFocus + , onBlur = localConfig.onBlur + , toId = localConfig.toId + , enabled = localConfig.enabled + , selectedDisplay = specificConfig.selectedDisplay + , optionDisplay = specificConfig.optionDisplay + , match = specificConfig.match + , htmlOptions = + { instructionsForBlank = "Start typing for options" + , noMatches = "No matches" + , atMaxLength = "Type backspace to edit" + , typeForMore = "Type for more options" + , noOptions = "No options" + , notAvailable = "N/A" + , classes = Css.Selectize.classes + , customCssClass = specificConfig.customCssClass + } + } diff --git a/lamassu-admin-elm/src/StatusDecoder.elm b/lamassu-admin-elm/src/StatusDecoder.elm new file mode 100644 index 00000000..c17c3eac --- /dev/null +++ b/lamassu-admin-elm/src/StatusDecoder.elm @@ -0,0 +1,29 @@ +module StatusDecoder exposing (..) + +import StatusTypes exposing (..) +import Json.Decode exposing (..) + + +rateDecoder : Decoder Rate +rateDecoder = + map3 Rate + (field "crypto" string) + (field "bid" float) + (field "ask" float) + + +serverDecoder : Decoder ServerRec +serverDecoder = + map5 ServerRec + (field "up" bool) + (field "lastPing" (nullable string)) + (field "rates" (list rateDecoder)) + (field "machineStatus" string) + (field "wasConfigured" bool) + + +statusDecoder : Decoder StatusRec +statusDecoder = + map2 StatusRec + (field "server" serverDecoder) + (field "invalidConfigGroups" (list string)) diff --git a/lamassu-admin-elm/src/StatusTypes.elm b/lamassu-admin-elm/src/StatusTypes.elm new file mode 100644 index 00000000..8fac9c85 --- /dev/null +++ b/lamassu-admin-elm/src/StatusTypes.elm @@ -0,0 +1,29 @@ +module StatusTypes exposing (..) + +import RemoteData exposing (..) + + +type alias Rate = + { crypto : String + , bid : Float + , ask : Float + } + + +type alias ServerRec = + { up : Bool + , lastPing : Maybe String + , rates : List Rate + , machineStatus : String + , wasConfigured : Bool + } + + +type alias StatusRec = + { server : ServerRec + , invalidConfigGroups : List String + } + + +type alias WebStatus = + WebData StatusRec diff --git a/lamassu-admin-elm/src/Stylesheets.elm b/lamassu-admin-elm/src/Stylesheets.elm new file mode 100644 index 00000000..c4e2cff0 --- /dev/null +++ b/lamassu-admin-elm/src/Stylesheets.elm @@ -0,0 +1,18 @@ +port module Stylesheets exposing (..) + +import Css.File exposing (CssFileStructure, CssCompilerProgram) +import Css.Main + + +port files : CssFileStructure -> Cmd msg + + +fileStructure : CssFileStructure +fileStructure = + Css.File.toFileStructure + [ ( "../public/styles.css", Css.File.compile [ Css.Main.css ] ) ] + + +main : CssCompilerProgram +main = + Css.File.compiler files fileStructure diff --git a/lamassu-admin-elm/src/SupportLogs/Rest.elm b/lamassu-admin-elm/src/SupportLogs/Rest.elm new file mode 100644 index 00000000..844de7fa --- /dev/null +++ b/lamassu-admin-elm/src/SupportLogs/Rest.elm @@ -0,0 +1,20 @@ +module SupportLogs.Rest exposing (..) + +import RemoteData exposing (..) +import Http +import Common.Logs.Decoder exposing (logsDecoder, supportLogDecoder, supportLogsDecoder) +import SupportLogs.Types exposing (..) + + +getAllLogs : Maybe String -> Cmd Msg +getAllLogs maybeId = + Http.get ("/api/support_logs/logs?supportLogId=" ++ (Maybe.withDefault "" maybeId)) logsDecoder + |> RemoteData.sendRequest + |> Cmd.map LoadLogs + + +getSupportLogs : Cmd Msg +getSupportLogs = + Http.get "/api/support_logs/" supportLogsDecoder + |> RemoteData.sendRequest + |> Cmd.map LoadSupportLogs diff --git a/lamassu-admin-elm/src/SupportLogs/State.elm b/lamassu-admin-elm/src/SupportLogs/State.elm new file mode 100644 index 00000000..9aee73b9 --- /dev/null +++ b/lamassu-admin-elm/src/SupportLogs/State.elm @@ -0,0 +1,34 @@ +module SupportLogs.State exposing (..) + +import RemoteData exposing (..) +import SupportLogs.Rest exposing (..) +import SupportLogs.Types exposing (..) + + +init : Model +init = + { logs = NotAsked, supportLogs = NotAsked } + + +load : Maybe String -> ( Model, Cmd Msg ) +load maybeId = + ( { logs = Loading, supportLogs = Loading }, getSupportData maybeId ) + + +getSupportData : Maybe String -> Cmd Msg +getSupportData maybeId = + Cmd.batch [ getAllLogs maybeId, getSupportLogs ] + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + LoadLogs response -> + ( { model | logs = response } + , Cmd.none + ) + + LoadSupportLogs response -> + ( { model | supportLogs = response } + , Cmd.none + ) diff --git a/lamassu-admin-elm/src/SupportLogs/Types.elm b/lamassu-admin-elm/src/SupportLogs/Types.elm new file mode 100644 index 00000000..d42e141d --- /dev/null +++ b/lamassu-admin-elm/src/SupportLogs/Types.elm @@ -0,0 +1,15 @@ +module SupportLogs.Types exposing (..) + +import RemoteData exposing (..) +import Common.Logs.Types exposing (..) + + +type alias Model = + { logs : WebData Logs + , supportLogs : WebData SupportLogs + } + + +type Msg + = LoadLogs (WebData Logs) + | LoadSupportLogs (WebData SupportLogs) diff --git a/lamassu-admin-elm/src/SupportLogs/View.elm b/lamassu-admin-elm/src/SupportLogs/View.elm new file mode 100644 index 00000000..fa8729d4 --- /dev/null +++ b/lamassu-admin-elm/src/SupportLogs/View.elm @@ -0,0 +1,123 @@ +module SupportLogs.View exposing (..) + +import Html exposing (..) +import Html.Attributes exposing (href) +import Css.Admin exposing (..) +import Css.Classes as C +import RemoteData exposing (..) +import List +import Common.Logs.Types exposing (..) +import SupportLogs.Types exposing (..) +import Date exposing (..) +import Date.Extra exposing (toFormattedString) + + +supportLogText : SupportLog -> Html Msg +supportLogText supportLog = + text (supportLog.name ++ " " ++ (toFormattedString "yyyy-MM-dd HH:mm" supportLog.timestamp)) + + +supportLogLink : SupportLog -> Html Msg +supportLogLink supportLog = + a [ href ("/#support_logs/" ++ supportLog.id) ] [ supportLogText supportLog ] + + +formatDate : Date -> String +formatDate date = + toFormattedString "yyyy-MM-dd HH:mm" date + + +rowView : Log -> Html Msg +rowView log = + tr [ class [] ] + [ td [] [ text (formatDate log.timestamp) ] + , td [] [ text log.logLevel ] + , td [] [ text log.message ] + ] + + +supportLogItemView : SupportLog -> Html Msg +supportLogItemView supportLog = + li [] [ supportLogLink supportLog ] + + +supportLogsView : SupportLogs -> Html Msg +supportLogsView supportLogs = + if List.isEmpty supportLogs then + div [ class [ C.EmptyTable ] ] [ text "No shared logs" ] + else + div [] + [ div [ class [ C.TxTable ] ] + [ ul [] (List.map supportLogItemView supportLogs) + ] + ] + + +supportLogs : Model -> Html Msg +supportLogs model = + case model.supportLogs of + NotAsked -> + div [] [] + + Loading -> + div [] [ text "Loading snapshots ..." ] + + Failure err -> + div [] [ text (toString err) ] + + Success supportLogs -> + div [] [ supportLogsView supportLogs ] + + +logsView : Logs -> Html Msg +logsView logs = + if List.isEmpty logs.logs then + div [] [ text "No logs yet." ] + else + div [] + [ table [ class [ C.TxTable ] ] + [ thead [] + [ tr [] + [ td [] [ text "Date" ] + , td [] [ text "Level" ] + , td [] [ text "Message" ] + ] + ] + , tbody [] (List.map rowView logs.logs) + ] + ] + + +logs : Model -> Html Msg +logs model = + case model.logs of + NotAsked -> + div [] [] + + Loading -> + div [] [ text "Loading logs..." ] + + Failure err -> + div [] [ text "No logs yet." ] + + Success logs -> + div [] + [ logsView logs + ] + + +view : Model -> Html Msg +view model = + div [] + [ h1 [] [ text "Lamassu support logs" ] + , div [ class [ C.PaneWrapper ] ] + [ div [ class [ C.LeftPane ] ] + [ h2 [] [ text "Shared snapshots" ] + , supportLogs model + ] + , div [ class [ C.ContentPane ] ] + [ h2 [] [ text "Logs" ] + , logs model + ] + ] + ] diff --git a/lamassu-admin-elm/src/Transaction/Decoder.elm b/lamassu-admin-elm/src/Transaction/Decoder.elm new file mode 100644 index 00000000..a5a98d72 --- /dev/null +++ b/lamassu-admin-elm/src/Transaction/Decoder.elm @@ -0,0 +1,102 @@ +module Transaction.Decoder exposing (..) + +import Json.Decode exposing (..) +import Json.Decode.Extra exposing (date, fromResult) +import Json.Decode.Pipeline exposing (decode, required, optional, hardcoded) +import Common.TransactionTypes exposing (..) +import String + +mapCryptoCode : String -> Decoder CryptoCode +mapCryptoCode code = + case code of + "BTC" -> succeed BTC + "BCH" -> succeed BCH + "ETH" -> succeed ETH + "ZEC" -> succeed ZEC + "DASH" -> succeed DASH + "LTC" -> succeed LTC + _ -> fail ("No such cryptocurrency: " ++ code) + +cryptoCodeDecoder : Decoder CryptoCode +cryptoCodeDecoder = + string + |> andThen mapCryptoCode + +txDecode : String -> Decoder Tx +txDecode txClass = + case txClass of + "cashIn" -> + map CashInTx cashInTxDecoder + + "cashOut" -> + map CashOutTx cashOutTxDecoder + + _ -> + fail ("Unknown tx class: " ++ txClass) + + +txsDecoder : Decoder (List Tx) +txsDecoder = + (field "transactions" (list txDecoder)) + + +txDecoder : Decoder Tx +txDecoder = + (field "txClass" string) + |> andThen txDecode + + +floatString : Decoder Float +floatString = + string |> andThen (String.toFloat >> fromResult) + + +intString : Decoder Int +intString = + string |> andThen (String.toInt >> fromResult) + + +cashInTxDecoder : Decoder CashInTxRec +cashInTxDecoder = + decode CashInTxRec + |> required "id" string + |> required "machineName" string + |> required "toAddress" string + |> required "cryptoAtoms" intString + |> required "cryptoCode" cryptoCodeDecoder + |> required "fiat" floatString + |> required "fiatCode" string + |> required "txHash" (nullable string) + |> required "phone" (nullable string) + |> required "error" (nullable string) + |> required "operatorCompleted" bool + |> required "send" bool + |> required "sendConfirmed" bool + |> required "expired" bool + |> required "created" date + + +confirmedDecoder : Decoder Bool +confirmedDecoder = + map (Maybe.map (always True) >> Maybe.withDefault False) + (nullable string) + + +cashOutTxDecoder : Decoder CashOutTxRec +cashOutTxDecoder = + decode CashOutTxRec + |> required "id" string + |> required "machineName" string + |> required "toAddress" string + |> required "cryptoAtoms" intString + |> required "cryptoCode" cryptoCodeDecoder + |> required "fiat" floatString + |> required "fiatCode" string + |> required "status" string + |> required "dispense" bool + |> required "notified" bool + |> required "redeem" bool + |> required "phone" (nullable string) + |> required "error" (nullable string) + |> required "created" date + |> required "confirmedAt" confirmedDecoder diff --git a/lamassu-admin-elm/src/Transaction/Rest.elm b/lamassu-admin-elm/src/Transaction/Rest.elm new file mode 100644 index 00000000..9672adaa --- /dev/null +++ b/lamassu-admin-elm/src/Transaction/Rest.elm @@ -0,0 +1,31 @@ +module Transaction.Rest exposing (..) + +import RemoteData exposing (..) +import HttpBuilder exposing (..) +import Http +import HttpBuilder exposing (..) +import BasicTypes exposing (..) +import Common.TransactionTypes exposing (..) +import Transaction.Types exposing (..) +import Transaction.Decoder exposing (txDecoder) + + +toModel : SavingStatus -> Tx -> SubModel +toModel status tx = + { status = status, tx = tx } + + +getForm : String -> Cmd Msg +getForm txId = + get ("/api/transaction/" ++ txId) + |> withExpect (Http.expectJson txDecoder) + |> send (Result.map (toModel NotSaving) >> RemoteData.fromResult) + |> Cmd.map Load + + +cancel : String -> Cmd Msg +cancel txId = + patch ("/api/transaction/" ++ txId ++ "?cancel=true") + |> withExpect (Http.expectJson txDecoder) + |> send (Result.map (toModel NotSaving) >> RemoteData.fromResult) + |> Cmd.map Load diff --git a/lamassu-admin-elm/src/Transaction/State.elm b/lamassu-admin-elm/src/Transaction/State.elm new file mode 100644 index 00000000..101770a7 --- /dev/null +++ b/lamassu-admin-elm/src/Transaction/State.elm @@ -0,0 +1,34 @@ +module Transaction.State exposing (..) + +import RemoteData exposing (..) +import Transaction.Types exposing (..) +import Transaction.Rest exposing (..) +import BasicTypes exposing (..) + + +init : Model +init = + NotAsked + + +load : String -> ( Model, Cmd Msg ) +load txId = + ( Loading, getForm txId ) + + +txUpdate : SubModel -> ( SubModel, Cmd Msg ) +txUpdate model = + model ! [] + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + Load newModel -> + RemoteData.update txUpdate newModel + + Cancel txId -> + model ! [ cancel txId ] + + HideSaveIndication -> + RemoteData.update (\subModel -> { subModel | status = NotSaving } ! []) model diff --git a/lamassu-admin-elm/src/Transaction/Types.elm b/lamassu-admin-elm/src/Transaction/Types.elm new file mode 100644 index 00000000..bbb04859 --- /dev/null +++ b/lamassu-admin-elm/src/Transaction/Types.elm @@ -0,0 +1,21 @@ +module Transaction.Types exposing (..) + +import RemoteData exposing (..) +import BasicTypes exposing (..) +import Common.TransactionTypes exposing (..) + + +type alias SubModel = + { status : SavingStatus + , tx : Tx + } + + +type alias Model = + RemoteData.WebData SubModel + + +type Msg + = Load Model + | Cancel String + | HideSaveIndication diff --git a/lamassu-admin-elm/src/Transaction/View.elm b/lamassu-admin-elm/src/Transaction/View.elm new file mode 100644 index 00000000..ec71a8f8 --- /dev/null +++ b/lamassu-admin-elm/src/Transaction/View.elm @@ -0,0 +1,94 @@ +module Transaction.View exposing (..) + +import Html exposing (..) +import Html.Events exposing (onClick) +import RemoteData exposing (..) +import Common.TransactionTypes exposing (..) +import Transaction.Types exposing (..) +import Numeral exposing (format) + + +-- import Css.Admin exposing (..) +-- import Css.Classes as C + + +cashInTxView : CashInTxRec -> Html Msg +cashInTxView tx = + let + cancelStatus = + if tx.operatorCompleted then + "Cancelled" + else if tx.sendConfirmed then + "Sent" + else if tx.expired then + "Expired" + else + "Pending" + + cancellable = + not (tx.operatorCompleted || tx.sendConfirmed || tx.expired) + + cancelButtonDiv = + if cancellable then + div [] + [ button [ onClick (Cancel tx.id) ] [ text "Cancel transaction" ] + ] + else + div [] [] + + error = + Maybe.withDefault "Successful" tx.error + in + div [] + [ div [] [ text tx.id ] + , div [] [ text "This is a cash-in transaction" ] + , div [] [ text ("Fiat: " ++ (format "0,0.00" tx.fiat)) ] + , div [] [ text ("Status: " ++ cancelStatus) ] + , div [] [ text error ] + , cancelButtonDiv + ] + + +cashOutTxView : CashOutTxRec -> Html Msg +cashOutTxView tx = + let + error = + case tx.error of + Nothing -> + "No errors" + + Just err -> + "Error: " ++ err + in + div [] + [ div [] [ text tx.id ] + , div [] [ text "This is a cash-out transaction" ] + , div [] [ text ("Fiat: " ++ (format "0,0.00" tx.fiat)) ] + , div [] [ text error ] + ] + + +txView : SubModel -> Html Msg +txView subModel = + case subModel.tx of + CashInTx cashInTxRec -> + cashInTxView cashInTxRec + + CashOutTx cashOutTxRec -> + cashOutTxView cashOutTxRec + + +view : Model -> Html Msg +view model = + case model of + NotAsked -> + div [] [] + + Loading -> + div [] [ text "Loading..." ] + + Failure err -> + div [] [ text (toString err) ] + + Success subModel -> + txView subModel diff --git a/lamassu-admin-elm/src/Transactions.elm b/lamassu-admin-elm/src/Transactions.elm new file mode 100644 index 00000000..be77c4ff --- /dev/null +++ b/lamassu-admin-elm/src/Transactions.elm @@ -0,0 +1,201 @@ +module Transactions exposing (..) + +import Html exposing (..) +import Html.Attributes exposing (colspan, href) +import Css.Admin exposing (..) +import Css.Classes as C +import RemoteData exposing (..) +import Http +import HttpBuilder exposing (..) +import Transaction.Decoder exposing (txsDecoder) +import Common.TransactionTypes exposing (..) +import List +import Numeral exposing (format) +import Date.Extra exposing (toFormattedString) +import Maybe.Extra exposing (isJust) + + +type alias Txs = + List Tx + + +type alias Model = + RemoteData.WebData Txs + + +init : Model +init = + NotAsked + + +loadCmd : Cmd Msg +loadCmd = + getTransactions + + +load : ( Model, Cmd Msg ) +load = + ( Loading, loadCmd ) + + +getTransactions : Cmd Msg +getTransactions = + get ("/api/transactions") + |> withExpect (Http.expectJson txsDecoder) + |> send RemoteData.fromResult + |> Cmd.map Load + + +type Msg + = Load Model + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + Load loadedModel -> + loadedModel ! [] + + +multiplier : CryptoCode -> Float +multiplier code = + case code of + BTC -> + 1.0e8 + + BCH -> + 1.0e8 + + ETH -> + 1.0e18 + + ZEC -> + 1.0e8 + + DASH -> + 1.0e8 + + LTC -> + 1.0e8 + + + +txLink : String -> Html Msg +txLink txId = + a [ href ("/#transaction/" ++ txId) ] [ text (String.left 8 txId) ] + + +cryptoCodeDisplay : CryptoCode -> Html Msg +cryptoCodeDisplay code = + let + txt : String + txt = case code of + BTC -> "BTC" + BCH -> "BCH" + ETH -> "ETH" + ZEC -> "ZEC" + DASH -> "DASH" + LTC -> "LTC" + in + text txt + +rowView : Tx -> Html Msg +rowView tx = + case tx of + CashInTx cashIn -> + let + rowClasses = + if cashIn.operatorCompleted then + [ C.CashIn, C.TxCancelled ] + else + [ C.CashIn ] + + status = + if isJust cashIn.error then + "Error" + else if cashIn.operatorCompleted then + "Cancelled" + else if cashIn.sendConfirmed then + "Sent" + else if cashIn.expired then + "Expired" + else + "Pending" + in + tr [ class rowClasses ] + [ td [] [ txLink cashIn.id ] + , td [] [ text status ] + , td + [ class [ C.NumberColumn ] ] + [ text (toFormattedString "yyyy-MM-dd HH:mm" cashIn.created) ] + , td [] [ text cashIn.machineName ] + , td [ class [ C.NumberColumn ] ] + [ text (format "0,0.000000" ((toFloat cashIn.cryptoAtoms) / multiplier cashIn.cryptoCode)) + ] + , td [] [ cryptoCodeDisplay cashIn.cryptoCode ] + , td [ class [ C.NumberColumn ] ] [ text (format "0,0.00" cashIn.fiat) ] + , td [ class [ C.NumberColumn ] ] [ text (Maybe.withDefault "" cashIn.phone) ] + , td [ class [ C.TxAddress ] ] [ text cashIn.toAddress ] + ] + + CashOutTx cashOut -> + let + status = + if isJust cashOut.error then + "Error" + else if cashOut.dispense then + "Success" + else + "Pending" + in + tr [ class [ C.CashOut ] ] + [ td [] [ txLink cashOut.id ] + , td [] [ text status ] + , td [ class [ C.NumberColumn, C.DateColumn ] ] [ text (toFormattedString "yyyy-MM-dd HH:mm" cashOut.created) ] + , td [] [ text cashOut.machineName ] + , td [ class [ C.NumberColumn ] ] + [ text (format "0,0.000000" ((toFloat cashOut.cryptoAtoms) / multiplier cashOut.cryptoCode)) + ] + , td [] [ cryptoCodeDisplay cashOut.cryptoCode ] + , td [ class [ C.NumberColumn ] ] [ text (format "0,0.00" cashOut.fiat) ] + , td [ class [ C.NumberColumn ] ] [ text (Maybe.withDefault "" cashOut.phone) ] + , td [ class [ C.TxAddress ] ] [ text cashOut.toAddress ] + ] + + +tableView : Txs -> Html Msg +tableView txs = + if List.isEmpty txs then + div [] [ text "No activity yet." ] + else + table [ class [ C.TxTable ] ] + [ thead [] + [ tr [] + [ td [ class [ C.TxId ] ] [ text "Id" ] + , td [] [ text "Status" ] + , td [ class [ C.TxDate ] ] [ text "Time" ] + , td [ class [ C.TxMachine ] ] [ text "Machine" ] + , td [ colspan 2 ] [ text "Crypto" ] + , td [ class [ C.TxAmount ] ] [ text "Fiat" ] + , td [ class [ C.TxPhone ] ] [ text "Phone" ] + , td [ class [ C.TxAddress ] ] [ text "To address" ] + ] + ] + , tbody [] (List.map rowView txs) + ] + + +view : Model -> Html Msg +view model = + case model of + NotAsked -> + div [] [] + + Loading -> + div [] [ text "Loading..." ] + + Failure err -> + div [] [ text (toString err) ] + + Success txs -> + div [] [ tableView txs ] diff --git a/lamassu-admin-elm/tests/.gitignore b/lamassu-admin-elm/tests/.gitignore new file mode 100644 index 00000000..aee98106 --- /dev/null +++ b/lamassu-admin-elm/tests/.gitignore @@ -0,0 +1 @@ +/elm-stuff/ diff --git a/lamassu-admin-elm/tests/AccountTypesTests.elm b/lamassu-admin-elm/tests/AccountTypesTests.elm new file mode 100644 index 00000000..a7dcadc6 --- /dev/null +++ b/lamassu-admin-elm/tests/AccountTypesTests.elm @@ -0,0 +1,66 @@ +module AccountTypesTests exposing (..) + +import Test exposing (..) +import Expect +import AccountTypes +import Result +import FieldSetTypes exposing (..) +import AccountDecoder + + +testString : String +testString = + """ + { + "code": "twilio", + "display": "Twilio", + "fieldSet": { + "fields": [ + { + "code": "accountSid", + "display": "Account SID", + "type": "string", + "secret": false, + "required": true, + "value": { + "fieldType": "string", + "value": "xx123" + }, + "status": { "code": "idle" } + } + ] + } + } + """ + + +testRecord : AccountTypes.Account +testRecord = + { code = "twilio" + , display = "Twilio" + , fieldSet = + { fields = + [ { code = "accountSid" + , display = "Account SID" + , secret = False + , required = True + , value = FieldString "xx123" + , loadedValue = FieldString "xx123" + , status = FieldIdle + } + ] + } + } + + +all : Test +all = + describe "Parse InitialRecord" + [ test "Basic record" <| + \() -> + let + parsed = + AccountDecoder.decodeAccount testString + in + Expect.equal parsed (Ok testRecord) + ] diff --git a/lamassu-admin-elm/tests/ConfigTypesTests.elm b/lamassu-admin-elm/tests/ConfigTypesTests.elm new file mode 100644 index 00000000..2935d846 --- /dev/null +++ b/lamassu-admin-elm/tests/ConfigTypesTests.elm @@ -0,0 +1,100 @@ +module ConfigTypesTests exposing (..) + +import Test exposing (..) +import Expect +import AccountTypes +import Result +import FieldSetTypes exposing (..) +import ConfigTypes exposing (..) +import ConfigDecoder exposing (configGroupDecoder) +import Json.Decode exposing (decodeString) + + +testString : String +testString = + """ +{ + "code": "main", + "display": "Main", + "crypto": "global", + "cryptoConfigs": [ + { + "crypto": "BTC", + "machineConfigs": [ + { + "machine": "01", + "fieldSet": { + "fields": [ + { + "code": "cash-in-commission", + "display": "Cash In Commission", + "secret": false, + "required": false, + "value": { + "fieldType": "percentage", + "value": 15 + }, + "status": { + "code": "idle" + } + } + ] + } + } + ] + } + ], + "cryptos": [ + { + "crypto": "BTC", + "display": "Bitcoin" + } + ] +} + """ + + +testRecord : ConfigTypes.ConfigGroup +testRecord = + { code = "main" + , display = "Main" + , crypto = GlobalCrypto + , cryptoConfigs = + [ { crypto = CryptoCode "BTC" + , machineConfigs = + [ { machine = MachineId "01" + , fieldSet = + { fields = + [ { code = "cash-in-commission" + , display = "Cash In Commission" + , secret = False + , required = False + , value = FieldPercentage 15 + , loadedValue = FieldPercentage 15 + , status = FieldIdle + } + ] + } + } + ] + } + ] + , cryptos = + [ { crypto = CryptoCode "BTC" + , display = "Bitcoin" + } + ] + } + + +all : Test +all = + describe "Parse InitialRecord" + [ test "Basic record" <| + \() -> + let + parsed = + decodeString configGroupDecoder testString + in + Expect.equal parsed (Ok testRecord) + ] diff --git a/lamassu-admin-elm/tests/Main.elm b/lamassu-admin-elm/tests/Main.elm new file mode 100644 index 00000000..6eb323d9 --- /dev/null +++ b/lamassu-admin-elm/tests/Main.elm @@ -0,0 +1,14 @@ +port module Main exposing (..) + +import AccountTypesTests +import ConfigTypesTests +import Test.Runner.Node exposing (run) +import Json.Encode exposing (Value) + + +main : Program Never +main = + run emit ConfigTypesTests.all + + +port emit : ( String, Value ) -> Cmd msg diff --git a/lamassu-admin-elm/tests/elm-package.json b/lamassu-admin-elm/tests/elm-package.json new file mode 100644 index 00000000..44fb067c --- /dev/null +++ b/lamassu-admin-elm/tests/elm-package.json @@ -0,0 +1,18 @@ +{ + "version": "1.0.0", + "summary": "Sample Elm Test", + "repository": "https://github.com/user/project.git", + "license": "Unlicense", + "source-directories": [ + ".", + "../src" + ], + "exposed-modules": [], + "dependencies": { + "elm-community/elm-test": "2.0.0 <= v < 3.0.0", + "elm-lang/core": "4.0.1 <= v < 5.0.0", + "evancz/elm-http": "3.0.1 <= v < 4.0.0", + "rtfeldman/node-test-runner": "1.0.0 <= v < 2.0.0" + }, + "elm-version": "0.17.0 <= v < 0.18.0" +} diff --git a/lamassu-admin-elm/todo.txt b/lamassu-admin-elm/todo.txt new file mode 100644 index 00000000..dacbd094 --- /dev/null +++ b/lamassu-admin-elm/todo.txt @@ -0,0 +1,37 @@ +- error page for non-existent crypto url +- 404 page + +- advanced types, get all config screens working + +- convert l-s to work with new config from db +- fix up pairing and login, get ready for release + +- transactions screen + +--------------------------- + +v on page load, correctly set page and category +v fix css +v get pages to work again +v run updateUrl on default page +v fix input boxes in config (seems to be issue with cryptos) +- set default crypto +- registration url, save in cookie + +--------------------------- + +- reload numbers on submit (low priority) + +--------------------------- + +validation: + +- base on json-schema validator (will need a port) +- each config screen has validation errors mapped to each field +- each errored field is red with error message +- if config screen has any errors, config name in navbar is red +- if any config groups have any errored configs, config group is red in nav bar +- same for accounts +- ribbon on top shows that config isn't complete +- besides individual json-schema compliance, also check that all chosen account are not in error + diff --git a/package.json b/package.json index dbda0f18..e0536b14 100644 --- a/package.json +++ b/package.json @@ -84,10 +84,21 @@ }, "scripts": { "start": "node bin/lamassu-server", - "test": "mocha --recursive tests" + "test": "mocha --recursive tests", + "build-admin:css": "cd lamassu-admin-elm && elm-css src/Stylesheets.elm", + "build-admin:main": "cd lamassu-admin-elm && elm-make src/Main.elm --output ../public/elm.js", + "build-admin:lamassu": "cd lamassu-admin-elm && elm-make src/Lamassu.elm --output ../public/lamassu-elm.js", + "build-admin": "npm run build-admin:css && npm run build-admin:main && npm run build-admin:lamassu" }, "devDependencies": { "ava": "^0.19.1", - "mocha": "^5.0.1" + "mocha": "^5.0.1", + "standard": "^12.0.1" + }, + "standard": { + "ignore": [ + "/lamassu-admin-elm", + "/public" + ] } } diff --git a/public/lamassu-elm.js b/public/lamassu-elm.js index 6685b65b..4f2a38b8 100644 --- a/public/lamassu-elm.js +++ b/public/lamassu-elm.js @@ -5759,11 +5759,17 @@ var _elm_lang$core$Platform$Router = {ctor: 'Router'}; var _NoRedInk$elm_decode_pipeline$Json_Decode_Pipeline$decode = _elm_lang$core$Json_Decode$succeed; var _NoRedInk$elm_decode_pipeline$Json_Decode_Pipeline$resolve = _elm_lang$core$Json_Decode$andThen(_elm_lang$core$Basics$identity); -var _NoRedInk$elm_decode_pipeline$Json_Decode_Pipeline$custom = _elm_lang$core$Json_Decode$map2( - F2( - function (x, y) { - return y(x); - })); +var _NoRedInk$elm_decode_pipeline$Json_Decode_Pipeline$custom = F2( + function (decoder, wrapped) { + return A3( + _elm_lang$core$Json_Decode$map2, + F2( + function (x, y) { + return x(y); + }), + wrapped, + decoder); + }); var _NoRedInk$elm_decode_pipeline$Json_Decode_Pipeline$hardcoded = function (_p0) { return _NoRedInk$elm_decode_pipeline$Json_Decode_Pipeline$custom( _elm_lang$core$Json_Decode$succeed(_p0)); @@ -5795,7 +5801,15 @@ var _NoRedInk$elm_decode_pipeline$Json_Decode_Pipeline$optionalDecoder = F3( return _elm_lang$core$Json_Decode$fail(_p2._0); } } else { - return _elm_lang$core$Json_Decode$succeed(fallback); + var _p3 = A2( + _elm_lang$core$Json_Decode$decodeValue, + _elm_lang$core$Json_Decode$keyValuePairs(_elm_lang$core$Json_Decode$value), + input); + if (_p3.ctor === 'Ok') { + return _elm_lang$core$Json_Decode$succeed(fallback); + } else { + return _elm_lang$core$Json_Decode$fail(_p3._0); + } } }; return A2(_elm_lang$core$Json_Decode$andThen, handleResult, _elm_lang$core$Json_Decode$value);