document.addEventListener("DOMContentLoaded", main);
function getState() {
const _mouseAcceleration = buildValue({
defaultValue: 10,
localStorageKey: "_mouseAcceleration",
});
const _scrollSpeed = buildValue({
defaultValue: 5,
localStorageKey: "_scrollSpeed",
});
const _gyromouseSwitch = buildValue({
defaultValue: false,
localStorageKey: "_gyromouseSwitch",
});
const _gyromouseSpeed = buildValue({
defaultValue: 10,
localStorageKey: "_gyromouseSpeed",
});
let _WSConnected = false;
function buildValue(param) {
function nullIfThrows(callback) {
try {
return callback();
} catch {
return null;
}
}
function getJsonStoreFromLocalStorage(param) {
const data = localStorage.getItem(param.localStorageKey);
if (data == null) {
return param.defaultValue;
}
const parsed = nullIfThrows(() => JSON.parse(data));
if (parsed == null) {
return param.defaultValue;
}
const value = parsed.store;
if (value == null) {
return param.defaultValue;
}
return value;
}
const value = getJsonStoreFromLocalStorage(param);
return {
...param,
value,
};
}
function setValue(param) {
setTimeout(() => {
localStorage.setItem(
param.localStorageKey,
JSON.stringify({ store: param.value })
);
});
}
return {
get WSConnected() {
return _WSConnected;
},
set WSConnected(value) {
_WSConnected = value;
connected.style.display = value ? "" : "none";
connect.style.display = value ? "none" : "";
light.classList.toggle("on", value);
},
get mouseAcceleration() {
return _mouseAcceleration.value;
},
set mouseAcceleration(value) {
_mouseAcceleration.value = value;
setValue(_mouseAcceleration);
},
get scrollSpeed() {
return _scrollSpeed.value;
},
set scrollSpeed(value) {
_scrollSpeed.value = value;
setValue(_scrollSpeed);
},
get gyromouseSwitch() {
return _gyromouseSwitch.value;
},
set gyromouseSwitch(value) {
touchpad.style.display = value ? "none" : "";
gryropad.style.display = value ? "" : "none";
_gyromouseSwitch.value = value;
setValue(_gyromouseSwitch);
},
get gyromouseSpeed() {
return _gyromouseSpeed.value;
},
set gyromouseSpeed(value) {
_gyromouseSpeed.value = value;
setValue(_gyromouseSpeed);
},
ws: null,
};
}
const state = getState();
function main() {
touchpadBinding(touchpad);
gryropadBinding(gryropad);
leftbtn.addEventListener("click", () => {
lefthold.checked = false;
lefthold.parentElement.open = false;
click("left");
});
leftholdBinding(lefthold);
middlebtnBinding(middlebtn);
rightbtn.addEventListener("click", () => click("right"));
kbrdcut.addEventListener("click", () => {
keyboardAction({ action: "cut" });
});
kbrdcopy.addEventListener("click", () => {
keyboardAction({ action: "copy" });
});
kbrdpaste.addEventListener("click", () => {
keyboardAction({ action: "paste" });
});
kbrdenter.addEventListener("click", () => {
keyboardAction({ action: "enter" });
});
form.addEventListener("submit", onsubmit);
fullscreen_button.addEventListener("click", (e) => {
const element = document.body;
if (document.fullscreenElement == null) {
element.requestFullscreen();
} else {
document.exitFullscreen();
}
});
connect.addEventListener("click", WSConnect);
maccelBinding(maccel);
scrollSpeedBinding(scroll_speed);
gyromouseSwitchBinding(gyromouse_switch);
gyromouseSpeedBinding(gyromouse_speed);
WSConnect();
document.addEventListener("visibilitychange", () => {
if (document.visibilityState === "visible") {
WSConnect();
}
});
}
function WSConnect() {
if (state.WSConnected) return;
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
state.ws = new WebSocket(`${protocol}//${window.location.host}/realtime`);
state.ws.addEventListener("open", (event) => {
state.WSConnected = true;
console.log("[websocket] connection open", event);
});
state.ws.addEventListener("close", (event) => {
state.WSConnected = false;
console.log("[websocket] connection closed", event);
});
state.ws.addEventListener("message", (event) => {
console.log("[websocket] message received", event);
});
}
function remap(value, low_from, high_from, low_to, high_to) {
return (
low_to + ((high_to - low_to) * (value - low_from)) / (high_from - low_from)
);
}
function touchpadBinding(node) {
let ongoingtouch = null;
let starttouch = null;
const ontouchstart = (e) => {
if (ongoingtouch != null) {
return;
}
ongoingtouch = {
x: e.touches[0].clientX,
y: e.touches[0].clientY,
};
starttouch = ongoingtouch;
};
const ontouchmove = (e) => {
if (ongoingtouch == null) {
return;
}
const position = {
x: e.touches[0].clientX,
y: e.touches[0].clientY,
};
const windowWidth = window.innerWidth;
const factor = {
x:
1 +
(state.mouseAcceleration - 1) *
(Math.abs(starttouch.x - position.x) / windowWidth),
y:
1 +
(state.mouseAcceleration - 1) *
(Math.abs(starttouch.y - position.y) / windowWidth),
};
const difference = {
x: (-ongoingtouch.x + position.x) * factor.x,
y: (-ongoingtouch.y + position.y) * factor.y,
};
state.ws?.send(JSON.stringify({ difference }));
ongoingtouch = position;
};
const ontouchend = () => {
if (starttouch != null && starttouch == ongoingtouch) {
state.ws?.send(JSON.stringify({ click: "left" }));
}
ongoingtouch = null;
starttouch = null;
};
node.addEventListener("touchstart", ontouchstart);
node.addEventListener("touchmove", ontouchmove);
node.addEventListener("touchend", ontouchend);
}
function gryropadBinding(node) {
let ongoingtouch = false;
let orientation = null;
const process = (orientation, previousOrientation) => {
if (!ongoingtouch) {
return;
}
if (previousOrientation == null) {
return;
}
const angleDiff = (current, previous) =>
Math.atan2(Math.sin(current - previous), Math.cos(current - previous));
const factor = remap(state.gyromouseSpeed, 1, 20, 5, 50);
const difference = {
x: -angleDiff(orientation.alpha, previousOrientation.alpha) * factor,
y: -angleDiff(orientation.beta, previousOrientation.beta) * factor,
};
state.ws?.send(JSON.stringify({ difference }));
};
const ondeviceorientation = (e) => {
const { alpha, beta, gamma } = event;
process({ alpha, beta, gamma }, orientation);
orientation = { alpha, beta, gamma };
};
const ontouchstart = () => {
ongoingtouch = true;
};
const ontouchend = () => {
ongoingtouch = false;
};
node.addEventListener("touchstart", ontouchstart);
node.addEventListener("touchend", ontouchend);
addEventListener("deviceorientation", ondeviceorientation);
}
function click(dir) {
state.ws?.send(JSON.stringify({ click: dir }));
}
function leftholdBinding(node) {
node.parentElement.addEventListener("click", (e) => {
e.stopImmediatePropagation();
});
node.addEventListener("input", (e) => {
click(e.target.checked ? "left_hold" : "left_release");
});
}
function middlebtnBinding(node) {
let ongoingtouch = null;
let starttouch = null;
const ontouchstart = (e) => {
if (ongoingtouch != null) {
return;
}
ongoingtouch = {
x: e.touches[0].clientX,
y: e.touches[0].clientY,
};
starttouch = ongoingtouch;
};
const ontouchmove = (e) => {
if (ongoingtouch == null) {
return;
}
const position = {
x: e.touches[0].clientX,
y: e.touches[0].clientY,
};
const value = {
x: position.x - starttouch.x,
y: starttouch.y - position.y,
};
value.x = Math.abs(value.x) < Math.abs(value.y) ? 0 : value.x;
value.y = Math.abs(value.x) >= Math.abs(value.y) ? 0 : value.y;
const sign = {
x: Math.sign(value.x),
y: Math.sign(value.y),
};
const scrollMod = state.scrollSpeed / 20;
state.ws?.send(
JSON.stringify({
scroll: {
x: sign.x * scrollMod,
y: sign.y * scrollMod,
},
})
);
ongoingtouch = position;
};
const ontouchend = () => {
if (starttouch != null && starttouch == ongoingtouch) {
state.ws?.send(JSON.stringify({ click: "middle" }));
}
ongoingtouch = null;
starttouch = null;
};
node.addEventListener("touchstart", ontouchstart);
node.addEventListener("touchmove", ontouchmove);
node.addEventListener("touchend", ontouchend);
}
function keyboardAction(data) {
state.ws?.send(JSON.stringify(data));
}
function onsubmit(e) {
e.preventDefault();
const formElement = e.target;
const formData = new FormData(formElement);
const text = formData.get("text");
if (text.length === 0) {
keyboardAction({ action: "enter" });
} else {
state.ws?.send(JSON.stringify({ text }));
}
formElement.reset();
}
function maccelBinding(node) {
node.value = state.mouseAcceleration;
maccel_value_display.innerText = `: ${state.mouseAcceleration}`;
node.addEventListener("input", (e) => {
state.mouseAcceleration = Number(e.target.value);
maccel_value_display.innerText = `: ${state.mouseAcceleration}`;
});
}
function scrollSpeedBinding(node) {
node.value = state.scrollSpeed;
scroll_speed_value_display.innerText = `: ${state.scrollSpeed}`;
node.addEventListener("input", (e) => {
state.scrollSpeed = Number(e.target.value);
scroll_speed_value_display.innerText = `: ${state.scrollSpeed}`;
});
}
function gyromouseSwitchBinding(node) {
state.gyromouseSwitch = state.gyromouseSwitch; node.checked = state.gyromouseSwitch;
node.addEventListener("input", (e) => {
state.gyromouseSwitch = e.target.checked;
});
}
function gyromouseSpeedBinding(node) {
node.value = state.gyromouseSpeed;
gyromouse_speed_value_display.innerText = `: ${state.gyromouseSpeed}`;
node.addEventListener("input", (e) => {
state.gyromouseSpeed = Number(e.target.value);
gyromouse_speed_value_display.innerText = `: ${state.gyromouseSpeed}`;
});
}