mirror of
https://github.com/4yn/slidershim.git
synced 2025-02-02 12:37:23 +01:00
345 lines
8.5 KiB
JavaScript
345 lines
8.5 KiB
JavaScript
|
/*
|
||
|
Post-process with https://babeljs.io/repl and https://javascript-minifier.com/
|
||
|
*/
|
||
|
|
||
|
const throttle = (func, wait) => {
|
||
|
var ready = true;
|
||
|
var args = null;
|
||
|
return function throttled() {
|
||
|
var context = this;
|
||
|
if (ready) {
|
||
|
ready = false;
|
||
|
setTimeout(function () {
|
||
|
ready = true;
|
||
|
if (args) {
|
||
|
throttled.apply(context);
|
||
|
}
|
||
|
}, wait);
|
||
|
if (args) {
|
||
|
func.apply(this, args);
|
||
|
args = null;
|
||
|
} else {
|
||
|
func.apply(this, arguments);
|
||
|
}
|
||
|
} else {
|
||
|
args = arguments;
|
||
|
}
|
||
|
};
|
||
|
};
|
||
|
|
||
|
// Element refs
|
||
|
var keys = document.getElementsByClassName("key");
|
||
|
var airKeys = [];
|
||
|
var midline = 0;
|
||
|
var touchKeys = [];
|
||
|
var allKeys = [];
|
||
|
var topKeys = airKeys;
|
||
|
var bottomKeys = touchKeys;
|
||
|
const compileKey = (key) => {
|
||
|
const prev = key.previousElementSibling;
|
||
|
const next = key.nextElementSibling;
|
||
|
return {
|
||
|
top: key.offsetTop,
|
||
|
bottom: key.offsetTop + key.offsetHeight,
|
||
|
left: key.offsetLeft,
|
||
|
right: key.offsetLeft + key.offsetWidth,
|
||
|
almostLeft: !!prev ? key.offsetLeft + key.offsetWidth / 4 : -99999,
|
||
|
almostRight: !!next ? key.offsetLeft + (key.offsetWidth * 3) / 4 : 99999,
|
||
|
kflag: parseInt(key.dataset.kflag) + (parseInt(key.dataset.air) ? 32 : 0),
|
||
|
isAir: parseInt(key.dataset.air) ? true : false,
|
||
|
prevKeyRef: prev,
|
||
|
prevKeyKflag: prev
|
||
|
? parseInt(prev.dataset.kflag) + (parseInt(prev.dataset.air) ? 32 : 0)
|
||
|
: null,
|
||
|
nextKeyRef: next,
|
||
|
nextKeyKflag: next
|
||
|
? parseInt(next.dataset.kflag) + (parseInt(next.dataset.air) ? 32 : 0)
|
||
|
: null,
|
||
|
ref: key,
|
||
|
};
|
||
|
};
|
||
|
const isInside = (x, y, compiledKey) => {
|
||
|
return (
|
||
|
compiledKey.left <= x &&
|
||
|
x < compiledKey.right &&
|
||
|
compiledKey.top <= y &&
|
||
|
y < compiledKey.bottom
|
||
|
);
|
||
|
};
|
||
|
const compileKeys = () => {
|
||
|
keys = document.getElementsByClassName("key");
|
||
|
airKeys = [];
|
||
|
touchKeys = [];
|
||
|
for (var i = 0, key; i < keys.length; i++) {
|
||
|
const compiledKey = compileKey(keys[i]);
|
||
|
if (!compiledKey.isAir) {
|
||
|
touchKeys.push(compiledKey);
|
||
|
} else {
|
||
|
airKeys.push(compiledKey);
|
||
|
}
|
||
|
allKeys.push(compiledKey);
|
||
|
}
|
||
|
|
||
|
if (!config.invert) {
|
||
|
// Not inverted
|
||
|
topKeys = airKeys;
|
||
|
bottomKeys = touchKeys;
|
||
|
midline = touchKeys[0].top;
|
||
|
} else {
|
||
|
// Inverted
|
||
|
topKeys = touchKeys;
|
||
|
bottomKeys = airKeys;
|
||
|
midline = touchKeys[0].bottom;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const getKey = (x, y) => {
|
||
|
if (y < midline) {
|
||
|
for (var i = 0; i < topKeys.length; i++) {
|
||
|
if (isInside(x, y, topKeys[i])) return topKeys[i];
|
||
|
}
|
||
|
} else {
|
||
|
for (var i = 0; i < bottomKeys.length; i++) {
|
||
|
if (isInside(x, y, bottomKeys[i])) {
|
||
|
return bottomKeys[i];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
};
|
||
|
|
||
|
// Button State
|
||
|
// prettier-ignore
|
||
|
var lastState = [
|
||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||
|
];
|
||
|
|
||
|
function updateTouches(e) {
|
||
|
try {
|
||
|
e.preventDefault();
|
||
|
|
||
|
// prettier-ignore
|
||
|
var keyFlags = [
|
||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||
|
];
|
||
|
|
||
|
throttledRequestFullscreen();
|
||
|
|
||
|
for (var i = 0; i < e.touches.length; i++) {
|
||
|
const touch = e.touches[i];
|
||
|
|
||
|
const x = touch.clientX;
|
||
|
const y = touch.clientY;
|
||
|
|
||
|
const key = getKey(x, y);
|
||
|
|
||
|
if (!key) continue;
|
||
|
|
||
|
setKey(keyFlags, key.kflag, key.isAir);
|
||
|
|
||
|
if (key.isAir) continue;
|
||
|
|
||
|
if (x < key.almostLeft) {
|
||
|
setKey(keyFlags, key.prevKeyKflag, false);
|
||
|
}
|
||
|
|
||
|
if (key.almostRight < x) {
|
||
|
setKey(keyFlags, key.nextKeyKflag, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Render keys
|
||
|
for (var i = 0; i < allKeys.length; i++) {
|
||
|
const key = allKeys[i];
|
||
|
const kflag = key.kflag;
|
||
|
if (keyFlags[kflag] !== lastState[kflag]) {
|
||
|
if (keyFlags[kflag]) {
|
||
|
key.ref.setAttribute("data-active", "");
|
||
|
} else {
|
||
|
key.ref.removeAttribute("data-active");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (keyFlags !== lastState) {
|
||
|
throttledSendKeys(keyFlags);
|
||
|
}
|
||
|
lastState = keyFlags;
|
||
|
} catch (err) {
|
||
|
alert(err);
|
||
|
}
|
||
|
}
|
||
|
const throttledUpdateTouches = throttle(updateTouches, 10);
|
||
|
|
||
|
const setKey = (keyFlags, kflag, isAir) => {
|
||
|
var idx = kflag;
|
||
|
if (keyFlags[idx] && !isAir) {
|
||
|
idx++;
|
||
|
}
|
||
|
keyFlags[idx] = 1;
|
||
|
};
|
||
|
|
||
|
const sendKeys = (keyFlags) => {
|
||
|
if (wsConnected) {
|
||
|
ws.send("b" + keyFlags.join(""));
|
||
|
}
|
||
|
};
|
||
|
const throttledSendKeys = throttle(sendKeys, 10);
|
||
|
|
||
|
// Websockets
|
||
|
var ws = null;
|
||
|
var wsTimeout = 0;
|
||
|
var wsConnected = false;
|
||
|
const wsConnect = () => {
|
||
|
ws = new WebSocket("ws://" + location.host + "/ws");
|
||
|
ws.binaryType = "arraybuffer";
|
||
|
ws.onopen = () => {
|
||
|
ws.send("alive?");
|
||
|
};
|
||
|
ws.onmessage = (e) => {
|
||
|
if (e.data.byteLength) {
|
||
|
updateLed(e.data);
|
||
|
} else if (e.data == "alive") {
|
||
|
wsTimeout = 0;
|
||
|
wsConnected = true;
|
||
|
}
|
||
|
};
|
||
|
};
|
||
|
const wsWatch = () => {
|
||
|
if (wsTimeout++ > 2) {
|
||
|
wsTimeout = 0;
|
||
|
ws.close();
|
||
|
wsConnected = false;
|
||
|
wsConnect();
|
||
|
return;
|
||
|
}
|
||
|
if (wsConnected) {
|
||
|
ws.send("alive?");
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// Canvas vars
|
||
|
var canvas = document.getElementById("canvas");
|
||
|
var canvasCtx = canvas.getContext("2d");
|
||
|
var canvasData = canvasCtx.getImageData(0, 0, 33, 1);
|
||
|
const setupLed = () => {
|
||
|
for (var i = 0; i < 33; i++) {
|
||
|
canvasData.data[i * 4 + 3] = 255;
|
||
|
}
|
||
|
};
|
||
|
setupLed();
|
||
|
const updateLed = (data) => {
|
||
|
const buf = new Uint8Array(data);
|
||
|
for (var i = 0; i < 32; i++) {
|
||
|
canvasData.data[i * 4] = buf[(31 - i) * 3 + 1]; // r
|
||
|
canvasData.data[i * 4 + 1] = buf[(31 - i) * 3 + 2]; // g
|
||
|
canvasData.data[i * 4 + 2] = buf[(31 - i) * 3 + 0]; // b
|
||
|
}
|
||
|
// Copy from first led
|
||
|
canvasData.data[128] = buf[94];
|
||
|
canvasData.data[129] = buf[95];
|
||
|
canvasData.data[130] = buf[93];
|
||
|
canvasCtx.putImageData(canvasData, 0, 0);
|
||
|
};
|
||
|
|
||
|
// Fullscreener
|
||
|
const fs = document.getElementById("fullscreen");
|
||
|
const requestFullscreen = () => {
|
||
|
if (!document.fullscreenElement && screen.height <= 1024) {
|
||
|
if (fs.requestFullscreen) {
|
||
|
fs.requestFullscreen();
|
||
|
} else if (fs.mozRequestFullScreen) {
|
||
|
fs.mozRequestFullScreen();
|
||
|
} else if (fs.webkitRequestFullScreen) {
|
||
|
fs.webkitRequestFullScreen();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
const throttledRequestFullscreen = throttle(requestFullscreen, 3000);
|
||
|
|
||
|
// Do update hooks
|
||
|
const cnt = document.getElementById("main");
|
||
|
|
||
|
cnt.addEventListener("touchstart", updateTouches);
|
||
|
cnt.addEventListener("touchmove", updateTouches);
|
||
|
cnt.addEventListener("touchend", updateTouches);
|
||
|
|
||
|
// cnt.addEventListener("touchstart", throttledUpdateTouches);
|
||
|
// cnt.addEventListener("touchmove", throttledUpdateTouches);
|
||
|
// cnt.addEventListener("touchend", throttledUpdateTouches);
|
||
|
|
||
|
// Load config
|
||
|
const readConfig = (config) => {
|
||
|
var style = "";
|
||
|
|
||
|
if (!!config.invert) {
|
||
|
style += `.container, .air-container {flex-flow: column-reverse nowrap;} `;
|
||
|
}
|
||
|
|
||
|
var bgColor = config.bgColor || "rbga(0, 0, 0, 0.9)";
|
||
|
if (!config.bgImage) {
|
||
|
style += `#fullscreen {background: ${bgColor};} `;
|
||
|
} else {
|
||
|
style += `#fullscreen {background: ${bgColor} url("${config.bgImage}") fixed center / cover!important; background-repeat: no-repeat;} `;
|
||
|
}
|
||
|
|
||
|
if (typeof config.ledOpacity === "number") {
|
||
|
if (config.ledOpacity === 0) {
|
||
|
style += `#canvas {display: none} `;
|
||
|
} else {
|
||
|
style += `#canvas {opacity: ${config.ledOpacity}} `;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (typeof config.keyColor === "string") {
|
||
|
style += `.key[data-active] {background-color: ${config.keyColor};} `;
|
||
|
}
|
||
|
if (typeof config.keyColor === "string") {
|
||
|
style += `.key.air[data-active] {background-color: ${config.lkeyColor};} `;
|
||
|
}
|
||
|
if (typeof config.keyBorderColor === "string") {
|
||
|
style += `.key {border: 1px solid ${config.keyBorderColor};} `;
|
||
|
}
|
||
|
if (!!config.keyColorFade && typeof config.keyColorFade === "number") {
|
||
|
style += `.key:not([data-active]) {transition: background ${config.keyColorFade}ms ease-out;} `;
|
||
|
}
|
||
|
|
||
|
if (typeof config.keyHeight === "number") {
|
||
|
if (config.keyHeight === 0) {
|
||
|
style += `.touch-container {display: none;} `;
|
||
|
} else {
|
||
|
style += `.touch-container {flex: ${config.keyHeight};} `;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (typeof config.lkeyHeight === "number") {
|
||
|
if (config.lkeyHeight === 0) {
|
||
|
style += `.air-container {display: none;} `;
|
||
|
} else {
|
||
|
style += `.air-container {flex: ${config.keyHeight};} `;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var styleRef = document.createElement("style");
|
||
|
styleRef.innerHTML = style;
|
||
|
document.head.appendChild(styleRef);
|
||
|
};
|
||
|
|
||
|
// Initialize
|
||
|
const initialize = () => {
|
||
|
readConfig(config);
|
||
|
compileKeys();
|
||
|
wsConnect();
|
||
|
setInterval(wsWatch, 1000);
|
||
|
};
|
||
|
initialize();
|
||
|
|
||
|
// Update keys on resize
|
||
|
window.onresize = compileKeys;
|