Compare commits
2 Commits
master
...
box-drawin
Author | SHA1 | Date |
---|---|---|
cpsdqs | a44eaa70e7 | 7 years ago |
cpsdqs | 8a732167c1 | 7 years ago |
@ -0,0 +1,28 @@ |
|||||||
|
<?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', |
||||||
|
'term_nav.fullscreen', |
||||||
|
'term_conn.connecting', |
||||||
|
'term_conn.waiting_content', |
||||||
|
'term_conn.disconnected', |
||||||
|
'term_conn.waiting_server', |
||||||
|
'term_conn.reconnecting' |
||||||
|
]; |
||||||
|
|
||||||
|
$out = []; |
||||||
|
foreach ($selected as $key) { |
||||||
|
$out[$key] = tr($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,5 +0,0 @@ |
|||||||
let data = require('locale-data') |
|
||||||
|
|
||||||
module.exports = function localize (key) { |
|
||||||
return data[key] || `?${key}?` |
|
||||||
} |
|
@ -1,539 +0,0 @@ |
|||||||
const { getColor } = require('./themes') |
|
||||||
const { |
|
||||||
ATTR_FG, |
|
||||||
ATTR_BG, |
|
||||||
ATTR_BOLD, |
|
||||||
ATTR_UNDERLINE, |
|
||||||
ATTR_BLINK, |
|
||||||
ATTR_ITALIC, |
|
||||||
ATTR_STRIKE, |
|
||||||
ATTR_OVERLINE, |
|
||||||
ATTR_FAINT, |
|
||||||
ATTR_FRAKTUR |
|
||||||
} = require('./screen_attr_bits') |
|
||||||
|
|
||||||
// debug toolbar, tooltip and screen
|
|
||||||
module.exports = function attachDebugger (screen, connection) { |
|
||||||
// debug screen overlay
|
|
||||||
const debugCanvas = document.createElement('canvas') |
|
||||||
debugCanvas.classList.add('debug-canvas') |
|
||||||
const ctx = debugCanvas.getContext('2d') |
|
||||||
|
|
||||||
// debug toolbar
|
|
||||||
const toolbar = document.createElement('div') |
|
||||||
toolbar.classList.add('debug-toolbar') |
|
||||||
|
|
||||||
// debug tooltip
|
|
||||||
const tooltip = document.createElement('div') |
|
||||||
tooltip.classList.add('debug-tooltip') |
|
||||||
tooltip.classList.add('hidden') |
|
||||||
|
|
||||||
// update functions, defined somewhere below
|
|
||||||
let updateTooltip |
|
||||||
let updateToolbar |
|
||||||
|
|
||||||
// tooltip cell
|
|
||||||
let selectedCell = null |
|
||||||
|
|
||||||
// update tooltip cell when mouse moves
|
|
||||||
const onMouseMove = (e) => { |
|
||||||
if (e.target !== screen.layout.canvas) { |
|
||||||
selectedCell = null |
|
||||||
return |
|
||||||
} |
|
||||||
selectedCell = screen.layout.screenToGrid(e.offsetX, e.offsetY) |
|
||||||
updateTooltip() |
|
||||||
} |
|
||||||
|
|
||||||
// hide tooltip when mouse leaves
|
|
||||||
const onMouseOut = (e) => { |
|
||||||
selectedCell = null |
|
||||||
tooltip.classList.add('hidden') |
|
||||||
} |
|
||||||
|
|
||||||
// updates debug canvas size
|
|
||||||
const updateCanvasSize = function () { |
|
||||||
let { width, height, devicePixelRatio } = screen.layout.window |
|
||||||
let cellSize = screen.layout.getCellSize() |
|
||||||
let padding = Math.round(screen.layout._padding) |
|
||||||
debugCanvas.width = (width * cellSize.width + 2 * padding) * devicePixelRatio |
|
||||||
debugCanvas.height = (height * cellSize.height + 2 * padding) * devicePixelRatio |
|
||||||
debugCanvas.style.width = `${width * cellSize.width + 2 * screen.layout._padding}px` |
|
||||||
debugCanvas.style.height = `${height * cellSize.height + 2 * screen.layout._padding}px` |
|
||||||
} |
|
||||||
|
|
||||||
// defined somewhere below
|
|
||||||
let startDrawLoop |
|
||||||
|
|
||||||
let screenAttached = false |
|
||||||
|
|
||||||
// node to which events were bound (kept here for when they need to be removed)
|
|
||||||
let eventNode |
|
||||||
|
|
||||||
// attaches/detaches debug screen overlay to/from DOM
|
|
||||||
const setScreenAttached = function (attached) { |
|
||||||
if (attached && !debugCanvas.parentNode) { |
|
||||||
screen.layout.canvas.parentNode.appendChild(debugCanvas) |
|
||||||
eventNode = debugCanvas.parentNode |
|
||||||
eventNode.addEventListener('mousemove', onMouseMove) |
|
||||||
eventNode.addEventListener('mouseout', onMouseOut) |
|
||||||
screen.layout.on('size-update', updateCanvasSize) |
|
||||||
updateCanvasSize() |
|
||||||
screenAttached = true |
|
||||||
startDrawLoop() |
|
||||||
} else if (!attached && debugCanvas.parentNode) { |
|
||||||
debugCanvas.parentNode.removeChild(debugCanvas) |
|
||||||
eventNode.removeEventListener('mousemove', onMouseMove) |
|
||||||
eventNode.removeEventListener('mouseout', onMouseOut) |
|
||||||
screen.layout.removeListener('size-update', updateCanvasSize) |
|
||||||
screenAttached = false |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// attaches/detaches toolbar and tooltip to/from DOM
|
|
||||||
const setToolbarAttached = function (attached) { |
|
||||||
if (attached && !toolbar.parentNode) { |
|
||||||
screen.layout.canvas.parentNode.appendChild(toolbar) |
|
||||||
screen.layout.canvas.parentNode.appendChild(tooltip) |
|
||||||
updateToolbar() |
|
||||||
} else if (!attached && toolbar.parentNode) { |
|
||||||
screen.layout.canvas.parentNode.removeChild(toolbar) |
|
||||||
screen.layout.canvas.parentNode.removeChild(tooltip) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// attach/detach toolbar when debug mode is enabled/disabled
|
|
||||||
screen.on('update-window:debug', enabled => { |
|
||||||
setToolbarAttached(enabled) |
|
||||||
}) |
|
||||||
|
|
||||||
// ditto ^
|
|
||||||
screen.layout.on('update-window:debug', enabled => { |
|
||||||
setScreenAttached(enabled) |
|
||||||
}) |
|
||||||
|
|
||||||
let drawData = { |
|
||||||
// last draw reason
|
|
||||||
reason: '', |
|
||||||
|
|
||||||
// when true, will show colored cell update overlays
|
|
||||||
showUpdates: false, |
|
||||||
|
|
||||||
// draw start time in milliseconds
|
|
||||||
startTime: 0, |
|
||||||
|
|
||||||
// end time
|
|
||||||
endTime: 0, |
|
||||||
|
|
||||||
// partial update frames
|
|
||||||
frames: [], |
|
||||||
|
|
||||||
// cell data
|
|
||||||
cells: new Map(), |
|
||||||
|
|
||||||
// scroll region
|
|
||||||
scrollRegion: null |
|
||||||
} |
|
||||||
|
|
||||||
// debug interface
|
|
||||||
screen._debug = screen.layout.renderer._debug = { |
|
||||||
drawStart (reason) { |
|
||||||
drawData.reason = reason |
|
||||||
drawData.startTime = window.performance.now() |
|
||||||
}, |
|
||||||
drawEnd () { |
|
||||||
drawData.endTime = window.performance.now() |
|
||||||
}, |
|
||||||
setCell (cell, flags) { |
|
||||||
drawData.cells.set(cell, [flags, window.performance.now()]) |
|
||||||
}, |
|
||||||
pushFrame (frame) { |
|
||||||
drawData.frames.push([...frame, window.performance.now()]) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
let isDrawing = false |
|
||||||
let drawLoop = function () { |
|
||||||
// draw while the screen is attached
|
|
||||||
if (screenAttached) window.requestAnimationFrame(drawLoop) |
|
||||||
else isDrawing = false |
|
||||||
|
|
||||||
let now = window.performance.now() |
|
||||||
|
|
||||||
let { width, height, devicePixelRatio } = screen.layout.window |
|
||||||
let padding = Math.round(screen.layout._padding) |
|
||||||
let cellSize = screen.layout.getCellSize() |
|
||||||
|
|
||||||
ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0) |
|
||||||
ctx.clearRect(0, 0, width * cellSize.width + 2 * padding, height * cellSize.height + 2 * padding) |
|
||||||
ctx.translate(padding, padding) |
|
||||||
|
|
||||||
ctx.lineWidth = 2 |
|
||||||
ctx.lineJoin = 'round' |
|
||||||
|
|
||||||
if (drawData.showUpdates) { |
|
||||||
const cells = drawData.cells |
|
||||||
for (let cell = 0; cell < width * height; cell++) { |
|
||||||
// cell does not exist or has no flags set
|
|
||||||
if (!cells.has(cell) || cells.get(cell)[0] === 0) continue |
|
||||||
|
|
||||||
const [flags, timestamp] = cells.get(cell) |
|
||||||
let elapsedTime = (now - timestamp) / 1000 |
|
||||||
|
|
||||||
if (elapsedTime > 1) { |
|
||||||
cells.delete(cell) |
|
||||||
continue |
|
||||||
} |
|
||||||
|
|
||||||
ctx.globalAlpha = 0.5 * Math.max(0, 1 - elapsedTime) |
|
||||||
|
|
||||||
let x = cell % width |
|
||||||
let y = Math.floor(cell / width) |
|
||||||
|
|
||||||
if (flags & 2) { |
|
||||||
// updated
|
|
||||||
ctx.fillStyle = '#0f0' |
|
||||||
} else if (flags & 1) { |
|
||||||
// redrawn
|
|
||||||
ctx.fillStyle = '#f0f' |
|
||||||
} |
|
||||||
|
|
||||||
if (!(flags & 4)) { |
|
||||||
// outside a clipped region
|
|
||||||
ctx.fillStyle = '#0ff' |
|
||||||
} |
|
||||||
|
|
||||||
if (flags & 16) { |
|
||||||
// was filled to speed up rendering
|
|
||||||
ctx.globalAlpha /= 2 |
|
||||||
} |
|
||||||
|
|
||||||
ctx.fillRect(x * cellSize.width, y * cellSize.height, cellSize.width, cellSize.height) |
|
||||||
|
|
||||||
if (flags & 8) { |
|
||||||
// wide cell
|
|
||||||
ctx.strokeStyle = '#f00' |
|
||||||
ctx.beginPath() |
|
||||||
ctx.moveTo(x * cellSize.width, (y + 1) * cellSize.height) |
|
||||||
ctx.lineTo((x + 1) * cellSize.width, (y + 1) * cellSize.height) |
|
||||||
ctx.stroke() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
let framesToDelete = [] |
|
||||||
for (let frame of drawData.frames) { |
|
||||||
let timestamp = frame[4] |
|
||||||
let elapsedTime = (now - timestamp) / 1000 |
|
||||||
if (elapsedTime > 1) framesToDelete.push(frame) |
|
||||||
else { |
|
||||||
ctx.globalAlpha = 1 - elapsedTime |
|
||||||
ctx.strokeStyle = '#ff0' |
|
||||||
ctx.strokeRect(frame[0] * cellSize.width, frame[1] * cellSize.height, |
|
||||||
frame[2] * cellSize.width, frame[3] * cellSize.height) |
|
||||||
} |
|
||||||
} |
|
||||||
for (let frame of framesToDelete) { |
|
||||||
drawData.frames.splice(drawData.frames.indexOf(frame), 1) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (selectedCell !== null) { |
|
||||||
// draw a dashed outline around the selected cell
|
|
||||||
let [x, y] = selectedCell |
|
||||||
|
|
||||||
ctx.save() |
|
||||||
ctx.globalAlpha = 0.5 |
|
||||||
ctx.lineWidth = 1 |
|
||||||
|
|
||||||
// draw X line
|
|
||||||
ctx.beginPath() |
|
||||||
ctx.moveTo(0, y * cellSize.height) |
|
||||||
ctx.lineTo(x * cellSize.width, y * cellSize.height) |
|
||||||
ctx.strokeStyle = '#f00' |
|
||||||
ctx.setLineDash([cellSize.width]) |
|
||||||
ctx.stroke() |
|
||||||
|
|
||||||
// draw Y line
|
|
||||||
ctx.beginPath() |
|
||||||
ctx.moveTo(x * cellSize.width, 0) |
|
||||||
ctx.lineTo(x * cellSize.width, y * cellSize.height) |
|
||||||
ctx.strokeStyle = '#0f0' |
|
||||||
ctx.setLineDash([cellSize.height]) |
|
||||||
ctx.stroke() |
|
||||||
|
|
||||||
ctx.globalAlpha = 1 |
|
||||||
ctx.lineWidth = 1 + 0.5 * Math.sin((now / 1000) * 10) |
|
||||||
ctx.strokeStyle = '#fff' |
|
||||||
ctx.lineJoin = 'round' |
|
||||||
ctx.setLineDash([2, 2]) |
|
||||||
ctx.lineDashOffset = (now / 1000) * 10 |
|
||||||
ctx.strokeRect(x * cellSize.width, y * cellSize.height, cellSize.width, cellSize.height) |
|
||||||
ctx.lineDashOffset += 2 |
|
||||||
ctx.strokeStyle = '#000' |
|
||||||
ctx.strokeRect(x * cellSize.width, y * cellSize.height, cellSize.width, cellSize.height) |
|
||||||
ctx.restore() |
|
||||||
} |
|
||||||
|
|
||||||
if (drawData.scrollRegion !== null) { |
|
||||||
// draw two lines marking the scroll region bounds
|
|
||||||
let [start, end] = drawData.scrollRegion |
|
||||||
|
|
||||||
ctx.save() |
|
||||||
ctx.globalAlpha = 1 |
|
||||||
ctx.strokeStyle = '#00f' |
|
||||||
ctx.lineWidth = 2 |
|
||||||
ctx.setLineDash([2, 2]) |
|
||||||
|
|
||||||
ctx.beginPath() |
|
||||||
ctx.moveTo(0, start * cellSize.height) |
|
||||||
ctx.lineTo(width * cellSize.width, start * cellSize.height) |
|
||||||
ctx.stroke() |
|
||||||
|
|
||||||
ctx.beginPath() |
|
||||||
ctx.moveTo(0, (end + 1) * cellSize.height) |
|
||||||
ctx.lineTo(width * cellSize.width, (end + 1) * cellSize.height) |
|
||||||
ctx.stroke() |
|
||||||
|
|
||||||
ctx.restore() |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
startDrawLoop = function () { |
|
||||||
if (isDrawing) return |
|
||||||
isDrawing = true |
|
||||||
drawLoop() |
|
||||||
} |
|
||||||
|
|
||||||
let pad2 = i => ('00' + i.toString()).substr(-2) |
|
||||||
let formatColor = color => color < 256 |
|
||||||
? color.toString() |
|
||||||
: '#' + pad2(color >> 16) + pad2((color >> 8) & 0xFF) + pad2(color & 0xFF) |
|
||||||
|
|
||||||
let makeSpan = (text, styles) => { |
|
||||||
let span = document.createElement('span') |
|
||||||
span.textContent = text |
|
||||||
Object.assign(span.style, styles || {}) |
|
||||||
return span |
|
||||||
} |
|
||||||
let formatAttributes = (target, attrs) => { |
|
||||||
if (attrs & ATTR_FG) target.appendChild(makeSpan('HasFG')) |
|
||||||
if (attrs & ATTR_BG) target.appendChild(makeSpan('HasBG')) |
|
||||||
if (attrs & ATTR_BOLD) target.appendChild(makeSpan('Bold', { fontWeight: 'bold' })) |
|
||||||
if (attrs & ATTR_UNDERLINE) target.appendChild(makeSpan('Uline', { textDecoration: 'underline' })) |
|
||||||
if (attrs & ATTR_BLINK) target.appendChild(makeSpan('Blink')) |
|
||||||
if (attrs & ATTR_ITALIC) target.appendChild(makeSpan('Italic', { fontStyle: 'italic' })) |
|
||||||
if (attrs & ATTR_STRIKE) target.appendChild(makeSpan('Strike', { textDecoration: 'line-through' })) |
|
||||||
if (attrs & ATTR_OVERLINE) target.appendChild(makeSpan('Oline', { textDecoration: 'overline' })) |
|
||||||
if (attrs & ATTR_FAINT) target.appendChild(makeSpan('Faint', { opacity: 0.5 })) |
|
||||||
if (attrs & ATTR_FRAKTUR) target.appendChild(makeSpan('Fraktur')) |
|
||||||
} |
|
||||||
|
|
||||||
updateTooltip = function () { |
|
||||||
// TODO: make this not destroy and recreate the same nodes every time
|
|
||||||
tooltip.classList.remove('hidden') |
|
||||||
tooltip.innerHTML = '' |
|
||||||
let cell = selectedCell[1] * screen.window.width + selectedCell[0] |
|
||||||
if (!screen.screen[cell]) return |
|
||||||
|
|
||||||
let foreground = document.createElement('span') |
|
||||||
foreground.textContent = formatColor(screen.screenFG[cell]) |
|
||||||
let preview = document.createElement('span') |
|
||||||
preview.textContent = ' ●' |
|
||||||
preview.style.color = getColor(screen.screenFG[cell], screen.layout.renderer.palette) |
|
||||||
foreground.appendChild(preview) |
|
||||||
|
|
||||||
let background = document.createElement('span') |
|
||||||
background.textContent = formatColor(screen.screenBG[cell]) |
|
||||||
let bgPreview = document.createElement('span') |
|
||||||
bgPreview.textContent = ' ●' |
|
||||||
bgPreview.style.color = getColor(screen.screenBG[cell], screen.layout.renderer.palette) |
|
||||||
background.appendChild(bgPreview) |
|
||||||
|
|
||||||
let character = screen.screen[cell] |
|
||||||
let codePoint = character.codePointAt(0) |
|
||||||
let formattedCodePoint = codePoint.toString(16).length <= 4 |
|
||||||
? `0000${codePoint.toString(16)}`.substr(-4) |
|
||||||
: codePoint.toString(16) |
|
||||||
|
|
||||||
let attributes = document.createElement('span') |
|
||||||
attributes.classList.add('attributes') |
|
||||||
formatAttributes(attributes, screen.screenAttrs[cell]) |
|
||||||
|
|
||||||
let data = { |
|
||||||
Cell: `col ${selectedCell[0] + 1}, ln ${selectedCell[1] + 1} (${cell})`, |
|
||||||
Foreground: foreground, |
|
||||||
Background: background, |
|
||||||
Character: `U+${formattedCodePoint}`, |
|
||||||
Attributes: attributes |
|
||||||
} |
|
||||||
|
|
||||||
let table = document.createElement('table') |
|
||||||
|
|
||||||
for (let name in data) { |
|
||||||
let row = document.createElement('tr') |
|
||||||
let label = document.createElement('td') |
|
||||||
label.appendChild(new window.Text(name)) |
|
||||||
label.classList.add('label') |
|
||||||
|
|
||||||
let value = document.createElement('td') |
|
||||||
value.appendChild(typeof data[name] === 'string' ? new window.Text(data[name]) : data[name]) |
|
||||||
value.classList.add('value') |
|
||||||
|
|
||||||
row.appendChild(label) |
|
||||||
row.appendChild(value) |
|
||||||
table.appendChild(row) |
|
||||||
} |
|
||||||
|
|
||||||
tooltip.appendChild(table) |
|
||||||
|
|
||||||
let cellSize = screen.layout.getCellSize() |
|
||||||
// add 3 to the position because for some reason the corner is off
|
|
||||||
let posX = (selectedCell[0] + 1) * cellSize.width + 3 |
|
||||||
let posY = (selectedCell[1] + 1) * cellSize.height + 3 |
|
||||||
tooltip.style.transform = `translate(${posX}px, ${posY}px)` |
|
||||||
} |
|
||||||
|
|
||||||
let toolbarData = null |
|
||||||
let toolbarNodes = {} |
|
||||||
|
|
||||||
// construct the toolbar if it wasn't already
|
|
||||||
const initToolbar = function () { |
|
||||||
if (toolbarData) return |
|
||||||
|
|
||||||
let showUpdates = document.createElement('input') |
|
||||||
showUpdates.type = 'checkbox' |
|
||||||
showUpdates.addEventListener('change', e => { |
|
||||||
drawData.showUpdates = showUpdates.checked |
|
||||||
}) |
|
||||||
|
|
||||||
let fancyGraphics = document.createElement('input') |
|
||||||
fancyGraphics.type = 'checkbox' |
|
||||||
fancyGraphics.value = !!screen.layout.renderer.graphics |
|
||||||
fancyGraphics.addEventListener('change', e => { |
|
||||||
screen.layout.renderer.graphics = +fancyGraphics.checked |
|
||||||
}) |
|
||||||
|
|
||||||
toolbarData = { |
|
||||||
cursor: { |
|
||||||
title: 'Cursor', |
|
||||||
Position: '', |
|
||||||
Style: '', |
|
||||||
Visible: true, |
|
||||||
Hanging: false |
|
||||||
}, |
|
||||||
internal: { |
|
||||||
Flags: '', |
|
||||||
'Cursor Attributes': '', |
|
||||||
'Code Page': '', |
|
||||||
Heap: 0, |
|
||||||
Clients: 0 |
|
||||||
}, |
|
||||||
drawing: { |
|
||||||
title: 'Drawing', |
|
||||||
'Last Update': '', |
|
||||||
'Show Updates': showUpdates, |
|
||||||
'Fancy Graphics': fancyGraphics, |
|
||||||
'Redraw Screen': () => { |
|
||||||
screen.layout.renderer.resetDrawn() |
|
||||||
screen.layout.renderer.draw('debug-redraw') |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
for (let i in toolbarData) { |
|
||||||
let group = toolbarData[i] |
|
||||||
let table = document.createElement('table') |
|
||||||
table.classList.add('toolbar-group') |
|
||||||
|
|
||||||
toolbarNodes[i] = {} |
|
||||||
|
|
||||||
for (let key in group) { |
|
||||||
let item = document.createElement('tr') |
|
||||||
let name = document.createElement('td') |
|
||||||
name.classList.add('name') |
|
||||||
let value = document.createElement('td') |
|
||||||
value.classList.add('value') |
|
||||||
|
|
||||||
toolbarNodes[i][key] = { name, value } |
|
||||||
|
|
||||||
if (key === 'title') { |
|
||||||
name.textContent = group[key] |
|
||||||
name.classList.add('title') |
|
||||||
} else { |
|
||||||
name.textContent = key |
|
||||||
if (group[key] instanceof Function) { |
|
||||||
name.textContent = '' |
|
||||||
let button = document.createElement('button') |
|
||||||
name.classList.add('has-button') |
|
||||||
name.appendChild(button) |
|
||||||
button.textContent = key |
|
||||||
button.addEventListener('click', e => group[key](e)) |
|
||||||
} else if (group[key] instanceof window.Node) value.appendChild(group[key]) |
|
||||||
else value.textContent = group[key] |
|
||||||
} |
|
||||||
|
|
||||||
item.appendChild(name) |
|
||||||
item.appendChild(value) |
|
||||||
table.appendChild(item) |
|
||||||
} |
|
||||||
|
|
||||||
toolbar.appendChild(table) |
|
||||||
} |
|
||||||
|
|
||||||
let heartbeat = toolbarNodes.heartbeat = document.createElement('div') |
|
||||||
heartbeat.classList.add('heartbeat') |
|
||||||
heartbeat.textContent = '❤' |
|
||||||
toolbar.appendChild(heartbeat) |
|
||||||
} |
|
||||||
|
|
||||||
connection.on('heartbeat', () => { |
|
||||||
if (screenAttached && toolbarNodes.heartbeat) { |
|
||||||
toolbarNodes.heartbeat.classList.remove('beat') |
|
||||||
window.requestAnimationFrame(() => { |
|
||||||
toolbarNodes.heartbeat.classList.add('beat') |
|
||||||
}) |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
updateToolbar = function () { |
|
||||||
initToolbar() |
|
||||||
|
|
||||||
Object.assign(toolbarData.cursor, { |
|
||||||
Position: `col ${screen.cursor.x + 1}, ln ${screen.cursor.y + 1}`, |
|
||||||
Style: screen.cursor.style + (screen.cursor.blinking ? ', blink' : ''), |
|
||||||
Visible: screen.cursor.visible, |
|
||||||
Hanging: screen.cursor.hanging |
|
||||||
}) |
|
||||||
|
|
||||||
let drawTime = Math.round((drawData.endTime - drawData.startTime) * 100) / 100 |
|
||||||
toolbarData.drawing['Last Update'] = `${drawData.reason} (${drawTime}ms)` |
|
||||||
toolbarData.drawing['Fancy Graphics'].checked = !!screen.layout.renderer.graphics |
|
||||||
|
|
||||||
for (let i in toolbarData) { |
|
||||||
let group = toolbarData[i] |
|
||||||
let nodes = toolbarNodes[i] |
|
||||||
for (let key in group) { |
|
||||||
if (key === 'title') continue |
|
||||||
let value = nodes[key].value |
|
||||||
if (!(group[key] instanceof window.Node) && !(group[key] instanceof Function)) { |
|
||||||
value.textContent = group[key] |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
screen.on('update', updateToolbar) |
|
||||||
screen.on('internal', data => { |
|
||||||
if (screenAttached && toolbarData) { |
|
||||||
Object.assign(toolbarData.internal, { |
|
||||||
Flags: data.flags.toString(2), |
|
||||||
'Cursor Attributes': data.cursorAttrs.toString(2), |
|
||||||
'Code Page': `${data.charsetGx} (${data.charsetG0}, ${data.charsetG1})`, |
|
||||||
Heap: data.freeHeap, |
|
||||||
Clients: data.clientCount |
|
||||||
}) |
|
||||||
drawData.scrollRegion = [data.regionStart, data.regionEnd] |
|
||||||
updateToolbar() |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
@ -0,0 +1,381 @@ |
|||||||
|
const { mk } = require('../utils') |
||||||
|
|
||||||
|
module.exports = function attachDebugScreen (screen) { |
||||||
|
const debugCanvas = mk('canvas') |
||||||
|
const ctx = debugCanvas.getContext('2d') |
||||||
|
|
||||||
|
debugCanvas.classList.add('debug-canvas') |
||||||
|
|
||||||
|
let mouseHoverCell = null |
||||||
|
let updateToolbar |
||||||
|
|
||||||
|
let onMouseMove = e => { |
||||||
|
mouseHoverCell = screen.screenToGrid(e.offsetX, e.offsetY) |
||||||
|
startDrawing() |
||||||
|
updateToolbar() |
||||||
|
} |
||||||
|
let onMouseOut = () => (mouseHoverCell = null) |
||||||
|
|
||||||
|
let addCanvas = function () { |
||||||
|
if (!debugCanvas.parentNode) { |
||||||
|
screen.canvas.parentNode.appendChild(debugCanvas) |
||||||
|
screen.canvas.addEventListener('mousemove', onMouseMove) |
||||||
|
screen.canvas.addEventListener('mouseout', onMouseOut) |
||||||
|
} |
||||||
|
} |
||||||
|
let removeCanvas = function () { |
||||||
|
if (debugCanvas.parentNode) { |
||||||
|
debugCanvas.parentNode.removeChild(debugCanvas) |
||||||
|
screen.canvas.removeEventListener('mousemove', onMouseMove) |
||||||
|
screen.canvas.removeEventListener('mouseout', onMouseOut) |
||||||
|
onMouseOut() |
||||||
|
} |
||||||
|
} |
||||||
|
let updateCanvasSize = function () { |
||||||
|
let { width, height, devicePixelRatio } = screen.window |
||||||
|
let cellSize = screen.getCellSize() |
||||||
|
debugCanvas.width = width * cellSize.width * devicePixelRatio |
||||||
|
debugCanvas.height = height * cellSize.height * devicePixelRatio |
||||||
|
debugCanvas.style.width = `${width * cellSize.width}px` |
||||||
|
debugCanvas.style.height = `${height * cellSize.height}px` |
||||||
|
} |
||||||
|
|
||||||
|
let drawInfo = mk('div') |
||||||
|
drawInfo.classList.add('draw-info') |
||||||
|
|
||||||
|
let startTime, endTime, lastReason |
||||||
|
let cells = new Map() |
||||||
|
let clippedRects = [] |
||||||
|
let updateFrames = [] |
||||||
|
|
||||||
|
let startDrawing |
||||||
|
|
||||||
|
screen._debug = { |
||||||
|
drawStart (reason) { |
||||||
|
lastReason = reason |
||||||
|
startTime = Date.now() |
||||||
|
clippedRects = [] |
||||||
|
}, |
||||||
|
drawEnd () { |
||||||
|
endTime = Date.now() |
||||||
|
console.log(drawInfo.textContent = `Draw: ${lastReason} (${(endTime - startTime)} ms) with graphics=${screen.window.graphics}`) |
||||||
|
startDrawing() |
||||||
|
}, |
||||||
|
setCell (cell, flags) { |
||||||
|
cells.set(cell, [flags, Date.now()]) |
||||||
|
}, |
||||||
|
clipRect (...args) { |
||||||
|
clippedRects.push(args) |
||||||
|
}, |
||||||
|
pushFrame (frame) { |
||||||
|
frame.push(Date.now()) |
||||||
|
updateFrames.push(frame) |
||||||
|
startDrawing() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
let clipPattern |
||||||
|
{ |
||||||
|
let patternCanvas = document.createElement('canvas') |
||||||
|
patternCanvas.width = patternCanvas.height = 12 |
||||||
|
let pctx = patternCanvas.getContext('2d') |
||||||
|
pctx.lineWidth = 1 |
||||||
|
pctx.strokeStyle = '#00f' |
||||||
|
pctx.beginPath() |
||||||
|
pctx.moveTo(0, 0) |
||||||
|
pctx.lineTo(0 - 4, 12) |
||||||
|
pctx.moveTo(4, 0) |
||||||
|
pctx.lineTo(4 - 4, 12) |
||||||
|
pctx.moveTo(8, 0) |
||||||
|
pctx.lineTo(8 - 4, 12) |
||||||
|
pctx.moveTo(12, 0) |
||||||
|
pctx.lineTo(12 - 4, 12) |
||||||
|
pctx.moveTo(16, 0) |
||||||
|
pctx.lineTo(16 - 4, 12) |
||||||
|
pctx.stroke() |
||||||
|
clipPattern = ctx.createPattern(patternCanvas, 'repeat') |
||||||
|
} |
||||||
|
|
||||||
|
let isDrawing = false |
||||||
|
let lastDrawTime = 0 |
||||||
|
let t = 0 |
||||||
|
|
||||||
|
let drawLoop = function () { |
||||||
|
if (isDrawing) window.requestAnimationFrame(drawLoop) |
||||||
|
|
||||||
|
let dt = (Date.now() - lastDrawTime) / 1000 |
||||||
|
lastDrawTime = Date.now() |
||||||
|
t += dt |
||||||
|
|
||||||
|
let { devicePixelRatio, width, height } = screen.window |
||||||
|
let { width: cellWidth, height: cellHeight } = screen.getCellSize() |
||||||
|
let screenLength = width * height |
||||||
|
let now = Date.now() |
||||||
|
|
||||||
|
ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0) |
||||||
|
ctx.clearRect(0, 0, width * cellWidth, height * cellHeight) |
||||||
|
|
||||||
|
let activeCells = 0 |
||||||
|
for (let cell = 0; cell < screenLength; cell++) { |
||||||
|
if (!cells.has(cell) || cells.get(cell)[0] === 0) continue |
||||||
|
|
||||||
|
let [flags, timestamp] = cells.get(cell) |
||||||
|
let elapsedTime = (now - timestamp) / 1000 |
||||||
|
|
||||||
|
if (elapsedTime > 1) continue |
||||||
|
|
||||||
|
activeCells++ |
||||||
|
ctx.globalAlpha = 0.5 * Math.max(0, 1 - elapsedTime) |
||||||
|
|
||||||
|
let x = cell % width |
||||||
|
let y = Math.floor(cell / width) |
||||||
|
|
||||||
|
if (flags & 1) { |
||||||
|
// redrawn
|
||||||
|
ctx.fillStyle = '#f0f' |
||||||
|
} |
||||||
|
if (flags & 2) { |
||||||
|
// updated
|
||||||
|
ctx.fillStyle = '#0f0' |
||||||
|
} |
||||||
|
|
||||||
|
ctx.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight) |
||||||
|
|
||||||
|
if (flags & 4) { |
||||||
|
// wide cell
|
||||||
|
ctx.lineWidth = 2 |
||||||
|
ctx.strokeStyle = '#f00' |
||||||
|
ctx.strokeRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (clippedRects.length) { |
||||||
|
ctx.globalAlpha = 0.5 |
||||||
|
ctx.beginPath() |
||||||
|
|
||||||
|
for (let rect of clippedRects) { |
||||||
|
ctx.rect(...rect) |
||||||
|
} |
||||||
|
|
||||||
|
ctx.fillStyle = clipPattern |
||||||
|
ctx.fill() |
||||||
|
} |
||||||
|
|
||||||
|
let didDrawUpdateFrames = false |
||||||
|
if (updateFrames.length) { |
||||||
|
let framesToDelete = [] |
||||||
|
for (let frame of updateFrames) { |
||||||
|
let time = frame[4] |
||||||
|
let elapsed = Date.now() - time |
||||||
|
if (elapsed > 1000) framesToDelete.push(frame) |
||||||
|
else { |
||||||
|
didDrawUpdateFrames = true |
||||||
|
ctx.globalAlpha = 1 - elapsed / 1000 |
||||||
|
ctx.strokeStyle = '#ff0' |
||||||
|
ctx.lineWidth = 2 |
||||||
|
ctx.strokeRect(frame[0] * cellWidth, frame[1] * cellHeight, frame[2] * cellWidth, frame[3] * cellHeight) |
||||||
|
} |
||||||
|
} |
||||||
|
for (let frame of framesToDelete) { |
||||||
|
updateFrames.splice(updateFrames.indexOf(frame), 1) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (mouseHoverCell) { |
||||||
|
ctx.save() |
||||||
|
ctx.globalAlpha = 1 |
||||||
|
ctx.lineWidth = 1 + 0.5 * Math.sin(t * 10) |
||||||
|
ctx.strokeStyle = '#fff' |
||||||
|
ctx.lineJoin = 'round' |
||||||
|
ctx.setLineDash([2, 2]) |
||||||
|
ctx.lineDashOffset = t * 10 |
||||||
|
ctx.strokeRect(mouseHoverCell[0] * cellWidth, mouseHoverCell[1] * cellHeight, cellWidth, cellHeight) |
||||||
|
ctx.lineDashOffset += 2 |
||||||
|
ctx.strokeStyle = '#000' |
||||||
|
ctx.strokeRect(mouseHoverCell[0] * cellWidth, mouseHoverCell[1] * cellHeight, cellWidth, cellHeight) |
||||||
|
ctx.restore() |
||||||
|
} |
||||||
|
|
||||||
|
if (activeCells === 0 && !mouseHoverCell && !didDrawUpdateFrames) { |
||||||
|
isDrawing = false |
||||||
|
removeCanvas() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
startDrawing = function () { |
||||||
|
if (isDrawing) return |
||||||
|
addCanvas() |
||||||
|
updateCanvasSize() |
||||||
|
isDrawing = true |
||||||
|
lastDrawTime = Date.now() |
||||||
|
drawLoop() |
||||||
|
} |
||||||
|
|
||||||
|
// debug toolbar
|
||||||
|
const toolbar = mk('div') |
||||||
|
toolbar.classList.add('debug-toolbar') |
||||||
|
let toolbarAttached = false |
||||||
|
const dataDisplay = mk('div') |
||||||
|
dataDisplay.classList.add('data-display') |
||||||
|
toolbar.appendChild(dataDisplay) |
||||||
|
const internalDisplay = mk('div') |
||||||
|
internalDisplay.classList.add('internal-display') |
||||||
|
toolbar.appendChild(internalDisplay) |
||||||
|
toolbar.appendChild(drawInfo) |
||||||
|
const buttons = mk('div') |
||||||
|
buttons.classList.add('toolbar-buttons') |
||||||
|
toolbar.appendChild(buttons) |
||||||
|
|
||||||
|
{ |
||||||
|
const redraw = mk('button') |
||||||
|
redraw.textContent = 'Redraw' |
||||||
|
redraw.addEventListener('click', e => { |
||||||
|
screen.renderer.resetDrawn() |
||||||
|
screen.renderer.draw('debug-redraw') |
||||||
|
}) |
||||||
|
buttons.appendChild(redraw) |
||||||
|
|
||||||
|
const fancyGraphics = mk('button') |
||||||
|
fancyGraphics.textContent = 'Toggle Graphics' |
||||||
|
fancyGraphics.addEventListener('click', e => { |
||||||
|
screen.window.graphics = +!screen.window.graphics |
||||||
|
}) |
||||||
|
buttons.appendChild(fancyGraphics) |
||||||
|
} |
||||||
|
|
||||||
|
const attachToolbar = function () { |
||||||
|
screen.canvas.parentNode.appendChild(toolbar) |
||||||
|
} |
||||||
|
const detachToolbar = function () { |
||||||
|
toolbar.parentNode.removeChild(toolbar) |
||||||
|
} |
||||||
|
|
||||||
|
screen.on('update-window:debug', debug => { |
||||||
|
if (debug !== toolbarAttached) { |
||||||
|
toolbarAttached = debug |
||||||
|
if (debug) attachToolbar() |
||||||
|
else { |
||||||
|
detachToolbar() |
||||||
|
removeCanvas() |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
const displayCellAttrs = attrs => { |
||||||
|
let result = attrs.toString(16) |
||||||
|
if (attrs & 1 || attrs & 2) { |
||||||
|
result += ':has(' |
||||||
|
if (attrs & 1) result += 'fg' |
||||||
|
if (attrs & 2) result += (attrs & 1 ? ',' : '') + 'bg' |
||||||
|
result += ')' |
||||||
|
} |
||||||
|
let attributes = [] |
||||||
|
if (attrs & (1 << 2)) attributes.push('\\[bold]bold\\()') |
||||||
|
if (attrs & (1 << 3)) attributes.push('\\[underline]underln\\()') |
||||||
|
if (attrs & (1 << 4)) attributes.push('\\[invert]invert\\()') |
||||||
|
if (attrs & (1 << 5)) attributes.push('blink') |
||||||
|
if (attrs & (1 << 6)) attributes.push('\\[italic]italic\\()') |
||||||
|
if (attrs & (1 << 7)) attributes.push('\\[strike]strike\\()') |
||||||
|
if (attrs & (1 << 8)) attributes.push('\\[overline]overln\\()') |
||||||
|
if (attrs & (1 << 9)) attributes.push('\\[faint]faint\\()') |
||||||
|
if (attrs & (1 << 10)) attributes.push('fraktur') |
||||||
|
if (attributes.length) result += ':' + attributes.join() |
||||||
|
return result.trim() |
||||||
|
} |
||||||
|
|
||||||
|
const formatColor = color => color < 256 ? color : `#${`000000${(color - 256).toString(16)}`.substr(-6)}` |
||||||
|
const getCellData = cell => { |
||||||
|
if (cell < 0 || cell > screen.screen.length) return '(-)' |
||||||
|
let cellAttrs = screen.renderer.drawnScreenAttrs[cell] | 0 |
||||||
|
let cellFG = screen.renderer.drawnScreenFG[cell] | 0 |
||||||
|
let cellBG = screen.renderer.drawnScreenBG[cell] | 0 |
||||||
|
let fgText = formatColor(cellFG) |
||||||
|
let bgText = formatColor(cellBG) |
||||||
|
fgText += `\\[color=${screen.renderer.getColor(cellFG).replace(/ /g, '')}]●\\[]` |
||||||
|
bgText += `\\[color=${screen.renderer.getColor(cellBG).replace(/ /g, '')}]●\\[]` |
||||||
|
let cellCode = (screen.renderer.drawnScreen[cell] || '').codePointAt(0) | 0 |
||||||
|
let hexcode = cellCode.toString(16).toUpperCase() |
||||||
|
if (hexcode.length < 4) hexcode = `0000${hexcode}`.substr(-4) |
||||||
|
hexcode = `U+${hexcode}` |
||||||
|
let x = cell % screen.window.width |
||||||
|
let y = Math.floor(cell / screen.window.width) |
||||||
|
return `((${y},${x})=${cell}:\\[bold]${hexcode}\\[]:F${fgText}:B${bgText}:A(${displayCellAttrs(cellAttrs)}))` |
||||||
|
} |
||||||
|
|
||||||
|
const setFormattedText = (node, text) => { |
||||||
|
node.innerHTML = '' |
||||||
|
|
||||||
|
let match |
||||||
|
let attrs = {} |
||||||
|
|
||||||
|
let pushSpan = content => { |
||||||
|
let span = mk('span') |
||||||
|
node.appendChild(span) |
||||||
|
span.textContent = content |
||||||
|
for (let key in attrs) span[key] = attrs[key] |
||||||
|
} |
||||||
|
|
||||||
|
while ((match = text.match(/\\\[(.*?)\]/))) { |
||||||
|
if (match.index > 0) pushSpan(text.substr(0, match.index)) |
||||||
|
|
||||||
|
attrs = { style: '' } |
||||||
|
let data = match[1].split(' ') |
||||||
|
for (let attr of data) { |
||||||
|
if (!attr) continue |
||||||
|
let key, value |
||||||
|
if (attr.indexOf('=') > -1) { |
||||||
|
key = attr.substr(0, attr.indexOf('=')) |
||||||
|
value = attr.substr(attr.indexOf('=') + 1) |
||||||
|
} else { |
||||||
|
key = attr |
||||||
|
value = true |
||||||
|
} |
||||||
|
|
||||||
|
if (key === 'color') console.log(value) |
||||||
|
|
||||||
|
if (key === 'bold') attrs.style += 'font-weight:bold;' |
||||||
|
if (key === 'italic') attrs.style += 'font-style:italic;' |
||||||
|
if (key === 'underline') attrs.style += 'text-decoration:underline;' |
||||||
|
if (key === 'invert') attrs.style += 'background:#000;filter:invert(1);' |
||||||
|
if (key === 'strike') attrs.style += 'text-decoration:line-through;' |
||||||
|
if (key === 'overline') attrs.style += 'text-decoration:overline;' |
||||||
|
if (key === 'faint') attrs.style += 'opacity:0.5;' |
||||||
|
else if (key === 'color') attrs.style += `color:${value};` |
||||||
|
else attrs[key] = value |
||||||
|
} |
||||||
|
|
||||||
|
text = text.substr(match.index + match[0].length) |
||||||
|
} |
||||||
|
|
||||||
|
if (text) pushSpan(text) |
||||||
|
} |
||||||
|
|
||||||
|
let internalInfo = {} |
||||||
|
|
||||||
|
updateToolbar = () => { |
||||||
|
if (!toolbarAttached) return |
||||||
|
let text = `C((${screen.cursor.y},${screen.cursor.x}),hang:${screen.cursor.hanging},vis:${screen.cursor.visible})` |
||||||
|
if (mouseHoverCell) { |
||||||
|
text += ' m' + getCellData(mouseHoverCell[1] * screen.window.width + mouseHoverCell[0]) |
||||||
|
} |
||||||
|
setFormattedText(dataDisplay, text) |
||||||
|
|
||||||
|
if ('flags' in internalInfo) { |
||||||
|
// we got ourselves some internal data
|
||||||
|
let text = ' ' |
||||||
|
text += ` flags:${internalInfo.flags.toString(2)}` |
||||||
|
text += ` curAttrs:${internalInfo.cursorAttrs.toString(2)}` |
||||||
|
text += ` Region:${internalInfo.regionStart}->${internalInfo.regionEnd}` |
||||||
|
text += ` Charset:${internalInfo.charsetGx} (0:${internalInfo.charsetG0},1:${internalInfo.charsetG1})` |
||||||
|
text += ` Heap:${internalInfo.freeHeap}` |
||||||
|
text += ` Clients:${internalInfo.clientCount}` |
||||||
|
setFormattedText(internalDisplay, text) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
screen.on('draw', updateToolbar) |
||||||
|
screen.on('internal', data => { |
||||||
|
internalInfo = data |
||||||
|
updateToolbar() |
||||||
|
}) |
||||||
|
} |
@ -1,15 +0,0 @@ |
|||||||
// Bits in the cell attribs word
|
|
||||||
|
|
||||||
/* eslint-disable no-multi-spaces */ |
|
||||||
exports.ATTR_FG = (1 << 0) // 1 if not using default background color (ignore cell bg) - color extension bit
|
|
||||||
exports.ATTR_BG = (1 << 1) // 1 if not using default foreground color (ignore cell fg) - color extension bit
|
|
||||||
exports.ATTR_BOLD = (1 << 2) // Bold font
|
|
||||||
exports.ATTR_UNDERLINE = (1 << 3) // Underline decoration
|
|
||||||
exports.ATTR_INVERSE = (1 << 4) // Invert colors - this is useful so we can clear then with SGR manipulation commands
|
|
||||||
exports.ATTR_BLINK = (1 << 5) // Blinking
|
|
||||||
exports.ATTR_ITALIC = (1 << 6) // Italic font
|
|
||||||
exports.ATTR_STRIKE = (1 << 7) // Strike-through decoration
|
|
||||||
exports.ATTR_OVERLINE = (1 << 8) // Over-line decoration
|
|
||||||
exports.ATTR_FAINT = (1 << 9) // Faint foreground color (reduced alpha)
|
|
||||||
exports.ATTR_FRAKTUR = (1 << 10) // Fraktur font (unicode substitution)
|
|
||||||
/* eslint-enable no-multi-spaces */ |
|
@ -1,285 +0,0 @@ |
|||||||
const EventEmitter = require('events') |
|
||||||
const CanvasRenderer = require('./screen_renderer') |
|
||||||
|
|
||||||
const DEFAULT_FONT = '"DejaVu Sans Mono", "Liberation Mono", "Inconsolata", "Menlo", monospace' |
|
||||||
|
|
||||||
/** |
|
||||||
* Manages terminal screen layout and sizing |
|
||||||
*/ |
|
||||||
module.exports = class ScreenLayout extends EventEmitter { |
|
||||||
constructor () { |
|
||||||
super() |
|
||||||
|
|
||||||
this.canvas = document.createElement('canvas') |
|
||||||
this.renderer = new CanvasRenderer(this.canvas) |
|
||||||
|
|
||||||
this._window = { |
|
||||||
width: 0, |
|
||||||
height: 0, |
|
||||||
devicePixelRatio: 1, |
|
||||||
fontFamily: DEFAULT_FONT, |
|
||||||
fontSize: 20, |
|
||||||
padding: 6, |
|
||||||
gridScaleX: 1.0, |
|
||||||
gridScaleY: 1.2, |
|
||||||
fitIntoWidth: 0, |
|
||||||
fitIntoHeight: 0, |
|
||||||
debug: false |
|
||||||
} |
|
||||||
|
|
||||||
// scaling caused by fitIntoWidth/fitIntoHeight
|
|
||||||
this._windowScale = 1 |
|
||||||
|
|
||||||
// actual padding, as it may be disabled by fullscreen mode etc.
|
|
||||||
this._padding = 0 |
|
||||||
|
|
||||||
// properties of this.window that require updating size and redrawing
|
|
||||||
this.windowState = { |
|
||||||
width: 0, |
|
||||||
height: 0, |
|
||||||
devicePixelRatio: 0, |
|
||||||
padding: 0, |
|
||||||
gridScaleX: 0, |
|
||||||
gridScaleY: 0, |
|
||||||
fontFamily: '', |
|
||||||
fontSize: 0, |
|
||||||
fitIntoWidth: 0, |
|
||||||
fitIntoHeight: 0 |
|
||||||
} |
|
||||||
|
|
||||||
this.charSize = { width: 0, height: 0 } |
|
||||||
|
|
||||||
const self = this |
|
||||||
|
|
||||||
// make writing to window update size and draw
|
|
||||||
this.window = new Proxy(this._window, { |
|
||||||
set (target, key, value) { |
|
||||||
if (target[key] !== value) { |
|
||||||
target[key] = value |
|
||||||
self.scheduleSizeUpdate() |
|
||||||
self.renderer.scheduleDraw(`window:${key}=${value}`) |
|
||||||
self.emit(`update-window:${key}`, value) |
|
||||||
} |
|
||||||
return true |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
this.on('update-window:debug', debug => { this.renderer.debug = debug }) |
|
||||||
|
|
||||||
this.canvas.addEventListener('mousedown', e => this.emit('mousedown', e)) |
|
||||||
this.canvas.addEventListener('mousemove', e => this.emit('mousemove', e)) |
|
||||||
this.canvas.addEventListener('mouseup', e => this.emit('mouseup', e)) |
|
||||||
this.canvas.addEventListener('touchstart', e => this.emit('touchstart', e)) |
|
||||||
this.canvas.addEventListener('touchmove', e => this.emit('touchmove', e)) |
|
||||||
this.canvas.addEventListener('touchend', e => this.emit('touchend', e)) |
|
||||||
this.canvas.addEventListener('wheel', e => this.emit('wheel', e)) |
|
||||||
this.canvas.addEventListener('contextmenu', e => this.emit('contextmenu', e)) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Schedule a size update in the next millisecond |
|
||||||
*/ |
|
||||||
scheduleSizeUpdate () { |
|
||||||
clearTimeout(this._scheduledSizeUpdate) |
|
||||||
this._scheduledSizeUpdate = setTimeout(() => this.updateSize(), 1) |
|
||||||
} |
|
||||||
|
|
||||||
get backgroundImage () { |
|
||||||
return this.canvas.style.backgroundImage |
|
||||||
} |
|
||||||
|
|
||||||
set backgroundImage (value) { |
|
||||||
this.canvas.style.backgroundImage = value ? `url(${value})` : '' |
|
||||||
if (this.renderer.backgroundImage !== !!value) { |
|
||||||
this.renderer.backgroundImage = !!value |
|
||||||
this.renderer.resetDrawn() |
|
||||||
this.renderer.scheduleDraw('background-image') |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
get selectable () { |
|
||||||
return this.canvas.classList.contains('selectable') |
|
||||||
} |
|
||||||
|
|
||||||
set selectable (selectable) { |
|
||||||
if (selectable) this.canvas.classList.add('selectable') |
|
||||||
else this.canvas.classList.remove('selectable') |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns a CSS font string with the current font settings and the |
|
||||||
* specified modifiers. |
|
||||||
* @param {Object} modifiers |
|
||||||
* @param {string} [modifiers.style] - the font style |
|
||||||
* @param {string} [modifiers.weight] - the font weight |
|
||||||
* @returns {string} a CSS font string |
|
||||||
*/ |
|
||||||
getFont (modifiers = {}) { |
|
||||||
let fontStyle = modifiers.style || 'normal' |
|
||||||
let fontWeight = modifiers.weight || 'normal' |
|
||||||
let fontFamily = this.window.fontFamily || '' |
|
||||||
if (fontFamily.length > 0) fontFamily += ',' |
|
||||||
fontFamily += DEFAULT_FONT |
|
||||||
return `${fontStyle} normal ${fontWeight} ${this.window.fontSize}px ${fontFamily}` |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Converts screen coordinates to grid coordinates. |
|
||||||
* @param {number} x - x in pixels |
|
||||||
* @param {number} y - y in pixels |
|
||||||
* @param {boolean} rounded - whether to round the coord, used for select highlighting |
|
||||||
* @returns {number[]} a tuple of (x, y) in cells |
|
||||||
*/ |
|
||||||
screenToGrid (x, y, rounded = false) { |
|
||||||
let cellSize = this.getCellSize() |
|
||||||
|
|
||||||
x = x / this._windowScale - this._padding |
|
||||||
y = y / this._windowScale - this._padding |
|
||||||
y = Math.floor(y / cellSize.height) |
|
||||||
if (this.renderer.drawnScreenLines[y]) x /= 2 // double size
|
|
||||||
x = Math.floor((x + (rounded ? cellSize.width / 2 : 0)) / cellSize.width) |
|
||||||
x = Math.max(0, Math.min(this.window.width - 1, x)) |
|
||||||
y = Math.max(0, Math.min(this.window.height - 1, y)) |
|
||||||
|
|
||||||
return [x, y] |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Converts grid coordinates to screen coordinates. |
|
||||||
* @param {number} x - x in cells |
|
||||||
* @param {number} y - y in cells |
|
||||||
* @param {boolean} [withScale] - when true, will apply window scale |
|
||||||
* @returns {number[]} a tuple of (x, y) in pixels |
|
||||||
*/ |
|
||||||
gridToScreen (x, y, withScale = false) { |
|
||||||
let cellSize = this.getCellSize() |
|
||||||
|
|
||||||
if (this.renderer.drawnScreenLines[y]) x *= 2 // double size
|
|
||||||
|
|
||||||
return [x * cellSize.width, y * cellSize.height].map(v => this._padding + (withScale ? v * this._windowScale : v)) |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Update the character size, used for calculating the cell size. |
|
||||||
* The space character is used for measuring. |
|
||||||
* @returns {Object} the character size with `width` and `height` in pixels |
|
||||||
*/ |
|
||||||
updateCharSize () { |
|
||||||
this.charSize = { |
|
||||||
width: this.renderer.getCharWidthFor(this.getFont()), |
|
||||||
height: this.window.fontSize |
|
||||||
} |
|
||||||
|
|
||||||
return this.charSize |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* The cell size, which is the character size multiplied by the grid scale. |
|
||||||
* @returns {Object} the cell size with `width` and `height` in pixels |
|
||||||
*/ |
|
||||||
getCellSize () { |
|
||||||
if (!this.charSize.height && this.window.fontSize) this.updateCharSize() |
|
||||||
|
|
||||||
return { |
|
||||||
width: Math.ceil(this.charSize.width * this.window.gridScaleX), |
|
||||||
height: Math.ceil(this.charSize.height * this.window.gridScaleY) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Updates the canvas size if it changed |
|
||||||
*/ |
|
||||||
updateSize () { |
|
||||||
// see below (this is just updating it)
|
|
||||||
this._window.devicePixelRatio = Math.ceil(this._windowScale * (window.devicePixelRatio || 1)) |
|
||||||
|
|
||||||
let didChange = false |
|
||||||
for (let key in this.windowState) { |
|
||||||
if (this.windowState.hasOwnProperty(key) && this.windowState[key] !== this.window[key]) { |
|
||||||
didChange = true |
|
||||||
this.windowState[key] = this.window[key] |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
if (didChange) { |
|
||||||
const { |
|
||||||
width, |
|
||||||
height, |
|
||||||
fitIntoWidth, |
|
||||||
fitIntoHeight, |
|
||||||
padding |
|
||||||
} = this.window |
|
||||||
|
|
||||||
this.updateCharSize() |
|
||||||
const cellSize = this.getCellSize() |
|
||||||
|
|
||||||
// real height of the canvas element in pixels
|
|
||||||
let realWidth = width * cellSize.width |
|
||||||
let realHeight = height * cellSize.height |
|
||||||
let originalWidth = realWidth |
|
||||||
|
|
||||||
if (fitIntoWidth && fitIntoHeight) { |
|
||||||
let terminalAspect = realWidth / realHeight |
|
||||||
let fitAspect = fitIntoWidth / fitIntoHeight |
|
||||||
|
|
||||||
if (terminalAspect < fitAspect) { |
|
||||||
// align heights
|
|
||||||
realHeight = fitIntoHeight - 2 * padding |
|
||||||
realWidth = realHeight * terminalAspect |
|
||||||
} else { |
|
||||||
// align widths
|
|
||||||
realWidth = fitIntoWidth - 2 * padding |
|
||||||
realHeight = realWidth / terminalAspect |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// store new window scale
|
|
||||||
this._windowScale = realWidth / originalWidth |
|
||||||
|
|
||||||
realWidth += 2 * padding |
|
||||||
realHeight += 2 * padding |
|
||||||
|
|
||||||
// store padding
|
|
||||||
this._padding = padding * (originalWidth / realWidth) |
|
||||||
|
|
||||||
// the DPR must be rounded to a very nice value to prevent gaps between cells
|
|
||||||
let devicePixelRatio = this._window.devicePixelRatio = Math.ceil(this._windowScale * (window.devicePixelRatio || 1)) |
|
||||||
|
|
||||||
this.canvas.width = (width * cellSize.width + 2 * Math.round(this._padding)) * devicePixelRatio |
|
||||||
this.canvas.style.width = `${realWidth}px` |
|
||||||
this.canvas.height = (height * cellSize.height + 2 * Math.round(this._padding)) * devicePixelRatio |
|
||||||
this.canvas.style.height = `${realHeight}px` |
|
||||||
|
|
||||||
// the screen has been cleared (by changing canvas width)
|
|
||||||
this.renderer.resetDrawn() |
|
||||||
|
|
||||||
this.renderer.render('update-size', this.serializeRenderData()) |
|
||||||
|
|
||||||
this.emit('size-update') |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
serializeRenderData () { |
|
||||||
return { |
|
||||||
padding: Math.round(this._padding), |
|
||||||
devicePixelRatio: this.window.devicePixelRatio, |
|
||||||
charSize: this.charSize, |
|
||||||
cellSize: this.getCellSize(), |
|
||||||
fonts: [ |
|
||||||
this.getFont(), |
|
||||||
this.getFont({ weight: 'bold' }), |
|
||||||
this.getFont({ style: 'italic' }), |
|
||||||
this.getFont({ weight: 'bold', style: 'italic' }) |
|
||||||
] |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
render (reason, data) { |
|
||||||
this.window.width = data.width |
|
||||||
this.window.height = data.height |
|
||||||
|
|
||||||
Object.assign(data, this.serializeRenderData()) |
|
||||||
|
|
||||||
this.renderer.render(reason, data) |
|
||||||
} |
|
||||||
} |
|
@ -1,14 +0,0 @@ |
|||||||
#! /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)); |
|
@ -1,54 +0,0 @@ |
|||||||
/* |
|
||||||
* 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) |
|
||||||
} |
|
@ -1,292 +0,0 @@ |
|||||||
<?php |
|
||||||
|
|
||||||
return [ |
|
||||||
'menu.cfg_wifi' => 'WiFi Beállítások', |
|
||||||
'menu.cfg_network' => 'Hálózati beállítások', |
|
||||||
'menu.cfg_term' => 'Terminál beállítások', |
|
||||||
'menu.about' => 'Az ESPTerm-ről', |
|
||||||
'menu.help' => 'Gyors referencia', |
|
||||||
'menu.term' => 'Vissza a terminálba', |
|
||||||
'menu.cfg_system' => 'Rendszer beállítások', |
|
||||||
'menu.cfg_wifi_conn' => 'Csatlakozás a hálózathoz', |
|
||||||
'menu.settings' => 'Beállítások', |
|
||||||
|
|
||||||
// Terminal page |
|
||||||
|
|
||||||
'title.term' => 'Terminál', // page title of the terminal page |
|
||||||
|
|
||||||
'term_nav.fullscreen' => 'Teljesképernyő', |
|
||||||
'term_nav.config' => 'Beállítás', |
|
||||||
'term_nav.wifi' => 'WiFi', |
|
||||||
'term_nav.help' => 'Segítség', |
|
||||||
'term_nav.about' => 'Info', |
|
||||||
'term_nav.paste' => 'Beillesztés', |
|
||||||
'term_nav.upload' => 'Feltöltés', |
|
||||||
'term_nav.keybd' => 'Billentyűzet', |
|
||||||
'term_nav.paste_prompt' => 'Szöveg beillesztése és küldése:', |
|
||||||
|
|
||||||
'term_conn.connecting' => 'Csatlakozás', |
|
||||||
'term_conn.waiting_content' => 'Várakozás a csatlakozásra', |
|
||||||
'term_conn.disconnected' => 'Kapcsolat bontva', |
|
||||||
'term_conn.waiting_server' => 'Várakozás a kiszolgálóra', |
|
||||||
'term_conn.reconnecting' => 'Újracsatlakozás', |
|
||||||
|
|
||||||
// Terminal settings page |
|
||||||
|
|
||||||
'term.defaults' => 'Alap beállítások', |
|
||||||
'term.expert' => 'Haladó beállítások', |
|
||||||
'term.explain_initials' => ' |
|
||||||
Ezek az alap beállítások amik az ESPTerm bekapcsolása után, |
|
||||||
vagy amikor képernyő reset parancsa érkezikd (<code>\ec</code>). |
|
||||||
Ezek megváltoztathatóak egy terminál alkalmzás és escape szekveciák segítségével. |
|
||||||
', |
|
||||||
'term.explain_expert' => ' |
|
||||||
Ezek haladó beállítási opciók amiket általában nem kell megváltoztatni. |
|
||||||
Csak akkor változtass rajta ha tudod mit csinálsz!', |
|
||||||
|
|
||||||
'term.example' => 'Alapértelmezet színek előnézete', |
|
||||||
|
|
||||||
'term.explain_scheme' => ' |
|
||||||
Az alapértelmezett szöveg és háttér szín kiválasztásához kattints a |
|
||||||
paletta előnézet gombra. Alternatíva: használd a 0-15 számokat a téma színekhez, |
|
||||||
16-255 számokat a normál színekhez és hexa (#FFFFFF) a True Color (24-bit) színekhez. |
|
||||||
', |
|
||||||
|
|
||||||
'term.fgbg_presets' => 'Alapértelmezett beállítások', |
|
||||||
'term.color_scheme' => 'Szín séma', |
|
||||||
'term.reset_screen' => 'A képernyő olvasó alapállapotba állítása', |
|
||||||
'term.term_title' => 'Fejléc szöveg', |
|
||||||
'term.term_width' => 'Szélesség', |
|
||||||
'term.term_height' => 'Magasség', |
|
||||||
'term.buttons' => 'Gomb cimkék', |
|
||||||
'term.theme' => 'Szín paletta', |
|
||||||
'term.cursor_shape' => 'Kurzor stílus', |
|
||||||
'term.parser_tout_ms' => 'Olvasó időtúllépés', |
|
||||||
'term.display_tout_ms' => 'Újrarajzolás késleltetése', |
|
||||||
'term.display_cooldown_ms' => 'Újrarajzolás cooldown', |
|
||||||
'term.allow_decopt_12' => '\e?12h/l engedélyezés', |
|
||||||
'term.fn_alt_mode' => 'SS3 Fn gombok', |
|
||||||
'term.show_config_links' => 'Navigációs linkek mutatása', |
|
||||||
'term.show_buttons' => 'Gombok mutatása', |
|
||||||
'term.loopback' => 'Helyi visszajelzés (<span style="text-decoration:overline">SRM</span>)', |
|
||||||
'term.crlf_mode' => 'Enter = CR+LF (LNM)', |
|
||||||
'term.want_all_fn' => 'F5, F11, F12 elfogása', |
|
||||||
'term.button_msgs' => 'Gomb kódok<br>(ASCII, dec, CSV)', |
|
||||||
'term.color_fg' => 'Alap előtér.', |
|
||||||
'term.color_bg' => 'Alap háttér', |
|
||||||
'term.color_fg_prev' => 'Előtér', |
|
||||||
'term.color_bg_prev' => 'Háttér', |
|
||||||
'term.colors_preview' => '', |
|
||||||
'term.debugbar' => 'Belső állapot hibakeresés', |
|
||||||
'term.ascii_debug' => 'Kontroll kódok mutatása', |
|
||||||
'term.backdrop' => 'Háttérkép URL.je', |
|
||||||
'term.button_count' => 'Gomb szám', |
|
||||||
'term.button_colors' => 'Gomb színek', |
|
||||||
'term.font_stack' => 'Betű típus', |
|
||||||
'term.font_size' => 'Betű méret', |
|
||||||
|
|
||||||
'cursor.block_blink' => 'Blokk, villog', |
|
||||||
'cursor.block_steady' => 'Blokk, fix', |
|
||||||
'cursor.underline_blink' => 'Aláhúzás, villog', |
|
||||||
'cursor.underline_steady' => 'Aláhúzás, fix', |
|
||||||
'cursor.bar_blink' => 'I, villog', |
|
||||||
'cursor.bar_steady' => 'I, fix', |
|
||||||
|
|
||||||
// Text upload dialog |
|
||||||
|
|
||||||
'upload.title' => 'Szöveg feltöltése', |
|
||||||
'upload.prompt' => 'Szöveg fájl betöltése:', |
|
||||||
'upload.endings' => 'Sor vége:', |
|
||||||
'upload.endings.cr' => 'CR (Enter gomb)', |
|
||||||
'upload.endings.crlf' => 'CR LF (Windows)', |
|
||||||
'upload.endings.lf' => 'LF (Linux)', |
|
||||||
'upload.chunk_delay' => 'Chunk késleltetés (ms):', |
|
||||||
'upload.chunk_size' => 'Chunk méret (0=line):', |
|
||||||
'upload.progress' => 'Feltöltés:', |
|
||||||
|
|
||||||
// Network config page |
|
||||||
|
|
||||||
'net.explain_sta' => ' |
|
||||||
Kapcsold ki a dinamikus IP címet a statikus cím beállításához.', |
|
||||||
|
|
||||||
'net.explain_ap' => ' |
|
||||||
Ezek a beállítások a beépített DHCP szervet és az AP módot befolyásolják.', |
|
||||||
|
|
||||||
'net.ap_dhcp_time' => 'Lízing idő', |
|
||||||
'net.ap_dhcp_start' => 'Kezdő IP cím', |
|
||||||
'net.ap_dhcp_end' => 'Záró IP cím', |
|
||||||
'net.ap_addr_ip' => 'Saját IP cím', |
|
||||||
'net.ap_addr_mask' => 'Hálózati maszk', |
|
||||||
|
|
||||||
'net.sta_dhcp_enable' => 'Dinamikus IP cím használata', |
|
||||||
'net.sta_addr_ip' => 'ESPTerm statikus IP címe', |
|
||||||
'net.sta_addr_mask' => 'Hálózati maszk', |
|
||||||
'net.sta_addr_gw' => 'Útválasztó IP címe', |
|
||||||
|
|
||||||
'net.ap' => 'DHCP Szerver (AP)', |
|
||||||
'net.sta' => 'DHCP Kliens (Station)', |
|
||||||
'net.sta_mac' => 'Állomás MAC címe', |
|
||||||
'net.ap_mac' => 'AP MAC címe', |
|
||||||
'net.details' => 'MAC címek', |
|
||||||
|
|
||||||
// Wifi config page |
|
||||||
|
|
||||||
'wifi.ap' => 'Beépített Access Point', |
|
||||||
'wifi.sta' => 'Kapcsolódás létező hálózathoz', |
|
||||||
|
|
||||||
'wifi.enable' => 'Engedélyezve', |
|
||||||
'wifi.tpw' => 'Adás teljesítmény', |
|
||||||
'wifi.ap_channel' => 'Csatorna', |
|
||||||
'wifi.ap_ssid' => 'AP SSID', |
|
||||||
'wifi.ap_password' => 'Jelszó', |
|
||||||
'wifi.ap_hidden' => 'SSID rejtése', |
|
||||||
'wifi.sta_info' => 'Kiválasztott', |
|
||||||
|
|
||||||
'wifi.not_conn' => 'Nincs csatlkoztatva.', |
|
||||||
'wifi.sta_none' => 'Egyiksem', |
|
||||||
'wifi.sta_active_pw' => '🔒 Jelszó elmentve', |
|
||||||
'wifi.sta_active_nopw' => '🔓 Szabad hozzáférés', |
|
||||||
'wifi.connected_ip_is' => 'Csatlakozva, az IP cím ', |
|
||||||
'wifi.sta_password' => 'Jelszó:', |
|
||||||
|
|
||||||
'wifi.scanning' => 'Keresés', |
|
||||||
'wifi.scan_now' => 'Kattints a keresés indításához!', |
|
||||||
'wifi.cant_scan_no_sta' => 'Kattints a kliens mód engedélyezéséhez és a keresés indításához!', |
|
||||||
'wifi.select_ssid' => 'Elérhető hálózatok:', |
|
||||||
'wifi.enter_passwd' => 'Jelszó a(z) ":ssid:" hálózathoz', |
|
||||||
'wifi.sta_explain' => 'A hálózat kiválasztása után nyomdj meg az Alkamaz gombot a csatlakozáshoz.', |
|
||||||
|
|
||||||
// Wifi connecting status page |
|
||||||
|
|
||||||
'wificonn.status' => 'Státusz:', |
|
||||||
'wificonn.back_to_config' => 'Vissza a WiFi beállításhoz', |
|
||||||
'wificonn.telemetry_lost' => 'Telemetria megszakadt; valami hiba történt, vagy az eszközöd elvesztette a kapcsolatot.', |
|
||||||
'wificonn.explain_android_sucks' => ' |
|
||||||
Ha okostelefonon kapcsolódsz az ESPTerm-hez, vagy amikor csatlakozol |
|
||||||
egy másik hálózatról, az eszközöd elveszítheti a kapcsolatot és |
|
||||||
ez az indikátor nem fog működni. Kérlek várj egy keveset (~ 15 másodpercet), |
|
||||||
és ellenőrizd, hogy a kapcsolat helyrejött-e.', |
|
||||||
|
|
||||||
'wificonn.explain_reset' => ' |
|
||||||
Az beépített AP engedélyezéséhez tarts lenyomva a BOOT gombot amíg a kék led |
|
||||||
villogni nem kezd. Tartsd addig lenyomva amíg a led el nem kezd gyorsan villogni |
|
||||||
a gyári alapállapot visszaállításához".', |
|
||||||
|
|
||||||
'wificonn.disabled' =>"Station mode letiltva.", |
|
||||||
'wificonn.idle' =>"Alapállapot, nincs csatlakozva és nincs IP címe.", |
|
||||||
'wificonn.success' => "Csatlakozva! Kaptam IP címet", |
|
||||||
'wificonn.working' => "Csatlakozás a beállított AP-hez", |
|
||||||
'wificonn.fail' => "Csatlakozás nem sikerült, ellenőrizd a beállítások és próbáld újra. A hibaok: ", |
|
||||||
|
|
||||||
// Access restrictions form |
|
||||||
|
|
||||||
'pwlock.title' => 'Hozzáférés korlátozása', |
|
||||||
'pwlock.explain' => ' |
|
||||||
A web interfész néhany része vagy a teljes interfész jelszavas védelemmel látható el. |
|
||||||
Hagyd a jelszó mezőt üresen ha nem akarod megváltoztatni.<br> |
|
||||||
Az alapértelmezett jelszó "%def_access_pw%". |
|
||||||
', |
|
||||||
'pwlock.region' => 'Védett oldalak', |
|
||||||
'pwlock.region.none' => 'Egyiksem, minden hozzáférhető', |
|
||||||
'pwlock.region.settings_noterm' => 'WiFi, Hálózat és Rendszer beállítások', |
|
||||||
'pwlock.region.settings' => 'Minden beállítás oldal', |
|
||||||
'pwlock.region.menus' => 'Ez a teljes menű rész', |
|
||||||
'pwlock.region.all' => 'Minden, még a terminál is', |
|
||||||
'pwlock.new_access_pw' => 'Új jelszó', |
|
||||||
'pwlock.new_access_pw2' => 'Jelszó ismét', |
|
||||||
'pwlock.admin_pw' => 'Admin jelszó', |
|
||||||
'pwlock.access_name' => 'Felhasználó név', |
|
||||||
|
|
||||||
// Setting admin password |
|
||||||
|
|
||||||
'adminpw.title' => 'Admin jelszó megváltoztatása', |
|
||||||
'adminpw.explain' => |
|
||||||
' |
|
||||||
Az "admin jelszo" a tárolt alap beállítások módosításához és a hozzáférések |
|
||||||
változtatásához kell. Ez a jelszó nincs a többi beállítással egy helyre mentve, |
|
||||||
tehát a mentés és visszaállítás műveletek nem befolyásolják. |
|
||||||
Ha az admin jelszó elveszik akkor a legegyszerűbb módja a hozzáférés |
|
||||||
visszaszerzésére a chip újraflashselésere.<br> |
|
||||||
Az alap jelszó: "%def_admin_pw%". |
|
||||||
', |
|
||||||
'adminpw.new_admin_pw' => 'Új admin jelszó', |
|
||||||
'adminpw.new_admin_pw2' => 'Jelszó ismét', |
|
||||||
'adminpw.old_admin_pw' => 'Régi admin jelszó', |
|
||||||
|
|
||||||
// Persist form |
|
||||||
|
|
||||||
'persist.title' => 'Mentés & Visszaállítás', |
|
||||||
'persist.explain' => ' |
|
||||||
ESPTerm az összes beállítást Flash-be menti. Az aktív beállítások at lehet másolni |
|
||||||
a "alapértelmezett" területre és az később a lenti kék gombbal visszaállítható. |
|
||||||
', |
|
||||||
'persist.confirm_restore' => 'Minden beállítást visszaállítasz az "alap" értékre?', |
|
||||||
'persist.confirm_restore_hard' => |
|
||||||
'Visszaállítod a rendszer alap beállításait? Ez minden aktív ' . |
|
||||||
'beállítást törölni fog és AP módban az alap SSID-vel for újraindulni.', |
|
||||||
'persist.confirm_store_defaults' => |
|
||||||
'Add meg az admin jelszót az alapállapotba állítás megerősítéshez.', |
|
||||||
'persist.password' => 'Admin jelszó:', |
|
||||||
'persist.restore_defaults' => 'Mentett beállítások visszaállítása', |
|
||||||
'persist.write_defaults' => 'Aktív beállítások mentése alapértelmezetnek', |
|
||||||
'persist.restore_hard' => 'Gyári alapbeállítások betöltése', |
|
||||||
'persist.restore_hard_explain' => |
|
||||||
'(Ez törli a Wifi beállításokat, de nincs hatása az admin jelszóra.)', |
|
||||||
|
|
||||||
'backup.title' => 'Configurációs fájl biztonsági másolat készítés', |
|
||||||
'backup.explain' => 'Minden beállítás menthető és visszaállítható az admin jelszó kivételévelAll config except the admin password can be backed up and restored using egy .INI fájllal.', |
|
||||||
'backup.export' => 'Fáljbe exportálás', |
|
||||||
'backup.import' => 'Importálás!', |
|
||||||
|
|
||||||
|
|
||||||
// UART settings form |
|
||||||
|
|
||||||
'uart.title' => 'Soros port paraméterek', |
|
||||||
'uart.explain' => ' |
|
||||||
Ez a beállítás szabályozza a kommunikációs UART-ot. A hibakereső UART fix |
|
||||||
115.200 baud-val, egy stop-bittel és paritás bit nélkül működik. |
|
||||||
', |
|
||||||
'uart.baud' => 'Baud rate', |
|
||||||
'uart.parity' => 'Parity', |
|
||||||
'uart.parity.none' => 'Egyiksem', |
|
||||||
'uart.parity.odd' => 'Páratlan', |
|
||||||
'uart.parity.even' => 'Páros', |
|
||||||
'uart.stop_bits' => 'Stop-bit', |
|
||||||
'uart.stop_bits.one' => 'Egy', |
|
||||||
'uart.stop_bits.one_and_half' => 'Másfél', |
|
||||||
'uart.stop_bits.two' => 'Kettő', |
|
||||||
|
|
||||||
// HW tuning form |
|
||||||
|
|
||||||
'hwtuning.title' => 'Hardware Tuning', |
|
||||||
'hwtuning.explain' => ' |
|
||||||
ESP8266-t órajelét lehetséges 80 MHz-ről 160 MHz-re emelni. Ettől |
|
||||||
jobb válaszidők és gyakoribb képernyő frissítések várhatóak, viszont megnövekszik |
|
||||||
az energia felhasználás. Az interferencia esélye is megnő. |
|
||||||
Ovatosan használd!. |
|
||||||
', |
|
||||||
'hwtuning.overclock' => 'Órajel emelése 160MHz-re', |
|
||||||
|
|
||||||
'gpio2_config' => 'GPIO2 function', // TODO translate |
|
||||||
'gpio4_config' => 'GPIO4 function', |
|
||||||
'gpio5_config' => 'GPIO5 function', |
|
||||||
'gpio_config.off' => 'Disabled', |
|
||||||
'gpio_config.off_2' => 'Debug UART Tx', |
|
||||||
'gpio_config.out_initial0' => 'Output (initial 0)', |
|
||||||
'gpio_config.out_initial1' => 'Output (initial 1)', |
|
||||||
'gpio_config.in_pull' => 'Input (pull-up)', |
|
||||||
'gpio_config.in_nopull' => 'Input (floating)', |
|
||||||
|
|
||||||
// Generic button / dialog labels |
|
||||||
|
|
||||||
'apply' => 'Alkalmaz', |
|
||||||
'start' => 'Start', |
|
||||||
'cancel' => 'Mégse', |
|
||||||
'enabled' => 'Engedélyezve', |
|
||||||
'disabled' => 'Letiltva', |
|
||||||
'yes' => 'Igen', |
|
||||||
'no' => 'Nem', |
|
||||||
'confirm' => 'OK', |
|
||||||
'copy' => 'Másolás', |
|
||||||
'form_errors' => 'Validációs hiba:', |
|
||||||
]; |
|
@ -1,12 +0,0 @@ |
|||||||
// 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' |
|
||||||
] |
|
@ -1,39 +0,0 @@ |
|||||||
<div class="Box fold"> |
|
||||||
<h2>Remote GPIO Control</h2> |
|
||||||
|
|
||||||
<div class="Row v"> |
|
||||||
<p> |
|
||||||
ESPTerm provides a simple API to remotely control and read GPIO pins GPIO2, GPIO4, and GPIO5. |
|
||||||
The main use of this API is to remotely reset a device that communicates with ESPTerm |
|
||||||
through the UART. |
|
||||||
</p> |
|
||||||
|
|
||||||
<p> |
|
||||||
GPIO2 is normally used for debug UART, so when used as GPIO, debug logging is disabled. You |
|
||||||
can configure the pin functions in <a href="<?= url('cfg_system') ?>">System Settings</a>.
|
|
||||||
</p> |
|
||||||
|
|
||||||
<p> |
|
||||||
The GPIO control endpoint is `/api/v1/gpio`, with optional GET arguments: |
|
||||||
</p> |
|
||||||
|
|
||||||
<ul> |
|
||||||
<li>`do2=<i>x</i>` - set GPIO2 level. <i>x</i> can be `0`, `1`, or `t` to toggle the pin. |
|
||||||
<li>`do4=<i>x</i>` - set GPIO4 level |
|
||||||
<li>`do5=<i>x</i>` - set GPIO5 level |
|
||||||
<li>`pulse=<i>ms</i>` - the command starts a pulse. After the given amount of time |
|
||||||
(milliseconds) has elapsed, the pins are set to the opposite levels than what was specified |
|
||||||
(in the case of toggle, the original pin state) |
|
||||||
</ul> |
|
||||||
|
|
||||||
<p> |
|
||||||
A quick example: <a href="/api/v1/gpio?do4=1&pulse=500">`/api/v1/gpio?do4=1&pulse=500`</a> |
|
||||||
sends a 500ms long positive pulse on GPIO4. |
|
||||||
</p> |
|
||||||
|
|
||||||
<p> |
|
||||||
The GPIO endpoint always returns a JSON object like this: `{"io2":0,"io4":1,"io5":0}`, showing |
|
||||||
the current input levels. Input reading works always, regardless of the GPIO settings. |
|
||||||
</p> |
|
||||||
</div> |
|
||||||
</div> |
|
Loading…
Reference in new issue