├── .gitignore
├── .jscsrc
├── .jshintrc
├── Gruntfile.js
├── README.md
├── bower.json
├── demo.html
├── hotbox.css
├── hotbox.css.map
├── hotbox.js
├── hotbox.min.js
├── less
└── hotbox.less
├── package.json
├── snap.png
├── src
├── expose.js
├── hotbox.js
├── key.js
├── keycontrol.js
└── keymap.js
└── test
├── SpecRunner.html
├── lib
└── jasmine-2.1.3
│ ├── boot.js
│ ├── console.js
│ ├── jasmine-html.js
│ ├── jasmine.css
│ └── jasmine.js
└── spec
└── .jshintrc
/.gitignore:
--------------------------------------------------------------------------------
1 | bower_components/
2 | dist/
3 | node_modules/
4 | .idea/
5 |
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fex-team/hotbox/440e62a01ae48bc0d5d6d6ed5379489a830fa967/.jscsrc
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "undef" : true,
3 | "unused" : false,
4 | "strict" : false,
5 | "curly" : false,
6 | "newcap" : true,
7 | "trailing" : true,
8 | "white": false,
9 | "quotmark": false,
10 | "browser": true,
11 | "boss": true,
12 | "loopfunc": true,
13 | "predef" : [
14 | "module",
15 | "require",
16 | "console",
17 | "define"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | /* global require, module */
2 |
3 | var path = require('path');
4 |
5 | module.exports = function(grunt) {
6 | 'use strict';
7 |
8 | // These plugins provide necessary tasks.
9 | /* [Build plugin & task ] ------------------------------------*/
10 | grunt.loadNpmTasks('grunt-module-dependence');
11 | grunt.loadNpmTasks('grunt-replace');
12 | grunt.loadNpmTasks('grunt-contrib-concat');
13 | grunt.loadNpmTasks('grunt-contrib-uglify');
14 | grunt.loadNpmTasks('grunt-contrib-less');
15 | grunt.loadNpmTasks('grunt-contrib-watch');
16 | grunt.loadNpmTasks('grunt-autoprefixer');
17 |
18 | var pkg = grunt.file.readJSON('package.json');
19 |
20 | var banner = '/*!\n' +
21 | ' * ====================================================\n' +
22 | ' * <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +
23 | '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
24 | '<%= pkg.homepage ? " * " + pkg.homepage + "\\n" : "" %>' +
25 | ' * GitHub: <%= pkg.repository.url %> \n' +
26 | ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
27 | ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n' +
28 | ' * ====================================================\n' +
29 | ' */\n\n';
30 |
31 | var expose = '\nuse(\'expose\');\n';
32 |
33 | // Project configuration.
34 | grunt.initConfig({
35 |
36 | // Metadata.
37 | pkg: pkg,
38 |
39 | // resolve dependence
40 | dependence: {
41 | options: {
42 | base: 'src',
43 | entrance: 'expose'
44 | },
45 | merge: {
46 | files: [{
47 | src: 'src/*.js',
48 | dest: 'hotbox.js'
49 | }]
50 | }
51 | },
52 |
53 | // concat
54 | concat: {
55 | closure: {
56 | options: {
57 | banner: banner + '(function () {\n',
58 | footer: expose + '})();'
59 | },
60 | files: {
61 | 'hotbox.js': ['hotbox.js']
62 | }
63 | }
64 | },
65 |
66 | uglify: {
67 | options: {
68 | banner: banner
69 | },
70 | minimize: {
71 | files: {
72 | 'hotbox.min.js': 'hotbox.js'
73 | }
74 | }
75 | },
76 |
77 | less: {
78 | dev: {
79 | files: {
80 | 'hotbox.css': [
81 | 'less/hotbox.less'
82 | ]
83 | },
84 | options: {
85 | sourceMap: true
86 | }
87 | },
88 | compile: {
89 | files: {
90 | 'hotbox.css': [
91 | 'less/hotbox.less'
92 | ]
93 | },
94 | options: {
95 | sourceMap: false
96 | }
97 | }
98 | },
99 |
100 | autoprefixer: {
101 | all: {
102 | src: 'hotbox.css',
103 | dest: 'hotbox.css'
104 | }
105 | },
106 |
107 | watch: {
108 | less: {
109 | files: ['less/*.less'],
110 | tasks: ['less:compile', 'autoprefixer']
111 | }
112 | }
113 |
114 | });
115 |
116 |
117 | // Build task(s).
118 | grunt.registerTask('default', ['dependence', 'concat', 'uglify', 'less', 'autoprefixer']);
119 |
120 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 热盒 UI
2 | ====
3 |
4 | 热盒 UI 是一种高效的上下文交互方式,在最大化编辑区域的同时(再也不需要臃肿的工具栏了)允许全键盘操作。
5 |
6 | 
7 |
8 | ## 使用示例
9 |
10 | ```js
11 | var hotbox = new HotBox('#editor');
12 |
13 | var main = hotbox.state('main');
14 | main.button({
15 | position: 'center',
16 | action: function() {
17 | // 编辑动作
18 | },
19 | label: '编辑',
20 | key: 'F2',
21 | next: 'idle'
22 | });
23 |
24 | hotbox.active('main', { x: 300, y: 300 });
25 | ```
26 |
27 | ## HotBox 类
28 |
29 | 用于构建热盒 UI。
30 |
31 | ### 构造函数 `HotBox.constructor`
32 |
33 | 直接使用 `new` 关键字创建 HotBox 实例。
34 |
35 | ```js
36 | var hotbox = new HotBox(selector);
37 | ```
38 |
39 | 构造函数接受一个**「必须」**的参数 `selector`,表示热盒渲染的位置。热盒创建之后的状态是 `idle`,这个状态下热盒是不可见的。
40 |
41 | ### 定义和获取状态 `state()` 方法
42 |
43 | 热盒在某个时刻有且只有一个状态(`state`),默认状态为 `idle`,此时热盒空闲,不会渲染,并且监听着按键,准备着进行定义的动作。用户需要通过 `state()` 方法来定义状态。
44 |
45 | ```js
46 | var main = hotbox.state('main');
47 | ```
48 |
49 | 该方法需要指定一个状态名称,返回指定名称的 `HotBoxState` 对象,该对象可以用于进一步定义状态。
50 |
51 | 热盒会自动把 `main` 状态作为主要状态,会在 `idle` 状态下监听 `main` 状态定义的按键。建议用户定义并使用 `main` 状态。
52 |
53 | ### 设置热盒当前状态 `active()` 方法
54 |
55 | 热盒默认在 `idle` 状态上,使用 `active` 方法,使热盒进入指定的状态。
56 |
57 | ```js
58 | hotbox.active('main', {x: 400, y: 400});
59 | ```
60 |
61 | 方法的完整签名为 `active(name, position)`。表示让热盒在 `position` 焦点位置渲染指定的状态。`position` 需要有 `x` 和 `y` 属性。
62 |
63 | ## HotBoxState 类
64 |
65 | `HotBoxState` 类用户无法创建,调用 `HotBox` 的 `.state()` 方法时返回。
66 |
67 | ### 为状态添加按钮 `button()`
68 |
69 | 使用 `button()` 方法为状态添加一个按钮。
70 |
71 | ```js
72 | var main = hotbox.state('main');
73 | main.button({
74 | label: '编辑',
75 | key: 'F2',
76 | action: function() { /* 执行的动作 */ }
77 | next: 'idle'
78 | });
79 | ```
80 |
81 | 下面是 `option` 对象支持的字段:
82 |
83 | 配置 | 类型 | 说明
84 | --- | --- | ---
85 | option.position | `String` | 按钮的位置。允许在以下位置:
`center` - 按钮在圆心处,只能定义一个
`ring` - 按钮在圆环处,能定义多个
`top` - 按钮在上栏,能定义多个
`bottom` - 按钮在下栏,能定义多个
86 | option.label | `String` | 按钮的标签文本
87 | option.key | `String` | 按钮的快捷键
88 | option.render | `Function` | 按钮的渲染器,如果指定,将使用指定的渲染器。如果不指定,将渲染标签。
渲染器需要返回按钮的 HTML 代码。
89 | option.enable | `Function` | 按钮是否可用的查询函数,如果不指定,则按钮始终可用。函数需要返回一个 `bool` 类型的值,来决定按钮是否可用。
90 | option.action | `Function` | 按钮执行的操作
91 | option.next | `string` | 操作执行之后热盒到达的状态。如不指定,默认到达 `idle`。可使用该参数来跳转到多级热盒。可以取值为 `back`
92 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hotbox",
3 | "version": "1.0.15",
4 | "main": [
5 | "hotbox.js",
6 | "hotbox.css"
7 | ],
8 | "keywords": [
9 | "hotbox",
10 | "html5",
11 | "javascript",
12 | "ui"
13 | ],
14 | "ignore": [
15 | ".gitignore",
16 | ".jscsrc",
17 | ".jshintrc",
18 | "Gruntfile.js",
19 | "package.json"
20 | ],
21 | "dependencies": {
22 | "seajs": "~2.3.0"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/demo.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
26 |
27 |
28 | 编辑区域获得焦点时,按空格呼出热盒。主菜单的快捷键可以直接执行
29 |
30 |
31 |
107 |
--------------------------------------------------------------------------------
/hotbox.css:
--------------------------------------------------------------------------------
1 | .hotbox {
2 | font-family: Arial, "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
3 | position: absolute;
4 | left: 0;
5 | top: 0;
6 | overflow: visible;
7 | }
8 | .hotbox .state {
9 | position: absolute;
10 | overflow: visible;
11 | display: none;
12 | }
13 | .hotbox .state .center .button,
14 | .hotbox .state .ring .button {
15 | position: absolute;
16 | width: 70px;
17 | height: 70px;
18 | margin-left: -35px;
19 | margin-top: -35px;
20 | border-radius: 100%;
21 | box-shadow: 0 0 30px rgba(0, 0, 0, 0.3);
22 | }
23 | .hotbox .state .center .label,
24 | .hotbox .state .ring .label,
25 | .hotbox .state .center .key,
26 | .hotbox .state .ring .key {
27 | display: block;
28 | text-align: center;
29 | line-height: 1.4em;
30 | vertical-align: middle;
31 | }
32 | .hotbox .state .center .label,
33 | .hotbox .state .ring .label {
34 | font-size: 16px;
35 | margin-top: 17px;
36 | color: black;
37 | font-weight: normal;
38 | line-height: 1em;
39 | }
40 | .hotbox .state .center .key,
41 | .hotbox .state .ring .key {
42 | font-size: 12px;
43 | color: #999;
44 | }
45 | .hotbox .state .ring-shape {
46 | position: absolute;
47 | left: -25px;
48 | top: -25px;
49 | border: 25px solid rgba(0, 0, 0, 0.3);
50 | border-radius: 100%;
51 | box-sizing: content-box;
52 | }
53 | .hotbox .state .top,
54 | .hotbox .state .bottom {
55 | position: absolute;
56 | white-space: nowrap;
57 | }
58 | .hotbox .state .top .button,
59 | .hotbox .state .bottom .button {
60 | display: inline-block;
61 | padding: 8px 15px;
62 | margin: 0 10px;
63 | border-radius: 15px;
64 | box-shadow: 0 0 30px rgba(0, 0, 0, 0.3);
65 | position: relative;
66 | }
67 | .hotbox .state .top .button .label,
68 | .hotbox .state .bottom .button .label {
69 | font-size: 14px;
70 | line-height: 14px;
71 | vertical-align: middle;
72 | color: black;
73 | line-height: 1em;
74 | }
75 | .hotbox .state .top .button .key,
76 | .hotbox .state .bottom .button .key {
77 | font-size: 12px;
78 | line-height: 12px;
79 | vertical-align: middle;
80 | color: #999;
81 | margin-left: 3px;
82 | }
83 | .hotbox .state .top .button .key:before,
84 | .hotbox .state .bottom .button .key:before {
85 | content: '(';
86 | }
87 | .hotbox .state .top .button .key:after,
88 | .hotbox .state .bottom .button .key:after {
89 | content: ')';
90 | }
91 | .hotbox .state .button {
92 | background: #F9F9F9;
93 | overflow: hidden;
94 | cursor: default;
95 | }
96 | .hotbox .state .button .key,
97 | .hotbox .state .button .label {
98 | opacity: 0.3;
99 | }
100 | .hotbox .state .button.enabled {
101 | background: white;
102 | }
103 | .hotbox .state .button.enabled .key,
104 | .hotbox .state .button.enabled .label {
105 | opacity: 1;
106 | }
107 | .hotbox .state .button.enabled:hover {
108 | background: #e87372;
109 | }
110 | .hotbox .state .button.enabled:hover .label {
111 | color: white;
112 | }
113 | .hotbox .state .button.enabled:hover .key {
114 | color: #fadfdf;
115 | }
116 | .hotbox .state .button.enabled.selected {
117 | -webkit-animation: selected .1s ease;
118 | background: #e45d5c;
119 | }
120 | .hotbox .state .button.enabled.selected .label {
121 | color: white;
122 | }
123 | .hotbox .state .button.enabled.selected .key {
124 | color: #fadfdf;
125 | }
126 | .hotbox .state .button.enabled.pressed,
127 | .hotbox .state .button.enabled:active {
128 | background: #FF974D;
129 | }
130 | .hotbox .state .button.enabled.pressed .label,
131 | .hotbox .state .button.enabled:active .label {
132 | color: white;
133 | }
134 | .hotbox .state .button.enabled.pressed .key,
135 | .hotbox .state .button.enabled:active .key {
136 | color: #fff0e6;
137 | }
138 | .hotbox .state.active {
139 | display: block;
140 | }
141 | @-webkit-keyframes selected {
142 | 0% {
143 | -webkit-transform: scale(1);
144 | transform: scale(1);
145 | }
146 | 50% {
147 | -webkit-transform: scale(1.1);
148 | transform: scale(1.1);
149 | }
150 | 100% {
151 | -webkit-transform: scale(1);
152 | transform: scale(1);
153 | }
154 | }
155 | .hotbox-key-receiver {
156 | position: absolute;
157 | left: -999999px;
158 | top: -999999px;
159 | width: 20px;
160 | height: 20px;
161 | outline: none;
162 | margin: 0;
163 | }
164 |
--------------------------------------------------------------------------------
/hotbox.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["less/hotbox.less"],"names":[],"mappings":"AACA;EACI,kBAAA;EACA,kBAAA;EACA,OAAA;EACA,MAAA;EACA,iBAAA;;AALJ,OAMI;EACI,kBAAA;EACA,iBAAA;EACA,aAAA;;AATR,OAMI,OAII,QACI;AAXZ,OAMI,OAIa,MACL;EACI,kBAAA;EACA,WAAA;EACA,YAAA;EACA,kBAAA;EACA,iBAAA;EACA,mBAAA;EACA,uCAAA;;AAlBhB,OAMI,OAII,QAUI;AApBZ,OAMI,OAIa,MAUL;AApBZ,OAMI,OAII,QAUY;AApBpB,OAMI,OAIa,MAUG;EACJ,cAAA;EACA,kBAAA;EACA,kBAAA;EACA,sBAAA;;AAxBhB,OAMI,OAII,QAgBI;AA1BZ,OAMI,OAIa,MAgBL;EACI,eAAA;EACA,gBAAA;EACA,YAAA;EACA,mBAAA;EACA,gBAAA;;AA/BhB,OAMI,OAII,QAuBI;AAjCZ,OAMI,OAIa,MAuBL;EACI,eAAA;EACA,WAAA;;AAnChB,OAMI,OAgCI;EACI,kBAAA;EACA,WAAA;EACA,UAAA;EACA,qCAAA;EACA,mBAAA;EACA,uBAAA;;AA5CZ,OAMI,OAwCI;AA9CR,OAMI,OAwCU;EACF,kBAAA;EACA,mBAAA;;AAhDZ,OAMI,OAwCI,KAGI;AAjDZ,OAMI,OAwCU,QAGF;EACI,qBAAA;EACA,iBAAA;EACA,cAAA;EACA,mBAAA;EACA,uCAAA;EACA,kBAAA;;AAvDhB,OAMI,OAwCI,KAGI,QAQI;AAzDhB,OAMI,OAwCU,QAGF,QAQI;EACI,eAAA;EACA,iBAAA;EACA,sBAAA;EACA,YAAA;EACA,gBAAA;;AA9DpB,OAMI,OAwCI,KAGI,QAeI;AAhEhB,OAMI,OAwCU,QAGF,QAeI;EACI,eAAA;EACA,iBAAA;EACA,sBAAA;EACA,WAAA;EACA,gBAAA;;AACA,OAhEhB,OAwCI,KAGI,QAeI,KAMK;AAAD,OAhEhB,OAwCU,QAGF,QAeI,KAMK;EACG,SAAS,GAAT;;AAEJ,OAnEhB,OAwCI,KAGI,QAeI,KASK;AAAD,OAnEhB,OAwCU,QAGF,QAeI,KASK;EACG,SAAS,GAAT;;AA1ExB,OAMI,OAyEI;EACI,mBAAA;EACA,gBAAA;EACA,eAAA;;AAlFZ,OAMI,OAyEI,QAKI;AApFZ,OAMI,OAyEI,QAKU;EACF,YAAA;;AArFhB,OAMI,OAkFI,QAAO;EACH,iBAAA;;AAzFZ,OAMI,OAkFI,QAAO,QAGH;AA3FZ,OAMI,OAkFI,QAAO,QAGG;EACF,UAAA;;AAGJ,OAzFR,OAkFI,QAAO,QAOF;EACG,mBAAA;;AADJ,OAzFR,OAkFI,QAAO,QAOF,MAEG;EACI,YAAA;;AAHR,OAzFR,OAkFI,QAAO,QAOF,MAKG;EACI,cAAA;;AAGR,OAlGR,OAkFI,QAAO,QAgBF;EACG,oCAAA;EACA,mBAAA;;AAFJ,OAlGR,OAkFI,QAAO,QAgBF,SAGG;EACI,YAAA;;AAJR,OAlGR,OAkFI,QAAO,QAgBF,SAMG;EACI,cAAA;;AAGR,OA5GR,OAkFI,QAAO,QA0BF;AAAU,OA5GnB,OAkFI,QAAO,QA0BS;EACR,mBAAA;;AADJ,OA5GR,OAkFI,QAAO,QA0BF,QAEG;AAFO,OA5GnB,OAkFI,QAAO,QA0BS,OAER;EACI,YAAA;;AAHR,OA5GR,OAkFI,QAAO,QA0BF,QAKG;AALO,OA5GnB,OAkFI,QAAO,QA0BS,OAKR;EACI,cAAA;;AAxHpB,OA6HI,OAAM;EACF,cAAA;;AAIR;EACI;IAAK,WAAW,QAAX;;EACL;IAAM,WAAW,UAAX;;EACN;IAAO,WAAW,QAAX;;;AAGX;EACI,kBAAA;EACA,eAAA;EACA,cAAA;EACA,WAAA;EACA,YAAA;EACA,aAAA;EACA,SAAA","file":"hotbox.css"}
--------------------------------------------------------------------------------
/hotbox.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * ====================================================
3 | * Hot Box UI - v1.0.15 - 2017-05-05
4 | * https://github.com/fex-team/hotbox
5 | * GitHub: https://github.com/fex-team/hotbox.git
6 | * Copyright (c) 2017 Baidu FEX; Licensed BSD
7 | * ====================================================
8 | */
9 |
10 | (function () {
11 | var _p = {
12 | r: function(index) {
13 | if (_p[index].inited) {
14 | return _p[index].value;
15 | }
16 | if (typeof _p[index].value === "function") {
17 | var module = {
18 | exports: {}
19 | }, returnValue = _p[index].value(null, module.exports, module);
20 | _p[index].inited = true;
21 | _p[index].value = returnValue;
22 | if (returnValue !== undefined) {
23 | return returnValue;
24 | } else {
25 | for (var key in module.exports) {
26 | if (module.exports.hasOwnProperty(key)) {
27 | _p[index].inited = true;
28 | _p[index].value = module.exports;
29 | return module.exports;
30 | }
31 | }
32 | }
33 | } else {
34 | _p[index].inited = true;
35 | return _p[index].value;
36 | }
37 | }
38 | };
39 |
40 | //src/expose.js
41 | _p[0] = {
42 | value: function(require, exports, module) {
43 | module.exports = window.HotBox = _p.r(1);
44 | }
45 | };
46 |
47 | //src/hotbox.js
48 | _p[1] = {
49 | value: function(require, exports, module) {
50 | var key = _p.r(2);
51 | var KeyControl = _p.r(3);
52 | /**** Dom Utils ****/
53 | function createElement(name) {
54 | return document.createElement(name);
55 | }
56 | function setElementAttribute(element, name, value) {
57 | element.setAttribute(name, value);
58 | }
59 | function getElementAttribute(element, name) {
60 | return element.getAttribute(name);
61 | }
62 | function addElementClass(element, name) {
63 | element.classList.add(name);
64 | }
65 | function removeElementClass(element, name) {
66 | element.classList.remove(name);
67 | }
68 | function appendChild(parent, child) {
69 | parent.appendChild(child);
70 | }
71 | /*******************/
72 | var IDLE = HotBox.STATE_IDLE = "idle";
73 | var div = "div";
74 | /**
75 | * Simple Formatter
76 | */
77 | function format(template, args) {
78 | if (typeof args != "object") {
79 | args = [].slice.apply(arguments, 1);
80 | }
81 | return String(template).replace(/\{(\w+)\}/g, function(match, name) {
82 | return args[name] || match;
83 | });
84 | }
85 | /**
86 | * Hot Box Class
87 | */
88 | function HotBox($container) {
89 | if (typeof $container == "string") {
90 | $container = document.querySelector($container);
91 | }
92 | if (!$container || !($container instanceof HTMLElement)) {
93 | throw new Error("No container or not invalid container for hot box");
94 | }
95 | // 创建 HotBox Dom 解构
96 | var $hotBox = createElement(div);
97 | addElementClass($hotBox, "hotbox");
98 | appendChild($container, $hotBox);
99 | // 保存 Dom 解构和父容器
100 | this.$element = $hotBox;
101 | this.$container = $container;
102 | // 标示是否是输入法状态
103 | this.isIME = false;
104 | /**
105 | * @Desc: 增加一个browser用于判断浏览器类型,方便解决兼容性问题
106 | * @Editor: Naixor
107 | * @Date: 2015.09.14
108 | */
109 | this.browser = {
110 | sg: /se[\s\S]+metasr/.test(navigator.userAgent.toLowerCase())
111 | };
112 | /*
113 | * added by zhangbobell
114 | * 2015.09.22
115 | * 增加父状态机,以解决在父 FSM 下状态控制的问题,最好的解决办法是增加一个函数队列
116 | * 将其中的函数一起执行。//TODO
117 | * */
118 | this._parentFSM = {};
119 | // 记录位置
120 | this.position = {};
121 | // 已定义的状态(string => HotBoxState)
122 | var _states = {};
123 | // 主状态(HotBoxState)
124 | var _mainState = null;
125 | // 当前状态(HotBoxState)
126 | var _currentState = IDLE;
127 | // 当前状态堆栈
128 | var _stateStack = [];
129 | // 实例引用
130 | var _this = this;
131 | var _controler;
132 | /**
133 | * Controller: {
134 | * constructor(hotbox: HotBox),
135 | * active: () => void
136 | * }
137 | */
138 | function _control(Controller) {
139 | if (_controler) {
140 | _controler.active();
141 | return;
142 | }
143 | Controller = Controller || KeyControl;
144 | _controler = new Controller(_this);
145 | _controler.active();
146 | $hotBox.onmousedown = function(e) {
147 | e.stopPropagation();
148 | e.preventDefault();
149 | };
150 | return _this;
151 | }
152 | function _dispatchKey(e) {
153 | var type = e.type.toLowerCase();
154 | e.keyHash = key.hash(e);
155 | e.isKey = function(keyExpression) {
156 | if (!keyExpression) return false;
157 | var expressions = keyExpression.split(/\s*\|\s*/);
158 | while (expressions.length) {
159 | if (e.keyHash == key.hash(expressions.shift())) return true;
160 | }
161 | return false;
162 | };
163 | e[type] = true;
164 | // Boot: keyup and activeKey pressed on IDLE, active main state.
165 | if (e.keyup && _this.activeKey && e.isKey(_this.activeKey) && _currentState == IDLE && _mainState) {
166 | _activeState("main", {
167 | x: $container.clientWidth / 2,
168 | y: $container.clientHeight / 2
169 | });
170 | return;
171 | }
172 | var handleState = _currentState == IDLE ? _mainState : _currentState;
173 | if (handleState) {
174 | var handleResult = handleState.handleKeyEvent(e);
175 | if (typeof _this.onkeyevent == "function") {
176 | e.handleResult = handleResult;
177 | _this.onkeyevent(e, handleResult);
178 | }
179 | return handleResult;
180 | }
181 | return null;
182 | }
183 | function _addState(name) {
184 | if (!name) return _currentState;
185 | if (name == IDLE) {
186 | throw new Error("Can not define or use the `idle` state.");
187 | }
188 | _states[name] = _states[name] || new HotBoxState(this, name);
189 | if (name == "main") {
190 | _mainState = _states[name];
191 | }
192 | return _states[name];
193 | }
194 | function _activeState(name, position) {
195 | _this.position = position;
196 | // 回到 IDLE
197 | if (name == IDLE) {
198 | if (_currentState != IDLE) {
199 | _stateStack.shift().deactive();
200 | _stateStack = [];
201 | }
202 | _currentState = IDLE;
203 | } else if (name == "back") {
204 | if (_currentState != IDLE) {
205 | _currentState.deactive();
206 | _stateStack.shift();
207 | _currentState = _stateStack[0];
208 | if (_currentState) {
209 | _currentState.active();
210 | } else {
211 | _currentState = "idle";
212 | }
213 | }
214 | } else {
215 | if (_currentState != IDLE) {
216 | _currentState.deactive();
217 | }
218 | var newState = _states[name];
219 | _stateStack.unshift(newState);
220 | if (typeof _this.position == "function") {
221 | position = _this.position(position);
222 | }
223 | newState.active(position);
224 | _currentState = newState;
225 | }
226 | }
227 | function setParentFSM(fsm) {
228 | _this._parentFSM = fsm;
229 | }
230 | function getParentFSM() {
231 | return _this._parentFSM;
232 | }
233 | this.control = _control;
234 | this.state = _addState;
235 | this.active = _activeState;
236 | this.dispatch = _dispatchKey;
237 | this.setParentFSM = setParentFSM;
238 | this.getParentFSM = getParentFSM;
239 | this.activeKey = "space";
240 | this.actionKey = "space";
241 | }
242 | /**
243 | * 表示热盒某个状态,包含这些状态需要的 Dom 对象
244 | */
245 | function HotBoxState(hotBox, stateName) {
246 | var BUTTON_SELECTED_CLASS = "selected";
247 | var BUTTON_PRESSED_CLASS = "pressed";
248 | var STATE_ACTIVE_CLASS = "active";
249 | // 状态容器
250 | var $state = createElement(div);
251 | // 四种可见的按钮容器
252 | var $center = createElement(div);
253 | var $ring = createElement(div);
254 | var $ringShape = createElement("div");
255 | var $top = createElement(div);
256 | var $bottom = createElement(div);
257 | // 添加 CSS 类
258 | addElementClass($state, "state");
259 | addElementClass($state, stateName);
260 | addElementClass($center, "center");
261 | addElementClass($ring, "ring");
262 | addElementClass($ringShape, "ring-shape");
263 | addElementClass($top, "top");
264 | addElementClass($bottom, "bottom");
265 | // 摆放容器
266 | appendChild(hotBox.$element, $state);
267 | appendChild($state, $ringShape);
268 | appendChild($state, $center);
269 | appendChild($state, $ring);
270 | appendChild($state, $top);
271 | appendChild($state, $bottom);
272 | // 记住状态名称
273 | this.name = stateName;
274 | // 五种按钮:中心,圆环,上栏,下栏,幕后
275 | var buttons = {
276 | center: null,
277 | ring: [],
278 | top: [],
279 | bottom: [],
280 | behind: []
281 | };
282 | var allButtons = [];
283 | var selectedButton = null;
284 | var pressedButton = null;
285 | var stateActived = false;
286 | // 布局,添加按钮后,标记需要布局
287 | var needLayout = true;
288 | function layout() {
289 | var radius = buttons.ring.length * 15;
290 | layoutRing(radius);
291 | layoutTop(radius);
292 | layoutBottom(radius);
293 | indexPosition();
294 | needLayout = false;
295 | function layoutRing(radius) {
296 | var ring = buttons.ring;
297 | var step = 2 * Math.PI / ring.length;
298 | if (buttons.center) {
299 | buttons.center.indexedPosition = [ 0, 0 ];
300 | }
301 | $ringShape.style.marginLeft = $ringShape.style.marginTop = -radius + "px";
302 | $ringShape.style.width = $ringShape.style.height = radius + radius + "px";
303 | var $button, angle, x, y;
304 | for (var i = 0; i < ring.length; i++) {
305 | $button = ring[i].$button;
306 | angle = step * i - Math.PI / 2;
307 | x = radius * Math.cos(angle);
308 | y = radius * Math.sin(angle);
309 | ring[i].indexedPosition = [ x, y ];
310 | $button.style.left = x + "px";
311 | $button.style.top = y + "px";
312 | }
313 | }
314 | function layoutTop(radius) {
315 | var xOffset = -$top.clientWidth / 2;
316 | var yOffset = -radius * 2 - $top.clientHeight / 2;
317 | $top.style.marginLeft = xOffset + "px";
318 | $top.style.marginTop = yOffset + "px";
319 | buttons.top.forEach(function(topButton) {
320 | var $button = topButton.$button;
321 | topButton.indexedPosition = [ xOffset + $button.offsetLeft + $button.clientWidth / 2, yOffset ];
322 | });
323 | }
324 | function layoutBottom(radius) {
325 | var xOffset = -$bottom.clientWidth / 2;
326 | var yOffset = radius * 2 - $bottom.clientHeight / 2;
327 | $bottom.style.marginLeft = xOffset + "px";
328 | $bottom.style.marginTop = yOffset + "px";
329 | buttons.bottom.forEach(function(bottomButton) {
330 | var $button = bottomButton.$button;
331 | bottomButton.indexedPosition = [ xOffset + $button.offsetLeft + $button.clientWidth / 2, yOffset ];
332 | });
333 | }
334 | function indexPosition() {
335 | var positionedButtons = allButtons.filter(function(button) {
336 | return button.indexedPosition;
337 | });
338 | positionedButtons.forEach(findNeightbour);
339 | function findNeightbour(button) {
340 | var neighbor = {};
341 | var coef = 0;
342 | var minCoef = {};
343 | var homePosition = button.indexedPosition;
344 | var candidatePosition, dx, dy, ds;
345 | var possible, dir;
346 | var abs = Math.abs;
347 | positionedButtons.forEach(function(candidate) {
348 | if (button == candidate) return;
349 | candidatePosition = candidate.indexedPosition;
350 | possible = [];
351 | dx = candidatePosition[0] - homePosition[0];
352 | dy = candidatePosition[1] - homePosition[1];
353 | ds = Math.sqrt(dx * dx + dy * dy);
354 | if (abs(dx) > 2) {
355 | possible.push(dx > 0 ? "right" : "left");
356 | possible.push(ds + abs(dy));
357 | }
358 | if (abs(dy) > 2) {
359 | possible.push(dy > 0 ? "down" : "up");
360 | possible.push(ds + abs(dx));
361 | }
362 | while (possible.length) {
363 | dir = possible.shift();
364 | coef = possible.shift();
365 | if (!neighbor[dir] || coef < minCoef[dir]) {
366 | neighbor[dir] = candidate;
367 | minCoef[dir] = coef;
368 | }
369 | }
370 | });
371 | button.neighbor = neighbor;
372 | }
373 | }
374 | }
375 | function alwaysEnable() {
376 | return true;
377 | }
378 | // 为状态创建按钮
379 | function createButton(option) {
380 | var $button = createElement(div);
381 | addElementClass($button, "button");
382 | var render = option.render || defaultButtonRender;
383 | $button.innerHTML = render(format, option);
384 | switch (option.position) {
385 | case "center":
386 | appendChild($center, $button);
387 | break;
388 |
389 | case "ring":
390 | appendChild($ring, $button);
391 | break;
392 |
393 | case "top":
394 | appendChild($top, $button);
395 | break;
396 |
397 | case "bottom":
398 | appendChild($bottom, $button);
399 | break;
400 | }
401 | return {
402 | action: option.action,
403 | enable: option.enable || alwaysEnable,
404 | beforeShow: option.beforeShow,
405 | key: option.key,
406 | next: option.next,
407 | label: option.label,
408 | data: option.data || null,
409 | $button: $button
410 | };
411 | }
412 | // 默认按钮渲染
413 | function defaultButtonRender(format, option) {
414 | return format('{label}{key}', {
415 | label: option.label,
416 | key: option.key && option.key.split("|")[0]
417 | });
418 | }
419 | // 为当前状态添加按钮
420 | this.button = function(option) {
421 | var button = createButton(option);
422 | if (option.position == "center") {
423 | buttons.center = button;
424 | } else if (buttons[option.position]) {
425 | buttons[option.position].push(button);
426 | }
427 | allButtons.push(button);
428 | needLayout = true;
429 | };
430 | function activeState(position) {
431 | position = position || {
432 | x: hotBox.$container.clientWidth / 2,
433 | y: hotBox.$container.clientHeight / 2
434 | };
435 | if (position) {
436 | $state.style.left = position.x + "px";
437 | $state.style.top = position.y + "px";
438 | }
439 | allButtons.forEach(function(button) {
440 | var $button = button.$button;
441 | if ($button) {
442 | $button.classList[button.enable() ? "add" : "remove"]("enabled");
443 | }
444 | if (button.beforeShow) {
445 | button.beforeShow();
446 | }
447 | });
448 | addElementClass($state, STATE_ACTIVE_CLASS);
449 | if (needLayout) {
450 | layout();
451 | }
452 | if (!selectedButton) {
453 | select(buttons.center || buttons.ring[0] || buttons.top[0] || buttons.bottom[0]);
454 | }
455 | stateActived = true;
456 | }
457 | function deactiveState() {
458 | removeElementClass($state, STATE_ACTIVE_CLASS);
459 | select(null);
460 | stateActived = false;
461 | }
462 | // 激活当前状态
463 | this.active = activeState;
464 | // 反激活当前状态
465 | this.deactive = deactiveState;
466 | function press(button) {
467 | if (pressedButton && pressedButton.$button) {
468 | removeElementClass(pressedButton.$button, BUTTON_PRESSED_CLASS);
469 | }
470 | pressedButton = button;
471 | if (pressedButton && pressedButton.$button) {
472 | addElementClass(pressedButton.$button, BUTTON_PRESSED_CLASS);
473 | }
474 | }
475 | function select(button) {
476 | if (selectedButton && selectedButton.$button) {
477 | if (selectedButton.$button) {
478 | removeElementClass(selectedButton.$button, BUTTON_SELECTED_CLASS);
479 | }
480 | }
481 | selectedButton = button;
482 | if (selectedButton && selectedButton.$button) {
483 | addElementClass(selectedButton.$button, BUTTON_SELECTED_CLASS);
484 | }
485 | }
486 | $state.onmouseup = function(e) {
487 | if (e.button) return;
488 | var target = e.target;
489 | while (target && target != $state) {
490 | if (target.classList.contains("button")) {
491 | allButtons.forEach(function(button) {
492 | if (button.$button == target) {
493 | execute(button);
494 | }
495 | });
496 | }
497 | target = target.parentNode;
498 | }
499 | };
500 | this.handleKeyEvent = function(e) {
501 | var handleResult = null;
502 | /**
503 | * @Desc: 搜狗浏览器下esc只触发keyup,因此做兼容性处理
504 | * @Editor: Naixor
505 | * @Date: 2015.09.14
506 | */
507 | if (hotBox.browser.sg) {
508 | if (e.isKey("esc")) {
509 | if (pressedButton) {
510 | // 若存在已经按下的按钮,则取消操作
511 | if (!e.isKey(pressedButton.key)) {
512 | // the button is not esc
513 | press(null);
514 | }
515 | } else {
516 | hotBox.active("back", hotBox.position);
517 | }
518 | return "back";
519 | }
520 | }
521 | if (e.keydown || hotBox.isIME && e.keyup) {
522 | allButtons.forEach(function(button) {
523 | if (button.enable() && e.isKey(button.key)) {
524 | if (stateActived || hotBox.hintDeactiveMainState) {
525 | select(button);
526 | press(button);
527 | handleResult = "buttonpress";
528 | // 如果是 keyup 事件触发的,因为没有后续的按键事件,所以就直接执行
529 | if (e.keyup) {
530 | execute(button);
531 | handleResult = "execute";
532 | return handleResult;
533 | }
534 | } else {
535 | execute(button);
536 | handleResult = "execute";
537 | }
538 | e.preventDefault();
539 | e.stopPropagation();
540 | if (!stateActived && hotBox.hintDeactiveMainState) {
541 | hotBox.active(stateName, hotBox.position);
542 | }
543 | }
544 | });
545 | if (stateActived) {
546 | if (e.isKey("esc")) {
547 | if (pressedButton) {
548 | // 若存在已经按下的按钮,则取消操作
549 | if (!e.isKey(pressedButton.key)) {
550 | // the button is not esc
551 | press(null);
552 | }
553 | } else {
554 | hotBox.active("back", hotBox.position);
555 | }
556 | return "back";
557 | }
558 | [ "up", "down", "left", "right" ].forEach(function(dir) {
559 | if (!e.isKey(dir)) return;
560 | if (!selectedButton) {
561 | select(buttons.center || buttons.ring[0] || buttons.top[0] || buttons.bottom[0]);
562 | return;
563 | }
564 | var neighbor = selectedButton.neighbor[dir];
565 | while (neighbor && !neighbor.enable()) {
566 | neighbor = neighbor.neighbor[dir];
567 | }
568 | if (neighbor) {
569 | select(neighbor);
570 | }
571 | handleResult = "navigate";
572 | });
573 | // 若是由 keyup 触发的,则直接执行选中的按钮
574 | if (e.isKey("space") && e.keyup) {
575 | execute(selectedButton);
576 | e.preventDefault();
577 | e.stopPropagation();
578 | handleResult = "execute";
579 | } else if (e.isKey("space") && selectedButton) {
580 | press(selectedButton);
581 | handleResult = "buttonpress";
582 | } else if (pressedButton && pressedButton != selectedButton) {
583 | press(null);
584 | handleResult = "selectcancel";
585 | }
586 | }
587 | } else if (e.keyup && (stateActived || !hotBox.hintDeactiveMainState)) {
588 | if (pressedButton) {
589 | if (e.isKey("space") && selectedButton == pressedButton || e.isKey(pressedButton.key)) {
590 | execute(pressedButton);
591 | e.preventDefault();
592 | e.stopPropagation();
593 | handleResult = "execute";
594 | }
595 | }
596 | }
597 | /*
598 | * Add by zhangbobell 2015.09.06
599 | * 增加了下面这一个判断因为 safari 下开启输入法后,所有的 keydown 的 keycode 都为 229,
600 | * 只能以 keyup 的 keycode 进行判断
601 | * */
602 | hotBox.isIME = e.keyCode == 229 && e.keydown;
603 | return handleResult;
604 | };
605 | function execute(button) {
606 | if (button) {
607 | if (!button.enable || button.enable()) {
608 | if (button.action) button.action(button);
609 | hotBox.active(button.next || IDLE, hotBox.position);
610 | }
611 | press(null);
612 | select(null);
613 | }
614 | }
615 | }
616 | module.exports = HotBox;
617 | }
618 | };
619 |
620 | //src/key.js
621 | _p[2] = {
622 | value: function(require, exports, module) {
623 | var keymap = _p.r(4);
624 | var CTRL_MASK = 4096;
625 | var ALT_MASK = 8192;
626 | var SHIFT_MASK = 16384;
627 | function hash(unknown) {
628 | if (typeof unknown == "string") {
629 | return hashKeyExpression(unknown);
630 | }
631 | return hashKeyEvent(unknown);
632 | }
633 | function is(a, b) {
634 | return a && b && hash(a) == hash(b);
635 | }
636 | exports.hash = hash;
637 | exports.is = is;
638 | function hashKeyEvent(keyEvent) {
639 | var hashCode = 0;
640 | if (keyEvent.ctrlKey || keyEvent.metaKey) {
641 | hashCode |= CTRL_MASK;
642 | }
643 | if (keyEvent.altKey) {
644 | hashCode |= ALT_MASK;
645 | }
646 | if (keyEvent.shiftKey) {
647 | hashCode |= SHIFT_MASK;
648 | }
649 | // Shift, Control, Alt KeyCode ignored.
650 | if ([ 16, 17, 18, 91 ].indexOf(keyEvent.keyCode) == -1) {
651 | hashCode |= keyEvent.keyCode;
652 | }
653 | return hashCode;
654 | }
655 | function hashKeyExpression(keyExpression) {
656 | var hashCode = 0;
657 | keyExpression.toLowerCase().split(/\s*\+\s*/).forEach(function(name) {
658 | switch (name) {
659 | case "ctrl":
660 | case "cmd":
661 | hashCode |= CTRL_MASK;
662 | break;
663 |
664 | case "alt":
665 | hashCode |= ALT_MASK;
666 | break;
667 |
668 | case "shift":
669 | hashCode |= SHIFT_MASK;
670 | break;
671 |
672 | default:
673 | hashCode |= keymap[name];
674 | }
675 | });
676 | return hashCode;
677 | }
678 | }
679 | };
680 |
681 | //src/keycontrol.js
682 | _p[3] = {
683 | value: function(require, exports, module) {
684 | var key = _p.r(2);
685 | var FOCUS_CLASS = "hotbox-focus";
686 | var RECEIVER_CLASS = "hotbox-key-receiver";
687 | function KeyControl(hotbox) {
688 | var _this = this;
689 | var _receiver;
690 | var _actived = true;
691 | var _receiverIsSelfCreated = false;
692 | var $container = hotbox.$container;
693 | _createReceiver();
694 | _bindReceiver();
695 | _bindContainer();
696 | _active();
697 | function _createReceiver() {
698 | _receiver = document.createElement("input");
699 | _receiver.classList.add(RECEIVER_CLASS);
700 | $container.appendChild(_receiver);
701 | _receiverIsSelfCreated = true;
702 | }
703 | function _bindReceiver() {
704 | _receiver.onkeyup = _handle;
705 | _receiver.onkeypress = _handle;
706 | _receiver.onkeydown = _handle;
707 | _receiver.onfocus = _active;
708 | _receiver.onblur = _deactive;
709 | if (_receiverIsSelfCreated) {
710 | _receiver.oninput = function(e) {
711 | _receiver.value = null;
712 | };
713 | }
714 | }
715 | function _bindContainer() {
716 | $container.onmousedown = function(e) {
717 | _active();
718 | e.preventDefault();
719 | };
720 | }
721 | function _handle(keyEvent) {
722 | if (!_actived) return;
723 | hotbox.dispatch(keyEvent);
724 | }
725 | function _active() {
726 | _receiver.select();
727 | _receiver.focus();
728 | _actived = true;
729 | $container.classList.add(FOCUS_CLASS);
730 | }
731 | function _deactive() {
732 | _receiver.blur();
733 | _actived = false;
734 | $container.classList.remove(FOCUS_CLASS);
735 | }
736 | this.handle = _handle;
737 | this.active = _active;
738 | this.deactive = _deactive;
739 | }
740 | module.exports = KeyControl;
741 | }
742 | };
743 |
744 | //src/keymap.js
745 | _p[4] = {
746 | value: function(require, exports, module) {
747 | var keymap = {
748 | Shift: 16,
749 | Control: 17,
750 | Alt: 18,
751 | CapsLock: 20,
752 | BackSpace: 8,
753 | Tab: 9,
754 | Enter: 13,
755 | Esc: 27,
756 | Space: 32,
757 | PageUp: 33,
758 | PageDown: 34,
759 | End: 35,
760 | Home: 36,
761 | Insert: 45,
762 | Left: 37,
763 | Up: 38,
764 | Right: 39,
765 | Down: 40,
766 | Direction: {
767 | 37: 1,
768 | 38: 1,
769 | 39: 1,
770 | 40: 1
771 | },
772 | Delete: 46,
773 | NumLock: 144,
774 | Cmd: 91,
775 | CmdFF: 224,
776 | F1: 112,
777 | F2: 113,
778 | F3: 114,
779 | F4: 115,
780 | F5: 116,
781 | F6: 117,
782 | F7: 118,
783 | F8: 119,
784 | F9: 120,
785 | F10: 121,
786 | F11: 122,
787 | F12: 123,
788 | "`": 192,
789 | "=": 187,
790 | "-": 189,
791 | "/": 191,
792 | ".": 190
793 | };
794 | // 小写适配
795 | for (var key in keymap) {
796 | if (keymap.hasOwnProperty(key)) {
797 | keymap[key.toLowerCase()] = keymap[key];
798 | }
799 | }
800 | var aKeyCode = 65;
801 | var aCharCode = "a".charCodeAt(0);
802 | // letters
803 | "abcdefghijklmnopqrstuvwxyz".split("").forEach(function(letter) {
804 | keymap[letter] = aKeyCode + (letter.charCodeAt(0) - aCharCode);
805 | });
806 | // numbers
807 | var n = 9;
808 | do {
809 | keymap[n.toString()] = n + 48;
810 | } while (n--);
811 | module.exports = keymap;
812 | }
813 | };
814 |
815 | var moduleMapping = {
816 | expose: 0
817 | };
818 |
819 | function use(name) {
820 | _p.r([ moduleMapping[name] ]);
821 | }
822 | use('expose');
823 | })();
--------------------------------------------------------------------------------
/hotbox.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * ====================================================
3 | * Hot Box UI - v1.0.15 - 2017-05-05
4 | * https://github.com/fex-team/hotbox
5 | * GitHub: https://github.com/fex-team/hotbox.git
6 | * Copyright (c) 2017 Baidu FEX; Licensed BSD
7 | * ====================================================
8 | */
9 |
10 | !function(){function a(a){b.r([c[a]])}var b={r:function(a){if(b[a].inited)return b[a].value;if("function"!=typeof b[a].value)return b[a].inited=!0,b[a].value;var c={exports:{}},d=b[a].value(null,c.exports,c);if(b[a].inited=!0,b[a].value=d,void 0!==d)return d;for(var e in c.exports)if(c.exports.hasOwnProperty(e))return b[a].inited=!0,b[a].value=c.exports,c.exports}};b[0]={value:function(a,c,d){d.exports=window.HotBox=b.r(1)}},b[1]={value:function(a,c,d){function e(a){return document.createElement(a)}function f(a,b){a.classList.add(b)}function g(a,b){a.classList.remove(b)}function h(a,b){a.appendChild(b)}function i(a,b){return"object"!=typeof b&&(b=[].slice.apply(arguments,1)),String(a).replace(/\{(\w+)\}/g,function(a,c){return b[c]||a})}function j(a){function b(a){return q?void q.active():(a=a||m,q=new a(v),q.active(),p.onmousedown=function(a){a.stopPropagation(),a.preventDefault()},v)}function c(b){var c=b.type.toLowerCase();if(b.keyHash=l.hash(b),b.isKey=function(a){if(!a)return!1;for(var c=a.split(/\s*\|\s*/);c.length;)if(b.keyHash==l.hash(c.shift()))return!0;return!1},b[c]=!0,b.keyup&&v.activeKey&&b.isKey(v.activeKey)&&t==n&&s)return void g("main",{x:a.clientWidth/2,y:a.clientHeight/2});var d=t==n?s:t;if(d){var e=d.handleKeyEvent(b);return"function"==typeof v.onkeyevent&&(b.handleResult=e,v.onkeyevent(b,e)),e}return null}function d(a){if(!a)return t;if(a==n)throw new Error("Can not define or use the `idle` state.");return r[a]=r[a]||new k(this,a),"main"==a&&(s=r[a]),r[a]}function g(a,b){if(v.position=b,a==n)t!=n&&(u.shift().deactive(),u=[]),t=n;else if("back"==a)t!=n&&(t.deactive(),u.shift(),t=u[0],t?t.active():t="idle");else{t!=n&&t.deactive();var c=r[a];u.unshift(c),"function"==typeof v.position&&(b=v.position(b)),c.active(b),t=c}}function i(a){v._parentFSM=a}function j(){return v._parentFSM}if("string"==typeof a&&(a=document.querySelector(a)),!(a&&a instanceof HTMLElement))throw new Error("No container or not invalid container for hot box");var p=e(o);f(p,"hotbox"),h(a,p),this.$element=p,this.$container=a,this.isIME=!1,this.browser={sg:/se[\s\S]+metasr/.test(navigator.userAgent.toLowerCase())},this._parentFSM={},this.position={};var q,r={},s=null,t=n,u=[],v=this;this.control=b,this.state=d,this.active=g,this.dispatch=c,this.setParentFSM=i,this.getParentFSM=j,this.activeKey="space",this.actionKey="space"}function k(a,b){function c(){function a(a){var b=B.ring,c=2*Math.PI/b.length;B.center&&(B.center.indexedPosition=[0,0]),y.style.marginLeft=y.style.marginTop=-a+"px",y.style.width=y.style.height=a+a+"px";for(var d,e,f,g,h=0;h2&&(g.push(d>0?"right":"left"),g.push(f+m(e))),m(e)>2&&(g.push(e>0?"down":"up"),g.push(f+m(d)));g.length;)h=g.shift(),j=g.shift(),(!i[h]||j{label}{key}',{label:b.label,key:b.key&&b.key.split("|")[0]})}function l(b){b=b||{x:a.$container.clientWidth/2,y:a.$container.clientHeight/2},b&&(v.style.left=b.x+"px",v.style.top=b.y+"px"),C.forEach(function(a){var b=a.$button;b&&b.classList[a.enable()?"add":"remove"]("enabled"),a.beforeShow&&a.beforeShow()}),f(v,u),G&&c(),D||q(B.center||B.ring[0]||B.top[0]||B.bottom[0]),F=!0}function m(){g(v,u),q(null),F=!1}function p(a){E&&E.$button&&g(E.$button,t),E=a,E&&E.$button&&f(E.$button,t)}function q(a){D&&D.$button&&D.$button&&g(D.$button,s),D=a,D&&D.$button&&f(D.$button,s)}function r(b){b&&((!b.enable||b.enable())&&(b.action&&b.action(b),a.active(b.next||n,a.position)),p(null),q(null))}var s="selected",t="pressed",u="active",v=e(o),w=e(o),x=e(o),y=e("div"),z=e(o),A=e(o);f(v,"state"),f(v,b),f(w,"center"),f(x,"ring"),f(y,"ring-shape"),f(z,"top"),f(A,"bottom"),h(a.$element,v),h(v,y),h(v,w),h(v,x),h(v,z),h(v,A),this.name=b;var B={center:null,ring:[],top:[],bottom:[],behind:[]},C=[],D=null,E=null,F=!1,G=!0;this.button=function(a){var b=j(a);"center"==a.position?B.center=b:B[a.position]&&B[a.position].push(b),C.push(b),G=!0},this.active=l,this.deactive=m,v.onmouseup=function(a){if(!a.button)for(var b=a.target;b&&b!=v;)b.classList.contains("button")&&C.forEach(function(a){a.$button==b&&r(a)}),b=b.parentNode},this.handleKeyEvent=function(c){var d=null;if(a.browser.sg&&c.isKey("esc"))return E?c.isKey(E.key)||p(null):a.active("back",a.position),"back";if(c.keydown||a.isIME&&c.keyup){if(C.forEach(function(e){if(e.enable()&&c.isKey(e.key)){if(F||a.hintDeactiveMainState){if(q(e),p(e),d="buttonpress",c.keyup)return r(e),d="execute"}else r(e),d="execute";c.preventDefault(),c.stopPropagation(),!F&&a.hintDeactiveMainState&&a.active(b,a.position)}}),F){if(c.isKey("esc"))return E?c.isKey(E.key)||p(null):a.active("back",a.position),"back";["up","down","left","right"].forEach(function(a){if(c.isKey(a)){if(!D)return void q(B.center||B.ring[0]||B.top[0]||B.bottom[0]);for(var b=D.neighbor[a];b&&!b.enable();)b=b.neighbor[a];b&&q(b),d="navigate"}}),c.isKey("space")&&c.keyup?(r(D),c.preventDefault(),c.stopPropagation(),d="execute"):c.isKey("space")&&D?(p(D),d="buttonpress"):E&&E!=D&&(p(null),d="selectcancel")}}else!c.keyup||!F&&a.hintDeactiveMainState||E&&(c.isKey("space")&&D==E||c.isKey(E.key))&&(r(E),c.preventDefault(),c.stopPropagation(),d="execute");return a.isIME=229==c.keyCode&&c.keydown,d}}var l=b.r(2),m=b.r(3),n=j.STATE_IDLE="idle",o="div";d.exports=j}},b[2]={value:function(a,c,d){function e(a){return"string"==typeof a?h(a):g(a)}function f(a,b){return a&&b&&e(a)==e(b)}function g(a){var b=0;return(a.ctrlKey||a.metaKey)&&(b|=j),a.altKey&&(b|=k),a.shiftKey&&(b|=l),-1==[16,17,18,91].indexOf(a.keyCode)&&(b|=a.keyCode),b}function h(a){var b=0;return a.toLowerCase().split(/\s*\+\s*/).forEach(function(a){switch(a){case"ctrl":case"cmd":b|=j;break;case"alt":b|=k;break;case"shift":b|=l;break;default:b|=i[a]}}),b}var i=b.r(4),j=4096,k=8192,l=16384;c.hash=e,c.is=f}},b[3]={value:function(a,c,d){function e(a){function b(){j=document.createElement("input"),j.classList.add(g),m.appendChild(j),l=!0}function c(){j.onkeyup=e,j.onkeypress=e,j.onkeydown=e,j.onfocus=h,j.onblur=i,l&&(j.oninput=function(a){j.value=null})}function d(){m.onmousedown=function(a){h(),a.preventDefault()}}function e(b){k&&a.dispatch(b)}function h(){j.select(),j.focus(),k=!0,m.classList.add(f)}function i(){j.blur(),k=!1,m.classList.remove(f)}var j,k=!0,l=!1,m=a.$container;b(),c(),d(),h(),this.handle=e,this.active=h,this.deactive=i}var f=(b.r(2),"hotbox-focus"),g="hotbox-key-receiver";d.exports=e}},b[4]={value:function(a,b,c){var d={Shift:16,Control:17,Alt:18,CapsLock:20,BackSpace:8,Tab:9,Enter:13,Esc:27,Space:32,PageUp:33,PageDown:34,End:35,Home:36,Insert:45,Left:37,Up:38,Right:39,Down:40,Direction:{37:1,38:1,39:1,40:1},Delete:46,NumLock:144,Cmd:91,CmdFF:224,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,"`":192,"=":187,"-":189,"/":191,".":190};for(var e in d)d.hasOwnProperty(e)&&(d[e.toLowerCase()]=d[e]);var f=65,g="a".charCodeAt(0);"abcdefghijklmnopqrstuvwxyz".split("").forEach(function(a){d[a]=f+(a.charCodeAt(0)-g)});var h=9;do d[h.toString()]=h+48;while(h--);c.exports=d}};var c={expose:0};a("expose")}();
--------------------------------------------------------------------------------
/less/hotbox.less:
--------------------------------------------------------------------------------
1 |
2 | .hotbox {
3 | font-family: Arial, "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
4 | position: absolute;
5 | left: 0;
6 | top: 0;
7 | overflow: visible;
8 | .state {
9 | position: absolute;
10 | overflow: visible;
11 | display: none;
12 | .center, .ring {
13 | .button {
14 | position: absolute;
15 | width: 70px;
16 | height: 70px;
17 | margin-left: -35px;
18 | margin-top: -35px;
19 | border-radius: 100%;
20 | box-shadow: 0 0 30px rgba(0, 0, 0, .3);
21 | }
22 | .label, .key {
23 | display: block;
24 | text-align: center;
25 | line-height: 1.4em;
26 | vertical-align: middle;
27 | }
28 | .label {
29 | font-size: 16px;
30 | margin-top: 17px;
31 | color: black;
32 | font-weight: normal;
33 | line-height: 1em;
34 | }
35 | .key {
36 | font-size: 12px;
37 | color: #999;
38 | }
39 | }
40 | .ring-shape {
41 | position: absolute;
42 | left: -25px;
43 | top: -25px;
44 | border: 25px solid rgba(0, 0, 0, .3);
45 | border-radius: 100%;
46 | box-sizing: content-box;
47 | }
48 | .top, .bottom {
49 | position: absolute;
50 | white-space: nowrap;
51 | .button {
52 | display: inline-block;
53 | padding: 8px 15px;
54 | margin: 0 10px;
55 | border-radius: 15px;
56 | box-shadow: 0 0 30px rgba(0, 0, 0, .3);
57 | position: relative;
58 |
59 | .label {
60 | font-size: 14px;
61 | line-height: 14px;
62 | vertical-align: middle;
63 | color: black;
64 | line-height: 1em;
65 | }
66 | .key {
67 | font-size: 12px;
68 | line-height: 12px;
69 | vertical-align: middle;
70 | color: #999;
71 | margin-left: 3px;
72 | &:before {
73 | content: '(';
74 | }
75 | &:after {
76 | content: ')';
77 | }
78 | }
79 | }
80 | }
81 | .button {
82 | background: #F9F9F9;
83 | overflow: hidden;
84 | cursor: default;
85 |
86 | .key, .label {
87 | opacity: 0.3;
88 | }
89 | }
90 | .button.enabled {
91 | background: white;
92 |
93 | .key, .label {
94 | opacity: 1;
95 | }
96 |
97 | &:hover {
98 | background: lighten(rgb(228, 93, 92), 5%);
99 | .label {
100 | color: white;
101 | }
102 | .key {
103 | color: lighten(rgb(228, 93, 92), 30%);
104 | }
105 | }
106 | &.selected {
107 | -webkit-animation: selected .1s ease;
108 | background: rgb(228, 93, 92);
109 | .label {
110 | color: white;
111 | }
112 | .key {
113 | color: lighten(rgb(228, 93, 92), 30%);
114 | }
115 | }
116 | &.pressed, &:active {
117 | background: #FF974D;
118 | .label {
119 | color: white;
120 | }
121 | .key {
122 | color: lighten(#FF974D, 30%);
123 | }
124 | }
125 | }
126 | }
127 | .state.active {
128 | display: block;
129 | }
130 | }
131 |
132 | @-webkit-keyframes selected {
133 | 0% { transform: scale(1); }
134 | 50% { transform: scale(1.1); }
135 | 100% { transform: scale(1); }
136 | }
137 |
138 | .hotbox-key-receiver {
139 | position: absolute;
140 | left: -999999px;
141 | top: -999999px;
142 | width: 20px;
143 | height: 20px;
144 | outline: none;
145 | margin: 0;
146 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hotbox",
3 | "title": "Hot Box UI",
4 | "description": "Efficiency And Flexible Editor UI",
5 | "version": "1.0.15",
6 | "homepage": "https://github.com/fex-team/hotbox",
7 | "author": {
8 | "name": "Baidu FEX",
9 | "url": "https://github.com/fex-team"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/fex-team/hotbox.git"
14 | },
15 | "keywords": [
16 | "ui",
17 | "hotbox",
18 | "efficiency",
19 | "flexible",
20 | "javascript",
21 | "library"
22 | ],
23 | "bugs": {
24 | "url": "https://github.com/fex-team/hotbox/issues"
25 | },
26 | "main": "src/mog.js",
27 | "licenses": [
28 | {
29 | "type": "BSD",
30 | "url": "https://github.com/fex-team/hotbox/blob/dev/LICENSE"
31 | }
32 | ],
33 | "dependencies": {},
34 | "devDependencies": {
35 | "grunt": "^0.4.5",
36 | "grunt-autoprefixer": "^2.0.0",
37 | "grunt-contrib-concat": "^0.5.0",
38 | "grunt-contrib-less": "^0.12.0",
39 | "grunt-contrib-uglify": "~0.4.0",
40 | "grunt-contrib-watch": "^0.6.1",
41 | "grunt-module-dependence": "~0.2.0",
42 | "grunt-replace": "^0.8.0",
43 | "less": "~2.1.1",
44 | "watch": "~0.13.0"
45 | },
46 | "scripts": {
47 | "watch-less": "watch \"lessc --source-map less/hotbox.less hotbox.css\" \"less\""
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/snap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fex-team/hotbox/440e62a01ae48bc0d5d6d6ed5379489a830fa967/snap.png
--------------------------------------------------------------------------------
/src/expose.js:
--------------------------------------------------------------------------------
1 | define('expose', function(require, exports, module) {
2 | module.exports = window.HotBox = require('./hotbox');
3 | });
--------------------------------------------------------------------------------
/src/hotbox.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module) {
2 | var key = require('./key');
3 | var KeyControl = require('./keycontrol');
4 |
5 | /**** Dom Utils ****/
6 | function createElement(name) {
7 | return document.createElement(name);
8 | }
9 |
10 | function setElementAttribute(element, name, value) {
11 | element.setAttribute(name, value);
12 | }
13 |
14 | function getElementAttribute(element, name) {
15 | return element.getAttribute(name);
16 | }
17 |
18 | function addElementClass(element, name) {
19 | element.classList.add(name);
20 | }
21 |
22 | function removeElementClass(element, name) {
23 | element.classList.remove(name);
24 | }
25 |
26 | function appendChild(parent, child) {
27 | parent.appendChild(child);
28 | }
29 | /*******************/
30 |
31 | var IDLE = HotBox.STATE_IDLE = 'idle';
32 | var div = 'div';
33 |
34 | /**
35 | * Simple Formatter
36 | */
37 | function format(template, args) {
38 | if (typeof(args) != 'object') {
39 | args = [].slice.apply(arguments, 1);
40 | }
41 | return String(template).replace(/\{(\w+)\}/g, function(match, name) {
42 | return args[name] || match;
43 | });
44 | }
45 |
46 | /**
47 | * Hot Box Class
48 | */
49 | function HotBox($container) {
50 | if (typeof($container) == 'string') {
51 | $container = document.querySelector($container);
52 | }
53 | if (!$container || !($container instanceof HTMLElement)) {
54 | throw new Error('No container or not invalid container for hot box');
55 | }
56 |
57 | // 创建 HotBox Dom 解构
58 | var $hotBox = createElement(div);
59 | addElementClass($hotBox, 'hotbox');
60 | appendChild($container, $hotBox);
61 |
62 | // 保存 Dom 解构和父容器
63 | this.$element = $hotBox;
64 | this.$container = $container;
65 |
66 | // 标示是否是输入法状态
67 | this.isIME = false;
68 |
69 | /**
70 | * @Desc: 增加一个browser用于判断浏览器类型,方便解决兼容性问题
71 | * @Editor: Naixor
72 | * @Date: 2015.09.14
73 | */
74 | this.browser = {
75 | sg: /se[\s\S]+metasr/.test(navigator.userAgent.toLowerCase())
76 | };
77 |
78 | /*
79 | * added by zhangbobell
80 | * 2015.09.22
81 | * 增加父状态机,以解决在父 FSM 下状态控制的问题,最好的解决办法是增加一个函数队列
82 | * 将其中的函数一起执行。//TODO
83 | * */
84 | this._parentFSM = {};
85 |
86 | // 记录位置
87 | this.position = {};
88 |
89 | // 已定义的状态(string => HotBoxState)
90 | var _states = {};
91 |
92 | // 主状态(HotBoxState)
93 | var _mainState = null;
94 |
95 | // 当前状态(HotBoxState)
96 | var _currentState = IDLE;
97 |
98 | // 当前状态堆栈
99 | var _stateStack = [];
100 |
101 | // 实例引用
102 | var _this = this;
103 | var _controler;
104 |
105 | /**
106 | * Controller: {
107 | * constructor(hotbox: HotBox),
108 | * active: () => void
109 | * }
110 | */
111 | function _control(Controller) {
112 | if (_controler) {
113 | _controler.active();
114 | return;
115 | }
116 |
117 | Controller = Controller || KeyControl;
118 |
119 | _controler = new Controller(_this);
120 | _controler.active();
121 |
122 | $hotBox.onmousedown = function(e) {
123 | e.stopPropagation();
124 | e.preventDefault();
125 | };
126 |
127 | return _this;
128 | }
129 |
130 | function _dispatchKey(e) {
131 | var type = e.type.toLowerCase();
132 | e.keyHash = key.hash(e);
133 | e.isKey = function(keyExpression) {
134 | if (!keyExpression) return false;
135 | var expressions = keyExpression.split(/\s*\|\s*/);
136 | while(expressions.length) {
137 | if (e.keyHash == key.hash(expressions.shift())) return true;
138 | }
139 | return false;
140 | };
141 | e[type] = true;
142 | // Boot: keyup and activeKey pressed on IDLE, active main state.
143 | if (e.keyup && _this.activeKey && e.isKey(_this.activeKey) && _currentState == IDLE && _mainState) {
144 | _activeState('main', {
145 | x: $container.clientWidth / 2,
146 | y: $container.clientHeight / 2
147 | });
148 | return;
149 | }
150 | var handleState = _currentState == IDLE ? _mainState : _currentState;
151 | if (handleState) {
152 | var handleResult = handleState.handleKeyEvent(e);
153 | if (typeof(_this.onkeyevent) == 'function') {
154 | e.handleResult = handleResult;
155 | _this.onkeyevent(e, handleResult);
156 | }
157 | return handleResult;
158 | }
159 | return null;
160 | }
161 |
162 | function _addState(name) {
163 | if (!name) return _currentState;
164 | if (name == IDLE) {
165 | throw new Error('Can not define or use the `idle` state.');
166 | }
167 | _states[name] = _states[name] || new HotBoxState(this, name);
168 | if (name == 'main') {
169 | _mainState = _states[name];
170 | }
171 | return _states[name];
172 | }
173 |
174 | function _activeState(name, position) {
175 | _this.position = position;
176 |
177 | // 回到 IDLE
178 | if (name == IDLE) {
179 | if (_currentState != IDLE) {
180 | _stateStack.shift().deactive();
181 | _stateStack = [];
182 | }
183 | _currentState = IDLE;
184 | }
185 | // 回退一个状态
186 | else if (name == 'back') {
187 | if (_currentState != IDLE) {
188 | _currentState.deactive();
189 | _stateStack.shift();
190 | _currentState = _stateStack[0];
191 | if (_currentState) {
192 | _currentState.active();
193 | } else {
194 | _currentState = 'idle';
195 | }
196 | }
197 | }
198 | // 切换到具体状态
199 | else {
200 | if (_currentState != IDLE) {
201 | _currentState.deactive();
202 | }
203 | var newState = _states[name];
204 | _stateStack.unshift(newState);
205 | if (typeof(_this.position) == 'function') {
206 | position = _this.position(position);
207 | }
208 | newState.active(position);
209 | _currentState = newState;
210 | }
211 | }
212 |
213 | function setParentFSM(fsm) {
214 | _this._parentFSM = fsm;
215 | }
216 |
217 | function getParentFSM() {
218 | return _this._parentFSM;
219 | }
220 |
221 | this.control = _control;
222 | this.state = _addState;
223 | this.active = _activeState;
224 | this.dispatch = _dispatchKey;
225 | this.setParentFSM = setParentFSM;
226 | this.getParentFSM = getParentFSM;
227 | this.activeKey = 'space';
228 | this.actionKey = 'space';
229 | }
230 |
231 | /**
232 | * 表示热盒某个状态,包含这些状态需要的 Dom 对象
233 | */
234 | function HotBoxState(hotBox, stateName) {
235 |
236 | var BUTTON_SELECTED_CLASS = 'selected';
237 | var BUTTON_PRESSED_CLASS = 'pressed';
238 | var STATE_ACTIVE_CLASS = 'active';
239 |
240 | // 状态容器
241 | var $state = createElement(div);
242 |
243 | // 四种可见的按钮容器
244 | var $center = createElement(div);
245 | var $ring = createElement(div);
246 | var $ringShape = createElement('div');
247 | var $top = createElement(div);
248 | var $bottom = createElement(div);
249 |
250 | // 添加 CSS 类
251 | addElementClass($state, 'state');
252 | addElementClass($state, stateName);
253 | addElementClass($center, 'center');
254 | addElementClass($ring, 'ring');
255 | addElementClass($ringShape, 'ring-shape');
256 | addElementClass($top, 'top');
257 | addElementClass($bottom, 'bottom');
258 |
259 | // 摆放容器
260 | appendChild(hotBox.$element, $state);
261 | appendChild($state, $ringShape);
262 | appendChild($state, $center);
263 | appendChild($state, $ring);
264 | appendChild($state, $top);
265 | appendChild($state, $bottom);
266 |
267 | // 记住状态名称
268 | this.name = stateName;
269 |
270 | // 五种按钮:中心,圆环,上栏,下栏,幕后
271 | var buttons = {
272 | center: null,
273 | ring: [],
274 | top: [],
275 | bottom: [],
276 | behind: []
277 | };
278 | var allButtons = [];
279 | var selectedButton = null;
280 | var pressedButton = null;
281 |
282 | var stateActived = false;
283 | // 布局,添加按钮后,标记需要布局
284 | var needLayout = true;
285 |
286 | function layout() {
287 | var radius = buttons.ring.length * 15;
288 | layoutRing(radius);
289 | layoutTop(radius);
290 | layoutBottom(radius);
291 | indexPosition();
292 | needLayout = false;
293 |
294 | function layoutRing(radius) {
295 | var ring = buttons.ring;
296 | var step = 2 * Math.PI / ring.length;
297 |
298 | if (buttons.center) {
299 | buttons.center.indexedPosition = [0, 0];
300 | }
301 |
302 | $ringShape.style.marginLeft = $ringShape.style.marginTop = -radius + 'px';
303 | $ringShape.style.width = $ringShape.style.height = (radius + radius) + 'px';
304 |
305 | var $button, angle, x, y;
306 | for (var i = 0; i < ring.length; i++) {
307 | $button = ring[i].$button;
308 | angle = step * i - Math.PI / 2;
309 | x = radius * Math.cos(angle);
310 | y = radius * Math.sin(angle);
311 | ring[i].indexedPosition = [x, y];
312 | $button.style.left = x + 'px';
313 | $button.style.top = y + 'px';
314 | }
315 | }
316 |
317 | function layoutTop(radius) {
318 | var xOffset = -$top.clientWidth / 2;
319 | var yOffset = -radius * 2 - $top.clientHeight / 2;
320 | $top.style.marginLeft = xOffset + 'px';
321 | $top.style.marginTop = yOffset + 'px';
322 | buttons.top.forEach(function(topButton) {
323 | var $button = topButton.$button;
324 | topButton.indexedPosition = [xOffset + $button.offsetLeft + $button.clientWidth / 2, yOffset];
325 | });
326 | }
327 | function layoutBottom(radius) {
328 | var xOffset = -$bottom.clientWidth / 2;
329 | var yOffset = radius * 2 - $bottom.clientHeight / 2;
330 | $bottom.style.marginLeft = xOffset + 'px';
331 | $bottom.style.marginTop = yOffset + 'px';
332 | buttons.bottom.forEach(function(bottomButton) {
333 | var $button = bottomButton.$button;
334 | bottomButton.indexedPosition = [xOffset + $button.offsetLeft + $button.clientWidth / 2, yOffset];
335 | });
336 | }
337 | function indexPosition() {
338 | var positionedButtons = allButtons.filter(function(button) {
339 | return button.indexedPosition;
340 | });
341 |
342 | positionedButtons.forEach(findNeightbour);
343 |
344 | function findNeightbour(button) {
345 | var neighbor = {};
346 | var coef = 0;
347 | var minCoef = {};
348 | var homePosition = button.indexedPosition;
349 | var candidatePosition, dx, dy, ds;
350 | var possible, dir;
351 | var abs = Math.abs;
352 |
353 | positionedButtons.forEach(function(candidate) {
354 | if (button == candidate) return;
355 |
356 | candidatePosition = candidate.indexedPosition;
357 |
358 | possible = [];
359 |
360 | dx = candidatePosition[0] - homePosition[0];
361 | dy = candidatePosition[1] - homePosition[1];
362 | ds = Math.sqrt(dx * dx + dy * dy);
363 |
364 | if (abs(dx) > 2) {
365 | possible.push(dx > 0 ? 'right' : 'left');
366 | possible.push(ds + abs(dy)); // coef for right/left neighbor
367 | }
368 | if (abs(dy) > 2) {
369 | possible.push(dy > 0 ? 'down' : 'up');
370 | possible.push(ds + abs(dx)); // coef for up/down neighbor
371 | }
372 |
373 | while (possible.length) {
374 | dir = possible.shift();
375 | coef = possible.shift();
376 | if (!neighbor[dir] || coef < minCoef[dir]) {
377 | neighbor[dir] = candidate;
378 | minCoef[dir] = coef;
379 | }
380 | }
381 | });
382 |
383 | button.neighbor = neighbor;
384 | }
385 | }
386 | }
387 |
388 | function alwaysEnable() {
389 | return true;
390 | }
391 |
392 | // 为状态创建按钮
393 | function createButton(option) {
394 | var $button = createElement(div);
395 | addElementClass($button, 'button');
396 | var render = option.render || defaultButtonRender;
397 | $button.innerHTML = render(format, option);
398 |
399 | switch (option.position) {
400 | case 'center': appendChild($center, $button); break;
401 | case 'ring': appendChild($ring, $button); break;
402 | case 'top': appendChild($top, $button); break;
403 | case 'bottom': appendChild($bottom, $button); break;
404 | }
405 |
406 | return {
407 | action: option.action,
408 | enable: option.enable || alwaysEnable,
409 | beforeShow: option.beforeShow,
410 | key: option.key,
411 | next: option.next,
412 | label: option.label,
413 | data: option.data || null,
414 | $button: $button
415 | };
416 | }
417 |
418 | // 默认按钮渲染
419 | function defaultButtonRender(format, option) {
420 | return format('{label}{key}', {
421 | label: option.label,
422 | key: option.key && option.key.split('|')[0]
423 | });
424 | }
425 |
426 | // 为当前状态添加按钮
427 | this.button = function(option) {
428 | var button = createButton(option);
429 | if (option.position == 'center') {
430 | buttons.center = button;
431 | } else if (buttons[option.position]) {
432 | buttons[option.position].push(button);
433 | }
434 | allButtons.push(button);
435 | needLayout = true;
436 | };
437 |
438 | function activeState(position) {
439 | position = position || {
440 | x: hotBox.$container.clientWidth / 2,
441 | y: hotBox.$container.clientHeight / 2
442 | };
443 | if (position) {
444 | $state.style.left = position.x + 'px';
445 | $state.style.top = position.y + 'px';
446 | }
447 | allButtons.forEach(function(button) {
448 | var $button = button.$button;
449 | if ($button) {
450 | $button.classList[button.enable() ? 'add' : 'remove']('enabled');
451 | }
452 |
453 | if (button.beforeShow) {
454 | button.beforeShow();
455 | }
456 | });
457 | addElementClass($state, STATE_ACTIVE_CLASS);
458 | if (needLayout) {
459 | layout();
460 | }
461 | if (!selectedButton) {
462 | select(buttons.center || buttons.ring[0] || buttons.top[0] || buttons.bottom[0]);
463 | }
464 | stateActived = true;
465 | }
466 |
467 | function deactiveState() {
468 | removeElementClass($state, STATE_ACTIVE_CLASS);
469 | select(null);
470 | stateActived = false;
471 | }
472 |
473 | // 激活当前状态
474 | this.active = activeState;
475 |
476 | // 反激活当前状态
477 | this.deactive = deactiveState;
478 |
479 | function press(button) {
480 | if (pressedButton && pressedButton.$button) {
481 | removeElementClass(pressedButton.$button, BUTTON_PRESSED_CLASS);
482 | }
483 | pressedButton = button;
484 | if (pressedButton && pressedButton.$button) {
485 | addElementClass(pressedButton.$button, BUTTON_PRESSED_CLASS);
486 | }
487 | }
488 |
489 | function select(button) {
490 | if (selectedButton && selectedButton.$button) {
491 | if (selectedButton.$button) {
492 | removeElementClass(selectedButton.$button, BUTTON_SELECTED_CLASS);
493 | }
494 | }
495 | selectedButton = button;
496 | if (selectedButton && selectedButton.$button) {
497 | addElementClass(selectedButton.$button, BUTTON_SELECTED_CLASS);
498 | }
499 | }
500 |
501 | $state.onmouseup = function(e) {
502 | if (e.button) return;
503 | var target = e.target;
504 | while (target && target != $state) {
505 | if (target.classList.contains('button')) {
506 | allButtons.forEach(function(button) {
507 | if (button.$button == target) {
508 | execute(button);
509 | }
510 | });
511 | }
512 | target = target.parentNode;
513 | }
514 | };
515 |
516 | this.handleKeyEvent = function(e) {
517 | var handleResult = null;
518 | /**
519 | * @Desc: 搜狗浏览器下esc只触发keyup,因此做兼容性处理
520 | * @Editor: Naixor
521 | * @Date: 2015.09.14
522 | */
523 | if (hotBox.browser.sg) {
524 | if (e.isKey('esc')) {
525 | if (pressedButton) { // 若存在已经按下的按钮,则取消操作
526 | if (!e.isKey(pressedButton.key)) { // the button is not esc
527 | press(null);
528 | }
529 | } else {
530 | hotBox.active('back', hotBox.position);
531 | }
532 | return 'back';
533 | };
534 | };
535 | if (e.keydown || (hotBox.isIME && e.keyup)) {
536 | allButtons.forEach(function(button) {
537 | if (button.enable() && e.isKey(button.key)) {
538 | if (stateActived || hotBox.hintDeactiveMainState) {
539 | select(button);
540 | press(button);
541 | handleResult = 'buttonpress';
542 |
543 | // 如果是 keyup 事件触发的,因为没有后续的按键事件,所以就直接执行
544 | if(e.keyup) {
545 | execute(button);
546 | handleResult = 'execute';
547 | return handleResult;
548 | }
549 | } else {
550 | execute(button);
551 | handleResult = 'execute';
552 | }
553 | e.preventDefault();
554 | e.stopPropagation();
555 | if (!stateActived && hotBox.hintDeactiveMainState) {
556 | hotBox.active(stateName, hotBox.position);
557 | }
558 | }
559 | });
560 | if (stateActived) {
561 | if (e.isKey('esc')) {
562 | if (pressedButton) { // 若存在已经按下的按钮,则取消操作
563 | if (!e.isKey(pressedButton.key)) { // the button is not esc
564 | press(null);
565 | }
566 | } else {
567 | hotBox.active('back', hotBox.position);
568 | }
569 | return 'back';
570 | }
571 | ['up', 'down', 'left', 'right'].forEach(function(dir) {
572 | if (!e.isKey(dir)) return;
573 | if (!selectedButton) {
574 | select(buttons.center || buttons.ring[0] || buttons.top[0] || buttons.bottom[0]);
575 | return;
576 | }
577 | var neighbor = selectedButton.neighbor[dir];
578 | while (neighbor && !neighbor.enable()) {
579 | neighbor = neighbor.neighbor[dir];
580 | }
581 | if (neighbor) {
582 | select(neighbor);
583 | }
584 | handleResult = 'navigate';
585 | });
586 |
587 | // 若是由 keyup 触发的,则直接执行选中的按钮
588 | if (e.isKey('space') && e.keyup) {
589 | execute(selectedButton);
590 | e.preventDefault();
591 | e.stopPropagation();
592 | handleResult = 'execute';
593 | } else if (e.isKey('space') && selectedButton) {
594 | press(selectedButton);
595 | handleResult = 'buttonpress';
596 | } else if (pressedButton && pressedButton != selectedButton) {
597 | press(null);
598 | handleResult = 'selectcancel';
599 | }
600 | }
601 | }
602 | else if (e.keyup && (stateActived || !hotBox.hintDeactiveMainState)) {
603 | if (pressedButton) {
604 | if (e.isKey('space') && selectedButton == pressedButton || e.isKey(pressedButton.key)) {
605 | execute(pressedButton);
606 | e.preventDefault();
607 | e.stopPropagation();
608 | handleResult = 'execute';
609 | }
610 | }
611 | }
612 |
613 | /*
614 | * Add by zhangbobell 2015.09.06
615 | * 增加了下面这一个判断因为 safari 下开启输入法后,所有的 keydown 的 keycode 都为 229,
616 | * 只能以 keyup 的 keycode 进行判断
617 | * */
618 | hotBox.isIME = (e.keyCode == 229 && e.keydown);
619 |
620 | return handleResult;
621 | };
622 |
623 | function execute(button) {
624 | if (button) {
625 | if (!button.enable || button.enable()) {
626 | if (button.action) button.action(button);
627 | hotBox.active(button.next || IDLE, hotBox.position);
628 | }
629 | press(null);
630 | select(null);
631 | }
632 | }
633 | }
634 |
635 | module.exports = HotBox;
636 | });
--------------------------------------------------------------------------------
/src/key.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module) {
2 | var keymap = require('./keymap');
3 |
4 | var CTRL_MASK = 0x1000;
5 | var ALT_MASK = 0x2000;
6 | var SHIFT_MASK = 0x4000;
7 |
8 | function hash(unknown) {
9 | if (typeof(unknown) == 'string') {
10 | return hashKeyExpression(unknown);
11 | }
12 | return hashKeyEvent(unknown);
13 | }
14 | function is(a, b) {
15 | return a && b && hash(a) == hash(b);
16 | }
17 | exports.hash = hash;
18 | exports.is = is;
19 |
20 |
21 | function hashKeyEvent(keyEvent) {
22 | var hashCode = 0;
23 | if (keyEvent.ctrlKey || keyEvent.metaKey) {
24 | hashCode |= CTRL_MASK;
25 | }
26 | if (keyEvent.altKey) {
27 | hashCode |= ALT_MASK;
28 | }
29 | if (keyEvent.shiftKey) {
30 | hashCode |= SHIFT_MASK;
31 | }
32 | // Shift, Control, Alt KeyCode ignored.
33 | if ([16, 17, 18, 91].indexOf(keyEvent.keyCode) == -1) {
34 | hashCode |= keyEvent.keyCode;
35 | }
36 | return hashCode;
37 | }
38 |
39 | function hashKeyExpression(keyExpression) {
40 | var hashCode = 0;
41 | keyExpression.toLowerCase().split(/\s*\+\s*/).forEach(function(name) {
42 | switch(name) {
43 | case 'ctrl':
44 | case 'cmd':
45 | hashCode |= CTRL_MASK;
46 | break;
47 | case 'alt':
48 | hashCode |= ALT_MASK;
49 | break;
50 | case 'shift':
51 | hashCode |= SHIFT_MASK;
52 | break;
53 | default:
54 | hashCode |= keymap[name];
55 | }
56 | });
57 | return hashCode;
58 | }
59 | });
--------------------------------------------------------------------------------
/src/keycontrol.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module) {
2 |
3 | var key = require('./key');
4 | var FOCUS_CLASS = 'hotbox-focus';
5 | var RECEIVER_CLASS = 'hotbox-key-receiver';
6 |
7 | function KeyControl(hotbox) {
8 | var _this = this;
9 | var _receiver;
10 | var _actived = true;
11 | var _receiverIsSelfCreated = false;
12 | var $container = hotbox.$container;
13 |
14 | _createReceiver();
15 | _bindReceiver();
16 | _bindContainer();
17 | _active();
18 |
19 | function _createReceiver() {
20 | _receiver = document.createElement('input');
21 | _receiver.classList.add(RECEIVER_CLASS);
22 | $container.appendChild(_receiver);
23 | _receiverIsSelfCreated = true;
24 | }
25 |
26 | function _bindReceiver() {
27 | _receiver.onkeyup = _handle;
28 | _receiver.onkeypress = _handle;
29 | _receiver.onkeydown = _handle;
30 | _receiver.onfocus = _active;
31 | _receiver.onblur = _deactive;
32 | if (_receiverIsSelfCreated) {
33 | _receiver.oninput = function(e) { _receiver.value = null; };
34 | }
35 | }
36 |
37 | function _bindContainer() {
38 | $container.onmousedown = function(e) {
39 | _active();
40 | e.preventDefault();
41 | };
42 | }
43 |
44 | function _handle(keyEvent) {
45 | if (!_actived) return;
46 | hotbox.dispatch(keyEvent);
47 | }
48 |
49 | function _active() {
50 | _receiver.select();
51 | _receiver.focus();
52 | _actived = true;
53 | $container.classList.add(FOCUS_CLASS);
54 | }
55 |
56 | function _deactive() {
57 | _receiver.blur();
58 | _actived = false;
59 | $container.classList.remove(FOCUS_CLASS);
60 | }
61 |
62 | this.handle = _handle;
63 | this.active = _active;
64 | this.deactive = _deactive;
65 | }
66 |
67 | module.exports = KeyControl;
68 | });
--------------------------------------------------------------------------------
/src/keymap.js:
--------------------------------------------------------------------------------
1 | define(function(require, exports, module) {
2 | var keymap = {
3 |
4 | 'Shift': 16,
5 | 'Control': 17,
6 | 'Alt': 18,
7 | 'CapsLock': 20,
8 |
9 | 'BackSpace': 8,
10 | 'Tab': 9,
11 | 'Enter': 13,
12 | 'Esc': 27,
13 | 'Space': 32,
14 |
15 | 'PageUp': 33,
16 | 'PageDown': 34,
17 | 'End': 35,
18 | 'Home': 36,
19 |
20 | 'Insert': 45,
21 |
22 | 'Left': 37,
23 | 'Up': 38,
24 | 'Right': 39,
25 | 'Down': 40,
26 |
27 | 'Direction': {
28 | 37: 1,
29 | 38: 1,
30 | 39: 1,
31 | 40: 1
32 | },
33 |
34 | 'Delete': 46,
35 |
36 | 'NumLock': 144,
37 |
38 | 'Cmd': 91,
39 | 'CmdFF': 224,
40 | 'F1': 112,
41 | 'F2': 113,
42 | 'F3': 114,
43 | 'F4': 115,
44 | 'F5': 116,
45 | 'F6': 117,
46 | 'F7': 118,
47 | 'F8': 119,
48 | 'F9': 120,
49 | 'F10': 121,
50 | 'F11': 122,
51 | 'F12': 123,
52 |
53 | '`': 192,
54 | '=': 187,
55 | '-': 189,
56 |
57 | '/': 191,
58 | '.': 190
59 | };
60 |
61 | // 小写适配
62 | for (var key in keymap) {
63 | if (keymap.hasOwnProperty(key)) {
64 | keymap[key.toLowerCase()] = keymap[key];
65 | }
66 | }
67 | var aKeyCode = 65;
68 | var aCharCode = 'a'.charCodeAt(0);
69 |
70 | // letters
71 | 'abcdefghijklmnopqrstuvwxyz'.split('').forEach(function(letter) {
72 | keymap[letter] = aKeyCode + (letter.charCodeAt(0) - aCharCode);
73 | });
74 |
75 | // numbers
76 | var n = 9;
77 | do {
78 | keymap[n.toString()] = n + 48;
79 | } while (n--);
80 |
81 | module.exports = keymap;
82 | });
--------------------------------------------------------------------------------
/test/SpecRunner.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Jasmine Spec Runner v2.1.3
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
21 |
22 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/test/lib/jasmine-2.1.3/boot.js:
--------------------------------------------------------------------------------
1 | /**
2 | Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project.
3 |
4 | If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms.
5 |
6 | The location of `boot.js` can be specified and/or overridden in `jasmine.yml`.
7 |
8 | [jasmine-gem]: http://github.com/pivotal/jasmine-gem
9 | */
10 |
11 | (function() {
12 |
13 | /**
14 | * ## Require & Instantiate
15 | *
16 | * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
17 | */
18 | window.jasmine = jasmineRequire.core(jasmineRequire);
19 |
20 | /**
21 | * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
22 | */
23 | jasmineRequire.html(jasmine);
24 |
25 | /**
26 | * Create the Jasmine environment. This is used to run all specs in a project.
27 | */
28 | var env = jasmine.getEnv();
29 |
30 | /**
31 | * ## The Global Interface
32 | *
33 | * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
34 | */
35 | var jasmineInterface = jasmineRequire.interface(jasmine, env);
36 |
37 | /**
38 | * Add all of the Jasmine global/public interface to the proper global, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
39 | */
40 | if (typeof window == "undefined" && typeof exports == "object") {
41 | extend(exports, jasmineInterface);
42 | } else {
43 | extend(window, jasmineInterface);
44 | }
45 |
46 | /**
47 | * ## Runner Parameters
48 | *
49 | * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface.
50 | */
51 |
52 | var queryString = new jasmine.QueryString({
53 | getWindowLocation: function() { return window.location; }
54 | });
55 |
56 | var catchingExceptions = queryString.getParam("catch");
57 | env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions);
58 |
59 | /**
60 | * ## Reporters
61 | * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
62 | */
63 | var htmlReporter = new jasmine.HtmlReporter({
64 | env: env,
65 | onRaiseExceptionsClick: function() { queryString.setParam("catch", !env.catchingExceptions()); },
66 | getContainer: function() { return document.body; },
67 | createElement: function() { return document.createElement.apply(document, arguments); },
68 | createTextNode: function() { return document.createTextNode.apply(document, arguments); },
69 | timer: new jasmine.Timer()
70 | });
71 |
72 | /**
73 | * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results from JavaScript.
74 | */
75 | env.addReporter(jasmineInterface.jsApiReporter);
76 | env.addReporter(htmlReporter);
77 |
78 | /**
79 | * Filter which specs will be run by matching the start of the full name against the `spec` query param.
80 | */
81 | var specFilter = new jasmine.HtmlSpecFilter({
82 | filterString: function() { return queryString.getParam("spec"); }
83 | });
84 |
85 | env.specFilter = function(spec) {
86 | return specFilter.matches(spec.getFullName());
87 | };
88 |
89 | /**
90 | * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack.
91 | */
92 | window.setTimeout = window.setTimeout;
93 | window.setInterval = window.setInterval;
94 | window.clearTimeout = window.clearTimeout;
95 | window.clearInterval = window.clearInterval;
96 |
97 | /**
98 | * ## Execution
99 | *
100 | * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
101 | */
102 | var currentWindowOnload = window.onload;
103 |
104 | window.onload = function() {
105 | if (currentWindowOnload) {
106 | currentWindowOnload();
107 | }
108 | htmlReporter.initialize();
109 | env.execute();
110 | };
111 |
112 | /**
113 | * Helper function for readability above.
114 | */
115 | function extend(destination, source) {
116 | for (var property in source) destination[property] = source[property];
117 | return destination;
118 | }
119 |
120 | }());
121 |
--------------------------------------------------------------------------------
/test/lib/jasmine-2.1.3/console.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2008-2014 Pivotal Labs
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 | function getJasmineRequireObj() {
24 | if (typeof module !== 'undefined' && module.exports) {
25 | return exports;
26 | } else {
27 | window.jasmineRequire = window.jasmineRequire || {};
28 | return window.jasmineRequire;
29 | }
30 | }
31 |
32 | getJasmineRequireObj().console = function(jRequire, j$) {
33 | j$.ConsoleReporter = jRequire.ConsoleReporter();
34 | };
35 |
36 | getJasmineRequireObj().ConsoleReporter = function() {
37 |
38 | var noopTimer = {
39 | start: function(){},
40 | elapsed: function(){ return 0; }
41 | };
42 |
43 | function ConsoleReporter(options) {
44 | var print = options.print,
45 | showColors = options.showColors || false,
46 | onComplete = options.onComplete || function() {},
47 | timer = options.timer || noopTimer,
48 | specCount,
49 | failureCount,
50 | failedSpecs = [],
51 | pendingCount,
52 | ansi = {
53 | green: '\x1B[32m',
54 | red: '\x1B[31m',
55 | yellow: '\x1B[33m',
56 | none: '\x1B[0m'
57 | },
58 | failedSuites = [];
59 |
60 | print('ConsoleReporter is deprecated and will be removed in a future version.');
61 |
62 | this.jasmineStarted = function() {
63 | specCount = 0;
64 | failureCount = 0;
65 | pendingCount = 0;
66 | print('Started');
67 | printNewline();
68 | timer.start();
69 | };
70 |
71 | this.jasmineDone = function() {
72 | printNewline();
73 | for (var i = 0; i < failedSpecs.length; i++) {
74 | specFailureDetails(failedSpecs[i]);
75 | }
76 |
77 | if(specCount > 0) {
78 | printNewline();
79 |
80 | var specCounts = specCount + ' ' + plural('spec', specCount) + ', ' +
81 | failureCount + ' ' + plural('failure', failureCount);
82 |
83 | if (pendingCount) {
84 | specCounts += ', ' + pendingCount + ' pending ' + plural('spec', pendingCount);
85 | }
86 |
87 | print(specCounts);
88 | } else {
89 | print('No specs found');
90 | }
91 |
92 | printNewline();
93 | var seconds = timer.elapsed() / 1000;
94 | print('Finished in ' + seconds + ' ' + plural('second', seconds));
95 | printNewline();
96 |
97 | for(i = 0; i < failedSuites.length; i++) {
98 | suiteFailureDetails(failedSuites[i]);
99 | }
100 |
101 | onComplete(failureCount === 0);
102 | };
103 |
104 | this.specDone = function(result) {
105 | specCount++;
106 |
107 | if (result.status == 'pending') {
108 | pendingCount++;
109 | print(colored('yellow', '*'));
110 | return;
111 | }
112 |
113 | if (result.status == 'passed') {
114 | print(colored('green', '.'));
115 | return;
116 | }
117 |
118 | if (result.status == 'failed') {
119 | failureCount++;
120 | failedSpecs.push(result);
121 | print(colored('red', 'F'));
122 | }
123 | };
124 |
125 | this.suiteDone = function(result) {
126 | if (result.failedExpectations && result.failedExpectations.length > 0) {
127 | failureCount++;
128 | failedSuites.push(result);
129 | }
130 | };
131 |
132 | return this;
133 |
134 | function printNewline() {
135 | print('\n');
136 | }
137 |
138 | function colored(color, str) {
139 | return showColors ? (ansi[color] + str + ansi.none) : str;
140 | }
141 |
142 | function plural(str, count) {
143 | return count == 1 ? str : str + 's';
144 | }
145 |
146 | function repeat(thing, times) {
147 | var arr = [];
148 | for (var i = 0; i < times; i++) {
149 | arr.push(thing);
150 | }
151 | return arr;
152 | }
153 |
154 | function indent(str, spaces) {
155 | var lines = (str || '').split('\n');
156 | var newArr = [];
157 | for (var i = 0; i < lines.length; i++) {
158 | newArr.push(repeat(' ', spaces).join('') + lines[i]);
159 | }
160 | return newArr.join('\n');
161 | }
162 |
163 | function specFailureDetails(result) {
164 | printNewline();
165 | print(result.fullName);
166 |
167 | for (var i = 0; i < result.failedExpectations.length; i++) {
168 | var failedExpectation = result.failedExpectations[i];
169 | printNewline();
170 | print(indent(failedExpectation.message, 2));
171 | print(indent(failedExpectation.stack, 2));
172 | }
173 |
174 | printNewline();
175 | }
176 |
177 | function suiteFailureDetails(result) {
178 | for (var i = 0; i < result.failedExpectations.length; i++) {
179 | printNewline();
180 | print(colored('red', 'An error was thrown in an afterAll'));
181 | printNewline();
182 | print(colored('red', 'AfterAll ' + result.failedExpectations[i].message));
183 |
184 | }
185 | printNewline();
186 | }
187 | }
188 |
189 | return ConsoleReporter;
190 | };
191 |
--------------------------------------------------------------------------------
/test/lib/jasmine-2.1.3/jasmine-html.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2008-2014 Pivotal Labs
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining
5 | a copy of this software and associated documentation files (the
6 | "Software"), to deal in the Software without restriction, including
7 | without limitation the rights to use, copy, modify, merge, publish,
8 | distribute, sublicense, and/or sell copies of the Software, and to
9 | permit persons to whom the Software is furnished to do so, subject to
10 | the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 | jasmineRequire.html = function(j$) {
24 | j$.ResultsNode = jasmineRequire.ResultsNode();
25 | j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
26 | j$.QueryString = jasmineRequire.QueryString();
27 | j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter();
28 | };
29 |
30 | jasmineRequire.HtmlReporter = function(j$) {
31 |
32 | var noopTimer = {
33 | start: function() {},
34 | elapsed: function() { return 0; }
35 | };
36 |
37 | function HtmlReporter(options) {
38 | var env = options.env || {},
39 | getContainer = options.getContainer,
40 | createElement = options.createElement,
41 | createTextNode = options.createTextNode,
42 | onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {},
43 | timer = options.timer || noopTimer,
44 | results = [],
45 | specsExecuted = 0,
46 | failureCount = 0,
47 | pendingSpecCount = 0,
48 | htmlReporterMain,
49 | symbols,
50 | failedSuites = [];
51 |
52 | this.initialize = function() {
53 | clearPrior();
54 | htmlReporterMain = createDom('div', {className: 'jasmine_html-reporter'},
55 | createDom('div', {className: 'banner'},
56 | createDom('a', {className: 'title', href: 'http://jasmine.github.io/', target: '_blank'}),
57 | createDom('span', {className: 'version'}, j$.version)
58 | ),
59 | createDom('ul', {className: 'symbol-summary'}),
60 | createDom('div', {className: 'alert'}),
61 | createDom('div', {className: 'results'},
62 | createDom('div', {className: 'failures'})
63 | )
64 | );
65 | getContainer().appendChild(htmlReporterMain);
66 |
67 | symbols = find('.symbol-summary');
68 | };
69 |
70 | var totalSpecsDefined;
71 | this.jasmineStarted = function(options) {
72 | totalSpecsDefined = options.totalSpecsDefined || 0;
73 | timer.start();
74 | };
75 |
76 | var summary = createDom('div', {className: 'summary'});
77 |
78 | var topResults = new j$.ResultsNode({}, '', null),
79 | currentParent = topResults;
80 |
81 | this.suiteStarted = function(result) {
82 | currentParent.addChild(result, 'suite');
83 | currentParent = currentParent.last();
84 | };
85 |
86 | this.suiteDone = function(result) {
87 | if (result.status == 'failed') {
88 | failedSuites.push(result);
89 | }
90 |
91 | if (currentParent == topResults) {
92 | return;
93 | }
94 |
95 | currentParent = currentParent.parent;
96 | };
97 |
98 | this.specStarted = function(result) {
99 | currentParent.addChild(result, 'spec');
100 | };
101 |
102 | var failures = [];
103 | this.specDone = function(result) {
104 | if(noExpectations(result) && typeof console !== 'undefined' && typeof console.error !== 'undefined') {
105 | console.error('Spec \'' + result.fullName + '\' has no expectations.');
106 | }
107 |
108 | if (result.status != 'disabled') {
109 | specsExecuted++;
110 | }
111 |
112 | symbols.appendChild(createDom('li', {
113 | className: noExpectations(result) ? 'empty' : result.status,
114 | id: 'spec_' + result.id,
115 | title: result.fullName
116 | }
117 | ));
118 |
119 | if (result.status == 'failed') {
120 | failureCount++;
121 |
122 | var failure =
123 | createDom('div', {className: 'spec-detail failed'},
124 | createDom('div', {className: 'description'},
125 | createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName)
126 | ),
127 | createDom('div', {className: 'messages'})
128 | );
129 | var messages = failure.childNodes[1];
130 |
131 | for (var i = 0; i < result.failedExpectations.length; i++) {
132 | var expectation = result.failedExpectations[i];
133 | messages.appendChild(createDom('div', {className: 'result-message'}, expectation.message));
134 | messages.appendChild(createDom('div', {className: 'stack-trace'}, expectation.stack));
135 | }
136 |
137 | failures.push(failure);
138 | }
139 |
140 | if (result.status == 'pending') {
141 | pendingSpecCount++;
142 | }
143 | };
144 |
145 | this.jasmineDone = function() {
146 | var banner = find('.banner');
147 | banner.appendChild(createDom('span', {className: 'duration'}, 'finished in ' + timer.elapsed() / 1000 + 's'));
148 |
149 | var alert = find('.alert');
150 |
151 | alert.appendChild(createDom('span', { className: 'exceptions' },
152 | createDom('label', { className: 'label', 'for': 'raise-exceptions' }, 'raise exceptions'),
153 | createDom('input', {
154 | className: 'raise',
155 | id: 'raise-exceptions',
156 | type: 'checkbox'
157 | })
158 | ));
159 | var checkbox = find('#raise-exceptions');
160 |
161 | checkbox.checked = !env.catchingExceptions();
162 | checkbox.onclick = onRaiseExceptionsClick;
163 |
164 | if (specsExecuted < totalSpecsDefined) {
165 | var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all';
166 | alert.appendChild(
167 | createDom('span', {className: 'bar skipped'},
168 | createDom('a', {href: '?', title: 'Run all specs'}, skippedMessage)
169 | )
170 | );
171 | }
172 | var statusBarMessage = '';
173 | var statusBarClassName = 'bar ';
174 |
175 | if (totalSpecsDefined > 0) {
176 | statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount);
177 | if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); }
178 | statusBarClassName += (failureCount > 0) ? 'failed' : 'passed';
179 | } else {
180 | statusBarClassName += 'skipped';
181 | statusBarMessage += 'No specs found';
182 | }
183 |
184 | alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage));
185 |
186 | for(i = 0; i < failedSuites.length; i++) {
187 | var failedSuite = failedSuites[i];
188 | for(var j = 0; j < failedSuite.failedExpectations.length; j++) {
189 | var errorBarMessage = 'AfterAll ' + failedSuite.failedExpectations[j].message;
190 | var errorBarClassName = 'bar errored';
191 | alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessage));
192 | }
193 | }
194 |
195 | var results = find('.results');
196 | results.appendChild(summary);
197 |
198 | summaryList(topResults, summary);
199 |
200 | function summaryList(resultsTree, domParent) {
201 | var specListNode;
202 | for (var i = 0; i < resultsTree.children.length; i++) {
203 | var resultNode = resultsTree.children[i];
204 | if (resultNode.type == 'suite') {
205 | var suiteListNode = createDom('ul', {className: 'suite', id: 'suite-' + resultNode.result.id},
206 | createDom('li', {className: 'suite-detail'},
207 | createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description)
208 | )
209 | );
210 |
211 | summaryList(resultNode, suiteListNode);
212 | domParent.appendChild(suiteListNode);
213 | }
214 | if (resultNode.type == 'spec') {
215 | if (domParent.getAttribute('class') != 'specs') {
216 | specListNode = createDom('ul', {className: 'specs'});
217 | domParent.appendChild(specListNode);
218 | }
219 | var specDescription = resultNode.result.description;
220 | if(noExpectations(resultNode.result)) {
221 | specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
222 | }
223 | specListNode.appendChild(
224 | createDom('li', {
225 | className: resultNode.result.status,
226 | id: 'spec-' + resultNode.result.id
227 | },
228 | createDom('a', {href: specHref(resultNode.result)}, specDescription)
229 | )
230 | );
231 | }
232 | }
233 | }
234 |
235 | if (failures.length) {
236 | alert.appendChild(
237 | createDom('span', {className: 'menu bar spec-list'},
238 | createDom('span', {}, 'Spec List | '),
239 | createDom('a', {className: 'failures-menu', href: '#'}, 'Failures')));
240 | alert.appendChild(
241 | createDom('span', {className: 'menu bar failure-list'},
242 | createDom('a', {className: 'spec-list-menu', href: '#'}, 'Spec List'),
243 | createDom('span', {}, ' | Failures ')));
244 |
245 | find('.failures-menu').onclick = function() {
246 | setMenuModeTo('failure-list');
247 | };
248 | find('.spec-list-menu').onclick = function() {
249 | setMenuModeTo('spec-list');
250 | };
251 |
252 | setMenuModeTo('failure-list');
253 |
254 | var failureNode = find('.failures');
255 | for (var i = 0; i < failures.length; i++) {
256 | failureNode.appendChild(failures[i]);
257 | }
258 | }
259 | };
260 |
261 | return this;
262 |
263 | function find(selector) {
264 | return getContainer().querySelector('.jasmine_html-reporter ' + selector);
265 | }
266 |
267 | function clearPrior() {
268 | // return the reporter
269 | var oldReporter = find('');
270 |
271 | if(oldReporter) {
272 | getContainer().removeChild(oldReporter);
273 | }
274 | }
275 |
276 | function createDom(type, attrs, childrenVarArgs) {
277 | var el = createElement(type);
278 |
279 | for (var i = 2; i < arguments.length; i++) {
280 | var child = arguments[i];
281 |
282 | if (typeof child === 'string') {
283 | el.appendChild(createTextNode(child));
284 | } else {
285 | if (child) {
286 | el.appendChild(child);
287 | }
288 | }
289 | }
290 |
291 | for (var attr in attrs) {
292 | if (attr == 'className') {
293 | el[attr] = attrs[attr];
294 | } else {
295 | el.setAttribute(attr, attrs[attr]);
296 | }
297 | }
298 |
299 | return el;
300 | }
301 |
302 | function pluralize(singular, count) {
303 | var word = (count == 1 ? singular : singular + 's');
304 |
305 | return '' + count + ' ' + word;
306 | }
307 |
308 | function specHref(result) {
309 | return '?spec=' + encodeURIComponent(result.fullName);
310 | }
311 |
312 | function setMenuModeTo(mode) {
313 | htmlReporterMain.setAttribute('class', 'jasmine_html-reporter ' + mode);
314 | }
315 |
316 | function noExpectations(result) {
317 | return (result.failedExpectations.length + result.passedExpectations.length) === 0 &&
318 | result.status === 'passed';
319 | }
320 | }
321 |
322 | return HtmlReporter;
323 | };
324 |
325 | jasmineRequire.HtmlSpecFilter = function() {
326 | function HtmlSpecFilter(options) {
327 | var filterString = options && options.filterString() && options.filterString().replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
328 | var filterPattern = new RegExp(filterString);
329 |
330 | this.matches = function(specName) {
331 | return filterPattern.test(specName);
332 | };
333 | }
334 |
335 | return HtmlSpecFilter;
336 | };
337 |
338 | jasmineRequire.ResultsNode = function() {
339 | function ResultsNode(result, type, parent) {
340 | this.result = result;
341 | this.type = type;
342 | this.parent = parent;
343 |
344 | this.children = [];
345 |
346 | this.addChild = function(result, type) {
347 | this.children.push(new ResultsNode(result, type, this));
348 | };
349 |
350 | this.last = function() {
351 | return this.children[this.children.length - 1];
352 | };
353 | }
354 |
355 | return ResultsNode;
356 | };
357 |
358 | jasmineRequire.QueryString = function() {
359 | function QueryString(options) {
360 |
361 | this.setParam = function(key, value) {
362 | var paramMap = queryStringToParamMap();
363 | paramMap[key] = value;
364 | options.getWindowLocation().search = toQueryString(paramMap);
365 | };
366 |
367 | this.getParam = function(key) {
368 | return queryStringToParamMap()[key];
369 | };
370 |
371 | return this;
372 |
373 | function toQueryString(paramMap) {
374 | var qStrPairs = [];
375 | for (var prop in paramMap) {
376 | qStrPairs.push(encodeURIComponent(prop) + '=' + encodeURIComponent(paramMap[prop]));
377 | }
378 | return '?' + qStrPairs.join('&');
379 | }
380 |
381 | function queryStringToParamMap() {
382 | var paramStr = options.getWindowLocation().search.substring(1),
383 | params = [],
384 | paramMap = {};
385 |
386 | if (paramStr.length > 0) {
387 | params = paramStr.split('&');
388 | for (var i = 0; i < params.length; i++) {
389 | var p = params[i].split('=');
390 | var value = decodeURIComponent(p[1]);
391 | if (value === 'true' || value === 'false') {
392 | value = JSON.parse(value);
393 | }
394 | paramMap[decodeURIComponent(p[0])] = value;
395 | }
396 | }
397 |
398 | return paramMap;
399 | }
400 |
401 | }
402 |
403 | return QueryString;
404 | };
405 |
--------------------------------------------------------------------------------
/test/lib/jasmine-2.1.3/jasmine.css:
--------------------------------------------------------------------------------
1 | body { overflow-y: scroll; }
2 |
3 | .jasmine_html-reporter { background-color: #eeeeee; padding: 5px; margin: -8px; font-size: 11px; font-family: Monaco, "Lucida Console", monospace; line-height: 14px; color: #333333; }
4 | .jasmine_html-reporter a { text-decoration: none; }
5 | .jasmine_html-reporter a:hover { text-decoration: underline; }
6 | .jasmine_html-reporter p, .jasmine_html-reporter h1, .jasmine_html-reporter h2, .jasmine_html-reporter h3, .jasmine_html-reporter h4, .jasmine_html-reporter h5, .jasmine_html-reporter h6 { margin: 0; line-height: 14px; }
7 | .jasmine_html-reporter .banner, .jasmine_html-reporter .symbol-summary, .jasmine_html-reporter .summary, .jasmine_html-reporter .result-message, .jasmine_html-reporter .spec .description, .jasmine_html-reporter .spec-detail .description, .jasmine_html-reporter .alert .bar, .jasmine_html-reporter .stack-trace { padding-left: 9px; padding-right: 9px; }
8 | .jasmine_html-reporter .banner { position: relative; }
9 | .jasmine_html-reporter .banner .title { background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFoAAAAZCAMAAACGusnyAAACdlBMVEX/////AP+AgICqVaqAQICZM5mAVYCSSZKAQICOOY6ATYCLRouAQICJO4mSSYCIRIiPQICHPIeOR4CGQ4aMQICGPYaLRoCFQ4WKQICPPYWJRYCOQoSJQICNPoSIRICMQoSHQICHRICKQoOHQICKPoOJO4OJQYOMQICMQ4CIQYKLQICIPoKLQ4CKQICNPoKJQISMQ4KJQoSLQYKJQISLQ4KIQoSKQYKIQICIQISMQoSKQYKLQIOLQoOJQYGLQIOKQIOMQoGKQYOLQYGKQIOLQoGJQYOJQIOKQYGJQIOKQoGKQIGLQIKLQ4KKQoGLQYKJQIGKQYKJQIGKQIKJQoGKQYKLQIGKQYKLQIOJQoKKQoOJQYKKQIOJQoKKQoOKQIOLQoKKQYOLQYKJQIOKQoKKQYKKQoKJQYOKQYKLQIOKQoKLQYOKQYKLQIOJQoGKQYKJQYGJQoGKQYKLQoGLQYGKQoGJQYKKQYGJQIKKQoGJQYKLQIKKQYGLQYKKQYGKQYGKQYKJQYOKQoKJQYOKQYKLQYOLQYOKQYKLQYOKQoKKQYKKQYOKQYOJQYKKQYKLQYKKQIKKQoKKQYKKQYKKQoKJQIKKQYKLQYKKQYKKQIKKQYKKQYKKQYKKQIKKQYKJQYGLQYGKQYKKQYKKQYGKQIKKQYGKQYOJQoKKQYOLQYKKQYOKQoKKQYKKQoKKQYKKQYKJQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKJQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKLQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKKQYKmIDpEAAAA0XRSTlMAAQIDBAUGBwgJCgsMDQ4PEBESExQVFhcYGRobHB0eHyAiIyQlJycoKissLS4wMTQ1Njc4OTo7PDw+P0BCQ0RISUpLTE1OUFNUVVdYWFlaW15fYGFiY2ZnaGlqa2xtb3BxcnN0dnh5ent8fX5/gIGChIWIioyNjo+QkZOUlZaYmZqbnJ2eoKGio6WmqKmsra6vsLGztre4ubq7vL2+wMHDxMjJysvNzs/Q0dLU1tfY2dvc3t/g4eLj5ebn6Onq6+zt7u/w8vP09fb3+Pn6+/z9/vkVQXAAAAMaSURBVHhe5dXxV1N1GMfxz2ABbDgIAm5VDJOyVDIJLUMaVpBWUZUaGbmqoGpZRSiGiRWp6KoZ5AB0ZY50RImZQIlahKkMYXv/R90dBvET/rJfOr3Ouc8v99zPec59zvf56j+vYKlViSf7250X4Mr3O29Tgq08BdGB4DhcekEJ5YkQKFsgWZdtj9JpV+I8xPjLFqkrsEIqO8PHSpis36jWazcqjEsfJjkvRssVU37SdIOu4XCf5vEJPsnwJpnRNU9JmxhMk8l1gehIrq7hTFjzOD+Vf88629qKMJVNltInFeRexRQyJlNeqd1iGDlSzrIUIyXbyFfm3RYprcQRe7lqtWyGYbfc6dT0R2vmdOOkX3u55C1rP37ftiH+tDby4r/RBT0w8TyEkr+epB9XgPDmSYYWbrhCuFYaIyw3fDQAXTnSkh+ANofiHmWf9l+FY1I90FdQTetstO00o23novzVsJ7uB3/C5TkbjRwZ5JerwV4iRWq9HFbFMaK/d0TYqayRiQPuIxxS3Bu8JWU90/60tKi7vkhaznez0a/TbVOKj5CaOZh6fWG6/Lyv9B/ZLR1gw/S/fpbeVD3MCW1li6SvWDOn65tr99/uvWtBS0XDm4s1t+sOHpG0kpBKx/l77wOSnxLpcx6TXmXLTPQOKYOf9Q1dfr8/SJ2mFdCvl1Yl93DiHUZvXeLJbGSzYu5gVJ2slbSakOR8dxCq5adQ2oFLqsE9Ex3L4qQO0eOPeU5x56bypXp4onSEb5OkICX6lDat55TeoztNKQcJaakrz9KCb95oD69IKq+yKW4XPjknaS52V0TZqE2cTtXjcHSCRmUO88e+85hj3EP74i9p8pylw7lxgMDyyl6OV7ZejnjNMfatu87LxRbH0IS35gt2a4ZjmGpVBdKK3Wr6INk8jWWSGqbA55CKgjBRC6E9w78ydTg3ABS3AFV1QN0Y4Aa2pgEjWnQURj9L0ayK6R2ysEqxHUKzYnLvvyU+i9KM2JHJzE4vyZOyDcOwOsySajeLPc8sNvPJkFlyJd20wpqAzZeAfZ3oWybxd+P/3j+SG3uSBdf2VQAAAABJRU5ErkJggg==') no-repeat; background: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgdmVyc2lvbj0iMS4xIgogICB3aWR0aD0iNjgxLjk2MjUyIgogICBoZWlnaHQ9IjE4Ny41IgogICBpZD0ic3ZnMiIKICAgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhOCI+PHJkZjpSREY+PGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPjxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PjxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz48L2NjOldvcms+PC9yZGY6UkRGPjwvbWV0YWRhdGE+PGRlZnMKICAgICBpZD0iZGVmczYiPjxjbGlwUGF0aAogICAgICAgaWQ9ImNsaXBQYXRoMTgiPjxwYXRoCiAgICAgICAgIGQ9Ik0gMCwxNTAwIDAsMCBsIDU0NTUuNzQsMCAwLDE1MDAgTCAwLDE1MDAgeiIKICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgaWQ9InBhdGgyMCIgLz48L2NsaXBQYXRoPjwvZGVmcz48ZwogICAgIHRyYW5zZm9ybT0ibWF0cml4KDEuMjUsMCwwLC0xLjI1LDAsMTg3LjUpIgogICAgIGlkPSJnMTAiPjxnCiAgICAgICB0cmFuc2Zvcm09InNjYWxlKDAuMSwwLjEpIgogICAgICAgaWQ9ImcxMiI+PGcKICAgICAgICAgaWQ9ImcxNCI+PGcKICAgICAgICAgICBjbGlwLXBhdGg9InVybCgjY2xpcFBhdGgxOCkiCiAgICAgICAgICAgaWQ9ImcxNiI+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTU0NCw1OTkuNDM0IGMgMC45MiwtNDAuMzUyIDI1LjY4LC04MS42MDIgNzEuNTMsLTgxLjYwMiAyNy41MSwwIDQ3LjY4LDEyLjgzMiA2MS40NCwzNS43NTQgMTIuODMsMjIuOTMgMTIuODMsNTYuODUyIDEyLjgzLDgyLjUyNyBsIDAsMzI5LjE4NCAtNzEuNTIsMCAwLDEwNC41NDMgMjY2LjgzLDAgMCwtMTA0LjU0MyAtNzAuNiwwIDAsLTM0NC43NyBjIDAsLTU4LjY5MSAtMy42OCwtMTA0LjUzMSAtNDQuOTMsLTE1Mi4yMTggLTM2LjY4LC00Mi4xOCAtOTYuMjgsLTY2LjAyIC0xNTMuMTQsLTY2LjAyIC0xMTcuMzcsMCAtMjA3LjI0LDc3Ljk0MSAtMjAyLjY0LDE5Ny4xNDUgbCAxMzAuMiwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMjIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDIzMDEuNCw2NjIuNjk1IGMgMCw4MC43MDMgLTY2Ljk0LDE0NS44MTMgLTE0Ny42MywxNDUuODEzIC04My40NCwwIC0xNDcuNjMsLTY4Ljc4MSAtMTQ3LjYzLC0xNTEuMzAxIDAsLTc5Ljc4NSA2Ni45NCwtMTQ1LjgwMSAxNDUuOCwtMTQ1LjgwMSA4NC4zNSwwIDE0OS40Niw2Ny44NTIgMTQ5LjQ2LDE1MS4yODkgeiBtIC0xLjgzLC0xODEuNTQ3IGMgLTM1Ljc3LC01NC4wOTcgLTkzLjUzLC03OC44NTkgLTE1Ny43MiwtNzguODU5IC0xNDAuMywwIC0yNTEuMjQsMTE2LjQ0OSAtMjUxLjI0LDI1NC45MTggMCwxNDIuMTI5IDExMy43LDI2MC40MSAyNTYuNzQsMjYwLjQxIDYzLjI3LDAgMTE4LjI5LC0yOS4zMzYgMTUyLjIyLC04Mi41MjMgbCAwLDY5LjY4NyAxNzUuMTQsMCAwLC0xMDQuNTI3IC02MS40NCwwIDAsLTI4MC41OTggNjEuNDQsMCAwLC0xMDQuNTI3IC0xNzUuMTQsMCAwLDY2LjAxOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAyNjIyLjMzLDU1Ny4yNTggYyAzLjY3LC00NC4wMTYgMzMuMDEsLTczLjM0OCA3OC44NiwtNzMuMzQ4IDMzLjkzLDAgNjYuOTMsMjMuODI0IDY2LjkzLDYwLjUwNCAwLDQ4LjYwNiAtNDUuODQsNTYuODU2IC04My40NCw2Ni45NDEgLTg1LjI4LDIyLjAwNCAtMTc4LjgxLDQ4LjYwNiAtMTc4LjgxLDE1NS44NzkgMCw5My41MzYgNzguODYsMTQ3LjYzMyAxNjUuOTgsMTQ3LjYzMyA0NCwwIDgzLjQzLC05LjE3NiAxMTAuOTQsLTQ0LjAwOCBsIDAsMzMuOTIyIDgyLjUzLDAgMCwtMTMyLjk2NSAtMTA4LjIxLDAgYyAtMS44MywzNC44NTYgLTI4LjQyLDU3Ljc3NCAtNjMuMjYsNTcuNzc0IC0zMC4yNiwwIC02Mi4zNSwtMTcuNDIyIC02Mi4zNSwtNTEuMzQ4IDAsLTQ1Ljg0NyA0NC45MywtNTUuOTMgODAuNjksLTY0LjE4IDg4LjAyLC0yMC4xNzUgMTgyLjQ3LC00Ny42OTUgMTgyLjQ3LC0xNTcuNzM0IDAsLTk5LjAyNyAtODMuNDQsLTE1NC4wMzkgLTE3NS4xMywtMTU0LjAzOSAtNDkuNTMsMCAtOTQuNDYsMTUuNTgyIC0xMjYuNTUsNTMuMTggbCAwLC00MC4zNCAtODUuMjcsMCAwLDE0Mi4xMjkgMTE0LjYyLDAiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGgyNiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMjk4OC4xOCw4MDAuMjU0IC02My4yNiwwIDAsMTA0LjUyNyAxNjUuMDUsMCAwLC03My4zNTUgYyAzMS4xOCw1MS4zNDcgNzguODYsODUuMjc3IDE0MS4yMSw4NS4yNzcgNjcuODUsMCAxMjQuNzEsLTQxLjI1OCAxNTIuMjEsLTEwMi42OTkgMjYuNiw2Mi4zNTEgOTIuNjIsMTAyLjY5OSAxNjAuNDcsMTAyLjY5OSA1My4xOSwwIDEwNS40NiwtMjIgMTQxLjIxLC02Mi4zNTEgMzguNTIsLTQ0LjkzOCAzOC41MiwtOTMuNTMyIDM4LjUyLC0xNDkuNDU3IGwgMCwtMTg1LjIzOSA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40MiwwIDAsMTA0LjUyNyA2My4yOCwwIDAsMTU3LjcxNSBjIDAsMzIuMTAyIDAsNjAuNTI3IC0xNC42Nyw4OC45NTcgLTE4LjM0LDI2LjU4MiAtNDguNjEsNDAuMzQ0IC03OS43Nyw0MC4zNDQgLTMwLjI2LDAgLTYzLjI4LC0xMi44NDQgLTgyLjUzLC0zNi42NzIgLTIyLjkzLC0yOS4zNTUgLTIyLjkzLC01Ni44NjMgLTIyLjkzLC05Mi42MjkgbCAwLC0xNTcuNzE1IDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM4LjQxLDAgMCwxMDQuNTI3IDYzLjI4LDAgMCwxNTAuMzgzIGMgMCwyOS4zNDggMCw2Ni4wMjMgLTE0LjY3LDkxLjY5OSAtMTUuNTksMjkuMzM2IC00Ny42OSw0NC45MzQgLTgwLjcsNDQuOTM0IC0zMS4xOCwwIC01Ny43NywtMTEuMDA4IC03Ny45NCwtMzUuNzc0IC0yNC43NywtMzAuMjUzIC0yNi42LC02Mi4zNDMgLTI2LjYsLTk5Ljk0MSBsIDAsLTE1MS4zMDEgNjMuMjcsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNiwwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDI4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSAzOTk4LjY2LDk1MS41NDcgLTExMS44NywwIDAsMTE4LjI5MyAxMTEuODcsMCAwLC0xMTguMjkzIHogbSAwLC00MzEuODkxIDYzLjI3LDAgMCwtMTA0LjUyNyAtMjM5LjMzLDAgMCwxMDQuNTI3IDY0LjE5LDAgMCwyODAuNTk4IC02My4yNywwIDAsMTA0LjUyNyAxNzUuMTQsMCAwLC0zODUuMTI1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzAiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDQxNTkuMTIsODAwLjI1NCAtNjMuMjcsMCAwLDEwNC41MjcgMTc1LjE0LDAgMCwtNjkuNjg3IGMgMjkuMzUsNTQuMTAxIDg0LjM2LDgwLjY5OSAxNDQuODcsODAuNjk5IDUzLjE5LDAgMTA1LjQ1LC0yMi4wMTYgMTQxLjIyLC02MC41MjcgNDAuMzQsLTQ0LjkzNCA0MS4yNiwtODguMDMyIDQxLjI2LC0xNDMuOTU3IGwgMCwtMTkxLjY1MyA2My4yNywwIDAsLTEwNC41MjcgLTIzOC40LDAgMCwxMDQuNTI3IDYzLjI2LDAgMCwxNTguNjM3IGMgMCwzMC4yNjIgMCw2MS40MzQgLTE5LjI2LDg4LjAzNSAtMjAuMTcsMjYuNTgyIC01My4xOCwzOS40MTQgLTg2LjE5LDM5LjQxNCAtMzMuOTMsMCAtNjguNzcsLTEzLjc1IC04OC45NCwtNDEuMjUgLTIxLjA5LC0yNy41IC0yMS4wOSwtNjkuNjg3IC0yMS4wOSwtMTAyLjcwNyBsIDAsLTE0Mi4xMjkgNjMuMjYsMCAwLC0xMDQuNTI3IC0yMzguNCwwIDAsMTA0LjUyNyA2My4yNywwIDAsMjgwLjU5OCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDMyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA1MDgyLjQ4LDcwMy45NjUgYyAtMTkuMjQsNzAuNjA1IC04MS42LDExNS41NDcgLTE1NC4wNCwxMTUuNTQ3IC02Ni4wNCwwIC0xMjkuMywtNTEuMzQ4IC0xNDMuMDUsLTExNS41NDcgbCAyOTcuMDksMCB6IG0gODUuMjcsLTE0NC44ODMgYyAtMzguNTEsLTkzLjUyMyAtMTI5LjI3LC0xNTYuNzkzIC0yMzEuMDUsLTE1Ni43OTMgLTE0My4wNywwIC0yNTcuNjgsMTExLjg3MSAtMjU3LjY4LDI1NS44MzYgMCwxNDQuODgzIDEwOS4xMiwyNjEuMzI4IDI1NC45MSwyNjEuMzI4IDY3Ljg3LDAgMTM1LjcyLC0zMC4yNTggMTgzLjM5LC03OC44NjMgNDguNjIsLTUxLjM0NCA2OC43OSwtMTEzLjY5NSA2OC43OSwtMTgzLjM4MyBsIC0zLjY3LC0zOS40MzQgLTM5Ni4xMywwIGMgMTQuNjcsLTY3Ljg2MyA3Ny4wMywtMTE3LjM2MyAxNDYuNzIsLTExNy4zNjMgNDguNTksMCA5MC43NiwxOC4zMjggMTE4LjI4LDU4LjY3MiBsIDExNi40NCwwIgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDY5MC44OTUsODUwLjcwMyA5MC43NSwwIDIyLjU0MywzMS4wMzUgMCwyNDMuMTIyIC0xMzUuODI5LDAgMCwtMjQzLjE0MSAyMi41MzYsLTMxLjAxNiIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDM2IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA2MzIuMzk1LDc0Mi4yNTggMjguMDM5LDg2LjMwNCAtMjIuNTUxLDMxLjA0IC0yMzEuMjIzLDc1LjEyOCAtNDEuOTc2LC0xMjkuMTgzIDIzMS4yNTcsLTc1LjEzNyAzNi40NTQsMTEuODQ4IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoMzgiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDcxNy40NDksNjUzLjEwNSAtNzMuNDEsNTMuMzYgLTM2LjQ4OCwtMTEuODc1IC0xNDIuOTAzLC0xOTYuNjkyIDEwOS44ODMsLTc5LjgyOCAxNDIuOTE4LDE5Ni43MDMgMCwzOC4zMzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0MCIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gODI4LjUyLDcwNi40NjUgLTczLjQyNiwtNTMuMzQgMC4wMTEsLTM4LjM1OSBMIDg5OC4wMDQsNDE4LjA3IDEwMDcuOSw0OTcuODk4IDg2NC45NzMsNjk0LjYwOSA4MjguNTIsNzA2LjQ2NSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQyIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA4MTIuMDg2LDgyOC41ODYgMjguMDU1LC04Ni4zMiAzNi40ODQsLTExLjgzNiAyMzEuMjI1LDc1LjExNyAtNDEuOTcsMTI5LjE4MyAtMjMxLjIzOSwtNzUuMTQgLTIyLjU1NSwtMzEuMDA0IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNDQiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDczNi4zMDEsMTMzNS44OCBjIC0zMjMuMDQ3LDAgLTU4NS44NzUsLTI2Mi43OCAtNTg1Ljg3NSwtNTg1Ljc4MiAwLC0zMjMuMTE4IDI2Mi44MjgsLTU4NS45NzcgNTg1Ljg3NSwtNTg1Ljk3NyAzMjMuMDE5LDAgNTg1LjgwOSwyNjIuODU5IDU4NS44MDksNTg1Ljk3NyAwLDMyMy4wMDIgLTI2Mi43OSw1ODUuNzgyIC01ODUuODA5LDU4NS43ODIgbCAwLDAgeiBtIDAsLTExOC42MSBjIDI1Ny45NzIsMCA0NjcuMTg5LC0yMDkuMTMgNDY3LjE4OSwtNDY3LjE3MiAwLC0yNTguMTI5IC0yMDkuMjE3LC00NjcuMzQ4IC00NjcuMTg5LC00NjcuMzQ4IC0yNTguMDc0LDAgLTQ2Ny4yNTQsMjA5LjIxOSAtNDY3LjI1NCw0NjcuMzQ4IDAsMjU4LjA0MiAyMDkuMTgsNDY3LjE3MiA0NjcuMjU0LDQ2Ny4xNzIiCiAgICAgICAgICAgICBpbmtzY2FwZTpjb25uZWN0b3ItY3VydmF0dXJlPSIwIgogICAgICAgICAgICAgaWQ9InBhdGg0NiIKICAgICAgICAgICAgIHN0eWxlPSJmaWxsOiM4YTQxODI7ZmlsbC1vcGFjaXR5OjE7ZmlsbC1ydWxlOm5vbnplcm87c3Ryb2tlOm5vbmUiIC8+PHBhdGgKICAgICAgICAgICAgIGQ9Im0gMTA5MS4xMyw2MTkuODgzIC0xNzUuNzcxLDU3LjEyMSAxMS42MjksMzUuODA4IDE3NS43NjIsLTU3LjEyMSAtMTEuNjIsLTM1LjgwOCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDQ4IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA4NjYuOTU3LDkwMi4wNzQgODM2LjUsOTI0LjE5OSA5NDUuMTIxLDEwNzMuNzMgOTc1LjU4NiwxMDUxLjYxIDg2Ni45NTcsOTAyLjA3NCIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDUwIgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0iTSA2MDcuNDY1LDkwMy40NDUgNDk4Ljg1NSwxMDUyLjk3IDUyOS4zMiwxMDc1LjEgNjM3LjkzLDkyNS41NjYgNjA3LjQ2NSw5MDMuNDQ1IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTIiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjxwYXRoCiAgICAgICAgICAgICBkPSJtIDM4MC42ODgsNjIyLjEyOSAtMTEuNjI2LDM1LjgwMSAxNzUuNzU4LDU3LjA5IDExLjYyMSwtMzUuODAxIC0xNzUuNzUzLC01Ny4wOSIKICAgICAgICAgICAgIGlua3NjYXBlOmNvbm5lY3Rvci1jdXJ2YXR1cmU9IjAiCiAgICAgICAgICAgICBpZD0icGF0aDU0IgogICAgICAgICAgICAgc3R5bGU9ImZpbGw6IzhhNDE4MjtmaWxsLW9wYWNpdHk6MTtmaWxsLXJ1bGU6bm9uemVybztzdHJva2U6bm9uZSIgLz48cGF0aAogICAgICAgICAgICAgZD0ibSA3MTYuMjg5LDM3Ni41OSAzNy42NDA2LDAgMCwxODQuODE2IC0zNy42NDA2LDAgMCwtMTg0LjgxNiB6IgogICAgICAgICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgICAgICAgIGlkPSJwYXRoNTYiCiAgICAgICAgICAgICBzdHlsZT0iZmlsbDojOGE0MTgyO2ZpbGwtb3BhY2l0eToxO2ZpbGwtcnVsZTpub256ZXJvO3N0cm9rZTpub25lIiAvPjwvZz48L2c+PC9nPjwvZz48L3N2Zz4=') no-repeat, none; -webkit-background-size: 100%; -moz-background-size: 100%; -o-background-size: 100%; background-size: 100%; display: block; float: left; width: 90px; height: 25px; }
10 | .jasmine_html-reporter .banner .version { margin-left: 14px; position: relative; top: 6px; }
11 | .jasmine_html-reporter .banner .duration { position: absolute; right: 14px; top: 6px; }
12 | .jasmine_html-reporter #jasmine_content { position: fixed; right: 100%; }
13 | .jasmine_html-reporter .version { color: #aaaaaa; }
14 | .jasmine_html-reporter .banner { margin-top: 14px; }
15 | .jasmine_html-reporter .duration { color: #aaaaaa; float: right; }
16 | .jasmine_html-reporter .symbol-summary { overflow: hidden; *zoom: 1; margin: 14px 0; }
17 | .jasmine_html-reporter .symbol-summary li { display: inline-block; height: 8px; width: 14px; font-size: 16px; }
18 | .jasmine_html-reporter .symbol-summary li.passed { font-size: 14px; }
19 | .jasmine_html-reporter .symbol-summary li.passed:before { color: #007069; content: "\02022"; }
20 | .jasmine_html-reporter .symbol-summary li.failed { line-height: 9px; }
21 | .jasmine_html-reporter .symbol-summary li.failed:before { color: #ca3a11; content: "\d7"; font-weight: bold; margin-left: -1px; }
22 | .jasmine_html-reporter .symbol-summary li.disabled { font-size: 14px; }
23 | .jasmine_html-reporter .symbol-summary li.disabled:before { color: #bababa; content: "\02022"; }
24 | .jasmine_html-reporter .symbol-summary li.pending { line-height: 17px; }
25 | .jasmine_html-reporter .symbol-summary li.pending:before { color: #ba9d37; content: "*"; }
26 | .jasmine_html-reporter .symbol-summary li.empty { font-size: 14px; }
27 | .jasmine_html-reporter .symbol-summary li.empty:before { color: #ba9d37; content: "\02022"; }
28 | .jasmine_html-reporter .exceptions { color: #fff; float: right; margin-top: 5px; margin-right: 5px; }
29 | .jasmine_html-reporter .bar { line-height: 28px; font-size: 14px; display: block; color: #eee; }
30 | .jasmine_html-reporter .bar.failed { background-color: #ca3a11; }
31 | .jasmine_html-reporter .bar.passed { background-color: #007069; }
32 | .jasmine_html-reporter .bar.skipped { background-color: #bababa; }
33 | .jasmine_html-reporter .bar.errored { background-color: #ca3a11; }
34 | .jasmine_html-reporter .bar.menu { background-color: #fff; color: #aaaaaa; }
35 | .jasmine_html-reporter .bar.menu a { color: #333333; }
36 | .jasmine_html-reporter .bar a { color: white; }
37 | .jasmine_html-reporter.spec-list .bar.menu.failure-list, .jasmine_html-reporter.spec-list .results .failures { display: none; }
38 | .jasmine_html-reporter.failure-list .bar.menu.spec-list, .jasmine_html-reporter.failure-list .summary { display: none; }
39 | .jasmine_html-reporter .running-alert { background-color: #666666; }
40 | .jasmine_html-reporter .results { margin-top: 14px; }
41 | .jasmine_html-reporter.showDetails .summaryMenuItem { font-weight: normal; text-decoration: inherit; }
42 | .jasmine_html-reporter.showDetails .summaryMenuItem:hover { text-decoration: underline; }
43 | .jasmine_html-reporter.showDetails .detailsMenuItem { font-weight: bold; text-decoration: underline; }
44 | .jasmine_html-reporter.showDetails .summary { display: none; }
45 | .jasmine_html-reporter.showDetails #details { display: block; }
46 | .jasmine_html-reporter .summaryMenuItem { font-weight: bold; text-decoration: underline; }
47 | .jasmine_html-reporter .summary { margin-top: 14px; }
48 | .jasmine_html-reporter .summary ul { list-style-type: none; margin-left: 14px; padding-top: 0; padding-left: 0; }
49 | .jasmine_html-reporter .summary ul.suite { margin-top: 7px; margin-bottom: 7px; }
50 | .jasmine_html-reporter .summary li.passed a { color: #007069; }
51 | .jasmine_html-reporter .summary li.failed a { color: #ca3a11; }
52 | .jasmine_html-reporter .summary li.empty a { color: #ba9d37; }
53 | .jasmine_html-reporter .summary li.pending a { color: #ba9d37; }
54 | .jasmine_html-reporter .description + .suite { margin-top: 0; }
55 | .jasmine_html-reporter .suite { margin-top: 14px; }
56 | .jasmine_html-reporter .suite a { color: #333333; }
57 | .jasmine_html-reporter .failures .spec-detail { margin-bottom: 28px; }
58 | .jasmine_html-reporter .failures .spec-detail .description { background-color: #ca3a11; }
59 | .jasmine_html-reporter .failures .spec-detail .description a { color: white; }
60 | .jasmine_html-reporter .result-message { padding-top: 14px; color: #333333; white-space: pre; }
61 | .jasmine_html-reporter .result-message span.result { display: block; }
62 | .jasmine_html-reporter .stack-trace { margin: 5px 0 0 0; max-height: 224px; overflow: auto; line-height: 18px; color: #666666; border: 1px solid #ddd; background: white; white-space: pre; }
63 |
--------------------------------------------------------------------------------
/test/spec/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "undef" : true,
3 | "unused" : false,
4 | "strict" : false,
5 | "curly" : false,
6 | "newcap" : true,
7 | "trailing" : true,
8 | "white": false,
9 | "quotmark": false,
10 | "browser": true,
11 | "boss": true,
12 | "loopfunc": true,
13 | "predef" : [
14 | "module",
15 | "require",
16 | "define",
17 | "describe",
18 | "it",
19 | "expect"
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------