diff --git a/Panels/BasePanel.js b/Panels/BasePanel.js index 821dc68..57bb0d6 100644 --- a/Panels/BasePanel.js +++ b/Panels/BasePanel.js @@ -27,7 +27,16 @@ class BasePanel { addCheckboxes = (name, items) => { this.addCheckedInputs('checkbox', name, items); }; - openField = name => this.addText(`${name}: `); + openField = (name, id) => { + if(id) { + if(this._field) throw `Unable to add field ${id} when prior field was not closed.`; + const field = document.createElement('span'); + field.id = this.childId(id); + this.append(field); + this._field = field; + } + this.addText(`${name}: `) + }; addText = text => this.append(document.createTextNode(text)); addDynamicText = (id, text) => { const span = document.createElement('span'); @@ -35,7 +44,10 @@ class BasePanel { span.innerText = text; return this.append(span); } - closeField = () => this.addNewLine(); + closeField = () => { + this._field = undefined; + this.addNewLine() + }; addNewLine = () => this.append(document.createElement('br')); addDropdown = (id, items, eventName = 'change') => { const select = document.createElement('select'); @@ -169,7 +181,6 @@ class BasePanel { getElement = id => { const element = this.getDomElement().querySelector(`#${this.childId(id)}`); - // const element = document.getElementById(this.childId(id)); if(!element) throw new Error(`Unable to find ${id}`); return element; } @@ -180,6 +191,15 @@ class BasePanel { code.className = type === '' ? 'raw-data' : `raw-data-${type}`; this.append(code); } + addImage = (id, src, options) => { + const image = document.createElement('img'); + image.id = this.childId(id); + image.src = src; + Object.keys(options).forEach(key => { + image[key] = options[key]; + }) + this.append(image); + } setValueById = (id, value) => { const element = this.getElement(id); switch(element.tagName) { @@ -187,6 +207,10 @@ class BasePanel { case 'SELECT': element.value = value; break; + case 'IMG': + element.src = value; + element.onload = () => URL.revokeObjectURL(value); + break; default: element.innerText = value; } @@ -209,11 +233,21 @@ class BasePanel { return element.innerText; } } + display = (id, isDisplayed) => { + const element = this.getElement(id); + element.style.display = isDisplayed ? 'block' : 'none'; + } setCheckedById = (id, checked = true) => { this.getElement(id).checked = !!checked; } getCheckedById = (id) => !!(this.getElement(id).checked); - append = (element) => this.container.appendChild(element); + append = (element) => { + if(this._field) { + return this._field.appendChild(element); + } else { + return this.container.appendChild(element); + } + } clear = () => this.container.innerHTML = ''; addEventListener = (eventName, callback) => this.dispatcher.addListener(eventName, callback); removeEventListener = (eventName, callback) => this.dispatcher.removeListener(eventName, callback); diff --git a/Panels/MessagePanel.js b/Panels/MessagePanel.js index 8252e3f..3ba7270 100644 --- a/Panels/MessagePanel.js +++ b/Panels/MessagePanel.js @@ -1,20 +1,82 @@ +import { bytesToText, textToBytes, urlToBytes, bytesToUrl } from '../converters'; +import { byteSize } from '../Humanize'; import BasePanel from './BasePanel'; class MessagePanel extends BasePanel { constructor() { super('Message'); - this.addInputText('text-to-send', '', 'messageChange'); + + this.openField('Data Type'); + this.addDropdown('data-type', [ + {text: 'Text', value: 'text', selected: true}, + {text: 'Image', value: 'image'} + ], 'dataTypeChange'); + this.closeField(); + + this.openField('Text', 'field-text'); + this.addInputText('text-to-send', '', {eventName: 'messageChange'}); + this.closeField(); + + this.openField('Image', 'field-image'); + this.addImage('image-to-send', 'interlaced-sample.gif', {eventName: 'messageChange'}); + this.closeField(); + this.addButton('send-button', 'Send', 'send'); + this.addNewLine(); + + this.openField('Bytes'); + this.addDynamicText('bytes', 0); + this.closeField(); + this.addSection('Received'); - this.addProgressBar('received-progress', .50); + this.addCode('decoded-text', '', 'small'); + this.addImage('decoded-image', undefined, {width: 32, height: 32}); + + this.addProgressBar('received-progress', .50); + + this.addEventListener('dataTypeChange', ({values: [value]}) => { + this.display('field-text', value === 'text'); + this.display('field-image', value === 'image'); + this.display('decoded-image', value === 'image'); + this.display('decoded-text', value==='text'); + // should be 487 bytes + this.setValueById('bytes', byteSize(this.getMessageBytes().length)); + }); + this.addEventListener('messageChange', e => { + this.setValueById('bytes', byteSize(this.getMessageBytes().length)); + }); + this.dispatcher.emit('dataTypeChange', {values: [this.getDataType()]}); } getSendButtonText = () => this.getValueById('send-button'); setSendButtonText = text => this.setValueById('send-button', text); - setMessage = text => this.setValueById('text-to-send', text); - getMessage = () => this.getValueById('text-to-send'); + setMessageText = text => { + this.setValueById('text-to-send', text); + this.setValueById('bytes', byteSize(textToBytes(text).length)); + } + getMessageText = () => this.getValueById('text-to-send'); + getMessageBytes = () => { + if(this.getDataType() === 'text') { + return textToBytes(this.getMessageText()); + } else { + return urlToBytes(this.getElement('image-to-send').src); + } + } setProgress = percent => this.setProgressById('received-progress', percent); setReceived = (html) => this.setHtmlById('decoded-text', html); + setReceivedBytes = bytes => { + if(this.getDataType() === 'text') { + this.setReceived(bytesToText(bytes)); + } else { + this.setValueById('decoded-image', bytesToUrl(bytes)); + } + } + + getDataType = () => this.getValueById('data-type'); + setDataType = (value) => { + this.setValueById('data-type', value); + this.dispatcher.emit('dataTypeChange', {values: [value]}); + } } export default MessagePanel; \ No newline at end of file diff --git a/Panels/SpeedPanel.js b/Panels/SpeedPanel.js index ade17a0..4618c22 100644 --- a/Panels/SpeedPanel.js +++ b/Panels/SpeedPanel.js @@ -20,18 +20,13 @@ class SpeedPanel extends BasePanel { this.addDynamicText('transfer-duration', 'n/a'); this.closeField(); - this.addSection('Maximum'); - - this.openField('Packets'); - this.addDynamicText('max-packets', 'n/a'); - this.closeField(); + this.addSection('Maximum Data'); this.openField('Duration'); this.addDynamicText('max-duration', 'n/a'); this.closeField(); }; - setMaximumPackets = (count) => this.setValueById('max-packets', count.toLocaleString()); setMaximumDurationMilliseconds = (milliseconds) => this.setValueById('max-duration', Humanize.durationMilliseconds(milliseconds)); setPacketizationBitsPerSecond = (bps) => this.setValueById('bps-packetization', Humanize.bitsPerSecond(bps)); setDataBitsPerSecond = (bps) => this.setValueById('bps-data', Humanize.bitsPerSecond(bps)); diff --git a/converters.js b/converters.js index 2e22d82..47ad700 100644 --- a/converters.js +++ b/converters.js @@ -61,3 +61,20 @@ export const bitsToInt = (bits, bitLength) => { 2 ); } +export const urlToBytes = src => { + const xhr = new XMLHttpRequest(); + // we need a synchronous response. + xhr.open('GET', src, false); + xhr.overrideMimeType('text/plain; charset=x-user-defined'); + xhr.send(null); + if(xhr.status !== 200) return []; + let bytes = []; + for(let i = 0; i < xhr.response.length; i++) { + bytes.push(xhr.response.charCodeAt(i) & 0xFF); + } + return bytes; +} +export const bytesToUrl = bytes => { + const blob = new Blob([new Uint8Array(bytes)]); + return URL.createObjectURL(blob); +} \ No newline at end of file diff --git a/index.html b/index.html index 326d05d..88d4968 100644 --- a/index.html +++ b/index.html @@ -16,26 +16,6 @@ - -