const $ = require('./lib/chibi') const { qs } = require('./utils') const modal = require('./modal') /** File upload utility */ module.exports = function (conn, input, screen) { let lines, // array of lines without newlines line_i, // current line index fuTout, // timeout handle for line sending send_delay_ms, // delay between lines (ms) nl_str, // newline string to use curLine, // current line (when using fuOil) inline_pos // Offset in line (for long lines) // lines longer than this are split to chunks // sending a super-ling string through the socket is not a good idea const MAX_LINE_LEN = 128 function openUploadDialog () { updateStatus('Ready...') modal.show('#fu_modal', onDialogClose) $('#fu_form').toggleClass('busy', false) input.blockKeys(true) } function onDialogClose () { console.log('Upload modal closed.') clearTimeout(fuTout) line_i = 0 input.blockKeys(false) } function updateStatus (msg) { qs('#fu_prog').textContent = msg } function startUpload () { let v = qs('#fu_text').value if (!v.length) { fuClose() return } lines = v.split('\n') line_i = 0 inline_pos = 0 // offset in line send_delay_ms = qs('#fu_delay').value // sanitize - 0 causes overflows if (send_delay_ms < 0) { send_delay_ms = 0 qs('#fu_delay').value = send_delay_ms } nl_str = { 'CR': '\r', 'LF': '\n', 'CRLF': '\r\n' }[qs('#fu_crlf').value] $('#fu_form').toggleClass('busy', true) updateStatus('Starting...') uploadLine() } function uploadLine () { if (!$('#fu_modal').hasClass('visible')) { // Modal is closed, cancel return } if (!conn.canSend()) { // postpone fuTout = setTimeout(uploadLine, 1) return } if (inline_pos === 0) { curLine = '' if (line_i === 0) { if (screen.bracketedPaste) { curLine = '\x1b[200~' } } curLine += lines[line_i++] + nl_str if (line_i === lines.length) { if (screen.bracketedPaste) { curLine += '\x1b[201~' } } } let chunk if ((curLine.length - inline_pos) <= MAX_LINE_LEN) { chunk = curLine.substr(inline_pos, MAX_LINE_LEN) inline_pos = 0 } else { chunk = curLine.substr(inline_pos, MAX_LINE_LEN) inline_pos += MAX_LINE_LEN } if (!input.sendString(chunk)) { updateStatus('FAILED!') return } let pt = Math.round((line_i / lines.length) * 1000) / 10 updateStatus(`${line_i} / ${lines.length} (${pt}%)`) if (lines.length > line_i || inline_pos > 0) { fuTout = setTimeout(uploadLine, send_delay_ms) } else { closeWhenReady() } } function closeWhenReady () { if (!conn.canSend()) { // stuck in XOFF still, wait to process... updateStatus('Waiting for Tx buffer...') setTimeout(closeWhenReady, 100) } else { updateStatus('Done.') // delay to show it fuClose() } } function fuClose () { modal.hide('#fu_modal') } return { init: function () { qs('#fu_file').addEventListener('change', function (evt) { let reader = new window.FileReader() let file = evt.target.files[0] let ftype = file.type || 'application/octet-stream' console.log('Selected file type: ' + ftype) if (!ftype.match(/text\/.*|application\/(json|csv|.*xml.*|.*script.*|x-php)/)) { // Deny load of blobs like img - can crash browser and will get corrupted anyway if (!window.confirm(`This does not look like a text file: ${ftype}\nReally load?`)) { qs('#fu_file').value = '' return } } reader.onload = function (e) { const txt = e.target.result.replace(/[\r\n]+/, '\n') qs('#fu_text').value = txt } console.log('Loading file...') reader.readAsText(file) }, false) qs('#term-fu-open').addEventListener('click', function () { openUploadDialog() return false }) qs('#term-fu-start').addEventListener('click', function () { startUpload() return false }) qs('#term-fu-close').addEventListener('click', function () { fuClose() return false }) }, open: openUploadDialog, setContent (content) { qs('#fu_text').value = content } } }