Skip to content

Commit

Permalink
Improve demo
Browse files Browse the repository at this point in the history
  • Loading branch information
timruffles committed Jun 17, 2018
1 parent 8173db4 commit a6e47f2
Show file tree
Hide file tree
Showing 6 changed files with 2,340 additions and 163 deletions.
54 changes: 21 additions & 33 deletions base-lol.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/**
* - Always encoded as UTF-8 - as efficient as UTF-16 for non-BMP,
* and a good standard
* Encodes binary data as emojis in utf8.
*/

// use the most recognisable emojis for ascii/utf8 text
Expand All @@ -17,38 +16,7 @@ const highEnd = highStart + 127;
// as utf8
const emojiLowElement = 55357;

export function decodeString(emojiString) {
const bytes = new Array(emojiString.length/2);
for(let i = 0; i < emojiString.length; i+=2) {
if(emojiString.charCodeAt(i) !== emojiLowElement) {
throw Error(`failed to decode string at index ${i}: '${emojiString.slice(i, i+2)}'`);
}
const codePoint = emojiString.codePointAt(i);
bytes[i/2] = decode(codePoint);
}
return bytes;
}

export function encodeUInt8Array(ints) {
return ints.map(encode).join('');
}

export function encodeString(s) {
return s.split("").map(s => encode(s.charCodeAt(0))).join('');
}

export function encode(byte) {
if(byte < 65) {
return String.fromCodePoint(byte + lowStart);
} if (byte >= 128) {
return String.fromCodePoint(byte + highStart - 128);
} else {
// ascii printable
return String.fromCodePoint(asciiTextStart + byte - 65);
}
}

export function encodeTo(byte) {
if(byte < 65) {
return String.fromCodePoint(byte + lowStart);
} if (byte >= 128) {
Expand All @@ -70,3 +38,23 @@ export function decode(codePoint) {
throw Error(`character out of range '${codePoint}'`);
}
}

export function decodeString(emojiString) {
const bytes = new Array(emojiString.length/2);
for(let i = 0; i < emojiString.length; i+=2) {
if(emojiString.charCodeAt(i) !== emojiLowElement) {
throw Error(`failed to decode string at index ${i}: '${emojiString.slice(i, i+2)}'`);
}
const codePoint = emojiString.codePointAt(i);
bytes[i/2] = decode(codePoint);
}
return bytes;
}

export function encodeUInt8Array(ints) {
return ints.map(encode).join('');
}

export function encodeString(s) {
return s.split("").map(s => encode(s.charCodeAt(0))).join('');
}
49 changes: 43 additions & 6 deletions demo-page.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import './node_modules/fast-text-encoding/text.min.js';
import { encode, decode, decodeString, encodeString } from './base-lol.mjs';

const BYTES_PREVIEW = 4;
const EMOJI_PER_BYTE = 4;
// two JS chars per emoji
const CHARACTERS_PER_EMOJI = 2;

document.addEventListener('DOMContentLoaded', main);

function main() {
// exposed for console-based fun
window.baseLol = {
encode,
decode,
Expand All @@ -13,14 +20,10 @@ function main() {
if(!assertBrowserSupport()) return;

addDragListeners();
addFileListeners();
}

function assertBrowserSupport() {
if(typeof TextDecoder === 'undefined' || !('ondrop' in document.body)) {
alert("Sorry - this was a on-the-tube hack, only Browsers with TextDecoder and drag/drop");
return false;
}

if(typeof Symbol === 'undefined') {
alert("Sorry - this was a on-the-tube hack, ES2015+ browsers only :)");
return false;
Expand Down Expand Up @@ -72,7 +75,7 @@ function addDragListeners() {


function encodeFiles(files) {
for(const file of files) {
for(const [index, file] of files.entries()) {
const reader = new FileReader();
reader.addEventListener("loadend", () => {
const bytes = new Uint8Array(reader.result);
Expand All @@ -83,12 +86,28 @@ function encodeFiles(files) {
const filename = file.name.replace(/(\.[^\.]+)?$/,
'$1.base-lol');

if(index === 0) {
preview(
Array.from(bytes.slice(0, BYTES_PREVIEW)),
encoded.slice(0, BYTES_PREVIEW * EMOJI_PER_BYTE * CHARACTERS_PER_EMOJI)
);
}

download(filename, new Blob([encoded]));
});
reader.readAsArrayBuffer(file);
}
}

function preview(bytes, emoji) {
const status = document.querySelector('.status');
status.classList.remove('hidden');
const bytesAsString = bytes.map(i => `0x${i.toString(16)}`).join(' ');
status.querySelector('.bytePreview').innerHTML = bytesAsString;
status.querySelector('.previewCount').innerHTML = BYTES_PREVIEW;
status.querySelector('.preview').innerHTML = emoji;
}

function decodeFiles(files) {
for(const file of files) {
const reader = new FileReader();
Expand All @@ -110,6 +129,24 @@ function decodeFiles(files) {
}
}

function addFileListeners() {
for (const el of document.querySelectorAll('.uploadBlock input[type=file]')) {
el.addEventListener('change', e => {
const files = [...event.target.files];
const isEncode = e.target
.closest('[data-action]').dataset.action
=== 'encode';
const handler = isEncode
? encodeFiles
: decodeFiles;

handler(files);
});

}

}

function download(filename, data) {
const element = document.createElement('a');
const url = URL.createObjectURL(data);
Expand Down
54 changes: 48 additions & 6 deletions dist/demo-page.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
(function () {
'use strict';

(function(l){function m(b){b=void 0===b?"utf-8":b;if("utf-8"!==b)throw new RangeError("Failed to construct 'TextEncoder': The encoding label provided ('"+b+"') is invalid.");}function k(b,a){b=void 0===b?"utf-8":b;a=void 0===a?{fatal:!1}:a;if("utf-8"!==b)throw new RangeError("Failed to construct 'TextDecoder': The encoding label provided ('"+b+"') is invalid.");if(a.fatal)throw Error("Failed to construct 'TextDecoder': the 'fatal' option is unsupported.");}if(l.TextEncoder&&l.TextDecoder)return !1;
Object.defineProperty(m.prototype,"encoding",{value:"utf-8"});m.prototype.encode=function(b,a){a=void 0===a?{stream:!1}:a;if(a.stream)throw Error("Failed to encode: the 'stream' option is unsupported.");a=0;for(var h=b.length,f=0,c=Math.max(32,h+(h>>1)+7),e=new Uint8Array(c>>3<<3);a<h;){var d=b.charCodeAt(a++);if(55296<=d&&56319>=d){if(a<h){var g=b.charCodeAt(a);56320===(g&64512)&&(++a,d=((d&1023)<<10)+(g&1023)+65536);}if(55296<=d&&56319>=d)continue}f+4>e.length&&(c+=8,c*=1+a/b.length*2,c=c>>3<<3,
g=new Uint8Array(c),g.set(e),e=g);if(0===(d&4294967168))e[f++]=d;else{if(0===(d&4294965248))e[f++]=d>>6&31|192;else if(0===(d&4294901760))e[f++]=d>>12&15|224,e[f++]=d>>6&63|128;else if(0===(d&4292870144))e[f++]=d>>18&7|240,e[f++]=d>>12&63|128,e[f++]=d>>6&63|128;else continue;e[f++]=d&63|128;}}return e.slice(0,f)};Object.defineProperty(k.prototype,"encoding",{value:"utf-8"});Object.defineProperty(k.prototype,"fatal",{value:!1});Object.defineProperty(k.prototype,"ignoreBOM",{value:!1});k.prototype.decode=
function(b,a){a=void 0===a?{stream:!1}:a;if(a.stream)throw Error("Failed to decode: the 'stream' option is unsupported.");b=new Uint8Array(b);a=0;for(var h=b.length,f=[];a<h;){var c=b[a++];if(0===c)break;if(0===(c&128))f.push(c);else if(192===(c&224)){var e=b[a++]&63;f.push((c&31)<<6|e);}else if(224===(c&240)){e=b[a++]&63;var d=b[a++]&63;f.push((c&31)<<12|e<<6|d);}else if(240===(c&248)){e=b[a++]&63;d=b[a++]&63;var g=b[a++]&63;c=(c&7)<<18|e<<12|d<<6|g;65535<c&&(c-=65536,f.push(c>>>10&1023|55296),c=56320|
c&1023);f.push(c);}}return String.fromCharCode.apply(null,f)};l.TextEncoder=m;l.TextDecoder=k;})("undefined"!==typeof window?window:"undefined"!==typeof global?global:undefined);

/**
* - Always encoded as UTF-8 - as efficient as UTF-16 for non-BMP,
* and a good standard
Expand Down Expand Up @@ -59,9 +65,15 @@
}
}

const BYTES_PREVIEW = 4;
const EMOJI_PER_BYTE = 4;
// two JS chars per emoji
const CHARACTERS_PER_EMOJI = 2;

document.addEventListener('DOMContentLoaded', main);

function main() {
// exposed for console-based fun
window.baseLol = {
encode,
decode,
Expand All @@ -72,14 +84,10 @@
if(!assertBrowserSupport()) return;

addDragListeners();
addFileListeners();
}

function assertBrowserSupport() {
if(typeof TextDecoder === 'undefined' || !('ondrop' in document.body)) {
alert("Sorry - this was a on-the-tube hack, only Browsers with TextDecoder and drag/drop");
return false;
}

if(typeof Symbol === 'undefined') {
alert("Sorry - this was a on-the-tube hack, ES2015+ browsers only :)");
return false;
Expand Down Expand Up @@ -131,7 +139,7 @@


function encodeFiles(files) {
for(const file of files) {
for(const [index, file] of files.entries()) {
const reader = new FileReader();
reader.addEventListener("loadend", () => {
const bytes = new Uint8Array(reader.result);
Expand All @@ -142,12 +150,28 @@
const filename = file.name.replace(/(\.[^\.]+)?$/,
'$1.base-lol');

if(index === 0) {
preview(
Array.from(bytes.slice(0, BYTES_PREVIEW)),
encoded.slice(0, BYTES_PREVIEW * EMOJI_PER_BYTE * CHARACTERS_PER_EMOJI)
);
}

download(filename, new Blob([encoded]));
});
reader.readAsArrayBuffer(file);
}
}

function preview(bytes, emoji) {
const status = document.querySelector('.status');
status.classList.remove('hidden');
const bytesAsString = bytes.map(i => `0x${i.toString(16)}`).join(' ');
status.querySelector('.bytePreview').innerHTML = bytesAsString;
status.querySelector('.previewCount').innerHTML = BYTES_PREVIEW;
status.querySelector('.preview').innerHTML = emoji;
}

function decodeFiles(files) {
for(const file of files) {
const reader = new FileReader();
Expand All @@ -169,6 +193,24 @@
}
}

function addFileListeners() {
for (const el of document.querySelectorAll('.uploadBlock input[type=file]')) {
el.addEventListener('change', e => {
const files = [...event.target.files];
const isEncode = e.target
.closest('[data-action]').dataset.action
=== 'encode';
const handler = isEncode
? encodeFiles
: decodeFiles;

handler(files);
});

}

}

function download(filename, data) {
const element = document.createElement('a');
const url = URL.createObjectURL(data);
Expand Down
23 changes: 20 additions & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,22 @@
background: rgba(255,255,255,0.1);
padding: 2px;
}

.minor {
font-size: 1rem;
}

.hidden {
display: none;
}

.status {
font-size: 1rem;
}

.bytePreview {
font-family: monospace;
}
</style>

<script src="dist/demo-page.js"
Expand All @@ -60,7 +73,7 @@ <h1>base-lol - encode binary data as emojis</h1>
and into a browser Richard Stallman!
</noscript>

<p>Encodes binary data as a sequence of emojis in UTF-8. Ready for embedding in tweets, file-names, WiFi SSIDs etc! Like it? Say hi <a href="https://twitter.com/timruffles">@timruffles</a>.</p>
<p>Encodes binary data as a sequence of emojis in UTF-8. Ready for embedding in tweets, file-names, WiFi SSIDs etc! Read <a href="https://github.com/timruffles/base-lol/blob/gh-pages/base-lol.mjs">the code</a>, and say hi <a href="https://twitter.com/timruffles">@timruffles</a>.</p>

<p>As an example, the byte sequence <code>0xcafecafe</code> becomes 💋💿💋💿. This SVG <img style="width: 1em; height: 1em; transform: scale(2) translate(4px, 4px); margin-right: .25em" src="./targets/smiley.svg"> (gzipped) becomes:</p>

Expand All @@ -69,12 +82,16 @@ <h1>base-lol - encode binary data as emojis</h1>
<p>Drag and drop files to encode and decode.</p>

<div class="uploadBlock" data-action="encode">
<p>1️⃣0️⃣1️⃣1️⃣ ➡ 🐋
<p>1️⃣0️⃣1️⃣1️⃣ ➡ 🐋 <input type="file" /></p>
<p class="status hidden">
First <span class="previewCount"></span> bytes (<span class="bytePreview"></span>) of your file encoded to:
<span class="preview"></span>
</p>
<p class="minor">File to emojis</p>
</div>

<div class="uploadBlock" data-action="decode">
<p>🐋 ➡ 1️⃣0️⃣1️⃣1️⃣
<p>🐋 ➡ 1️⃣0️⃣1️⃣1️⃣ <input type="file" /></p>
<p class="minor">Emojis to file</p>
</div>
</body>
Loading

0 comments on commit a6e47f2

Please sign in to comment.