Add lamassu-admin-elm to this repo (#185)

This commit is contained in:
Rafael Taranto 2018-10-08 16:29:06 -03:00 committed by Josh Harvey
parent 5ee7e40872
commit f4fc9d6328
134 changed files with 28171 additions and 83 deletions

View file

@ -1,3 +0,0 @@
[ignore]
[include]

66
.jscsrc
View file

@ -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
}

View file

@ -1,6 +0,0 @@
{
"trailing": false,
"node": true,
"unused": "last-param",
"globalstrict": true
}

8
lamassu-admin-elm/.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
node_modules
elm-stuff
build/styles.css
build/elm.js
.vscode
.idea

View file

@ -0,0 +1,28 @@
# lamassu-admin
## Development
Start the hot-reloading webpack dev server:
npm start
Navigate to <http://localhost:8080>.
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

View file

@ -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 <http://unlicense.org>

View file

@ -0,0 +1,18 @@
{
"name": "lamassu-admin-elm",
"homepage": "https://github.com/lamassu/lamassu-admin-elm",
"authors": [
"Josh Harvey <josh@lamassu.is>"
],
"description": "",
"main": "",
"license": "MIT",
"private": true,
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
]
}

View file

@ -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
}

View file

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

View file

@ -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 thats one third of the sections width,
> and then next to that I want another bit thats two thirds of the sectionss width.
> Now, in the next section…
I dont 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 Ive used dont match that thought process *at all*. I usually have to:
1. Remember how many columns are in the grid for the particular framework Im using.
1. Decide how I want to divide up this particular sections 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 Im using.
1. Remember the class naming structure for the framework Im 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.
### Gridisms Goals
I couldnt 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 projects folder. Boom. Done.
### 2. Link the stylesheet
Add the following stylesheet to your HTMLs `<head>` section:
```html
<link rel="stylesheet" href="bower_components/gridism/gridism.css">
```
**Note:** If you didnt 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 HTMLs `<head>` section:
```html
<meta name="viewport" content="width=device-width,initial-scale=1">
```
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 devices actual viewport width.
## Contributing
Id :heart: to receive contributions to this project. It doesnt matter if its just a typo, or if youre proposing an overhaul of the entire project—Ill 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).

View file

@ -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"
}

View file

@ -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;
}
}

View file

@ -0,0 +1,40 @@
{
"name": "qr-code",
"version": "0.1.8",
"homepage": "https://github.com/educastellano/qr-code",
"authors": [
"Eduard Castellano <educastellano08@gmail.com>"
],
"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
}

View file

@ -0,0 +1,94 @@
# &lt;qr-code&gt;
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
<script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
<script src="bower_components/qrjs/qr.js"></script>
<script src="bower_components/webcomponent-qr-code/src/qr-code.js"></script>
```
> 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
> <link rel="import" href="bower_components/webcomponent-qr-code/src/qr-code.html">
> ```
* **Start using it!**
```html
<qr-code data="hello world!"></qr-code>
```
## 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)

View file

@ -0,0 +1,30 @@
{
"name": "qr-code",
"version": "0.1.7",
"homepage": "https://github.com/educastellano/qr-code",
"authors": [
"Eduard Castellano <educastellano08@gmail.com>"
],
"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"
}
}

View file

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>&lt;qr-code&gt;</title>
</head>
<body>
<a href="demo">Demo here</a>
</body>
</html>

View file

@ -0,0 +1 @@
require('./src/qr-code.js')

View file

@ -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"
}
}

View file

@ -0,0 +1,56 @@
<polymer-element name="qr-code"
attributes="data format modulesize margin">
<script>
Polymer('qr-code', {
format: 'png',
dataChanged: function () {
this.generate();
},
generate: function () {
var options = {
modulesize: this.modulesize,
margin: this.margin === 0 ? -1 : this.margin
};
if (this.format === 'png') {
this.generatePNG(options);
}
else {
this.generateHTML(options);
}
},
generatePNG: function (options) {
var img;
try {
img = document.createElement('img');
img.src = QRCode.generatePNG(this.data, options);
this.clear();
this.appendChild(img);
}
catch (e) {
console.log('no canvas support');
}
},
generateHTML: function (options) {
var div = QRCode.generateHTML(this.data, options);
this.clear();
this.appendChild(div);
},
clear: function () {
var i;
for (i=0; i<this.children.length; i++) {
this.children[i].parentNode.removeChild(this.children[i]);
}
}
});
</script>
</polymer-element>

View file

@ -0,0 +1 @@
<script src="qr-code.js"></script>

View file

@ -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<attrs.length; i++) {
attr = attrs[i];
(function (attr) {
Object.defineProperty(this, attr, {
get: function () {
var value = this.getAttribute(attr);
return value === null ? this.attrs[attr] : value;
},
set: function (value) {
this.setAttribute(attr, value);
}
});
}.bind(this))(attr);
}
}
},
//
// LifeCycle Callbacks
//
createdCallback: {
value: function () {
this.createShadowRoot();
this.defineAttributes();
this.generate();
}
},
attributeChangedCallback: {
value: function (attrName, oldVal, newVal) {
var fn = this[attrName+'Changed'];
if (fn && typeof fn === 'function') {
fn.call(this, oldVal, newVal);
}
this.generate();
}
},
//
// Methods
//
getOptions: {
value: function () {
var modulesize = this.modulesize,
margin = this.margin;
return {
modulesize: modulesize !== null ? parseInt(modulesize) : modulesize,
margin: margin !== null ? parseInt(margin) : margin
};
}
},
generate: {
value: function () {
if (this.data !== null) {
if (this.format === 'png') {
this.generatePNG();
}
else if (this.format === 'html') {
this.generateHTML();
}
else if (this.format === 'svg') {
this.generateSVG();
}
else {
this.shadowRoot.innerHTML = '<div>qr-code: '+ this.format +' not supported!</div>'
}
}
else {
this.shadowRoot.innerHTML = '<div>qr-code: no data!</div>'
}
}
},
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 = '<div>qr-code: no canvas support!</div>'
}
}
},
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
});
});

View file

@ -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
}

View file

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

View file

@ -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
```
<div id="qrcode"></div>
<script type="text/javascript">
new QRCode(document.getElementById("qrcode"), "http://jindo.dev.naver.com/collie");
</script>
```
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")

View file

@ -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"
]
}

View file

@ -0,0 +1,44 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ko" lang="ko">
<head>
<title>Cross-Browser QRCode generator for Javascript</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
<script type="text/javascript" src="jquery.min.js"></script>
<script type="text/javascript" src="qrcode.js"></script>
</head>
<body>
<input id="text" type="text" value="http://jindo.dev.naver.com/collie" style="width:80%" /><br />
<div id="qrcode" style="width:100px; height:100px; margin-top:15px;"></div>
<script type="text/javascript">
var qrcode = new QRCode(document.getElementById("qrcode"), {
width : 100,
height : 100
});
function makeCode () {
var elText = document.getElementById("text");
if (!elText.value) {
alert("Input a text");
elText.focus();
return;
}
qrcode.makeCode(elText.value);
}
makeCode();
$("#text").
on("blur", function () {
makeCode();
}).
on("keydown", function (e) {
if (e.keyCode == 13) {
makeCode();
}
});
</script>
</body>

View file

@ -0,0 +1,37 @@
<?xml version="1.0" standalone="yes"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-50 0 200 100">
<g id="qrcode"/>
<foreignObject x="-50" y="0" width="100" height="100">
<body xmlns="http://www.w3.org/1999/xhtml" style="padding:0; margin:0">
<div style="padding:inherit; margin:inherit; height:100%">
<textarea id="text" style="height:100%; width:100%; position:absolute; margin:inherit; padding:inherit">james</textarea>
</div>
<script type="application/ecmascript" src="qrcode.js"></script>
<script type="application/ecmascript">
var elem = document.getElementById("qrcode");
var qrcode = new QRCode(elem, {
width : 100,
height : 100
});
function makeCode () {
var elText = document.getElementById("text");
if (elText.value === "") {
//alert("Input a text");
//elText.focus();
return;
}
qrcode.makeCode(elText.value);
}
makeCode();
document.getElementById("text").onkeyup = function (e) {
makeCode();
};
</script>
</body>
</foreignObject>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

View file

@ -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 <a href="http://www.d-project.com/" target="_blank">http://www.d-project.com/</a>
* @see <a href="http://jeromeetienne.github.com/jquery-qrcode/" target="_blank">http://jeromeetienne.github.com/jquery-qrcode/</a>
*/
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<this.moduleCount;row++){this.modules[row]=new Array(this.moduleCount);for(var col=0;col<this.moduleCount;col++){this.modules[row][col]=null;}}
this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-7,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(test,maskPattern);if(this.typeNumber>=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<this.modules.length;row++){var y=row*cs;for(var col=0;col<this.modules[row].length;col++){var x=col*cs;var dark=this.modules[row][col];if(dark){qr_mc.beginFill(0,100);qr_mc.moveTo(x,y);qr_mc.lineTo(x+cs,y);qr_mc.lineTo(x+cs,y+cs);qr_mc.lineTo(x,y+cs);qr_mc.endFill();}}}
return qr_mc;},setupTimingPattern:function(){for(var r=8;r<this.moduleCount-8;r++){if(this.modules[r][6]!=null){continue;}
this.modules[r][6]=(r%2==0);}
for(var c=8;c<this.moduleCount-8;c++){if(this.modules[6][c]!=null){continue;}
this.modules[6][c]=(c%2==0);}},setupPositionAdjustPattern:function(){var pos=QRUtil.getPatternPosition(this.typeNumber);for(var i=0;i<pos.length;i++){for(var j=0;j<pos.length;j++){var row=pos[i];var col=pos[j];if(this.modules[row][col]!=null){continue;}
for(var r=-2;r<=2;r++){for(var c=-2;c<=2;c++){if(r==-2||r==2||c==-2||c==2||(r==0&&c==0)){this.modules[row+r][col+c]=true;}else{this.modules[row+r][col+c]=false;}}}}}},setupTypeNumber:function(test){var bits=QRUtil.getBCHTypeNumber(this.typeNumber);for(var i=0;i<18;i++){var mod=(!test&&((bits>>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<data.length){dark=(((data[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;i<dataList.length;i++){var data=dataList[i];buffer.put(data.mode,4);buffer.put(data.getLength(),QRUtil.getLengthInBits(data.mode,typeNumber));data.write(buffer);}
var totalDataCount=0;for(var i=0;i<rsBlocks.length;i++){totalDataCount+=rsBlocks[i].dataCount;}
if(buffer.getLengthInBits()>totalDataCount*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<rsBlocks.length;r++){var dcCount=rsBlocks[r].dataCount;var ecCount=rsBlocks[r].totalCount-dcCount;maxDcCount=Math.max(maxDcCount,dcCount);maxEcCount=Math.max(maxEcCount,ecCount);dcdata[r]=new Array(dcCount);for(var i=0;i<dcdata[r].length;i++){dcdata[r][i]=0xff&buffer.buffer[i+offset];}
offset+=dcCount;var rsPoly=QRUtil.getErrorCorrectPolynomial(ecCount);var rawPoly=new QRPolynomial(dcdata[r],rsPoly.getLength()-1);var modPoly=rawPoly.mod(rsPoly);ecdata[r]=new Array(rsPoly.getLength()-1);for(var i=0;i<ecdata[r].length;i++){var modIndex=i+modPoly.getLength()-ecdata[r].length;ecdata[r][i]=(modIndex>=0)?modPoly.get(modIndex):0;}}
var totalCodeCount=0;for(var i=0;i<rsBlocks.length;i++){totalCodeCount+=rsBlocks[i].totalCount;}
var data=new Array(totalCodeCount);var index=0;for(var i=0;i<maxDcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<dcdata[r].length){data[index++]=dcdata[r][i];}}}
for(var i=0;i<maxEcCount;i++){for(var r=0;r<rsBlocks.length;r++){if(i<ecdata[r].length){data[index++]=ecdata[r][i];}}}
return data;};var QRMode={MODE_NUMBER:1<<0,MODE_ALPHA_NUM:1<<1,MODE_8BIT_BYTE:1<<2,MODE_KANJI:1<<3};var QRErrorCorrectLevel={L:1,M:0,Q:3,H:2};var QRMaskPattern={PATTERN000:0,PATTERN001:1,PATTERN010:2,PATTERN011:3,PATTERN100:4,PATTERN101:5,PATTERN110:6,PATTERN111:7};var QRUtil={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:(1<<10)|(1<<8)|(1<<5)|(1<<4)|(1<<2)|(1<<1)|(1<<0),G18:(1<<12)|(1<<11)|(1<<10)|(1<<9)|(1<<8)|(1<<5)|(1<<2)|(1<<0),G15_MASK:(1<<14)|(1<<12)|(1<<10)|(1<<4)|(1<<1),getBCHTypeInfo:function(data){var d=data<<10;while(QRUtil.getBCHDigit(d)-QRUtil.getBCHDigit(QRUtil.G15)>=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;i<errorCorrectLength;i++){a=a.multiply(new QRPolynomial([1,QRMath.gexp(i)],0));}
return a;},getLengthInBits:function(mode,type){if(1<=type&&type<10){switch(mode){case QRMode.MODE_NUMBER:return 10;case QRMode.MODE_ALPHA_NUM:return 9;case QRMode.MODE_8BIT_BYTE:return 8;case QRMode.MODE_KANJI:return 8;default:throw new Error("mode:"+mode);}}else if(type<27){switch(mode){case QRMode.MODE_NUMBER:return 12;case QRMode.MODE_ALPHA_NUM:return 11;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 10;default:throw new Error("mode:"+mode);}}else if(type<41){switch(mode){case QRMode.MODE_NUMBER:return 14;case QRMode.MODE_ALPHA_NUM:return 13;case QRMode.MODE_8BIT_BYTE:return 16;case QRMode.MODE_KANJI:return 12;default:throw new Error("mode:"+mode);}}else{throw new Error("type:"+type);}},getLostPoint:function(qrCode){var moduleCount=qrCode.getModuleCount();var lostPoint=0;for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount;col++){var sameCount=0;var dark=qrCode.isDark(row,col);for(var r=-1;r<=1;r++){if(row+r<0||moduleCount<=row+r){continue;}
for(var c=-1;c<=1;c++){if(col+c<0||moduleCount<=col+c){continue;}
if(r==0&&c==0){continue;}
if(dark==qrCode.isDark(row+r,col+c)){sameCount++;}}}
if(sameCount>5){lostPoint+=(3+sameCount-5);}}}
for(var row=0;row<moduleCount-1;row++){for(var col=0;col<moduleCount-1;col++){var count=0;if(qrCode.isDark(row,col))count++;if(qrCode.isDark(row+1,col))count++;if(qrCode.isDark(row,col+1))count++;if(qrCode.isDark(row+1,col+1))count++;if(count==0||count==4){lostPoint+=3;}}}
for(var row=0;row<moduleCount;row++){for(var col=0;col<moduleCount-6;col++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row,col+1)&&qrCode.isDark(row,col+2)&&qrCode.isDark(row,col+3)&&qrCode.isDark(row,col+4)&&!qrCode.isDark(row,col+5)&&qrCode.isDark(row,col+6)){lostPoint+=40;}}}
for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount-6;row++){if(qrCode.isDark(row,col)&&!qrCode.isDark(row+1,col)&&qrCode.isDark(row+2,col)&&qrCode.isDark(row+3,col)&&qrCode.isDark(row+4,col)&&!qrCode.isDark(row+5,col)&&qrCode.isDark(row+6,col)){lostPoint+=40;}}}
var darkCount=0;for(var col=0;col<moduleCount;col++){for(var row=0;row<moduleCount;row++){if(qrCode.isDark(row,col)){darkCount++;}}}
var ratio=Math.abs(100*darkCount/moduleCount/moduleCount-50)/5;lostPoint+=ratio*10;return lostPoint;}};var QRMath={glog:function(n){if(n<1){throw new Error("glog("+n+")");}
return QRMath.LOG_TABLE[n];},gexp:function(n){while(n<0){n+=255;}
while(n>=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<<i;}
for(var i=8;i<256;i++){QRMath.EXP_TABLE[i]=QRMath.EXP_TABLE[i-4]^QRMath.EXP_TABLE[i-5]^QRMath.EXP_TABLE[i-6]^QRMath.EXP_TABLE[i-8];}
for(var i=0;i<255;i++){QRMath.LOG_TABLE[QRMath.EXP_TABLE[i]]=i;}
function QRPolynomial(num,shift){if(num.length==undefined){throw new Error(num.length+"/"+shift);}
var offset=0;while(offset<num.length&&num[offset]==0){offset++;}
this.num=new Array(num.length-offset+shift);for(var i=0;i<num.length-offset;i++){this.num[i]=num[i+offset];}}
QRPolynomial.prototype={get:function(index){return this.num[index];},getLength:function(){return this.num.length;},multiply:function(e){var num=new Array(this.getLength()+e.getLength()-1);for(var i=0;i<this.getLength();i++){for(var j=0;j<e.getLength();j++){num[i+j]^=QRMath.gexp(QRMath.glog(this.get(i))+QRMath.glog(e.get(j)));}}
return new QRPolynomial(num,0);},mod:function(e){if(this.getLength()-e.getLength()<0){return this;}
var ratio=QRMath.glog(this.get(0))-QRMath.glog(e.get(0));var num=new Array(this.getLength());for(var i=0;i<this.getLength();i++){num[i]=this.get(i);}
for(var i=0;i<e.getLength();i++){num[i]^=QRMath.gexp(QRMath.glog(e.get(i))+ratio);}
return new QRPolynomial(num,0).mod(e);}};function QRRSBlock(totalCount,dataCount){this.totalCount=totalCount;this.dataCount=dataCount;}
QRRSBlock.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];QRRSBlock.getRSBlocks=function(typeNumber,errorCorrectLevel){var rsBlock=QRRSBlock.getRsBlockTable(typeNumber,errorCorrectLevel);if(rsBlock==undefined){throw new Error("bad rs block @ typeNumber:"+typeNumber+"/errorCorrectLevel:"+errorCorrectLevel);}
var length=rsBlock.length/3;var list=[];for(var i=0;i<length;i++){var count=rsBlock[i*3+0];var totalCount=rsBlock[i*3+1];var dataCount=rsBlock[i*3+2];for(var j=0;j<count;j++){list.push(new QRRSBlock(totalCount,dataCount));}}
return list;};QRRSBlock.getRsBlockTable=function(typeNumber,errorCorrectLevel){switch(errorCorrectLevel){case QRErrorCorrectLevel.L:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+0];case QRErrorCorrectLevel.M:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+1];case QRErrorCorrectLevel.Q:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+2];case QRErrorCorrectLevel.H:return QRRSBlock.RS_BLOCK_TABLE[(typeNumber-1)*4+3];default:return undefined;}};function QRBitBuffer(){this.buffer=[];this.length=0;}
QRBitBuffer.prototype={get:function(index){var bufIndex=Math.floor(index/8);return((this.buffer[bufIndex]>>>(7-index%8))&1)==1;},put:function(num,length){for(var i=0;i<length;i++){this.putBit(((num>>>(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 = ['<table style="border:0;border-collapse:collapse;">'];
for (var row = 0; row < nCount; row++) {
aHTML.push('<tr>');
for (var col = 0; col < nCount; col++) {
aHTML.push('<td style="border:0;border-collapse:collapse;padding:0;margin:0;width:' + nWidth + 'px;height:' + nHeight + 'px;background-color:' + (oQRCode.isDark(row, col) ? _htOption.colorDark : _htOption.colorLight) + ';"></td>');
}
aHTML.push('</tr>');
}
aHTML.push('</table>');
_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;
})();

File diff suppressed because one or more lines are too long

View file

@ -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
}

View file

@ -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 <http://www.gnu.org/licenses/>.

View file

@ -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"
]
}

View file

@ -0,0 +1,36 @@
{
"name": "qrjs",
"main": "qr.js",
"version": "0.1.2",
"homepage": "https://github.com/educastellano/qr.js",
"authors": [
"Kang Seonghoon <kang.seonghoon@mearie.org>",
"Eduard Castellano <educastellano08@gmail.com>"
],
"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"
}

View file

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

View file

@ -0,0 +1,27 @@
{
"name": "qrjs",
"main": "qr.js",
"version": "0.1.2",
"homepage": "https://github.com/educastellano/qr.js",
"authors": [
"Kang Seonghoon <kang.seonghoon@mearie.org>",
"Eduard Castellano <educastellano08@gmail.com>"
],
"description": "QR code generator in Javascript",
"moduleType": [
"globals"
],
"keywords": [
"qr",
"qr.js",
"qrcode"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
]
}

View file

@ -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 <kang.seonghoon@mearie.org>",
"license": "MIT",
"bugs": {
"url": "https://github.com/educastellano/qr.js/issues"
},
"homepage": "https://github.com/educastellano/qr.js"
}

View file

@ -0,0 +1,804 @@
/* qr.js -- QR code generator in Javascript (revision 2011-01-19)
* Written by Kang Seonghoon <public+qrjs@mearie.org>.
*
* 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 = ['<table border="0" cellspacing="0" cellpadding="0" style="border:' +
modsize*margin + 'px solid #fff;background:#fff">'];
for (var i = 0; i < n; ++i) {
html.push('<tr>');
for (var j = 0; j < n; ++j) {
html.push('<td style="width:' + modsize + 'px;height:' + modsize + 'px' +
(matrix[i][j] ? ';background:#000' : '') + '"></td>');
}
html.push('</tr>');
}
e.className = 'qrcode';
e.innerHTML = html.join('') + '</table>';
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 = [
'<style scoped>.bg{fill:#FFF}.fg{fill:#000}</style>',
'<rect class="bg" x="0" y="0"',
'width="'+size+'" height="'+size+'"/>',
];
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('<rect x="'+xo+'" y="'+yo+'"', common);
xo += modsize;
}
yo += modsize;
}
e.innerHTML = svg.join('');
return e;
},
'generatePNG': 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 n = matrix.length;
var size = modsize * (n + 2 * margin);
var canvas = document.createElement('canvas'), context;
canvas.width = canvas.height = size;
context = canvas.getContext('2d');
if (!context) throw 'canvas support is needed for PNG output';
context.fillStyle = '#fff';
context.fillRect(0, 0, size, size);
context.fillStyle = '#000';
for (var i = 0; i < n; ++i) {
for (var j = 0; j < n; ++j) {
if (matrix[i][j]) {
context.fillRect(modsize * (margin + j),
modsize * (margin + i),
modsize, modsize);
}
}
}
//context.fillText('evaluation: ' + evaluatematrix(matrix), 10, 10);
return canvas.toDataURL();
}
};
return QRCode;
});

View file

@ -0,0 +1,31 @@
{
"name": "webcomponentsjs",
"main": "webcomponents.js",
"version": "0.7.22",
"homepage": "http://webcomponents.org",
"authors": [
"The Polymer Authors"
],
"repository": {
"type": "git",
"url": "https://github.com/webcomponents/webcomponentsjs.git"
},
"keywords": [
"webcomponents"
],
"license": "BSD",
"ignore": [],
"devDependencies": {
"web-component-tester": "^4.0.1"
},
"_release": "0.7.22",
"_resolution": {
"type": "version",
"tag": "v0.7.22",
"commit": "50f9751f8e638301603aebb33ba9f1e90d2b0d32"
},
"_source": "https://github.com/Polymer/webcomponentsjs.git",
"_target": "^0.7.22",
"_originalSource": "webcomponentsjs",
"_direct": true
}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -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);

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,155 @@
webcomponents.js
================
[![Join the chat at https://gitter.im/webcomponents/webcomponentsjs](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/webcomponents/webcomponentsjs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
A suite of polyfills supporting the [Web Components](http://webcomponents.org) specs:
**Custom Elements**: allows authors to define their own custom tags ([spec](https://w3c.github.io/webcomponents/spec/custom/)).
**HTML Imports**: a way to include and reuse HTML documents via other HTML documents ([spec](https://w3c.github.io/webcomponents/spec/imports/)).
**Shadow DOM**: provides encapsulation by hiding DOM subtrees under shadow roots ([spec](https://w3c.github.io/webcomponents/spec/shadow/)).
This also folds in polyfills for `MutationObserver` and `WeakMap`.
## Releases
Pre-built (concatenated & minified) versions of the polyfills are maintained in the [tagged versions](https://github.com/webcomponents/webcomponentsjs/releases) of this repo. There are two variants:
`webcomponents.js` includes all of the polyfills.
`webcomponents-lite.js` includes all polyfills except for shadow DOM.
## Browser Support
Our polyfills are intended to work in the latest versions of evergreen browsers. See below
for our complete browser support matrix:
| Polyfill | IE10 | IE11+ | Chrome* | Firefox* | Safari 7+* | Chrome Android* | Mobile Safari* |
| ---------- |:----:|:-----:|:-------:|:--------:|:----------:|:---------------:|:--------------:|
| Custom Elements | ~ | ✓ | ✓ | ✓ | ✓ | ✓| ✓ |
| HTML Imports | ~ | ✓ | ✓ | ✓ | ✓| ✓| ✓ |
| Shadow DOM | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| Templates | ✓ | ✓ | ✓ | ✓| ✓ | ✓ | ✓ |
*Indicates the current version of the browser
~Indicates support may be flaky. If using Custom Elements or HTML Imports with Shadow DOM,
you will get the non-flaky Mutation Observer polyfill that Shadow DOM includes.
The polyfills may work in older browsers, however require additional polyfills (such as classList)
to be used. We cannot guarantee support for browsers outside of our compatibility matrix.
### Manually Building
If you wish to build the polyfills yourself, you'll need `node` and `gulp` on your system:
* install [node.js](http://nodejs.org/) using the instructions on their website
* use `npm` to install [gulp.js](http://gulpjs.com/): `npm install -g gulp`
Now you are ready to build the polyfills with:
# install dependencies
npm install
# build
gulp build
The builds will be placed into the `dist/` directory.
## Contribute
See the [contributing guide](CONTRIBUTING.md)
## License
Everything in this repository is BSD style license unless otherwise specified.
Copyright (c) 2015 The Polymer Authors. All rights reserved.
## Helper utilities
### `WebComponentsReady`
Under native HTML Imports, `<script>` 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 <a id="encapsulation"></a>
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 `<style>` elements from the shadow root, rewrites it rules using the given scope and reinserts the style as a document level stylesheet. Note that the `:host` and `:host-context` pseudo classes are also rewritten.
For a full explanation on the implementation and both the possibilities and the limitations of ShadowCSS please view the documentation in the [ShadowCSS source](src/ShadowCSS/ShadowCSS.js).
### Element wrapping / unwrapping limitations <a id="wrapping"></a>
The Shadow DOM polyfill is implemented by [wrapping](http://webcomponents.org/polyfills/shadow-dom/#wrappers) DOM elements whenever possible. It does this by wrapping methods like `document.querySelector` to return wrapped DOM elements. This has a few caveats:
* Not _everything_ can be wrapped. For example, elements like `document`, `window`, `document.body`, `document.fullscreenElement` and others are non-configurable and thus cannot be overridden.
* Wrappers don't support [live NodeLists](https://developer.mozilla.org/en-US/docs/Web/API/NodeList#A_sometimes-live_collection) like `HTMLElement.childNodes` and `HTMLFormElement.elements`. All NodeLists are snapshotted upon read. See [#217](https://github.com/webcomponents/webcomponentsjs/issues/217) for an explanation.
In order to work around these limitations the polyfill provides the `ShadowDOMPolyfill.wrap` and `ShadowDOMPolyfill.unwrap` methods to respectively wrap and unwrap DOM elements manually.
### Custom element's constructor property is unreliable <a id="constructor"></a>
See [#215](https://github.com/webcomponents/webcomponentsjs/issues/215) for background.
In Safari and IE, instances of Custom Elements have a `constructor` property of `HTMLUnknownElementConstructor` and `HTMLUnknownElement`, respectively. It's unsafe to rely on this property for checking element types.
It's worth noting that `customElement.__proto__.__proto__.constructor` is `HTMLElementPrototype` and that the prototype chain isn't modified by the polyfills(onto `ElementPrototype`, etc.)
### Contenteditable elements do not trigger MutationObserver <a id="contentedit"></a>
Using the MutationObserver polyfill, it isn't possible to monitor mutations of an element marked `contenteditable`.
See [the mailing list](https://groups.google.com/forum/#!msg/polymer-dev/LHdtRVXXVsA/v1sGoiTYWUkJ)
### ShadowCSS: :host-context(...):host(...) doesn't work <a id="hostcontext"></a>
See [#16](https://github.com/webcomponents/webcomponentsjs/issues/16) for background.
Under the shadow DOM polyfill, rules like:
```
:host-context(.foo):host(.bar) {...}
```
don't work, despite working under native Shadow DOM. The solution is to use `polyfill-next-selector` like:
```
polyfill-next-selector { content: '.foo :host.bar, :host.foo.bar'; }
```
### ShadowCSS: :host(.zot:not(.bar:nth-child(2))) doesn't work <a id="nestedparens"></a>
ShadowCSS `:host()` rules can only have (at most) 1-level of nested parentheses in its argument selector under ShadowCSS. For example, `:host(.zot)` and `:host(.zot:not(.bar))` both work, but `:host(.zot:not(.bar:nth-child(2)))` does not.
### HTML imports: document.currentScript doesn't work as expected <a id="currentscript"></a>
In native HTML Imports, document.currentScript.ownerDocument references the import document itself. In the polyfill use document._currentScript.ownerDocument (note the underscore).
### execCommand and contenteditable isn't supported under Shadow DOM <a id="execcommand"></a>
See [#212](https://github.com/webcomponents/webcomponentsjs/issues/212)
`execCommand`, and `contenteditable` aren't supported under the ShadowDOM polyfill, with commands that insert or remove nodes being especially prone to failure.

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,21 @@
{
"name": "webcomponentsjs",
"main": "webcomponents.js",
"version": "0.7.22",
"homepage": "http://webcomponents.org",
"authors": [
"The Polymer Authors"
],
"repository": {
"type": "git",
"url": "https://github.com/webcomponents/webcomponentsjs.git"
},
"keywords": [
"webcomponents"
],
"license": "BSD",
"ignore": [],
"devDependencies": {
"web-component-tester": "^4.0.1"
}
}

View file

@ -0,0 +1,31 @@
{
"name": "webcomponents.js",
"version": "0.7.22",
"description": "webcomponents.js",
"main": "webcomponents.js",
"directories": {
"test": "tests"
},
"repository": {
"type": "git",
"url": "https://github.com/webcomponents/webcomponentsjs.git"
},
"author": "The Polymer Authors",
"license": "BSD-3-Clause",
"bugs": {
"url": "https://github.com/webcomponents/webcomponentsjs/issues"
},
"scripts": {
"test": "wct"
},
"homepage": "http://webcomponents.org",
"devDependencies": {
"gulp": "^3.8.8",
"gulp-audit": "^1.0.0",
"gulp-concat": "^2.4.1",
"gulp-header": "^1.1.1",
"gulp-uglify": "^1.0.1",
"run-sequence": "^1.0.1",
"web-component-tester": "^4.0.1"
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -0,0 +1,23 @@
<html>
<head>
<base href="/">
</head>
<body>
<script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
<script src="bower_components/qrjs/qr.js"></script>
<script src="bower_components/qr-code/src/qr-code.js"></script>
<script src="elm.js"></script>
<script>
Elm.Main.fullscreen()
</script>
<link rel="stylesheet" href="bower_components/gridism/gridism.css">
<link rel="stylesheet" href="styles.css">
<style>
@keyframes fadein {
from { opacity: 0; }
to { opacity: 1; }
}
</style>
<meta name="viewport" content="width=device-width,initial-scale=1">
</body>
</html>

View file

@ -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

View file

@ -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"
}

View file

@ -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 ]
]
]
]

View file

@ -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

View file

@ -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) )
]

View file

@ -0,0 +1,10 @@
module AccountTypes exposing (..)
import FieldSet.Types exposing (..)
type alias Account =
{ code : String
, display : String
, fields : List Field
}

View file

@ -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))

View file

@ -0,0 +1,8 @@
module BasicTypes exposing (..)
type SavingStatus
= Saving
| Saved
| Editing
| NotSaving

View file

@ -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)

View file

@ -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)

View file

@ -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"

View file

@ -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

View file

@ -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 }

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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))

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
]
]

View file

@ -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"

View file

@ -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"
]
]
]
]
]
]

View file

@ -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
}

View file

@ -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

View file

@ -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 ]

View file

@ -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

View file

@ -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 ]

View file

@ -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

View file

@ -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 ! []

View file

@ -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

View file

@ -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 ]

View file

@ -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

View file

@ -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 ! []

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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
[]

View file

@ -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

View file

@ -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

View file

@ -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
)

View file

@ -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)

View file

@ -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
]
]
]

Some files were not shown because too many files have changed in this diff Show more