commit
						891a44624e
					
				@ -0,0 +1,18 @@ | 
				
			|||||||
 | 
					{ | 
				
			||||||
 | 
					  "presets": [ | 
				
			||||||
 | 
					    ["env", { | 
				
			||||||
 | 
					      "targets": { | 
				
			||||||
 | 
					        "browsers": [ | 
				
			||||||
 | 
					          "last 2 versions", | 
				
			||||||
 | 
					          "> 4%", | 
				
			||||||
 | 
					          "ie 11", | 
				
			||||||
 | 
					          "safari 8", | 
				
			||||||
 | 
					          "android 4.4" | 
				
			||||||
 | 
					        ] | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    }], | 
				
			||||||
 | 
					    ["minify", { | 
				
			||||||
 | 
					      "mergeVars": false | 
				
			||||||
 | 
					    }] | 
				
			||||||
 | 
					  ] | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,8 @@ | 
				
			|||||||
 | 
					# possibly minified output | 
				
			||||||
 | 
					out/**/* | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# libraries | 
				
			||||||
 | 
					js/lib/* | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# php generated file | 
				
			||||||
 | 
					js/lang.js | 
				
			||||||
@ -0,0 +1,191 @@ | 
				
			|||||||
 | 
					{ | 
				
			||||||
 | 
					  "parserOptions": { | 
				
			||||||
 | 
					    "ecmaVersion": 8, | 
				
			||||||
 | 
					    "ecmaFeatures": { | 
				
			||||||
 | 
					      "experimentalObjectRestSpread": true, | 
				
			||||||
 | 
					      "jsx": true | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					    "sourceType": "module" | 
				
			||||||
 | 
					  }, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  "env": { | 
				
			||||||
 | 
					    "es6": true, | 
				
			||||||
 | 
					    "node": true | 
				
			||||||
 | 
					  }, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  "plugins": [ | 
				
			||||||
 | 
					    "import", | 
				
			||||||
 | 
					    "node", | 
				
			||||||
 | 
					    "promise", | 
				
			||||||
 | 
					    "standard" | 
				
			||||||
 | 
					  ], | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  "globals": { | 
				
			||||||
 | 
					    "document": false, | 
				
			||||||
 | 
					    "navigator": false, | 
				
			||||||
 | 
					    "window": false | 
				
			||||||
 | 
					  }, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  "rules": { | 
				
			||||||
 | 
					    "accessor-pairs": "error", | 
				
			||||||
 | 
					    "arrow-spacing": ["error", { "before": true, "after": true }], | 
				
			||||||
 | 
					    "block-spacing": ["error", "always"], | 
				
			||||||
 | 
					    "brace-style": ["warn", "1tbs", { "allowSingleLine": true }], | 
				
			||||||
 | 
					    "camelcase": ["off", { "properties": "never" }], | 
				
			||||||
 | 
					    "comma-dangle": ["error", { | 
				
			||||||
 | 
					      "arrays": "never", | 
				
			||||||
 | 
					      "objects": "never", | 
				
			||||||
 | 
					      "imports": "never", | 
				
			||||||
 | 
					      "exports": "never", | 
				
			||||||
 | 
					      "functions": "never" | 
				
			||||||
 | 
					    }], | 
				
			||||||
 | 
					    "comma-spacing": ["error", { "before": false, "after": true }], | 
				
			||||||
 | 
					    "comma-style": ["error", "last"], | 
				
			||||||
 | 
					    "constructor-super": "error", | 
				
			||||||
 | 
					    "curly": ["error", "multi-line"], | 
				
			||||||
 | 
					    "dot-location": ["error", "property"], | 
				
			||||||
 | 
					    "eol-last": "error", | 
				
			||||||
 | 
					    "eqeqeq": ["error", "smart"], | 
				
			||||||
 | 
					    "func-call-spacing": ["error", "never"], | 
				
			||||||
 | 
					    "generator-star-spacing": ["error", { "before": true, "after": true }], | 
				
			||||||
 | 
					    "handle-callback-err": ["error", "^(err|error)$" ], | 
				
			||||||
 | 
					    "indent": ["error", 2, { "SwitchCase": 1 }], | 
				
			||||||
 | 
					    "key-spacing": ["error", { "beforeColon": false, "afterColon": true }], | 
				
			||||||
 | 
					    "keyword-spacing": ["error", { "before": true, "after": true }], | 
				
			||||||
 | 
					    "new-cap": ["error", { "newIsCap": true, "capIsNew": false }], | 
				
			||||||
 | 
					    "new-parens": "error", | 
				
			||||||
 | 
					    "no-array-constructor": "error", | 
				
			||||||
 | 
					    "no-caller": "error", | 
				
			||||||
 | 
					    "no-class-assign": "error", | 
				
			||||||
 | 
					    "no-compare-neg-zero": "error", | 
				
			||||||
 | 
					    "no-cond-assign": "error", | 
				
			||||||
 | 
					    "no-const-assign": "error", | 
				
			||||||
 | 
					    "no-constant-condition": ["error", { "checkLoops": false }], | 
				
			||||||
 | 
					    "no-control-regex": "error", | 
				
			||||||
 | 
					    "no-debugger": "error", | 
				
			||||||
 | 
					    "no-delete-var": "error", | 
				
			||||||
 | 
					    "no-dupe-args": "error", | 
				
			||||||
 | 
					    "no-dupe-class-members": "error", | 
				
			||||||
 | 
					    "no-dupe-keys": "error", | 
				
			||||||
 | 
					    "no-duplicate-case": "error", | 
				
			||||||
 | 
					    "no-empty-character-class": "error", | 
				
			||||||
 | 
					    "no-empty-pattern": "error", | 
				
			||||||
 | 
					    "no-eval": "error", | 
				
			||||||
 | 
					    "no-ex-assign": "error", | 
				
			||||||
 | 
					    "no-extend-native": "warn", | 
				
			||||||
 | 
					    "no-extra-bind": "error", | 
				
			||||||
 | 
					    "no-extra-boolean-cast": "error", | 
				
			||||||
 | 
					    "no-extra-parens": ["error", "functions"], | 
				
			||||||
 | 
					    "no-fallthrough": "error", | 
				
			||||||
 | 
					    "no-floating-decimal": "error", | 
				
			||||||
 | 
					    "no-func-assign": "error", | 
				
			||||||
 | 
					    "no-global-assign": "error", | 
				
			||||||
 | 
					    "no-implied-eval": "error", | 
				
			||||||
 | 
					    "no-inner-declarations": ["error", "functions"], | 
				
			||||||
 | 
					    "no-invalid-regexp": "error", | 
				
			||||||
 | 
					    "no-irregular-whitespace": "error", | 
				
			||||||
 | 
					    "no-iterator": "error", | 
				
			||||||
 | 
					    "no-label-var": "error", | 
				
			||||||
 | 
					    "no-labels": ["error", { "allowLoop": false, "allowSwitch": false }], | 
				
			||||||
 | 
					    "no-lone-blocks": "warn", | 
				
			||||||
 | 
					    "no-mixed-operators": ["error", { | 
				
			||||||
 | 
					      "groups": [ | 
				
			||||||
 | 
					        ["==", "!=", "===", "!==", ">", ">=", "<", "<="], | 
				
			||||||
 | 
					        ["&&", "||"], | 
				
			||||||
 | 
					        ["in", "instanceof"] | 
				
			||||||
 | 
					      ], | 
				
			||||||
 | 
					      "allowSamePrecedence": true | 
				
			||||||
 | 
					    }], | 
				
			||||||
 | 
					    "no-mixed-spaces-and-tabs": "error", | 
				
			||||||
 | 
					    "no-multi-spaces": "warn", | 
				
			||||||
 | 
					    "no-multi-str": "error", | 
				
			||||||
 | 
					    "no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 0 }], | 
				
			||||||
 | 
					    "no-negated-in-lhs": "error", | 
				
			||||||
 | 
					    "no-new": "error", | 
				
			||||||
 | 
					    "no-new-func": "error", | 
				
			||||||
 | 
					    "no-new-object": "error", | 
				
			||||||
 | 
					    "no-new-require": "error", | 
				
			||||||
 | 
					    "no-new-symbol": "error", | 
				
			||||||
 | 
					    "no-new-wrappers": "error", | 
				
			||||||
 | 
					    "no-obj-calls": "error", | 
				
			||||||
 | 
					    "no-octal": "error", | 
				
			||||||
 | 
					    "no-octal-escape": "error", | 
				
			||||||
 | 
					    "no-path-concat": "error", | 
				
			||||||
 | 
					    "no-proto": "error", | 
				
			||||||
 | 
					    "no-redeclare": "error", | 
				
			||||||
 | 
					    "no-regex-spaces": "error", | 
				
			||||||
 | 
					    "no-return-assign": ["error", "except-parens"], | 
				
			||||||
 | 
					    "no-return-await": "error", | 
				
			||||||
 | 
					    "no-self-assign": "error", | 
				
			||||||
 | 
					    "no-self-compare": "error", | 
				
			||||||
 | 
					    "no-sequences": "error", | 
				
			||||||
 | 
					    "no-shadow-restricted-names": "error", | 
				
			||||||
 | 
					    "no-sparse-arrays": "error", | 
				
			||||||
 | 
					    "no-tabs": "error", | 
				
			||||||
 | 
					    "no-template-curly-in-string": "error", | 
				
			||||||
 | 
					    "no-this-before-super": "error", | 
				
			||||||
 | 
					    "no-throw-literal": "error", | 
				
			||||||
 | 
					    "no-trailing-spaces": "off", | 
				
			||||||
 | 
					    "no-undef": "off", | 
				
			||||||
 | 
					    "no-undef-init": "error", | 
				
			||||||
 | 
					    "no-unexpected-multiline": "error", | 
				
			||||||
 | 
					    "no-unmodified-loop-condition": "error", | 
				
			||||||
 | 
					    "no-unneeded-ternary": ["error", { "defaultAssignment": false }], | 
				
			||||||
 | 
					    "no-unreachable": "error", | 
				
			||||||
 | 
					    "no-unsafe-finally": "error", | 
				
			||||||
 | 
					    "no-unsafe-negation": "error", | 
				
			||||||
 | 
					    "no-unused-expressions": ["warn", { "allowShortCircuit": true, "allowTernary": true, "allowTaggedTemplates": true }], | 
				
			||||||
 | 
					    "no-unused-vars": ["off", { "vars": "local", "args": "none", "ignoreRestSiblings": true }], | 
				
			||||||
 | 
					    "no-use-before-define": ["error", { "functions": false, "classes": false, "variables": false }], | 
				
			||||||
 | 
					    "no-useless-call": "error", | 
				
			||||||
 | 
					    "no-useless-computed-key": "error", | 
				
			||||||
 | 
					    "no-useless-constructor": "error", | 
				
			||||||
 | 
					    "no-useless-escape": "error", | 
				
			||||||
 | 
					    "no-useless-rename": "error", | 
				
			||||||
 | 
					    "no-useless-return": "error", | 
				
			||||||
 | 
					    "no-whitespace-before-property": "error", | 
				
			||||||
 | 
					    "no-with": "error", | 
				
			||||||
 | 
					    "object-property-newline": ["error", { "allowMultiplePropertiesPerLine": true }], | 
				
			||||||
 | 
					    "one-var": ["error", { "initialized": "never" }], | 
				
			||||||
 | 
					    "operator-linebreak": ["error", "after", { "overrides": { "?": "before", ":": "before" } }], | 
				
			||||||
 | 
					    "padded-blocks": ["error", { "blocks": "never", "switches": "never", "classes": "never" }], | 
				
			||||||
 | 
					    "prefer-promise-reject-errors": "error", | 
				
			||||||
 | 
					    "quotes": ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": true }], | 
				
			||||||
 | 
					    "rest-spread-spacing": ["error", "never"], | 
				
			||||||
 | 
					    "semi": ["error", "never"], | 
				
			||||||
 | 
					    "semi-spacing": ["error", { "before": false, "after": true }], | 
				
			||||||
 | 
					    "space-before-blocks": ["error", "always"], | 
				
			||||||
 | 
					    "space-before-function-paren": ["error", "always"], | 
				
			||||||
 | 
					    "space-in-parens": ["error", "never"], | 
				
			||||||
 | 
					    "space-infix-ops": "error", | 
				
			||||||
 | 
					    "space-unary-ops": ["error", { "words": true, "nonwords": false }], | 
				
			||||||
 | 
					    "spaced-comment": ["error", "always", { | 
				
			||||||
 | 
					      "line": { "markers": ["*package", "!", "/", ","] }, | 
				
			||||||
 | 
					      "block": { "balanced": true, "markers": ["*package", "!", ",", ":", "::", "flow-include"], "exceptions": ["*"] } | 
				
			||||||
 | 
					    }], | 
				
			||||||
 | 
					    "symbol-description": "error", | 
				
			||||||
 | 
					    "template-curly-spacing": ["error", "never"], | 
				
			||||||
 | 
					    "template-tag-spacing": ["error", "never"], | 
				
			||||||
 | 
					    "unicode-bom": ["error", "never"], | 
				
			||||||
 | 
					    "use-isnan": "error", | 
				
			||||||
 | 
					    "valid-typeof": ["error", { "requireStringLiterals": true }], | 
				
			||||||
 | 
					    "wrap-iife": ["error", "any", { "functionPrototypeMethods": true }], | 
				
			||||||
 | 
					    "yield-star-spacing": ["error", "both"], | 
				
			||||||
 | 
					    "yoda": ["error", "never"], | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "import/export": "error", | 
				
			||||||
 | 
					    "import/first": "error", | 
				
			||||||
 | 
					    "import/no-duplicates": "error", | 
				
			||||||
 | 
					    "import/no-webpack-loader-syntax": "error", | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "node/no-deprecated-api": "error", | 
				
			||||||
 | 
					    "node/process-exit-as-throw": "error", | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "promise/param-names": "error", | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    "standard/array-bracket-even-spacing": ["error", "either"], | 
				
			||||||
 | 
					    "standard/computed-property-even-spacing": ["error", "even"], | 
				
			||||||
 | 
					    "standard/no-callback-literal": "error", | 
				
			||||||
 | 
					    "standard/object-curly-even-spacing": ["error", "either"] | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,16 @@ | 
				
			|||||||
 | 
					#!/bin/bash | 
				
			||||||
 | 
					source "_build_common.sh" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					echo 'Copying resources...' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cp -r img out/img | 
				
			||||||
 | 
					cp favicon.ico out/ | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [[ $ESP_PROD ]]; then | 
				
			||||||
 | 
						echo 'Cleaning junk files...' | 
				
			||||||
 | 
						find out/ -name "*.orig" -delete | 
				
			||||||
 | 
						find out/ -name "*.xcf"  -delete | 
				
			||||||
 | 
						find out/ -name "*~"     -delete | 
				
			||||||
 | 
						find out/ -name "*.bak"  -delete | 
				
			||||||
 | 
						find out/ -name "*.map"  -delete | 
				
			||||||
 | 
					fi | 
				
			||||||
@ -0,0 +1,3 @@ | 
				
			|||||||
 | 
					#!/bin/bash | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export FRONT_END_HASH=$(git rev-parse --short HEAD) | 
				
			||||||
@ -0,0 +1,13 @@ | 
				
			|||||||
 | 
					#!/bin/bash | 
				
			||||||
 | 
					source "_build_common.sh" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					echo 'Building CSS...' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [[ $ESP_PROD ]]; then | 
				
			||||||
 | 
						stylearg=compressed | 
				
			||||||
 | 
					else | 
				
			||||||
 | 
						stylearg=expanded | 
				
			||||||
 | 
					fi | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mkdir -p out/css | 
				
			||||||
 | 
					npm run sass -- --output-style ${stylearg} sass/app.scss "out/css/app.$FRONT_END_HASH.css" | 
				
			||||||
@ -0,0 +1,6 @@ | 
				
			|||||||
 | 
					#!/bin/bash | 
				
			||||||
 | 
					source "_build_common.sh" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					echo 'Building HTML...' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					php ./compile_html.php | 
				
			||||||
@ -0,0 +1,35 @@ | 
				
			|||||||
 | 
					#!/bin/bash | 
				
			||||||
 | 
					source "_build_common.sh" | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mkdir -p out/js | 
				
			||||||
 | 
					echo 'Generating lang.js...' | 
				
			||||||
 | 
					php ./dump_js_lang.php | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [[ $ESP_DEMO ]]; then | 
				
			||||||
 | 
						demofile=js/demo.js | 
				
			||||||
 | 
					else | 
				
			||||||
 | 
						demofile= | 
				
			||||||
 | 
					fi | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					echo 'Processing JS...' | 
				
			||||||
 | 
					if [[ $ESP_PROD ]]; then | 
				
			||||||
 | 
						smarg= | 
				
			||||||
 | 
					else | 
				
			||||||
 | 
						smarg=--source-maps | 
				
			||||||
 | 
					fi | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					npm run babel -- -o "out/js/app.$FRONT_END_HASH.js" ${smarg} \ | 
				
			||||||
 | 
					    js/lib/chibi.js \ | 
				
			||||||
 | 
					    js/lib/keymaster.js \ | 
				
			||||||
 | 
					    js/lib/polyfills.js \ | 
				
			||||||
 | 
					    js/utils.js \ | 
				
			||||||
 | 
					    js/modal.js \ | 
				
			||||||
 | 
					    js/notif.js \ | 
				
			||||||
 | 
					    js/appcommon.js \ | 
				
			||||||
 | 
					    $demofile \ | 
				
			||||||
 | 
					    js/lang.js \ | 
				
			||||||
 | 
					    js/wifi.js \ | 
				
			||||||
 | 
					    js/term_* \ | 
				
			||||||
 | 
					    js/debug_screen.js \ | 
				
			||||||
 | 
					    js/soft_keyboard.js \ | 
				
			||||||
 | 
					    js/term.js | 
				
			||||||
@ -0,0 +1,13 @@ | 
				
			|||||||
 | 
					<?php | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (preg_match('/\\/(?:js|css)/', $_SERVER["REQUEST_URI"])) { | 
				
			||||||
 | 
					  $path = pathinfo($_SERVER["REQUEST_URI"]); | 
				
			||||||
 | 
					  if ($path["extension"] == "js") { | 
				
			||||||
 | 
					    header("Content-Type: application/javascript"); | 
				
			||||||
 | 
					  } else if ($path["extension"] == "css") { | 
				
			||||||
 | 
					    header("Content-Type: text/css"); | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  readfile("out" . $_SERVER["REQUEST_URI"]); | 
				
			||||||
 | 
					} else { | 
				
			||||||
 | 
					  return false; | 
				
			||||||
 | 
					} | 
				
			||||||
@ -1,25 +1,14 @@ | 
				
			|||||||
#!/bin/bash | 
					#!/bin/bash | 
				
			||||||
 | 
					
 | 
				
			||||||
echo "Packing JS..." | 
					cd $(dirname $0) | 
				
			||||||
 | 
					
 | 
				
			||||||
cat jssrc/chibi.js \ | 
					source "_build_common.sh" | 
				
			||||||
  jssrc/keymaster.js \ | 
					 | 
				
			||||||
  jssrc/utils.js \ | 
					 | 
				
			||||||
  jssrc/modal.js \ | 
					 | 
				
			||||||
  jssrc/notif.js \ | 
					 | 
				
			||||||
  jssrc/appcommon.js \ | 
					 | 
				
			||||||
  jssrc/lang.js \ | 
					 | 
				
			||||||
  jssrc/wifi.js \ | 
					 | 
				
			||||||
  jssrc/term_* \ | 
					 | 
				
			||||||
  jssrc/term.js > js/app-full.js | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
yuicompressor js/app-full.js > js/app.js | 
					rm -fr out/* | 
				
			||||||
 | 
					
 | 
				
			||||||
echo "Building CSS..." | 
					./_build_css.sh | 
				
			||||||
 | 
					./_build_js.sh | 
				
			||||||
 | 
					./_build_html.sh | 
				
			||||||
 | 
					./_build_assets.sh | 
				
			||||||
 | 
					
 | 
				
			||||||
sass --style=compressed sass/app.scss css/app.css | 
					echo 'ESPTerm front-end ready' | 
				
			||||||
 | 
					 | 
				
			||||||
echo "Building HTML..." | 
					 | 
				
			||||||
php ./build_html.php | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
echo "ESPTerm front-end ready" | 
					 | 
				
			||||||
 | 
				
			|||||||
									
										Binary file not shown.
									
								
							
						@ -0,0 +1,131 @@ | 
				
			|||||||
 | 
					/** Global generic init */ | 
				
			||||||
 | 
					$.ready(function () { | 
				
			||||||
 | 
					  // Checkbox UI (checkbox CSS and hidden input with int value)
 | 
				
			||||||
 | 
					  $('.Row.checkbox').forEach(function (x) { | 
				
			||||||
 | 
					    let inp = x.querySelector('input') | 
				
			||||||
 | 
					    let box = x.querySelector('.box') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $(box).toggleClass('checked', inp.value) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let hdl = function () { | 
				
			||||||
 | 
					      inp.value = 1 - inp.value | 
				
			||||||
 | 
					      $(box).toggleClass('checked', inp.value) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $(x).on('click', hdl).on('keypress', cr(hdl)) | 
				
			||||||
 | 
					  }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Expanding boxes on mobile
 | 
				
			||||||
 | 
					  $('.Box.mobcol,.Box.fold').forEach(function (x) { | 
				
			||||||
 | 
					    let h = x.querySelector('h2') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let hdl = function () { | 
				
			||||||
 | 
					      $(x).toggleClass('expanded') | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    $(h).on('click', hdl).on('keypress', cr(hdl)) | 
				
			||||||
 | 
					  }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  $('form').forEach(function (x) { | 
				
			||||||
 | 
					    $(x).on('keypress', function (e) { | 
				
			||||||
 | 
					      if ((e.keyCode === 10 || e.keyCode === 13) && e.ctrlKey) { | 
				
			||||||
 | 
					        x.submit() | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					  }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // loader dots...
 | 
				
			||||||
 | 
					  setInterval(function () { | 
				
			||||||
 | 
					    $('.anim-dots').each(function (x) { | 
				
			||||||
 | 
					      let $x = $(x) | 
				
			||||||
 | 
					      let dots = $x.html() + '.' | 
				
			||||||
 | 
					      if (dots.length === 5) dots = '.' | 
				
			||||||
 | 
					      $x.html(dots) | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					  }, 1000) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // flipping number boxes with the mouse wheel
 | 
				
			||||||
 | 
					  $('input[type=number]').on('mousewheel', function (e) { | 
				
			||||||
 | 
					    let $this = $(this) | 
				
			||||||
 | 
					    let val = +$this.val() | 
				
			||||||
 | 
					    if (isNaN(val)) val = 1 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const step = +($this.attr('step') || 1) | 
				
			||||||
 | 
					    const min = +$this.attr('min') | 
				
			||||||
 | 
					    const max = +$this.attr('max') | 
				
			||||||
 | 
					    if (e.wheelDelta > 0) { | 
				
			||||||
 | 
					      val += step | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					      val -= step | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (undef(min)) val = Math.max(val, +min) | 
				
			||||||
 | 
					    if (undef(max)) val = Math.min(val, +max) | 
				
			||||||
 | 
					    $this.val(val) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ('createEvent' in document) { | 
				
			||||||
 | 
					      let evt = document.createEvent('HTMLEvents') | 
				
			||||||
 | 
					      evt.initEvent('change', false, true) | 
				
			||||||
 | 
					      $this[0].dispatchEvent(evt) | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					      $this[0].fireEvent('onchange') | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    e.preventDefault() | 
				
			||||||
 | 
					  }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // populate the form errors box from GET arg ?err=...
 | 
				
			||||||
 | 
					  // (a way to pass errors back from server via redirect)
 | 
				
			||||||
 | 
					  let errAt = location.search.indexOf('err=') | 
				
			||||||
 | 
					  if (errAt !== -1 && qs('.Box.errors')) { | 
				
			||||||
 | 
					    let errs = location.search.substr(errAt + 4).split(',') | 
				
			||||||
 | 
					    let humanReadableErrors = [] | 
				
			||||||
 | 
					    errs.forEach(function (er) { | 
				
			||||||
 | 
					      let lbl = qs('label[for="' + er + '"]') | 
				
			||||||
 | 
					      if (lbl) { | 
				
			||||||
 | 
					        lbl.classList.add('error') | 
				
			||||||
 | 
					        humanReadableErrors.push(lbl.childNodes[0].textContent.trim().replace(/: ?$/, '')) | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      // else {
 | 
				
			||||||
 | 
					      //   hres.push(er)
 | 
				
			||||||
 | 
					      // }
 | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    qs('.Box.errors .list').innerHTML = humanReadableErrors.join(', ') | 
				
			||||||
 | 
					    qs('.Box.errors').classList.remove('hidden') | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Modal.init() | 
				
			||||||
 | 
					  Notify.init() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // remove tabindixes from h2 if wide
 | 
				
			||||||
 | 
					  if (window.innerWidth > 550) { | 
				
			||||||
 | 
					    $('.Box h2').forEach(function (x) { | 
				
			||||||
 | 
					      x.removeAttribute('tabindex') | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // brand works as a link back to term in widescreen mode
 | 
				
			||||||
 | 
					    let br = qs('#brand') | 
				
			||||||
 | 
					    br && br.addEventListener('click', function () { | 
				
			||||||
 | 
					      location.href = '/' // go to terminal
 | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					}) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// setup the ajax loader
 | 
				
			||||||
 | 
					$._loader = function (vis) { | 
				
			||||||
 | 
					  $('#loader').toggleClass('show', vis) | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// reveal content on load
 | 
				
			||||||
 | 
					function showPage () { | 
				
			||||||
 | 
					  $('#content').addClass('load') | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Auto reveal pages other than the terminal (sets window.noAutoShow)
 | 
				
			||||||
 | 
					$.ready(function () { | 
				
			||||||
 | 
					  if (window.noAutoShow !== true) { | 
				
			||||||
 | 
					    setTimeout(function () { | 
				
			||||||
 | 
					      showPage() | 
				
			||||||
 | 
					    }, 1) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					}) | 
				
			||||||
@ -0,0 +1,106 @@ | 
				
			|||||||
 | 
					window.attachDebugScreen = function (screen) { | 
				
			||||||
 | 
					  const debugCanvas = mk('canvas') | 
				
			||||||
 | 
					  const ctx = debugCanvas.getContext('2d') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  debugCanvas.style.position = 'absolute' | 
				
			||||||
 | 
					  // hackity hack should probably set this in CSS
 | 
				
			||||||
 | 
					  debugCanvas.style.top = '6px' | 
				
			||||||
 | 
					  debugCanvas.style.left = '6px' | 
				
			||||||
 | 
					  debugCanvas.style.pointerEvents = 'none' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let addCanvas = function () { | 
				
			||||||
 | 
					    if (!debugCanvas.parentNode) screen.canvas.parentNode.appendChild(debugCanvas) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  let removeCanvas = function () { | 
				
			||||||
 | 
					    if (debugCanvas.parentNode) debugCanvas.parentNode.removeChild(debugCanvas) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  let updateCanvasSize = function () { | 
				
			||||||
 | 
					    let { width, height, devicePixelRatio } = screen.window | 
				
			||||||
 | 
					    let cellSize = screen.getCellSize() | 
				
			||||||
 | 
					    debugCanvas.width = width * cellSize.width * devicePixelRatio | 
				
			||||||
 | 
					    debugCanvas.height = height * cellSize.height * devicePixelRatio | 
				
			||||||
 | 
					    debugCanvas.style.width = `${width * cellSize.width}px` | 
				
			||||||
 | 
					    debugCanvas.style.height = `${height * cellSize.height}px` | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let startTime, endTime, lastReason | 
				
			||||||
 | 
					  let cells = new Map() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let startDrawing | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  screen._debug = { | 
				
			||||||
 | 
					    drawStart (reason) { | 
				
			||||||
 | 
					      lastReason = reason | 
				
			||||||
 | 
					      startTime = Date.now() | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					    drawEnd () { | 
				
			||||||
 | 
					      endTime = Date.now() | 
				
			||||||
 | 
					      console.log(`Draw: ${lastReason} (${(endTime - startTime)} ms) with fancy graphics: ${screen.window.graphics}`) | 
				
			||||||
 | 
					      startDrawing() | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					    setCell (cell, flags) { | 
				
			||||||
 | 
					      cells.set(cell, [flags, Date.now()]) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let isDrawing = false | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let drawLoop = function () { | 
				
			||||||
 | 
					    if (isDrawing) requestAnimationFrame(drawLoop) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let { devicePixelRatio, width, height } = screen.window | 
				
			||||||
 | 
					    let { width: cellWidth, height: cellHeight } = screen.getCellSize() | 
				
			||||||
 | 
					    let screenLength = width * height | 
				
			||||||
 | 
					    let now = Date.now() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0) | 
				
			||||||
 | 
					    ctx.clearRect(0, 0, width * cellWidth, height * cellHeight) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let activeCells = 0 | 
				
			||||||
 | 
					    for (let cell = 0; cell < screenLength; cell++) { | 
				
			||||||
 | 
					      if (!cells.has(cell) || cells.get(cell)[0] === 0) continue | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let [flags, timestamp] = cells.get(cell) | 
				
			||||||
 | 
					      let elapsedTime = (now - timestamp) / 1000 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (elapsedTime > 1) continue | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      activeCells++ | 
				
			||||||
 | 
					      ctx.globalAlpha = 0.5 * Math.max(0, 1 - elapsedTime) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let x = cell % width | 
				
			||||||
 | 
					      let y = Math.floor(cell / width) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (flags & 1) { | 
				
			||||||
 | 
					        // redrawn
 | 
				
			||||||
 | 
					        ctx.fillStyle = '#f0f' | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      if (flags & 2) { | 
				
			||||||
 | 
					        // updated
 | 
				
			||||||
 | 
					        ctx.fillStyle = '#0f0' | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      ctx.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (flags & 4) { | 
				
			||||||
 | 
					        // wide cell
 | 
				
			||||||
 | 
					        ctx.lineWidth = 2 | 
				
			||||||
 | 
					        ctx.strokeStyle = '#f00' | 
				
			||||||
 | 
					        ctx.strokeRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight) | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (activeCells === 0) { | 
				
			||||||
 | 
					      isDrawing = false | 
				
			||||||
 | 
					      removeCanvas() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  startDrawing = function () { | 
				
			||||||
 | 
					    if (isDrawing) return | 
				
			||||||
 | 
					    addCanvas() | 
				
			||||||
 | 
					    updateCanvasSize() | 
				
			||||||
 | 
					    isDrawing = true | 
				
			||||||
 | 
					    drawLoop() | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,758 @@ | 
				
			|||||||
 | 
					class ANSIParser { | 
				
			||||||
 | 
					  constructor (handler) { | 
				
			||||||
 | 
					    this.reset() | 
				
			||||||
 | 
					    this.handler = handler | 
				
			||||||
 | 
					    this.joinChunks = true | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  reset () { | 
				
			||||||
 | 
					    this.currentSequence = 0 | 
				
			||||||
 | 
					    this.sequence = '' | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  parseSequence (sequence) { | 
				
			||||||
 | 
					    if (sequence[0] === '[') { | 
				
			||||||
 | 
					      let type = sequence[sequence.length - 1] | 
				
			||||||
 | 
					      let content = sequence.substring(1, sequence.length - 1) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let numbers = content ? content.split(';').map(i => +i.replace(/\D/g, '')) : [] | 
				
			||||||
 | 
					      let numOr1 = numbers.length ? numbers[0] : 1 | 
				
			||||||
 | 
					      if (type === 'H') { | 
				
			||||||
 | 
					        this.handler('set-cursor', (numbers[0] | 0) - 1, (numbers[1] | 0) - 1) | 
				
			||||||
 | 
					      } else if (type >= 'A' && type <= 'D') { | 
				
			||||||
 | 
					        this.handler(`move-cursor-${type <= 'B' ? 'y' : 'x'}`, ((type === 'B' || type === 'C') ? 1 : -1) * numOr1) | 
				
			||||||
 | 
					      } else if (type === 'E' || type === 'F') { | 
				
			||||||
 | 
					        this.handler('move-cursor-line', (type === 'E' ? 1 : -1) * numOr1) | 
				
			||||||
 | 
					      } else if (type === 'G') { | 
				
			||||||
 | 
					        this.handler('set-cursor-x', numOr1 - 1) | 
				
			||||||
 | 
					      } else if (type === 'J') { | 
				
			||||||
 | 
					        let number = numbers.length ? numbers[0] : 2 | 
				
			||||||
 | 
					        if (number === 2) this.handler('clear') | 
				
			||||||
 | 
					      } else if (type === 'P') { | 
				
			||||||
 | 
					        this.handler('delete', numOr1) | 
				
			||||||
 | 
					      } else if (type === '@') { | 
				
			||||||
 | 
					        this.handler('insert-blanks', numOr1) | 
				
			||||||
 | 
					      } else if (type === 'q') this.handler('set-cursor-style', numOr1) | 
				
			||||||
 | 
					      else if (type === 'm') { | 
				
			||||||
 | 
					        if (!numbers.length || numbers[0] === 0) { | 
				
			||||||
 | 
					          this.handler('reset-style') | 
				
			||||||
 | 
					          return | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        let type = numbers[0] | 
				
			||||||
 | 
					        if (type === 1) this.handler('add-attrs', 1) // bold
 | 
				
			||||||
 | 
					        else if (type === 2) this.handler('add-attrs', 1 << 1) // faint
 | 
				
			||||||
 | 
					        else if (type === 3) this.handler('add-attrs', 1 << 2) // italic
 | 
				
			||||||
 | 
					        else if (type === 4) this.handler('add-attrs', 1 << 3) // underline
 | 
				
			||||||
 | 
					        else if (type === 5 || type === 6) this.handler('add-attrs', 1 << 4) // blink
 | 
				
			||||||
 | 
					        else if (type === 7) this.handler('add-attrs', -1) // invert
 | 
				
			||||||
 | 
					        else if (type === 9) this.handler('add-attrs', 1 << 6) // strike
 | 
				
			||||||
 | 
					        else if (type === 20) this.handler('add-attrs', 1 << 5) // fraktur
 | 
				
			||||||
 | 
					        else if (type >= 30 && type <= 37) this.handler('set-color-fg', type % 10) | 
				
			||||||
 | 
					        else if (type >= 40 && type <= 47) this.handler('set-color-bg', type % 10) | 
				
			||||||
 | 
					        else if (type === 39) this.handler('set-color-fg', 7) | 
				
			||||||
 | 
					        else if (type === 49) this.handler('set-color-bg', 0) | 
				
			||||||
 | 
					        else if (type >= 90 && type <= 98) this.handler('set-color-fg', (type % 10) + 8) | 
				
			||||||
 | 
					        else if (type >= 100 && type <= 108) this.handler('set-color-bg', (type % 10) + 8) | 
				
			||||||
 | 
					        else if (type === 38 || type === 48) { | 
				
			||||||
 | 
					          if (numbers[1] === 5) { | 
				
			||||||
 | 
					            let color = (numbers[2] | 0) & 0xFF | 
				
			||||||
 | 
					            if (type === 38) this.handler('set-color-fg', color) | 
				
			||||||
 | 
					            if (type === 48) this.handler('set-color-bg', color) | 
				
			||||||
 | 
					          } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					      } else if (type === 'h' || type === 'l') { | 
				
			||||||
 | 
					        if (content === '?25') { | 
				
			||||||
 | 
					          if (type === 'h') this.handler('show-cursor') | 
				
			||||||
 | 
					          else if (type === 'l') this.handler('hide-cursor') | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  write (text) { | 
				
			||||||
 | 
					    for (let character of text.toString()) { | 
				
			||||||
 | 
					      let code = character.codePointAt(0) | 
				
			||||||
 | 
					      if (code === 0x1b) this.currentSequence = 1 | 
				
			||||||
 | 
					      else if (this.currentSequence === 1 && character === '[') { | 
				
			||||||
 | 
					        this.currentSequence = 2 | 
				
			||||||
 | 
					        this.sequence += '[' | 
				
			||||||
 | 
					      } else if (this.currentSequence && character.match(/[\x40-\x7e]/)) { | 
				
			||||||
 | 
					        this.parseSequence(this.sequence + character) | 
				
			||||||
 | 
					        this.currentSequence = 0 | 
				
			||||||
 | 
					        this.sequence = '' | 
				
			||||||
 | 
					      } else if (this.currentSequence > 1) this.sequence += character | 
				
			||||||
 | 
					      else if (this.currentSequence === 1) { | 
				
			||||||
 | 
					        // something something nothing
 | 
				
			||||||
 | 
					        this.currentSequence = 0 | 
				
			||||||
 | 
					        this.handler('write', character) | 
				
			||||||
 | 
					      } else if (code === 0x07) this.handler('bell') | 
				
			||||||
 | 
					      else if (code === 0x08) this.handler('back') | 
				
			||||||
 | 
					      else if (code === 0x0a) this.handler('new-line') | 
				
			||||||
 | 
					      else if (code === 0x0d) this.handler('return') | 
				
			||||||
 | 
					      else if (code === 0x15) this.handler('delete-line') | 
				
			||||||
 | 
					      else if (code === 0x17) this.handler('delete-word') | 
				
			||||||
 | 
					      else this.handler('write', character) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    if (!this.joinChunks) this.reset() | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					const TERM_DEFAULT_STYLE = 7 | 
				
			||||||
 | 
					const TERM_MIN_DRAW_DELAY = 10 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let getRainbowColor = t => { | 
				
			||||||
 | 
					  let r = Math.floor(Math.sin(t) * 2.5 + 2.5) | 
				
			||||||
 | 
					  let g = Math.floor(Math.sin(t + 2 / 3 * Math.PI) * 2.5 + 2.5) | 
				
			||||||
 | 
					  let b = Math.floor(Math.sin(t + 4 / 3 * Math.PI) * 2.5 + 2.5) | 
				
			||||||
 | 
					  return 16 + 36 * r + 6 * g + b | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ScrollingTerminal { | 
				
			||||||
 | 
					  constructor (screen) { | 
				
			||||||
 | 
					    this.width = 80 | 
				
			||||||
 | 
					    this.height = 25 | 
				
			||||||
 | 
					    this.termScreen = screen | 
				
			||||||
 | 
					    this.parser = new ANSIParser((...args) => this.handleParsed(...args)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.reset() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this._lastLoad = Date.now() | 
				
			||||||
 | 
					    this.termScreen.load(this.serialize(), 0) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  reset () { | 
				
			||||||
 | 
					    this.style = TERM_DEFAULT_STYLE | 
				
			||||||
 | 
					    this.cursor = { x: 0, y: 0, style: 1, visible: true } | 
				
			||||||
 | 
					    this.trackMouse = false | 
				
			||||||
 | 
					    this.theme = 0 | 
				
			||||||
 | 
					    this.rainbow = false | 
				
			||||||
 | 
					    this.parser.reset() | 
				
			||||||
 | 
					    this.clear() | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  clear () { | 
				
			||||||
 | 
					    this.screen = [] | 
				
			||||||
 | 
					    for (let i = 0; i < this.width * this.height; i++) { | 
				
			||||||
 | 
					      this.screen.push([' ', this.style]) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  scroll () { | 
				
			||||||
 | 
					    this.screen.splice(0, this.width) | 
				
			||||||
 | 
					    for (let i = 0; i < this.width; i++) { | 
				
			||||||
 | 
					      this.screen.push([' ', TERM_DEFAULT_STYLE]) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    this.cursor.y-- | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  newLine () { | 
				
			||||||
 | 
					    this.cursor.y++ | 
				
			||||||
 | 
					    if (this.cursor.y >= this.height) this.scroll() | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  writeChar (character) { | 
				
			||||||
 | 
					    this.screen[this.cursor.y * this.width + this.cursor.x] = [character, this.style] | 
				
			||||||
 | 
					    this.cursor.x++ | 
				
			||||||
 | 
					    if (this.cursor.x >= this.width) { | 
				
			||||||
 | 
					      this.cursor.x = 0 | 
				
			||||||
 | 
					      this.newLine() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  moveBack (n = 1) { | 
				
			||||||
 | 
					    for (let i = 0; i < n; i++) { | 
				
			||||||
 | 
					      this.cursor.x-- | 
				
			||||||
 | 
					      if (this.cursor.x < 0) { | 
				
			||||||
 | 
					        if (this.cursor.y > 0) this.cursor.x = this.width - 1 | 
				
			||||||
 | 
					        else this.cursor.x = 0 | 
				
			||||||
 | 
					        this.cursor.y = Math.max(0, this.cursor.y - 1) | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  moveForward (n = 1) { | 
				
			||||||
 | 
					    for (let i = 0; i < n; i++) { | 
				
			||||||
 | 
					      this.cursor.x++ | 
				
			||||||
 | 
					      if (this.cursor.x >= this.width) { | 
				
			||||||
 | 
					        this.cursor.x = 0 | 
				
			||||||
 | 
					        this.cursor.y++ | 
				
			||||||
 | 
					        if (this.cursor.y >= this.height) this.scroll() | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  deleteChar () { | 
				
			||||||
 | 
					    this.moveBack() | 
				
			||||||
 | 
					    this.screen.splice((this.cursor.y + 1) * this.width, 0, [' ', TERM_DEFAULT_STYLE]) | 
				
			||||||
 | 
					    this.screen.splice(this.cursor.y * this.width + this.cursor.x, 1) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  deleteForward (n) { | 
				
			||||||
 | 
					    n = Math.min(this.width, n) | 
				
			||||||
 | 
					    for (let i = 0; i < n; i++) this.screen.splice((this.cursor.y + 1) * this.width, 0, [' ', TERM_DEFAULT_STYLE]) | 
				
			||||||
 | 
					    this.screen.splice(this.cursor.y * this.width + this.cursor.x, n) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  clampCursor () { | 
				
			||||||
 | 
					    if (this.cursor.x < 0) this.cursor.x = 0 | 
				
			||||||
 | 
					    if (this.cursor.y < 0) this.cursor.y = 0 | 
				
			||||||
 | 
					    if (this.cursor.x > this.width - 1) this.cursor.x = this.width - 1 | 
				
			||||||
 | 
					    if (this.cursor.y > this.height - 1) this.cursor.y = this.height - 1 | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  handleParsed (action, ...args) { | 
				
			||||||
 | 
					    if (action === 'write') { | 
				
			||||||
 | 
					      this.writeChar(args[0]) | 
				
			||||||
 | 
					    } else if (action === 'delete') { | 
				
			||||||
 | 
					      this.deleteForward(args[0]) | 
				
			||||||
 | 
					    } else if (action === 'insert-blanks') { | 
				
			||||||
 | 
					      this.insertBlanks(args[0]) | 
				
			||||||
 | 
					    } else if (action === 'clear') { | 
				
			||||||
 | 
					      this.clear() | 
				
			||||||
 | 
					    } else if (action === 'bell') { | 
				
			||||||
 | 
					      this.terminal.load('B') | 
				
			||||||
 | 
					    } else if (action === 'back') { | 
				
			||||||
 | 
					      this.moveBack() | 
				
			||||||
 | 
					    } else if (action === 'new-line') { | 
				
			||||||
 | 
					      this.newLine() | 
				
			||||||
 | 
					    } else if (action === 'return') { | 
				
			||||||
 | 
					      this.cursor.x = 0 | 
				
			||||||
 | 
					    } else if (action === 'set-cursor') { | 
				
			||||||
 | 
					      this.cursor.x = args[0] | 
				
			||||||
 | 
					      this.cursor.y = args[1] | 
				
			||||||
 | 
					      this.clampCursor() | 
				
			||||||
 | 
					    } else if (action === 'move-cursor-y') { | 
				
			||||||
 | 
					      this.cursor.y += args[0] | 
				
			||||||
 | 
					      this.clampCursor() | 
				
			||||||
 | 
					    } else if (action === 'move-cursor-x') { | 
				
			||||||
 | 
					      this.cursor.x += args[0] | 
				
			||||||
 | 
					      this.clampCursor() | 
				
			||||||
 | 
					    } else if (action === 'move-cursor-line') { | 
				
			||||||
 | 
					      this.cursor.x = 0 | 
				
			||||||
 | 
					      this.cursor.y += args[0] | 
				
			||||||
 | 
					      this.clampCursor() | 
				
			||||||
 | 
					    } else if (action === 'set-cursor-x') { | 
				
			||||||
 | 
					      this.cursor.x = args[0] | 
				
			||||||
 | 
					    } else if (action === 'set-cursor-style') { | 
				
			||||||
 | 
					      this.cursor.style = Math.max(0, Math.min(6, args[0])) | 
				
			||||||
 | 
					    } else if (action === 'reset-style') { | 
				
			||||||
 | 
					      this.style = TERM_DEFAULT_STYLE | 
				
			||||||
 | 
					    } else if (action === 'add-attrs') { | 
				
			||||||
 | 
					      if (args[0] === -1) { | 
				
			||||||
 | 
					        this.style = (this.style & 0xFF0000) | ((this.style >> 8) & 0xFF) | ((this.style & 0xFF) << 8) | 
				
			||||||
 | 
					      } else { | 
				
			||||||
 | 
					        this.style |= (args[0] << 16) | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } else if (action === 'set-color-fg') { | 
				
			||||||
 | 
					      this.style = (this.style & 0xFFFF00) | args[0] | 
				
			||||||
 | 
					    } else if (action === 'set-color-bg') { | 
				
			||||||
 | 
					      this.style = (this.style & 0xFF00FF) | (args[0] << 8) | 
				
			||||||
 | 
					    } else if (action === 'hide-cursor') { | 
				
			||||||
 | 
					      this.cursor.visible = false | 
				
			||||||
 | 
					    } else if (action === 'show-cursor') { | 
				
			||||||
 | 
					      this.cursor.visible = true | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  write (text) { | 
				
			||||||
 | 
					    this.parser.write(text) | 
				
			||||||
 | 
					    this.scheduleLoad() | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  serialize () { | 
				
			||||||
 | 
					    let serialized = 'S' | 
				
			||||||
 | 
					    serialized += encode2B(this.height) + encode2B(this.width) | 
				
			||||||
 | 
					    serialized += encode2B(this.cursor.y) + encode2B(this.cursor.x) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let attributes = +this.cursor.visible | 
				
			||||||
 | 
					    attributes |= (3 << 5) * +this.trackMouse // track mouse controls both
 | 
				
			||||||
 | 
					    attributes |= 3 << 7 // buttons/links always visible
 | 
				
			||||||
 | 
					    attributes |= (this.cursor.style << 9) | 
				
			||||||
 | 
					    serialized += encode3B(attributes) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let lastStyle = null | 
				
			||||||
 | 
					    let index = 0 | 
				
			||||||
 | 
					    for (let cell of this.screen) { | 
				
			||||||
 | 
					      let style = cell[1] | 
				
			||||||
 | 
					      if (this.rainbow) { | 
				
			||||||
 | 
					        let x = index % this.width | 
				
			||||||
 | 
					        let y = Math.floor(index / this.width) | 
				
			||||||
 | 
					        style = (style & 0xFF0000) | getRainbowColor((x + y) / 10 + Date.now() / 1000) | 
				
			||||||
 | 
					        index++ | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      if (style !== lastStyle) { | 
				
			||||||
 | 
					        let foreground = style & 0xFF | 
				
			||||||
 | 
					        let background = (style >> 8) & 0xFF | 
				
			||||||
 | 
					        let attributes = (style >> 16) & 0xFF | 
				
			||||||
 | 
					        let setForeground = foreground !== (lastStyle & 0xFF) | 
				
			||||||
 | 
					        let setBackground = background !== ((lastStyle >> 8) & 0xFF) | 
				
			||||||
 | 
					        let setAttributes = attributes !== ((lastStyle >> 16) & 0xFF) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (setForeground && setBackground) serialized += '\x03' + encode3B(style & 0xFFFF) | 
				
			||||||
 | 
					        else if (setForeground) serialized += '\x05' + encode2B(foreground) | 
				
			||||||
 | 
					        else if (setBackground) serialized += '\x06' + encode2B(background) | 
				
			||||||
 | 
					        if (setAttributes) serialized += '\x04' + encode2B(attributes) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        lastStyle = style | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      serialized += cell[0] | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    return serialized | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  scheduleLoad () { | 
				
			||||||
 | 
					    clearTimeout(this._scheduledLoad) | 
				
			||||||
 | 
					    if (this._lastLoad < Date.now() - TERM_MIN_DRAW_DELAY) { | 
				
			||||||
 | 
					      this.termScreen.load(this.serialize(), this.theme) | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					      this._scheduledLoad = setTimeout(() => { | 
				
			||||||
 | 
					        this.termScreen.load(this.serialize()) | 
				
			||||||
 | 
					      }, TERM_MIN_DRAW_DELAY - this._lastLoad) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  rainbowTimer () { | 
				
			||||||
 | 
					    if (!this.rainbow) return | 
				
			||||||
 | 
					    clearInterval(this._rainbowTimer) | 
				
			||||||
 | 
					    this._rainbowTimer = setInterval(() => { | 
				
			||||||
 | 
					      if (this.rainbow) this.scheduleLoad() | 
				
			||||||
 | 
					    }, 50) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Process { | 
				
			||||||
 | 
					  constructor (args) { | 
				
			||||||
 | 
					    // event listeners
 | 
				
			||||||
 | 
					    this._listeners = {} | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  on (event, listener) { | 
				
			||||||
 | 
					    if (!this._listeners[event]) this._listeners[event] = [] | 
				
			||||||
 | 
					    this._listeners[event].push({ listener }) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  once (event, listener) { | 
				
			||||||
 | 
					    if (!this._listeners[event]) this._listeners[event] = [] | 
				
			||||||
 | 
					    this._listeners[event].push({ listener, once: true }) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  off (event, listener) { | 
				
			||||||
 | 
					    let listeners = this._listeners[event] | 
				
			||||||
 | 
					    if (listeners) { | 
				
			||||||
 | 
					      for (let i in listeners) { | 
				
			||||||
 | 
					        if (listeners[i].listener === listener) { | 
				
			||||||
 | 
					          listeners.splice(i, 1) | 
				
			||||||
 | 
					          break | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  emit (event, ...args) { | 
				
			||||||
 | 
					    let listeners = this._listeners[event] | 
				
			||||||
 | 
					    if (listeners) { | 
				
			||||||
 | 
					      let remove = [] | 
				
			||||||
 | 
					      for (let listener of listeners) { | 
				
			||||||
 | 
					        try { | 
				
			||||||
 | 
					          listener.listener(...args) | 
				
			||||||
 | 
					          if (listener.once) remove.push(listener) | 
				
			||||||
 | 
					        } catch (err) { | 
				
			||||||
 | 
					          console.error(err) | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      for (let listener of remove) { | 
				
			||||||
 | 
					        listeners.splice(listeners.indexOf(listener), 1) | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  write (data) { | 
				
			||||||
 | 
					    this.emit('in', data) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  destroy () { | 
				
			||||||
 | 
					    // death.
 | 
				
			||||||
 | 
					    this.emit('exit', 0) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  run () { | 
				
			||||||
 | 
					    // noop
 | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let demoData = { | 
				
			||||||
 | 
					  buttons: { | 
				
			||||||
 | 
					    1: '', | 
				
			||||||
 | 
					    2: '', | 
				
			||||||
 | 
					    3: '', | 
				
			||||||
 | 
					    4: '', | 
				
			||||||
 | 
					    5: function (terminal, shell) { | 
				
			||||||
 | 
					      if (shell.child) shell.child.destroy() | 
				
			||||||
 | 
					      let chars = 'info\r' | 
				
			||||||
 | 
					      let loop = function () { | 
				
			||||||
 | 
					        shell.write(chars[0]) | 
				
			||||||
 | 
					        chars = chars.substr(1) | 
				
			||||||
 | 
					        if (chars) setTimeout(loop, 100) | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      setTimeout(loop, 200) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let demoshIndex = { | 
				
			||||||
 | 
					  clear: class Clear extends Process { | 
				
			||||||
 | 
					    run () { | 
				
			||||||
 | 
					      this.emit('write', '\x1b[2J\x1b[1;1H') | 
				
			||||||
 | 
					      this.destroy() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  }, | 
				
			||||||
 | 
					  screenfetch: class Screenfetch extends Process { | 
				
			||||||
 | 
					    run () { | 
				
			||||||
 | 
					      let image = ` | 
				
			||||||
 | 
					 ###.                          ESPTerm Demo | 
				
			||||||
 | 
					   '###.                       Hostname: ${window.location.hostname} | 
				
			||||||
 | 
					     '###.                     Shell: ESPTerm Demo Shell | 
				
			||||||
 | 
					       '###.                   Resolution: 80x25@${window.devicePixelRatio}x | 
				
			||||||
 | 
					         :###- | 
				
			||||||
 | 
					       .###' | 
				
			||||||
 | 
					     .###' | 
				
			||||||
 | 
					   .###'      ############### | 
				
			||||||
 | 
					 ###'         ############### | 
				
			||||||
 | 
					      `.split('\n').filter(line => line.trim())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let chars = '' | 
				
			||||||
 | 
					      for (let y = 0; y < image.length; y++) { | 
				
			||||||
 | 
					        for (let x = 0; x < 80; x++) { | 
				
			||||||
 | 
					          if (image[y][x]) { | 
				
			||||||
 | 
					            chars += `\x1b[38;5;${getRainbowColor((x + y) / 10)}m${image[y][x]}` | 
				
			||||||
 | 
					          } else chars += ' ' | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.emit('write', '\r\n\x1b[?25l') | 
				
			||||||
 | 
					      let loop = () => { | 
				
			||||||
 | 
					        this.emit('write', chars.substr(0, 80)) | 
				
			||||||
 | 
					        chars = chars.substr(80) | 
				
			||||||
 | 
					        if (chars.length) setTimeout(loop, 50) | 
				
			||||||
 | 
					        else { | 
				
			||||||
 | 
					          this.emit('write', '\r\n\x1b[?25h') | 
				
			||||||
 | 
					          this.destroy() | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      loop() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  }, | 
				
			||||||
 | 
					  'local-echo': class LocalEcho extends Process { | 
				
			||||||
 | 
					    run (...args) { | 
				
			||||||
 | 
					      if (!args.includes('--suppress-note')) { | 
				
			||||||
 | 
					        this.emit('write', '\x1b[38;5;239mNote: not all terminal features are supported or and may not work as expected in this demo\x1b[0m\r\n') | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    write (data) { | 
				
			||||||
 | 
					      this.emit('write', data) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  }, | 
				
			||||||
 | 
					  'info': class Info extends Process { | 
				
			||||||
 | 
					    run (...args) { | 
				
			||||||
 | 
					      let fast = args.includes('--fast') | 
				
			||||||
 | 
					      this.showSplash().then(() => { | 
				
			||||||
 | 
					        this.printText(fast) | 
				
			||||||
 | 
					      }) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    showSplash () { | 
				
			||||||
 | 
					      let splash = ` | 
				
			||||||
 | 
					              -#####- -###*..#####- ######- | 
				
			||||||
 | 
					              -#*    -#-    .## .##.  *#- | 
				
			||||||
 | 
					              -##### .-###*..#####-   *#-  -*##*- #*-#--#**#-*##- | 
				
			||||||
 | 
					              -#*        -#-.##.      *#-  *##@#* ##.  -#* *# .#* | 
				
			||||||
 | 
					              -#####--####- .##.      *#-  -*#@@- ##.  -#* *# .#* | 
				
			||||||
 | 
					      `.split('\n').filter(line => line.trim())
 | 
				
			||||||
 | 
					      let levels = { | 
				
			||||||
 | 
					        ' ': -231, | 
				
			||||||
 | 
					        '.': 4, | 
				
			||||||
 | 
					        '-': 8, | 
				
			||||||
 | 
					        '*': 17, | 
				
			||||||
 | 
					        '#': 24 | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      for (let i in splash) { | 
				
			||||||
 | 
					        if (splash[i].length < 79) splash[i] += ' '.repeat(79 - splash[i].length) | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      this.emit('write', '\r\n'.repeat(splash.length + 1)) | 
				
			||||||
 | 
					      this.emit('write', '\x1b[A'.repeat(splash.length)) | 
				
			||||||
 | 
					      this.emit('write', '\x1b[?25l') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let cursorX = 0 | 
				
			||||||
 | 
					      let cursorY = 0 | 
				
			||||||
 | 
					      let moveTo = (x, y) => { | 
				
			||||||
 | 
					        let moveX = x - cursorX | 
				
			||||||
 | 
					        let moveY = y - cursorY | 
				
			||||||
 | 
					        this.emit('write', `\x1b[${Math.abs(moveX)}${moveX > 0 ? 'C' : 'D'}`) | 
				
			||||||
 | 
					        this.emit('write', `\x1b[${Math.abs(moveY)}${moveY > 0 ? 'B' : 'A'}`) | 
				
			||||||
 | 
					        cursorX = x | 
				
			||||||
 | 
					        cursorY = y | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      let drawCell = (x, y) => { | 
				
			||||||
 | 
					        moveTo(x, y) | 
				
			||||||
 | 
					        if (splash[y][x] === '@') { | 
				
			||||||
 | 
					          this.emit('write', '\x1b[48;5;8m\x1b[38;5;255m▄\b') | 
				
			||||||
 | 
					        } else { | 
				
			||||||
 | 
					          this.emit('write', `\x1b[48;5;${231 + levels[splash[y][x]]}m \b`) | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      return new Promise((resolve, reject) => { | 
				
			||||||
 | 
					        const self = this | 
				
			||||||
 | 
					        let x = 14 | 
				
			||||||
 | 
					        let cycles = 0 | 
				
			||||||
 | 
					        let loop = function () { | 
				
			||||||
 | 
					          for (let y = 0; y < splash.length; y++) { | 
				
			||||||
 | 
					            let dx = x - y | 
				
			||||||
 | 
					            if (dx > 0) drawCell(dx, y) | 
				
			||||||
 | 
					          } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (++x < 79) { | 
				
			||||||
 | 
					            if (++cycles >= 3) { | 
				
			||||||
 | 
					              setTimeout(loop, 20) | 
				
			||||||
 | 
					              cycles = 0 | 
				
			||||||
 | 
					            } else loop() | 
				
			||||||
 | 
					          } else { | 
				
			||||||
 | 
					            moveTo(0, splash.length) | 
				
			||||||
 | 
					            self.emit('write', '\x1b[m\x1b[?25h') | 
				
			||||||
 | 
					            resolve() | 
				
			||||||
 | 
					          } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        loop() | 
				
			||||||
 | 
					      }) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    printText (fast = false) { | 
				
			||||||
 | 
					      // lots of printing
 | 
				
			||||||
 | 
					      let parts = [ | 
				
			||||||
 | 
					        '', | 
				
			||||||
 | 
					        '  ESPTerm is a VT100-like terminal emulator running on the ESP8266 WiFi chip.', | 
				
			||||||
 | 
					        '', | 
				
			||||||
 | 
					        '  \x1b[93mThis is an online demo of the web user interface, simulating a simple ', | 
				
			||||||
 | 
					        '  terminal in your browser.\x1b[m', | 
				
			||||||
 | 
					        '', | 
				
			||||||
 | 
					        '  Type \x1b[92mls\x1b[m to list available commands.', | 
				
			||||||
 | 
					        '  Use the \x1b[94mlinks\x1b[m below this screen for a demo of the options and more info.', | 
				
			||||||
 | 
					        '' | 
				
			||||||
 | 
					      ] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (fast) { | 
				
			||||||
 | 
					        this.emit('write', parts.join('\r\n') + '\r\n') | 
				
			||||||
 | 
					        this.destroy() | 
				
			||||||
 | 
					      } else { | 
				
			||||||
 | 
					        const self = this | 
				
			||||||
 | 
					        let loop = function () { | 
				
			||||||
 | 
					          self.emit('write', parts.shift() + '\r\n') | 
				
			||||||
 | 
					          if (parts.length) setTimeout(loop, 17) | 
				
			||||||
 | 
					          else self.destroy() | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        loop() | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  }, | 
				
			||||||
 | 
					  colors: class PrintColors extends Process { | 
				
			||||||
 | 
					    run () { | 
				
			||||||
 | 
					      this.emit('write', '\r\n') | 
				
			||||||
 | 
					      let fgtext = 'foreground-color' | 
				
			||||||
 | 
					      this.emit('write', '    ') | 
				
			||||||
 | 
					      for (let i = 0; i < 16; i++) { | 
				
			||||||
 | 
					        this.emit('write', '\x1b[' + (i < 8 ? `3${i}` : `9${i - 8}`) + 'm') | 
				
			||||||
 | 
					        this.emit('write', fgtext[i] + ' ') | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      this.emit('write', '\r\n    ') | 
				
			||||||
 | 
					      for (let i = 0; i < 16; i++) { | 
				
			||||||
 | 
					        this.emit('write', '\x1b[' + (i < 8 ? `4${i}` : `10${i - 8}`) + 'm  ') | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      this.emit('write', '\x1b[m\r\n') | 
				
			||||||
 | 
					      for (let r = 0; r < 6; r++) { | 
				
			||||||
 | 
					        this.emit('write', '    ') | 
				
			||||||
 | 
					        for (let g = 0; g < 6; g++) { | 
				
			||||||
 | 
					          for (let b = 0; b < 6; b++) { | 
				
			||||||
 | 
					            this.emit('write', `\x1b[48;5;${16 + r * 36 + g * 6 + b}m  `) | 
				
			||||||
 | 
					          } | 
				
			||||||
 | 
					          this.emit('write', '\x1b[m') | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        this.emit('write', '\r\n') | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      this.emit('write', '    ') | 
				
			||||||
 | 
					      for (let g = 0; g < 24; g++) { | 
				
			||||||
 | 
					        this.emit('write', `\x1b[48;5;${232 + g}m  `) | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      this.emit('write', '\x1b[m\r\n\n') | 
				
			||||||
 | 
					      this.destroy() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  }, | 
				
			||||||
 | 
					  ls: class ListCommands extends Process { | 
				
			||||||
 | 
					    run () { | 
				
			||||||
 | 
					      this.emit('write', '\x1b[92mList of demo commands\x1b[m\r\n') | 
				
			||||||
 | 
					      for (let i in demoshIndex) { | 
				
			||||||
 | 
					        if (typeof demoshIndex[i] === 'string') continue | 
				
			||||||
 | 
					        this.emit('write', i + '\r\n') | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      this.destroy() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  }, | 
				
			||||||
 | 
					  theme: class SetTheme extends Process { | 
				
			||||||
 | 
					    constructor (shell) { | 
				
			||||||
 | 
					      super() | 
				
			||||||
 | 
					      this.shell = shell | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    run (...args) { | 
				
			||||||
 | 
					      let theme = args[0] | 0 | 
				
			||||||
 | 
					      if (!args.length || !Number.isFinite(theme) || theme < 0 || theme > 5) { | 
				
			||||||
 | 
					        this.emit('write', '\x1b[31mUsage: theme [0–5]\r\n') | 
				
			||||||
 | 
					        this.destroy() | 
				
			||||||
 | 
					        return | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      this.shell.terminal.theme = theme | 
				
			||||||
 | 
					      // HACK: reset drawn screen to prevent only partly redrawn screen
 | 
				
			||||||
 | 
					      this.shell.terminal.termScreen.drawnScreenFG = [] | 
				
			||||||
 | 
					      this.emit('write', '') | 
				
			||||||
 | 
					      this.destroy() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  }, | 
				
			||||||
 | 
					  cursor: class SetCursor extends Process { | 
				
			||||||
 | 
					    run (...args) { | 
				
			||||||
 | 
					      let steady = args.includes('--steady') | 
				
			||||||
 | 
					      if (args.includes('block')) { | 
				
			||||||
 | 
					        this.emit('write', `\x1b[${0 + 2 * steady} q`) | 
				
			||||||
 | 
					      } else if (args.includes('line')) { | 
				
			||||||
 | 
					        this.emit('write', `\x1b[${3 + steady} q`) | 
				
			||||||
 | 
					      } else if (args.includes('bar') || args.includes('beam')) { | 
				
			||||||
 | 
					        this.emit('write', `\x1b[${5 + steady} q`) | 
				
			||||||
 | 
					      } else { | 
				
			||||||
 | 
					        this.emit('write', '\x1b[31mUsage: cursor [block|line|bar] [--steady]\r\n') | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      this.destroy() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  }, | 
				
			||||||
 | 
					  rainbow: class ToggleRainbow extends Process { | 
				
			||||||
 | 
					    constructor (shell) { | 
				
			||||||
 | 
					      super() | 
				
			||||||
 | 
					      this.shell = shell | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    run () { | 
				
			||||||
 | 
					      this.shell.terminal.rainbow = !this.shell.terminal.rainbow | 
				
			||||||
 | 
					      this.shell.terminal.rainbowTimer() | 
				
			||||||
 | 
					      this.emit('write', '') | 
				
			||||||
 | 
					      this.destroy() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  }, | 
				
			||||||
 | 
					  pwd: '/this/is/a/demo\r\n', | 
				
			||||||
 | 
					  cd: '\x1b[38;5;239mNo directories to change to\r\n', | 
				
			||||||
 | 
					  whoami: `${window.navigator.userAgent}\r\n`, | 
				
			||||||
 | 
					  hostname: `${window.location.hostname}`, | 
				
			||||||
 | 
					  uname: 'ESPTerm Demo\r\n', | 
				
			||||||
 | 
					  mkdir: '\x1b[38;5;239mDid not create a directory because this is a demo.\r\n', | 
				
			||||||
 | 
					  rm: '\x1b[38;5;239mDid not delete anything because this is a demo.\r\n', | 
				
			||||||
 | 
					  cp: '\x1b[38;5;239mNothing to copy because this is a demo.\r\n', | 
				
			||||||
 | 
					  mv: '\x1b[38;5;239mNothing to move because this is a demo.\r\n', | 
				
			||||||
 | 
					  ln: '\x1b[38;5;239mNothing to link because this is a demo.\r\n', | 
				
			||||||
 | 
					  touch: '\x1b[38;5;239mNothing to touch\r\n', | 
				
			||||||
 | 
					  exit: '\x1b[38;5;239mNowhere to go\r\n' | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DemoShell { | 
				
			||||||
 | 
					  constructor (terminal, printInfo) { | 
				
			||||||
 | 
					    this.terminal = terminal | 
				
			||||||
 | 
					    this.terminal.reset() | 
				
			||||||
 | 
					    this.parser = new ANSIParser((...args) => this.handleParsed(...args)) | 
				
			||||||
 | 
					    this.input = '' | 
				
			||||||
 | 
					    this.cursorPos = 0 | 
				
			||||||
 | 
					    this.child = null | 
				
			||||||
 | 
					    this.index = demoshIndex | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (printInfo) this.run('info') | 
				
			||||||
 | 
					    else this.prompt() | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  write (text) { | 
				
			||||||
 | 
					    if (this.child) { | 
				
			||||||
 | 
					      if (text.codePointAt(0) === 3) this.child.destroy() | 
				
			||||||
 | 
					      else this.child.write(text) | 
				
			||||||
 | 
					    } else this.parser.write(text) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  prompt (success = true) { | 
				
			||||||
 | 
					    if (this.terminal.cursor.x !== 0) this.terminal.write('\x1b[m\x1b[38;5;238m⏎\r\n') | 
				
			||||||
 | 
					    this.terminal.write('\x1b[34;1mdemosh \x1b[m') | 
				
			||||||
 | 
					    if (!success) this.terminal.write('\x1b[31m') | 
				
			||||||
 | 
					    this.terminal.write('$ \x1b[m') | 
				
			||||||
 | 
					    this.input = '' | 
				
			||||||
 | 
					    this.cursorPos = 0 | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  handleParsed (action, ...args) { | 
				
			||||||
 | 
					    this.terminal.write('\b\x1b[P'.repeat(this.cursorPos)) | 
				
			||||||
 | 
					    if (action === 'write') { | 
				
			||||||
 | 
					      this.input = this.input.substr(0, this.cursorPos) + args[0] + this.input.substr(this.cursorPos) | 
				
			||||||
 | 
					      this.cursorPos++ | 
				
			||||||
 | 
					    } else if (action === 'back') { | 
				
			||||||
 | 
					      this.input = this.input.substr(0, this.cursorPos - 1) + this.input.substr(this.cursorPos) | 
				
			||||||
 | 
					      this.cursorPos-- | 
				
			||||||
 | 
					      if (this.cursorPos < 0) this.cursorPos = 0 | 
				
			||||||
 | 
					    } else if (action === 'move-cursor-x') { | 
				
			||||||
 | 
					      this.cursorPos = Math.max(0, Math.min(this.input.length, this.cursorPos + args[0])) | 
				
			||||||
 | 
					    } else if (action === 'delete-line') { | 
				
			||||||
 | 
					      this.input = '' | 
				
			||||||
 | 
					      this.cursorPos = 0 | 
				
			||||||
 | 
					    } else if (action === 'delete-word') { | 
				
			||||||
 | 
					      let words = this.input.substr(0, this.cursorPos).split(' ') | 
				
			||||||
 | 
					      words.pop() | 
				
			||||||
 | 
					      this.input = words.join(' ') + this.input.substr(this.cursorPos) | 
				
			||||||
 | 
					      this.cursorPos = words.join(' ').length | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.terminal.write(this.input) | 
				
			||||||
 | 
					    this.terminal.write('\b'.repeat(this.input.length)) | 
				
			||||||
 | 
					    this.terminal.moveForward(this.cursorPos) | 
				
			||||||
 | 
					    this.terminal.write('') // dummy. Apply the moveFoward
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (action === 'return') { | 
				
			||||||
 | 
					      this.terminal.write('\r\n') | 
				
			||||||
 | 
					      this.parse(this.input) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  parse (input) { | 
				
			||||||
 | 
					    if (input === 'help') input = 'info' | 
				
			||||||
 | 
					    // TODO: basic chaining (i.e. semicolon)
 | 
				
			||||||
 | 
					    this.run(input) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  run (command) { | 
				
			||||||
 | 
					    let parts = [''] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let inQuote = false | 
				
			||||||
 | 
					    for (let character of command.trim()) { | 
				
			||||||
 | 
					      if (inQuote && character !== inQuote) { | 
				
			||||||
 | 
					        parts[parts.length - 1] += character | 
				
			||||||
 | 
					      } else if (inQuote) { | 
				
			||||||
 | 
					        inQuote = false | 
				
			||||||
 | 
					      } else if (character === '"' || character === "'") { | 
				
			||||||
 | 
					        inQuote = character | 
				
			||||||
 | 
					      } else if (character.match(/\s/)) { | 
				
			||||||
 | 
					        if (parts[parts.length - 1]) parts.push('') | 
				
			||||||
 | 
					      } else parts[parts.length - 1] += character | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let name = parts.shift() | 
				
			||||||
 | 
					    if (name in this.index) { | 
				
			||||||
 | 
					      this.spawn(name, parts) | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					      this.terminal.write(`demosh: Unknown command: ${name}\r\n`) | 
				
			||||||
 | 
					      this.prompt(false) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  spawn (name, args = []) { | 
				
			||||||
 | 
					    let Process = this.index[name] | 
				
			||||||
 | 
					    if (Process instanceof Function) { | 
				
			||||||
 | 
					      this.child = new Process(this) | 
				
			||||||
 | 
					      let write = data => this.terminal.write(data) | 
				
			||||||
 | 
					      this.child.on('write', write) | 
				
			||||||
 | 
					      this.child.on('exit', code => { | 
				
			||||||
 | 
					        if (this.child) this.child.off('write', write) | 
				
			||||||
 | 
					        this.child = null | 
				
			||||||
 | 
					        this.prompt(!code) | 
				
			||||||
 | 
					      }) | 
				
			||||||
 | 
					      this.child.run(...args) | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					      this.terminal.write(Process) | 
				
			||||||
 | 
					      this.prompt() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					window.demoInterface = { | 
				
			||||||
 | 
					  input (data) { | 
				
			||||||
 | 
					    let type = data[0] | 
				
			||||||
 | 
					    let content = data.substr(1) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (type === 's') { | 
				
			||||||
 | 
					      this.shell.write(content) | 
				
			||||||
 | 
					    } else if (type === 'b') { | 
				
			||||||
 | 
					      let button = content.charCodeAt(0) | 
				
			||||||
 | 
					      let action = demoData.buttons[button] | 
				
			||||||
 | 
					      if (action) { | 
				
			||||||
 | 
					        if (typeof action === 'string') this.shell.write(action) | 
				
			||||||
 | 
					        else if (action instanceof Function) action(this.terminal, this.shell) | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } else if (type === 'm' || type === 'p' || type === 'r') { | 
				
			||||||
 | 
					      console.log(JSON.stringify(data)) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  }, | 
				
			||||||
 | 
					  init (screen) { | 
				
			||||||
 | 
					    this.terminal = new ScrollingTerminal(screen) | 
				
			||||||
 | 
					    this.shell = new DemoShell(this.terminal, true) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -1,8 +1,8 @@ | 
				
			|||||||
// Generated from PHP locale file
 | 
					// Generated from PHP locale file
 | 
				
			||||||
var _tr = { | 
					let _tr = { | 
				
			||||||
    "wifi.connected_ip_is": "Connected, IP is ", | 
					    "wifi.connected_ip_is": "Connected, IP is ", | 
				
			||||||
    "wifi.not_conn": "Not connected.", | 
					    "wifi.not_conn": "Not connected.", | 
				
			||||||
    "wifi.enter_passwd": "Enter password for \":ssid:\"" | 
					    "wifi.enter_passwd": "Enter password for \":ssid:\"" | 
				
			||||||
}; | 
					}; | 
				
			||||||
 | 
					
 | 
				
			||||||
function tr(key) { return _tr[key] || '?'+key+'?'; } | 
					function tr (key) { return _tr[key] || '?' + key + '?' } | 
				
			||||||
@ -0,0 +1,63 @@ | 
				
			|||||||
 | 
					/*! http://mths.be/fromcodepoint v0.1.0 by @mathias */ | 
				
			||||||
 | 
					if (!String.fromCodePoint) { | 
				
			||||||
 | 
					  (function () { | 
				
			||||||
 | 
					    var defineProperty = (function () { | 
				
			||||||
 | 
					      // IE 8 only supports `Object.defineProperty` on DOM elements
 | 
				
			||||||
 | 
					      try { | 
				
			||||||
 | 
					        var object = {}; | 
				
			||||||
 | 
					        var $defineProperty = Object.defineProperty; | 
				
			||||||
 | 
					        var result = $defineProperty(object, object, object) && $defineProperty; | 
				
			||||||
 | 
					      } catch (error) { | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      return result; | 
				
			||||||
 | 
					    }()); | 
				
			||||||
 | 
					    var stringFromCharCode = String.fromCharCode; | 
				
			||||||
 | 
					    var floor = Math.floor; | 
				
			||||||
 | 
					    var fromCodePoint = function () { | 
				
			||||||
 | 
					      var MAX_SIZE = 0x4000; | 
				
			||||||
 | 
					      var codeUnits = []; | 
				
			||||||
 | 
					      var highSurrogate; | 
				
			||||||
 | 
					      var lowSurrogate; | 
				
			||||||
 | 
					      var index = -1; | 
				
			||||||
 | 
					      var length = arguments.length; | 
				
			||||||
 | 
					      if (!length) { | 
				
			||||||
 | 
					        return ''; | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      var result = ''; | 
				
			||||||
 | 
					      while (++index < length) { | 
				
			||||||
 | 
					        var codePoint = Number(arguments[index]); | 
				
			||||||
 | 
					        if ( | 
				
			||||||
 | 
					          !isFinite(codePoint) ||       // `NaN`, `+Infinity`, or `-Infinity`
 | 
				
			||||||
 | 
					          codePoint < 0 ||              // not a valid Unicode code point
 | 
				
			||||||
 | 
					          codePoint > 0x10FFFF ||       // not a valid Unicode code point
 | 
				
			||||||
 | 
					          floor(codePoint) != codePoint // not an integer
 | 
				
			||||||
 | 
					        ) { | 
				
			||||||
 | 
					          throw RangeError('Invalid code point: ' + codePoint); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        if (codePoint <= 0xFFFF) { // BMP code point
 | 
				
			||||||
 | 
					          codeUnits.push(codePoint); | 
				
			||||||
 | 
					        } else { // Astral code point; split in surrogate halves
 | 
				
			||||||
 | 
					          // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
 | 
				
			||||||
 | 
					          codePoint -= 0x10000; | 
				
			||||||
 | 
					          highSurrogate = (codePoint >> 10) + 0xD800; | 
				
			||||||
 | 
					          lowSurrogate = (codePoint % 0x400) + 0xDC00; | 
				
			||||||
 | 
					          codeUnits.push(highSurrogate, lowSurrogate); | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        if (index + 1 == length || codeUnits.length > MAX_SIZE) { | 
				
			||||||
 | 
					          result += stringFromCharCode.apply(null, codeUnits); | 
				
			||||||
 | 
					          codeUnits.length = 0; | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      return result; | 
				
			||||||
 | 
					    }; | 
				
			||||||
 | 
					    if (defineProperty) { | 
				
			||||||
 | 
					      defineProperty(String, 'fromCodePoint', { | 
				
			||||||
 | 
					        'value': fromCodePoint, | 
				
			||||||
 | 
					        'configurable': true, | 
				
			||||||
 | 
					        'writable': true | 
				
			||||||
 | 
					      }); | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					      String.fromCodePoint = fromCodePoint; | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  }()); | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,44 @@ | 
				
			|||||||
 | 
					/** Module for toggling a modal overlay */ | 
				
			||||||
 | 
					(function () { | 
				
			||||||
 | 
					  let modal = {} | 
				
			||||||
 | 
					  let curCloseCb = null | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  modal.show = function (sel, closeCb) { | 
				
			||||||
 | 
					    let $m = $(sel) | 
				
			||||||
 | 
					    $m.removeClass('hidden visible') | 
				
			||||||
 | 
					    setTimeout(function () { | 
				
			||||||
 | 
					      $m.addClass('visible') | 
				
			||||||
 | 
					    }, 1) | 
				
			||||||
 | 
					    curCloseCb = closeCb | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  modal.hide = function (sel) { | 
				
			||||||
 | 
					    let $m = $(sel) | 
				
			||||||
 | 
					    $m.removeClass('visible') | 
				
			||||||
 | 
					    setTimeout(function () { | 
				
			||||||
 | 
					      $m.addClass('hidden') | 
				
			||||||
 | 
					      if (curCloseCb) curCloseCb() | 
				
			||||||
 | 
					    }, 500) // transition time
 | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  modal.init = function () { | 
				
			||||||
 | 
					    // close modal by click outside the dialog
 | 
				
			||||||
 | 
					    $('.Modal').on('click', function () { | 
				
			||||||
 | 
					      if ($(this).hasClass('no-close')) return // this is a no-close modal
 | 
				
			||||||
 | 
					      modal.hide(this) | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $('.Dialog').on('click', function (e) { | 
				
			||||||
 | 
					      e.stopImmediatePropagation() | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Hide all modals on esc
 | 
				
			||||||
 | 
					    $(window).on('keydown', function (e) { | 
				
			||||||
 | 
					      if (e.which === 27) { | 
				
			||||||
 | 
					        modal.hide('.Modal') | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  window.Modal = modal | 
				
			||||||
 | 
					})() | 
				
			||||||
@ -0,0 +1,65 @@ | 
				
			|||||||
 | 
					window.Notify = (function () { | 
				
			||||||
 | 
					  let nt = {} | 
				
			||||||
 | 
					  const sel = '#notif' | 
				
			||||||
 | 
					  let $balloon | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let timerHideBegin // timeout to start hiding (transition)
 | 
				
			||||||
 | 
					  let timerHideEnd // timeout to add the hidden class
 | 
				
			||||||
 | 
					  let timerCanCancel | 
				
			||||||
 | 
					  let canCancel = false | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let stopTimeouts = function () { | 
				
			||||||
 | 
					    clearTimeout(timerHideBegin) | 
				
			||||||
 | 
					    clearTimeout(timerHideEnd) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  nt.show = function (message, timeout, isError) { | 
				
			||||||
 | 
					    $balloon.toggleClass('error', isError === true) | 
				
			||||||
 | 
					    $balloon.html(message) | 
				
			||||||
 | 
					    Modal.show($balloon) | 
				
			||||||
 | 
					    stopTimeouts() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (undef(timeout) || timeout === null || timeout <= 0) { | 
				
			||||||
 | 
					      timeout = 2500 | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    timerHideBegin = setTimeout(nt.hide, timeout) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    canCancel = false | 
				
			||||||
 | 
					    timerCanCancel = setTimeout(function () { | 
				
			||||||
 | 
					      canCancel = true | 
				
			||||||
 | 
					    }, 500) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  nt.hide = function () { | 
				
			||||||
 | 
					    let $m = $(sel) | 
				
			||||||
 | 
					    $m.removeClass('visible') | 
				
			||||||
 | 
					    timerHideEnd = setTimeout(function () { | 
				
			||||||
 | 
					      $m.addClass('hidden') | 
				
			||||||
 | 
					    }, 250) // transition time
 | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  nt.init = function () { | 
				
			||||||
 | 
					    $balloon = $(sel) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // close by click outside
 | 
				
			||||||
 | 
					    $(document).on('click', function () { | 
				
			||||||
 | 
					      if (!canCancel) return | 
				
			||||||
 | 
					      nt.hide(this) | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // click caused by selecting, prevent it from bubbling
 | 
				
			||||||
 | 
					    $balloon.on('click', function (e) { | 
				
			||||||
 | 
					      e.stopImmediatePropagation() | 
				
			||||||
 | 
					      return false | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // stop fading if moused
 | 
				
			||||||
 | 
					    $balloon.on('mouseenter', function () { | 
				
			||||||
 | 
					      stopTimeouts() | 
				
			||||||
 | 
					      $balloon.removeClass('hidden').addClass('visible') | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return nt | 
				
			||||||
 | 
					})() | 
				
			||||||
@ -0,0 +1,113 @@ | 
				
			|||||||
 | 
					window.initSoftKeyboard = function (screen, input) { | 
				
			||||||
 | 
					  const keyInput = qs('#softkb-input') | 
				
			||||||
 | 
					  if (!keyInput) return // abort, we're not on the terminal page
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let keyboardOpen = false | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let updateInputPosition = function () { | 
				
			||||||
 | 
					    if (!keyboardOpen) return | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let [x, y] = screen.gridToScreen(screen.cursor.x, screen.cursor.y, true) | 
				
			||||||
 | 
					    keyInput.style.transform = `translate(${x}px, ${y}px)` | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  keyInput.addEventListener('focus', () => { | 
				
			||||||
 | 
					    keyboardOpen = true | 
				
			||||||
 | 
					    updateInputPosition() | 
				
			||||||
 | 
					  }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  keyInput.addEventListener('blur', () => (keyboardOpen = false)) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  screen.on('cursor-moved', updateInputPosition) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let kbOpen = function (open) { | 
				
			||||||
 | 
					    keyboardOpen = open | 
				
			||||||
 | 
					    updateInputPosition() | 
				
			||||||
 | 
					    if (open) keyInput.focus() | 
				
			||||||
 | 
					    else keyInput.blur() | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  qs('#term-kb-open').addEventListener('click', function () { | 
				
			||||||
 | 
					    kbOpen(true) | 
				
			||||||
 | 
					    return false | 
				
			||||||
 | 
					  }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Chrome for Android doesn't send proper keydown/keypress events with
 | 
				
			||||||
 | 
					  // real key values instead of 229 “Unidentified,” so here's a workaround
 | 
				
			||||||
 | 
					  // that deals with the input composition events.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let lastCompositionString = '' | 
				
			||||||
 | 
					  let compositing = false | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // sends the difference between the last and the new composition string
 | 
				
			||||||
 | 
					  let sendInputDelta = function (newValue) { | 
				
			||||||
 | 
					    let resend = false | 
				
			||||||
 | 
					    if (newValue.length > lastCompositionString.length) { | 
				
			||||||
 | 
					      if (newValue.startsWith(lastCompositionString)) { | 
				
			||||||
 | 
					        // characters have been added at the end
 | 
				
			||||||
 | 
					        input.sendString(newValue.substr(lastCompositionString.length)) | 
				
			||||||
 | 
					      } else resend = true | 
				
			||||||
 | 
					    } else if (newValue.length < lastCompositionString.length) { | 
				
			||||||
 | 
					      if (lastCompositionString.startsWith(newValue)) { | 
				
			||||||
 | 
					        // characters have been removed at the end
 | 
				
			||||||
 | 
					        input.sendString('\b'.repeat(lastCompositionString.length - | 
				
			||||||
 | 
					          newValue.length)) | 
				
			||||||
 | 
					      } else resend = true | 
				
			||||||
 | 
					    } else if (newValue !== lastCompositionString) resend = true | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (resend) { | 
				
			||||||
 | 
					      // the entire string changed; resend everything
 | 
				
			||||||
 | 
					      input.sendString('\b'.repeat(lastCompositionString.length) + | 
				
			||||||
 | 
					        newValue) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    lastCompositionString = newValue | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  keyInput.addEventListener('keydown', e => { | 
				
			||||||
 | 
					    if (e.key === 'Unidentified') return | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    keyInput.value = '' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (e.key === 'Backspace') { | 
				
			||||||
 | 
					      e.preventDefault() | 
				
			||||||
 | 
					      input.sendString('\b') | 
				
			||||||
 | 
					    } else if (e.key === 'Enter') { | 
				
			||||||
 | 
					      e.preventDefault() | 
				
			||||||
 | 
					      input.sendString('\x0d') | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  keyInput.addEventListener('keypress', e => { | 
				
			||||||
 | 
					    // prevent key duplication on iOS (because Safari *does* send proper events)
 | 
				
			||||||
 | 
					    e.stopPropagation() | 
				
			||||||
 | 
					  }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  keyInput.addEventListener('input', e => { | 
				
			||||||
 | 
					    e.stopPropagation() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (e.isComposing) { | 
				
			||||||
 | 
					      sendInputDelta(e.data) | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					      if (e.inputType === 'insertCompositionText') input.sendString(e.data) | 
				
			||||||
 | 
					      else if (e.inputType === 'deleteContentBackward') { | 
				
			||||||
 | 
					        lastCompositionString = '' | 
				
			||||||
 | 
					        sendInputDelta('') | 
				
			||||||
 | 
					      } else if (e.inputType === 'insertText') { | 
				
			||||||
 | 
					        input.sendString(e.data) | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  keyInput.addEventListener('compositionstart', e => { | 
				
			||||||
 | 
					    lastCompositionString = '' | 
				
			||||||
 | 
					    compositing = true | 
				
			||||||
 | 
					  }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  keyInput.addEventListener('compositionend', e => { | 
				
			||||||
 | 
					    lastCompositionString = '' | 
				
			||||||
 | 
					    compositing = false | 
				
			||||||
 | 
					    keyInput.value = '' | 
				
			||||||
 | 
					  }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  screen.on('open-soft-keyboard', () => keyInput.focus()) | 
				
			||||||
 | 
					} | 
				
			||||||
									
										
											File diff suppressed because it is too large
											Load Diff
										
									
								
							
						@ -0,0 +1,97 @@ | 
				
			|||||||
 | 
					/** Init the terminal sub-module - called from HTML */ | 
				
			||||||
 | 
					window.termInit = function (opts) { | 
				
			||||||
 | 
					  let { labels, theme, allFn } = opts | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const screen = new TermScreen() | 
				
			||||||
 | 
					  const conn = Conn(screen) | 
				
			||||||
 | 
					  const input = Input(conn) | 
				
			||||||
 | 
					  const termUpload = TermUpl(conn, input, screen) | 
				
			||||||
 | 
					  screen.input = input | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  conn.init() | 
				
			||||||
 | 
					  input.init({ allFn }) | 
				
			||||||
 | 
					  termUpload.init() | 
				
			||||||
 | 
					  Notify.init() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  window.onerror = function (errorMsg, file, line, col) { | 
				
			||||||
 | 
					    Notify.show(`<b>JS ERROR!</b><br>${errorMsg}<br>at ${file}:${line}:${col}`, 10000, true) | 
				
			||||||
 | 
					    return false | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  qs('#screen').appendChild(screen.canvas) | 
				
			||||||
 | 
					  screen.load(labels, theme) // load labels and theme
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  window.initSoftKeyboard(screen, input) | 
				
			||||||
 | 
					  if (window.attachDebugScreen) window.attachDebugScreen(screen) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let isFullscreen = false | 
				
			||||||
 | 
					  let fitScreen = false | 
				
			||||||
 | 
					  let fitScreenIfNeeded = function fitScreenIfNeeded () { | 
				
			||||||
 | 
					    if (isFullscreen) { | 
				
			||||||
 | 
					      screen.window.fitIntoWidth = window.screen.width | 
				
			||||||
 | 
					      screen.window.fitIntoHeight = window.screen.height | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					      screen.window.fitIntoWidth = fitScreen ? window.innerWidth - 20 : 0 | 
				
			||||||
 | 
					      screen.window.fitIntoHeight = fitScreen ? window.innerHeight : 0 | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  fitScreenIfNeeded() | 
				
			||||||
 | 
					  window.addEventListener('resize', fitScreenIfNeeded) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let toggleFitScreen = function () { | 
				
			||||||
 | 
					    fitScreen = !fitScreen | 
				
			||||||
 | 
					    const resizeButtonIcon = qs('#resize-button-icon') | 
				
			||||||
 | 
					    if (fitScreen) { | 
				
			||||||
 | 
					      resizeButtonIcon.classList.remove('icn-resize-small') | 
				
			||||||
 | 
					      resizeButtonIcon.classList.add('icn-resize-full') | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					      resizeButtonIcon.classList.remove('icn-resize-full') | 
				
			||||||
 | 
					      resizeButtonIcon.classList.add('icn-resize-small') | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    fitScreenIfNeeded() | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  qs('#term-fit-screen').addEventListener('click', function () { | 
				
			||||||
 | 
					    toggleFitScreen() | 
				
			||||||
 | 
					    return false | 
				
			||||||
 | 
					  }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // add fullscreen mode & button
 | 
				
			||||||
 | 
					  if (Element.prototype.requestFullscreen || Element.prototype.webkitRequestFullscreen) { | 
				
			||||||
 | 
					    let checkForFullscreen = function () { | 
				
			||||||
 | 
					      // document.fullscreenElement is not really supported yet, so here's a hack
 | 
				
			||||||
 | 
					      if (isFullscreen && (innerWidth !== window.screen.width || innerHeight !== window.screen.height)) { | 
				
			||||||
 | 
					        isFullscreen = false | 
				
			||||||
 | 
					        fitScreenIfNeeded() | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    setInterval(checkForFullscreen, 500) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // (why are the buttons anchors?)
 | 
				
			||||||
 | 
					    let button = mk('a') | 
				
			||||||
 | 
					    button.href = '#' | 
				
			||||||
 | 
					    button.addEventListener('click', e => { | 
				
			||||||
 | 
					      e.preventDefault() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      isFullscreen = true | 
				
			||||||
 | 
					      fitScreenIfNeeded() | 
				
			||||||
 | 
					      screen.updateSize() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (screen.canvas.requestFullscreen) screen.canvas.requestFullscreen() | 
				
			||||||
 | 
					      else screen.canvas.webkitRequestFullscreen() | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					    let icon = mk('i') | 
				
			||||||
 | 
					    icon.classList.add('icn-resize-full') // TODO: less confusing icons
 | 
				
			||||||
 | 
					    button.appendChild(icon) | 
				
			||||||
 | 
					    let span = mk('span') | 
				
			||||||
 | 
					    span.textContent = 'Fullscreen' | 
				
			||||||
 | 
					    button.appendChild(span) | 
				
			||||||
 | 
					    qs('#term-nav').insertBefore(button, qs('#term-nav').firstChild) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // for debugging
 | 
				
			||||||
 | 
					  window.termScreen = screen | 
				
			||||||
 | 
					  window.conn = conn | 
				
			||||||
 | 
					  window.input = input | 
				
			||||||
 | 
					  window.termUpl = termUpload | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,144 @@ | 
				
			|||||||
 | 
					/** Handle connections */ | 
				
			||||||
 | 
					window.Conn = function (screen) { | 
				
			||||||
 | 
					  let ws | 
				
			||||||
 | 
					  let heartbeatTout | 
				
			||||||
 | 
					  let pingIv | 
				
			||||||
 | 
					  let xoff = false | 
				
			||||||
 | 
					  let autoXoffTout | 
				
			||||||
 | 
					  let reconTout | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let pageShown = false | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function onOpen (evt) { | 
				
			||||||
 | 
					    console.log('CONNECTED') | 
				
			||||||
 | 
					    heartbeat() | 
				
			||||||
 | 
					    doSend('i') | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function onClose (evt) { | 
				
			||||||
 | 
					    console.warn('SOCKET CLOSED, code ' + evt.code + '. Reconnecting...') | 
				
			||||||
 | 
					    clearTimeout(reconTout) | 
				
			||||||
 | 
					    reconTout = setTimeout(function () { | 
				
			||||||
 | 
					      init() | 
				
			||||||
 | 
					    }, 2000) | 
				
			||||||
 | 
					    // this happens when the buffer gets fucked up via invalid unicode.
 | 
				
			||||||
 | 
					    // we basically use polling instead of socket then
 | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function onMessage (evt) { | 
				
			||||||
 | 
					    try { | 
				
			||||||
 | 
					      // . = heartbeat
 | 
				
			||||||
 | 
					      switch (evt.data.charAt(0)) { | 
				
			||||||
 | 
					        case '.': | 
				
			||||||
 | 
					          // heartbeat, no-op message
 | 
				
			||||||
 | 
					          break | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case '-': | 
				
			||||||
 | 
					          // console.log('xoff');
 | 
				
			||||||
 | 
					          xoff = true | 
				
			||||||
 | 
					          autoXoffTout = setTimeout(function () { | 
				
			||||||
 | 
					            xoff = false | 
				
			||||||
 | 
					          }, 250) | 
				
			||||||
 | 
					          break | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        case '+': | 
				
			||||||
 | 
					          // console.log('xon');
 | 
				
			||||||
 | 
					          xoff = false | 
				
			||||||
 | 
					          clearTimeout(autoXoffTout) | 
				
			||||||
 | 
					          break | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        default: | 
				
			||||||
 | 
					          screen.load(evt.data) | 
				
			||||||
 | 
					          if (!pageShown) { | 
				
			||||||
 | 
					            showPage() | 
				
			||||||
 | 
					            pageShown = true | 
				
			||||||
 | 
					          } | 
				
			||||||
 | 
					          break | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      heartbeat() | 
				
			||||||
 | 
					    } catch (e) { | 
				
			||||||
 | 
					      console.error(e) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function canSend () { | 
				
			||||||
 | 
					    return !xoff | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function doSend (message) { | 
				
			||||||
 | 
					    if (_demo) { | 
				
			||||||
 | 
					      if (typeof demoInterface !== 'undefined') { | 
				
			||||||
 | 
					        demoInterface.input(message) | 
				
			||||||
 | 
					      } else { | 
				
			||||||
 | 
					        console.log(`TX: ${JSON.stringify(message)}`) | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      return true // Simulate success
 | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    if (xoff) { | 
				
			||||||
 | 
					      // TODO queue
 | 
				
			||||||
 | 
					      console.log("Can't send, flood control.") | 
				
			||||||
 | 
					      return false | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!ws) return false // for dry testing
 | 
				
			||||||
 | 
					    if (ws.readyState !== 1) { | 
				
			||||||
 | 
					      console.error('Socket not ready') | 
				
			||||||
 | 
					      return false | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    if (typeof message != 'string') { | 
				
			||||||
 | 
					      message = JSON.stringify(message) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    ws.send(message) | 
				
			||||||
 | 
					    return true | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function init () { | 
				
			||||||
 | 
					    if (window._demo) { | 
				
			||||||
 | 
					      if (typeof demoInterface === 'undefined') { | 
				
			||||||
 | 
					        alert('Demoing non-demo demo!') // this will catch mistakes when deploying to the website
 | 
				
			||||||
 | 
					      } else { | 
				
			||||||
 | 
					        demoInterface.init(screen) | 
				
			||||||
 | 
					        showPage() | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      return | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    clearTimeout(reconTout) | 
				
			||||||
 | 
					    clearTimeout(heartbeatTout) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ws = new WebSocket('ws://' + _root + '/term/update.ws') | 
				
			||||||
 | 
					    ws.onopen = onOpen | 
				
			||||||
 | 
					    ws.onclose = onClose | 
				
			||||||
 | 
					    ws.onmessage = onMessage | 
				
			||||||
 | 
					    console.log('Opening socket.') | 
				
			||||||
 | 
					    heartbeat() | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function heartbeat () { | 
				
			||||||
 | 
					    clearTimeout(heartbeatTout) | 
				
			||||||
 | 
					    heartbeatTout = setTimeout(heartbeatFail, 2000) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function heartbeatFail () { | 
				
			||||||
 | 
					    console.error('Heartbeat lost, probing server...') | 
				
			||||||
 | 
					    pingIv = setInterval(function () { | 
				
			||||||
 | 
					      console.log('> ping') | 
				
			||||||
 | 
					      $.get('http://' + _root + '/system/ping', function (resp, status) { | 
				
			||||||
 | 
					        if (status === 200) { | 
				
			||||||
 | 
					          clearInterval(pingIv) | 
				
			||||||
 | 
					          console.info('Server ready, reloading page...') | 
				
			||||||
 | 
					          location.reload() | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					      }, { | 
				
			||||||
 | 
					        timeout: 100 | 
				
			||||||
 | 
					      }) | 
				
			||||||
 | 
					    }, 1000) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { | 
				
			||||||
 | 
					    ws: null, | 
				
			||||||
 | 
					    init: init, | 
				
			||||||
 | 
					    send: doSend, | 
				
			||||||
 | 
					    canSend: canSend // check flood control
 | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,303 @@ | 
				
			|||||||
 | 
					/** | 
				
			||||||
 | 
					 * User input | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * --- Rx messages: --- | 
				
			||||||
 | 
					 * S - screen content (binary encoding of the entire screen with simple compression) | 
				
			||||||
 | 
					 * T - text labels - Title and buttons, \0x01-separated | 
				
			||||||
 | 
					 * B - beep | 
				
			||||||
 | 
					 * . - heartbeat | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * --- Tx messages --- | 
				
			||||||
 | 
					 * s - string | 
				
			||||||
 | 
					 * b - action button | 
				
			||||||
 | 
					 * p - mb press | 
				
			||||||
 | 
					 * r - mb release | 
				
			||||||
 | 
					 * m - mouse move | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					window.Input = function (conn) { | 
				
			||||||
 | 
					  let cfg = { | 
				
			||||||
 | 
					    np_alt: false, | 
				
			||||||
 | 
					    cu_alt: false, | 
				
			||||||
 | 
					    fn_alt: false, | 
				
			||||||
 | 
					    mt_click: false, | 
				
			||||||
 | 
					    mt_move: false, | 
				
			||||||
 | 
					    no_keys: false, | 
				
			||||||
 | 
					    crlf_mode: false | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Send a literal message */ | 
				
			||||||
 | 
					  function sendStrMsg (str) { | 
				
			||||||
 | 
					    return conn.send('s' + str) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Send a button event */ | 
				
			||||||
 | 
					  function sendBtnMsg (n) { | 
				
			||||||
 | 
					    conn.send('b' + Chr(n)) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Fn alt choice for key message */ | 
				
			||||||
 | 
					  function fa (alt, normal) { | 
				
			||||||
 | 
					    return cfg.fn_alt ? alt : normal | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Cursor alt choice for key message */ | 
				
			||||||
 | 
					  function ca (alt, normal) { | 
				
			||||||
 | 
					    return cfg.cu_alt ? alt : normal | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Numpad alt choice for key message */ | 
				
			||||||
 | 
					  function na (alt, normal) { | 
				
			||||||
 | 
					    return cfg.np_alt ? alt : normal | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function _bindFnKeys (allFn) { | 
				
			||||||
 | 
					    const keymap = { | 
				
			||||||
 | 
					      'tab': '\x09', | 
				
			||||||
 | 
					      'backspace': '\x08', | 
				
			||||||
 | 
					      'enter': cfg.crlf_mode ? '\x0d\x0a' : '\x0d', | 
				
			||||||
 | 
					      'ctrl+enter': '\x0a', | 
				
			||||||
 | 
					      'esc': '\x1b', | 
				
			||||||
 | 
					      'up': ca('\x1bOA', '\x1b[A'), | 
				
			||||||
 | 
					      'down': ca('\x1bOB', '\x1b[B'), | 
				
			||||||
 | 
					      'right': ca('\x1bOC', '\x1b[C'), | 
				
			||||||
 | 
					      'left': ca('\x1bOD', '\x1b[D'), | 
				
			||||||
 | 
					      'home': ca('\x1bOH', fa('\x1b[H', '\x1b[1~')), | 
				
			||||||
 | 
					      'insert': '\x1b[2~', | 
				
			||||||
 | 
					      'delete': '\x1b[3~', | 
				
			||||||
 | 
					      'end': ca('\x1bOF', fa('\x1b[F', '\x1b[4~')), | 
				
			||||||
 | 
					      'pageup': '\x1b[5~', | 
				
			||||||
 | 
					      'pagedown': '\x1b[6~', | 
				
			||||||
 | 
					      'f1': fa('\x1bOP', '\x1b[11~'), | 
				
			||||||
 | 
					      'f2': fa('\x1bOQ', '\x1b[12~'), | 
				
			||||||
 | 
					      'f3': fa('\x1bOR', '\x1b[13~'), | 
				
			||||||
 | 
					      'f4': fa('\x1bOS', '\x1b[14~'), | 
				
			||||||
 | 
					      'f5': '\x1b[15~', // note the disconnect
 | 
				
			||||||
 | 
					      'f6': '\x1b[17~', | 
				
			||||||
 | 
					      'f7': '\x1b[18~', | 
				
			||||||
 | 
					      'f8': '\x1b[19~', | 
				
			||||||
 | 
					      'f9': '\x1b[20~', | 
				
			||||||
 | 
					      'f10': '\x1b[21~', // note the disconnect
 | 
				
			||||||
 | 
					      'f11': '\x1b[23~', | 
				
			||||||
 | 
					      'f12': '\x1b[24~', | 
				
			||||||
 | 
					      'shift+f1': fa('\x1bO1;2P', '\x1b[25~'), | 
				
			||||||
 | 
					      'shift+f2': fa('\x1bO1;2Q', '\x1b[26~'), // note the disconnect
 | 
				
			||||||
 | 
					      'shift+f3': fa('\x1bO1;2R', '\x1b[28~'), | 
				
			||||||
 | 
					      'shift+f4': fa('\x1bO1;2S', '\x1b[29~'), // note the disconnect
 | 
				
			||||||
 | 
					      'shift+f5': fa('\x1b[15;2~', '\x1b[31~'), | 
				
			||||||
 | 
					      'shift+f6': fa('\x1b[17;2~', '\x1b[32~'), | 
				
			||||||
 | 
					      'shift+f7': fa('\x1b[18;2~', '\x1b[33~'), | 
				
			||||||
 | 
					      'shift+f8': fa('\x1b[19;2~', '\x1b[34~'), | 
				
			||||||
 | 
					      'shift+f9': fa('\x1b[20;2~', '\x1b[35~'), // 35-38 are not standard - but what is?
 | 
				
			||||||
 | 
					      'shift+f10': fa('\x1b[21;2~', '\x1b[36~'), | 
				
			||||||
 | 
					      'shift+f11': fa('\x1b[22;2~', '\x1b[37~'), | 
				
			||||||
 | 
					      'shift+f12': fa('\x1b[23;2~', '\x1b[38~'), | 
				
			||||||
 | 
					      'np_0': na('\x1bOp', '0'), | 
				
			||||||
 | 
					      'np_1': na('\x1bOq', '1'), | 
				
			||||||
 | 
					      'np_2': na('\x1bOr', '2'), | 
				
			||||||
 | 
					      'np_3': na('\x1bOs', '3'), | 
				
			||||||
 | 
					      'np_4': na('\x1bOt', '4'), | 
				
			||||||
 | 
					      'np_5': na('\x1bOu', '5'), | 
				
			||||||
 | 
					      'np_6': na('\x1bOv', '6'), | 
				
			||||||
 | 
					      'np_7': na('\x1bOw', '7'), | 
				
			||||||
 | 
					      'np_8': na('\x1bOx', '8'), | 
				
			||||||
 | 
					      'np_9': na('\x1bOy', '9'), | 
				
			||||||
 | 
					      'np_mul': na('\x1bOR', '*'), | 
				
			||||||
 | 
					      'np_add': na('\x1bOl', '+'), | 
				
			||||||
 | 
					      'np_sub': na('\x1bOS', '-'), | 
				
			||||||
 | 
					      'np_point': na('\x1bOn', '.'), | 
				
			||||||
 | 
					      'np_div': na('\x1bOQ', '/') | 
				
			||||||
 | 
					      // we don't implement numlock key (should change in numpad_alt mode, but it's even more useless than the rest)
 | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const blacklist = [ | 
				
			||||||
 | 
					      'f5', 'f11', 'f12', 'shift+f5' | 
				
			||||||
 | 
					    ] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (let k in keymap) { | 
				
			||||||
 | 
					      if (!allFn && blacklist.includes(k)) continue | 
				
			||||||
 | 
					      if (keymap.hasOwnProperty(k)) { | 
				
			||||||
 | 
					        bind(k, keymap[k]) | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Bind a keystroke to message */ | 
				
			||||||
 | 
					  function bind (combo, str) { | 
				
			||||||
 | 
					    // mac fix - allow also cmd
 | 
				
			||||||
 | 
					    if (combo.indexOf('ctrl+') !== -1) { | 
				
			||||||
 | 
					      combo += ',' + combo.replace('ctrl', 'command') | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // unbind possible old binding
 | 
				
			||||||
 | 
					    key.unbind(combo) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    key(combo, function (e) { | 
				
			||||||
 | 
					      if (cfg.no_keys) return | 
				
			||||||
 | 
					      e.preventDefault() | 
				
			||||||
 | 
					      sendStrMsg(str) | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Bind/rebind key messages */ | 
				
			||||||
 | 
					  function _initKeys (opts) { | 
				
			||||||
 | 
					    let { allFn } = opts | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // This takes care of text characters typed
 | 
				
			||||||
 | 
					    window.addEventListener('keypress', function (evt) { | 
				
			||||||
 | 
					      if (cfg.no_keys) return | 
				
			||||||
 | 
					      let str = '' | 
				
			||||||
 | 
					      if (evt.key) str = evt.key | 
				
			||||||
 | 
					      else if (evt.which) str = String.fromCodePoint(evt.which) | 
				
			||||||
 | 
					      if (str.length > 0 && str.charCodeAt(0) >= 32) { | 
				
			||||||
 | 
					        // console.log("Typed ", str);
 | 
				
			||||||
 | 
					        // prevent space from scrolling
 | 
				
			||||||
 | 
					        if (evt.which === 32) evt.preventDefault() | 
				
			||||||
 | 
					        sendStrMsg(str) | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // ctrl-letter codes are sent as simple low ASCII codes
 | 
				
			||||||
 | 
					    for (let i = 1; i <= 26; i++) { | 
				
			||||||
 | 
					      bind('ctrl+' + String.fromCharCode(96 + i), String.fromCharCode(i)) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    /* eslint-disable */ | 
				
			||||||
 | 
					    bind('ctrl+]',  '\x1b') // alternate way to enter ESC
 | 
				
			||||||
 | 
					    bind('ctrl+\\', '\x1c') | 
				
			||||||
 | 
					    bind('ctrl+[',  '\x1d') | 
				
			||||||
 | 
					    bind('ctrl+^',  '\x1e') | 
				
			||||||
 | 
					    bind('ctrl+_',  '\x1f') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // extra ctrl-
 | 
				
			||||||
 | 
					    bind('ctrl+left',  '\x1f[1;5D') | 
				
			||||||
 | 
					    bind('ctrl+right', '\x1f[1;5C') | 
				
			||||||
 | 
					    bind('ctrl+up',    '\x1f[1;5A') | 
				
			||||||
 | 
					    bind('ctrl+down',  '\x1f[1;5B') | 
				
			||||||
 | 
					    bind('ctrl+home',  '\x1f[1;5H') | 
				
			||||||
 | 
					    bind('ctrl+end',   '\x1f[1;5F') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // extra shift-
 | 
				
			||||||
 | 
					    bind('shift+left',  '\x1f[1;2D') | 
				
			||||||
 | 
					    bind('shift+right', '\x1f[1;2C') | 
				
			||||||
 | 
					    bind('shift+up',    '\x1f[1;2A') | 
				
			||||||
 | 
					    bind('shift+down',  '\x1f[1;2B') | 
				
			||||||
 | 
					    bind('shift+home',  '\x1f[1;2H') | 
				
			||||||
 | 
					    bind('shift+end',   '\x1f[1;2F') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // macOS editing commands
 | 
				
			||||||
 | 
					    bind('⌥+left',      '\x1bb')    // ⌥← to go back a word (^[b)
 | 
				
			||||||
 | 
					    bind('⌥+right',     '\x1bf')    // ⌥→ to go forward one word (^[f)
 | 
				
			||||||
 | 
					    bind('⌘+left',      '\x01')     // ⌘← to go to the beginning of a line (^A)
 | 
				
			||||||
 | 
					    bind('⌘+right',     '\x05')     // ⌘→ to go to the end of a line (^E)
 | 
				
			||||||
 | 
					    bind('⌥+backspace', '\x17') // ⌥⌫ to delete a word (^W, I think)
 | 
				
			||||||
 | 
					    bind('⌘+backspace', '\x15') // ⌘⌫ to delete to the beginning of a line (possibly ^U)
 | 
				
			||||||
 | 
					    /* eslint-enable */ | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _bindFnKeys(allFn) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // mouse button states
 | 
				
			||||||
 | 
					  let mb1 = 0 | 
				
			||||||
 | 
					  let mb2 = 0 | 
				
			||||||
 | 
					  let mb3 = 0 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Init the Input module */ | 
				
			||||||
 | 
					  function init (opts) { | 
				
			||||||
 | 
					    _initKeys(opts) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Button presses
 | 
				
			||||||
 | 
					    $('#action-buttons button').forEach(function (s) { | 
				
			||||||
 | 
					      s.addEventListener('click', function () { | 
				
			||||||
 | 
					        sendBtnMsg(+this.dataset['n']) | 
				
			||||||
 | 
					      }) | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // global mouse state tracking - for motion reporting
 | 
				
			||||||
 | 
					    window.addEventListener('mousedown', function (evt) { | 
				
			||||||
 | 
					      if (evt.button === 0) mb1 = 1 | 
				
			||||||
 | 
					      if (evt.button === 1) mb2 = 1 | 
				
			||||||
 | 
					      if (evt.button === 2) mb3 = 1 | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window.addEventListener('mouseup', function (evt) { | 
				
			||||||
 | 
					      if (evt.button === 0) mb1 = 0 | 
				
			||||||
 | 
					      if (evt.button === 1) mb2 = 0 | 
				
			||||||
 | 
					      if (evt.button === 2) mb3 = 0 | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Prepare modifiers byte for mouse message */ | 
				
			||||||
 | 
					  function packModifiersForMouse () { | 
				
			||||||
 | 
					    return (key.isModifier('ctrl') ? 1 : 0) | | 
				
			||||||
 | 
					      (key.isModifier('shift') ? 2 : 0) | | 
				
			||||||
 | 
					      (key.isModifier('alt') ? 4 : 0) | | 
				
			||||||
 | 
					      (key.isModifier('meta') ? 8 : 0) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { | 
				
			||||||
 | 
					    /** Init the Input module */ | 
				
			||||||
 | 
					    init: init, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Send a literal string message */ | 
				
			||||||
 | 
					    sendString: sendStrMsg, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** Enable alternate key modes (cursors, numpad, fn) */ | 
				
			||||||
 | 
					    setAlts: function (cu, np, fn, crlf) { | 
				
			||||||
 | 
					      if (cfg.cu_alt !== cu || cfg.np_alt !== np || cfg.fn_alt !== fn || cfg.crlf_mode !== crlf) { | 
				
			||||||
 | 
					        cfg.cu_alt = cu | 
				
			||||||
 | 
					        cfg.np_alt = np | 
				
			||||||
 | 
					        cfg.fn_alt = fn | 
				
			||||||
 | 
					        cfg.crlf_mode = crlf | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // rebind keys - codes have changed
 | 
				
			||||||
 | 
					        _bindFnKeys() | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    setMouseMode: function (click, move) { | 
				
			||||||
 | 
					      cfg.mt_click = click | 
				
			||||||
 | 
					      cfg.mt_move = move | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Mouse events
 | 
				
			||||||
 | 
					    onMouseMove: function (x, y) { | 
				
			||||||
 | 
					      if (!cfg.mt_move) return | 
				
			||||||
 | 
					      const b = mb1 ? 1 : mb2 ? 2 : mb3 ? 3 : 0 | 
				
			||||||
 | 
					      const m = packModifiersForMouse() | 
				
			||||||
 | 
					      conn.send('m' + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)) | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    onMouseDown: function (x, y, b) { | 
				
			||||||
 | 
					      if (!cfg.mt_click) return | 
				
			||||||
 | 
					      if (b > 3 || b < 1) return | 
				
			||||||
 | 
					      const m = packModifiersForMouse() | 
				
			||||||
 | 
					      conn.send('p' + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)) | 
				
			||||||
 | 
					      // console.log("B ",b," M ",m);
 | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    onMouseUp: function (x, y, b) { | 
				
			||||||
 | 
					      if (!cfg.mt_click) return | 
				
			||||||
 | 
					      if (b > 3 || b < 1) return | 
				
			||||||
 | 
					      const m = packModifiersForMouse() | 
				
			||||||
 | 
					      conn.send('r' + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)) | 
				
			||||||
 | 
					      // console.log("B ",b," M ",m);
 | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    onMouseWheel: function (x, y, dir) { | 
				
			||||||
 | 
					      if (!cfg.mt_click) return | 
				
			||||||
 | 
					      // -1 ... btn 4 (away from user)
 | 
				
			||||||
 | 
					      // +1 ... btn 5 (towards user)
 | 
				
			||||||
 | 
					      const m = packModifiersForMouse() | 
				
			||||||
 | 
					      const b = (dir < 0 ? 4 : 5) | 
				
			||||||
 | 
					      conn.send('p' + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)) | 
				
			||||||
 | 
					      // console.log("B ",b," M ",m);
 | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mouseTracksClicks: function () { | 
				
			||||||
 | 
					      return cfg.mt_click | 
				
			||||||
 | 
					    }, | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    blockKeys: function (yes) { | 
				
			||||||
 | 
					      cfg.no_keys = yes | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
									
										
											File diff suppressed because it is too large
											Load Diff
										
									
								
							
						@ -0,0 +1,169 @@ | 
				
			|||||||
 | 
					/** File upload utility */ | 
				
			||||||
 | 
					window.TermUpl = function (conn, input, screen) { | 
				
			||||||
 | 
					  let lines, // array of lines without newlines
 | 
				
			||||||
 | 
					    line_i, // current line index
 | 
				
			||||||
 | 
					    fuTout, // timeout handle for line sending
 | 
				
			||||||
 | 
					    send_delay_ms, // delay between lines (ms)
 | 
				
			||||||
 | 
					    nl_str, // newline string to use
 | 
				
			||||||
 | 
					    curLine, // current line (when using fuOil)
 | 
				
			||||||
 | 
					    inline_pos // Offset in line (for long lines)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // lines longer than this are split to chunks
 | 
				
			||||||
 | 
					  // sending a super-ling string through the socket is not a good idea
 | 
				
			||||||
 | 
					  const MAX_LINE_LEN = 128 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function openUploadDialog () { | 
				
			||||||
 | 
					    updateStatus('Ready...') | 
				
			||||||
 | 
					    Modal.show('#fu_modal', onDialogClose) | 
				
			||||||
 | 
					    $('#fu_form').toggleClass('busy', false) | 
				
			||||||
 | 
					    input.blockKeys(true) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function onDialogClose () { | 
				
			||||||
 | 
					    console.log('Upload modal closed.') | 
				
			||||||
 | 
					    clearTimeout(fuTout) | 
				
			||||||
 | 
					    line_i = 0 | 
				
			||||||
 | 
					    input.blockKeys(false) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function updateStatus (msg) { | 
				
			||||||
 | 
					    qs('#fu_prog').textContent = msg | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function startUpload () { | 
				
			||||||
 | 
					    let v = qs('#fu_text').value | 
				
			||||||
 | 
					    if (!v.length) { | 
				
			||||||
 | 
					      fuClose() | 
				
			||||||
 | 
					      return | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    lines = v.split('\n') | 
				
			||||||
 | 
					    line_i = 0 | 
				
			||||||
 | 
					    inline_pos = 0 // offset in line
 | 
				
			||||||
 | 
					    send_delay_ms = qs('#fu_delay').value | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // sanitize - 0 causes overflows
 | 
				
			||||||
 | 
					    if (send_delay_ms < 0) { | 
				
			||||||
 | 
					      send_delay_ms = 0 | 
				
			||||||
 | 
					      qs('#fu_delay').value = send_delay_ms | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nl_str = { | 
				
			||||||
 | 
					      'CR': '\r', | 
				
			||||||
 | 
					      'LF': '\n', | 
				
			||||||
 | 
					      'CRLF': '\r\n' | 
				
			||||||
 | 
					    }[qs('#fu_crlf').value] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $('#fu_form').toggleClass('busy', true) | 
				
			||||||
 | 
					    updateStatus('Starting...') | 
				
			||||||
 | 
					    uploadLine() | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function uploadLine () { | 
				
			||||||
 | 
					    if (!$('#fu_modal').hasClass('visible')) { | 
				
			||||||
 | 
					      // Modal is closed, cancel
 | 
				
			||||||
 | 
					      return | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!conn.canSend()) { | 
				
			||||||
 | 
					      // postpone
 | 
				
			||||||
 | 
					      fuTout = setTimeout(uploadLine, 1) | 
				
			||||||
 | 
					      return | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (inline_pos === 0) { | 
				
			||||||
 | 
					      curLine = '' | 
				
			||||||
 | 
					      if (line_i === 0) { | 
				
			||||||
 | 
					        if (screen.bracketedPaste) { | 
				
			||||||
 | 
					          curLine = '\x1b[200~' | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      curLine += lines[line_i++] + nl_str | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (line_i === lines.length) { | 
				
			||||||
 | 
					        if (screen.bracketedPaste) { | 
				
			||||||
 | 
					          curLine += '\x1b[201~' | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let chunk | 
				
			||||||
 | 
					    if ((curLine.length - inline_pos) <= MAX_LINE_LEN) { | 
				
			||||||
 | 
					      chunk = curLine.substr(inline_pos, MAX_LINE_LEN) | 
				
			||||||
 | 
					      inline_pos = 0 | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					      chunk = curLine.substr(inline_pos, MAX_LINE_LEN) | 
				
			||||||
 | 
					      inline_pos += MAX_LINE_LEN | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log(chunk) | 
				
			||||||
 | 
					    if (!input.sendString(chunk)) { | 
				
			||||||
 | 
					      updateStatus('FAILED!') | 
				
			||||||
 | 
					      return | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let pt = Math.round((line_i / lines.length) * 1000) / 10 | 
				
			||||||
 | 
					    updateStatus(`${line_i} / ${lines.length} (${pt}%)`) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (lines.length > line_i || inline_pos > 0) { | 
				
			||||||
 | 
					      fuTout = setTimeout(uploadLine, send_delay_ms) | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					      closeWhenReady() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function closeWhenReady () { | 
				
			||||||
 | 
					    if (!conn.canSend()) { | 
				
			||||||
 | 
					      // stuck in XOFF still, wait to process...
 | 
				
			||||||
 | 
					      updateStatus('Waiting for Tx buffer...') | 
				
			||||||
 | 
					      setTimeout(closeWhenReady, 100) | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					      updateStatus('Done.') | 
				
			||||||
 | 
					      // delay to show it
 | 
				
			||||||
 | 
					      fuClose() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function fuClose () { | 
				
			||||||
 | 
					    Modal.hide('#fu_modal') | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { | 
				
			||||||
 | 
					    init: function () { | 
				
			||||||
 | 
					      qs('#fu_file').addEventListener('change', function (evt) { | 
				
			||||||
 | 
					        let reader = new FileReader() | 
				
			||||||
 | 
					        let file = evt.target.files[0] | 
				
			||||||
 | 
					        console.log('Selected file type: ' + file.type) | 
				
			||||||
 | 
					        if (!file.type.match(/text\/.*|application\/(json|csv|.*xml.*|.*script.*)/)) { | 
				
			||||||
 | 
					          // Deny load of blobs like img - can crash browser and will get corrupted anyway
 | 
				
			||||||
 | 
					          if (!confirm('This does not look like a text file: ' + file.type + '\nReally load?')) { | 
				
			||||||
 | 
					            qs('#fu_file').value = '' | 
				
			||||||
 | 
					            return | 
				
			||||||
 | 
					          } | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        reader.onload = function (e) { | 
				
			||||||
 | 
					          const txt = e.target.result.replace(/[\r\n]+/, '\n') | 
				
			||||||
 | 
					          qs('#fu_text').value = txt | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        console.log('Loading file...') | 
				
			||||||
 | 
					        reader.readAsText(file) | 
				
			||||||
 | 
					      }, false) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      qs('#term-fu-open').addEventListener('click', function () { | 
				
			||||||
 | 
					        openUploadDialog() | 
				
			||||||
 | 
					        return false | 
				
			||||||
 | 
					      }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      qs('#term-fu-start').addEventListener('click', function () { | 
				
			||||||
 | 
					        startUpload() | 
				
			||||||
 | 
					        return false | 
				
			||||||
 | 
					      }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      qs('#term-fu-close').addEventListener('click', function () { | 
				
			||||||
 | 
					        fuClose() | 
				
			||||||
 | 
					        return false | 
				
			||||||
 | 
					      }) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,90 @@ | 
				
			|||||||
 | 
					/** Make a node */ | 
				
			||||||
 | 
					function mk (e) { | 
				
			||||||
 | 
					  return document.createElement(e) | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Find one by query */ | 
				
			||||||
 | 
					function qs (s) { | 
				
			||||||
 | 
					  return document.querySelector(s) | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Find all by query */ | 
				
			||||||
 | 
					function qsa (s) { | 
				
			||||||
 | 
					  return document.querySelectorAll(s) | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Convert any to bool safely */ | 
				
			||||||
 | 
					function bool (x) { | 
				
			||||||
 | 
					  return (x === 1 || x === '1' || x === true || x === 'true') | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** | 
				
			||||||
 | 
					 * Filter 'spacebar' and 'return' from keypress handler, | 
				
			||||||
 | 
					 * and when they're pressed, fire the callback. | 
				
			||||||
 | 
					 * use $(...).on('keypress', cr(handler)) | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					function cr (hdl) { | 
				
			||||||
 | 
					  return function (e) { | 
				
			||||||
 | 
					    if (e.which === 10 || e.which === 13 || e.which === 32) { | 
				
			||||||
 | 
					      hdl() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** HTML escape */ | 
				
			||||||
 | 
					function esc (str) { | 
				
			||||||
 | 
					  return $.htmlEscape(str) | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Check for undefined */ | 
				
			||||||
 | 
					function undef (x) { | 
				
			||||||
 | 
					  return typeof x == 'undefined' | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Safe json parse */ | 
				
			||||||
 | 
					function jsp (str) { | 
				
			||||||
 | 
					  try { | 
				
			||||||
 | 
					    return JSON.parse(str) | 
				
			||||||
 | 
					  } catch (e) { | 
				
			||||||
 | 
					    console.error(e) | 
				
			||||||
 | 
					    return null | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Create a character from ASCII code */ | 
				
			||||||
 | 
					function Chr (n) { | 
				
			||||||
 | 
					  return String.fromCharCode(n) | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Decode number from 2B encoding */ | 
				
			||||||
 | 
					function parse2B (s, i = 0) { | 
				
			||||||
 | 
					  return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127 | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Decode number from 3B encoding */ | 
				
			||||||
 | 
					function parse3B (s, i = 0) { | 
				
			||||||
 | 
					  return (s.charCodeAt(i) - 1) + (s.charCodeAt(i + 1) - 1) * 127 + (s.charCodeAt(i + 2) - 1) * 127 * 127 | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Encode using 2B encoding, returns string. */ | 
				
			||||||
 | 
					function encode2B (n) { | 
				
			||||||
 | 
					  let lsb, msb | 
				
			||||||
 | 
					  lsb = (n % 127) | 
				
			||||||
 | 
					  n = ((n - lsb) / 127) | 
				
			||||||
 | 
					  lsb += 1 | 
				
			||||||
 | 
					  msb = (n + 1) | 
				
			||||||
 | 
					  return Chr(lsb) + Chr(msb) | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** Encode using 3B encoding, returns string. */ | 
				
			||||||
 | 
					function encode3B (n) { | 
				
			||||||
 | 
					  let lsb, msb, xsb | 
				
			||||||
 | 
					  lsb = (n % 127) | 
				
			||||||
 | 
					  n = (n - lsb) / 127 | 
				
			||||||
 | 
					  lsb += 1 | 
				
			||||||
 | 
					  msb = (n % 127) | 
				
			||||||
 | 
					  n = (n - msb) / 127 | 
				
			||||||
 | 
					  msb += 1 | 
				
			||||||
 | 
					  xsb = (n + 1) | 
				
			||||||
 | 
					  return Chr(lsb) + Chr(msb) + Chr(xsb) | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,163 @@ | 
				
			|||||||
 | 
					(function (w) { | 
				
			||||||
 | 
					  const authStr = ['Open', 'WEP', 'WPA', 'WPA2', 'WPA/WPA2'] | 
				
			||||||
 | 
					  let curSSID | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Get XX % for a slider input
 | 
				
			||||||
 | 
					  function rangePt (inp) { | 
				
			||||||
 | 
					    return Math.round(((inp.value / inp.max) * 100)) + '%' | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Display selected STA SSID etc
 | 
				
			||||||
 | 
					  function selectSta (name, password, ip) { | 
				
			||||||
 | 
					    $('#sta_ssid').val(name) | 
				
			||||||
 | 
					    $('#sta_password').val(password) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $('#sta-nw').toggleClass('hidden', name.length === 0) | 
				
			||||||
 | 
					    $('#sta-nw-nil').toggleClass('hidden', name.length > 0) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $('#sta-nw .essid').html(esc(name)) | 
				
			||||||
 | 
					    const nopw = undef(password) || password.length === 0 | 
				
			||||||
 | 
					    $('#sta-nw .passwd').toggleClass('hidden', nopw) | 
				
			||||||
 | 
					    $('#sta-nw .nopasswd').toggleClass('hidden', !nopw) | 
				
			||||||
 | 
					    $('#sta-nw .ip').html(ip.length > 0 ? tr('wifi.connected_ip_is') + ip : tr('wifi.not_conn')) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Update display for received response */ | 
				
			||||||
 | 
					  function onScan (resp, status) { | 
				
			||||||
 | 
					    // var ap_json = {
 | 
				
			||||||
 | 
					    //  "result": {
 | 
				
			||||||
 | 
					    //    "inProgress": "0",
 | 
				
			||||||
 | 
					    //    "APs": [
 | 
				
			||||||
 | 
					    //      {"essid": "Chlivek", "bssid": "88:f7:c7:52:b3:99", "rssi": "204", "enc": "4", "channel": "1"},
 | 
				
			||||||
 | 
					    //      {"essid": "TyNikdy", "bssid": "5c:f4:ab:0d:f1:1b", "rssi": "164", "enc": "3", "channel": "1"},
 | 
				
			||||||
 | 
					    //    ]
 | 
				
			||||||
 | 
					    //  }
 | 
				
			||||||
 | 
					    // };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (status !== 200) { | 
				
			||||||
 | 
					      // bad response
 | 
				
			||||||
 | 
					      rescan(5000) // wait 5sm then retry
 | 
				
			||||||
 | 
					      return | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try { | 
				
			||||||
 | 
					      resp = JSON.parse(resp) | 
				
			||||||
 | 
					    } catch (e) { | 
				
			||||||
 | 
					      console.log(e) | 
				
			||||||
 | 
					      rescan(5000) | 
				
			||||||
 | 
					      return | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const done = !bool(resp.result.inProgress) && (resp.result.APs.length > 0) | 
				
			||||||
 | 
					    rescan(done ? 15000 : 1000) | 
				
			||||||
 | 
					    if (!done) return // no redraw yet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // clear the AP list
 | 
				
			||||||
 | 
					    let $list = $('#ap-list') | 
				
			||||||
 | 
					    // remove old APs
 | 
				
			||||||
 | 
					    $('#ap-list .AP').remove() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    $list.toggleClass('hidden', !done) | 
				
			||||||
 | 
					    $('#ap-loader').toggleClass('hidden', done) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // scan done
 | 
				
			||||||
 | 
					    resp.result.APs.sort(function (a, b) { | 
				
			||||||
 | 
					      return b.rssi - a.rssi | 
				
			||||||
 | 
					    }).forEach(function (ap) { | 
				
			||||||
 | 
					      ap.enc = parseInt(ap.enc) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (ap.enc > 4) return // hide unsupported auths
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let item = mk('div') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let $item = $(item) | 
				
			||||||
 | 
					        .data('ssid', ap.essid) | 
				
			||||||
 | 
					        .data('pwd', ap.enc) | 
				
			||||||
 | 
					        .attr('tabindex', 0) | 
				
			||||||
 | 
					        .addClass('AP') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // mark current SSID
 | 
				
			||||||
 | 
					      if (ap.essid === curSSID) { | 
				
			||||||
 | 
					        $item.addClass('selected') | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let inner = mk('div') | 
				
			||||||
 | 
					      let escapedSSID = $.htmlEscape(ap.essid) | 
				
			||||||
 | 
					      $(inner).addClass('inner') | 
				
			||||||
 | 
					        .htmlAppend(`<div class="rssi">${ap.rssi_perc}</div>`) | 
				
			||||||
 | 
					        .htmlAppend(`<div class="essid" title="${escapedSSID}">${escapedSSID}</div>`) | 
				
			||||||
 | 
					        .htmlAppend(`<div class="auth">${authStr[ap.enc]}</div>`) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      $item.on('click', function () { | 
				
			||||||
 | 
					        let $th = $(this) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const conn_ssid = $th.data('ssid') | 
				
			||||||
 | 
					        let conn_pass = '' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (+$th.data('pwd')) { | 
				
			||||||
 | 
					          // this AP needs a password
 | 
				
			||||||
 | 
					          conn_pass = prompt(tr('wifi.enter_passwd').replace(':ssid:', conn_ssid)) | 
				
			||||||
 | 
					          if (!conn_pass) return | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        $('#sta_password').val(conn_pass) | 
				
			||||||
 | 
					        $('#sta_ssid').val(conn_ssid) | 
				
			||||||
 | 
					        selectSta(conn_ssid, conn_pass, '') | 
				
			||||||
 | 
					      }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      item.appendChild(inner) | 
				
			||||||
 | 
					      $list[0].appendChild(item) | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function startScanning () { | 
				
			||||||
 | 
					    $('#ap-loader').removeClass('hidden') | 
				
			||||||
 | 
					    $('#ap-scan').addClass('hidden') | 
				
			||||||
 | 
					    $('#ap-loader .anim-dots').html('.') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    scanAPs() | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Ask the CGI what APs are visible (async) */ | 
				
			||||||
 | 
					  function scanAPs () { | 
				
			||||||
 | 
					    if (_demo) { | 
				
			||||||
 | 
					      onScan(_demo_aps, 200) | 
				
			||||||
 | 
					    } else { | 
				
			||||||
 | 
					      $.get('http://' + _root + '/cfg/wifi/scan', onScan) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function rescan (time) { | 
				
			||||||
 | 
					    setTimeout(scanAPs, time) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /** Set up the WiFi page */ | 
				
			||||||
 | 
					  function wifiInit (cfg) { | 
				
			||||||
 | 
					    // Update slider value displays
 | 
				
			||||||
 | 
					    $('.Row.range').forEach(function (x) { | 
				
			||||||
 | 
					      let inp = x.querySelector('input') | 
				
			||||||
 | 
					      let disp1 = x.querySelector('.x-disp1') | 
				
			||||||
 | 
					      let disp2 = x.querySelector('.x-disp2') | 
				
			||||||
 | 
					      let t = rangePt(inp) | 
				
			||||||
 | 
					      $(disp1).html(t) | 
				
			||||||
 | 
					      $(disp2).html(t) | 
				
			||||||
 | 
					      $(inp).on('input', function () { | 
				
			||||||
 | 
					        t = rangePt(inp) | 
				
			||||||
 | 
					        $(disp1).html(t) | 
				
			||||||
 | 
					        $(disp2).html(t) | 
				
			||||||
 | 
					      }) | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Forget STA credentials
 | 
				
			||||||
 | 
					    $('#forget-sta').on('click', function () { | 
				
			||||||
 | 
					      selectSta('', '', '') | 
				
			||||||
 | 
					      return false | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    selectSta(cfg.sta_ssid, cfg.sta_password, cfg.sta_active_ip) | 
				
			||||||
 | 
					    curSSID = cfg.sta_active_ssid | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  w.init = wifiInit | 
				
			||||||
 | 
					  w.startScanning = startScanning | 
				
			||||||
 | 
					})(window.WiFi = {}) | 
				
			||||||
@ -1,189 +0,0 @@ | 
				
			|||||||
/** Global generic init */ | 
					 | 
				
			||||||
$.ready(function () { | 
					 | 
				
			||||||
	// Checkbox UI (checkbox CSS and hidden input with int value)
 | 
					 | 
				
			||||||
	$('.Row.checkbox').forEach(function(x) { | 
					 | 
				
			||||||
		var inp = x.querySelector('input'); | 
					 | 
				
			||||||
		var box = x.querySelector('.box'); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		$(box).toggleClass('checked', inp.value); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var hdl = function() { | 
					 | 
				
			||||||
			inp.value = 1 - inp.value; | 
					 | 
				
			||||||
			$(box).toggleClass('checked', inp.value) | 
					 | 
				
			||||||
		}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		$(x).on('click', hdl).on('keypress', cr(hdl)); | 
					 | 
				
			||||||
	}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Expanding boxes on mobile
 | 
					 | 
				
			||||||
	$('.Box.mobcol,.Box.fold').forEach(function(x) { | 
					 | 
				
			||||||
		var h = x.querySelector('h2'); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var hdl = function() { | 
					 | 
				
			||||||
			$(x).toggleClass('expanded'); | 
					 | 
				
			||||||
		}; | 
					 | 
				
			||||||
		$(h).on('click', hdl).on('keypress', cr(hdl)); | 
					 | 
				
			||||||
	}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	$('form').forEach(function(x) { | 
					 | 
				
			||||||
		$(x).on('keypress', function(e) { | 
					 | 
				
			||||||
			if ((e.keyCode == 10 || e.keyCode == 13) && e.ctrlKey) { | 
					 | 
				
			||||||
				x.submit(); | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
		}) | 
					 | 
				
			||||||
	}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// loader dots...
 | 
					 | 
				
			||||||
	setInterval(function () { | 
					 | 
				
			||||||
		$('.anim-dots').each(function (x) { | 
					 | 
				
			||||||
			var $x = $(x); | 
					 | 
				
			||||||
			var dots = $x.html() + '.'; | 
					 | 
				
			||||||
			if (dots.length == 5) dots = '.'; | 
					 | 
				
			||||||
			$x.html(dots); | 
					 | 
				
			||||||
		}); | 
					 | 
				
			||||||
	}, 1000); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// flipping number boxes with the mouse wheel
 | 
					 | 
				
			||||||
	$('input[type=number]').on('mousewheel', function(e) { | 
					 | 
				
			||||||
		var $this = $(this); | 
					 | 
				
			||||||
		var val = +$this.val(); | 
					 | 
				
			||||||
		if (isNaN(val)) val = 1; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var step = +($this.attr('step') || 1); | 
					 | 
				
			||||||
		var min = +$this.attr('min'); | 
					 | 
				
			||||||
		var max = +$this.attr('max'); | 
					 | 
				
			||||||
		if(e.wheelDelta > 0) { | 
					 | 
				
			||||||
			val += step; | 
					 | 
				
			||||||
		} else { | 
					 | 
				
			||||||
			val -= step; | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (typeof min != 'undefined') val = Math.max(val, +min); | 
					 | 
				
			||||||
		if (typeof max != 'undefined') val = Math.min(val, +max); | 
					 | 
				
			||||||
		$this.val(val); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if ("createEvent" in document) { | 
					 | 
				
			||||||
			var evt = document.createEvent("HTMLEvents"); | 
					 | 
				
			||||||
			evt.initEvent("change", false, true); | 
					 | 
				
			||||||
			$this[0].dispatchEvent(evt); | 
					 | 
				
			||||||
		} else { | 
					 | 
				
			||||||
			$this[0].fireEvent("onchange"); | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		e.preventDefault(); | 
					 | 
				
			||||||
	}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var errAt = location.search.indexOf('err='); | 
					 | 
				
			||||||
	if (errAt !== -1 && qs('.Box.errors')) { | 
					 | 
				
			||||||
		var errs = location.search.substr(errAt+4).split(','); | 
					 | 
				
			||||||
		var hres = []; | 
					 | 
				
			||||||
		errs.forEach(function(er) { | 
					 | 
				
			||||||
			var lbl = qs('label[for="'+er+'"]'); | 
					 | 
				
			||||||
			if (lbl) { | 
					 | 
				
			||||||
				lbl.classList.add('error'); | 
					 | 
				
			||||||
				hres.push(lbl.childNodes[0].textContent.trim().replace(/: ?$/, '')); | 
					 | 
				
			||||||
			} else { | 
					 | 
				
			||||||
				hres.push(er); | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
		}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		qs('.Box.errors .list').innerHTML = hres.join(', '); | 
					 | 
				
			||||||
		qs('.Box.errors').classList.remove('hidden'); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	Modal.init(); | 
					 | 
				
			||||||
	Notify.init(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// remove tabindixes from h2 if wide
 | 
					 | 
				
			||||||
	if (window.innerWidth > 550) { | 
					 | 
				
			||||||
		$('.Box h2').forEach(function (x) { | 
					 | 
				
			||||||
			x.removeAttribute('tabindex'); | 
					 | 
				
			||||||
		}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// brand works as a link back to term in widescreen mode
 | 
					 | 
				
			||||||
		var br = qs('#brand'); | 
					 | 
				
			||||||
		br && br.addEventListener('click', function() { | 
					 | 
				
			||||||
			location.href='/'; // go to terminal
 | 
					 | 
				
			||||||
		}); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
$._loader = function(vis) { | 
					 | 
				
			||||||
	$('#loader').toggleClass('show', vis); | 
					 | 
				
			||||||
}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function showPage() { | 
					 | 
				
			||||||
	$('#content').addClass('load'); | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
$.ready(function() { | 
					 | 
				
			||||||
	if (window.noAutoShow !== true) { | 
					 | 
				
			||||||
		setTimeout(function () { | 
					 | 
				
			||||||
			showPage(); | 
					 | 
				
			||||||
		}, 1); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/*! http://mths.be/fromcodepoint v0.1.0 by @mathias */ | 
					 | 
				
			||||||
if (!String.fromCodePoint) { | 
					 | 
				
			||||||
	(function() { | 
					 | 
				
			||||||
		var defineProperty = (function() { | 
					 | 
				
			||||||
			// IE 8 only supports `Object.defineProperty` on DOM elements
 | 
					 | 
				
			||||||
			try { | 
					 | 
				
			||||||
				var object = {}; | 
					 | 
				
			||||||
				var $defineProperty = Object.defineProperty; | 
					 | 
				
			||||||
				var result = $defineProperty(object, object, object) && $defineProperty; | 
					 | 
				
			||||||
			} catch(error) {} | 
					 | 
				
			||||||
			return result; | 
					 | 
				
			||||||
		}()); | 
					 | 
				
			||||||
		var stringFromCharCode = String.fromCharCode; | 
					 | 
				
			||||||
		var floor = Math.floor; | 
					 | 
				
			||||||
		var fromCodePoint = function() { | 
					 | 
				
			||||||
			var MAX_SIZE = 0x4000; | 
					 | 
				
			||||||
			var codeUnits = []; | 
					 | 
				
			||||||
			var highSurrogate; | 
					 | 
				
			||||||
			var lowSurrogate; | 
					 | 
				
			||||||
			var index = -1; | 
					 | 
				
			||||||
			var length = arguments.length; | 
					 | 
				
			||||||
			if (!length) { | 
					 | 
				
			||||||
				return ''; | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
			var result = ''; | 
					 | 
				
			||||||
			while (++index < length) { | 
					 | 
				
			||||||
				var codePoint = Number(arguments[index]); | 
					 | 
				
			||||||
				if ( | 
					 | 
				
			||||||
					!isFinite(codePoint) ||       // `NaN`, `+Infinity`, or `-Infinity`
 | 
					 | 
				
			||||||
					codePoint < 0 ||              // not a valid Unicode code point
 | 
					 | 
				
			||||||
					codePoint > 0x10FFFF ||       // not a valid Unicode code point
 | 
					 | 
				
			||||||
					floor(codePoint) != codePoint // not an integer
 | 
					 | 
				
			||||||
				) { | 
					 | 
				
			||||||
					throw RangeError('Invalid code point: ' + codePoint); | 
					 | 
				
			||||||
				} | 
					 | 
				
			||||||
				if (codePoint <= 0xFFFF) { // BMP code point
 | 
					 | 
				
			||||||
					codeUnits.push(codePoint); | 
					 | 
				
			||||||
				} else { // Astral code point; split in surrogate halves
 | 
					 | 
				
			||||||
					// http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
 | 
					 | 
				
			||||||
					codePoint -= 0x10000; | 
					 | 
				
			||||||
					highSurrogate = (codePoint >> 10) + 0xD800; | 
					 | 
				
			||||||
					lowSurrogate = (codePoint % 0x400) + 0xDC00; | 
					 | 
				
			||||||
					codeUnits.push(highSurrogate, lowSurrogate); | 
					 | 
				
			||||||
				} | 
					 | 
				
			||||||
				if (index + 1 == length || codeUnits.length > MAX_SIZE) { | 
					 | 
				
			||||||
					result += stringFromCharCode.apply(null, codeUnits); | 
					 | 
				
			||||||
					codeUnits.length = 0; | 
					 | 
				
			||||||
				} | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
			return result; | 
					 | 
				
			||||||
		}; | 
					 | 
				
			||||||
		if (defineProperty) { | 
					 | 
				
			||||||
			defineProperty(String, 'fromCodePoint', { | 
					 | 
				
			||||||
				'value': fromCodePoint, | 
					 | 
				
			||||||
				'configurable': true, | 
					 | 
				
			||||||
				'writable': true | 
					 | 
				
			||||||
			}); | 
					 | 
				
			||||||
		} else { | 
					 | 
				
			||||||
			String.fromCodePoint = fromCodePoint; | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
	}()); | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,44 +0,0 @@ | 
				
			|||||||
/** Module for toggling a modal overlay */ | 
					 | 
				
			||||||
(function () { | 
					 | 
				
			||||||
	var modal = {}; | 
					 | 
				
			||||||
	var curCloseCb = null; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	modal.show = function (sel, closeCb) { | 
					 | 
				
			||||||
		var $m = $(sel); | 
					 | 
				
			||||||
		$m.removeClass('hidden visible'); | 
					 | 
				
			||||||
		setTimeout(function () { | 
					 | 
				
			||||||
			$m.addClass('visible'); | 
					 | 
				
			||||||
		}, 1); | 
					 | 
				
			||||||
		curCloseCb = closeCb; | 
					 | 
				
			||||||
	}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	modal.hide = function (sel) { | 
					 | 
				
			||||||
		var $m = $(sel); | 
					 | 
				
			||||||
		$m.removeClass('visible'); | 
					 | 
				
			||||||
		setTimeout(function () { | 
					 | 
				
			||||||
			$m.addClass('hidden'); | 
					 | 
				
			||||||
			if (curCloseCb) curCloseCb(); | 
					 | 
				
			||||||
		}, 500); // transition time
 | 
					 | 
				
			||||||
	}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	modal.init = function () { | 
					 | 
				
			||||||
		// close modal by click outside the dialog
 | 
					 | 
				
			||||||
		$('.Modal').on('click', function () { | 
					 | 
				
			||||||
			if ($(this).hasClass('no-close')) return; // this is a no-close modal
 | 
					 | 
				
			||||||
			modal.hide(this); | 
					 | 
				
			||||||
		}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		$('.Dialog').on('click', function (e) { | 
					 | 
				
			||||||
			e.stopImmediatePropagation(); | 
					 | 
				
			||||||
		}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Hide all modals on esc
 | 
					 | 
				
			||||||
		$(window).on('keydown', function (e) { | 
					 | 
				
			||||||
			if (e.which == 27) { | 
					 | 
				
			||||||
				modal.hide('.Modal'); | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
		}); | 
					 | 
				
			||||||
	}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	window.Modal = modal; | 
					 | 
				
			||||||
})(); | 
					 | 
				
			||||||
@ -1,32 +0,0 @@ | 
				
			|||||||
(function (nt) { | 
					 | 
				
			||||||
	var sel = '#notif'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var hideTmeo1; // timeout to start hiding (transition)
 | 
					 | 
				
			||||||
	var hideTmeo2; // timeout to add the hidden class
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	nt.show = function (message, timeout) { | 
					 | 
				
			||||||
		$(sel).html(message); | 
					 | 
				
			||||||
		Modal.show(sel); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		clearTimeout(hideTmeo1); | 
					 | 
				
			||||||
		clearTimeout(hideTmeo2); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (undef(timeout)) timeout = 2500; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		hideTmeo1 = setTimeout(nt.hide, timeout); | 
					 | 
				
			||||||
	}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	nt.hide = function () { | 
					 | 
				
			||||||
		var $m = $(sel); | 
					 | 
				
			||||||
		$m.removeClass('visible'); | 
					 | 
				
			||||||
		hideTmeo2 = setTimeout(function () { | 
					 | 
				
			||||||
			$m.addClass('hidden'); | 
					 | 
				
			||||||
		}, 250); // transition time
 | 
					 | 
				
			||||||
	}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	nt.init = function() { | 
					 | 
				
			||||||
		$(sel).on('click', function() { | 
					 | 
				
			||||||
			nt.hide(this); | 
					 | 
				
			||||||
		}); | 
					 | 
				
			||||||
	}; | 
					 | 
				
			||||||
})(window.Notify = {}); | 
					 | 
				
			||||||
@ -1,6 +0,0 @@ | 
				
			|||||||
/** Init the terminal sub-module - called from HTML */ | 
					 | 
				
			||||||
window.termInit = function () { | 
					 | 
				
			||||||
	Conn.init(); | 
					 | 
				
			||||||
	Input.init(); | 
					 | 
				
			||||||
	TermUpl.init(); | 
					 | 
				
			||||||
}; | 
					 | 
				
			||||||
@ -1,134 +0,0 @@ | 
				
			|||||||
/** Handle connections */ | 
					 | 
				
			||||||
var Conn = (function () { | 
					 | 
				
			||||||
  var ws; | 
					 | 
				
			||||||
  var heartbeatTout; | 
					 | 
				
			||||||
  var pingIv; | 
					 | 
				
			||||||
  var xoff = false; | 
					 | 
				
			||||||
  var autoXoffTout; | 
					 | 
				
			||||||
  var reconTout; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  var pageShown = false; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function onOpen(evt) { | 
					 | 
				
			||||||
    console.log("CONNECTED"); | 
					 | 
				
			||||||
    doSend("i"); | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function onClose(evt) { | 
					 | 
				
			||||||
    console.warn("SOCKET CLOSED, code " + evt.code + ". Reconnecting..."); | 
					 | 
				
			||||||
    clearTimeout(reconTout); | 
					 | 
				
			||||||
    reconTout = setTimeout(function () { | 
					 | 
				
			||||||
      init(); | 
					 | 
				
			||||||
    }, 2000); | 
					 | 
				
			||||||
    // this happens when the buffer gets fucked up via invalid unicode.
 | 
					 | 
				
			||||||
    // we basically use polling instead of socket then
 | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function onMessage(evt) { | 
					 | 
				
			||||||
    try { | 
					 | 
				
			||||||
      // . = heartbeat
 | 
					 | 
				
			||||||
      switch (evt.data.charAt(0)) { | 
					 | 
				
			||||||
        case 'B': | 
					 | 
				
			||||||
        case 'T': | 
					 | 
				
			||||||
        case 'S': | 
					 | 
				
			||||||
          Screen.load(evt.data); | 
					 | 
				
			||||||
          if(!pageShown) { | 
					 | 
				
			||||||
            showPage(); | 
					 | 
				
			||||||
            pageShown = true; | 
					 | 
				
			||||||
          } | 
					 | 
				
			||||||
          break; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        case '-': | 
					 | 
				
			||||||
          //console.log('xoff');
 | 
					 | 
				
			||||||
          xoff = true; | 
					 | 
				
			||||||
          autoXoffTout = setTimeout(function () { | 
					 | 
				
			||||||
            xoff = false; | 
					 | 
				
			||||||
          }, 250); | 
					 | 
				
			||||||
          break; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        case '+': | 
					 | 
				
			||||||
          //console.log('xon');
 | 
					 | 
				
			||||||
          xoff = false; | 
					 | 
				
			||||||
          clearTimeout(autoXoffTout); | 
					 | 
				
			||||||
          break; | 
					 | 
				
			||||||
      } | 
					 | 
				
			||||||
      heartbeat(); | 
					 | 
				
			||||||
    } catch (e) { | 
					 | 
				
			||||||
      console.error(e); | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function canSend() { | 
					 | 
				
			||||||
    return !xoff; | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function doSend(message) { | 
					 | 
				
			||||||
    if (_demo) { | 
					 | 
				
			||||||
      console.log("TX: ", message); | 
					 | 
				
			||||||
      return true; // Simulate success
 | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
    if (xoff) { | 
					 | 
				
			||||||
      // TODO queue
 | 
					 | 
				
			||||||
      console.log("Can't send, flood control."); | 
					 | 
				
			||||||
      return false; | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!ws) return false; // for dry testing
 | 
					 | 
				
			||||||
    if (ws.readyState != 1) { | 
					 | 
				
			||||||
      console.error("Socket not ready"); | 
					 | 
				
			||||||
      return false; | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
    if (typeof message != "string") { | 
					 | 
				
			||||||
      message = JSON.stringify(message); | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
    ws.send(message); | 
					 | 
				
			||||||
    return true; | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function init() { | 
					 | 
				
			||||||
    if (_demo) { | 
					 | 
				
			||||||
      console.log("Demo mode!"); | 
					 | 
				
			||||||
      Screen.load(_demo_screen); | 
					 | 
				
			||||||
      showPage(); | 
					 | 
				
			||||||
      return; | 
					 | 
				
			||||||
    } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    clearTimeout(reconTout); | 
					 | 
				
			||||||
    clearTimeout(heartbeatTout); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ws = new WebSocket("ws://" + _root + "/term/update.ws"); | 
					 | 
				
			||||||
    ws.onopen = onOpen; | 
					 | 
				
			||||||
    ws.onclose = onClose; | 
					 | 
				
			||||||
    ws.onmessage = onMessage; | 
					 | 
				
			||||||
    console.log("Opening socket."); | 
					 | 
				
			||||||
    heartbeat(); | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function heartbeat() { | 
					 | 
				
			||||||
    clearTimeout(heartbeatTout); | 
					 | 
				
			||||||
    heartbeatTout = setTimeout(heartbeatFail, 2000); | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  function heartbeatFail() { | 
					 | 
				
			||||||
    console.error("Heartbeat lost, probing server..."); | 
					 | 
				
			||||||
    pingIv = setInterval(function () { | 
					 | 
				
			||||||
      console.log("> ping"); | 
					 | 
				
			||||||
      $.get('http://' + _root + '/system/ping', function (resp, status) { | 
					 | 
				
			||||||
        if (status == 200) { | 
					 | 
				
			||||||
          clearInterval(pingIv); | 
					 | 
				
			||||||
          console.info("Server ready, reloading page..."); | 
					 | 
				
			||||||
          location.reload(); | 
					 | 
				
			||||||
        } | 
					 | 
				
			||||||
      }, { | 
					 | 
				
			||||||
        timeout: 100, | 
					 | 
				
			||||||
      }); | 
					 | 
				
			||||||
    }, 1000); | 
					 | 
				
			||||||
  } | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  return { | 
					 | 
				
			||||||
    ws: null, | 
					 | 
				
			||||||
    init: init, | 
					 | 
				
			||||||
    send: doSend, | 
					 | 
				
			||||||
    canSend: canSend, // check flood control
 | 
					 | 
				
			||||||
  }; | 
					 | 
				
			||||||
})(); | 
					 | 
				
			||||||
@ -1,264 +0,0 @@ | 
				
			|||||||
/** | 
					 | 
				
			||||||
 * User input | 
					 | 
				
			||||||
 * | 
					 | 
				
			||||||
 * --- Rx messages: --- | 
					 | 
				
			||||||
 * S - screen content (binary encoding of the entire screen with simple compression) | 
					 | 
				
			||||||
 * T - text labels - Title and buttons, \0x01-separated | 
					 | 
				
			||||||
 * B - beep | 
					 | 
				
			||||||
 * . - heartbeat | 
					 | 
				
			||||||
 * | 
					 | 
				
			||||||
 * --- Tx messages --- | 
					 | 
				
			||||||
 * s - string | 
					 | 
				
			||||||
 * b - action button | 
					 | 
				
			||||||
 * p - mb press | 
					 | 
				
			||||||
 * r - mb release | 
					 | 
				
			||||||
 * m - mouse move | 
					 | 
				
			||||||
 */ | 
					 | 
				
			||||||
var Input = (function() { | 
					 | 
				
			||||||
	var opts = { | 
					 | 
				
			||||||
		np_alt: false, | 
					 | 
				
			||||||
		cu_alt: false, | 
					 | 
				
			||||||
		fn_alt: false, | 
					 | 
				
			||||||
		mt_click: false, | 
					 | 
				
			||||||
		mt_move: false, | 
					 | 
				
			||||||
		no_keys: false, | 
					 | 
				
			||||||
		crlf_mode: false, | 
					 | 
				
			||||||
	}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Send a literal message */ | 
					 | 
				
			||||||
	function sendStrMsg(str) { | 
					 | 
				
			||||||
		return Conn.send("s"+str); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Send a button event */ | 
					 | 
				
			||||||
	function sendBtnMsg(n) { | 
					 | 
				
			||||||
		Conn.send("b"+Chr(n)); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Fn alt choice for key message */ | 
					 | 
				
			||||||
	function fa(alt, normal) { | 
					 | 
				
			||||||
		return opts.fn_alt ? alt : normal; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Cursor alt choice for key message */ | 
					 | 
				
			||||||
	function ca(alt, normal) { | 
					 | 
				
			||||||
		return opts.cu_alt ? alt : normal; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Numpad alt choice for key message */ | 
					 | 
				
			||||||
	function na(alt, normal) { | 
					 | 
				
			||||||
		return opts.np_alt ? alt : normal; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	function _bindFnKeys() { | 
					 | 
				
			||||||
		var keymap = { | 
					 | 
				
			||||||
			'tab': '\x09', | 
					 | 
				
			||||||
			'backspace': '\x08', | 
					 | 
				
			||||||
			'enter': opts.crlf_mode ? '\x0d\x0a' : '\x0d', | 
					 | 
				
			||||||
			'ctrl+enter': '\x0a', | 
					 | 
				
			||||||
			'esc': '\x1b', | 
					 | 
				
			||||||
			'up': ca('\x1bOA', '\x1b[A'), | 
					 | 
				
			||||||
			'down': ca('\x1bOB', '\x1b[B'), | 
					 | 
				
			||||||
			'right': ca('\x1bOC', '\x1b[C'), | 
					 | 
				
			||||||
			'left': ca('\x1bOD', '\x1b[D'), | 
					 | 
				
			||||||
			'home': ca('\x1bOH', fa('\x1b[H', '\x1b[1~')), | 
					 | 
				
			||||||
			'insert': '\x1b[2~', | 
					 | 
				
			||||||
			'delete': '\x1b[3~', | 
					 | 
				
			||||||
			'end': ca('\x1bOF', fa('\x1b[F', '\x1b[4~')), | 
					 | 
				
			||||||
			'pageup': '\x1b[5~', | 
					 | 
				
			||||||
			'pagedown': '\x1b[6~', | 
					 | 
				
			||||||
			'f1': fa('\x1bOP', '\x1b[11~'), | 
					 | 
				
			||||||
			'f2': fa('\x1bOQ', '\x1b[12~'), | 
					 | 
				
			||||||
			'f3': fa('\x1bOR', '\x1b[13~'), | 
					 | 
				
			||||||
			'f4': fa('\x1bOS', '\x1b[14~'), | 
					 | 
				
			||||||
			'f5': '\x1b[15~', // note the disconnect
 | 
					 | 
				
			||||||
			'f6': '\x1b[17~', | 
					 | 
				
			||||||
			'f7': '\x1b[18~', | 
					 | 
				
			||||||
			'f8': '\x1b[19~', | 
					 | 
				
			||||||
			'f9': '\x1b[20~', | 
					 | 
				
			||||||
			'f10': '\x1b[21~', // note the disconnect
 | 
					 | 
				
			||||||
			'f11': '\x1b[23~', | 
					 | 
				
			||||||
			'f12': '\x1b[24~', | 
					 | 
				
			||||||
			'shift+f1': fa('\x1bO1;2P', '\x1b[25~'), | 
					 | 
				
			||||||
			'shift+f2': fa('\x1bO1;2Q', '\x1b[26~'), // note the disconnect
 | 
					 | 
				
			||||||
			'shift+f3': fa('\x1bO1;2R', '\x1b[28~'), | 
					 | 
				
			||||||
			'shift+f4': fa('\x1bO1;2S', '\x1b[29~'), // note the disconnect
 | 
					 | 
				
			||||||
			'shift+f5': fa('\x1b[15;2~', '\x1b[31~'), | 
					 | 
				
			||||||
			'shift+f6': fa('\x1b[17;2~', '\x1b[32~'), | 
					 | 
				
			||||||
			'shift+f7': fa('\x1b[18;2~', '\x1b[33~'), | 
					 | 
				
			||||||
			'shift+f8': fa('\x1b[19;2~', '\x1b[34~'), | 
					 | 
				
			||||||
			'shift+f9': fa('\x1b[20;2~', '\x1b[35~'), // 35-38 are not standard - but what is?
 | 
					 | 
				
			||||||
			'shift+f10': fa('\x1b[21;2~', '\x1b[36~'), | 
					 | 
				
			||||||
			'shift+f11': fa('\x1b[22;2~', '\x1b[37~'), | 
					 | 
				
			||||||
			'shift+f12': fa('\x1b[23;2~', '\x1b[38~'), | 
					 | 
				
			||||||
			'np_0': na('\x1bOp', '0'), | 
					 | 
				
			||||||
			'np_1': na('\x1bOq', '1'), | 
					 | 
				
			||||||
			'np_2': na('\x1bOr', '2'), | 
					 | 
				
			||||||
			'np_3': na('\x1bOs', '3'), | 
					 | 
				
			||||||
			'np_4': na('\x1bOt', '4'), | 
					 | 
				
			||||||
			'np_5': na('\x1bOu', '5'), | 
					 | 
				
			||||||
			'np_6': na('\x1bOv', '6'), | 
					 | 
				
			||||||
			'np_7': na('\x1bOw', '7'), | 
					 | 
				
			||||||
			'np_8': na('\x1bOx', '8'), | 
					 | 
				
			||||||
			'np_9': na('\x1bOy', '9'), | 
					 | 
				
			||||||
			'np_mul': na('\x1bOR', '*'), | 
					 | 
				
			||||||
			'np_add': na('\x1bOl', '+'), | 
					 | 
				
			||||||
			'np_sub': na('\x1bOS', '-'), | 
					 | 
				
			||||||
			'np_point': na('\x1bOn', '.'), | 
					 | 
				
			||||||
			'np_div': na('\x1bOQ', '/'), | 
					 | 
				
			||||||
			// we don't implement numlock key (should change in numpad_alt mode, but it's even more useless than the rest)
 | 
					 | 
				
			||||||
		}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for (var k in keymap) { | 
					 | 
				
			||||||
			if (keymap.hasOwnProperty(k)) { | 
					 | 
				
			||||||
				bind(k, keymap[k]); | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Bind a keystroke to message */ | 
					 | 
				
			||||||
	function bind(combo, str) { | 
					 | 
				
			||||||
		// mac fix - allow also cmd
 | 
					 | 
				
			||||||
		if (combo.indexOf('ctrl+') !== -1) { | 
					 | 
				
			||||||
			combo += ',' + combo.replace('ctrl', 'command'); | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// unbind possible old binding
 | 
					 | 
				
			||||||
		key.unbind(combo); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		key(combo, function (e) { | 
					 | 
				
			||||||
			if (opts.no_keys) return; | 
					 | 
				
			||||||
			e.preventDefault(); | 
					 | 
				
			||||||
			sendStrMsg(str) | 
					 | 
				
			||||||
		}); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Bind/rebind key messages */ | 
					 | 
				
			||||||
	function _initKeys() { | 
					 | 
				
			||||||
		// This takes care of text characters typed
 | 
					 | 
				
			||||||
		window.addEventListener('keypress', function(evt) { | 
					 | 
				
			||||||
			if (opts.no_keys) return; | 
					 | 
				
			||||||
			var str = ''; | 
					 | 
				
			||||||
			if (evt.key) str = evt.key; | 
					 | 
				
			||||||
			else if (evt.which) str = String.fromCodePoint(evt.which); | 
					 | 
				
			||||||
			if (str.length>0 && str.charCodeAt(0) >= 32) { | 
					 | 
				
			||||||
//				console.log("Typed ", str);
 | 
					 | 
				
			||||||
				sendStrMsg(str); | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
		}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// ctrl-letter codes are sent as simple low ASCII codes
 | 
					 | 
				
			||||||
		for (var i = 1; i<=26;i++) { | 
					 | 
				
			||||||
			bind('ctrl+' + String.fromCharCode(96+i), String.fromCharCode(i)); | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
		bind('ctrl+]', '\x1b'); // alternate way to enter ESC
 | 
					 | 
				
			||||||
		bind('ctrl+\\', '\x1c'); | 
					 | 
				
			||||||
		bind('ctrl+[', '\x1d'); | 
					 | 
				
			||||||
		bind('ctrl+^', '\x1e'); | 
					 | 
				
			||||||
		bind('ctrl+_', '\x1f'); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		_bindFnKeys(); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// mouse button states
 | 
					 | 
				
			||||||
	var mb1 = 0; | 
					 | 
				
			||||||
	var mb2 = 0; | 
					 | 
				
			||||||
	var mb3 = 0; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Init the Input module */ | 
					 | 
				
			||||||
	function init() { | 
					 | 
				
			||||||
		_initKeys(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Button presses
 | 
					 | 
				
			||||||
		$('#action-buttons button').forEach(function(s) { | 
					 | 
				
			||||||
			s.addEventListener('click', function() { | 
					 | 
				
			||||||
				sendBtnMsg(+this.dataset['n']); | 
					 | 
				
			||||||
			}); | 
					 | 
				
			||||||
		}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// global mouse state tracking - for motion reporting
 | 
					 | 
				
			||||||
		window.addEventListener('mousedown', function(evt) { | 
					 | 
				
			||||||
			if (evt.button == 0) mb1 = 1; | 
					 | 
				
			||||||
			if (evt.button == 1) mb2 = 1; | 
					 | 
				
			||||||
			if (evt.button == 2) mb3 = 1; | 
					 | 
				
			||||||
		}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		window.addEventListener('mouseup', function(evt) { | 
					 | 
				
			||||||
			if (evt.button == 0) mb1 = 0; | 
					 | 
				
			||||||
			if (evt.button == 1) mb2 = 0; | 
					 | 
				
			||||||
			if (evt.button == 2) mb3 = 0; | 
					 | 
				
			||||||
		}); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Prepare modifiers byte for mouse message */ | 
					 | 
				
			||||||
	function packModifiersForMouse() { | 
					 | 
				
			||||||
		return (key.isModifier('ctrl')?1:0) | | 
					 | 
				
			||||||
			(key.isModifier('shift')?2:0) | | 
					 | 
				
			||||||
			(key.isModifier('alt')?4:0) | | 
					 | 
				
			||||||
			(key.isModifier('meta')?8:0); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return { | 
					 | 
				
			||||||
		/** Init the Input module */ | 
					 | 
				
			||||||
		init: init, | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/** Send a literal string message */ | 
					 | 
				
			||||||
		sendString: sendStrMsg, | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/** Enable alternate key modes (cursors, numpad, fn) */ | 
					 | 
				
			||||||
		setAlts: function(cu, np, fn, crlf) { | 
					 | 
				
			||||||
			if (opts.cu_alt != cu || opts.np_alt != np || opts.fn_alt != fn || opts.crlf_mode != crlf) { | 
					 | 
				
			||||||
				opts.cu_alt = cu; | 
					 | 
				
			||||||
				opts.np_alt = np; | 
					 | 
				
			||||||
				opts.fn_alt = fn; | 
					 | 
				
			||||||
				opts.crlf_mode = crlf; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				// rebind keys - codes have changed
 | 
					 | 
				
			||||||
				_bindFnKeys(); | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
		}, | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		setMouseMode: function(click, move) { | 
					 | 
				
			||||||
			opts.mt_click = click; | 
					 | 
				
			||||||
			opts.mt_move = move; | 
					 | 
				
			||||||
		}, | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Mouse events
 | 
					 | 
				
			||||||
		onMouseMove: function (x, y) { | 
					 | 
				
			||||||
			if (!opts.mt_move) return; | 
					 | 
				
			||||||
			var b = mb1 ? 1 : mb2 ? 2 : mb3 ? 3 : 0; | 
					 | 
				
			||||||
			var m = packModifiersForMouse(); | 
					 | 
				
			||||||
			Conn.send("m" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)); | 
					 | 
				
			||||||
		}, | 
					 | 
				
			||||||
		onMouseDown: function (x, y, b) { | 
					 | 
				
			||||||
			if (!opts.mt_click) return; | 
					 | 
				
			||||||
			if (b > 3 || b < 1) return; | 
					 | 
				
			||||||
			var m = packModifiersForMouse(); | 
					 | 
				
			||||||
			Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)); | 
					 | 
				
			||||||
			// console.log("B ",b," M ",m);
 | 
					 | 
				
			||||||
		}, | 
					 | 
				
			||||||
		onMouseUp: function (x, y, b) { | 
					 | 
				
			||||||
			if (!opts.mt_click) return; | 
					 | 
				
			||||||
			if (b > 3 || b < 1) return; | 
					 | 
				
			||||||
			var m = packModifiersForMouse(); | 
					 | 
				
			||||||
			Conn.send("r" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)); | 
					 | 
				
			||||||
			// console.log("B ",b," M ",m);
 | 
					 | 
				
			||||||
		}, | 
					 | 
				
			||||||
		onMouseWheel: function (x, y, dir) { | 
					 | 
				
			||||||
			if (!opts.mt_click) return; | 
					 | 
				
			||||||
			// -1 ... btn 4 (away from user)
 | 
					 | 
				
			||||||
			// +1 ... btn 5 (towards user)
 | 
					 | 
				
			||||||
			var m = packModifiersForMouse(); | 
					 | 
				
			||||||
			var b = (dir < 0 ? 4 : 5); | 
					 | 
				
			||||||
			Conn.send("p" + encode2B(y) + encode2B(x) + encode2B(b) + encode2B(m)); | 
					 | 
				
			||||||
			// console.log("B ",b," M ",m);
 | 
					 | 
				
			||||||
		}, | 
					 | 
				
			||||||
		mouseTracksClicks: function() { | 
					 | 
				
			||||||
			return opts.mt_click; | 
					 | 
				
			||||||
		}, | 
					 | 
				
			||||||
		blockKeys: function(yes) { | 
					 | 
				
			||||||
			opts.no_keys = yes; | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
	}; | 
					 | 
				
			||||||
})(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@ -1,380 +0,0 @@ | 
				
			|||||||
var Screen = (function () { | 
					 | 
				
			||||||
	var W = 0, H = 0; // dimensions
 | 
					 | 
				
			||||||
	var inited = false; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var cursor = { | 
					 | 
				
			||||||
		a: false,        // active (blink state)
 | 
					 | 
				
			||||||
		x: 0,            // 0-based coordinates
 | 
					 | 
				
			||||||
		y: 0, | 
					 | 
				
			||||||
		fg: 7,           // colors 0-15
 | 
					 | 
				
			||||||
		bg: 0, | 
					 | 
				
			||||||
		attrs: 0, | 
					 | 
				
			||||||
		suppress: false, // do not turn on in blink interval (for safe moving)
 | 
					 | 
				
			||||||
		forceOn: false,  // force on unless hanging: used to keep cursor visible during move
 | 
					 | 
				
			||||||
		hidden: false,    // do not show (DEC opt)
 | 
					 | 
				
			||||||
		hanging: false,   // cursor at column "W+1" - not visible
 | 
					 | 
				
			||||||
	}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var screen = []; | 
					 | 
				
			||||||
	var blinkIval; | 
					 | 
				
			||||||
	var cursorFlashStartIval; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Some non-bold Fraktur symbols are outside the contiguous block
 | 
					 | 
				
			||||||
	var frakturExceptions = { | 
					 | 
				
			||||||
		'C': '\u212d', | 
					 | 
				
			||||||
		'H': '\u210c', | 
					 | 
				
			||||||
		'I': '\u2111', | 
					 | 
				
			||||||
		'R': '\u211c', | 
					 | 
				
			||||||
		'Z': '\u2128', | 
					 | 
				
			||||||
	}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// for BEL
 | 
					 | 
				
			||||||
	var audioCtx = null; | 
					 | 
				
			||||||
	try { | 
					 | 
				
			||||||
		audioCtx = new (window.AudioContext || window.audioContext || window.webkitAudioContext)(); | 
					 | 
				
			||||||
	} catch (er) { | 
					 | 
				
			||||||
		console.error("No AudioContext!", er); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Get cell under cursor */ | 
					 | 
				
			||||||
	function _curCell() { | 
					 | 
				
			||||||
		return screen[cursor.y*W + cursor.x]; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Safely move cursor */ | 
					 | 
				
			||||||
	function cursorSet(y, x) { | 
					 | 
				
			||||||
		// Hide and prevent from showing up during the move
 | 
					 | 
				
			||||||
		cursor.suppress = true; | 
					 | 
				
			||||||
		_draw(_curCell(), false); | 
					 | 
				
			||||||
		cursor.x = x; | 
					 | 
				
			||||||
		cursor.y = y; | 
					 | 
				
			||||||
		// Show again
 | 
					 | 
				
			||||||
		cursor.suppress = false; | 
					 | 
				
			||||||
		_draw(_curCell()); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	function alpha2fraktur(t) { | 
					 | 
				
			||||||
		// perform substitution
 | 
					 | 
				
			||||||
		if (t >= 'a' && t <= 'z') { | 
					 | 
				
			||||||
			t = String.fromCodePoint(0x1d51e - 97 + t.charCodeAt(0)); | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
		else if (t >= 'A' && t <= 'Z') { | 
					 | 
				
			||||||
			// this set is incomplete, some exceptions are needed
 | 
					 | 
				
			||||||
			if (frakturExceptions.hasOwnProperty(t)) { | 
					 | 
				
			||||||
				t = frakturExceptions[t]; | 
					 | 
				
			||||||
			} else { | 
					 | 
				
			||||||
				t = String.fromCodePoint(0x1d504 - 65 + t.charCodeAt(0)); | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
		return t; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Update cell on display. inv = invert (for cursor) */ | 
					 | 
				
			||||||
	function _draw(cell, inv) { | 
					 | 
				
			||||||
		if (!cell) return; | 
					 | 
				
			||||||
		if (typeof inv == 'undefined') { | 
					 | 
				
			||||||
			inv = cursor.a && cursor.x == cell.x && cursor.y == cell.y; | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var fg, bg, cn, t; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		fg = inv ? cell.bg : cell.fg; | 
					 | 
				
			||||||
		bg = inv ? cell.fg : cell.bg; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		t = cell.t; | 
					 | 
				
			||||||
		if (!t.length) t = ' '; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		cn = 'fg' + fg + ' bg' + bg; | 
					 | 
				
			||||||
		if (cell.attrs & (1<<0)) cn += ' bold'; | 
					 | 
				
			||||||
		if (cell.attrs & (1<<1)) cn += ' faint'; | 
					 | 
				
			||||||
		if (cell.attrs & (1<<2)) cn += ' italic'; | 
					 | 
				
			||||||
		if (cell.attrs & (1<<3)) cn += ' under'; | 
					 | 
				
			||||||
		if (cell.attrs & (1<<4)) cn += ' blink'; | 
					 | 
				
			||||||
		if (cell.attrs & (1<<5)) { | 
					 | 
				
			||||||
			cn += ' fraktur'; | 
					 | 
				
			||||||
			t = alpha2fraktur(t); | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
		if (cell.attrs & (1<<6)) cn += ' strike'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		cell.slot.textContent = t; | 
					 | 
				
			||||||
		cell.elem.className = cn; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Show entire screen */ | 
					 | 
				
			||||||
	function _drawAll() { | 
					 | 
				
			||||||
		for (var i = W*H-1; i>=0; i--) { | 
					 | 
				
			||||||
			_draw(screen[i]); | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	function _rebuild(rows, cols) { | 
					 | 
				
			||||||
		W = cols; | 
					 | 
				
			||||||
		H = rows; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/* Build screen & show */ | 
					 | 
				
			||||||
		var cOuter, cInner, cell, screenDiv = qs('#screen'); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Empty the screen node
 | 
					 | 
				
			||||||
		while (screenDiv.firstChild) screenDiv.removeChild(screenDiv.firstChild); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		screen = []; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for(var i = 0; i < W*H; i++) { | 
					 | 
				
			||||||
			cOuter = mk('span'); | 
					 | 
				
			||||||
			cInner = mk('span'); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			/* Mouse tracking */ | 
					 | 
				
			||||||
			(function() { | 
					 | 
				
			||||||
				var x = i % W; | 
					 | 
				
			||||||
				var y = Math.floor(i / W); | 
					 | 
				
			||||||
				cOuter.addEventListener('mouseenter', function (evt) { | 
					 | 
				
			||||||
					Input.onMouseMove(x, y); | 
					 | 
				
			||||||
				}); | 
					 | 
				
			||||||
				cOuter.addEventListener('mousedown', function (evt) { | 
					 | 
				
			||||||
					Input.onMouseDown(x, y, evt.button+1); | 
					 | 
				
			||||||
				}); | 
					 | 
				
			||||||
				cOuter.addEventListener('mouseup', function (evt) { | 
					 | 
				
			||||||
					Input.onMouseUp(x, y, evt.button+1); | 
					 | 
				
			||||||
				}); | 
					 | 
				
			||||||
				cOuter.addEventListener('contextmenu', function (evt) { | 
					 | 
				
			||||||
					if (Input.mouseTracksClicks()) { | 
					 | 
				
			||||||
						evt.preventDefault(); | 
					 | 
				
			||||||
					} | 
					 | 
				
			||||||
				}); | 
					 | 
				
			||||||
				cOuter.addEventListener('mousewheel', function (evt) { | 
					 | 
				
			||||||
					Input.onMouseWheel(x, y, evt.deltaY>0?1:-1); | 
					 | 
				
			||||||
					return false; | 
					 | 
				
			||||||
				}); | 
					 | 
				
			||||||
			})(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			/* End of line */ | 
					 | 
				
			||||||
			if ((i > 0) && (i % W == 0)) { | 
					 | 
				
			||||||
				screenDiv.appendChild(mk('br')); | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
			/* The cell */ | 
					 | 
				
			||||||
			cOuter.appendChild(cInner); | 
					 | 
				
			||||||
			screenDiv.appendChild(cOuter); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			cell = { | 
					 | 
				
			||||||
				t: ' ', | 
					 | 
				
			||||||
				fg: 7, | 
					 | 
				
			||||||
				bg: 0, // the colors will be replaced immediately as we receive data (user won't see this)
 | 
					 | 
				
			||||||
				attrs: 0, | 
					 | 
				
			||||||
				elem: cOuter, | 
					 | 
				
			||||||
				slot: cInner, | 
					 | 
				
			||||||
				x: i % W, | 
					 | 
				
			||||||
				y: Math.floor(i / W), | 
					 | 
				
			||||||
			}; | 
					 | 
				
			||||||
			screen.push(cell); | 
					 | 
				
			||||||
			_draw(cell); | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Init the terminal */ | 
					 | 
				
			||||||
	function _init() { | 
					 | 
				
			||||||
		/* Cursor blinking */ | 
					 | 
				
			||||||
		clearInterval(blinkIval); | 
					 | 
				
			||||||
		blinkIval = setInterval(function () { | 
					 | 
				
			||||||
			cursor.a = !cursor.a; | 
					 | 
				
			||||||
			if (cursor.hidden || cursor.hanging) { | 
					 | 
				
			||||||
				cursor.a = false; | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (!cursor.suppress) { | 
					 | 
				
			||||||
				_draw(_curCell(), cursor.forceOn || cursor.a); | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
		}, 500); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		/* blink attribute animation */ | 
					 | 
				
			||||||
		setInterval(function () { | 
					 | 
				
			||||||
			$('#screen').removeClass('blink-hide'); | 
					 | 
				
			||||||
			setTimeout(function () { | 
					 | 
				
			||||||
				$('#screen').addClass('blink-hide'); | 
					 | 
				
			||||||
			}, 800); // 200 ms ON
 | 
					 | 
				
			||||||
		}, 1000); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		inited = true; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// constants for decoding the update blob
 | 
					 | 
				
			||||||
	var SEQ_SET_COLOR_ATTR = 1; | 
					 | 
				
			||||||
	var SEQ_REPEAT = 2; | 
					 | 
				
			||||||
	var SEQ_SET_COLOR = 3; | 
					 | 
				
			||||||
	var SEQ_SET_ATTR = 4; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Parse received screen update object (leading S removed already) */ | 
					 | 
				
			||||||
	function _load_content(str) { | 
					 | 
				
			||||||
		var i = 0, ci = 0, j, jc, num, num2, t = ' ', fg, bg, attrs, cell; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (!inited) _init(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var cursorMoved; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Set size
 | 
					 | 
				
			||||||
		num = parse2B(str, i); i += 2;  // height
 | 
					 | 
				
			||||||
		num2 = parse2B(str, i); i += 2; // width
 | 
					 | 
				
			||||||
		if (num != H || num2 != W) { | 
					 | 
				
			||||||
			_rebuild(num, num2); | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
		// console.log("Size ",num, num2);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Cursor position
 | 
					 | 
				
			||||||
		num = parse2B(str, i); i += 2; // row
 | 
					 | 
				
			||||||
		num2 = parse2B(str, i); i += 2; // col
 | 
					 | 
				
			||||||
		cursorMoved = (cursor.x != num2 || cursor.y != num); | 
					 | 
				
			||||||
		cursorSet(num, num2); | 
					 | 
				
			||||||
		// console.log("Cursor at ",num, num2);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Attributes
 | 
					 | 
				
			||||||
		num = parse3B(str, i); i += 3; | 
					 | 
				
			||||||
		cursor.hidden = !(num & (1<<0)); // DEC opt "visible"
 | 
					 | 
				
			||||||
		cursor.hanging = !!(num & (1<<1)); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		Input.setAlts( | 
					 | 
				
			||||||
			!!(num & (1<<2)), // cursors alt
 | 
					 | 
				
			||||||
			!!(num & (1<<3)), // numpad alt
 | 
					 | 
				
			||||||
			!!(num & (1<<4)), // fn keys alt
 | 
					 | 
				
			||||||
			!!(num & (1<<12)) // crlf mode
 | 
					 | 
				
			||||||
		); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var mt_click = !!(num & (1<<5)); | 
					 | 
				
			||||||
		var mt_move = !!(num & (1<<6)); | 
					 | 
				
			||||||
		Input.setMouseMode( | 
					 | 
				
			||||||
			mt_click, | 
					 | 
				
			||||||
			mt_move | 
					 | 
				
			||||||
		); | 
					 | 
				
			||||||
		$('#screen').toggleClass('noselect', mt_move); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var show_buttons = !!(num & (1<<7)); | 
					 | 
				
			||||||
		var show_config_links = !!(num & (1<<8)); | 
					 | 
				
			||||||
		$('.x-term-conf-btn').toggleClass('hidden', !show_config_links); | 
					 | 
				
			||||||
		$('#action-buttons').toggleClass('hidden', !show_buttons); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// bits 9-11 are cursor shape (not implemented)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		fg = 7; | 
					 | 
				
			||||||
		bg = 0; | 
					 | 
				
			||||||
		attrs = 0; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Here come the content
 | 
					 | 
				
			||||||
		while(i < str.length && ci<W*H) { | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			j = str[i++]; | 
					 | 
				
			||||||
			jc = j.charCodeAt(0); | 
					 | 
				
			||||||
			if (jc == SEQ_SET_COLOR_ATTR) { | 
					 | 
				
			||||||
				num = parse3B(str, i); i += 3; | 
					 | 
				
			||||||
				fg = num & 0x0F; | 
					 | 
				
			||||||
				bg = (num & 0xF0) >> 4; | 
					 | 
				
			||||||
				attrs = (num & 0xFF00)>>8; | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
			else if (jc == SEQ_SET_COLOR) { | 
					 | 
				
			||||||
				num = parse2B(str, i); i += 2; | 
					 | 
				
			||||||
				fg = num & 0x0F; | 
					 | 
				
			||||||
				bg = (num & 0xF0) >> 4; | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
			else if (jc == SEQ_SET_ATTR) { | 
					 | 
				
			||||||
				num = parse2B(str, i); i += 2; | 
					 | 
				
			||||||
				attrs = num & 0xFF; | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
			else if (jc == SEQ_REPEAT) { | 
					 | 
				
			||||||
				num = parse2B(str, i); i += 2; | 
					 | 
				
			||||||
				// console.log("Repeat x ",num);
 | 
					 | 
				
			||||||
				for (; num>0 && ci<W*H; num--) { | 
					 | 
				
			||||||
					cell = screen[ci++]; | 
					 | 
				
			||||||
					cell.fg = fg; | 
					 | 
				
			||||||
					cell.bg = bg; | 
					 | 
				
			||||||
					cell.t = t; | 
					 | 
				
			||||||
					cell.attrs = attrs; | 
					 | 
				
			||||||
				} | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
			else { | 
					 | 
				
			||||||
				cell = screen[ci++]; | 
					 | 
				
			||||||
				// Unique cell character
 | 
					 | 
				
			||||||
				t = cell.t = j; | 
					 | 
				
			||||||
				cell.fg = fg; | 
					 | 
				
			||||||
				cell.bg = bg; | 
					 | 
				
			||||||
				cell.attrs = attrs; | 
					 | 
				
			||||||
				// console.log("Symbol ", j);
 | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		_drawAll(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// if (!cursor.hidden || cursor.hanging || !cursor.suppress) {
 | 
					 | 
				
			||||||
		// 	// hide cursor asap
 | 
					 | 
				
			||||||
		// 	_draw(_curCell(), false);
 | 
					 | 
				
			||||||
		// }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (cursorMoved) { | 
					 | 
				
			||||||
			cursor.forceOn = true; | 
					 | 
				
			||||||
			cursorFlashStartIval = setTimeout(function() { | 
					 | 
				
			||||||
				cursor.forceOn = false; | 
					 | 
				
			||||||
			}, 1200); | 
					 | 
				
			||||||
			_draw(_curCell(), true); | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Apply labels to buttons and screen title (leading T removed already) */ | 
					 | 
				
			||||||
	function _load_labels(str) { | 
					 | 
				
			||||||
		var pieces = str.split('\x01'); | 
					 | 
				
			||||||
		qs('h1').textContent = pieces[0]; | 
					 | 
				
			||||||
		$('#action-buttons button').forEach(function(x, i) { | 
					 | 
				
			||||||
			var s = pieces[i+1].trim(); | 
					 | 
				
			||||||
			// if empty string, use the "dim" effect and put nbsp instead to stretch the btn vertically
 | 
					 | 
				
			||||||
			x.innerHTML = s.length > 0 ? e(s) : " "; | 
					 | 
				
			||||||
			x.style.opacity = s.length > 0 ? 1 : 0.2; | 
					 | 
				
			||||||
		}) | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Audible beep for ASCII 7 */ | 
					 | 
				
			||||||
	function _beep() { | 
					 | 
				
			||||||
		var osc, gain; | 
					 | 
				
			||||||
		if (!audioCtx) return; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Main beep
 | 
					 | 
				
			||||||
		osc = audioCtx.createOscillator(); | 
					 | 
				
			||||||
		gain = audioCtx.createGain(); | 
					 | 
				
			||||||
		osc.connect(gain); | 
					 | 
				
			||||||
		gain.connect(audioCtx.destination); | 
					 | 
				
			||||||
		gain.gain.value = 0.5; | 
					 | 
				
			||||||
		osc.frequency.value = 750; | 
					 | 
				
			||||||
		osc.type = 'sine'; | 
					 | 
				
			||||||
		osc.start(); | 
					 | 
				
			||||||
		osc.stop(audioCtx.currentTime+0.05); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Surrogate beep (making it sound like 'oops')
 | 
					 | 
				
			||||||
		osc = audioCtx.createOscillator(); | 
					 | 
				
			||||||
		gain = audioCtx.createGain(); | 
					 | 
				
			||||||
		osc.connect(gain); | 
					 | 
				
			||||||
		gain.connect(audioCtx.destination); | 
					 | 
				
			||||||
		gain.gain.value = 0.2; | 
					 | 
				
			||||||
		osc.frequency.value = 400; | 
					 | 
				
			||||||
		osc.type = 'sine'; | 
					 | 
				
			||||||
		osc.start(audioCtx.currentTime+0.05); | 
					 | 
				
			||||||
		osc.stop(audioCtx.currentTime+0.08); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Load screen content from a binary sequence (new) */ | 
					 | 
				
			||||||
	function load(str) { | 
					 | 
				
			||||||
		//console.log(JSON.stringify(str));
 | 
					 | 
				
			||||||
		var content = str.substr(1); | 
					 | 
				
			||||||
		switch(str.charAt(0)) { | 
					 | 
				
			||||||
			case 'S': | 
					 | 
				
			||||||
				_load_content(content); | 
					 | 
				
			||||||
				break; | 
					 | 
				
			||||||
			case 'T': | 
					 | 
				
			||||||
				_load_labels(content); | 
					 | 
				
			||||||
				break; | 
					 | 
				
			||||||
			case 'B': | 
					 | 
				
			||||||
				_beep(); | 
					 | 
				
			||||||
				break; | 
					 | 
				
			||||||
			default: | 
					 | 
				
			||||||
				console.warn("Bad data message type, ignoring."); | 
					 | 
				
			||||||
				console.log(str); | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return  { | 
					 | 
				
			||||||
		load: load, // full load (string)
 | 
					 | 
				
			||||||
	}; | 
					 | 
				
			||||||
})(); | 
					 | 
				
			||||||
@ -1,146 +0,0 @@ | 
				
			|||||||
/** File upload utility */ | 
					 | 
				
			||||||
var TermUpl = (function() { | 
					 | 
				
			||||||
	var lines, // array of lines without newlines
 | 
					 | 
				
			||||||
		line_i, // current line index
 | 
					 | 
				
			||||||
		fuTout, // timeout handle for line sending
 | 
					 | 
				
			||||||
		send_delay_ms, // delay between lines (ms)
 | 
					 | 
				
			||||||
		nl_str, // newline string to use
 | 
					 | 
				
			||||||
		curLine, // current line (when using fuOil)
 | 
					 | 
				
			||||||
		inline_pos; // Offset in line (for long lines)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// lines longer than this are split to chunks
 | 
					 | 
				
			||||||
	// sending a super-ling string through the socket is not a good idea
 | 
					 | 
				
			||||||
	var MAX_LINE_LEN = 128; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	function fuOpen() { | 
					 | 
				
			||||||
		fuStatus("Ready..."); | 
					 | 
				
			||||||
		Modal.show('#fu_modal', onClose); | 
					 | 
				
			||||||
		$('#fu_form').toggleClass('busy', false); | 
					 | 
				
			||||||
		Input.blockKeys(true); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	function onClose() { | 
					 | 
				
			||||||
		console.log("Upload modal closed."); | 
					 | 
				
			||||||
		clearTimeout(fuTout); | 
					 | 
				
			||||||
		line_i = 0; | 
					 | 
				
			||||||
		Input.blockKeys(false); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	function fuStatus(msg) { | 
					 | 
				
			||||||
		qs('#fu_prog').textContent = msg; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	function fuSend() { | 
					 | 
				
			||||||
		var v = qs('#fu_text').value; | 
					 | 
				
			||||||
		if (!v.length) { | 
					 | 
				
			||||||
			fuClose(); | 
					 | 
				
			||||||
			return; | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		lines = v.split('\n'); | 
					 | 
				
			||||||
		line_i = 0; | 
					 | 
				
			||||||
		inline_pos = 0; // offset in line
 | 
					 | 
				
			||||||
		send_delay_ms = qs('#fu_delay').value; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// sanitize - 0 causes overflows
 | 
					 | 
				
			||||||
		if (send_delay_ms < 0) { | 
					 | 
				
			||||||
			send_delay_ms = 0; | 
					 | 
				
			||||||
			qs('#fu_delay').value = send_delay_ms; | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		nl_str = { | 
					 | 
				
			||||||
			'CR': '\r', | 
					 | 
				
			||||||
			'LF': '\n', | 
					 | 
				
			||||||
			'CRLF': '\r\n', | 
					 | 
				
			||||||
		}[qs('#fu_crlf').value]; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		$('#fu_form').toggleClass('busy', true); | 
					 | 
				
			||||||
		fuStatus("Starting..."); | 
					 | 
				
			||||||
		fuSendLine(); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	function fuSendLine() { | 
					 | 
				
			||||||
		if (!$('#fu_modal').hasClass('visible')) { | 
					 | 
				
			||||||
			// Modal is closed, cancel
 | 
					 | 
				
			||||||
			return; | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (!Conn.canSend()) { | 
					 | 
				
			||||||
			// postpone
 | 
					 | 
				
			||||||
			fuTout = setTimeout(fuSendLine, 1); | 
					 | 
				
			||||||
			return; | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (inline_pos == 0) { | 
					 | 
				
			||||||
			curLine = lines[line_i++] + nl_str; | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var chunk; | 
					 | 
				
			||||||
		if ((curLine.length - inline_pos) <= MAX_LINE_LEN) { | 
					 | 
				
			||||||
			chunk = curLine.substr(inline_pos, MAX_LINE_LEN); | 
					 | 
				
			||||||
			inline_pos = 0; | 
					 | 
				
			||||||
		} else { | 
					 | 
				
			||||||
			chunk = curLine.substr(inline_pos, MAX_LINE_LEN); | 
					 | 
				
			||||||
			inline_pos += MAX_LINE_LEN; | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (!Input.sendString(chunk)) { | 
					 | 
				
			||||||
			fuStatus("FAILED!"); | 
					 | 
				
			||||||
			return; | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var all = lines.length; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		fuStatus(line_i+" / "+all+ " ("+(Math.round((line_i/all)*1000)/10)+"%)"); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (lines.length > line_i || inline_pos > 0) { | 
					 | 
				
			||||||
			fuTout = setTimeout(fuSendLine, send_delay_ms); | 
					 | 
				
			||||||
		} else { | 
					 | 
				
			||||||
			closeWhenReady(); | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	function closeWhenReady() { | 
					 | 
				
			||||||
		if (!Conn.canSend()) { | 
					 | 
				
			||||||
			// stuck in XOFF still, wait to process...
 | 
					 | 
				
			||||||
			fuStatus("Waiting for Tx buffer..."); | 
					 | 
				
			||||||
			setTimeout(closeWhenReady, 100); | 
					 | 
				
			||||||
		} else { | 
					 | 
				
			||||||
			fuStatus("Done."); | 
					 | 
				
			||||||
			// delay to show it
 | 
					 | 
				
			||||||
			setTimeout(function() { | 
					 | 
				
			||||||
				fuClose(); | 
					 | 
				
			||||||
			}, 100); | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	function fuClose() { | 
					 | 
				
			||||||
		Modal.hide('#fu_modal'); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return { | 
					 | 
				
			||||||
		init: function() { | 
					 | 
				
			||||||
			qs('#fu_file').addEventListener('change', function (evt) { | 
					 | 
				
			||||||
				var reader = new FileReader(); | 
					 | 
				
			||||||
				var file = evt.target.files[0]; | 
					 | 
				
			||||||
				console.log("Selected file type: "+file.type); | 
					 | 
				
			||||||
				if (!file.type.match(/text\/.*|application\/(json|csv|.*xml.*|.*script.*)/)) { | 
					 | 
				
			||||||
					// Deny load of blobs like img - can crash browser and will get corrupted anyway
 | 
					 | 
				
			||||||
					if (!confirm("This does not look like a text file: "+file.type+"\nReally load?")) { | 
					 | 
				
			||||||
						qs('#fu_file').value = ''; | 
					 | 
				
			||||||
						return; | 
					 | 
				
			||||||
					} | 
					 | 
				
			||||||
				} | 
					 | 
				
			||||||
				reader.onload = function(e) { | 
					 | 
				
			||||||
					var txt = e.target.result.replace(/[\r\n]+/,'\n'); | 
					 | 
				
			||||||
					qs('#fu_text').value = txt; | 
					 | 
				
			||||||
				}; | 
					 | 
				
			||||||
				console.log("Loading file..."); | 
					 | 
				
			||||||
				reader.readAsText(file); | 
					 | 
				
			||||||
			}, false); | 
					 | 
				
			||||||
		}, | 
					 | 
				
			||||||
		close: fuClose, | 
					 | 
				
			||||||
		start: fuSend, | 
					 | 
				
			||||||
		open: fuOpen, | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
})(); | 
					 | 
				
			||||||
@ -1,161 +0,0 @@ | 
				
			|||||||
/** Make a node */ | 
					 | 
				
			||||||
function mk(e) {return document.createElement(e)} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** Find one by query */ | 
					 | 
				
			||||||
function qs(s) {return document.querySelector(s)} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** Find all by query */ | 
					 | 
				
			||||||
function qsa(s) {return document.querySelectorAll(s)} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** Convert any to bool safely */ | 
					 | 
				
			||||||
function bool(x) { | 
					 | 
				
			||||||
	return (x === 1 || x === '1' || x === true || x === 'true'); | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** | 
					 | 
				
			||||||
 * Filter 'spacebar' and 'return' from keypress handler, | 
					 | 
				
			||||||
 * and when they're pressed, fire the callback. | 
					 | 
				
			||||||
 * use $(...).on('keypress', cr(handler)) | 
					 | 
				
			||||||
 */ | 
					 | 
				
			||||||
function cr(hdl) { | 
					 | 
				
			||||||
	return function(e) { | 
					 | 
				
			||||||
		if (e.which == 10 || e.which == 13 || e.which == 32) { | 
					 | 
				
			||||||
			hdl(); | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
	}; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** Extend an objects with options */ | 
					 | 
				
			||||||
function extend(defaults, options) { | 
					 | 
				
			||||||
	var target = {}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	Object.keys(defaults).forEach(function(k){ | 
					 | 
				
			||||||
		target[k] = defaults[k]; | 
					 | 
				
			||||||
	}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	Object.keys(options).forEach(function(k){ | 
					 | 
				
			||||||
		target[k] = options[k]; | 
					 | 
				
			||||||
	}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return target; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** Escape string for use as literal in RegExp */ | 
					 | 
				
			||||||
function rgxe(str) { | 
					 | 
				
			||||||
	return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** Format number to N decimal places, output as string */ | 
					 | 
				
			||||||
function numfmt(x, places) { | 
					 | 
				
			||||||
	var pow = Math.pow(10, places); | 
					 | 
				
			||||||
	return Math.round(x*pow) / pow; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** Get millisecond timestamp */ | 
					 | 
				
			||||||
function msNow() { | 
					 | 
				
			||||||
	return +(new Date); | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** Get ms elapsed since msNow() */ | 
					 | 
				
			||||||
function msElapsed(start) { | 
					 | 
				
			||||||
	return msNow() - start; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** Shim for log base 10 */ | 
					 | 
				
			||||||
Math.log10 = Math.log10 || function(x) { | 
					 | 
				
			||||||
	return Math.log(x) / Math.LN10; | 
					 | 
				
			||||||
}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** | 
					 | 
				
			||||||
 * Perform a substitution in the given string. | 
					 | 
				
			||||||
 * | 
					 | 
				
			||||||
 * Arguments - array or list of replacements. | 
					 | 
				
			||||||
 * Arguments numeric keys will replace {0}, {1} etc. | 
					 | 
				
			||||||
 * Named keys also work, ie. {foo: "bar"} -> replaces {foo} with bar. | 
					 | 
				
			||||||
 * | 
					 | 
				
			||||||
 * Braces are added to keys if missing. | 
					 | 
				
			||||||
 * | 
					 | 
				
			||||||
 * @returns {String} result | 
					 | 
				
			||||||
 */ | 
					 | 
				
			||||||
String.prototype.format = function () { | 
					 | 
				
			||||||
	var out = this; | 
					 | 
				
			||||||
	var repl = arguments; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (arguments.length == 1 && (Array.isArray(arguments[0]) || typeof arguments[0] == 'object')) { | 
					 | 
				
			||||||
		repl = arguments[0]; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for (var ph in repl) { | 
					 | 
				
			||||||
		if (repl.hasOwnProperty(ph)) { | 
					 | 
				
			||||||
			var ph_orig = ph; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (!ph.match(/^\{.*\}$/)) { | 
					 | 
				
			||||||
				ph = '{' + ph + '}'; | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// replace all occurrences
 | 
					 | 
				
			||||||
			var pattern = new RegExp(rgxe(ph), "g"); | 
					 | 
				
			||||||
			out = out.replace(pattern, repl[ph_orig]); | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return out; | 
					 | 
				
			||||||
}; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** HTML escape */ | 
					 | 
				
			||||||
function e(str) { | 
					 | 
				
			||||||
	return $.htmlEscape(str); | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** Check for undefined */ | 
					 | 
				
			||||||
function undef(x) { | 
					 | 
				
			||||||
	return typeof x == 'undefined'; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** Safe json parse */ | 
					 | 
				
			||||||
function jsp(str) { | 
					 | 
				
			||||||
	try { | 
					 | 
				
			||||||
		return JSON.parse(str); | 
					 | 
				
			||||||
	} catch(e) { | 
					 | 
				
			||||||
		console.error(e); | 
					 | 
				
			||||||
		return null; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** Create a character from ASCII code */ | 
					 | 
				
			||||||
function Chr(n) { | 
					 | 
				
			||||||
	return String.fromCharCode(n); | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** Decode number from 2B encoding */ | 
					 | 
				
			||||||
function parse2B(s, i) { | 
					 | 
				
			||||||
	return (s.charCodeAt(i++) - 1) + (s.charCodeAt(i) - 1) * 127; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** Decode number from 3B encoding */ | 
					 | 
				
			||||||
function parse3B(s, i) { | 
					 | 
				
			||||||
	return (s.charCodeAt(i) - 1) + (s.charCodeAt(i+1) - 1) * 127 + (s.charCodeAt(i+2) - 1) * 127 * 127; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** Encode using 2B encoding, returns string. */ | 
					 | 
				
			||||||
function encode2B(n) { | 
					 | 
				
			||||||
	var lsb, msb; | 
					 | 
				
			||||||
	lsb = (n % 127); | 
					 | 
				
			||||||
	n = ((n - lsb) / 127); | 
					 | 
				
			||||||
	lsb += 1; | 
					 | 
				
			||||||
	msb = (n + 1); | 
					 | 
				
			||||||
	return Chr(lsb) + Chr(msb); | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** Encode using 3B encoding, returns string. */ | 
					 | 
				
			||||||
function encode3B(n) { | 
					 | 
				
			||||||
	var lsb, msb, xsb; | 
					 | 
				
			||||||
	lsb = (n % 127); | 
					 | 
				
			||||||
	n = (n - lsb) / 127; | 
					 | 
				
			||||||
	lsb += 1; | 
					 | 
				
			||||||
	msb = (n % 127); | 
					 | 
				
			||||||
	n = (n - msb) / 127; | 
					 | 
				
			||||||
	msb += 1; | 
					 | 
				
			||||||
	xsb = (n + 1); | 
					 | 
				
			||||||
	return Chr(lsb) + Chr(msb) + Chr(xsb); | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
@ -1,163 +0,0 @@ | 
				
			|||||||
(function(w) { | 
					 | 
				
			||||||
	var authStr = ['Open', 'WEP', 'WPA', 'WPA2', 'WPA/WPA2']; | 
					 | 
				
			||||||
	var curSSID; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Get XX % for a slider input
 | 
					 | 
				
			||||||
	function rangePt(inp) { | 
					 | 
				
			||||||
		return Math.round(((inp.value / inp.max)*100)) + '%'; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Display selected STA SSID etc
 | 
					 | 
				
			||||||
	function selectSta(name, password, ip) { | 
					 | 
				
			||||||
		$('#sta_ssid').val(name); | 
					 | 
				
			||||||
		$('#sta_password').val(password); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		$('#sta-nw').toggleClass('hidden', name.length == 0); | 
					 | 
				
			||||||
		$('#sta-nw-nil').toggleClass('hidden', name.length > 0); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		$('#sta-nw .essid').html(e(name)); | 
					 | 
				
			||||||
		var nopw = undef(password) || password.length == 0; | 
					 | 
				
			||||||
		$('#sta-nw .passwd').toggleClass('hidden', nopw); | 
					 | 
				
			||||||
		$('#sta-nw .nopasswd').toggleClass('hidden', !nopw); | 
					 | 
				
			||||||
		$('#sta-nw .ip').html(ip.length>0 ? tr('wifi.connected_ip_is')+ip : tr('wifi.not_conn')); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Update display for received response */ | 
					 | 
				
			||||||
	function onScan(resp, status) { | 
					 | 
				
			||||||
		//var ap_json = {
 | 
					 | 
				
			||||||
		//	"result": {
 | 
					 | 
				
			||||||
		//		"inProgress": "0",
 | 
					 | 
				
			||||||
		//		"APs": [
 | 
					 | 
				
			||||||
		//			{"essid": "Chlivek", "bssid": "88:f7:c7:52:b3:99", "rssi": "204", "enc": "4", "channel": "1"},
 | 
					 | 
				
			||||||
		//			{"essid": "TyNikdy", "bssid": "5c:f4:ab:0d:f1:1b", "rssi": "164", "enc": "3", "channel": "1"},
 | 
					 | 
				
			||||||
		//		]
 | 
					 | 
				
			||||||
		//	}
 | 
					 | 
				
			||||||
		//};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (status != 200) { | 
					 | 
				
			||||||
			// bad response
 | 
					 | 
				
			||||||
			rescan(5000); // wait 5sm then retry
 | 
					 | 
				
			||||||
			return; | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		try { | 
					 | 
				
			||||||
			resp = JSON.parse(resp); | 
					 | 
				
			||||||
		} catch (e) { | 
					 | 
				
			||||||
			console.log(e); | 
					 | 
				
			||||||
			rescan(5000); | 
					 | 
				
			||||||
			return; | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var done = !bool(resp.result.inProgress) && (resp.result.APs.length > 0); | 
					 | 
				
			||||||
		rescan(done ? 15000 : 1000); | 
					 | 
				
			||||||
		if (!done) return; // no redraw yet
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// clear the AP list
 | 
					 | 
				
			||||||
		var $list = $('#ap-list'); | 
					 | 
				
			||||||
		// remove old APs
 | 
					 | 
				
			||||||
		$('#ap-list .AP').remove(); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		$list.toggleClass('hidden', !done); | 
					 | 
				
			||||||
		$('#ap-loader').toggleClass('hidden', done); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// scan done
 | 
					 | 
				
			||||||
		resp.result.APs.sort(function (a, b) { | 
					 | 
				
			||||||
			return b.rssi - a.rssi; | 
					 | 
				
			||||||
		}).forEach(function (ap) { | 
					 | 
				
			||||||
			ap.enc = parseInt(ap.enc); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (ap.enc > 4) return; // hide unsupported auths
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			var item = mk('div'); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			var $item = $(item) | 
					 | 
				
			||||||
				.data('ssid', ap.essid) | 
					 | 
				
			||||||
				.data('pwd', ap.enc) | 
					 | 
				
			||||||
				.attr('tabindex', 0) | 
					 | 
				
			||||||
				.addClass('AP'); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			// mark current SSID
 | 
					 | 
				
			||||||
			if (ap.essid == curSSID) { | 
					 | 
				
			||||||
				$item.addClass('selected'); | 
					 | 
				
			||||||
			} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			var inner = mk('div'); | 
					 | 
				
			||||||
			$(inner).addClass('inner') | 
					 | 
				
			||||||
				.htmlAppend('<div class="rssi">{0}</div>'.format(ap.rssi_perc)) | 
					 | 
				
			||||||
				.htmlAppend('<div class="essid" title="{0}">{0}</div>'.format($.htmlEscape(ap.essid))) | 
					 | 
				
			||||||
				.htmlAppend('<div class="auth">{0}</div>'.format(authStr[ap.enc])); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			$item.on('click', function () { | 
					 | 
				
			||||||
				var $th = $(this); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				var conn_ssid = $th.data('ssid'); | 
					 | 
				
			||||||
				var conn_pass = ''; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				if (+$th.data('pwd')) { | 
					 | 
				
			||||||
					// this AP needs a password
 | 
					 | 
				
			||||||
					conn_pass = prompt(tr("wifi.enter_passwd").replace(":ssid:", conn_ssid)); | 
					 | 
				
			||||||
					if (!conn_pass) return; | 
					 | 
				
			||||||
				} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				$('#sta_password').val(conn_pass); | 
					 | 
				
			||||||
				$('#sta_ssid').val(conn_ssid); | 
					 | 
				
			||||||
				selectSta(conn_ssid, conn_pass, ''); | 
					 | 
				
			||||||
			}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			item.appendChild(inner); | 
					 | 
				
			||||||
			$list[0].appendChild(item); | 
					 | 
				
			||||||
		}); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	function startScanning() { | 
					 | 
				
			||||||
		$('#ap-loader').removeClass('hidden'); | 
					 | 
				
			||||||
		$('#ap-scan').addClass('hidden'); | 
					 | 
				
			||||||
		$('#ap-loader .anim-dots').html('.'); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		scanAPs(); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Ask the CGI what APs are visible (async) */ | 
					 | 
				
			||||||
	function scanAPs() { | 
					 | 
				
			||||||
		if (_demo) { | 
					 | 
				
			||||||
			onScan(_demo_aps, 200); | 
					 | 
				
			||||||
		} else { | 
					 | 
				
			||||||
			$.get('http://' + _root + '/cfg/wifi/scan', onScan); | 
					 | 
				
			||||||
		} | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	function rescan(time) { | 
					 | 
				
			||||||
		setTimeout(scanAPs, time); | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/** Set up the WiFi page */ | 
					 | 
				
			||||||
	function wifiInit(cfg) { | 
					 | 
				
			||||||
		// Update slider value displays
 | 
					 | 
				
			||||||
		$('.Row.range').forEach(function(x) { | 
					 | 
				
			||||||
			var inp = x.querySelector('input'); | 
					 | 
				
			||||||
			var disp1 = x.querySelector('.x-disp1'); | 
					 | 
				
			||||||
			var disp2 = x.querySelector('.x-disp2'); | 
					 | 
				
			||||||
			var t = rangePt(inp); | 
					 | 
				
			||||||
			$(disp1).html(t); | 
					 | 
				
			||||||
			$(disp2).html(t); | 
					 | 
				
			||||||
			$(inp).on('input', function() { | 
					 | 
				
			||||||
				t = rangePt(inp); | 
					 | 
				
			||||||
				$(disp1).html(t); | 
					 | 
				
			||||||
				$(disp2).html(t); | 
					 | 
				
			||||||
			}); | 
					 | 
				
			||||||
		}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Forget STA credentials
 | 
					 | 
				
			||||||
		$('#forget-sta').on('click', function() { | 
					 | 
				
			||||||
			selectSta('', '', ''); | 
					 | 
				
			||||||
			return false; | 
					 | 
				
			||||||
		}); | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		selectSta(cfg.sta_ssid, cfg.sta_password, cfg.sta_active_ip); | 
					 | 
				
			||||||
		curSSID = cfg.sta_active_ssid; | 
					 | 
				
			||||||
	} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	w.init = wifiInit; | 
					 | 
				
			||||||
	w.startScanning = startScanning; | 
					 | 
				
			||||||
})(window.WiFi = {}); | 
					 | 
				
			||||||
@ -0,0 +1,18 @@ | 
				
			|||||||
 | 
					{ | 
				
			||||||
 | 
					  "name": "espterm-front-end", | 
				
			||||||
 | 
					  "version": "1.0.0", | 
				
			||||||
 | 
					  "description": "ESPTerm web interface", | 
				
			||||||
 | 
					  "license": "MPL-2.0", | 
				
			||||||
 | 
					  "devDependencies": { | 
				
			||||||
 | 
					    "babel-cli": "^6.26.0", | 
				
			||||||
 | 
					    "babel-minify": "^0.2.0", | 
				
			||||||
 | 
					    "babel-preset-env": "^1.6.0", | 
				
			||||||
 | 
					    "node-sass": "^4.5.3", | 
				
			||||||
 | 
					    "standard": "^10.0.3" | 
				
			||||||
 | 
					  }, | 
				
			||||||
 | 
					  "scripts": { | 
				
			||||||
 | 
					    "babel": "babel $@", | 
				
			||||||
 | 
					    "minify": "babel-minify $@", | 
				
			||||||
 | 
					    "sass": "node-sass $@" | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
									
										
											File diff suppressed because one or more lines are too long
										
									
								
							
						@ -0,0 +1,115 @@ | 
				
			|||||||
 | 
					@media print { | 
				
			||||||
 | 
						.Row.buttons, nav { | 
				
			||||||
 | 
							display: none !important; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						.Row.buttons .button { | 
				
			||||||
 | 
							display: none !important; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						h1, h2, h3 { | 
				
			||||||
 | 
							// chrome ignores those :( | 
				
			||||||
 | 
							break-after: avoid-page!important; | 
				
			||||||
 | 
							page-break-after: avoid!important; | 
				
			||||||
 | 
							font-family: sans-serif; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						html, body { | 
				
			||||||
 | 
							background: white; | 
				
			||||||
 | 
							color: black; | 
				
			||||||
 | 
							font-family: serif; | 
				
			||||||
 | 
							//font-size: 12pt; | 
				
			||||||
 | 
							line-height: 1.3em; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						label, p { | 
				
			||||||
 | 
							color: black !important; | 
				
			||||||
 | 
							text-shadow: none !important; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						.Box { | 
				
			||||||
 | 
							box-shadow: none; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						input, select, button { | 
				
			||||||
 | 
							background: white !important; | 
				
			||||||
 | 
							color: black !important; | 
				
			||||||
 | 
							border: 1px solid black !important; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a { | 
				
			||||||
 | 
							color: #004eff !important; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						a[href^="http://"]::after, | 
				
			||||||
 | 
						a[href^="https://"]::after { | 
				
			||||||
 | 
							content: attr(href); | 
				
			||||||
 | 
							padding-left: .8ex; | 
				
			||||||
 | 
							text-decoration: underline !important; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
						p a { | 
				
			||||||
 | 
							word-wrap: break-word; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						.Row.checkbox .box { | 
				
			||||||
 | 
							border-color: black; | 
				
			||||||
 | 
							border-radius: 3px; | 
				
			||||||
 | 
							background: white; | 
				
			||||||
 | 
							color: black; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						.button { | 
				
			||||||
 | 
							background: white; border: 1px solid black; | 
				
			||||||
 | 
							text-shadow: none !important; | 
				
			||||||
 | 
							color: black; | 
				
			||||||
 | 
							box-shadow: none; | 
				
			||||||
 | 
							text-decoration: underline !important; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[class^="icn-"], [class*=" icn-"] { | 
				
			||||||
 | 
							&::before { | 
				
			||||||
 | 
								display: none; | 
				
			||||||
 | 
							} | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						.Box .Row { | 
				
			||||||
 | 
							display: block !important; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						.Box.fold h2::after { | 
				
			||||||
 | 
							display: none; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						#outer { | 
				
			||||||
 | 
							display: block; | 
				
			||||||
 | 
							overflow: auto; | 
				
			||||||
 | 
							width: unset; | 
				
			||||||
 | 
							height: unset; | 
				
			||||||
 | 
							position: static; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						html, body { | 
				
			||||||
 | 
							overflow: auto !important; | 
				
			||||||
 | 
							width: unset; height: unset; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						.Box { | 
				
			||||||
 | 
							padding: 0; border: 0 none; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						.charset div span:nth-child(1), | 
				
			||||||
 | 
						.charset div span:nth-child(2) { | 
				
			||||||
 | 
							color: #666; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						.page-help code { | 
				
			||||||
 | 
							background: rgba(0, 215, 255, 0.31); | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[noprint] { | 
				
			||||||
 | 
							display: none !important; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						#content tbody th { | 
				
			||||||
 | 
							color: black !important; | 
				
			||||||
 | 
						} | 
				
			||||||
 | 
					} | 
				
			||||||
@ -1,3 +1,3 @@ | 
				
			|||||||
#!/bin/bash | 
					#!/bin/bash | 
				
			||||||
 | 
					
 | 
				
			||||||
xterm -e "php -S 0.0.0.0:2000" | 
					xterm -e "php -S 0.0.0.0:2000  _dev_router.php" | 
				
			||||||
 | 
				
			|||||||
					Loading…
					
					
				
		Reference in new issue