First public release
This commit is contained in:
		
							parent
							
								
									963cae3756
								
							
						
					
					
						commit
						64333fdb4a
					
				
					 14 changed files with 1580 additions and 0 deletions
				
			
		
							
								
								
									
										180
									
								
								static/css/app.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								static/css/app.css
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,180 @@ | |||
| body { | ||||
|     max-width: 500px; | ||||
|     margin: 10px auto; | ||||
|     background-color: #1c1b1f; | ||||
|     color: #e1e1e1; | ||||
| } | ||||
| 
 | ||||
| .container { | ||||
|     border-radius: 10px; | ||||
|     background-color: #272629; | ||||
|     padding: 20px; | ||||
|     margin: 10px; | ||||
| } | ||||
| 
 | ||||
| .ascii { | ||||
|     text-align: center; | ||||
|     padding: 8px 0; | ||||
| } | ||||
| 
 | ||||
| .ascii pre { | ||||
|     font-family: ui-monospace, 'Segoe UI Mono', 'Roboto Mono', 'Courier New', monospace; | ||||
|     display: inline-block; | ||||
|     text-align: left; | ||||
|     margin: 0; | ||||
|     font-weight: bold; | ||||
|     font-size: 10px; | ||||
|     line-height: 10px; | ||||
|     color: #FFF; | ||||
| } | ||||
| 
 | ||||
| .flex { | ||||
|     display: flex; | ||||
| } | ||||
| 
 | ||||
| .joycons { | ||||
|     margin-top: 10px; | ||||
| } | ||||
| 
 | ||||
| .joycons-wrapper button { | ||||
|     width: 100%; | ||||
| } | ||||
| 
 | ||||
| .joycons .joycons-wrapper { | ||||
|     position: relative; | ||||
| } | ||||
| 
 | ||||
| .joycons .joycons-wrapper .empty { | ||||
|     height: 60px; | ||||
|     line-height: 60px; | ||||
|     margin: auto; | ||||
|     text-align: center; | ||||
| } | ||||
| 
 | ||||
| .joycons .joycons-wrapper .joycons-list { | ||||
|     padding: 0; | ||||
|     margin: 0; | ||||
|     list-style: none; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| .joycons .joycons-wrapper .joycons-list li { | ||||
|     border-bottom: 1px solid #2f2f2f; | ||||
|     padding: 12px 0; | ||||
| } | ||||
| 
 | ||||
| .joycons .joycons-wrapper .joycons-list .joycon-color { | ||||
|     width: 35%; | ||||
|     margin: auto; | ||||
| } | ||||
| 
 | ||||
| .joycons .joycons-wrapper .joycons-list .joycon-name { | ||||
|     align-items: center; | ||||
|     font-size: 16px; | ||||
|     font-weight: bold; | ||||
| } | ||||
| 
 | ||||
| .joycons .joycons-wrapper .joycons-list .joycon-info { | ||||
|     display: flex; | ||||
|     flex-direction: column; | ||||
| } | ||||
| 
 | ||||
| .joycons .joycons-wrapper .joycons-list .joycon-info .joycon-state { | ||||
|     color: #757575; | ||||
|     font-size: 14px; | ||||
|     text-align: left; | ||||
| } | ||||
| 
 | ||||
| .joycons-list .pairing-code { | ||||
|     border-radius: 4px; | ||||
|     font-size: 12px; | ||||
|     background-color: #202020; | ||||
|     display: inline-block; | ||||
|     color: #a6a6a6; | ||||
|     padding: 4px; | ||||
|     margin: auto; | ||||
| } | ||||
| 
 | ||||
| .joycons .btn-refresh { | ||||
|     color: #383000; | ||||
|     background-color: #e2c700; | ||||
|     margin: auto; | ||||
| } | ||||
| 
 | ||||
| input, select { | ||||
|     box-shadow: none !important; | ||||
|     border-width: 2px !important; | ||||
| } | ||||
| 
 | ||||
| input:invalid { | ||||
|     border-color: #e9322d !important; | ||||
| } | ||||
| 
 | ||||
| input[readonly] { | ||||
|     background-color: #ccc !important; | ||||
|     color: #000; | ||||
| } | ||||
| 
 | ||||
| #ipAddr, #pairingCode, .joycons-list .pairing-code { | ||||
|     font-family: ui-monospace, 'Segoe UI Mono', 'Roboto Mono', 'Courier New', monospace; | ||||
| } | ||||
| 
 | ||||
| .footer { | ||||
|     text-align: center; | ||||
|     margin-top: 16px; | ||||
| } | ||||
| 
 | ||||
| .footer a { | ||||
|     text-decoration: none; | ||||
|     color: #424242; | ||||
|     font-size: 14px; | ||||
| } | ||||
| 
 | ||||
| .footer a:hover { | ||||
|     color: #4d4d4d; | ||||
| } | ||||
| 
 | ||||
| .pure-button-primary { | ||||
|     background-color: #1976D2 !important; | ||||
|     color: #E3F2FD !important; | ||||
| } | ||||
| 
 | ||||
| .pure-form fieldset { | ||||
|     padding-bottom: 0; | ||||
| } | ||||
| 
 | ||||
| .battery-level { | ||||
|     display: inline-block; | ||||
|     margin-left: 8px; | ||||
| } | ||||
| 
 | ||||
| .battery-level svg { | ||||
|     width: 16px; | ||||
|     margin: auto; | ||||
|     vertical-align: middle; | ||||
| } | ||||
| 
 | ||||
| .battery-level.full > * { | ||||
|     fill: #8BC34A; | ||||
| } | ||||
| 
 | ||||
| .battery-level.medium > * { | ||||
|     fill: #FFEB3B; | ||||
| } | ||||
| 
 | ||||
| .battery-level.low > * { | ||||
|     fill: #FF9800; | ||||
| } | ||||
| 
 | ||||
| .battery-level.critical > * { | ||||
|     fill: #FF5722; | ||||
| } | ||||
| 
 | ||||
| .battery-level.medium .battery-bar-4, | ||||
| .battery-level.low .battery-bar-3, | ||||
| .battery-level.low .battery-bar-4, | ||||
| .battery-level.critical .battery-bar-2, | ||||
| .battery-level.critical .battery-bar-3, | ||||
| .battery-level.critical .battery-bar-4 { | ||||
|     display: none; | ||||
| } | ||||
							
								
								
									
										7
									
								
								static/css/grids-responsive-min.css
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								static/css/grids-responsive-min.css
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										11
									
								
								static/css/pure-min.css
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								static/css/pure-min.css
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										
											BIN
										
									
								
								static/favicon.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								static/favicon.png
									
										
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 470 B | 
							
								
								
									
										17
									
								
								static/index.html
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								static/index.html
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,17 @@ | |||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <head> | ||||
|     <title>JoyDance</title> | ||||
|     <meta charset="utf-8"> | ||||
|     <link rel="icon" type="image/png" href="/favicon.png"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> | ||||
|     <link href="/css/pure-min.css" type="text/css" rel="stylesheet" /> | ||||
|     <link href="/css/grids-responsive-min.css" type="text/css" rel="stylesheet" /> | ||||
|     <link href="/css/app.css" type="text/css" rel="stylesheet" /> | ||||
|     <script type="text/javascript">window.CONFIG = [[CONFIG]]; window.VERSION = '[[VERSION]]';</script> | ||||
|     <script type="text/javascript" src="/js/mitt.umd.js"></script> | ||||
|     <script type="module" src="/js/app.js"></script> | ||||
| </head> | ||||
| <body> | ||||
| </body> | ||||
| </html> | ||||
							
								
								
									
										507
									
								
								static/js/app.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										507
									
								
								static/js/app.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,507 @@ | |||
| import { h, Component, render } from '/js/preact.module.js'; | ||||
| import htm from '/js/htm.module.js'; | ||||
| 
 | ||||
| // Initialize htm with Preact
 | ||||
| const html = htm.bind(h); | ||||
| window.mitty = mitt() | ||||
| 
 | ||||
| const SVG_BATTERY_LEVEL = html`<svg style="enable-background:new 0 0 16 16" viewBox="0 0 16 16" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path d="M15 4H0v8h15V9h1V7h-1V4zm-1 3v4H1V5h13v2z"/><rect class="battery-bar-4" height="4" width="2" x="11" y="6"/><rect class="battery-bar-3" height="4" width="2" x="8" y="6"/><rect class="battery-bar-2" height="4" width="2" x="5" y="6"/><rect class="battery-bar-1" height="4" width="2" x="2" y="6"/></svg>` | ||||
| 
 | ||||
| const BATTERY_LEVEL = { | ||||
|     4: 'full', | ||||
|     3: 'medium', | ||||
|     2: 'low', | ||||
|     1: 'critical', | ||||
|     0: 'critical', | ||||
| } | ||||
| 
 | ||||
| const PairingMethod = { | ||||
|     DEFAULT: 'default', | ||||
|     FAST: 'fast' | ||||
| } | ||||
| 
 | ||||
| const WsCommand = { | ||||
|     GET_JOYCON_LIST: 'get_joycon_list', | ||||
|     CONNECT_JOYCON: 'connect_joycon', | ||||
|     DISCONNECT_JOYCON: 'disconnect_joycon', | ||||
|     UPDATE_JOYCON_STATE: 'update_joycon_state', | ||||
| } | ||||
| 
 | ||||
| const PairingState = { | ||||
|     IDLE: 0, | ||||
|     GETTING_TOKEN: 1, | ||||
|     PAIRING: 2, | ||||
|     CONNECTING: 3, | ||||
|     CONNECTED: 4, | ||||
|     DISCONNECTING: 5, | ||||
|     DISCONNECTED: 10, | ||||
| 
 | ||||
|     ERROR_JOYCON: 101, | ||||
|     ERROR_CONNECTION: 102, | ||||
|     ERROR_INVALID_PAIRING_CODE: 103, | ||||
|     ERROR_PUNCH_PAIRING: 104, | ||||
|     ERROR_HOLE_PUNCHING: 105, | ||||
|     ERROR_CONSOLE_CONNECTION: 106, | ||||
| } | ||||
| 
 | ||||
| const PairingStateMessage = { | ||||
|     [PairingState.IDLE]: 'Idle', | ||||
|     [PairingState.GETTING_TOKEN]: 'Getting auth token...', | ||||
|     [PairingState.PAIRING]: 'Sending pairing code...', | ||||
|     [PairingState.CONNECTING]: 'Connecting with console...', | ||||
|     [PairingState.CONNECTED]: 'Connected!', | ||||
|     [PairingState.DISCONNECTED]: 'Disconnected', | ||||
| 
 | ||||
|     [PairingState.ERROR_JOYCON]: 'Joy-Con problem!', | ||||
|     [PairingState.ERROR_CONNECTION]: 'Couldn\'t get auth token!', | ||||
|     [PairingState.ERROR_INVALID_PAIRING_CODE]: 'Invalid pairing code!', | ||||
|     [PairingState.ERROR_PUNCH_PAIRING]: 'Couldn\'t punch pairing!', | ||||
|     [PairingState.ERROR_HOLE_PUNCHING]: 'Couldn\'t connect with console!', | ||||
|     [PairingState.ERROR_CONSOLE_CONNECTION]: 'Couldn\'t connect with console!', | ||||
| } | ||||
| 
 | ||||
| class PairingMethodPicker extends Component { | ||||
|     constructor(props) { | ||||
|         super() | ||||
|         this.state = { | ||||
|             pairing_method: props.pairing_method, | ||||
|         } | ||||
| 
 | ||||
|         this.onChange = this.onChange.bind(this) | ||||
|     } | ||||
| 
 | ||||
|     onChange(e) { | ||||
|         const pairing_method = e.target.value | ||||
|         this.setState({ | ||||
|             pairing_method: pairing_method, | ||||
|         }) | ||||
| 
 | ||||
|         window.mitty.emit('update_method', pairing_method) | ||||
|     } | ||||
| 
 | ||||
|     render(props) { | ||||
|         return html` | ||||
|             <label for="stacked-state">Pairing Method</label> | ||||
|             <select id="stacked-state" onChange=${this.onChange} value=${props.pairing_method}> | ||||
|                 <option value="${PairingMethod.DEFAULT}">Default: All platforms (incl. Xbox Series/Stadia)</option> | ||||
|                 <option value="${PairingMethod.FAST}">Fast: Xbox One/PlayStation/Nintendo Switch</option> | ||||
|             </select> | ||||
|         ` | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| class PrivateIpAddress extends Component { | ||||
|     constructor(props) { | ||||
|         super(props) | ||||
| 
 | ||||
|         let lock_host = false | ||||
|         let host_ip_addr = props.host_ip_addr | ||||
|         let console_ip_addr = props.console_ip_addr | ||||
| 
 | ||||
|         let hostname = window.location.hostname | ||||
|         if (hostname.startsWith('192.168.')) { | ||||
|             host_ip_addr = hostname | ||||
|             lock_host = true | ||||
|         } | ||||
| 
 | ||||
|         this.state = { | ||||
|             host_ip_addr: host_ip_addr, | ||||
|             console_ip_addr: console_ip_addr, | ||||
|             lock_host: lock_host, | ||||
|         } | ||||
| 
 | ||||
|         this.onKeyPress = this.onKeyPress.bind(this) | ||||
|         this.onChange = this.onChange.bind(this) | ||||
|     } | ||||
| 
 | ||||
|     onChange(e) { | ||||
|         const key = this.props.pairing_method == PairingMethod.DEFAULT ? 'host_ip_addr' : 'console_ip_addr' | ||||
|         const value = e.target.value | ||||
|         this.setState({ | ||||
|             [key]: value, | ||||
|         }) | ||||
| 
 | ||||
|         window.mitty.emit('update_addr', value) | ||||
|     } | ||||
| 
 | ||||
|     onKeyPress(e) { | ||||
|         if (!/[0-9\.]/.test(e.key)) { | ||||
|             e.preventDefault() | ||||
|             return | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     componentDidMount() { | ||||
|         window.mitty.emit('update_addr', this.state.host_ip_addr) | ||||
|     } | ||||
| 
 | ||||
|     render(props, state) { | ||||
|         const pairing_method = props.pairing_method | ||||
|         const addr = pairing_method == PairingMethod.DEFAULT ? state.host_ip_addr : state.console_ip_addr | ||||
|         return html` | ||||
|             <label> | ||||
|                 ${pairing_method == PairingMethod.DEFAULT && html`Host's Private IP Address`} | ||||
|                 ${pairing_method == PairingMethod.FAST && html`Console's Private IP Address`} | ||||
|             </label> | ||||
| 
 | ||||
|             ${pairing_method == PairingMethod.DEFAULT && state.lock_host && html` | ||||
|                 <input readonly required id="ipAddr" type="text" size="15" placeholder="${addr}" /> | ||||
|             `}
 | ||||
| 
 | ||||
|             ${(pairing_method == PairingMethod.FAST || !state.lock_host) && html` | ||||
|                 <input required id="ipAddr" type="text" inputmode="decimal" size="15" maxlength="15" placeholder="192.168.x.x" pattern="^192\\.168\\.((\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.)(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])$" value=${addr} onKeyPress=${this.onKeyPress} onChange="${this.onChange}" /> | ||||
|             `}
 | ||||
| 
 | ||||
|         ` | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| class PairingCode extends Component { | ||||
|     constructor(props) { | ||||
|         super(props) | ||||
|         this.state = { | ||||
|             pairing_code: props.pairing_code, | ||||
|         } | ||||
| 
 | ||||
|         this.onChange = this.onChange.bind(this) | ||||
|     } | ||||
| 
 | ||||
|     onChange(e) { | ||||
|         const value = e.target.value | ||||
|         this.setState({ | ||||
|             pairing_code: value, | ||||
|         }) | ||||
| 
 | ||||
|         window.mitty.emit('update_code', value) | ||||
|     } | ||||
| 
 | ||||
|     render(props, state) { | ||||
|         return html` | ||||
|             <label>Pairing Code</label> | ||||
|             ${props.pairing_method == PairingMethod.DEFAULT && html` | ||||
|                 <input required id="pairingCode" type="text" inputmode="decimal" value=${state.pairing_code} placeholder="000000" maxlength="6" size="6" pattern="[0-9]{6}" onKeyPress=${(e) => !/[0-9]/.test(e.key) && e.preventDefault()} onChange=${this.onChange} /> | ||||
|             `}
 | ||||
|             ${props.pairing_method == PairingMethod.FAST && html` | ||||
|                 <input type="text" id="pairingCode" value="" readonly placeholder="Not Required" size="12" /> | ||||
|             `}
 | ||||
|         ` | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| class JoyCon extends Component { | ||||
|     constructor(props) { | ||||
|         super(props) | ||||
| 
 | ||||
|         this.connect = this.connect.bind(this) | ||||
|         this.disconnect = this.disconnect.bind(this) | ||||
|         this.onStateUpdated = this.onStateUpdated.bind(this) | ||||
| 
 | ||||
|         this.state = { | ||||
|             ...props.joycon, | ||||
|         } | ||||
| 
 | ||||
|         window.mitty.on('resp_' + WsCommand.DISCONNECT_JOYCON, this.onDisconnected) | ||||
|         window.mitty.on('resp_' + WsCommand.UPDATE_JOYCON_STATE, this.onStateUpdated) | ||||
|     } | ||||
| 
 | ||||
|     connect() { | ||||
|         window.mitty.emit('req_' + WsCommand.CONNECT_JOYCON, this.props.joycon.serial) | ||||
|     } | ||||
| 
 | ||||
|     disconnect() { | ||||
|         window.mitty.emit('req_' + WsCommand.DISCONNECT_JOYCON, this.props.joycon.serial) | ||||
|     } | ||||
| 
 | ||||
|     onStateUpdated(data) { | ||||
|         if (data['serial'] != this.props.joycon.serial) { | ||||
|             return | ||||
|         } | ||||
| 
 | ||||
|         const state = data['state'] | ||||
|         if (PairingStateMessage.hasOwnProperty(state)) { | ||||
|             this.setState({ | ||||
|                 ...data, | ||||
|             }) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     render(props, { name, state, pairing_code, is_left, color, battery_level }) { | ||||
|         const joyconState = state | ||||
|         const stateMessage = PairingStateMessage[joyconState] | ||||
|         let showButton = true | ||||
|         if ([PairingState.GETTING_TOKEN, PairingState.PAIRING, PairingState.CONNECTING].indexOf(joyconState) > -1) { | ||||
|             showButton = false | ||||
|         } | ||||
| 
 | ||||
|         let joyconSvg | ||||
|         if (is_left) { | ||||
|             joyconSvg = html`<svg class="joycon-color" viewBox="0 0 171 453" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd"><path d="M219.594 33.518v412.688c0 1.023-.506 1.797-1.797 1.797h-49.64c-51.68 0-85.075-45.698-85.075-85.075V114.987c0-57.885 56.764-84.719 84.719-84.719h48.79c2.486 0 3.003 1.368 3.003 3.25zm-32.123 105.087c0 17.589-14.474 32.062-32.063 32.062-17.589 0-32.062-14.473-32.062-32.062s14.473-32.063 32.062-32.063 32.063 14.474 32.063 32.063z" style="fill:${color};stroke:#000;stroke-width:8.33px" transform="translate(-65.902 -13.089)"/></svg>` | ||||
|         } else { | ||||
|             joyconSvg = html`<svg class="joycon-color" viewBox="0 0 171 453" xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd"><path d="M324.763 40.363v412.688c0 1.023.506 1.797 1.797 1.797h49.64c51.68 0 85.075-45.698 85.075-85.075V121.832c0-6.774-.777-13.123-2.195-19.054-10.696-44.744-57.841-65.665-82.524-65.665h-48.79c-2.486 0-3.003 1.368-3.003 3.25zm96 218.094c0 17.589-14.473 32.063-32.062 32.063s-32.063-14.474-32.063-32.063c0-17.589 14.474-32.062 32.063-32.062 17.589 0 32.062 14.473 32.062 32.062z" style="fill:${color};fill-rule:nonzero;stroke:#000;stroke-width:8.33px" transform="translate(-307.583 -19.934)"/></svg>` | ||||
|         } | ||||
| 
 | ||||
|         const batteryLevel = BATTERY_LEVEL[battery_level] | ||||
| 
 | ||||
|         return html` | ||||
|             <li> | ||||
|                 <div class="pure-g"> | ||||
| 
 | ||||
|                     <div class="pure-u-2-24 flex">${joyconSvg}</div> | ||||
|                     <div class="pure-u-12-24 joycon-info"> | ||||
|                         <div class="flex"> | ||||
|                             <span class="joycon-name">${name}</span> | ||||
|                             <span class="battery-level ${batteryLevel}">${SVG_BATTERY_LEVEL}</span> | ||||
|                         </div> | ||||
|                         <span class="joycon-state">${stateMessage}</span> | ||||
|                     </div> | ||||
|                     <div class="pure-u-4-24 flex"> | ||||
|                         ${pairing_code && html` | ||||
|                             <span class="pairing-code">${pairing_code}</span> | ||||
|                         `}
 | ||||
|                     </div> | ||||
|                     <div class="pure-u-6-24"> | ||||
|                         ${showButton && joyconState == PairingState.CONNECTED && html` | ||||
|                             <button type="button" onClick=${this.disconnect} class="pure-button pure-button-error">Disconnect</button> | ||||
|                         `}
 | ||||
|                         ${showButton && joyconState != PairingState.CONNECTED && html` | ||||
|                             <button type="button" onClick=${this.connect} class="pure-button pure-button-primary">Connect</button> | ||||
|                         `}
 | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </li> | ||||
|         ` | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| class JoyCons extends Component { | ||||
|     constructor() { | ||||
|         super() | ||||
|         this.state = { | ||||
|             isRefreshing: false, | ||||
|         } | ||||
| 
 | ||||
|         this.refreshJoyconList = this.refreshJoyconList.bind(this) | ||||
|     } | ||||
| 
 | ||||
|     refreshJoyconList() { | ||||
|         this.setState({ | ||||
|             isRefreshing: false, | ||||
|         }) | ||||
|         window.mitty.emit('req_' + WsCommand.GET_JOYCON_LIST) | ||||
|     } | ||||
| 
 | ||||
|     componentDidMount() { | ||||
|     } | ||||
| 
 | ||||
|     render(props, state) { | ||||
|         return html` | ||||
|             <div class="pure-g"> | ||||
|                 <h2 class="pure-u-18-24">Joy-Cons</h2> | ||||
|                     ${state.isRefreshing && html` | ||||
|                         <button type="button" disabled class="pure-button btn-refresh pure-u-6-24">Refresh</a> | ||||
|                     `}
 | ||||
|                     ${!state.isRefreshing && html` | ||||
|                         <button type="button" class="pure-button btn-refresh pure-u-6-24" onClick=${this.refreshJoyconList}>Refresh</button> | ||||
|                     `}
 | ||||
|             </div> | ||||
|             <div class="joycons-wrapper"> | ||||
|                 ${props.joycons.length == 0 && html` | ||||
|                     <p class="empty">No Joy-Cons found!</p> | ||||
|                 `}
 | ||||
| 
 | ||||
| 
 | ||||
|                 ${props.joycons.length > 0 && html` | ||||
|                     <ul class="joycons-list"> | ||||
|                         ${props.joycons.map(item => ( | ||||
|                             html`<${JoyCon} joycon=${item} key=${item.serial} />` | ||||
|                         ))} | ||||
|                     </ul> | ||||
|                 `}
 | ||||
|             </div> | ||||
|         ` | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| class App extends Component { | ||||
|     constructor(props) { | ||||
|         super() | ||||
| 
 | ||||
|         this.state = { | ||||
|             pairing_method: window.CONFIG.pairing_method, | ||||
|             host_ip_addr: window.CONFIG.host_ip_addr, | ||||
|             console_ip_addr: window.CONFIG.console_ip_addr, | ||||
|             pairing_code: window.CONFIG.pairing_code, | ||||
|             joycons: [], | ||||
|         } | ||||
| 
 | ||||
|         this.connectWs = this.connectWs.bind(this) | ||||
|         this.sendRequest = this.sendRequest.bind(this) | ||||
|         this.requestGetJoyconList = this.requestGetJoyconList.bind(this) | ||||
|         this.requestConnectJoycon = this.requestConnectJoycon.bind(this) | ||||
|         this.requestDisconnectJoycon = this.requestDisconnectJoycon.bind(this) | ||||
|         this.handleMethodChange = this.handleMethodChange.bind(this) | ||||
|         this.handleAddrChange = this.handleAddrChange.bind(this) | ||||
|         this.handleCodeChange = this.handleCodeChange.bind(this) | ||||
| 
 | ||||
|         window.mitty.on('req_' + WsCommand.GET_JOYCON_LIST, this.requestGetJoyconList) | ||||
|         window.mitty.on('req_' + WsCommand.CONNECT_JOYCON, this.requestConnectJoycon) | ||||
|         window.mitty.on('req_' + WsCommand.DISCONNECT_JOYCON, this.requestDisconnectJoycon) | ||||
|         window.mitty.on('update_method', this.handleMethodChange) | ||||
|         window.mitty.on('update_addr', this.handleAddrChange) | ||||
|         window.mitty.on('update_code', this.handleCodeChange) | ||||
|     } | ||||
| 
 | ||||
|     sendRequest(cmd, data) { | ||||
|         if (!data) { | ||||
|             data = {} | ||||
|         } | ||||
|         const msg = { | ||||
|             cmd: cmd, | ||||
|             data: data, | ||||
|         } | ||||
|         console.log('send', msg) | ||||
|         this.socket.send(JSON.stringify(msg)) | ||||
|     } | ||||
| 
 | ||||
|     requestGetJoyconList() { | ||||
|         this.sendRequest(WsCommand.GET_JOYCON_LIST); | ||||
|     } | ||||
| 
 | ||||
|     requestConnectJoycon(serial) { | ||||
|         const state = this.state | ||||
|         const pairing_method = state.pairing_method | ||||
|         let addr = pairing_method == PairingMethod.DEFAULT ? state.host_ip_addr : state.console_ip_addr | ||||
|         if (!addr.match(/^192\.168\.((\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.)(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$/)) { | ||||
|             alert('ERROR: Invalid IP address!') | ||||
|             document.getElementById('ipAddr').focus() | ||||
|             return | ||||
|         } | ||||
| 
 | ||||
|         if (pairing_method == PairingMethod.DEFAULT) { | ||||
|             const pairing_code = state.pairing_code | ||||
|             if (!pairing_code.match(/^\d{6}$/)) { | ||||
|                 alert('ERROR: Invalid pairing code!') | ||||
|                 document.getElementById('pairingCode').focus() | ||||
|                 return | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         this.sendRequest(WsCommand.CONNECT_JOYCON, { | ||||
|             pairing_method: state.pairing_method, | ||||
|             host_ip_addr: state.host_ip_addr, | ||||
|             console_ip_addr: state.console_ip_addr, | ||||
|             pairing_code: state.pairing_code, | ||||
|             joycon_serial: serial, | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     requestDisconnectJoycon(serial) { | ||||
|         this.sendRequest(WsCommand.DISCONNECT_JOYCON, { | ||||
|             joycon_serial: serial, | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     connectWs() { | ||||
|         const that = this | ||||
|         this.socket = new WebSocket('ws://' + window.location.host + '/ws') | ||||
| 
 | ||||
|         this.socket.onopen = function(e) { | ||||
|             console.log('[open] Connection established') | ||||
|             that.requestGetJoyconList() | ||||
|         } | ||||
| 
 | ||||
|         this.socket.onmessage = function(event) { | ||||
|             const msg = JSON.parse(event.data) | ||||
|             console.log(msg) | ||||
|             const cmd = msg['cmd'] | ||||
|             const shortCmd = msg['cmd'].slice(5)  // Remove "resp_" prefix
 | ||||
| 
 | ||||
|             switch (shortCmd) { | ||||
|                 case WsCommand.GET_JOYCON_LIST: | ||||
|                     that.setState({ | ||||
|                         joycons: msg['data'], | ||||
|                     }) | ||||
|                     break | ||||
| 
 | ||||
|                 default: | ||||
|                     window.mitty.emit(cmd, msg['data']) | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         this.socket.onclose = function(event) { | ||||
|             if (event.wasClean) { | ||||
|                 console.log(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`); | ||||
|             } else { | ||||
|                 console.log('[close] Connection died'); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         this.socket.onerror = function(error) { | ||||
|             console.log(`[error] ${error.message}`); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     handleMethodChange(pairing_method) { | ||||
|         this.setState({ | ||||
|             pairing_method: pairing_method, | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     handleAddrChange(addr) { | ||||
|         const key = this.state.pairing_method == PairingMethod.DEFAULT ? 'host_ip_addr' : 'console_ip_addr' | ||||
|         this.setState({ | ||||
|             [key]: addr, | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     handleCodeChange(pairing_code) { | ||||
|         this.setState({ | ||||
|             pairing_code: pairing_code, | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     componentDidMount() { | ||||
|         this.connectWs() | ||||
|     } | ||||
| 
 | ||||
|     render(props, state) { | ||||
|         return html` | ||||
|             <div class="container"> | ||||
|                 <div class="ascii"> | ||||
|                     <pre>     ░░  ░░░░░░  ░░    ░░ ░░░░░░   ░░░░░  ░░░    ░░  ░░░░░░ ░░░░░░░ | ||||
|      ▒▒ ▒▒    ▒▒  ▒▒  ▒▒  ▒▒   ▒▒ ▒▒   ▒▒ ▒▒▒▒   ▒▒ ▒▒      ▒▒ | ||||
|      ▒▒ ▒▒    ▒▒   ▒▒▒▒   ▒▒   ▒▒ ▒▒▒▒▒▒▒ ▒▒ ▒▒  ▒▒ ▒▒      ▒▒▒▒▒ | ||||
| ▓▓   ▓▓ ▓▓    ▓▓    ▓▓    ▓▓   ▓▓ ▓▓   ▓▓ ▓▓  ▓▓ ▓▓ ▓▓      ▓▓ | ||||
|  █████   ██████     ██    ██████  ██   ██ ██   ████  ██████ ███████ | ||||
|                     </pre> | ||||
|                 </div> | ||||
| 
 | ||||
|                 <form class="pure-form pure-form-stacked"> | ||||
|                     <fieldset> | ||||
|                         <div class="pure-g"> | ||||
|                             <h2 class="pure-u-1">Config</h2> | ||||
|                             <div class="pure-u-1"> | ||||
|                                 <${PairingMethodPicker} pairing_method=${state.pairing_method}/> | ||||
|                             </div> | ||||
|                             <div class="pure-u-1-2"> | ||||
|                                 <${PrivateIpAddress} pairing_method=${state.pairing_method} host_ip_addr=${state.host_ip_addr} console_ip_addr=${state.console_ip_addr} /> | ||||
|                             </div> | ||||
|                             <div class="pure-u-1-2"> | ||||
|                                 <${PairingCode} pairing_method=${state.pairing_method} pairing_code=${state.pairing_code} /> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </fieldset> | ||||
|                 </form> | ||||
| 
 | ||||
|                 <div class="pure-u-1 joycons"> | ||||
|                     <${JoyCons} pairing_method=${state.pairing_method} joycons=${state.joycons} /> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="footer"> | ||||
|                 <a href="https://github.com/redphx/joydance" target="_blank">${window.VERSION}</a> | ||||
|             </div> | ||||
|         ` | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| render(html`<${App} />`, document.body) | ||||
							
								
								
									
										1
									
								
								static/js/htm.module.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								static/js/htm.module.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| var n = function (t, s, r, e) {var u;s[0] = 0;for (var h = 1; h < s.length; h++) {var p = s[h++],a = s[h] ? (s[0] |= p ? 1 : 2, r[s[h++]]) : s[++h];3 === p ? e[0] = a : 4 === p ? e[1] = Object.assign(e[1] || {}, a) : 5 === p ? (e[1] = e[1] || {})[s[++h]] = a : 6 === p ? e[1][s[++h]] += a + "" : p ? (u = t.apply(a, n(t, a, r, ["", null])), e.push(u), a[0] ? s[0] |= 2 : (s[h - 2] = 0, s[h] = u)) : e.push(a);}return e;},t = new Map();export default function (s) {var r = t.get(this);return r || (r = new Map(), t.set(this, r)), (r = n(this, r.get(s) || (r.set(s, r = function (n) {for (var t, s, r = 1, e = "", u = "", h = [0], p = function (n) {1 === r && (n || (e = e.replace(/^\s*\n\s*|\s*\n\s*$/g, ""))) ? h.push(0, n, e) : 3 === r && (n || e) ? (h.push(3, n, e), r = 2) : 2 === r && "..." === e && n ? h.push(4, n, 0) : 2 === r && e && !n ? h.push(5, 0, !0, e) : r >= 5 && ((e || !n && 5 === r) && (h.push(r, 0, e, s), r = 6), n && (h.push(r, n, 0, s), r = 6)), e = "";}, a = 0; a < n.length; a++) {a && (1 === r && p(), p(a));for (var l = 0; l < n[a].length; l++) t = n[a][l], 1 === r ? "<" === t ? (p(), h = [h], r = 3) : e += t : 4 === r ? "--" === e && ">" === t ? (r = 1, e = "") : e = t + e[0] : u ? t === u ? u = "" : e += t : '"' === t || "'" === t ? u = t : ">" === t ? (p(), r = 1) : r && ("=" === t ? (r = 5, s = e, e = "") : "/" === t && (r < 5 || ">" === n[a][l + 1]) ? (p(), 3 === r && (h = h[0]), r = h, (h = h[0]).push(2, 0, r), r = 0) : " " === t || "\t" === t || "\n" === t || "\r" === t ? (p(), r = 2) : e += t), 3 === r && "!--" === e && (r = 4, h = h[0]);}return p(), h;}(s)), r), arguments, [])).length > 1 ? r : r[0];} | ||||
							
								
								
									
										1
									
								
								static/js/mitt.umd.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								static/js/mitt.umd.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | |||
| !function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e=e||self).mitt=n()}(this,function(){return function(e){return{all:e=e||new Map,on:function(n,t){var f=e.get(n);f?f.push(t):e.set(n,[t])},off:function(n,t){var f=e.get(n);f&&(t?f.splice(f.indexOf(t)>>>0,1):e.set(n,[]))},emit:function(n,t){var f=e.get(n);f&&f.slice().map(function(e){e(t)}),(f=e.get("*"))&&f.slice().map(function(e){e(n,t)})}}}}); | ||||
							
								
								
									
										1
									
								
								static/js/preact.module.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								static/js/preact.module.js
									
										
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue