Added webapp functionality
This commit is contained in:
parent
c026b357a0
commit
22cdc98d6c
9 changed files with 1581 additions and 69 deletions
|
|
@ -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()
|
||||
|
|
|
|||
636
nxbt/web/static/css/main.css
Normal file
636
nxbt/web/static/css/main.css
Normal 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
BIN
nxbt/web/static/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 26 KiB |
1
nxbt/web/static/joyconl.svg
Normal file
1
nxbt/web/static/joyconl.svg
Normal 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 |
1
nxbt/web/static/joyconr.svg
Normal file
1
nxbt/web/static/joyconr.svg
Normal 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
676
nxbt/web/static/js/main.js
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
nxbt/web/static/procon-transparent.svg
Normal file
1
nxbt/web/static/procon-transparent.svg
Normal 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 |
1
nxbt/web/static/procon.svg
Normal file
1
nxbt/web/static/procon.svg
Normal 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 |
|
|
@ -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>
|
||||
Loading…
Add table
Add a link
Reference in a new issue