Added webapp functionality

This commit is contained in:
Brikwerk 2020-09-12 22:04:25 -07:00
commit 22cdc98d6c
9 changed files with 1581 additions and 69 deletions

View file

@ -1,8 +1,10 @@
import json
import os
from threading import RLock
from time import perf_counter
from ..nxbt import Nxbt
from flask import Flask, render_template
from ..nxbt import Nxbt, PRO_CONTROLLER
from flask import Flask, render_template, request
from flask_socketio import SocketIO, emit
import eventlet
@ -29,6 +31,9 @@ app.config['SECRET_KEY'] = secret_key
# Starting socket server with Flask app
sio = SocketIO(app, cookie=False)
user_info_lock = RLock()
USER_INFO = {}
@app.route('/')
def index():
@ -37,24 +42,70 @@ def index():
@sio.on('connect')
def on_connect():
print("Connected")
index = nxbt.create_controller()
with user_info_lock:
USER_INFO[request.sid] = {}
@sio.on('state')
def on_state():
state_proxy = nxbt.state.copy()
state = {}
for controller in state_proxy.keys():
state[controller] = state_proxy[controller].copy()
emit('state', state)
@sio.on('disconnect')
def on_disconnect():
print("Disconnected")
with user_info_lock:
try:
index = USER_INFO[request.sid]["controller_index"]
nxbt.remove_controller(index)
except KeyError:
pass
@sio.on('create_controller')
@sio.on('shutdown')
def on_shutdown(index):
nxbt.remove_controller(index)
@sio.on('create_pro_controller')
def on_create_controller():
pass
print("Create Controller")
try:
reconnect_addresses = nxbt.get_switch_addresses()
index = nxbt.create_controller(PRO_CONTROLLER, reconnect_address=reconnect_addresses)
with user_info_lock:
USER_INFO[request.sid]["controller_index"] = index
emit('create_pro_controller', index)
except Exception as e:
emit('error', str(e))
@sio.on('input')
def handle_input(message):
print(json.loads(message))
message = json.loads(message)
index = message[0]
input_packet = message[1]
nxbt.set_controller_input(index, input_packet)
@sio.on('macro')
def handle_macro(message):
message = json.loads(message)
index = message[0]
macro = message[1]
nxbt.macro(index, macro)
def start_web_app():
eventlet.wsgi.server(eventlet.listen(('', 8000)), app)
eventlet.wsgi.server(eventlet.listen(('127.0.0.1', 8000)), app)
if __name__ == "__main__":
start_web_app()

View file

@ -0,0 +1,636 @@
/**************************************************
General Stylings
**************************************************/
html, body {
padding: 0;
margin: 0;
background-color: #1a1f28;
width: 100%;
height: 100%;
display: block;
}
* {
color: #d9dada;
font-family: Helvetica, Arial, sans-serif;
}
main {
max-width: 1200px;
margin: auto;
height: 100%;
width: 100%;
padding-top: 5rem;
box-sizing: border-box;
}
footer {
text-align: center;
padding: 1rem 4rem;
}
footer p, footer a {
margin: 0;
color: #3f5066;
}
h1 {
font-weight: 700;
}
p {
padding: 0;
line-height: 1.4;
}
button {
color: #1a1f28;
padding: 1rem;
border-radius: 5px;
margin: auto;
font-weight: 700;
text-transform: uppercase;
display: block;
-webkit-appearance: none;
background-color: #d9dada;
font-size: 0.9rem;
}
button:hover {
cursor: pointer;
}
.mono {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace !important
}
.surface {
display: block;
background-color: #2c3544;
border-radius: 5px;
box-shadow: 0px 1px 2px -1px rgba(0,0,0,0.2), 0px 4px 2px 0px rgba(0,0,0,0.14);
}
.surface-lighter {
display: block;
background-color: #3a4759;
border-radius: 5px;
box-shadow: 0px 2px 1px -1px rgba(0,0,0,0.2), 0px 1px 1px 0px rgba(0,0,0,0.14), 0px 1px 3px 0px rgba(0,0,0,0.12)
}
.hidden {
display: none !important;
}
.text-section p {
text-align: left;
}
/**************************************************
Top Bar Stying
**************************************************/
.top-bar {
display: block;
position: fixed;
width: 100%;
background-color: #2c3544;
box-shadow: 0px 2px 4px -1px rgba(0,0,0,0.2), 0px 4px 5px 0px rgba(0,0,0,0.14);
z-index: 100;
}
.top-bar-wrapper {
background-color: #2c3544;
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1200px;
margin: auto;
padding: 1rem 2rem;
}
.top-bar-wrapper h1 {
margin: 0;
display: inline-block;
}
.top-bar-wrapper .mono {
display: inline-block;
}
.top-bar-spacer {
width: 100%;
height: 5rem;
}
/**************************************************
Status Indicator Stying
**************************************************/
#status-indicator {
display: block;
background-color: #3a4759;
padding: 0.75rem 1rem 0.75rem 2.5rem;
border-radius: 5px;
position: relative;
}
#status-indicator-text {
font-size: 1rem;
text-transform: uppercase;
}
#status-indicator-light {
border-radius: 100%;
height: 0.8rem;
width: 0.8rem;
position: absolute;
left: 1rem;
top: 0.8rem;
}
.indicator-green {
background-color: #5fc54f;
border: 1px solid #55a83d;
}
.indicator-yellow {
background-color: #f6be45;
border: 1px solid #d9a33b;
}
.indicator-red {
background-color: #ef6b5c;
border: 1px solid #d25647;
}
/**************************************************
Controller Selection Stying
**************************************************/
#controller-selection {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
align-content: center;
justify-content: center;
width: 100%;
text-align: center;
box-sizing: border-box;
padding-top: 2.5%;
padding-bottom: 2.5%;
}
#controller-selection h1 {
width: 100%;
margin: 0.5rem 0;
}
.controller-option {
position: relative;
width: 18rem;
padding: 0 0 1.5rem 0;
margin: 1rem;
display: inline-block;
font-weight: 500;
text-align: center;
text-transform: uppercase;
transition: 0.1s;
bottom: 0px;
}
.controller-option:hover {
cursor: pointer;
bottom: 5px;
transition: 0.1s;
}
.controller-option-img-wrapper {
display: block;
background-color: #3a4759;
width: 100%;
padding: 2rem 0;
border-radius: 5px;
margin-bottom: 1.5rem;
}
.controller-option-img-wrapper img {
max-height: 10rem;
display: block;
width: 100%;
}
@media screen and (max-width: 960px) {
#controller-selection {
display: block;
}
.controller-option {
width: 90%;
margin: 0.5rem;
}
.controller-option-img-wrapper img {
max-height: 5rem;
}
}
/**************************************************
Loader Stying
**************************************************/
#loader {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
align-content: center;
justify-content: center;
min-height: 40%;
width: 100%;
text-align: center;
box-sizing: border-box;
margin-top: 10%;
padding: 2rem 20%;
}
#loader-progress-wrapper {
width: 100%;
padding: 2rem 1rem;
}
#loader-blocks {
display: flex;
align-items: center;
justify-content: space-between;
width: 8rem;
margin: auto;
}
.loader-block {
width: 1rem;
height: 1rem;
background-color: #1a1f28;
}
.loader-block-highlight {
background-color: #5fc54f !important;
}
#loader-text {
width: 100%;
font-size: 1.2rem;
text-transform: uppercase;
margin-top: 1rem;
padding: 1rem;
}
#loader .text-section {
padding: 1.5rem 2rem;
margin-top: 2rem;
border-radius: 5px;
background-color: #1a1f28;
}
#loader .text-section h2 {
margin-top: 0;
margin: auto;
width: 50%;
background-color: #2c3544;
padding: 0.5rem;
border-radius: 5px;
font-size: 1.2rem;
box-sizing: border-box;
}
#loader .text-section .tip-box {
width: 100%;
text-align: left;
font-weight: 500;
font-size: 1.1rem;
margin-top: 1rem;
background-color: #1a1f28;
}
@media screen and (max-width: 960px) {
#loader {
padding-left: 5%;
padding-right: 5%;
}
}
/**************************************************
Controller Config Stying
**************************************************/
#controller-config {
padding: 2rem;
margin-top: 3rem;
box-sizing: border-box;
text-align: center;
}
#controller-underlay-container {
box-sizing: border-box;
background-color: #1a1f28;
border-radius: 5px;
padding: 2rem 10%;
}
.controller-underlay {
width: 100%;
display: block;
z-index: 2;
position: relative;
}
.controller-display {
width: 100%;
max-width: 25rem;
margin: auto;
display: block;
position: relative;
}
.control-indicator {
display: block;
position: absolute;
width: 6%;
padding-top: 6%;
border-radius: 100%;
background-color: #647f96;
z-index: 1;
}
.square-button {
border-radius: 0px !important;
}
.big-button {
width: 8% !important;
padding-top: 8% !important;
}
/** Pro Controller Control Indicator Positioning **/
#pc_ls {
left: 19.25%; /* LM: 15.5, RM: 23*/
top: 23.75%; /* TM: 18, BM: 29*/ }
#pc_rs {
left: 59.75%; /* LM: 56, RM: 63.5*/
top: 44.125%; /* TM: 38.125, BM: 50*/ }
#pc_du {
left: 30.7%;
top: 36%; }
#pc_dl {
left: 25.8%;
top: 45.2%; }
#pc_dr {
left: 36.8%;
top: 44.4%; }
#pc_dd {
left: 31.3%;
top: 52.4%; }
#pc_h {
left: 54%;
top: 24%; }
#pc_c {
left: 40%;
top: 24%; }
#pc_m {
left: 59%;
top: 12%; }
#pc_p {
left: 35%;
top: 13%; }
#pc_b {
left: 72%;
top: 32%; }
#pc_a {
left: 80%;
top: 22%; }
#pc_y {
left: 64%;
top: 22%; }
#pc_x {
left: 72%;
top: 12%; }
#pc_l {
left: 10%;
top: 2%; }
#pc_zl {
left: 17%;
top: -2%; }
#pc_r {
left: 84%;
top: 2%; }
#pc_zr {
left: 77%;
top: -2%; }
.controller-config-subsection {
padding: 1rem;
margin-top: 2%;
display: inline-block;
min-width: 48.75%;
max-width: 48.75%;
box-sizing: border-box;
vertical-align: top;
}
.controller-config-subsection h2 {
padding-bottom: 0.5rem;
padding-top: 0.5rem;
font-weight: 500;
margin-top: 2rem;
font-size: 1.25rem;
border-radius: 5px;
}
.controller-config-subsection h2:first-child {
margin-top: 0;
}
.controller-config-subsection p {
text-align: left;
padding: 0.25rem;
}
.controller-config-subsection select {
width: 99%;
padding: 0.25rem;
color: #1a1f28;
}
.controller-config-subsection:nth-child(even) {
margin-right: 1%;
}
.controller-config-subsection:nth-child(odd) {
margin-left: 1%;
}
.controller-config-subsection table {
width: 100%;
border-spacing: 0;
border-collapse: collapse;
}
.controller-config-subsection td {
border-top: 2px solid #2c3544;
border-bottom: 2px solid #2c3544;
box-sizing: border-box;
padding: 1rem;
}
.controller-config-subsection td:nth-child(1) {
text-align: left;
}
.controller-config-subsection td:nth-child(2) {
text-align: right;
}
.controller-config-subsection button {
width: 100%;
margin-bottom: 0.25rem;
margin-left: auto;
margin-right: auto;
}
#controller-map {
padding: 1rem;
}
.controller-config-subsection button:last-child {
margin-bottom: 0;
}
.controller-config-subsection textarea {
width: 98%;
margin: auto;
min-height: 10rem;
color: black;
margin-bottom: 0.25rem;
text-transform: uppercase;
box-sizing: border-box;
padding: 1rem;
}
@media screen and (max-width: 960px) {
#controller-config {
padding-left: 1rem;
padding-right: 1rem;
margin-top: 1rem;
}
.controller-config-subsection {
width: 100%;
max-width: 100%;
margin-top: 4%;
padding-left: 0;
padding-right: 0;
}
.controller-config-subsection:nth-child(even) {
margin-right: 0;
}
.controller-config-subsection:nth-child(odd) {
margin-left: 0;
}
}
/**************************************************
Controller Sessions Stying
**************************************************/
#controller-sessions {
display: block;
padding: 2rem;
max-width: 100%;
margin: auto;
margin-top: 1rem;
}
#controller-sessions h1 {
margin-top: 0
}
.controller-session {
box-sizing: border-box;
padding: 1rem;
display: flex;
align-items: center;
justify-content: space-between;
background-color: #1a1f28;
border-radius: 5px;
}
.controller-session h1 {
font-size: 1.3rem;
padding: 0;
margin: 0;
display: inline-block;
text-align: left;
}
.controller-session button {
font-size: 1rem;
padding: 0.75rem 1rem;
margin: 0;
display: inline-block;
}
@media screen and (max-width: 960px) {
#controller-sessions {
padding-left: 1rem;
padding-right: 1rem;
}
}
/**************************************************
Error Display Stying
**************************************************/
#error-display {
width: 80%;
max-width: 960px;
display: block;
position: fixed;
bottom: 0;
text-align: left;
left: 50%;
transform: translateX(-50%);
box-sizing: border-box;
padding: 1rem;
}
.error {
border-radius: 5px;
background-color: #d25647;
padding: 1rem 1.5rem;
box-sizing: border-box;
margin-bottom: 0.5rem;
transition: 0.25s;
}
.error h1 {
padding: 0;
margin: 0;
font-size: 1.1rem;
color: #d9dada;
text-transform: uppercase;
font-weight: bold;
display: inline;
padding-right: 0.5rem;
}
.error p {
padding: 0;
margin: 0;
font-size: 1.1rem;
display: inline;
color: #d9dada;
}

BIN
nxbt/web/static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View file

@ -0,0 +1 @@
<svg id="Joy-Con_L" data-name="Joy-Con L" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 169.93 477.55"><defs><style>.cls-1{fill:#191f28;}.cls-2{fill:#d8dada;}</style></defs><path class="cls-1" d="M314.16,37.59s3,5.34,7.32,3a5,5,0,0,1,6.74,1.56v72.57l-2.63,5.43s-1.48,2.46,2,3.86c0,0,1.07-.74,1.07,8.71v22.53s.16,4.35-1.32,4.76-1.15,3-1.15,3,2.15,3.62,2.47,14,0,120.27,0,120.27a21.24,21.24,0,0,1-3,10.63s-1.86,3.23,1,4.49c0,0,1.42,0,1.48,4.06s0,30.35,0,30.35-.11,2.47-2.36,2.58,2.8,13.86,2.8,13.86.26.39.26,4.06v69s0,3.95-2.62,4.33-5.42,0-5.86-5.31-.33-15.78-.33-15.78h-9.64V42.76Z" transform="translate(-158.85 -11.02)"/><path class="cls-1" d="M171.31,78.71S166.16,75.2,169.67,66c1.83-4.81,8.45-14.68,16.31-22.82A103.89,103.89,0,0,1,205.5,27.53s26.2-20.6,75.4-15.78c0,0,9.1,1.43,7.89,5.92S171.31,78.71,171.31,78.71Z" transform="translate(-158.85 -11.02)"/><path class="cls-2" d="M308.74,488.57H255.81s-96.6-3-96.6-98.52c0-64.48-.82-170.21,0-232.11.4-29.78,2.79-49.42,2.79-49.42s5.26-91.18,104.33-92.93h41.31s8.22-.55,8.33,8l-2,458.08s.5,6.9-3.5,6.9Z" transform="translate(-158.85 -11.02)"/><circle class="cls-1" cx="82.08" cy="117.16" r="38.71"/><circle class="cls-1" cx="82.49" cy="215.85" r="18.25"/><circle class="cls-1" cx="120.49" cy="251.85" r="18.25"/><circle class="cls-1" cx="45.49" cy="251.85" r="18.25"/><circle class="cls-1" cx="82.49" cy="287.85" r="18.25"/><rect class="cls-1" x="93.36" y="326.5" width="30.25" height="30.25"/><rect class="cls-1" x="117.67" y="41.39" width="27.07" height="9.75"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1 @@
<svg id="Joy-Con_R" data-name="Joy-Con R" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 168.63 478.04"><defs><style>.cls-1{fill:#191f28;}.cls-2{fill:#d8dada;}</style></defs><path class="cls-1" d="M172.15,37.59s-3,5.34-7.31,3a5,5,0,0,0-6.74,1.56v72.57l2.63,5.43s1.48,2.46-2,3.86c0,0-1.07-.74-1.07,8.71v22.53s-.17,4.35,1.31,4.76,1.15,3,1.15,3-2.14,3.62-2.47,14,0,120.27,0,120.27a21.24,21.24,0,0,0,3,10.63s1.86,3.23-1,4.49c0,0-1.42,0-1.47,4.06s0,30.35,0,30.35.1,2.47,2.35,2.58-2.79,13.86-2.79,13.86-.26.39-.26,4.06v69s0,3.95,2.61,4.33,5.43,0,5.87-5.31.32-15.78.32-15.78H176V42.76Z" transform="translate(-157.54 -11.02)"/><path class="cls-1" d="M315,78.71s5.15-3.51,1.65-12.71c-1.84-4.81-8.46-14.68-16.32-22.82a103.57,103.57,0,0,0-19.52-15.65s-26.19-20.6-75.4-15.78c0,0-9.09,1.43-7.89,5.92S315,78.71,315,78.71Z" transform="translate(-157.54 -11.02)"/><path class="cls-2" d="M174.74,489.06h52.93s98.27-3.28,98.27-98.79c0-69.13.77-185.53-.52-244.56-.49-22.53-3.94-36.7-3.94-36.7s-5.26-91.18-104.33-92.93H175.84s-8.22-.55-8.33,8l2,458.08s-.5,6.9,3.5,6.9Z" transform="translate(-157.54 -11.02)"/><circle class="cls-1" cx="50.8" cy="116.94" r="18.25"/><circle class="cls-1" cx="87.8" cy="152.94" r="18.25"/><circle class="cls-1" cx="125.8" cy="116.94" r="18.25"/><circle class="cls-1" cx="87.8" cy="80.94" r="18.25"/><circle class="cls-1" cx="90.39" cy="250.55" r="38.71"/><circle class="cls-1" cx="62.42" cy="343.36" r="18.9"/><rect class="cls-1" x="36.02" y="32.4" width="8.11" height="25.53"/><rect class="cls-1" x="193.56" y="43.42" width="8.11" height="25.53" transform="translate(-16.11 242.78) rotate(-90)"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

676
nxbt/web/static/js/main.js Normal file
View file

@ -0,0 +1,676 @@
/**********************************************/
/* Globals, Constants, and Enums */
/**********************************************/
let NXBT_CONTROLLER_INDEX = false;
let CONTROLLER_INDEX = false;
let CONTROLLER_CONNECTED = false;
let STATE = false;
// HTML SECTIONS
let HTML_CONTROLLER_SELECTION = document.getElementById("controller-selection");
let HTML_LOADER = document.getElementById("loader");
let HTML_LOADER_TEXT = document.getElementById("loader-text");
let HTML_CONTROLLER_CONFIG = document.getElementById("controller-config");
let HTML_MACRO_TEXT = document.getElementById("macro-text");
let HTML_STATUS_INDICATOR = document.getElementById("status-indicator");
let HTML_STATUS_INDICATOR_LIGHT = document.getElementById("status-indicator-light");
let HTML_STATUS_INDICATOR_TEXT = document.getElementById("status-indicator-text");
let HTML_KEYBOARD_MAP = document.getElementById("keyboard-map");
let HTML_CONTROLLER_MAP = document.getElementById("controller-map");
let HTML_ERROR_DISPLAY = document.getElementById("error-display");
let HTML_CONTROLLER_SESSIONS = document.getElementById("controller-sessions");
let HTML_CONTROLLER_SESSIONS_CONTAINER = document.getElementById("controller-session-container");
const ControllerState = {
INITIALIZING: "initializing",
CONNECTING: "connecting",
RECONNECTING: "reconnecting",
CONNECTED: "connected",
CRASHED: "crashed",
}
const InputDevice = {
KEYBOARD: "keyboard",
GAMEPAD: "gamepad"
}
let INPUT_DEVICE = InputDevice.KEYBOARD;
const ControllerType = {
PRO_CONTROLLER: "pro_controller",
JOYCON_L: "joycon_l",
JOYCON_R: "joycon_r"
}
const KEYMAP = {
// Left Stick
87: "LS_UP",
65: "LS_LEFT",
68: "LS_RIGHT",
83: "LS_DOWN",
84: "LS_PRESS",
// Right Stick
38: "RS_UP",
37: "RS_LEFT",
39: "RS_RIGHT",
40: "RS_DOWN",
89: "RS_PRESS",
// Dpad
71: "DPAD_UP",
86: "DPAD_LEFT",
78: "DPAD_RIGHT",
66: "DPAD_DOWN",
// Home & Capture
219: "HOME",
221: "CAPTURE",
// Plus & Minus
54: "PLUS",
55: "MINUS",
// A B X Y
76: "A",
75: "B",
73: "X",
74: "Y",
// L & ZL
49: "L",
50: "ZL",
// R & ZR
56: "ZR",
57: "R",
}
const LEFT_STICK = [
"LS_UP",
"LS_LEFT",
"LS_RIGHT",
"LS_DOWN"
]
const RIGHT_STICK = [
"RS_UP",
"RS_LEFT",
"RS_RIGHT",
"RS_DOWN"
]
let INPUT_PACKET = {
// Sticks
"L_STICK": {
"PRESSED": false,
"X_VALUE": 0,
"Y_VALUE": 0,
// Keyboard position calculation values
"LS_UP": false,
"LS_LEFT": false,
"LS_RIGHT": false,
"LS_DOWN": false
},
"R_STICK": {
"PRESSED": false,
"X_VALUE": 0,
"Y_VALUE": 0,
// Keyboard position calculation values
"RS_UP": false,
"RS_LEFT": false,
"RS_RIGHT": false,
"RS_DOWN": false
},
// Dpad
"DPAD_UP": false,
"DPAD_LEFT": false,
"DPAD_RIGHT": false,
"DPAD_DOWN": false,
// Triggers
"L": false,
"ZL": false,
"R": false,
"ZR": false,
// Joy-Con Specific Buttons
"JCL_SR": false,
"JCL_SL": false,
"JCR_SR": false,
"JCR_SL": false,
// Meta buttons
"PLUS": false,
"MINUS": false,
"HOME": false,
"CAPTURE": false,
// Buttons
"Y": false,
"X": false,
"B": false,
"A": false
}
let INPUT_PACKET_OLD = JSON.parse(JSON.stringify(INPUT_PACKET));
let PRO_CONTROLLER_DISPLAY = {
// Sticks
"L_STICK": {
"STICK": true,
"ELEMENT": document.getElementById("pc_ls"),
"MAX_X": 23,
"MIN_X": 15.5,
"DIFF_X": 7.5,
"MAX_Y": 29,
"MIN_Y": 18,
"DIFF_Y": 11
},
"R_STICK": {
"STICK": true,
"ELEMENT": document.getElementById("pc_rs"),
"MAX_X": 63.5,
"MIN_X": 56,
"DIFF_X": 7.5,
"MAX_Y": 50,
"MIN_Y": 38.125,
"DIFF_Y": 11.875,
},
// Dpad
"DPAD_UP": document.getElementById("pc_du"),
"DPAD_LEFT": document.getElementById("pc_dl"),
"DPAD_RIGHT": document.getElementById("pc_dr"),
"DPAD_DOWN": document.getElementById("pc_dd"),
// Triggers
"L": document.getElementById("pc_l"),
"ZL": document.getElementById("pc_zl"),
"R": document.getElementById("pc_r"),
"ZR": document.getElementById("pc_zr"),
// Meta buttons
"PLUS": document.getElementById("pc_p"),
"MINUS": document.getElementById("pc_m"),
"HOME": document.getElementById("pc_h"),
"CAPTURE": document.getElementById("pc_c"),
// Buttons
"Y": document.getElementById("pc_y"),
"X": document.getElementById("pc_x"),
"B": document.getElementById("pc_b"),
"A": document.getElementById("pc_a")
}
/**********************************************/
/* Socket.IO Functionality */
/**********************************************/
let socket = io();
// Request to the state at 1Hz
socket.emit('state');
let stateInterval = setInterval(function() {
socket.emit('state');
}, 1000);
socket.on('state', function(state) {
STATE = state;
});
socket.on('connect', function() {
console.log("Connected");
});
checkForLoadInterval = false;
socket.on('create_pro_controller', function(index) {
NXBT_CONTROLLER_INDEX = index;
checkForLoadInterval = setInterval(checkForLoad, 1000);
});
socket.on('error', function(errorMessage) {
displayError(errorMessage);
});
/**********************************************/
/* Listeners and Startup Functionality */
/**********************************************/
// Startup Functions
window.onload = function() {
// Run the Loader animation
setInterval(updateLoader, 85);
// // Print out the latency of setTimeout
// measureTimeoutLatency.start(120, 60);
}
// Keydown listener
function globalKeydownHandler(evt) {
if (INPUT_DEVICE !== InputDevice.KEYBOARD) {
return
}
evt = evt || window.event;
// Prevent scrolling on keypress
if([32, 37, 38, 39, 40].indexOf(evt.keyCode) > -1) {
evt.preventDefault();
}
if (Object.keys(KEYMAP).indexOf(JSON.stringify(evt.keyCode)) > -1) {
control = KEYMAP[evt.keyCode];
if (LEFT_STICK.indexOf(control) > -1) {
INPUT_PACKET["L_STICK"][control] = true;
} else if (RIGHT_STICK.indexOf(control) > -1) {
INPUT_PACKET["R_STICK"][control] = true;
} else {
INPUT_PACKET[control] = true;
}
}
}
document.onkeydown = globalKeydownHandler;
// Keyup listener
function globalKeyupHandler(evt) {
if (INPUT_DEVICE !== InputDevice.KEYBOARD) {
return
}
evt = evt || window.event;
if (Object.keys(KEYMAP).indexOf(JSON.stringify(evt.keyCode)) > -1) {
control = KEYMAP[evt.keyCode];
if (LEFT_STICK.indexOf(control) > -1) {
INPUT_PACKET["L_STICK"][control] = false;
} else if (RIGHT_STICK.indexOf(control) > -1) {
INPUT_PACKET["R_STICK"][control] = false;
} else {
INPUT_PACKET[control] = false;
}
}
}
document.onkeyup = globalKeyupHandler;
function disableKeyHandlers() {
document.onkeydown = false;
document.onkeyup = false;
}
function enableKeyHandlers() {
document.onkeydown = globalKeydownHandler;
document.onkeyup = globalKeyupHandler;
}
window.addEventListener("gamepadconnected", function(evt) {
gamepadIndex = evt.gamepad.index;
gamepadID = evt.gamepad.id;
console.log("Gamepad connected with ID:", gamepadID);
inputs = document.getElementById("input-device");
input = document.createElement("option");
input.innerHTML = gamepadID;
input.setAttribute("value", "gamepad");
input.setAttribute("index", gamepadIndex);
input.id = gamepadID;
inputs.appendChild(input);
});
window.addEventListener("gamepaddisconnected", function(evt) {
INPUT_DEVICE = InputDevice.KEYBOARD;
HTML_CONTROLLER_MAP.classList.add('hidden');
HTML_KEYBOARD_MAP.classList.remove('hidden');
CONTROLLER_INDEX = false;
gamepadInput = document.getElementById(evt.gamepad.id);
gamepadInput.remove();
});
/**********************************************/
/* UI, Input Monitoring, Gamepad Functionality */
/**********************************************/
function displayOtherSessions() {
controllerIndices = Object.keys(STATE);
// If there aren't any controller sessions
if (controllerIndices.length < 1) {
HTML_CONTROLLER_SESSIONS.classList.add("hidden");
return;
// If the only controller session is the current one
} else if (controllerIndices.length === 1 &&
Number(controllerIndices[0]) === NXBT_CONTROLLER_INDEX) {
HTML_CONTROLLER_SESSIONS.classList.add("hidden");
return;
}
HTML_CONTROLLER_SESSIONS.classList.remove('hidden');
HTML_CONTROLLER_SESSIONS_CONTAINER.innerHTML = ""
for (let i = 0; i < controllerIndices.length; i++) {
sessionIndex = controllerIndices[i];
if (sessionIndex === NXBT_CONTROLLER_INDEX) {
continue
}
let session = document.createElement("div")
session.classList.add("controller-session");
let sessionTitle = document.createElement("h1");
sessionTitle.innerHTML = "Session #" + sessionIndex;
let sessionEnd = document.createElement("button");
sessionEnd.innerHTML = "END";
sessionEnd.onclick = function() {
socket.emit('shutdown', Number(sessionIndex));
}
session.appendChild(sessionTitle);
session.appendChild(sessionEnd);
HTML_CONTROLLER_SESSIONS_CONTAINER.appendChild(session);
}
}
// Set a first quick call to make sure results appear
// on page load, if available. The timeout is necessary
// due to the time it takes for the initial State to load.
setTimeout(displayOtherSessions, 250);
setInterval(displayOtherSessions, 2000);
function displayError(errorText) {
let errorContainer = document.createElement('div');
errorContainer.classList.add('error');
let errorHeader = document.createElement('h1');
errorHeader.innerHTML = "ERROR";
let errorMessage = document.createElement('p');
errorMessage.innerHTML = errorText;
errorContainer.appendChild(errorHeader);
errorContainer.appendChild(errorMessage);
HTML_ERROR_DISPLAY.appendChild(errorContainer);
setTimeout(function() {
errorContainer.remove();
}, 10000);
}
function createProController() {
HTML_CONTROLLER_SELECTION.classList.add('hidden');
HTML_LOADER.classList.remove('hidden');
socket.emit('create_pro_controller');
}
function shutdownController() {
if (STATE[NXBT_CONTROLLER_INDEX]) {
socket.emit('shutdown', NXBT_CONTROLLER_INDEX);
}
}
function recreateProController() {
socket.emit('create_pro_controller');
}
function checkForLoad() {
if (STATE[NXBT_CONTROLLER_INDEX]) {
controller_state = STATE[NXBT_CONTROLLER_INDEX].state
HTML_LOADER_TEXT.innerHTML = controller_state;
if (controller_state === ControllerState.CONNECTED) {
// Show the connected message for 1 second
setTimeout(function() {
clearInterval(checkForLoadInterval);
HTML_LOADER.classList.add('hidden');
HTML_CONTROLLER_CONFIG.classList.remove('hidden');
HTML_STATUS_INDICATOR.classList.remove('hidden');
setInterval(updateStatusIndicator, 1000);
eventLoop();
}, 1000);
}
}
}
function updateStatusIndicator() {
if (STATE[NXBT_CONTROLLER_INDEX]) {
controller_state = STATE[NXBT_CONTROLLER_INDEX].state;
if (controller_state === ControllerState.CONNECTED) {
changeStatusIndicatorState("indicator-green", "CONNECTED");
} else if (controller_state === ControllerState.CONNECTING) {
changeStatusIndicatorState("indicator-yellow", "CONNECTING");
} else if (controller_state === ControllerState.RECONNECTING) {
changeStatusIndicatorState("indicator-yellow", "RECONNECTING");
} else if (controller_state === ControllerState.CRASHED) {
changeStatusIndicatorState("indicator-red", "CRASHED");
}
} else {
changeStatusIndicatorState("indicator-red", "NO INPUT");
}
}
function changeStatusIndicatorState(className, text) {
HTML_STATUS_INDICATOR_LIGHT.className = "";
HTML_STATUS_INDICATOR_LIGHT.classList.add(className);
HTML_STATUS_INDICATOR_TEXT.innerHTML = text;
}
function changeInput(evt) {
inputType = evt.target.value
if (inputType === InputDevice.KEYBOARD) {
INPUT_DEVICE = InputDevice.KEYBOARD;
document.onkeydown = globalKeydownHandler;
document.onkeyup = globalKeyupHandler;
HTML_CONTROLLER_MAP.classList.add('hidden');
HTML_KEYBOARD_MAP.classList.remove('hidden');
CONTROLLER_INDEX = false;
} else if (inputType == InputDevice.GAMEPAD) {
INPUT_DEVICE = InputDevice.GAMEPAD;
document.onkeydown = false;
document.onkeyup = false;
HTML_KEYBOARD_MAP.classList.add('hidden');
HTML_CONTROLLER_MAP.classList.remove('hidden');
selectedGamepad = evt.target.children[evt.target.selectedIndex]
CONTROLLER_INDEX = selectedGamepad.getAttribute("index");
}
}
const LOADER_ANIMATION_FRAMES = [0,1,2,3,3,2,1,0];
let loaderFrame = 1;
let highlightedBlock = false;
function updateLoader() {
loaderBlocks = document.getElementById("loader-blocks").children;
if (highlightedBlock === false) {
highlightedBlock = loaderBlocks[0];
}
highlightedBlock.classList.remove("loader-block-highlight");
highlightedBlock = loaderBlocks[LOADER_ANIMATION_FRAMES[loaderFrame]];
highlightedBlock.classList.add("loader-block-highlight");
loaderFrame += 1;
if (loaderFrame >= LOADER_ANIMATION_FRAMES.length) {
loaderFrame = 0;
}
}
function updateGamepadInput() {
let gp = navigator.getGamepads()[CONTROLLER_INDEX];
INPUT_PACKET["L_STICK"]["X_VALUE"] = gp.axes[0] * 100;
INPUT_PACKET["L_STICK"]["Y_VALUE"] = gp.axes[1] * -100;
INPUT_PACKET["L_STICK"]["PRESSED"] = gp["buttons"][10]["pressed"];
INPUT_PACKET["R_STICK"]["X_VALUE"] = gp.axes[2] * 100;
INPUT_PACKET["R_STICK"]["Y_VALUE"] = gp.axes[3] * -100;
INPUT_PACKET["R_STICK"]["PRESSED"] = gp["buttons"][11]["pressed"];
INPUT_PACKET["DPAD_UP"] = gp["buttons"][12]["pressed"];
INPUT_PACKET["DPAD_DOWN"] = gp["buttons"][13]["pressed"];
INPUT_PACKET["DPAD_LEFT"] = gp["buttons"][14]["pressed"];
INPUT_PACKET["DPAD_RIGHT"] = gp["buttons"][15]["pressed"];
if (gp["buttons"][16]) {
INPUT_PACKET["HOME"] = gp["buttons"][16]["pressed"];
}
if (gp["buttons"][17]) {
INPUT_PACKET["CAPTURE"] = gp["buttons"][17]["pressed"];
}
INPUT_PACKET["B"] = gp["buttons"][0]["pressed"];
INPUT_PACKET["A"] = gp["buttons"][1]["pressed"];
INPUT_PACKET["Y"] = gp["buttons"][2]["pressed"];
INPUT_PACKET["X"] = gp["buttons"][3]["pressed"];
INPUT_PACKET["L"] = gp["buttons"][4]["pressed"];
INPUT_PACKET["R"] = gp["buttons"][5]["pressed"];
INPUT_PACKET["ZL"] = gp["buttons"][6]["pressed"];
INPUT_PACKET["ZR"] = gp["buttons"][7]["pressed"];
INPUT_PACKET["PLUS"] = gp["buttons"][8]["pressed"];
INPUT_PACKET["MINUS"] = gp["buttons"][9]["pressed"];
}
function updateGamepadDisplay() {
controls = Object.keys(INPUT_PACKET);
for (let i = 0; i < controls.length; i++) {
controlState = INPUT_PACKET[controls[i]];
control = PRO_CONTROLLER_DISPLAY[controls[i]];
if (!control) {
continue;
}
if (control.STICK) {
xRatio = (controlState["X_VALUE"] + 100)/200;
yRatio = (controlState["Y_VALUE"] + 100)/200;
xPos = control["MIN_X"] + (xRatio * control["DIFF_X"]);
yPos = control["MAX_Y"] - (yRatio * control["DIFF_Y"]);
control["ELEMENT"].style.left = xPos + "%";
control["ELEMENT"].style.top = yPos + "%";
} else {
if (controlState) {
control.classList.remove('hidden');
} else {
control.classList.add('hidden');
}
}
}
}
let timeOld = false;
let frequency = (1/120) * 1000;
function eventLoop() {
// Update x/y ratio for the sticks based on
// pressed buttons if we're using a keyboard
if (INPUT_DEVICE == InputDevice.KEYBOARD) {
// Calculating left x/y stick values
lXValue = 0
lYValue = 0
if (INPUT_PACKET["L_STICK"]["LS_LEFT"]) {
lXValue -= 100
}
if (INPUT_PACKET["L_STICK"]["LS_RIGHT"]) {
lXValue += 100
}
if (INPUT_PACKET["L_STICK"]["LS_UP"]) {
lYValue += 100
}
if (INPUT_PACKET["L_STICK"]["LS_DOWN"]) {
lYValue -= 100
}
INPUT_PACKET["L_STICK"]["X_VALUE"] = lXValue
INPUT_PACKET["L_STICK"]["Y_VALUE"] = lYValue
// Calculating left x/y stick values
rXValue = 0
rYValue = 0
if (INPUT_PACKET["R_STICK"]["RS_LEFT"]) {
rXValue -= 100
}
if (INPUT_PACKET["R_STICK"]["RS_RIGHT"]) {
rXValue += 100
}
if (INPUT_PACKET["R_STICK"]["RS_UP"]) {
rYValue += 100
}
if (INPUT_PACKET["R_STICK"]["RS_DOWN"]) {
rYValue -= 100
}
INPUT_PACKET["R_STICK"]["X_VALUE"] = rXValue
INPUT_PACKET["R_STICK"]["Y_VALUE"] = rYValue
} else if (INPUT_DEVICE == InputDevice.GAMEPAD) {
updateGamepadInput();
}
// Only send packet if it's not a duplicate of previous.
// We can do this since NXBT will hold the previously sent value
// until we send it a new one.
if (JSON.stringify(INPUT_PACKET) !== JSON.stringify(INPUT_PACKET_OLD)) {
socket.emit('input', JSON.stringify([NXBT_CONTROLLER_INDEX, INPUT_PACKET]));
INPUT_PACKET_OLD = JSON.parse(JSON.stringify(INPUT_PACKET));
}
updateGamepadDisplay()
// if (!timeOld) {
// timeOld = performance.now();
// }
// timeNew = performance.now();
// delta = timeNew - timeOld;
// diff = delta - frequency;
// if (diff > 0) {
// setTimeout(eventLoop, frequency - diff);
// } else {
// setTimeout(eventLoop, frequency);
// }
// timeOld = timeNew;
requestAnimationFrame(eventLoop);
}
function sendMacro() {
let macro = HTML_MACRO_TEXT.value.toUpperCase();
socket.emit('macro', JSON.stringify([NXBT_CONTROLLER_INDEX, macro]));
}
/**********************************************/
/* Debug Functionality */
/**********************************************/
const measureTimeoutLatency = {
debug: false,
count: 0,
maxCount: 0,
deltaSum: 0,
diffSum: 0,
timeOld: false,
rawFrequency: false,
frequency: false,
start: function(frequency, iterations) {
this.rawFrequency = frequency;
this.frequency = (1/frequency)*1000;
this.maxCount = iterations;
this.timeOld = performance.now();
this.loop(this);
},
loop: function(context) {
timeNew = performance.now();
delta = timeNew - context.timeOld;
diff = delta - context.frequency;
if (context.debug) {
console.log(diff);
}
context.deltaSum += Math.abs(delta);
context.diffSum += Math.abs(diff);
context.count += 1
if (context.count >= context.maxCount) {
avgDelta = context.deltaSum/context.count;
avgDiff = context.diffSum/context.count;
console.log("Average Time setTimeout Delta (ms):", avgDelta);
console.log("Average Difference between Delta and Frequency (ms):", avgDiff);
} else {
if (diff > 0) {
setTimeout(function(){context.loop(context);}, context.frequency - diff);
} else {
setTimeout(function(){context.loop(context);}, context.frequency);
}
context.timeOld = timeNew;
}
}
}

View file

@ -0,0 +1 @@
<svg id="Transparent" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 460.11 308.82"><defs><style>.cls-1{fill:#dadada;}</style></defs><path class="cls-1" d="M42.27,213.45l-5,25.92-8.44,51.18L22.49,337.5s-2.52,22,3.29,40.39,22.68,26,22.68,26,4.61,4.71,21.48,3.29,28-21.48,28-21.48l5.59-9.54L110.05,363s11.07-22.79,19.29-37.15a23.07,23.07,0,0,1,5.67-6.64C114.18,298,62.08,221.09,45.64,196.56Z" transform="translate(-21.95 -98.6)"/><path class="cls-1" d="M481.51,337.5l-6.36-46.95-8.44-51.18-5-25.92-3.37-16.89C441.92,221.09,389.82,298,369,319.19a23.07,23.07,0,0,1,5.67,6.64C382.88,340.19,394,363,394,363l6.57,13.15,5.59,9.54s11.07,20.05,27.95,21.48,21.48-3.29,21.48-3.29,16.87-7.56,22.68-26S481.51,337.5,481.51,337.5Z" transform="translate(-21.95 -98.6)"/><path class="cls-1" d="M451,163.67s-4.6-17.47-5.67-19.11-2.59-8.59-12.29-16.32-28.93-16.31-28.93-16.31-12.7-7.69-53.38-10.81S252,98.66,252,98.66s-58.08-.66-98.77,2.46-53.38,10.81-53.38,10.81-19.24,8.59-28.93,16.31S59.7,142.92,58.63,144.56,53,163.67,53,163.67l-5.87,26.05c12.25,18.33,71.5,106.37,92.24,126.72a32,32,0,0,1,16-3H348.69a32,32,0,0,1,16,3c20.74-20.35,80-108.39,92.24-126.72ZM193.83,140.76A10.2,10.2,0,1,1,183.64,151,10.2,10.2,0,0,1,193.83,140.76Zm-69.7,80a35,35,0,1,1,35-35A35,35,0,0,1,124.13,220.79Zm90.42,41.1H191.37v23.18H167.86V261.89H144.68V238.38h23.18V215.2h23.51v23.18h23.18Zm12.76-66.58H208.68V176.68h18.63Zm80.39-54.55A10.2,10.2,0,1,1,297.5,151,10.2,10.2,0,0,1,307.7,140.76ZM272.5,186a10.2,10.2,0,1,1,10.2,10.19A10.2,10.2,0,0,1,272.5,186Zm38.78,97.65a35,35,0,1,1,35-35A35,35,0,0,1,311.28,283.61Zm24.48-81a16.93,16.93,0,1,1,16.94-16.93A16.93,16.93,0,0,1,335.76,202.6Zm36,31a16.93,16.93,0,1,1,16.94-16.93A16.93,16.93,0,0,1,371.76,233.6Zm0-62a16.93,16.93,0,1,1,16.94-16.93A16.93,16.93,0,0,1,371.76,171.6Zm36,31a16.93,16.93,0,1,1,16.94-16.93A16.93,16.93,0,0,1,407.76,202.6Z" transform="translate(-21.95 -98.6)"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 460.11 316.67"><defs><style>.cls-1{fill:#dadada;}.cls-2{fill:#191f28;}</style></defs><path class="cls-1" d="M45.64,196.56l-3.37,16.89-5,25.92-8.44,51.18L22.49,337.5s-2.52,22,3.29,40.39,22.68,26,22.68,26,4.61,4.71,21.48,3.29,28-21.48,28-21.48l5.59-9.54L110.05,363s11.07-22.79,19.29-37.15a23.07,23.07,0,0,1,5.67-6.64C114.18,298,62.08,221.09,45.64,196.56Z" transform="translate(-21.95 -90.75)"/><path class="cls-1" d="M153.23,101.12c-40.68,3.12-53.38,10.81-53.38,10.81s-19.24,8.59-28.93,16.31S59.7,142.92,58.63,144.56,53,163.67,53,163.67l-5.87,26.05c12.25,18.33,71.5,106.37,92.24,126.72a32,32,0,0,1,16-3H252V98.66S193.92,98,153.23,101.12Z" transform="translate(-21.95 -90.75)"/><path class="cls-1" d="M458.36,196.56l3.37,16.89,5,25.92,8.44,51.18,6.36,46.95s2.52,22-3.29,40.39-22.68,26-22.68,26-4.61,4.71-21.48,3.29-27.95-21.48-27.95-21.48l-5.59-9.54L394,363s-11.07-22.79-19.29-37.15a23.07,23.07,0,0,0-5.67-6.64C389.82,298,441.92,221.09,458.36,196.56Z" transform="translate(-21.95 -90.75)"/><path class="cls-1" d="M350.77,101.12c40.68,3.12,53.38,10.81,53.38,10.81s19.24,8.59,28.93,16.31,11.22,14.68,12.29,16.32S451,163.67,451,163.67l5.87,26.05c-12.25,18.33-71.5,106.37-92.24,126.72a32,32,0,0,0-16-3H252V98.66S310.08,98,350.77,101.12Z" transform="translate(-21.95 -90.75)"/><circle class="cls-2" cx="102.19" cy="95.03" r="35.01"/><circle class="cls-2" cx="289.34" cy="157.85" r="35.01"/><circle class="cls-2" cx="171.89" cy="60.21" r="10.19"/><circle class="cls-2" cx="285.75" cy="60.21" r="10.19"/><circle class="cls-2" cx="260.75" cy="95.21" r="10.19"/><rect class="cls-2" x="186.74" y="85.93" width="18.63" height="18.63"/><rect class="cls-2" x="145.91" y="124.45" width="23.51" height="69.86"/><rect class="cls-2" x="167.86" y="215.2" width="23.51" height="69.86" transform="translate(-92.47 339) rotate(-90)"/><circle class="cls-2" cx="313.82" cy="94.92" r="16.93"/><circle class="cls-2" cx="385.82" cy="94.92" r="16.93"/><circle class="cls-2" cx="349.82" cy="125.92" r="16.93"/><circle class="cls-2" cx="349.82" cy="63.92" r="16.93"/><path class="cls-2" d="M71.94,127.34s11.7-25.07,40.63-31,41.21-5.59,41.21-5.59a33,33,0,0,1,11.94,2c6.25,2.3,16.77,6.79,16.77,6.79l-30.79,1.65s-31.13,2.32-48.61,9.64A274,274,0,0,0,71.94,127.34Z" transform="translate(-21.95 -90.75)"/><path class="cls-2" d="M431.91,127.34s-11.69-25.07-40.63-31-41.2-5.59-41.2-5.59a33.1,33.1,0,0,0-11.95,2c-6.24,2.3-16.76,6.79-16.76,6.79l30.79,1.65s31.12,2.32,48.6,9.64A272.23,272.23,0,0,1,431.91,127.34Z" transform="translate(-21.95 -90.75)"/></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -1,69 +1,214 @@
<!DOCTYPE html>
<html>
<head>
<title>NXBT: Control Your Switch Locally or Remotely</title>
<title>NXBT Webapp</title>
<link rel="icon" type="image/png" href="favicon.png"/>
<link rel="stylesheet" type="text/css" href="css/main.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<header class="top-bar">
<div class="top-bar-wrapper">
<div>
<h1>NXBT</h1><div class="mono">v0.1</div>
</div>
<div id="status-indicator" class="hidden surface-lighter">
<div id="status-indicator-light" class="indicator-yellow"></div>
<span id="status-indicator-text">Loading</span>
</div>
</div>
</header>
<main>
<section id="controller-selection">
<h1>Create a Controller</h1>
<div class="hidden surface controller-option" onclick="createJoyConL()">
<div class="controller-option-img-wrapper">
<img src="joyconl.svg" draggable="false">
</div>
Joy-Con (L)
</div>
<div class="surface controller-option" onclick="createProController()">
<div class="controller-option-img-wrapper">
<img src="procon.svg" draggable="false">
</div>
Pro Controller
</div>
<div class="hidden surface controller-option" onclick="createJoyConR()">
<div class="controller-option-img-wrapper">
<img src="joyconr.svg" draggable="false">
</div>
Joy-Con (R)
</div>
</section>
<section id="loader" class="surface hidden">
<div id="loader-progress-wrapper" class="surface-lighter">
<div id="loader-blocks">
<div class="loader-block loader-block-highlight"></div>
<div class="loader-block"></div>
<div class="loader-block"></div>
<div class="loader-block"></div>
</div>
</div>
<div id="loader-text" class="surface-lighter">Loading</div>
<div class="text-section">
<h2>Tips</h2>
<h2 class="tip-box">1. First Time Connection</h2>
<p>
If this is your first time connecting to your Nintendo
Switch with NXBT, please ensure that you're on the "Change Grip/Order" menu.
This sub-menu is available from within the Controllers menu, located on the
bottom row of the Switch's Home Screen.
</p>
<h2 class="tip-box">2. Reconnection</h2>
<p>
If you've previously connected to this Nintendo Switch using NXBT,
please ensure that you're on the Home Screen.
</p>
</div>
</section>
<section id="controller-config" class="hidden surface">
<div id="controller-underlay-container">
<div class="controller-display hidden">
<div class="control-indicator" id="jl_"></div>
<img id="joyconl-underlay" class="hidden controller-underlay" src="joyconl.svg" draggable="false">
</div>
<div class="controller-display hidden">
<div class="control-indicator" id="jr_"></div>
<img id="joyconr-underlay" class="hidden controller-underlay" src="joyconr.svg" draggable="false">
</div>
<div class="controller-display">
<img id="procon-underlay" class="controller-underlay" src="procon-transparent.svg" draggable="false">
<div class="control-indicator" id="pc_ls"></div>
<div class="control-indicator" id="pc_rs"></div>
<div class="control-indicator square-button hidden" id="pc_du"></div>
<div class="control-indicator square-button hidden" id="pc_dl"></div>
<div class="control-indicator square-button hidden" id="pc_dr"></div>
<div class="control-indicator square-button hidden" id="pc_dd"></div>
<div class="control-indicator hidden" id="pc_h"></div>
<div class="control-indicator hidden" id="pc_c"></div>
<div class="control-indicator hidden" id="pc_m"></div>
<div class="control-indicator hidden" id="pc_p"></div>
<div class="control-indicator big-button hidden" id="pc_a"></div>
<div class="control-indicator big-button hidden" id="pc_b"></div>
<div class="control-indicator big-button hidden" id="pc_x"></div>
<div class="control-indicator big-button hidden" id="pc_y"></div>
<div class="control-indicator hidden" id="pc_l"></div>
<div class="control-indicator hidden" id="pc_zl"></div>
<div class="control-indicator hidden" id="pc_r"></div>
<div class="control-indicator hidden" id="pc_zr"></div>
</div>
</div>
<div id="controller-input" class="controller-config-subsection">
<h2 class="surface-lighter">Input Device</h2>
<select id="input-device" name="Select an Input Device" onchange="changeInput(event)">
<option value="keyboard">Keyboard</option>
</select>
<p>
<strong>Supported Input Devices: </strong>
Keyboards, Gamepads
<br><br>
<strong>Note:</strong> You will likely need to press
a button on the gamepad before it shows up on the
input device list.
</p>
<h2 class="surface-lighter">Control Mapping</h2>
<div id="keyboard-map" class="surface-lighter">
<table>
<tbody>
<tr>
<td>Left Stick</td>
<td>W A S D</td>
</tr>
<tr>
<td>Left Stick Press</td>
<td>T</td>
</tr>
<tr>
<td>Right Stick</td>
<td>Arrow Keys</td>
</tr>
<tr>
<td>Right Stick Press</td>
<td>Y</td>
</tr>
<tr>
<td>Dpad</td>
<td>G V B N</td>
</tr>
<tr>
<td>Home & Capture</td>
<td>[ & ]</td>
</tr>
<tr>
<td>Plus & Minus</td>
<td>6 & 7</td>
</tr>
<tr>
<td>A B X Y</td>
<td>L K I J</td>
</tr>
<tr>
<td>L & ZL</td>
<td>1 & 2</td>
</tr>
<tr>
<td>R & ZR</td>
<td>8 & 9</td>
</tr>
</tbody>
</table>
</div>
<div id="controller-map" class="hidden surface-lighter">
<p>
Gamepad controls should match the Nintendo Switch
Pro Controller to some degree in a physical manner.
Please consult the gamepad display above to see
which buttons are actuating.
</p>
<p>
Future plans include enabling key remapping. In the
meantime, I apologize for any inconvience!
</p>
</div>
</div>
<div id="controller-commands" class="controller-config-subsection">
<h2 class="surface-lighter">Controller Commands</h2>
<button onclick="shutdownController()">Shutdown Controller</button>
<button onclick="recreateProController()">Recreate Controller</button>
<h2 class="surface-lighter">Controller Macro</h2>
<p>
<strong>Note:</strong> the main input device will override a running
macro.
</p>
<textarea id="macro-text" class="mono" placeholder="Type Your Macro Here" onfocus="disableKeyHandlers()" onblur="enableKeyHandlers()"></textarea>
<button onclick="sendMacro()">Run Macro</button>
</div>
</section>
<section id="controller-sessions" class="hidden surface">
<h1>Other Controller Sessions</h1>
<div id="controller-session-container"></div>
</section>
<section id="error-display"></section>
<footer>
<p>Source Code and Documentation Available <a href="https://github.com/Brikwerk/nxbt" target="_blank">Here</a></p>
<p>Made By <a href="http://github.com/Brikwerk" target="_blank">Reece Walsh</a></p>
</footer>
</main>
<script src="js/socket.io.js"></script>
<script type="text/javascript" charset="utf-8">
var socket = io();
socket.on('connect', function() {
let date = new Date();
socket.emit('message', {"timestamp": date.getTime()});
});
socket.on('response', function(data) {
console.log(data)
});
socket.on('dateResponse', function(data) {
console.log(data)
});
window.addEventListener("gamepadconnected", function(e) {
let gp = navigator.getGamepads()[e.gamepad.index];
console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.",
gp.index, gp.id,
gp.buttons.length, gp.axes.length);
setInterval(function() {
let gp = navigator.getGamepads()[e.gamepad.index];
let axes = gp["axes"];
let buttons = []
for (let i = 0; i < gp["buttons"].length; i++) {
let button = gp["buttons"][i];
buttons.push({
"pressed": button["pressed"],
})
}
gpData = {
"axes": gp["axes"],
"buttons": {
"B": gp["buttons"][0]["pressed"],
"A": gp["buttons"][1]["pressed"],
"Y": gp["buttons"][2]["pressed"],
"X": gp["buttons"][3]["pressed"],
"L": gp["buttons"][4]["pressed"],
"R": gp["buttons"][5]["pressed"],
"ZL": gp["buttons"][6]["pressed"],
"ZR": gp["buttons"][7]["pressed"],
"-": gp["buttons"][8]["pressed"],
"+": gp["buttons"][9]["pressed"],
"L_ANALOG": gp["buttons"][10]["pressed"],
"R_ANALOG": gp["buttons"][11]["pressed"],
"DPAD_UP": gp["buttons"][12]["pressed"],
"DPAD_DOWN": gp["buttons"][13]["pressed"],
"DPAD_LEFT": gp["buttons"][14]["pressed"],
"DPAD_RIGHT": gp["buttons"][15]["pressed"],
"HOME": gp["buttons"][16]["pressed"],
"CAPTURE": gp["buttons"][17]["pressed"],
}
}
socket.emit('input', JSON.stringify(gpData));
}, 1/60);
});
</script>
<script src="js/main.js"></script>
</body>
</html>