commit
						4a032ee3b5
					
				@ -1,3 +1,7 @@ | 
				
			|||||||
#!/bin/bash | 
					#!/bin/bash | 
				
			||||||
 | 
					
 | 
				
			||||||
export FRONT_END_HASH=$(git rev-parse --short HEAD) | 
					export FRONT_END_HASH=$(git rev-parse --short HEAD) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if [ -z "$ESP_LANG" ]; then | 
				
			||||||
 | 
						export ESP_LANG=en | 
				
			||||||
 | 
					fi | 
				
			||||||
 | 
				
			|||||||
@ -1,22 +0,0 @@ | 
				
			|||||||
<?php | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* This script is run on demand to generate JS version of tr() */ | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
require_once __DIR__ . '/base.php'; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
$selected = [ | 
					 | 
				
			||||||
	'wifi.connected_ip_is', | 
					 | 
				
			||||||
	'wifi.not_conn', | 
					 | 
				
			||||||
	'wifi.enter_passwd', | 
					 | 
				
			||||||
]; | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
$out = []; | 
					 | 
				
			||||||
foreach ($selected as $key) { | 
					 | 
				
			||||||
	$out[$key] = $_messages[$key]; | 
					 | 
				
			||||||
} | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
file_put_contents(__DIR__. '/js/lang.js', | 
					 | 
				
			||||||
	"// Generated from PHP locale file\n" . | 
					 | 
				
			||||||
	'let _tr = ' . json_encode($out, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE) . ";\n\n" . | 
					 | 
				
			||||||
	"module.exports = function tr (key) { return _tr[key] || '?' + key + '?' }\n" | 
					 | 
				
			||||||
); | 
					 | 
				
			||||||
@ -1,8 +1,5 @@ | 
				
			|||||||
// Generated from PHP locale file
 | 
					let data = require('locale-data') | 
				
			||||||
let _tr = { | 
					 | 
				
			||||||
    "wifi.connected_ip_is": "Connected, IP is ", | 
					 | 
				
			||||||
    "wifi.not_conn": "Not connected.", | 
					 | 
				
			||||||
    "wifi.enter_passwd": "Enter password for \":ssid:\"" | 
					 | 
				
			||||||
}; | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = function tr (key) { return _tr[key] || '?' + key + '?' } | 
					module.exports = function localize (key) { | 
				
			||||||
 | 
					  return data[key] || `?${key}?` | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
				
			|||||||
@ -0,0 +1,118 @@ | 
				
			|||||||
 | 
					/* | 
				
			||||||
 | 
					 * Copyright (c) 2010 Tim Baumann | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * Permission is hereby granted, free of charge, to any person obtaining a copy | 
				
			||||||
 | 
					 * of this software and associated documentation files (the "Software"), to deal | 
				
			||||||
 | 
					 * in the Software without restriction, including without limitation the rights | 
				
			||||||
 | 
					 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | 
				
			||||||
 | 
					 * copies of the Software, and to permit persons to whom the Software is | 
				
			||||||
 | 
					 * furnished to do so, subject to the following conditions: | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * The above copyright notice and this permission notice shall be included in | 
				
			||||||
 | 
					 * all copies or substantial portions of the Software. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 
				
			||||||
 | 
					 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | 
				
			||||||
 | 
					 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | 
				
			||||||
 | 
					 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 
				
			||||||
 | 
					 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 
				
			||||||
 | 
					 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | 
				
			||||||
 | 
					 * THE SOFTWARE. | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NOTE:
 | 
				
			||||||
 | 
					// Extracted from ColorTriangle and
 | 
				
			||||||
 | 
					// Converted to ES6 by MightyPork (2017)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/******************* | 
				
			||||||
 | 
					 * Color conversion * | 
				
			||||||
 | 
					 *******************/ | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const M = Math | 
				
			||||||
 | 
					const TAU = 2 * M.PI | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports.hue2rgb = function (v1, v2, h) { | 
				
			||||||
 | 
					  if (h < 0) h += 1 | 
				
			||||||
 | 
					  if (h > 1) h -= 1 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if ((6 * h) < 1) return v1 + (v2 - v1) * 6 * h | 
				
			||||||
 | 
					  if ((2 * h) < 1) return v2 | 
				
			||||||
 | 
					  if ((3 * h) < 2) return v1 + (v2 - v1) * ((2 / 3) - h) * 6 | 
				
			||||||
 | 
					  return v1 | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports.hsl2rgb = function (h, s, l) { | 
				
			||||||
 | 
					  h /= TAU | 
				
			||||||
 | 
					  let r, g, b | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (s === 0) { | 
				
			||||||
 | 
					    r = g = b = l | 
				
			||||||
 | 
					  } else { | 
				
			||||||
 | 
					    let var_1, var_2 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (l < 0.5) var_2 = l * (1 + s) | 
				
			||||||
 | 
					    else var_2 = (l + s) - (s * l) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    var_1 = 2 * l - var_2 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    r = exports.hue2rgb(var_1, var_2, h + (1 / 3)) | 
				
			||||||
 | 
					    g = exports.hue2rgb(var_1, var_2, h) | 
				
			||||||
 | 
					    b = exports.hue2rgb(var_1, var_2, h - (1 / 3)) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  return [r, g, b] | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports.rgb2hsl = function (r, g, b) { | 
				
			||||||
 | 
					  const min = M.min(r, g, b) | 
				
			||||||
 | 
					  const max = M.max(r, g, b) | 
				
			||||||
 | 
					  const d = max - min // delta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let h, s, l | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  l = (max + min) / 2 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (d === 0) { | 
				
			||||||
 | 
					    // gray
 | 
				
			||||||
 | 
					    h = s = 0 // HSL results from 0 to 1
 | 
				
			||||||
 | 
					  } else { | 
				
			||||||
 | 
					    // chroma
 | 
				
			||||||
 | 
					    if (l < 0.5) s = d / (max + min) | 
				
			||||||
 | 
					    else s = d / (2 - max - min) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const d_r = (((max - r) / 6) + (d / 2)) / d | 
				
			||||||
 | 
					    const d_g = (((max - g) / 6) + (d / 2)) / d | 
				
			||||||
 | 
					    const d_b = (((max - b) / 6) + (d / 2)) / d // deltas
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (r === max) h = d_b - d_g | 
				
			||||||
 | 
					    else if (g === max) h = (1 / 3) + d_r - d_b | 
				
			||||||
 | 
					    else if (b === max) h = (2 / 3) + d_g - d_r | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (h < 0) h += 1 | 
				
			||||||
 | 
					    else if (h > 1) h -= 1 | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  h *= TAU | 
				
			||||||
 | 
					  return [h, s, l] | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports.hex2rgb = function (hex) { | 
				
			||||||
 | 
					  const groups = hex.match(/^#([\da-f]{3,6})$/i) | 
				
			||||||
 | 
					  if (groups) { | 
				
			||||||
 | 
					    hex = groups[1] | 
				
			||||||
 | 
					    const bytes = hex.length / 3 | 
				
			||||||
 | 
					    const max = (16 ** bytes) - 1 | 
				
			||||||
 | 
					    return [0, 1, 2].map(x => parseInt(hex.slice(x * bytes, (x + 1) * bytes), 16) / max) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					  return [0, 0, 0] | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function pad (n) { | 
				
			||||||
 | 
					  return `00${n}`.substr(-2) | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports.rgb255ToHex = function (r, g, b) { | 
				
			||||||
 | 
					  return '#' + [r, g, b].map(x => pad(x.toString(16))).join('') | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports.rgb2hex = function (r, g, b) { | 
				
			||||||
 | 
					  return '#' + [r, g, b].map(x => pad(Math.round(x * 255).toString(16))).join('') | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,572 @@ | 
				
			|||||||
 | 
					/* | 
				
			||||||
 | 
					 * Copyright (c) 2010 Tim Baumann | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * Permission is hereby granted, free of charge, to any person obtaining a copy | 
				
			||||||
 | 
					 * of this software and associated documentation files (the "Software"), to deal | 
				
			||||||
 | 
					 * in the Software without restriction, including without limitation the rights | 
				
			||||||
 | 
					 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | 
				
			||||||
 | 
					 * copies of the Software, and to permit persons to whom the Software is | 
				
			||||||
 | 
					 * furnished to do so, subject to the following conditions: | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * The above copyright notice and this permission notice shall be included in | 
				
			||||||
 | 
					 * all copies or substantial portions of the Software. | 
				
			||||||
 | 
					 * | 
				
			||||||
 | 
					 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | 
				
			||||||
 | 
					 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | 
				
			||||||
 | 
					 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | 
				
			||||||
 | 
					 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | 
				
			||||||
 | 
					 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | 
				
			||||||
 | 
					 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | 
				
			||||||
 | 
					 * THE SOFTWARE. | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NOTE: Converted to ES6 by MightyPork (2017)
 | 
				
			||||||
 | 
					// Modified for ESPTerm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const EventEmitter = require('events') | 
				
			||||||
 | 
					const { | 
				
			||||||
 | 
					  rgb2hex, | 
				
			||||||
 | 
					  hex2rgb, | 
				
			||||||
 | 
					  hsl2rgb, | 
				
			||||||
 | 
					  rgb2hsl | 
				
			||||||
 | 
					} = require('./color_utils') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const win = window | 
				
			||||||
 | 
					const doc = document | 
				
			||||||
 | 
					const M = Math | 
				
			||||||
 | 
					const TAU = 2 * M.PI | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function times (i, fn) { | 
				
			||||||
 | 
					  for (let j = 0; j < i; j++) { | 
				
			||||||
 | 
					    fn(j) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function each (obj, fn) { | 
				
			||||||
 | 
					  if (obj.length) { | 
				
			||||||
 | 
					    times(obj.length, function (i) { | 
				
			||||||
 | 
					      fn(obj[i], i) | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					  } else { | 
				
			||||||
 | 
					    for (let key in obj) { | 
				
			||||||
 | 
					      if (obj.hasOwnProperty(key)) { | 
				
			||||||
 | 
					        fn(obj[key], key) | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = class ColorTriangle extends EventEmitter { | 
				
			||||||
 | 
					  /**************** | 
				
			||||||
 | 
					   * ColorTriangle * | 
				
			||||||
 | 
					   ****************/ | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Constructor function:
 | 
				
			||||||
 | 
					  constructor (color, options) { | 
				
			||||||
 | 
					    super() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.options = { | 
				
			||||||
 | 
					      size: 150, | 
				
			||||||
 | 
					      padding: 8, | 
				
			||||||
 | 
					      triangleSize: 0.8, | 
				
			||||||
 | 
					      wheelPointerColor1: '#444', | 
				
			||||||
 | 
					      wheelPointerColor2: '#eee', | 
				
			||||||
 | 
					      trianglePointerSize: 16, | 
				
			||||||
 | 
					      // wheelPointerSize: 16,
 | 
				
			||||||
 | 
					      trianglePointerColor1: '#eee', | 
				
			||||||
 | 
					      trianglePointerColor2: '#444', | 
				
			||||||
 | 
					      background: 'transparent' | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.pixelRatio = window.devicePixelRatio | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.setOptions(options) | 
				
			||||||
 | 
					    this.calculateProperties() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.createContainer() | 
				
			||||||
 | 
					    this.createTriangle() | 
				
			||||||
 | 
					    this.createWheel() | 
				
			||||||
 | 
					    this.createWheelPointer() | 
				
			||||||
 | 
					    this.createTrianglePointer() | 
				
			||||||
 | 
					    this.attachEvents() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    color = color || '#f00' | 
				
			||||||
 | 
					    if (typeof color == 'string') { | 
				
			||||||
 | 
					      this.setHEX(color) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  calculateProperties () { | 
				
			||||||
 | 
					    let opts = this.options | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.padding = opts.padding | 
				
			||||||
 | 
					    this.innerSize = opts.size - opts.padding * 2 | 
				
			||||||
 | 
					    this.triangleSize = opts.triangleSize * this.innerSize | 
				
			||||||
 | 
					    this.wheelThickness = (this.innerSize - this.triangleSize) / 2 | 
				
			||||||
 | 
					    this.wheelPointerSize = opts.wheelPointerSize || this.wheelThickness | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.wheelRadius = (this.innerSize) / 2 | 
				
			||||||
 | 
					    this.triangleRadius = (this.triangleSize) / 2 | 
				
			||||||
 | 
					    this.triangleSideLength = M.sqrt(3) * this.triangleRadius | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  calculatePositions () { | 
				
			||||||
 | 
					    const r = this.triangleRadius | 
				
			||||||
 | 
					    const hue = this.hue | 
				
			||||||
 | 
					    const third = TAU / 3 | 
				
			||||||
 | 
					    const s = this.saturation | 
				
			||||||
 | 
					    const l = this.lightness | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Colored point
 | 
				
			||||||
 | 
					    const hx = this.hx = M.cos(hue) * r | 
				
			||||||
 | 
					    const hy = this.hy = -M.sin(hue) * r | 
				
			||||||
 | 
					    // Black point
 | 
				
			||||||
 | 
					    const sx = this.sx = M.cos(hue - third) * r | 
				
			||||||
 | 
					    const sy = this.sy = -M.sin(hue - third) * r | 
				
			||||||
 | 
					    // White point
 | 
				
			||||||
 | 
					    const vx = this.vx = M.cos(hue + third) * r | 
				
			||||||
 | 
					    const vy = this.vy = -M.sin(hue + third) * r | 
				
			||||||
 | 
					    // Current point
 | 
				
			||||||
 | 
					    const mx = (sx + vx) / 2 | 
				
			||||||
 | 
					    const my = (sy + vy) / 2 | 
				
			||||||
 | 
					    const a = (1 - 2 * M.abs(l - 0.5)) * s | 
				
			||||||
 | 
					    this.x = sx + (vx - sx) * l + (hx - mx) * a | 
				
			||||||
 | 
					    this.y = sy + (vy - sy) * l + (hy - my) * a | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  createContainer () { | 
				
			||||||
 | 
					    let c = this.container = doc.createElement('div') | 
				
			||||||
 | 
					    c.className = 'color-triangle' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    c.style.display = 'block' | 
				
			||||||
 | 
					    c.style.padding = `${this.padding}px` | 
				
			||||||
 | 
					    c.style.position = 'relative' | 
				
			||||||
 | 
					    c.style.boxShadow = '0 1px 10px black' | 
				
			||||||
 | 
					    c.style.borderRadius = '5px' | 
				
			||||||
 | 
					    c.style.width = c.style.height = `${this.innerSize + 2 * this.padding}px` | 
				
			||||||
 | 
					    c.style.background = this.options.background | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  createWheel () { | 
				
			||||||
 | 
					    let c = this.wheel = doc.createElement('canvas') | 
				
			||||||
 | 
					    c.width = c.height = this.innerSize * this.pixelRatio | 
				
			||||||
 | 
					    c.style.width = c.style.height = `${this.innerSize}px` | 
				
			||||||
 | 
					    c.style.position = 'absolute' | 
				
			||||||
 | 
					    c.style.margin = c.style.padding = '0' | 
				
			||||||
 | 
					    c.style.left = c.style.top = `${this.padding}px` | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.drawWheel(c.getContext('2d')) | 
				
			||||||
 | 
					    this.container.appendChild(c) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  drawWheel (ctx) { | 
				
			||||||
 | 
					    let s, i | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ctx.save() | 
				
			||||||
 | 
					    ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0) | 
				
			||||||
 | 
					    ctx.translate(this.wheelRadius, this.wheelRadius) | 
				
			||||||
 | 
					    s = this.wheelRadius - this.triangleRadius | 
				
			||||||
 | 
					    // Draw a circle for every color
 | 
				
			||||||
 | 
					    for (i = 0; i < 360; i++) { | 
				
			||||||
 | 
					      ctx.rotate(TAU / -360) // rotate one degree
 | 
				
			||||||
 | 
					      ctx.beginPath() | 
				
			||||||
 | 
					      ctx.fillStyle = 'hsl(' + i + ', 100%, 50%)' | 
				
			||||||
 | 
					      ctx.arc(this.wheelRadius - (s / 2), 0, s / 2, 0, TAU, true) | 
				
			||||||
 | 
					      ctx.fill() | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					    ctx.restore() | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  createTriangle () { | 
				
			||||||
 | 
					    let c = this.triangle = doc.createElement('canvas') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    c.width = c.height = this.innerSize * this.pixelRatio | 
				
			||||||
 | 
					    c.style.width = c.style.height = `${this.innerSize}px` | 
				
			||||||
 | 
					    c.style.position = 'absolute' | 
				
			||||||
 | 
					    c.style.margin = c.style.padding = '0' | 
				
			||||||
 | 
					    c.style.left = c.style.top = this.padding + 'px' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.triangleCtx = c.getContext('2d') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.container.appendChild(c) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  drawTriangle () { | 
				
			||||||
 | 
					    const hx = this.hx | 
				
			||||||
 | 
					    const hy = this.hy | 
				
			||||||
 | 
					    const sx = this.sx | 
				
			||||||
 | 
					    const sy = this.sy | 
				
			||||||
 | 
					    const vx = this.vx | 
				
			||||||
 | 
					    const vy = this.vy | 
				
			||||||
 | 
					    const size = this.innerSize | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let ctx = this.triangleCtx | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // clear
 | 
				
			||||||
 | 
					    ctx.clearRect(0, 0, size * this.pixelRatio, size * this.pixelRatio) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ctx.save() | 
				
			||||||
 | 
					    ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0) | 
				
			||||||
 | 
					    ctx.translate(this.wheelRadius, this.wheelRadius) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // make a triangle
 | 
				
			||||||
 | 
					    ctx.beginPath() | 
				
			||||||
 | 
					    ctx.moveTo(hx, hy) | 
				
			||||||
 | 
					    ctx.lineTo(sx, sy) | 
				
			||||||
 | 
					    ctx.lineTo(vx, vy) | 
				
			||||||
 | 
					    ctx.closePath() | 
				
			||||||
 | 
					    ctx.clip() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ctx.fillStyle = '#000' | 
				
			||||||
 | 
					    ctx.fillRect(-this.wheelRadius, -this.wheelRadius, size, size) | 
				
			||||||
 | 
					    // => black triangle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // create gradient from hsl(hue, 1, 1) to transparent
 | 
				
			||||||
 | 
					    let grad0 = ctx.createLinearGradient(hx, hy, (sx + vx) / 2, (sy + vy) / 2) | 
				
			||||||
 | 
					    const hsla = 'hsla(' + M.round(this.hue * (360 / TAU)) + ', 100%, 50%, ' | 
				
			||||||
 | 
					    grad0.addColorStop(0, hsla + '1)') | 
				
			||||||
 | 
					    grad0.addColorStop(1, hsla + '0)') | 
				
			||||||
 | 
					    ctx.fillStyle = grad0 | 
				
			||||||
 | 
					    ctx.fillRect(-this.wheelRadius, -this.wheelRadius, size, size) | 
				
			||||||
 | 
					    // => gradient: one side of the triangle is black, the opponent angle is $color
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // create color gradient from white to transparent
 | 
				
			||||||
 | 
					    let grad1 = ctx.createLinearGradient(vx, vy, (hx + sx) / 2, (hy + sy) / 2) | 
				
			||||||
 | 
					    grad1.addColorStop(0, '#fff') | 
				
			||||||
 | 
					    grad1.addColorStop(1, 'rgba(255, 255, 255, 0)') | 
				
			||||||
 | 
					    ctx.globalCompositeOperation = 'lighter' | 
				
			||||||
 | 
					    ctx.fillStyle = grad1 | 
				
			||||||
 | 
					    ctx.fillRect(-this.wheelRadius, -this.wheelRadius, size, size) | 
				
			||||||
 | 
					    // => white angle
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ctx.restore() | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // The two pointers
 | 
				
			||||||
 | 
					  createWheelPointer () { | 
				
			||||||
 | 
					    let c = this.wheelPointer = doc.createElement('canvas') | 
				
			||||||
 | 
					    const size = this.wheelPointerSize | 
				
			||||||
 | 
					    c.width = c.height = size * this.pixelRatio | 
				
			||||||
 | 
					    c.style.width = c.style.height = `${size}px` | 
				
			||||||
 | 
					    c.style.position = 'absolute' | 
				
			||||||
 | 
					    c.style.margin = c.style.padding = '0' | 
				
			||||||
 | 
					    this.drawPointer(c.getContext('2d'), size / 2, this.options.wheelPointerColor1, this.options.wheelPointerColor2) | 
				
			||||||
 | 
					    this.container.appendChild(c) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  moveWheelPointer () { | 
				
			||||||
 | 
					    const r = this.wheelPointerSize / 2 | 
				
			||||||
 | 
					    const s = this.wheelPointer.style | 
				
			||||||
 | 
					    s.top = this.padding + this.wheelRadius - M.sin(this.hue) * (this.triangleRadius + this.wheelThickness / 2) - r + 'px' | 
				
			||||||
 | 
					    s.left = this.padding + this.wheelRadius + M.cos(this.hue) * (this.triangleRadius + this.wheelThickness / 2) - r + 'px' | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  createTrianglePointer () { // create pointer in the triangle
 | 
				
			||||||
 | 
					    let c = this.trianglePointer = doc.createElement('canvas') | 
				
			||||||
 | 
					    const size = this.options.trianglePointerSize | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    c.width = c.height = size * this.pixelRatio | 
				
			||||||
 | 
					    c.style.width = c.style.height = `${size}px` | 
				
			||||||
 | 
					    c.style.position = 'absolute' | 
				
			||||||
 | 
					    c.style.margin = c.style.padding = '0' | 
				
			||||||
 | 
					    this.drawPointer(c.getContext('2d'), size / 2, this.options.trianglePointerColor1, this.options.trianglePointerColor2) | 
				
			||||||
 | 
					    this.container.appendChild(c) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  moveTrianglePointer (x, y) { | 
				
			||||||
 | 
					    const s = this.trianglePointer.style | 
				
			||||||
 | 
					    const r = this.options.trianglePointerSize / 2 | 
				
			||||||
 | 
					    s.top = (this.y + this.wheelRadius + this.padding - r) + 'px' | 
				
			||||||
 | 
					    s.left = (this.x + this.wheelRadius + this.padding - r) + 'px' | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  drawPointer (ctx, r, color1, color2) { | 
				
			||||||
 | 
					    ctx.setTransform(this.pixelRatio, 0, 0, this.pixelRatio, 0, 0) | 
				
			||||||
 | 
					    ctx.fillStyle = color2 | 
				
			||||||
 | 
					    ctx.beginPath() | 
				
			||||||
 | 
					    ctx.arc(r, r, r, 0, TAU, true) | 
				
			||||||
 | 
					    ctx.fill() // => black circle
 | 
				
			||||||
 | 
					    ctx.fillStyle = color1 | 
				
			||||||
 | 
					    ctx.beginPath() | 
				
			||||||
 | 
					    ctx.arc(r, r, r - 2, 0, TAU, true) | 
				
			||||||
 | 
					    ctx.fill() // => white circle with 1px black border
 | 
				
			||||||
 | 
					    ctx.fillStyle = color2 | 
				
			||||||
 | 
					    ctx.beginPath() | 
				
			||||||
 | 
					    ctx.arc(r, r, r / 4 + 2, 0, TAU, true) | 
				
			||||||
 | 
					    ctx.fill() // => black circle with big white border and a small black border
 | 
				
			||||||
 | 
					    ctx.globalCompositeOperation = 'destination-out' | 
				
			||||||
 | 
					    ctx.beginPath() | 
				
			||||||
 | 
					    ctx.arc(r, r, r / 4, 0, TAU, true) | 
				
			||||||
 | 
					    ctx.fill() // => transparent center
 | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // The Element and the DOM
 | 
				
			||||||
 | 
					  inject (parent) { | 
				
			||||||
 | 
					    parent.appendChild(this.container) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getRelativeCoordinates (evt) { | 
				
			||||||
 | 
					    let elem = this.triangle | 
				
			||||||
 | 
					    let rect = elem.getBoundingClientRect() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return { | 
				
			||||||
 | 
					      x: evt.clientX - rect.x, | 
				
			||||||
 | 
					      y: evt.clientY - rect.y | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  dispose () { | 
				
			||||||
 | 
					    let parent = this.container.parentNode | 
				
			||||||
 | 
					    if (parent) { | 
				
			||||||
 | 
					      parent.removeChild(this.container) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getElement () { | 
				
			||||||
 | 
					    return this.container | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Color accessors
 | 
				
			||||||
 | 
					  getCSS () { | 
				
			||||||
 | 
					    const h = Math.round(this.hue * (360 / TAU)) | 
				
			||||||
 | 
					    const s = Math.round(this.saturation * 100) | 
				
			||||||
 | 
					    const l = Math.round(this.lightness * 100) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return `hsl(${h}, ${s}%, ${l}%)` | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getHEX () { | 
				
			||||||
 | 
					    return rgb2hex(...this.getRGB()) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setHEX (hex) { | 
				
			||||||
 | 
					    this.setRGB(...hex2rgb(hex)) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getRGB () { | 
				
			||||||
 | 
					    return hsl2rgb(...this.getHSL()) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setRGB (r, g, b) { | 
				
			||||||
 | 
					    this.setHSL(...rgb2hsl(r, g, b)) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getHSL () { | 
				
			||||||
 | 
					    return [this.hue, this.saturation, this.lightness] | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setHSL (h, s, l) { | 
				
			||||||
 | 
					    this.hue = h | 
				
			||||||
 | 
					    this.saturation = s | 
				
			||||||
 | 
					    this.lightness = l | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.initColor() | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  initColor () { | 
				
			||||||
 | 
					    this.calculatePositions() | 
				
			||||||
 | 
					    this.moveWheelPointer() | 
				
			||||||
 | 
					    this.drawTriangle() | 
				
			||||||
 | 
					    this.moveTrianglePointer() | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // Mouse event handling
 | 
				
			||||||
 | 
					  attachEvents () { | 
				
			||||||
 | 
					    this.down = null | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mousedown = (evt) => { | 
				
			||||||
 | 
					      evt.stopPropagation() | 
				
			||||||
 | 
					      evt.preventDefault() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      doc.body.addEventListener('mousemove', mousemove, false) | 
				
			||||||
 | 
					      doc.body.addEventListener('mouseup', mouseup, false) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let xy = this.getRelativeCoordinates(evt) | 
				
			||||||
 | 
					      this.map(xy.x, xy.y) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mousemove = (evt) => { | 
				
			||||||
 | 
					      let xy = this.getRelativeCoordinates(evt) | 
				
			||||||
 | 
					      this.move(xy.x, xy.y) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mouseup = (evt) => { | 
				
			||||||
 | 
					      if (this.down) { | 
				
			||||||
 | 
					        this.down = null | 
				
			||||||
 | 
					        this.emit('dragend') | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					      doc.body.removeEventListener('mousemove', mousemove, false) | 
				
			||||||
 | 
					      doc.body.removeEventListener('mouseup', mouseup, false) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.container.addEventListener('mousedown', mousedown, false) | 
				
			||||||
 | 
					    this.container.addEventListener('mousemove', mousemove, false) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  map (x, y) { | 
				
			||||||
 | 
					    let x0 = x | 
				
			||||||
 | 
					    let y0 = y | 
				
			||||||
 | 
					    x -= this.wheelRadius | 
				
			||||||
 | 
					    y -= this.wheelRadius | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const r = M.sqrt(x * x + y * y) // Pythagoras
 | 
				
			||||||
 | 
					    if (r > this.triangleRadius && r < this.wheelRadius) { | 
				
			||||||
 | 
					      // Wheel
 | 
				
			||||||
 | 
					      this.down = 'wheel' | 
				
			||||||
 | 
					      this.emit('dragstart') | 
				
			||||||
 | 
					      this.move(x0, y0) | 
				
			||||||
 | 
					    } else if (r < this.triangleRadius) { | 
				
			||||||
 | 
					      // Inner circle
 | 
				
			||||||
 | 
					      this.down = 'triangle' | 
				
			||||||
 | 
					      this.emit('dragstart') | 
				
			||||||
 | 
					      this.move(x0, y0) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  move (x, y) { | 
				
			||||||
 | 
					    if (!this.down) { | 
				
			||||||
 | 
					      return | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    x -= this.wheelRadius | 
				
			||||||
 | 
					    y -= this.wheelRadius | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let rad = M.atan2(-y, x) | 
				
			||||||
 | 
					    if (rad < 0) { | 
				
			||||||
 | 
					      rad += TAU | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (this.down === 'wheel') { | 
				
			||||||
 | 
					      this.hue = rad | 
				
			||||||
 | 
					      this.initColor() | 
				
			||||||
 | 
					      this.emit('drag') | 
				
			||||||
 | 
					    } else if (this.down === 'triangle') { | 
				
			||||||
 | 
					      // get radius and max radius
 | 
				
			||||||
 | 
					      let rad0 = (rad + TAU - this.hue) % TAU | 
				
			||||||
 | 
					      let rad1 = rad0 % (TAU / 3) - (TAU / 6) | 
				
			||||||
 | 
					      let a = 0.5 * this.triangleRadius | 
				
			||||||
 | 
					      let b = M.tan(rad1) * a | 
				
			||||||
 | 
					      let r = M.sqrt(x * x + y * y) // Pythagoras
 | 
				
			||||||
 | 
					      let maxR = M.sqrt(a * a + b * b) // Pythagoras
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (r > maxR) { | 
				
			||||||
 | 
					        const dx = M.tan(rad1) * r | 
				
			||||||
 | 
					        let rad2 = M.atan(dx / maxR) | 
				
			||||||
 | 
					        if (rad2 > TAU / 6) { | 
				
			||||||
 | 
					          rad2 = TAU / 6 | 
				
			||||||
 | 
					        } else if (rad2 < -TAU / 6) { | 
				
			||||||
 | 
					          rad2 = -TAU / 6 | 
				
			||||||
 | 
					        } | 
				
			||||||
 | 
					        rad += rad2 - rad1 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        rad0 = (rad + TAU - this.hue) % TAU | 
				
			||||||
 | 
					        rad1 = rad0 % (TAU / 3) - (TAU / 6) | 
				
			||||||
 | 
					        b = M.tan(rad1) * a | 
				
			||||||
 | 
					        r = maxR = M.sqrt(a * a + b * b) // Pythagoras
 | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      x = M.round(M.cos(rad) * r) | 
				
			||||||
 | 
					      y = M.round(-M.sin(rad) * r) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const l = this.lightness = ((M.sin(rad0) * r) / this.triangleSideLength) + 0.5 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const widthShare = 1 - (M.abs(l - 0.5) * 2) | 
				
			||||||
 | 
					      let s = this.saturation = (((M.cos(rad0) * r) + (this.triangleRadius / 2)) / (1.5 * this.triangleRadius)) / widthShare | 
				
			||||||
 | 
					      s = M.max(0, s) // cannot be lower than 0
 | 
				
			||||||
 | 
					      s = M.min(1, s) // cannot be greater than 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.lightness = l | 
				
			||||||
 | 
					      this.saturation = s | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.x = x | 
				
			||||||
 | 
					      this.y = y | 
				
			||||||
 | 
					      this.moveTrianglePointer() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      this.emit('drag') | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /*************** | 
				
			||||||
 | 
					   * Init helpers * | 
				
			||||||
 | 
					   ***************/ | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static initInput (input, options) { | 
				
			||||||
 | 
					    options = options || {} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let ct | 
				
			||||||
 | 
					    let openColorTriangle = function () { | 
				
			||||||
 | 
					      let hex = input.value | 
				
			||||||
 | 
					      if (options.parseColor) hex = options.parseColor(hex) | 
				
			||||||
 | 
					      if (!ct) { | 
				
			||||||
 | 
					        options.size = options.size || input.offsetWidth | 
				
			||||||
 | 
					        options.background = win.getComputedStyle(input, null).backgroundColor | 
				
			||||||
 | 
					        options.margin = options.margin || 10 | 
				
			||||||
 | 
					        options.event = options.event || 'dragend' | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ct = new ColorTriangle(hex, options) | 
				
			||||||
 | 
					        ct.on(options.event, () => { | 
				
			||||||
 | 
					          const hex = ct.getHEX() | 
				
			||||||
 | 
					          input.value = options.uppercase ? hex.toUpperCase() : hex | 
				
			||||||
 | 
					          fireChangeEvent() | 
				
			||||||
 | 
					        }) | 
				
			||||||
 | 
					      } else { | 
				
			||||||
 | 
					        ct.setHEX(hex) | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let top = input.offsetTop | 
				
			||||||
 | 
					      if (win.innerHeight - input.getBoundingClientRect().top > input.offsetHeight + options.margin + options.size) { | 
				
			||||||
 | 
					        top += input.offsetHeight + options.margin // below
 | 
				
			||||||
 | 
					      } else { | 
				
			||||||
 | 
					        top -= options.margin + options.size // above
 | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      let el = ct.getElement() | 
				
			||||||
 | 
					      el.style.position = 'absolute' | 
				
			||||||
 | 
					      el.style.left = input.offsetLeft + 'px' | 
				
			||||||
 | 
					      el.style.top = top + 'px' | 
				
			||||||
 | 
					      el.style.zIndex = '1338' // above everything
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      ct.inject(input.parentNode) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let closeColorTriangle = () => { | 
				
			||||||
 | 
					      if (ct) { | 
				
			||||||
 | 
					        ct.dispose() | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let fireChangeEvent = () => { | 
				
			||||||
 | 
					      let evt = doc.createEvent('HTMLEvents') | 
				
			||||||
 | 
					      evt.initEvent('input', true, false) // bubbles = true, cancable = false
 | 
				
			||||||
 | 
					      input.dispatchEvent(evt) // fire event
 | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    input.addEventListener('focus', openColorTriangle, false) | 
				
			||||||
 | 
					    input.addEventListener('blur', closeColorTriangle, false) | 
				
			||||||
 | 
					    input.addEventListener('keyup', () => { | 
				
			||||||
 | 
					      const val = input.value | 
				
			||||||
 | 
					      if (val.match(/^#((?:[0-9A-Fa-f]{3})|(?:[0-9A-Fa-f]{6}))$/)) { | 
				
			||||||
 | 
					        openColorTriangle() | 
				
			||||||
 | 
					        fireChangeEvent() | 
				
			||||||
 | 
					      } else { | 
				
			||||||
 | 
					        closeColorTriangle() | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    }, false) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /******************* | 
				
			||||||
 | 
					   * Helper functions * | 
				
			||||||
 | 
					   *******************/ | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setOptions (opts) { | 
				
			||||||
 | 
					    opts = opts || {} | 
				
			||||||
 | 
					    let dflt = this.options | 
				
			||||||
 | 
					    let options = this.options = {} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    each(dflt, function (val, key) { | 
				
			||||||
 | 
					      options[key] = (opts.hasOwnProperty(key)) | 
				
			||||||
 | 
					        ? opts[key] | 
				
			||||||
 | 
					        : val | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,57 @@ | 
				
			|||||||
 | 
					const { qs } = require('../utils') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = function initButtons (input) { | 
				
			||||||
 | 
					  let container = qs('#action-buttons') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // button labels
 | 
				
			||||||
 | 
					  let labels = [] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // button elements
 | 
				
			||||||
 | 
					  let buttons = [] | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // add a button element
 | 
				
			||||||
 | 
					  let pushButton = function pushButton () { | 
				
			||||||
 | 
					    let button = document.createElement('button') | 
				
			||||||
 | 
					    button.classList.add('action-button') | 
				
			||||||
 | 
					    button.setAttribute('data-n', buttons.length) | 
				
			||||||
 | 
					    buttons.push(button) | 
				
			||||||
 | 
					    container.appendChild(button) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    button.addEventListener('click', e => { | 
				
			||||||
 | 
					      // might as well use the attribute ¯\_(ツ)_/¯
 | 
				
			||||||
 | 
					      let index = +button.getAttribute('data-n') | 
				
			||||||
 | 
					      input.sendButton(index) | 
				
			||||||
 | 
					    }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return button | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // remove a button element
 | 
				
			||||||
 | 
					  let popButton = function popButton () { | 
				
			||||||
 | 
					    let button = buttons.pop() | 
				
			||||||
 | 
					    button.parentNode.removeChild(button) | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // sync with DOM
 | 
				
			||||||
 | 
					  let update = function updateButtons () { | 
				
			||||||
 | 
					    if (labels.length > buttons.length) { | 
				
			||||||
 | 
					      for (let i = buttons.length; i < labels.length; i++) { | 
				
			||||||
 | 
					        pushButton() | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } else if (buttons.length > labels.length) { | 
				
			||||||
 | 
					      for (let i = labels.length; i <= buttons.length; i++) { | 
				
			||||||
 | 
					        popButton() | 
				
			||||||
 | 
					      } | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (let i = 0; i < labels.length; i++) { | 
				
			||||||
 | 
					      let label = labels[i].trim() | 
				
			||||||
 | 
					      let button = buttons[i] | 
				
			||||||
 | 
					      button.textContent = label || '\u00a0' // label or nbsp
 | 
				
			||||||
 | 
					      if (!label) button.classList.add('inactive') | 
				
			||||||
 | 
					      else button.classList.remove('inactive') | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return { update, labels } | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,105 @@ | 
				
			|||||||
 | 
					const ColorTriangle = require('./lib/colortriangle') | 
				
			||||||
 | 
					const $ = require('./lib/chibi') | 
				
			||||||
 | 
					const themes = require('./term/themes') | 
				
			||||||
 | 
					const { qs } = require('./utils') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function selectedTheme () { | 
				
			||||||
 | 
					  return +$('#theme').val() | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports.init = function () { | 
				
			||||||
 | 
					  $('#theme').on('change', showColor) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  $('#default_fg').on('input', showColor) | 
				
			||||||
 | 
					  $('#default_bg').on('input', showColor) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let opts = { | 
				
			||||||
 | 
					    padding: 10, | 
				
			||||||
 | 
					    event: 'drag', | 
				
			||||||
 | 
					    uppercase: true, | 
				
			||||||
 | 
					    trianglePointerSize: 20, | 
				
			||||||
 | 
					    // wheelPointerSize: 12,
 | 
				
			||||||
 | 
					    size: 200, | 
				
			||||||
 | 
					    parseColor: (color) => { | 
				
			||||||
 | 
					      return themes.toHex(color, selectedTheme()) | 
				
			||||||
 | 
					    } | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ColorTriangle.initInput(qs('#default_fg'), opts) | 
				
			||||||
 | 
					  ColorTriangle.initInput(qs('#default_bg'), opts) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  $('.colorprev.bg span').on('click', function () { | 
				
			||||||
 | 
					    const bg = this.dataset.bg | 
				
			||||||
 | 
					    if (typeof bg != 'undefined') $('#default_bg').val(bg) | 
				
			||||||
 | 
					    showColor() | 
				
			||||||
 | 
					  }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  $('.colorprev.fg span').on('click', function () { | 
				
			||||||
 | 
					    const fg = this.dataset.fg | 
				
			||||||
 | 
					    if (typeof fg != 'undefined') $('#default_fg').val(fg) | 
				
			||||||
 | 
					    showColor() | 
				
			||||||
 | 
					  }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let $presets = $('#fgbg_presets') | 
				
			||||||
 | 
					  for (let i = 0; i < themes.fgbgThemes.length; i++) { | 
				
			||||||
 | 
					    const thm = themes.fgbgThemes[i] | 
				
			||||||
 | 
					    const fg = thm[0] | 
				
			||||||
 | 
					    const bg = thm[1] | 
				
			||||||
 | 
					    const lbl = thm[2] | 
				
			||||||
 | 
					    const tit = thm[3] | 
				
			||||||
 | 
					    $presets.htmlAppend( | 
				
			||||||
 | 
					        '<span class="preset" ' + | 
				
			||||||
 | 
					        'data-xfg="' + fg + '" data-xbg="' + bg + '" ' + | 
				
			||||||
 | 
					        'style="color:' + fg + ';background:' + bg + '" title="' + tit + '"> ' + lbl + ' </span>') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if ((i + 1) % 5 === 0) $presets.htmlAppend('<br>') | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  $('.preset').on('click', function () { | 
				
			||||||
 | 
					    $('#default_fg').val(this.dataset.xfg) | 
				
			||||||
 | 
					    $('#default_bg').val(this.dataset.xbg) | 
				
			||||||
 | 
					    showColor() | 
				
			||||||
 | 
					  }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  showColor() | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function showColor () { | 
				
			||||||
 | 
					  let ex = qs('.color-example') | 
				
			||||||
 | 
					  let fg = $('#default_fg').val() | 
				
			||||||
 | 
					  let bg = $('#default_bg').val() | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (/^\d+$/.test(fg)) { | 
				
			||||||
 | 
					    fg = +fg | 
				
			||||||
 | 
					  } else if (!/^#[\da-f]{6}$/i.test(fg)) { | 
				
			||||||
 | 
					    fg = 'black' | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (/^\d+$/.test(bg)) { | 
				
			||||||
 | 
					    bg = +bg | 
				
			||||||
 | 
					  } else if (!/^#[\da-f]{6}$/i.test(bg)) { | 
				
			||||||
 | 
					    bg = 'black' | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const themeN = selectedTheme() | 
				
			||||||
 | 
					  ex.dataset.fg = fg | 
				
			||||||
 | 
					  ex.dataset.bg = bg | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  themes.themePreview(themeN) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  $('.colorprev.fg span').css('background', themes.toHex(bg, themeN)) | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports.nextTheme = () => { | 
				
			||||||
 | 
					  let sel = qs('#theme') | 
				
			||||||
 | 
					  let i = sel.selectedIndex | 
				
			||||||
 | 
					  sel.options[++i % sel.options.length].selected = true | 
				
			||||||
 | 
					  showColor() | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports.prevTheme = () => { | 
				
			||||||
 | 
					  let sel = qs('#theme') | 
				
			||||||
 | 
					  let i = sel.selectedIndex | 
				
			||||||
 | 
					  sel.options[(sel.options.length + (--i)) % sel.options.length].selected = true | 
				
			||||||
 | 
					  showColor() | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,14 @@ | 
				
			|||||||
 | 
					#! /usr/bin/env php | 
				
			||||||
 | 
					<?php | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require_once __DIR__ . '/../base.php'; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$selected = array_slice($argv, 1); | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					$output = []; | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					foreach ($selected as $key) { | 
				
			||||||
 | 
					  $output[$key] = tr($key); | 
				
			||||||
 | 
					} | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fwrite(STDOUT, json_encode($output, JSON_UNESCAPED_UNICODE)); | 
				
			||||||
@ -0,0 +1,54 @@ | 
				
			|||||||
 | 
					/* | 
				
			||||||
 | 
					 * This is a Webpack loader that loads the language data by running | 
				
			||||||
 | 
					 * dump_selected.php. | 
				
			||||||
 | 
					 */ | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { spawnSync } = require('child_process') | 
				
			||||||
 | 
					const path = require('path') | 
				
			||||||
 | 
					const selectedKeys = require('./js-keys') | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = function (source) { | 
				
			||||||
 | 
					  let child = spawnSync(path.resolve(__dirname, '_js-dump.php'), selectedKeys, { | 
				
			||||||
 | 
					    timeout: 1000 | 
				
			||||||
 | 
					  }) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let data | 
				
			||||||
 | 
					  try { | 
				
			||||||
 | 
					    data = JSON.parse(child.stdout.toString().trim()) | 
				
			||||||
 | 
					  } catch (err) { | 
				
			||||||
 | 
					    console.error(`\x1b[31;1m[lang-loader] Failed to parse JSON:`) | 
				
			||||||
 | 
					    console.error(child.stdout.toString().trim()) | 
				
			||||||
 | 
					    console.error(`\x1b[m`) | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (err) throw err | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // adapted from webpack/loader-utils
 | 
				
			||||||
 | 
					  let remainingRequest = this.remainingRequest | 
				
			||||||
 | 
					  if (!remainingRequest) { | 
				
			||||||
 | 
					    remainingRequest = this.loaders.slice(this.loaderIndex + 1) | 
				
			||||||
 | 
					      .map(obj => obj.request) | 
				
			||||||
 | 
					      .concat([this.resource]).join('!') | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let currentRequest = this.currentRequest | 
				
			||||||
 | 
					  if (!currentRequest) { | 
				
			||||||
 | 
					    remainingRequest = this.loaders.slice(this.loaderIndex) | 
				
			||||||
 | 
					      .map(obj => obj.request) | 
				
			||||||
 | 
					      .concat([this.resource]).join('!') | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let map = { | 
				
			||||||
 | 
					    version: 3, | 
				
			||||||
 | 
					    file: currentRequest, | 
				
			||||||
 | 
					    sourceRoot: '', | 
				
			||||||
 | 
					    sources: [remainingRequest], | 
				
			||||||
 | 
					    sourcesContent: [source], | 
				
			||||||
 | 
					    names: [], | 
				
			||||||
 | 
					    mappings: 'AAAA;AAAA' | 
				
			||||||
 | 
					  } | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  this.callback(null, | 
				
			||||||
 | 
					    `/* Generated language file */\n` + | 
				
			||||||
 | 
					    `module.exports=${JSON.stringify(data)}\n`, map) | 
				
			||||||
 | 
					} | 
				
			||||||
@ -0,0 +1,19 @@ | 
				
			|||||||
 | 
					<?php | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					return [ | 
				
			||||||
 | 
						'appname' => 'ESPTerm', | 
				
			||||||
 | 
						'appname_demo' => 'ESPTerm<sup> DEMO</sup>', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// not used - api etc. Added to suppress warnings | 
				
			||||||
 | 
						'menu.term_set' => '', | 
				
			||||||
 | 
						'menu.wifi_connstatus' => '', | 
				
			||||||
 | 
						'menu.wifi_set' => '', | 
				
			||||||
 | 
						'menu.wifi_scan' => '', | 
				
			||||||
 | 
						'menu.network_set' => '', | 
				
			||||||
 | 
						'menu.system_set' => '', | 
				
			||||||
 | 
						'menu.write_defaults' => '', | 
				
			||||||
 | 
						'menu.restore_defaults' => '', | 
				
			||||||
 | 
						'menu.restore_hard' => '', | 
				
			||||||
 | 
						'menu.reset_screen' => '', | 
				
			||||||
 | 
						'menu.index' => '', | 
				
			||||||
 | 
					]; | 
				
			||||||
@ -0,0 +1,271 @@ | 
				
			|||||||
 | 
					<?php | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					return [ | 
				
			||||||
 | 
						'menu.cfg_wifi' => 'Nastavení WiFi', | 
				
			||||||
 | 
						'menu.cfg_network' => 'Nastavení sítě', | 
				
			||||||
 | 
						'menu.cfg_term' => 'Nastavení terminalu', | 
				
			||||||
 | 
						'menu.about' => 'About', | 
				
			||||||
 | 
						'menu.help' => 'Nápověda', | 
				
			||||||
 | 
						'menu.term' => 'Zpět k terminálu', | 
				
			||||||
 | 
						'menu.cfg_system' => 'Nastavení systému', | 
				
			||||||
 | 
						'menu.cfg_wifi_conn' => 'Připojování', | 
				
			||||||
 | 
						'menu.settings' => 'Nastavení', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Terminal page | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'title.term' => 'Terminál', // page title of the terminal page | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'term_nav.fullscreen' => 'Celá obr.', | 
				
			||||||
 | 
						'term_nav.config' => 'Nastavení', | 
				
			||||||
 | 
						'term_nav.wifi' => 'WiFi', | 
				
			||||||
 | 
						'term_nav.help' => 'Nápověda', | 
				
			||||||
 | 
						'term_nav.about' => 'About', | 
				
			||||||
 | 
						'term_nav.paste' => 'Vložit', | 
				
			||||||
 | 
						'term_nav.upload' => 'Nahrát', | 
				
			||||||
 | 
						'term_nav.keybd' => 'Klávesnice', | 
				
			||||||
 | 
						'term_nav.paste_prompt' => 'Vložte text k~odeslání:', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'term_conn.connecting' => 'Připojuji se', | 
				
			||||||
 | 
						'term_conn.waiting_content' => 'Čekám na data', | 
				
			||||||
 | 
						'term_conn.disconnected' => 'Odpojen', | 
				
			||||||
 | 
						'term_conn.waiting_server' => 'Čekám na server', | 
				
			||||||
 | 
						'term_conn.reconnecting' => 'Obnova spojení', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Terminal settings page | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'term.defaults' => 'Výchozí nastavení', | 
				
			||||||
 | 
						'term.expert' => 'Pokročilé volby', | 
				
			||||||
 | 
						'term.explain_initials' => ' | 
				
			||||||
 | 
							Tato nastavení jsou použita po spuštění a při resetu obrazovky | 
				
			||||||
 | 
							(příkaz RIS, <code>\ec</code>). Tyto volby lze měnit za běhu  | 
				
			||||||
 | 
							pomocí řídicích sekvencí. | 
				
			||||||
 | 
							', | 
				
			||||||
 | 
						'term.explain_expert' => ' | 
				
			||||||
 | 
							Interní parametry terminálu. Změnou časování lze dosáhnout kratší | 
				
			||||||
 | 
							latence a~rychlejšího překreslování, hodnoty záleží na konkrétní  | 
				
			||||||
 | 
							aplikaci. Timeout parseru je čas do automatického zrušení započaté  | 
				
			||||||
 | 
							řídicí sekvence.', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'term.example' => 'Náhled výchozích barev', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'term.explain_scheme' => ' | 
				
			||||||
 | 
							Výchozí barvu textu a pozadí vyberete kliknutím na barvy v~paletě. | 
				
			||||||
 | 
							Dále lze použít ANSI barvy 0-255 a hex ve formátu #FFFFFF. | 
				
			||||||
 | 
							', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'term.fgbg_presets' => 'Předvolby výchozích<br>barev textu a pozadí', | 
				
			||||||
 | 
						'term.color_scheme' => 'Barevné schéma', | 
				
			||||||
 | 
						'term.reset_screen' => 'Resetovat obrazovku a parser', | 
				
			||||||
 | 
						'term.term_title' => 'Nadpis', | 
				
			||||||
 | 
						'term.term_width' => 'Šířka', | 
				
			||||||
 | 
						'term.term_height' => 'Výška', | 
				
			||||||
 | 
						'term.buttons' => 'Text tlačítke', | 
				
			||||||
 | 
						'term.theme' => 'Barevná paleta', | 
				
			||||||
 | 
						'term.cursor_shape' => 'Styl kurzoru', | 
				
			||||||
 | 
						'term.parser_tout_ms' => 'Timeout parseru', | 
				
			||||||
 | 
						'term.display_tout_ms' => 'Prodleva překreslení', | 
				
			||||||
 | 
						'term.display_cooldown_ms' => 'Min. čas překreslení', | 
				
			||||||
 | 
						'term.allow_decopt_12' => 'Povolit \e?12h/l', | 
				
			||||||
 | 
						'term.fn_alt_mode' => 'SS3 Fx klávesy', | 
				
			||||||
 | 
						'term.show_config_links' => 'Menu pod obrazovkou', | 
				
			||||||
 | 
						'term.show_buttons' => 'Zobrazit tlačítka', | 
				
			||||||
 | 
						'term.loopback' => 'Loopback (<span style="text-decoration:overline">SRM</span>)', | 
				
			||||||
 | 
						'term.crlf_mode' => 'Enter = CR+LF (LNM)', | 
				
			||||||
 | 
						'term.want_all_fn' => 'Zachytávat F5, F11, F12', | 
				
			||||||
 | 
						'term.button_msgs' => 'Reporty tlačítek<br>(dek. ASCII CSV)', | 
				
			||||||
 | 
						'term.color_fg' => 'Výchozí text', | 
				
			||||||
 | 
						'term.color_bg' => 'Výchozí pozadí', | 
				
			||||||
 | 
						'term.color_fg_prev' => 'Barva textu', | 
				
			||||||
 | 
						'term.color_bg_prev' => 'Barva pozadí', | 
				
			||||||
 | 
						'term.colors_preview' => '', | 
				
			||||||
 | 
					//	'term.debugbar' => 'Ladění ', | 
				
			||||||
 | 
					//	'term.ascii_debug' => 'Použít debug parser', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'cursor.block_blink' => 'Blok, blikající', | 
				
			||||||
 | 
					    'cursor.block_steady' => 'Blok, stálý', | 
				
			||||||
 | 
					    'cursor.underline_blink' => 'Podtržítko, blikající', | 
				
			||||||
 | 
					    'cursor.underline_steady' => 'Podtržítko, stálé', | 
				
			||||||
 | 
					    'cursor.bar_blink' => 'Svislice, blikající', | 
				
			||||||
 | 
					    'cursor.bar_steady' => 'Svislice, stálá', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Text upload dialog | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'upload.title' => 'Upload textu', | 
				
			||||||
 | 
						'upload.prompt' => 'Načíst ze souboru:', | 
				
			||||||
 | 
						'upload.endings' => 'Konce řádku:', | 
				
			||||||
 | 
						'upload.endings.cr' => 'CR (klávesa Enter)', | 
				
			||||||
 | 
						'upload.endings.crlf' => 'CR LF (Windows)', | 
				
			||||||
 | 
						'upload.endings.lf' => 'LF (Linux)', | 
				
			||||||
 | 
						'upload.chunk_delay' => 'Prodleva (ms):', | 
				
			||||||
 | 
						'upload.chunk_size' => 'Délka úseku (0=řádek):', | 
				
			||||||
 | 
						'upload.progress' => 'Proběh:', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Network config page | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'net.explain_sta' => ' | 
				
			||||||
 | 
							Odškrtněte "Použít dynamickou IP" pro nastavení statické IP adresy.', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'net.explain_ap' => ' | 
				
			||||||
 | 
							Tato nastavení ovlivňují interní DHCP server v AP režimu (hotspot).', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'net.ap_dhcp_time' => 'Doba zapůjčení adresy', | 
				
			||||||
 | 
						'net.ap_dhcp_start' => 'Začátek IP poolu', | 
				
			||||||
 | 
						'net.ap_dhcp_end' => 'Konec IP poolu', | 
				
			||||||
 | 
						'net.ap_addr_ip' => 'Vlastní IP adresa', | 
				
			||||||
 | 
						'net.ap_addr_mask' => 'Maska podsítě', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'net.sta_dhcp_enable' => 'Použít dynamickou IP', | 
				
			||||||
 | 
						'net.sta_addr_ip' => 'Statická IP modulu', | 
				
			||||||
 | 
						'net.sta_addr_mask' => 'Maska podsítě', | 
				
			||||||
 | 
						'net.sta_addr_gw' => 'Gateway', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'net.ap' => 'DHCP server (AP)', | 
				
			||||||
 | 
						'net.sta' => 'DHCP klient', | 
				
			||||||
 | 
						'net.sta_mac' => 'MAC adresa klienta', | 
				
			||||||
 | 
						'net.ap_mac' => 'MAC adresa AP', | 
				
			||||||
 | 
						'net.details' => 'MAC adresy', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Wifi config page | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'wifi.ap' => 'WiFi hotspot', | 
				
			||||||
 | 
						'wifi.sta' => 'Připojení k~externí síti', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'wifi.enable' => 'Zapnuto', | 
				
			||||||
 | 
						'wifi.tpw' => 'Vysílací výkon', | 
				
			||||||
 | 
						'wifi.ap_channel' => 'WiFi kanál', | 
				
			||||||
 | 
						'wifi.ap_ssid' => 'Jméno hotspotu', | 
				
			||||||
 | 
						'wifi.ap_password' => 'Přístupové heslo', | 
				
			||||||
 | 
						'wifi.ap_hidden' => 'Skrýt síť', | 
				
			||||||
 | 
						'wifi.sta_info' => 'Zvolená síť', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'wifi.not_conn' => 'Nepřipojen.', | 
				
			||||||
 | 
						'wifi.sta_none' => 'Žádná', | 
				
			||||||
 | 
						'wifi.sta_active_pw' => '🔒 Uložené heslo', | 
				
			||||||
 | 
						'wifi.sta_active_nopw' => '🔓 Bez hesla', | 
				
			||||||
 | 
						'wifi.connected_ip_is' => 'Připojen, IP: ', | 
				
			||||||
 | 
						'wifi.sta_password' => 'Heslo:', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'wifi.scanning' => 'Hledám sítě', | 
				
			||||||
 | 
						'wifi.scan_now' => 'Klikněte pro vyhledání sítí!', | 
				
			||||||
 | 
						'wifi.cant_scan_no_sta' => 'Klikněte pro zapnutí režimu klienta a vyhledání sítí!', | 
				
			||||||
 | 
						'wifi.select_ssid' => 'Dostupné sítě:', | 
				
			||||||
 | 
						'wifi.enter_passwd' => 'Zadejte heslo pro ":ssid:"', | 
				
			||||||
 | 
						'wifi.sta_explain' => 'Vyberte síť a připojte se tlačítkem vpravo nahoře.', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Wifi connecting status page | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'wificonn.status' => 'Stav:', | 
				
			||||||
 | 
						'wificonn.back_to_config' => 'Zpět k~nastavení WiFi', | 
				
			||||||
 | 
						'wificonn.telemetry_lost' => 'Spojení bylo přerušeno; připojování selhalo, nebo jste byli odpojeni od sítě.', | 
				
			||||||
 | 
						'wificonn.explain_android_sucks' => ' | 
				
			||||||
 | 
							Pokud ESPTerm konfigurujete pomocí mobilu nebo z~externí sítě, může se stát | 
				
			||||||
 | 
							že některé ze zařízení změní síť a~ukazatel průběhu přestane fungovat.  | 
				
			||||||
 | 
							Počkejte ~15s a pak zkontrolujte, zda se připojení zdařilo. | 
				
			||||||
 | 
							', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'wificonn.explain_reset' => ' | 
				
			||||||
 | 
							Interní hotspot lze kdykoliv vynutit podržením tlačítka BOOT, až modrá LED začne blikat. | 
				
			||||||
 | 
							Podržíte-li tlačítko déle (LED začne blikat rychleji), dojde k~obnovení do výchozích anstavení.', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'wificonn.disabled' => "Režim klienta není povolen.", | 
				
			||||||
 | 
						'wificonn.idle' => "Žádná IP adresa, připojování neprobíhá.", | 
				
			||||||
 | 
						'wificonn.success' => "Připijen! IP adresa je ", | 
				
			||||||
 | 
						'wificonn.working' => "Připojuji k zvolené síti", | 
				
			||||||
 | 
						'wificonn.fail' => "Připojení selhalo, zkontrolujte nastavení a~pokus opakujte. Důvod: ", | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Access restrictions form | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'pwlock.title' => 'Omezení přístupu', | 
				
			||||||
 | 
						'pwlock.explain' => ' | 
				
			||||||
 | 
							Části webového rozhraní lze chránit heslem. Nemáte-li v úmyslu heslo měnit,  | 
				
			||||||
 | 
							do jeho políčka nic nevyplňujte.<br> | 
				
			||||||
 | 
							Výchozí přístupové heslo je "%def_access_pw%". | 
				
			||||||
 | 
						', | 
				
			||||||
 | 
						'pwlock.region' => 'Chránit heslem', | 
				
			||||||
 | 
						'pwlock.region.none' => 'Nic, vše volně přístupné', | 
				
			||||||
 | 
						'pwlock.region.settings_noterm' => 'Nastavení, mimo terminál', | 
				
			||||||
 | 
						'pwlock.region.settings' => 'Všechna nastavení', | 
				
			||||||
 | 
						'pwlock.region.menus' => 'Celá admin. sekce', | 
				
			||||||
 | 
						'pwlock.region.all' => 'Vše, včetně terminálu', | 
				
			||||||
 | 
						'pwlock.new_access_pw' => 'Nové přístupové heslo', | 
				
			||||||
 | 
						'pwlock.new_access_pw2' => 'Zopakujte nové heslo', | 
				
			||||||
 | 
						'pwlock.admin_pw' => 'Systémové heslo', | 
				
			||||||
 | 
						'pwlock.access_name' => 'Uživatelské jméno', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setting admin password | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'adminpw.title' => 'Změna systémového hesla', | 
				
			||||||
 | 
						'adminpw.explain' => | 
				
			||||||
 | 
							' | 
				
			||||||
 | 
							Systémové heslo slouží k úpravám uložených výchozích nastavení | 
				
			||||||
 | 
							a ke změně přístupových oprávnění. | 
				
			||||||
 | 
							Toto heslo je uloženo mimo ostatní data, obnovení do výchozách nastavení | 
				
			||||||
 | 
							na něj nemá vliv. | 
				
			||||||
 | 
							Toto heslo nelze jednoduše obnovit, v případě zapomenutí vymažte flash paměť a obnovte firmware.<br> | 
				
			||||||
 | 
							Vychozí systémové heslo je "%def_admin_pw%". | 
				
			||||||
 | 
							', | 
				
			||||||
 | 
						'adminpw.new_admin_pw' => 'Nové systémové heslo', | 
				
			||||||
 | 
						'adminpw.new_admin_pw2' => 'Zopakujte nové heslo', | 
				
			||||||
 | 
						'adminpw.old_admin_pw' => 'Původní systémové heslo', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Persist form | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'persist.title' => 'Záloha a~obnovení konfigurace', | 
				
			||||||
 | 
						'persist.explain' => ' | 
				
			||||||
 | 
							Všechna nastavení jsou ukládána do flash paměti. V~paměti jsou | 
				
			||||||
 | 
							vyhrazené dva oddíly, aktivní nastavení a záloha. Zálohu lze přepsat | 
				
			||||||
 | 
							za použití systémového hesla, původní nastavení z ní pak můžete kdykoliv obnovit. | 
				
			||||||
 | 
							Pro obnovení ze zálohy stačí podržet tlačítko BOOT, až modrá LED začne rychle blikat. | 
				
			||||||
 | 
							', | 
				
			||||||
 | 
						'persist.confirm_restore' => 'Chcete obnovit všechna nastavení?', | 
				
			||||||
 | 
						'persist.confirm_restore_hard' => | 
				
			||||||
 | 
							'Opravdu chcete načíst tovární nastavení? Všechna nastavení kromě zálohy a systémového hesla | 
				
			||||||
 | 
							 budou přepsána, včetně nastavení WiFi!', | 
				
			||||||
 | 
						'persist.confirm_store_defaults' => | 
				
			||||||
 | 
							'Zadejte systémové heslo pro přepsání zálohy aktuálními parametry.', | 
				
			||||||
 | 
						'persist.password' => 'Systémové heslo:', | 
				
			||||||
 | 
						'persist.restore_defaults' => 'Obnovit ze zálohy', | 
				
			||||||
 | 
						'persist.write_defaults' => 'Zálohovat aktuální nastavení', | 
				
			||||||
 | 
						'persist.restore_hard' => 'Načíst tovární nastavení', | 
				
			||||||
 | 
						'persist.restore_hard_explain' => | 
				
			||||||
 | 
							'(Tímto vymažete nastavení WiFi! Záloha a systémové heslo zůstanou beze změny.)', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// UART settings form | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'uart.title' => 'Sériový port', | 
				
			||||||
 | 
						'uart.explain' => ' | 
				
			||||||
 | 
							Tímto formulářem můžete upravit nastavení komunikačního UARTu.  | 
				
			||||||
 | 
							Ladicí výpisy jsou na pinu P2 s~pevnými parametry: 115200 baud, 1 stop bit, žádná parita.  | 
				
			||||||
 | 
							', | 
				
			||||||
 | 
						'uart.baud' => 'Rychlost', | 
				
			||||||
 | 
						'uart.parity' => 'Parita', | 
				
			||||||
 | 
						'uart.parity.none' => 'Źádná', | 
				
			||||||
 | 
						'uart.parity.odd' => 'Lichá', | 
				
			||||||
 | 
						'uart.parity.even' => 'Sudá', | 
				
			||||||
 | 
						'uart.stop_bits' => 'Stop-bity', | 
				
			||||||
 | 
						'uart.stop_bits.one' => '1', | 
				
			||||||
 | 
						'uart.stop_bits.one_and_half' => '1.5', | 
				
			||||||
 | 
						'uart.stop_bits.two' => '2', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// HW tuning form | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'hwtuning.title' => 'Tuning hardwaru', | 
				
			||||||
 | 
						'hwtuning.explain' => ' | 
				
			||||||
 | 
							ESP8266 lze přetaktovat z~80~MHz na 160~MHz. Vyšší rychlost umožní rychlejší překreslování | 
				
			||||||
 | 
							obrazovky a stránky se budou načítat rychleji. Nevýhodou je vyšší spotřeba a citlivost k~rušení. | 
				
			||||||
 | 
							', | 
				
			||||||
 | 
						'hwtuning.overclock' => 'Přetaktovat na 160~MHz', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Generic button / dialog labels | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'apply' => 'Uložit!', | 
				
			||||||
 | 
						'start' => 'Start', | 
				
			||||||
 | 
						'cancel' => 'Zrušit', | 
				
			||||||
 | 
						'enabled' => 'Zapnuto', | 
				
			||||||
 | 
						'disabled' => 'Vypnuto', | 
				
			||||||
 | 
						'yes' => 'Ano', | 
				
			||||||
 | 
						'no' => 'Ne', | 
				
			||||||
 | 
						'confirm' => 'OK', | 
				
			||||||
 | 
						'copy' => 'Kopírovat', | 
				
			||||||
 | 
						'form_errors' => 'Neplatné hodnoty:', | 
				
			||||||
 | 
					]; | 
				
			||||||
@ -0,0 +1,270 @@ | 
				
			|||||||
 | 
					<?php | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					return [ | 
				
			||||||
 | 
						'menu.cfg_wifi' => 'WLAN-Einstellungen', | 
				
			||||||
 | 
						'menu.cfg_network' => 'Netzwerkeinstellungen', | 
				
			||||||
 | 
						'menu.cfg_term' => 'Terminaleinstellungen', | 
				
			||||||
 | 
						'menu.about' => 'Über ESPTerm', | 
				
			||||||
 | 
						'menu.help' => 'Schnellreferenz', | 
				
			||||||
 | 
						'menu.term' => 'Zurück zum Terminal', | 
				
			||||||
 | 
						'menu.cfg_system' => 'Systemeinstellungen', | 
				
			||||||
 | 
						'menu.cfg_wifi_conn' => 'Verbinden mit dem Netzwerk', | 
				
			||||||
 | 
						'menu.settings' => 'Einstellungen', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Terminal page | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'title.term' => 'Terminal', // page title of the terminal page | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'term_nav.fullscreen' => 'Vollbild', | 
				
			||||||
 | 
						'term_nav.config' => 'Konfiguration', | 
				
			||||||
 | 
						'term_nav.wifi' => 'WLAN', | 
				
			||||||
 | 
						'term_nav.help' => 'Hilfe', | 
				
			||||||
 | 
						'term_nav.about' => 'Info', | 
				
			||||||
 | 
						'term_nav.paste' => 'Einfügen', | 
				
			||||||
 | 
						'term_nav.upload' => 'Hochladen', | 
				
			||||||
 | 
						'term_nav.keybd' => 'Tastatur', | 
				
			||||||
 | 
						'term_nav.paste_prompt' => 'Text einfügen zum Versenden:', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'term_conn.connecting' => 'Verbinden', | 
				
			||||||
 | 
						'term_conn.waiting_content' => 'Warten auf Inhalt', | 
				
			||||||
 | 
						'term_conn.disconnected' => 'Nicht verbunden', | 
				
			||||||
 | 
						'term_conn.waiting_server' => 'Warten auf Server', | 
				
			||||||
 | 
						'term_conn.reconnecting' => 'Verbinden', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Terminal settings page | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'term.defaults' => 'Anfangseinstellungen', | 
				
			||||||
 | 
						'term.expert' => 'Expertenoptionen', | 
				
			||||||
 | 
						'term.explain_initials' => ' | 
				
			||||||
 | 
							Dies sind die Anfangseinstellungen, die benutzt werden, nachdem ESPTerm startet, | 
				
			||||||
 | 
							oder wenn der Bildschirm mit dem <code>\ec</code>-Kommando zurückgesetzt wird. | 
				
			||||||
 | 
							Sie können durch Escape-Sequenzen verändert werden. | 
				
			||||||
 | 
							', | 
				
			||||||
 | 
						'term.explain_expert' => ' | 
				
			||||||
 | 
							Dies sind erweiterte Konfigurationsoptionen, die meistens nicht verändert | 
				
			||||||
 | 
							werden müssen. Bearbeite sie nur, wenn du weißt, was du tust.', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'term.example' => 'Standardfarbenvorschau', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'term.explain_scheme' => ' | 
				
			||||||
 | 
							Um die Standardtextfarbe und Standardhintergrundfarbe auszuwählen, klicke auf | 
				
			||||||
 | 
							die Vorschaupalette, oder benutze die Zahlen 0-15 für die Themafarben, 16-255 | 
				
			||||||
 | 
							für Standardfarben, oder Hexadezimal (#FFFFFF) für True Color (24-bit). | 
				
			||||||
 | 
							', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'term.fgbg_presets' => 'Voreinstellungen', | 
				
			||||||
 | 
						'term.color_scheme' => 'Farbschema', | 
				
			||||||
 | 
						'term.reset_screen' => 'Bildschirm & Parser zurücksetzen', | 
				
			||||||
 | 
						'term.term_title' => 'Titeltext', | 
				
			||||||
 | 
						'term.term_width' => 'Breite', | 
				
			||||||
 | 
						'term.term_height' => 'Höhe', | 
				
			||||||
 | 
						'term.buttons' => 'Tastentext', | 
				
			||||||
 | 
						'term.theme' => 'Farbthema', | 
				
			||||||
 | 
						'term.cursor_shape' => 'Cursorstil', | 
				
			||||||
 | 
						'term.parser_tout_ms' => 'Parser-Auszeit', | 
				
			||||||
 | 
						'term.display_tout_ms' => 'Zeichenverzögerung', | 
				
			||||||
 | 
						'term.display_cooldown_ms' => 'Zeichenabkühlzeit', | 
				
			||||||
 | 
						'term.allow_decopt_12' => '\e?12h/l erlauben', | 
				
			||||||
 | 
						'term.fn_alt_mode' => 'SS3 Fn-Tasten', | 
				
			||||||
 | 
						'term.show_config_links' => 'Links anzeigen', | 
				
			||||||
 | 
						'term.show_buttons' => 'Tasten anzeigen', | 
				
			||||||
 | 
						'term.loopback' => 'Lokales Echo (<span style="text-decoration:overline">SRM</span>)', | 
				
			||||||
 | 
						'term.crlf_mode' => 'Enter = CR+LF (LNM)', | 
				
			||||||
 | 
						'term.want_all_fn' => 'F5, F11, F12 erfassen', | 
				
			||||||
 | 
						'term.button_msgs' => 'Tastencodes<br>(ASCII, dec, CSV)', | 
				
			||||||
 | 
						'term.color_fg' => 'Standardvordergr.', | 
				
			||||||
 | 
						'term.color_bg' => 'Standardhintergr.', | 
				
			||||||
 | 
						'term.color_fg_prev' => 'Vordergrund', | 
				
			||||||
 | 
						'term.color_bg_prev' => 'Hintergrund', | 
				
			||||||
 | 
						'term.colors_preview' => '', | 
				
			||||||
 | 
						'term.debugbar' => 'Debug-Leiste anzeigen', | 
				
			||||||
 | 
						'term.ascii_debug' => 'Kontrollcodes anzeigen', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'cursor.block_blink' => 'Block, blinkend', | 
				
			||||||
 | 
							'cursor.block_steady' => 'Block, ruhig', | 
				
			||||||
 | 
							'cursor.underline_blink' => 'Unterstrich, blinkend', | 
				
			||||||
 | 
							'cursor.underline_steady' => 'Unterstrich, ruhig', | 
				
			||||||
 | 
							'cursor.bar_blink' => 'Balken, blinkend', | 
				
			||||||
 | 
							'cursor.bar_steady' => 'Balken, ruhig', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Text upload dialog | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'upload.title' => 'Text Hochladen', | 
				
			||||||
 | 
						'upload.prompt' => 'Eine Textdatei laden:', | 
				
			||||||
 | 
						'upload.endings' => 'Zeilenumbruch:', | 
				
			||||||
 | 
						'upload.endings.cr' => 'CR (Enter-Taste)', | 
				
			||||||
 | 
						'upload.endings.crlf' => 'CR LF (Windows)', | 
				
			||||||
 | 
						'upload.endings.lf' => 'LF (Linux)', | 
				
			||||||
 | 
						'upload.chunk_delay' => 'Datenblockverzögerung (ms):', | 
				
			||||||
 | 
						'upload.chunk_size' => 'Datenblockgröße (0=Linie):', | 
				
			||||||
 | 
						'upload.progress' => 'Hochladen:', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Network config page | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'net.explain_sta' => ' | 
				
			||||||
 | 
							Schalte Dynamische IP aus um die statische IP-Addresse zu konfigurieren.', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'net.explain_ap' => ' | 
				
			||||||
 | 
							Diese Einstellungen beeinflussen den eingebauten DHCP-Server im AP-Modus.', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'net.ap_dhcp_time' => 'Leasezeit', | 
				
			||||||
 | 
						'net.ap_dhcp_start' => 'Pool Start-IP', | 
				
			||||||
 | 
						'net.ap_dhcp_end' => 'Pool End-IP', | 
				
			||||||
 | 
						'net.ap_addr_ip' => 'Eigene IP-Addresse', | 
				
			||||||
 | 
						'net.ap_addr_mask' => 'Subnet-Maske', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'net.sta_dhcp_enable' => 'Dynamische IP', | 
				
			||||||
 | 
						'net.sta_addr_ip' => 'ESPTerm statische IP', | 
				
			||||||
 | 
						'net.sta_addr_mask' => 'Subnet-Maske', | 
				
			||||||
 | 
						'net.sta_addr_gw' => 'Gateway-IP', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'net.ap' => 'DHCP Server (AP)', | 
				
			||||||
 | 
						'net.sta' => 'DHCP Client (Station)', | 
				
			||||||
 | 
						'net.sta_mac' => 'Station MAC', | 
				
			||||||
 | 
						'net.ap_mac' => 'AP MAC', | 
				
			||||||
 | 
						'net.details' => 'MAC-Addressen', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Wifi config page | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'wifi.ap' => 'Eingebauter Access Point', | 
				
			||||||
 | 
						'wifi.sta' => 'Bestehendes Netzwerk beitreten', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'wifi.enable' => 'Aktiviert', | 
				
			||||||
 | 
						'wifi.tpw' => 'Sendeleistung', | 
				
			||||||
 | 
						'wifi.ap_channel' => 'Kanal', | 
				
			||||||
 | 
						'wifi.ap_ssid' => 'AP SSID', | 
				
			||||||
 | 
						'wifi.ap_password' => 'Passwort', | 
				
			||||||
 | 
						'wifi.ap_hidden' => 'SSID verbergen', | 
				
			||||||
 | 
						'wifi.sta_info' => 'Ausgewählt', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'wifi.not_conn' => 'Nicht verbunden.', | 
				
			||||||
 | 
						'wifi.sta_none' => 'Keine', | 
				
			||||||
 | 
						'wifi.sta_active_pw' => '🔒 Passwort gespeichert', | 
				
			||||||
 | 
						'wifi.sta_active_nopw' => '🔓 Offen', | 
				
			||||||
 | 
						'wifi.connected_ip_is' => 'Verbunden, IP ist ', | 
				
			||||||
 | 
						'wifi.sta_password' => 'Passwort:', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'wifi.scanning' => 'Scannen', | 
				
			||||||
 | 
						'wifi.scan_now' => 'Klicke hier um zu scannen!', | 
				
			||||||
 | 
						'wifi.cant_scan_no_sta' => 'Klicke hier um Client-Modus zu aktivieren und zu scannen!', | 
				
			||||||
 | 
						'wifi.select_ssid' => 'Verfügbare Netzwerke:', | 
				
			||||||
 | 
						'wifi.enter_passwd' => 'Passwort für ":ssid:"', | 
				
			||||||
 | 
						'wifi.sta_explain' => | 
				
			||||||
 | 
							'Nach dem Auswählen eines Netzwerks, drücke Bestätigen, um dich zu verbinden.', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Wifi connecting status page | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'wificonn.status' => 'Status:', | 
				
			||||||
 | 
						'wificonn.back_to_config' => 'Zurück zur WLAN-Konfiguration', | 
				
			||||||
 | 
						'wificonn.telemetry_lost' => 'Telemetrie verloren; etwas lief schief, oder dein Gerät wurde getrennt.', | 
				
			||||||
 | 
						'wificonn.explain_android_sucks' => ' | 
				
			||||||
 | 
							Wenn du gerade ESPTerm mit einem Handy oder über ein anderes externes Netzwerk | 
				
			||||||
 | 
							konfigurierst, kann dein Gerät die Verbindung verlieren und diese Fortschrittsanzeige | 
				
			||||||
 | 
							wird nicht funktionieren. Bitte warte eine Weile (etwa 15 Sekunden) und prüfe dann, | 
				
			||||||
 | 
							ob die Verbindung gelangen ist.', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'wificonn.explain_reset' => ' | 
				
			||||||
 | 
							Um den eingebauten AP zur Aktivierung zu zwingen, halte den BOOT-Knopf gedrückt bis die | 
				
			||||||
 | 
							blaue LED beginnt, zu blinken. Halte ihn länger gedrückt (bis die LED schnell blinkt) | 
				
			||||||
 | 
							um eine "Werksrückstellung" zu vollziehen.', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'wificonn.disabled' => "Stationsmodus ist deaktiviert.", | 
				
			||||||
 | 
						'wificonn.idle' => "Nicht verbunden und ohne IP.", | 
				
			||||||
 | 
						'wificonn.success' => "Verbunden! Empfangene IP: ", | 
				
			||||||
 | 
						'wificonn.working' => "Verbinden mit dem ausgewählten AP", | 
				
			||||||
 | 
						'wificonn.fail' => "Verbindung fehlgeschlagen; prüfe die Einstellungen und versuche es erneut. Grund: ", | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Access restrictions form | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'pwlock.title' => 'Zugriffsbeschränkungen', | 
				
			||||||
 | 
						'pwlock.explain' => ' | 
				
			||||||
 | 
							Manche, oder alle Teile des Web-Interface können mit einem Passwort geschützt werden. | 
				
			||||||
 | 
							Lass die Passwortfelder leer wenn du es sie verändern möchtest.<br> | 
				
			||||||
 | 
							Das voreingestellte Passwort ist "%def_access_pw%".', | 
				
			||||||
 | 
						'pwlock.region' => 'Geschützte Seiten', | 
				
			||||||
 | 
						'pwlock.region.none' => 'Keine, alles offen', | 
				
			||||||
 | 
						'pwlock.region.settings_noterm' => 'WLAN-, Netzwerk- & Systemeinstellungen', | 
				
			||||||
 | 
						'pwlock.region.settings' => 'Alle Einstellungsseiten', | 
				
			||||||
 | 
						'pwlock.region.menus' => 'Dieser ganze Menüabschnitt', | 
				
			||||||
 | 
						'pwlock.region.all' => 'Alles, sogar das Terminal', | 
				
			||||||
 | 
						'pwlock.new_access_pw' => 'Neues Passwort', | 
				
			||||||
 | 
						'pwlock.new_access_pw2' => 'Wiederholen', | 
				
			||||||
 | 
						'pwlock.admin_pw' => 'Systempasswort', | 
				
			||||||
 | 
						'pwlock.access_name' => 'Benutzername', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Setting admin password | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'adminpw.title' => 'Systempasswort ändern', | 
				
			||||||
 | 
						'adminpw.explain' =>' | 
				
			||||||
 | 
							Das "Systempasswort" wird benutzt, um die gespeicherten Standardeinstellungen | 
				
			||||||
 | 
							und die Zugriffsbeschränkungen zu verändern. Dieses Passwort wird nicht als Teil | 
				
			||||||
 | 
							der Hauptkonfiguration gespeichert, d.h. Speichern / Wiederherstellen wird das | 
				
			||||||
 | 
							Passwort nicht beeinflussen. Wenn das Systempasswort vergessen wird, ist | 
				
			||||||
 | 
							die einfachste Weise, wieder Zugriff zu erhalten, ein Re-flash des Chips.<br> | 
				
			||||||
 | 
							Das voreingestellte Systempasswort ist "%def_admin_pw%". | 
				
			||||||
 | 
							', | 
				
			||||||
 | 
						'adminpw.new_admin_pw' => 'Neues Systempasswort', | 
				
			||||||
 | 
						'adminpw.new_admin_pw2' => 'Wiederholen', | 
				
			||||||
 | 
						'adminpw.old_admin_pw' => 'Altes Systempasswort', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Persist form | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'persist.title' => 'Speichern & Wiederherstellen', | 
				
			||||||
 | 
						'persist.explain' => ' | 
				
			||||||
 | 
							ESPTerm speichert alle Einstellungen im Flash-Speicher. Die aktiven Einstellungen | 
				
			||||||
 | 
							können in den “Voreinstellungsbereich” kopiert werden und später wiederhergestellt | 
				
			||||||
 | 
							werden mit der Taste unten.', | 
				
			||||||
 | 
						'persist.confirm_restore' => 'Alle Einstellungen zu den Voreinstellungen zurücksetzen?', | 
				
			||||||
 | 
						'persist.confirm_restore_hard' => ' | 
				
			||||||
 | 
							Zurücksetzen zu den Firmware-Voreinstellungen? Dies wird alle aktiven | 
				
			||||||
 | 
							Einstellungen zürucksetzen und den AP-Modus aktivieren mit der Standard-SSID.', | 
				
			||||||
 | 
						'persist.confirm_store_defaults' => | 
				
			||||||
 | 
							'Systempasswort eingeben um Voreinstellungen zu überschreiben', | 
				
			||||||
 | 
						'persist.password' => 'Systempasswort:', | 
				
			||||||
 | 
						'persist.restore_defaults' => 'Zu gespeicherten Voreinstellungen zurücksetzen', | 
				
			||||||
 | 
						'persist.write_defaults' => 'Aktive Einstellungen als Voreinstellungen speichern', | 
				
			||||||
 | 
						'persist.restore_hard' => 'Aktive Einstellungen zu Werkseinstellungen zurücksetzen', | 
				
			||||||
 | 
						'persist.restore_hard_explain' => ' | 
				
			||||||
 | 
							(Dies löscht die WLAN-Konfiguration! Beeinflusst die gespeicherten Voreinstellungen | 
				
			||||||
 | 
							oder das Systempasswort nicht.)', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// UART settings form | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'uart.title' => 'Serieller Port Parameter', | 
				
			||||||
 | 
						'uart.explain' => ' | 
				
			||||||
 | 
							Dies steuert den Kommunikations-UART. Der Debug-UART ist auf 115.200 baud fest | 
				
			||||||
 | 
							eingestellt mit einem Stop-Bit und keiner Parität. | 
				
			||||||
 | 
							', | 
				
			||||||
 | 
						'uart.baud' => 'Baudrate', | 
				
			||||||
 | 
						'uart.parity' => 'Parität', | 
				
			||||||
 | 
						'uart.parity.none' => 'Keine', | 
				
			||||||
 | 
						'uart.parity.odd' => 'Ungerade', | 
				
			||||||
 | 
						'uart.parity.even' => 'Gerade', | 
				
			||||||
 | 
						'uart.stop_bits' => 'Stop-Bits', | 
				
			||||||
 | 
						'uart.stop_bits.one' => 'Eins', | 
				
			||||||
 | 
						'uart.stop_bits.one_and_half' => 'Eineinhalb', | 
				
			||||||
 | 
						'uart.stop_bits.two' => 'Zwei', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// HW tuning form | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'hwtuning.title' => 'Hardware-Tuning', | 
				
			||||||
 | 
						'hwtuning.explain' => ' | 
				
			||||||
 | 
							ESP8266 kann übertaktet werden von 80 MHz auf 160 MHz. | 
				
			||||||
 | 
							Alles wird etwas schneller sein, aber mit höherem Stromverbrauch, | 
				
			||||||
 | 
							und eventuell auch mit höherer Interferenz. Mit Sorgfalt benutzen. | 
				
			||||||
 | 
							', | 
				
			||||||
 | 
						'hwtuning.overclock' => 'Übertakten', | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Generic button / dialog labels | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						'apply' => 'Bestätigen!', | 
				
			||||||
 | 
						'start' => 'Starten', | 
				
			||||||
 | 
						'cancel' => 'Abbrechen', | 
				
			||||||
 | 
						'enabled' => 'Aktiviert', | 
				
			||||||
 | 
						'disabled' => 'Deaktiviert', | 
				
			||||||
 | 
						'yes' => 'Ja', | 
				
			||||||
 | 
						'no' => 'Nein', | 
				
			||||||
 | 
						'confirm' => 'OK', | 
				
			||||||
 | 
						'copy' => 'Kopieren', | 
				
			||||||
 | 
						'form_errors' => 'Gültigkeitsfehler für:', | 
				
			||||||
 | 
					]; | 
				
			||||||
@ -0,0 +1,12 @@ | 
				
			|||||||
 | 
					// define language keys used by JS here
 | 
				
			||||||
 | 
					module.exports = [ | 
				
			||||||
 | 
					  'wifi.connected_ip_is', | 
				
			||||||
 | 
					  'wifi.not_conn', | 
				
			||||||
 | 
					  'wifi.enter_passwd', | 
				
			||||||
 | 
					  'term_nav.fullscreen', | 
				
			||||||
 | 
					  'term_conn.connecting', | 
				
			||||||
 | 
					  'term_conn.waiting_content', | 
				
			||||||
 | 
					  'term_conn.disconnected', | 
				
			||||||
 | 
					  'term_conn.waiting_server', | 
				
			||||||
 | 
					  'term_conn.reconnecting' | 
				
			||||||
 | 
					] | 
				
			||||||
@ -0,0 +1,92 @@ | 
				
			|||||||
 | 
					<div class="Box fold"> | 
				
			||||||
 | 
						<h2>Commands: Networking</h2> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						<div class="Row v"> | 
				
			||||||
 | 
							<p> | 
				
			||||||
 | 
								ESPTerm implements commands for device-to-device messaging and for requesting external | 
				
			||||||
 | 
								servers. This can be used e.g. for remote control, status reporting or data upload / download. | 
				
			||||||
 | 
							</p> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<p> | 
				
			||||||
 | 
								Networking commands use the format `\e^...\a`, a Privacy Message (PM). | 
				
			||||||
 | 
								PM is similar to OSC, which uses `]` in place of `^`. The PM payload (text between `\e^` and `\a`) | 
				
			||||||
 | 
								must be shorter than 256 bytes,	and should not contain any control characters (ASCII < 32). | 
				
			||||||
 | 
							</p> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<h3>Device-to-device Messaging</h3> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<p> | 
				
			||||||
 | 
								To send a message to another ESPTerm module, use: `\e^M;<i>DestIP</i>;<i>message</i>\a`. | 
				
			||||||
 | 
							</p> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<p> | 
				
			||||||
 | 
								This command sends a POST request to `http://<i><DestIP></i>/api/v1/msg`. | 
				
			||||||
 | 
								The IP address may be appended by a port, if needed (eg. :8080). In addition to POST, | 
				
			||||||
 | 
								a GET request can also be used. In that case, any GET arguments (`/api/v1/msg?<i>arguments</i>`) | 
				
			||||||
 | 
								will be used instead of the request body. This is intended for external access | 
				
			||||||
 | 
								when sending POST requests is not convenient. | 
				
			||||||
 | 
							</p> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<p> | 
				
			||||||
 | 
								Each ESPTerm listens for such requests and relays them to UART: | 
				
			||||||
 | 
								`\e^m;<i>SrcIP</i>;L=<i>length</i>;<i>message</i>\a`, with _length_ being the byte length of | 
				
			||||||
 | 
								_message_, as ASCII. | 
				
			||||||
 | 
							</p> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<p> | 
				
			||||||
 | 
								Notice a pattern with the first letter: capital is always a command, lower case a response. | 
				
			||||||
 | 
								This is followed with the HTTP commands and any networking commands added in the future. | 
				
			||||||
 | 
							</p> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<p> | 
				
			||||||
 | 
								*Example:* Node 192.168.0.10 sends a message to 192.168.0.19: `\e^M;192.168.0.19;Hello\a`. | 
				
			||||||
 | 
								Node 192.168.0.19 receives `\e^m;192.168.0.10;L=5;Hello\a` on the UART. Note that the IP | 
				
			||||||
 | 
								address in the reception message is that of the first node, thus it can be used to send a message back. | 
				
			||||||
 | 
							</p> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<h3>External HTTP requests</h3> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<p> | 
				
			||||||
 | 
								To request an external server, use `\e^H;<i>method</i>;<i>options</i>;<i>url</i>\n<i>body</i>\a`. | 
				
			||||||
 | 
							</p> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<ul> | 
				
			||||||
 | 
								<li>`_method_` - can be any usual HTTP verb, such as `GET`, `POST`, `PUT`, `HEAD`. | 
				
			||||||
 | 
								<li>`_options_` - is a comma-separated list of flags and parameters: | 
				
			||||||
 | 
									<ul> | 
				
			||||||
 | 
										<li>`H` - get response headers | 
				
			||||||
 | 
										<li>`B` - get response body | 
				
			||||||
 | 
										<li>`X` - ignore the response, return nothing | 
				
			||||||
 | 
										<li>`N=<i>nonce</i>` - a custom string that will be added in the options field of the response message. | 
				
			||||||
 | 
											Use this to keep track of which request a response belongs to. | 
				
			||||||
 | 
										<li>`T=<i>ms</i>` - request timeout (default 5000~ms), in milliseconds | 
				
			||||||
 | 
										<li>`L=<i>bytes</i>` - limit response length (default 0 = don't limit). Applies to the head, body, or both combined, depending on the `H` and `B` flags | 
				
			||||||
 | 
										<li>`l=<i>bytes</i>` - limit the response buffer size (default 5000~B). | 
				
			||||||
 | 
											This can reduce RAM usage, however it shouldn't be set too small, as this buffer | 
				
			||||||
 | 
											is used for both headers and the response body. | 
				
			||||||
 | 
									</ul> | 
				
			||||||
 | 
								<li>`_url_` - full request URL, including `http://`. Port may be specified if different from :80, | 
				
			||||||
 | 
									and GET arguments may be appended to the URL if needed. | 
				
			||||||
 | 
								<li>`_body_` - optional, separated from `_url_` by a single line feed character (`\n`). | 
				
			||||||
 | 
									This can be used for POST and PUT requests. Note: the command may be truncated to the | 
				
			||||||
 | 
									maximum total length of 256 characters if too long. | 
				
			||||||
 | 
							</ul> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<p>The response has the following format: `\e^h;<i>status</i>;<i>options</i>;<i>response</i>\a`</p> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<ul> | 
				
			||||||
 | 
								<li>`_status_` - a HTTP status code, eg. 200 is OK, 404 Not found. | 
				
			||||||
 | 
								<li>`_options_` - similar to those in the request, here describing the response data. | 
				
			||||||
 | 
									This field can contain comma-separated `B`, `H` and `L=<i>bytes</i>` and `N=<i>nonce</i>`. | 
				
			||||||
 | 
								<li>`_response_` - the response, as requested. If both headers and body are received, | 
				
			||||||
 | 
									they will be separated by an empty line (i.e. `\r\n\r\n`). Response can be up to several | 
				
			||||||
 | 
									kilobytes long, depending on the `L=` and `l=` options. | 
				
			||||||
 | 
							</ul> | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							<p> | 
				
			||||||
 | 
								*Example:* `\e^H;GET;B;http://wtfismyip.com/text\a` - get the body of a web page | 
				
			||||||
 | 
								(wtfismyip.com is a service that sends back your IP address). | 
				
			||||||
 | 
								A response could be `\e^h;200;B,L=11;80.70.60.50\a`. | 
				
			||||||
 | 
							</p> | 
				
			||||||
 | 
						</div> | 
				
			||||||
 | 
					</div> | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
					Loading…
					
					
				
		Reference in new issue